Compare commits
91 Commits
e02ccc2b60
...
master
Author | SHA1 | Date | |
---|---|---|---|
34809d4618 | |||
843d9c3509 | |||
1716e0b5a8 | |||
254b885648 | |||
e2c0098eb9 | |||
2fc3eb50f5 | |||
fc44151f2b | |||
f8a7c5f1e7 | |||
a9ea8c3038 | |||
e611d06987 | |||
638e57bb7f | |||
0009dd22cd | |||
79474b14d2 | |||
ee4812bdbb | |||
7d2a5e7862 | |||
c09362c75e | |||
457262049d | |||
ebbbc3a1f0 | |||
8c0db895da | |||
5943b10d16 | |||
cda7ebadcc | |||
dbdf1eeb7c | |||
500163d8f4 | |||
9374f8d280 | |||
f5194334de | |||
21777d4b9e | |||
3b4cf63c48 | |||
e2b2ab466d | |||
50e21896ba | |||
07af67b33f | |||
f4d0ccca51 | |||
51bc0bd6e8 | |||
c229b14779 | |||
49942b35da | |||
0ffe3198e6 | |||
ace34fc0e8 | |||
27c444f3b4 | |||
c589da2a14 | |||
3fe4a1b244 | |||
1925dd9b36 | |||
d637b92f6c | |||
af4bab0d12 | |||
44dc690736 | |||
c9af5ad308 | |||
27403a6e20 | |||
38a42dcca0 | |||
5782046b7a | |||
2b407d7f27 | |||
8f5f880754 | |||
3d92c3afb6 | |||
5e1f98ab70 | |||
276f5b2dc1 | |||
9c72b8cda4 | |||
ee023b5d2c | |||
974347cbde | |||
e6b77bcad6 | |||
36eb1227cf | |||
4be37945cb | |||
3e6cf96040 | |||
d1a04a7a66 | |||
fcac9af7d1 | |||
e16487431d | |||
50f5ab671a | |||
5a04052f8e | |||
c86855ac16 | |||
001239fe57 | |||
0c7fb9b370 | |||
f416e30d45 | |||
e271ac2964 | |||
2bb09ad42b | |||
4f55890092 | |||
76470b963e | |||
76fc673e98 | |||
307b5132df | |||
ac52e024f3 | |||
bb6d221ced | |||
2acfa53b63 | |||
640b255e1d | |||
ed0db5391d | |||
cef4af80f0 | |||
7f56645ba5 | |||
827c13887c | |||
0ff2ae1296 | |||
e7b528718c | |||
d411618e63 | |||
decf302851 | |||
d3097781bc | |||
2942a030a6 | |||
69af006001 | |||
c60fb613d4 | |||
33e4e053cf |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
/.idea
|
||||
/*/target
|
||||
dependency-reduced-pom.xml
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
*.iml
|
@@ -9,17 +9,18 @@ that are detailed in their respective Readme file (if any).
|
||||
- `pandalib-util` General purpose utility and helper classes;
|
||||
- `pandalib-chat` A chat API working on top of the Adventure API;
|
||||
- `pandalib-db` An ORM working with a MySQL server through JDBC;
|
||||
- `pandalib-bungee` Utility and helper classes to use in Bungeecord plugins. Also provides platform implementation for `pandalib-players` and `pandalib-commands`;
|
||||
- `pandalib-bungee` Utility and helper classes to use in BungeeCord plugins. Also provides platform implementation for `pandalib-players` and `pandalib-commands`;
|
||||
- `pandalib-paper` Utility and helper classes to use in Spigot/Paper plugins. Also provides platform implementation for `pandalib-players` and `pandalib-commands`;
|
||||
- `pandalib-reflect` A reflection wrapper to make reflective operation easier;
|
||||
- `pandalib-permissions` A general purpose permission system;
|
||||
- `pandalib-bungee-permissions` Integration of the permission system `pandalib-permissions` into Bungeecord;
|
||||
- `pandalib-bungee-permissions` Integration of the permission system `pandalib-permissions` into BungeeCord;
|
||||
- `pandalib-paper-permissions` Integration of the permission system `pandalib-permissions` into Bukkit, Vault and WEPIF permission systems;
|
||||
- `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-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-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;
|
||||
|
||||
### Use in your projects
|
||||
|
1
pandalib-bungee-chat/.gitignore
vendored
Normal file
1
pandalib-bungee-chat/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
44
pandalib-bungee-chat/pom.xml
Normal file
44
pandalib-bungee-chat/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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-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.FormattableChat;
|
||||
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 FormattableChat} from the provided Bungee {@link BaseComponent}.
|
||||
* @param c the {@link BaseComponent}.
|
||||
* @return a new {@link FormattableChat}.
|
||||
*/
|
||||
public static FormattableChat chatComponent(BaseComponent c) {
|
||||
return Chat.chatComponent(toAdventure(c));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormattableChat} from the provided Bungee {@link BaseComponent BaseComponent[]}.
|
||||
* @param c the array of {@link BaseComponent}.
|
||||
* @return a new {@link FormattableChat}.
|
||||
*/
|
||||
public static FormattableChat 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() {}
|
||||
}
|
@@ -25,7 +25,6 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class PandalibBungeePermissions implements Listener {
|
||||
|
||||
|
||||
/**
|
||||
* Registers event listener to redirect permission checks to {@code pandalib-permissions}.
|
||||
* @param bungeePlugin a BungeeCord plugin.
|
||||
@@ -35,6 +34,8 @@ public class PandalibBungeePermissions implements Listener {
|
||||
}
|
||||
|
||||
|
||||
private PandalibBungeePermissions() {}
|
||||
|
||||
/**
|
||||
* Event handler called when a plugin asks if a player has a permission.
|
||||
* @param event the permission check event.
|
||||
|
@@ -33,7 +33,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-chat</artifactId>
|
||||
<artifactId>pandalib-bungee-chat</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package fr.pandacube.lib.bungee;
|
||||
|
||||
import fr.pandacube.lib.bungee.util.BungeeDailyLogRotateFileHandler;
|
||||
import fr.pandacube.lib.bungee.util.PluginMessagePassthrough;
|
||||
import fr.pandacube.lib.bungee.util.PluginMessagePassThrough;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
/**
|
||||
@@ -24,7 +24,7 @@ public class PandaLibBungee {
|
||||
* Method to be called in {@link Plugin#onEnable()} method.
|
||||
*/
|
||||
public static void onEnable() {
|
||||
PluginMessagePassthrough.init(plugin);
|
||||
PluginMessagePassThrough.init(plugin);
|
||||
BungeeDailyLogRotateFileHandler.init(true);
|
||||
}
|
||||
|
||||
@@ -43,4 +43,7 @@ public class PandaLibBungee {
|
||||
public static Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private PandaLibBungee() {}
|
||||
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class that holds the configuration varables for {@link BungeeBackupManager}.
|
||||
* Class that holds the configuration variables for {@link BungeeBackupManager}.
|
||||
*/
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
public class BungeeBackupConfig {
|
||||
@@ -35,4 +35,11 @@ public class BungeeBackupConfig {
|
||||
* A list of ignored files or directory in the workdir to exclude from the backup.
|
||||
*/
|
||||
public List<String> workdirIgnoreList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link BungeeBackupConfig}.
|
||||
*/
|
||||
public BungeeBackupConfig() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -7,14 +7,14 @@ import fr.pandacube.lib.core.backup.RotatedLogsBackupProcess;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Handles the backup processes for a Bungeecord instance.
|
||||
* Handles the backup processes for a BungeeCord instance.
|
||||
*/
|
||||
public class BungeeBackupManager extends BackupManager {
|
||||
|
||||
BungeeBackupConfig config;
|
||||
|
||||
/**
|
||||
* Instanciate a new {@link BungeeBackupManager}.
|
||||
* Creates a new {@link BungeeBackupManager}.
|
||||
* @param config the configuration.
|
||||
*/
|
||||
public BungeeBackupManager(BungeeBackupConfig config) {
|
||||
|
@@ -6,7 +6,7 @@ import java.io.File;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* The backup process responsible for the working directory of the current Bungeecord instance.
|
||||
* The backup process responsible for the working directory of the current BungeeCord instance.
|
||||
*/
|
||||
public class BungeeWorkdirProcess extends BackupProcess {
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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 net.kyori.adventure.text.ComponentLike;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
@@ -71,6 +71,6 @@ public class BungeeBrigadierDispatcher extends BrigadierDispatcher<CommandSender
|
||||
|
||||
@Override
|
||||
protected void sendSenderMessage(CommandSender sender, ComponentLike message) {
|
||||
sender.sendMessage(Chat.toBungee(message.asComponent()));
|
||||
sender.sendMessage(ChatBungee.toBungee(message.asComponent()));
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
||||
|
||||
/**
|
||||
* Represents any player on a Bungeecord proxy, either offline or online.
|
||||
* Represents any player on a BungeeCord proxy, either offline or online.
|
||||
*/
|
||||
public interface BungeeOffPlayer extends AbstractOffPlayer {
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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.players.standalone.AbstractOnlinePlayer;
|
||||
import fr.pandacube.lib.reflect.Reflect;
|
||||
@@ -20,7 +20,7 @@ import net.md_5.bungee.protocol.packet.PluginMessage;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Represents any online player on a Bungeecord proxy.
|
||||
* Represents any online player on a BungeeCord proxy.
|
||||
*/
|
||||
public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlayer {
|
||||
|
||||
@@ -84,13 +84,13 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye
|
||||
|
||||
@Override
|
||||
default void sendMessage(Component message) {
|
||||
getBungeeProxiedPlayer().sendMessage(Chat.toBungee(message));
|
||||
getBungeeProxiedPlayer().sendMessage(ChatBungee.toBungee(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendTitle(Component title, Component subtitle, int fadeIn, int stay, int fadeOut) {
|
||||
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)
|
||||
.send(getBungeeProxiedPlayer());
|
||||
}
|
||||
|
@@ -19,16 +19,16 @@ import net.md_5.bungee.event.EventHandler;
|
||||
* <p>
|
||||
* Usage example, in your plugin init code:
|
||||
* <pre>{@code
|
||||
* PluginMessagePassthrough.init(yourPluginInstance);
|
||||
* PluginMessagePassthrough.register("worldedit:cui"); // plugin message used by WorldEdit
|
||||
* PluginMessagePassThrough.init(yourPluginInstance);
|
||||
* PluginMessagePassThrough.register("worldedit:cui"); // plugin message used by WorldEdit
|
||||
* }</pre>
|
||||
*/
|
||||
public class PluginMessagePassthrough implements Listener {
|
||||
public class PluginMessagePassThrough implements Listener {
|
||||
private static final List<String> channels = Collections.synchronizedList(new ArrayList<>());
|
||||
private static final PluginMessagePassthrough instance = new PluginMessagePassthrough();
|
||||
private static final PluginMessagePassThrough instance = new PluginMessagePassThrough();
|
||||
|
||||
/**
|
||||
* Initialize the {@link PluginMessagePassthrough}.
|
||||
* Initialize the {@link PluginMessagePassThrough}.
|
||||
* It registers the required event listener.
|
||||
* @param plugin the plugin instance.
|
||||
*/
|
||||
@@ -92,7 +92,7 @@ public class PluginMessagePassthrough implements Listener {
|
||||
}
|
||||
|
||||
|
||||
private PluginMessagePassthrough() { }
|
||||
private PluginMessagePassThrough() { }
|
||||
|
||||
|
||||
/**
|
@@ -30,8 +30,13 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-platform-bungeecord</artifactId>
|
||||
<version>4.3.0</version>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>4.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>4.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
@@ -46,17 +51,10 @@
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>${bungeecord.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<version>${gson.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
@@ -5,6 +5,8 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentBuilder;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslationArgument;
|
||||
import net.kyori.adventure.text.TranslationArgumentLike;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.event.HoverEventSource;
|
||||
@@ -14,15 +16,13 @@ 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.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
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 java.awt.*;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.UnaryOperator;
|
||||
@@ -35,8 +35,8 @@ import java.util.function.UnaryOperator;
|
||||
* This class implements {@link ComponentLike} and {@link HoverEventSource} so they can be used directly in
|
||||
* Adventure API and its implementation without using the final methods of this builder.
|
||||
* <p>
|
||||
* The unique possible concrete subclass of this class, {@link FormatableChat}, takes care of the formatting of the
|
||||
* built component. The rationale for this design is explained in the documentation of {@link FormatableChat}.
|
||||
* The unique possible concrete subclass of this class, {@link FormattableChat}, takes care of the formatting of the
|
||||
* built component. The rationale for this design is explained in the documentation of {@link FormattableChat}.
|
||||
*/
|
||||
public abstract sealed class Chat extends ChatStatic implements HoverEventSource<Component>, ComponentLike {
|
||||
|
||||
@@ -65,26 +65,10 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* Builds the component into Adventure Component instance.
|
||||
* @return the {@link Component} built from this {@link Chat} component.
|
||||
*/
|
||||
public Component getAdv() {
|
||||
public Component get() {
|
||||
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()
|
||||
.hexColors()
|
||||
.useUnusualXRepeatedCharacterHexFormat()
|
||||
@@ -95,7 +79,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @return the legacy text. RGB colors are in BungeeCord format.
|
||||
*/
|
||||
public String getLegacyText() {
|
||||
return LEGACY_SERIALIZER_BUNGEE_FRIENDLY.serialize(getAdv());
|
||||
return LEGACY_SERIALIZER_BUNGEE_FRIENDLY.serialize(get());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,12 +87,12 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @return the plain text of this component.
|
||||
*/
|
||||
public String getPlainText() {
|
||||
return PlainTextComponentSerializer.plainText().serializeOr(getAdv(), "");
|
||||
return PlainTextComponentSerializer.plainText().serializeOr(get(), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HoverEvent<Component> asHoverEvent(@NotNull UnaryOperator<Component> op) {
|
||||
return HoverEvent.showText(op.apply(getAdv()));
|
||||
return HoverEvent.showText(op.apply(get()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +101,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Component asComponent() {
|
||||
return getAdv();
|
||||
return get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,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.
|
||||
*/
|
||||
public Component getAsDownSampledColorsComponent() {
|
||||
String json = GsonComponentSerializer.colorDownsamplingGson().serialize(getAdv());
|
||||
String json = GsonComponentSerializer.colorDownsamplingGson().serialize(get());
|
||||
return GsonComponentSerializer.gson().deserialize(json);
|
||||
}
|
||||
|
||||
@@ -142,7 +126,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @return the MiniMessage representation if this {@link Chat} component.
|
||||
*/
|
||||
public String getMiniMessage() {
|
||||
return MiniMessage.miniMessage().serialize(getAdv());
|
||||
return MiniMessage.miniMessage().serialize(get());
|
||||
}
|
||||
|
||||
|
||||
@@ -182,15 +166,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
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.
|
||||
* @param comp the component to append.
|
||||
@@ -205,15 +180,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -330,7 +296,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param key the keybinding to display.
|
||||
* @return this.
|
||||
*/
|
||||
public Chat thenKeyBind(String key) { return then(keybind(key)); }
|
||||
public Chat thenKeyBind(String key) { return then(keyBind(key)); }
|
||||
|
||||
/**
|
||||
* Appends a component with the provided score name and objective.
|
||||
@@ -488,69 +454,34 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* 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
|
||||
* @return a new {@link FormattableChat} filling a chat line with the configured decoration character
|
||||
* and color and a left-aligned text.
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* @return a new {@link FormattableChat} filling a chat line with the configured decoration character
|
||||
* and color and a right-aligned text.
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* @return a new {@link FormattableChat} filling a chat line with the configured decoration character
|
||||
* and color and a centered text.
|
||||
*/
|
||||
public Chat thenCenterText(ComponentLike centerText) {
|
||||
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.
|
||||
* @return a new {@link FormatableChat} filling a chat line with a decoration character and color.
|
||||
* @return a new {@link FormattableChat} filling a chat line with a decoration character and color.
|
||||
*/
|
||||
public Chat thenFilledLine() { return then(filledLine(console)); }
|
||||
|
||||
@@ -589,10 +520,10 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* .thenText("!"); // short for .then(Chat.text("!"))
|
||||
* // the red color for "!" is not needed because the parent component is already red.
|
||||
* }</pre>
|
||||
* When calling {@link #then(Component) #then(...)} on a {@link FormatableChat}, the method returns itself, cast
|
||||
* When calling {@link #then(Component) #then(...)} on a {@link FormattableChat}, the method returns itself, cast
|
||||
* to {@link Chat}, to prevent future formatting (that the programmer would think it formats the previously appended
|
||||
* subcomponent). If the formatting of the currently built component is needed, since {@link Chat} is a sealed
|
||||
* class which only subclass is {@link FormatableChat}, you can cast the builder, and use the format methods again.
|
||||
* class which only subclass is {@link FormattableChat}, you can cast the builder, and use the format methods again.
|
||||
* <pre>{@code
|
||||
* Chat component = Chat.text("Hello ").red()
|
||||
* .then(Chat.text("world").darkRed().bold())
|
||||
@@ -601,8 +532,8 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* ((FormatableChat)component).underlined(); // this will not format only the last appended text.
|
||||
* }</pre>
|
||||
*/
|
||||
public static final class FormatableChat extends Chat {
|
||||
/* package */ FormatableChat(ComponentBuilder<?, ?> c) {
|
||||
public static final class FormattableChat extends Chat {
|
||||
/* package */ FormattableChat(ComponentBuilder<?, ?> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@@ -612,177 +543,180 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param c true for console, false for game UI.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat console(boolean c) { console = c; return this; }
|
||||
public FormattableChat console(boolean c) { console = c; return this; }
|
||||
/**
|
||||
* Configure the width of the line.
|
||||
* @param w the width to consider when rendering the line. In pixel for game UI rendering, n character for
|
||||
* console rendering.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat maxWidth(int w) { maxWidth = w; return this; }
|
||||
public FormattableChat maxWidth(int w) { maxWidth = w; return this; }
|
||||
|
||||
/**
|
||||
* Sets the color of this component.
|
||||
* @param c the color.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat color(TextColor c) { builder.color(c); return this; }
|
||||
public FormattableChat 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())); }
|
||||
public FormattableChat color(Color c) { return color(c == null ? null : TextColor.color(c.getRGB())); }
|
||||
/**
|
||||
* Sets the color of this component.
|
||||
* @param c the color.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat color(Color c) { return color(c == null ? null : TextColor.color(c.getRGB())); }
|
||||
/**
|
||||
* Sets the color of this component.
|
||||
* @param c the color.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat color(String c) { return color(c == null ? null : ChatColor.of(c)); }
|
||||
public FormattableChat 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#BLACK}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat black() { return color(NamedTextColor.BLACK); }
|
||||
public FormattableChat black() { return color(NamedTextColor.BLACK); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#DARK_BLUE}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat darkBlue() { return color(NamedTextColor.DARK_BLUE); }
|
||||
public FormattableChat darkBlue() { return color(NamedTextColor.DARK_BLUE); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#DARK_GREEN}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat darkGreen() { return color(NamedTextColor.DARK_GREEN); }
|
||||
public FormattableChat darkGreen() { return color(NamedTextColor.DARK_GREEN); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#DARK_AQUA}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat darkAqua() { return color(NamedTextColor.DARK_AQUA); }
|
||||
public FormattableChat darkAqua() { return color(NamedTextColor.DARK_AQUA); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#DARK_RED}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat darkRed() { return color(NamedTextColor.DARK_RED); }
|
||||
public FormattableChat darkRed() { return color(NamedTextColor.DARK_RED); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#DARK_PURPLE}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat darkPurple() { return color(NamedTextColor.DARK_PURPLE); }
|
||||
public FormattableChat darkPurple() { return color(NamedTextColor.DARK_PURPLE); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#GOLD}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat gold() { return color(NamedTextColor.GOLD); }
|
||||
public FormattableChat gold() { return color(NamedTextColor.GOLD); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#GRAY}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat gray() { return color(NamedTextColor.GRAY); }
|
||||
public FormattableChat gray() { return color(NamedTextColor.GRAY); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#DARK_GRAY}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat darkGray() { return color(NamedTextColor.DARK_GRAY); }
|
||||
public FormattableChat darkGray() { return color(NamedTextColor.DARK_GRAY); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#BLUE}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat blue() { return color(NamedTextColor.BLUE); }
|
||||
public FormattableChat blue() { return color(NamedTextColor.BLUE); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#GREEN}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat green() { return color(NamedTextColor.GREEN); }
|
||||
public FormattableChat green() { return color(NamedTextColor.GREEN); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#AQUA}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat aqua() { return color(NamedTextColor.AQUA); }
|
||||
public FormattableChat aqua() { return color(NamedTextColor.AQUA); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#RED}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat red() { return color(NamedTextColor.RED); }
|
||||
public FormattableChat red() { return color(NamedTextColor.RED); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#LIGHT_PURPLE}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat lightPurple() { return color(NamedTextColor.LIGHT_PURPLE); }
|
||||
public FormattableChat lightPurple() { return color(NamedTextColor.LIGHT_PURPLE); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#YELLOW}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat yellow() { return color(NamedTextColor.YELLOW); }
|
||||
public FormattableChat yellow() { return color(NamedTextColor.YELLOW); }
|
||||
/**
|
||||
* Sets the color of this component to {@link NamedTextColor#WHITE}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat white() { return color(NamedTextColor.WHITE); }
|
||||
public FormattableChat white() { return color(NamedTextColor.WHITE); }
|
||||
|
||||
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#successColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat successColor() { return color(ChatConfig.successColor); }
|
||||
public FormattableChat successColor() { return color(ChatConfig.successColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#failureColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat failureColor() { return color(ChatConfig.failureColor); }
|
||||
public FormattableChat failureColor() { return color(ChatConfig.failureColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#infoColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat infoColor() { return color(ChatConfig.infoColor); }
|
||||
public FormattableChat infoColor() { return color(ChatConfig.infoColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#warningColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat warningColor() { return color(ChatConfig.warningColor); }
|
||||
public FormattableChat warningColor() { return color(ChatConfig.warningColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#dataColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat dataColor() { return color(ChatConfig.dataColor); }
|
||||
public FormattableChat dataColor() { return color(ChatConfig.dataColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#decorationColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat decorationColor() { return color(ChatConfig.decorationColor); }
|
||||
public FormattableChat decorationColor() { return color(ChatConfig.decorationColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#urlColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat urlColor() { return color(ChatConfig.urlColor); }
|
||||
public FormattableChat urlColor() { return color(ChatConfig.urlColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#commandColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat commandColor() { return color(ChatConfig.commandColor); }
|
||||
public FormattableChat commandColor() { return color(ChatConfig.commandColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#highlightedCommandColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat highlightedCommandColor() { return color(ChatConfig.highlightedCommandColor); }
|
||||
public FormattableChat highlightedCommandColor() { return color(ChatConfig.highlightedCommandColor); }
|
||||
/**
|
||||
* Sets the color of this component to {@link ChatConfig#broadcastColor}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat broadcastColor() { return color(ChatConfig.broadcastColor); }
|
||||
public FormattableChat broadcastColor() { return color(ChatConfig.broadcastColor); }
|
||||
|
||||
|
||||
private FormatableChat setStyle(Consumer<Style.Builder> styleOp) { builder.style(styleOp); return this; }
|
||||
private FormatableChat setDecoration(TextDecoration deco, Boolean state) {
|
||||
private FormattableChat setStyle(Consumer<Style.Builder> styleOp) { builder.style(styleOp); return this; }
|
||||
private FormattableChat setDecoration(TextDecoration deco, Boolean state) {
|
||||
return setStyle(b -> b.decoration(deco, State.byBoolean(state)));
|
||||
}
|
||||
|
||||
@@ -792,56 +726,56 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param b true to enable, false to disable, or null to inherit from parent.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat bold(Boolean b) { return setDecoration(TextDecoration.BOLD, b); }
|
||||
public FormattableChat bold(Boolean b) { return setDecoration(TextDecoration.BOLD, b); }
|
||||
/**
|
||||
* Enables the bold status of this component.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat bold() { return bold(true); }
|
||||
public FormattableChat bold() { return bold(true); }
|
||||
/**
|
||||
* Sets the italic status of this component.
|
||||
* @param i true to enable, false to disable, or null to inherit from parent.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat italic(Boolean i) { return setDecoration(TextDecoration.ITALIC, i); }
|
||||
public FormattableChat italic(Boolean i) { return setDecoration(TextDecoration.ITALIC, i); }
|
||||
/**
|
||||
* Enables the italic status of this component.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat italic() { return italic(true); }
|
||||
public FormattableChat italic() { return italic(true); }
|
||||
/**
|
||||
* Sets the underlined status of this component.
|
||||
* @param u true to enable, false to disable, or null to inherit from parent.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat underlined(Boolean u) { return setDecoration(TextDecoration.UNDERLINED, u); }
|
||||
public FormattableChat underlined(Boolean u) { return setDecoration(TextDecoration.UNDERLINED, u); }
|
||||
/**
|
||||
* Enables the underlined status of this component.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat underlined() { return underlined(true); }
|
||||
public FormattableChat underlined() { return underlined(true); }
|
||||
/**
|
||||
* Sets the strikethrough status of this component.
|
||||
* @param s true to enable, false to disable, or null to inherit from parent.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat strikethrough(Boolean s) { return setDecoration(TextDecoration.STRIKETHROUGH, s); }
|
||||
public FormattableChat strikethrough(Boolean s) { return setDecoration(TextDecoration.STRIKETHROUGH, s); }
|
||||
/**
|
||||
* Enables the strikethrough status of this component.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat strikethrough() { return strikethrough(true); }
|
||||
public FormattableChat strikethrough() { return strikethrough(true); }
|
||||
/**
|
||||
* Sets the obfuscated status of this component.
|
||||
* @param o true to enable, false to disable, or null to inherit from parent.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat obfuscated(Boolean o) { return setDecoration(TextDecoration.OBFUSCATED, o); }
|
||||
public FormattableChat obfuscated(Boolean o) { return setDecoration(TextDecoration.OBFUSCATED, o); }
|
||||
/**
|
||||
* Enables the obfuscated status of this component.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat obfuscated() { return obfuscated(true); }
|
||||
public FormattableChat obfuscated() { return obfuscated(true); }
|
||||
|
||||
|
||||
/**
|
||||
@@ -849,7 +783,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param f the font namespaced key.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat font(Key f) { return setStyle(s -> s.font(f)); }
|
||||
public FormattableChat font(Key f) { return setStyle(s -> s.font(f)); }
|
||||
|
||||
|
||||
/**
|
||||
@@ -857,7 +791,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param i the text to insert.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat shiftClickInsertion(String i) { builder.insertion(i); return this; }
|
||||
public FormattableChat shiftClickInsertion(String i) { builder.insertion(i); return this; }
|
||||
|
||||
|
||||
/**
|
||||
@@ -865,37 +799,37 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param e the {@link ClickEvent}.
|
||||
* @return this.
|
||||
*/
|
||||
private FormatableChat click(ClickEvent e) { builder.clickEvent(e); return this; }
|
||||
private FormattableChat click(ClickEvent e) { builder.clickEvent(e); return this; }
|
||||
/**
|
||||
* Configure this component to execute the specified command when clicked.
|
||||
* @param cmdWithSlash the command to execute.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat clickCommand(String cmdWithSlash) { return click(ClickEvent.runCommand(cmdWithSlash)); }
|
||||
public FormattableChat clickCommand(String cmdWithSlash) { return click(ClickEvent.runCommand(cmdWithSlash)); }
|
||||
/**
|
||||
* Configure this component to insert in the chat-box the specified command when clicked.
|
||||
* @param cmdWithSlash the command to suggest.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat clickSuggest(String cmdWithSlash) { return click(ClickEvent.suggestCommand(cmdWithSlash)); }
|
||||
public FormattableChat clickSuggest(String cmdWithSlash) { return click(ClickEvent.suggestCommand(cmdWithSlash)); }
|
||||
/**
|
||||
* Configure this component to copy into clipboard the specified text when clicked.
|
||||
* @param value the text to copy.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat clickClipboard(String value) { return click(ClickEvent.copyToClipboard(value)); }
|
||||
public FormattableChat clickClipboard(String value) { return click(ClickEvent.copyToClipboard(value)); }
|
||||
/**
|
||||
* Configure this component to open the specified URL when clicked.
|
||||
* @param url the URL to open.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat clickURL(String url) { return click(ClickEvent.openUrl(url)); }
|
||||
public FormattableChat clickURL(String url) { return click(ClickEvent.openUrl(url)); }
|
||||
/**
|
||||
* Configure this component to change the page of the opened book when clicked.
|
||||
* @param page the page to go to.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat clickBookPage(int page) { return click(ClickEvent.changePage(page)); }
|
||||
public FormattableChat clickBookPage(int page) { return click(ClickEvent.changePage(page)); }
|
||||
|
||||
|
||||
/**
|
||||
@@ -903,43 +837,31 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
* @param e the {@link HoverEventSource}.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat hover(HoverEventSource<?> e) { builder.hoverEvent(e); return this; }
|
||||
public FormattableChat hover(HoverEventSource<?> e) { builder.hoverEvent(e); return this; }
|
||||
/**
|
||||
* Configure this component to show the provided component when hovered.
|
||||
* @param v the component to show.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat hover(Component v) { return hover((HoverEventSource<Component>) v); }
|
||||
public FormattableChat hover(Component v) { return hover((HoverEventSource<Component>) v); }
|
||||
/**
|
||||
* Configure this component to show the provided component when hovered.
|
||||
* @param v the component to show.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat hover(Chat v) { return hover((HoverEventSource<Component>) v); }
|
||||
public FormattableChat hover(Chat v) { return hover((HoverEventSource<Component>) v); }
|
||||
/**
|
||||
* Configure this component to show the provided component when hovered.
|
||||
* @param v the component to show.
|
||||
* @return this.
|
||||
*/
|
||||
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)); }
|
||||
public FormattableChat hover(ComponentLike v) { return hover(v.asComponent()); }
|
||||
/**
|
||||
* Configure this component to show the provided legacy text when hovered.
|
||||
* @param legacyText the legacy text to show.
|
||||
* @return this.
|
||||
*/
|
||||
public FormatableChat hover(String legacyText) { return hover(legacyText(legacyText)); }
|
||||
public FormattableChat hover(String legacyText) { return hover(legacyText(legacyText)); }
|
||||
|
||||
}
|
||||
|
||||
@@ -961,7 +883,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getAdv().hashCode();
|
||||
return get().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -972,71 +894,52 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
|
||||
|
||||
|
||||
/* package */ static ComponentLike filterObjToComponentLike(Object v) {
|
||||
return switch (v) {
|
||||
case ComponentLike componentLike -> componentLike;
|
||||
case null, default -> Component.text(Objects.toString(v));
|
||||
};
|
||||
}
|
||||
|
||||
/* package */ static ComponentLike[] filterObjToComponentLike(Object[] values) {
|
||||
if (values == null)
|
||||
return null;
|
||||
ComponentLike[] ret = new ComponentLike[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
Object v = values[i];
|
||||
if (v instanceof BaseComponent[])
|
||||
ret[i] = toAdventure((BaseComponent[]) v);
|
||||
else if (v instanceof BaseComponent)
|
||||
ret[i] = toAdventure((BaseComponent) v);
|
||||
else if (v instanceof ComponentLike)
|
||||
ret[i] = (ComponentLike) v;
|
||||
else
|
||||
ret[i] = Component.text(Objects.toString(v));
|
||||
ret[i] = filterObjToComponentLike(values[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 });
|
||||
/* package */ static TranslationArgumentLike[] filterObjToTranslationArgumentLike(Object[] values) {
|
||||
if (values == null)
|
||||
return null;
|
||||
TranslationArgumentLike[] ret = new TranslationArgumentLike[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
Object v = values[i];
|
||||
if (v instanceof Number n)
|
||||
ret[i] = TranslationArgument.numeric(n);
|
||||
else if (v instanceof Boolean b)
|
||||
ret[i] = TranslationArgument.bool(b);
|
||||
else
|
||||
ret[i] = TranslationArgument.component(filterObjToComponentLike(values[i]));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* This is useful for item lores that defaults to italic in the game UI.
|
||||
* This is useful for item lore that defaults to italic in the game UI.
|
||||
* @param c the {@link Chat} in which to set the italic property if needed.
|
||||
* @return the provided {@link Chat} instance.
|
||||
*/
|
||||
public static Chat italicFalseIfNotSet(Chat c) {
|
||||
c.builder.style(b -> {
|
||||
if (b.build().decoration(TextDecoration.ITALIC) == State.NOT_SET) {
|
||||
((FormatableChat) c).italic(false);
|
||||
((FormattableChat) c).italic(false);
|
||||
}
|
||||
});
|
||||
return c;
|
||||
|
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
* A custom gradient with at least 2 colors in it.
|
||||
*/
|
||||
public class ChatColorGradient {
|
||||
|
||||
private record GradientColor(
|
||||
float location,
|
||||
TextColor color
|
||||
@@ -22,6 +23,11 @@ public class ChatColorGradient {
|
||||
|
||||
private final List<GradientColor> colors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create the custom gradient.
|
||||
*/
|
||||
public ChatColorGradient() {}
|
||||
|
||||
/**
|
||||
* Put a specific color at a specific location in the gradient.
|
||||
* @param gradientLocation the location in the gradient.
|
||||
@@ -43,7 +49,7 @@ public class ChatColorGradient {
|
||||
if (colors.isEmpty())
|
||||
throw new IllegalStateException("Must define at least one color in this ChatColorGradient instance.");
|
||||
if (colors.size() == 1)
|
||||
return colors.get(0).color();
|
||||
return colors.getFirst().color();
|
||||
|
||||
int i = 0;
|
||||
for (; i < colors.size(); i++) {
|
||||
@@ -54,7 +60,7 @@ public class ChatColorGradient {
|
||||
if (i == 0)
|
||||
return colors.get(i).color();
|
||||
if (i == colors.size())
|
||||
return colors.get(colors.size() - 1).color();
|
||||
return colors.getLast().color();
|
||||
|
||||
int p = i - 1;
|
||||
float pLoc = colors.get(p).location();
|
||||
|
@@ -1,14 +1,14 @@
|
||||
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 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 {
|
||||
|
||||
@@ -38,12 +38,12 @@ public class ChatColorUtil {
|
||||
int length = legacyText.length();
|
||||
|
||||
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
|
||||
String rgb;
|
||||
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')
|
||||
&& HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
|
||||
@@ -64,7 +64,7 @@ public class ChatColorUtil {
|
||||
|
||||
// try detect non-rgb format
|
||||
char colorChar = legacyText.charAt(index + 1);
|
||||
ChatColor legacyColor = getChatColorByChar(colorChar);
|
||||
LegacyChatFormat legacyColor = LegacyChatFormat.of(colorChar);
|
||||
|
||||
if (legacyColor != null) {
|
||||
result.insert(0, legacyColor);
|
||||
@@ -83,15 +83,6 @@ public class ChatColorUtil {
|
||||
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
|
||||
* format.
|
||||
* <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).
|
||||
* 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)}).
|
||||
@@ -112,7 +103,7 @@ public class ChatColorUtil {
|
||||
*/
|
||||
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
|
||||
{
|
||||
char colorChar = ChatColor.COLOR_CHAR;
|
||||
char colorChar = LegacyChatFormat.COLOR_CHAR;
|
||||
StringBuilder acc = new StringBuilder();
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for ( int i = 0; i < b.length; i++ )
|
||||
@@ -180,7 +171,7 @@ public class ChatColorUtil {
|
||||
* @return the text fully italic.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
.replace(format.toString(), "") // remove previous tag to make the result cleaner
|
||||
.replaceAll("§([a-frA-FR\\d])", "§$1" + format);
|
||||
.replace(formatStr, "") // remove previous tag to make the result cleaner
|
||||
.replaceAll("§([a-frA-FR\\d])", "§$1" + formatStr);
|
||||
}
|
||||
|
||||
|
||||
@@ -243,40 +235,12 @@ public class ChatColorUtil {
|
||||
* @return the resulting text.
|
||||
*/
|
||||
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.
|
||||
* @param v0 the value corresponding to color {@code cc0}.
|
||||
@@ -293,4 +257,7 @@ public class ChatColorUtil {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ChatColorUtil() {}
|
||||
|
||||
}
|
@@ -87,7 +87,7 @@ public class ChatConfig {
|
||||
*/
|
||||
public static int getPrefixWidth(boolean console) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -157,5 +157,9 @@ public class ChatConfig {
|
||||
.thenText("] ");
|
||||
}
|
||||
|
||||
private PandaTheme() {}
|
||||
|
||||
}
|
||||
|
||||
private ChatConfig() {}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||
import fr.pandacube.lib.chat.Chat.FormattableChat;
|
||||
|
||||
/**
|
||||
* Builder for a {@link Chat} component for filling a chat line, with decoration and eventual aligned text.
|
||||
@@ -150,10 +150,10 @@ public class ChatFilledLine implements ComponentLike {
|
||||
|
||||
|
||||
/**
|
||||
* Renders this line to a {@link FormatableChat}.
|
||||
* @return a new {@link FormatableChat} built by this {@link ChatFilledLine}.
|
||||
* Renders this line to a {@link FormattableChat}.
|
||||
* @return a new {@link FormattableChat} built by this {@link ChatFilledLine}.
|
||||
*/
|
||||
public FormatableChat toChat() {
|
||||
public FormattableChat toChat() {
|
||||
int maxWidth = (this.maxWidth != null)
|
||||
? this.maxWidth
|
||||
: console ? ChatUtil.CONSOLE_NB_CHAR_DEFAULT : ChatUtil.DEFAULT_CHAT_WIDTH;
|
||||
@@ -170,7 +170,7 @@ public class ChatFilledLine implements ComponentLike {
|
||||
int textWidth = ChatUtil.componentWidth(text.asComponent(), console);
|
||||
|
||||
if (textWidth > maxWidth)
|
||||
return (FormatableChat) text;
|
||||
return (FormattableChat) text;
|
||||
|
||||
int repeatedCharWidth = ChatUtil.charW(decorationChar, console, decorationBold);
|
||||
int nbCharLeft = 0, nbCharRight = 0;
|
||||
@@ -179,12 +179,12 @@ public class ChatFilledLine implements ComponentLike {
|
||||
case CENTER -> {
|
||||
nbCharLeft = nbCharRight = (maxWidth - textWidth) / 2 / repeatedCharWidth;
|
||||
if (nbCharLeft == 0)
|
||||
return (FormatableChat) text;
|
||||
return (FormattableChat) text;
|
||||
}
|
||||
case LEFT, RIGHT -> {
|
||||
int remWidth = textWidth + nbSide * repeatedCharWidth;
|
||||
if (remWidth > maxWidth)
|
||||
return (FormatableChat) text;
|
||||
return (FormattableChat) text;
|
||||
boolean left = alignment == Alignment.LEFT;
|
||||
int nbOtherSide = (maxWidth - remWidth) / repeatedCharWidth;
|
||||
nbCharLeft = left ? nbSide : nbOtherSide;
|
||||
@@ -197,7 +197,7 @@ public class ChatFilledLine implements ComponentLike {
|
||||
.then(text);
|
||||
if (decorationChar != ' ' || spacesDecorationRightSide)
|
||||
d.then(Chat.text(ChatUtil.repeatedChar(decorationChar, nbCharRight)).color(decorationColor).bold(decorationBold));
|
||||
return (FormatableChat) d;
|
||||
return (FormattableChat) d;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package fr.pandacube.lib.chat;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat.FormattableChat;
|
||||
import net.kyori.adventure.text.BlockNBTComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
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.minimessage.MiniMessage;
|
||||
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.
|
||||
@@ -29,45 +27,27 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
|
||||
private static FormatableChat chatComponent(Component c) {
|
||||
return new FormatableChat(componentToBuilder(c));
|
||||
private static FormattableChat chatComponent(Component c) {
|
||||
return new FormattableChat(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 FormattableChat} from the provided {@link ComponentLike}.
|
||||
* If the provided component is an instance of {@link Chat}, its content will be duplicated, and the provided one
|
||||
* will be untouched.
|
||||
* @param c the {@link ComponentLike}.
|
||||
* @return a new {@link FormatableChat}.
|
||||
* @return a new {@link FormattableChat}.
|
||||
*/
|
||||
public static FormatableChat chatComponent(ComponentLike c) {
|
||||
public static FormattableChat chatComponent(ComponentLike c) {
|
||||
return chatComponent(c.asComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with an empty main text content.
|
||||
* @return a new empty {@link FormatableChat}.
|
||||
* Creates a {@link FormattableChat} with an empty main text content.
|
||||
* @return a new empty {@link FormattableChat}.
|
||||
*/
|
||||
public static FormatableChat chat() {
|
||||
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));
|
||||
public static FormattableChat chat() {
|
||||
return new FormattableChat(Component.text());
|
||||
}
|
||||
|
||||
|
||||
@@ -76,60 +56,60 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content.
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
|
||||
* instead.
|
||||
*/
|
||||
public static FormatableChat text(Object plainText) {
|
||||
public static FormattableChat text(Object plainText) {
|
||||
if (plainText instanceof ComponentLike) {
|
||||
throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + plainText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
|
||||
}
|
||||
return new FormatableChat(Component.text().content(Objects.toString(plainText)));
|
||||
return new FormattableChat(Component.text().content(Objects.toString(plainText)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided legacy text as its content, using the section {@code "§"}
|
||||
* Creates a {@link FormattableChat} with the provided legacy text as its content, using the section {@code "§"}
|
||||
* character.
|
||||
* @param legacyText the legacy text to use as the content, that uses the {@code "§"} character.
|
||||
* @return a new {@link FormatableChat} with the provided text as its content.
|
||||
* @return a new {@link FormattableChat} with the provided text as its content.
|
||||
* @throws IllegalArgumentException If the {@code legacyText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
|
||||
* instead.
|
||||
*/
|
||||
public static FormatableChat legacyText(Object legacyText) {
|
||||
public static FormattableChat legacyText(Object legacyText) {
|
||||
return legacyText(legacyText, LegacyComponentSerializer.SECTION_CHAR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided legacy text as its content, using the ampersand {@code "&"}
|
||||
* Creates a {@link FormattableChat} with the provided legacy text as its content, using the ampersand {@code "&"}
|
||||
* character.
|
||||
* @param legacyText the legacy text to use as the content, that uses the {@code "&"} character.
|
||||
* @return a new {@link FormatableChat} with the provided text as its content.
|
||||
* @return a new {@link FormattableChat} with the provided text as its content.
|
||||
* @throws IllegalArgumentException If the {@code legacyText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
|
||||
* instead.
|
||||
*/
|
||||
public static FormatableChat legacyAmpersandText(Object legacyText) {
|
||||
public static FormattableChat legacyAmpersandText(Object legacyText) {
|
||||
return legacyText(legacyText, LegacyComponentSerializer.AMPERSAND_CHAR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided legacy text as its content, using the specified
|
||||
* Creates a {@link FormattableChat} with the provided legacy text as its content, using the specified
|
||||
* legacyCharacter.
|
||||
* @param legacyText the legacy text to use as the content.
|
||||
* @param legacyCharacter the character used in the provided text to prefix color and format code.
|
||||
* @return a new {@link FormatableChat} with the provided text as its content.
|
||||
* @return a new {@link FormattableChat} with the provided text as its content.
|
||||
* @throws IllegalArgumentException If the {@code legacyText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
|
||||
* instead.
|
||||
*/
|
||||
private static FormatableChat legacyText(Object legacyText, char legacyCharacter) {
|
||||
private static FormattableChat legacyText(Object legacyText, char legacyCharacter) {
|
||||
if (legacyText instanceof ComponentLike) {
|
||||
throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + legacyText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
|
||||
}
|
||||
@@ -138,118 +118,118 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided MiniMessage text as its content.
|
||||
* Creates a {@link FormattableChat} with the provided MiniMessage text as its content.
|
||||
* @param miniMessageText the MiniMessage text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its content.
|
||||
* @return a new {@link FormattableChat} with the provided text as its content.
|
||||
*/
|
||||
public static FormatableChat miniMessageText(String miniMessageText) {
|
||||
public static FormattableChat miniMessageText(String miniMessageText) {
|
||||
return chatComponent(MiniMessage.miniMessage().deserialize(miniMessageText));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content, and colored using the
|
||||
* {@link ChatConfig#infoColor configured info color}.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#infoColor()} instead.
|
||||
* {@link FormattableChat#infoColor()} instead.
|
||||
*/
|
||||
public static FormatableChat infoText(Object plainText) {
|
||||
public static FormattableChat infoText(Object plainText) {
|
||||
return text(plainText).infoColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content, and colored using the
|
||||
* {@link ChatConfig#warningColor configured warning color}.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#warningColor()} instead.
|
||||
* {@link FormattableChat#warningColor()} instead.
|
||||
*/
|
||||
public static FormatableChat warningText(Object plainText) {
|
||||
public static FormattableChat warningText(Object plainText) {
|
||||
return text(plainText).warningColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content, and colored using the
|
||||
* {@link ChatConfig#dataColor configured data color}.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#dataColor()} instead.
|
||||
* {@link FormattableChat#dataColor()} instead.
|
||||
*/
|
||||
public static FormatableChat dataText(Object plainText) {
|
||||
public static FormattableChat dataText(Object plainText) {
|
||||
return text(plainText).dataColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content, and colored using the
|
||||
* {@link ChatConfig#decorationColor configured decorationColor color}.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#decorationColor()} instead.
|
||||
* {@link FormattableChat#decorationColor()} instead.
|
||||
*/
|
||||
public static FormatableChat decorationText(Object plainText) {
|
||||
public static FormattableChat decorationText(Object plainText) {
|
||||
return text(plainText).decorationColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content, and colored using the
|
||||
* {@link ChatConfig#successColor configured success color}.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#successColor()} instead.
|
||||
* {@link FormattableChat#successColor()} instead.
|
||||
*/
|
||||
public static FormatableChat successText(Object plainText) {
|
||||
public static FormattableChat successText(Object plainText) {
|
||||
return text(plainText).successColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
|
||||
* Creates a {@link FormattableChat} with the provided plain text as its main text content, and colored using the
|
||||
* {@link ChatConfig#failureColor configured failure color}.
|
||||
* @param plainText the text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#failureColor()} instead.
|
||||
* {@link FormattableChat#failureColor()} instead.
|
||||
*/
|
||||
public static FormatableChat failureText(Object plainText) {
|
||||
public static FormattableChat failureText(Object plainText) {
|
||||
return text(plainText).failureColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided legacy text as its main text content, and colored in white in
|
||||
* Creates a {@link FormattableChat} with the provided legacy text as its main text content, and colored in white in
|
||||
* case there is no color on the generated parent component.
|
||||
* @param legacyText the legacy text to use as the content.
|
||||
* @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
|
||||
* @return a new {@link FormattableChat} with the provided text as its main text content, and the configured color.
|
||||
* @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
|
||||
* {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
|
||||
* {@link FormatableChat#failureColor()} instead.
|
||||
* {@link FormattableChat#failureColor()} instead.
|
||||
*/
|
||||
public static FormatableChat playerNameText(String legacyText) {
|
||||
FormatableChat fc = legacyText(legacyText);
|
||||
public static FormattableChat playerNameText(String legacyText) {
|
||||
FormattableChat fc = legacyText(legacyText);
|
||||
fc.builder.colorIfAbsent(NamedTextColor.WHITE);
|
||||
return fc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} from the provided {@link Component}, coloring in white the generated parent
|
||||
* Creates a {@link FormattableChat} from the provided {@link Component}, coloring in white the generated parent
|
||||
* component in case there is no color defined.
|
||||
* If the provided component is an instance of {@link Chat}, its content will be duplicated, and the provided one
|
||||
* will be untouched.
|
||||
* @param c the {@link Component}.
|
||||
* @return a new {@link FormatableChat}.
|
||||
* @return a new {@link FormattableChat}.
|
||||
*/
|
||||
public static FormatableChat playerNameComponent(ComponentLike c) {
|
||||
FormatableChat fc = chatComponent(c);
|
||||
public static FormattableChat playerNameComponent(ComponentLike c) {
|
||||
FormattableChat fc = chatComponent(c);
|
||||
fc.builder.colorIfAbsent(NamedTextColor.WHITE);
|
||||
return fc;
|
||||
}
|
||||
@@ -258,32 +238,32 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided translation key and parameters.
|
||||
* Creates a {@link FormattableChat} with the provided translation key and parameters.
|
||||
* @param key the translation key.
|
||||
* @param with the translation parameters.
|
||||
* @return a new {@link FormatableChat} with the provided translation key and parameters.
|
||||
* @return a new {@link FormattableChat} with the provided translation key and parameters.
|
||||
*/
|
||||
public static FormatableChat translation(String key, Object... with) {
|
||||
return new FormatableChat(Component.translatable().key(key).args(Chat.filterObjToComponentLike(with)));
|
||||
public static FormattableChat translation(String key, Object... with) {
|
||||
return new FormattableChat(Component.translatable().key(key).arguments(Chat.filterObjToTranslationArgumentLike(with)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided keybinding.
|
||||
* Creates a {@link FormattableChat} with the provided keybinding.
|
||||
* @param key the keybinding to display.
|
||||
* @return a new {@link FormatableChat} with the provided keybinding.
|
||||
* @return a new {@link FormattableChat} with the provided keybinding.
|
||||
*/
|
||||
public static FormatableChat keybind(String key) {
|
||||
return new FormatableChat(Component.keybind().keybind(key));
|
||||
public static FormattableChat keyBind(String key) {
|
||||
return new FormattableChat(Component.keybind().keybind(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} with the provided score name and objective.
|
||||
* Creates a {@link FormattableChat} with the provided score name and objective.
|
||||
* @param name the score name.
|
||||
* @param objective the score objective.
|
||||
* @return a new {@link FormatableChat} with the provided score name and objective.
|
||||
* @return a new {@link FormattableChat} with the provided score name and objective.
|
||||
*/
|
||||
public static FormatableChat score(String name, String objective) {
|
||||
return new FormatableChat(Component.score().name(name).objective(objective));
|
||||
public static FormattableChat score(String name, String objective) {
|
||||
return new FormattableChat(Component.score().name(name).objective(objective));
|
||||
}
|
||||
|
||||
|
||||
@@ -292,49 +272,49 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that leads to a URL when clicked.
|
||||
* Creates a {@link FormattableChat} that leads to a URL when clicked.
|
||||
* @param inner the component to make clickable.
|
||||
* @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
|
||||
* @param hover the content to display when hovering the component.
|
||||
* @return a new {@link FormatableChat} that leads to a URL when clicked.
|
||||
* @return a new {@link FormattableChat} that leads to a URL when clicked.
|
||||
*/
|
||||
public static FormatableChat clickableURL(ComponentLike inner, String url, HoverEventSource<?> hover) {
|
||||
public static FormattableChat clickableURL(ComponentLike inner, String url, HoverEventSource<?> hover) {
|
||||
Objects.requireNonNull(url, "url");
|
||||
if (inner == null)
|
||||
inner = text(url);
|
||||
if (hover == null)
|
||||
hover = text(ChatUtil.wrapInLimitedPixels(url, 240));
|
||||
return (FormatableChat) chat().clickURL(url).urlColor().hover(hover).then(inner);
|
||||
return (FormattableChat) chat().clickURL(url).urlColor().hover(hover).then(inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that leads to a URL when clicked.
|
||||
* Creates a {@link FormattableChat} that leads to a URL when clicked.
|
||||
* <p>
|
||||
* When hovered, the component will display the url. To customize the hover content, use
|
||||
* {@link #clickableURL(ComponentLike, String, HoverEventSource)}.
|
||||
* @param inner the component to make clickable.
|
||||
* @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
|
||||
* @return a new {@link FormatableChat} that leads to a URL when clicked.
|
||||
* @return a new {@link FormattableChat} that leads to a URL when clicked.
|
||||
*/
|
||||
public static FormatableChat clickableURL(ComponentLike inner, String url) {
|
||||
public static FormattableChat clickableURL(ComponentLike inner, String url) {
|
||||
return clickableURL(inner, url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that leads to a URL when clicked.
|
||||
* Creates a {@link FormattableChat} that leads to a URL when clicked.
|
||||
* <p>
|
||||
* The text on which to click will be the URL itself. To configure the clicked text, use
|
||||
* {@link #clickableURL(ComponentLike, String, HoverEventSource)}.
|
||||
* @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
|
||||
* @param hover the content to display when hovering the component.
|
||||
* @return a new {@link FormatableChat} that leads to a URL when clicked.
|
||||
* @return a new {@link FormattableChat} that leads to a URL when clicked.
|
||||
*/
|
||||
public static FormatableChat clickableURL(String url, HoverEventSource<?> hover) {
|
||||
public static FormattableChat clickableURL(String url, HoverEventSource<?> hover) {
|
||||
return clickableURL(null, url, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that leads to a URL when clicked.
|
||||
* Creates a {@link FormattableChat} that leads to a URL when clicked.
|
||||
* <p>
|
||||
* The text on which to click will be the URL itself. To configure the clicked text, use
|
||||
* {@link #clickableURL(ComponentLike, String)}.
|
||||
@@ -342,9 +322,9 @@ public abstract class ChatStatic {
|
||||
* When hovered, the component will display the url. To customize the hover content, use
|
||||
* {@link #clickableURL(String, HoverEventSource)}.
|
||||
* @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
|
||||
* @return a new {@link FormatableChat} that leads to a URL when clicked.
|
||||
* @return a new {@link FormattableChat} that leads to a URL when clicked.
|
||||
*/
|
||||
public static FormatableChat clickableURL(String url) {
|
||||
public static FormattableChat clickableURL(String url) {
|
||||
return clickableURL(null, url, null);
|
||||
}
|
||||
|
||||
@@ -354,14 +334,14 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that runs a command when clicked.
|
||||
* Creates a {@link FormattableChat} that runs a command when clicked.
|
||||
* @param inner the component to make clickable.
|
||||
* @param commandWithSlash the command to run. Must start with {@code "/"}.
|
||||
* @param hover the content to display when hovering the component.
|
||||
* @return a new {@link FormatableChat} that runs a command when clicked.
|
||||
* @return a new {@link FormattableChat} that runs a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableCommand(ComponentLike inner, String commandWithSlash, HoverEventSource<?> hover) {
|
||||
public static FormattableChat clickableCommand(ComponentLike inner, String commandWithSlash, HoverEventSource<?> hover) {
|
||||
Objects.requireNonNull(commandWithSlash, "commandWithSlash");
|
||||
if (!commandWithSlash.startsWith("/"))
|
||||
throw new IllegalArgumentException("commandWithSlash must start with a '/' character.");
|
||||
@@ -369,39 +349,39 @@ public abstract class ChatStatic {
|
||||
inner = text(commandWithSlash);
|
||||
if (hover == null)
|
||||
hover = text(ChatUtil.wrapInLimitedPixels(commandWithSlash, 240));
|
||||
return (FormatableChat) chat().clickCommand(commandWithSlash).commandColor().hover(hover).then(inner);
|
||||
return (FormattableChat) chat().clickCommand(commandWithSlash).commandColor().hover(hover).then(inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that runs a command when clicked.
|
||||
* Creates a {@link FormattableChat} that runs a command when clicked.
|
||||
* <p>
|
||||
* When hovered, the component will display the command itself. To customize the hover content, use
|
||||
* {@link #clickableCommand(ComponentLike, String, HoverEventSource)}.
|
||||
* @param inner the component to make clickable.
|
||||
* @param commandWithSlash the command to run. Must start with {@code "/"}.
|
||||
* @return a new {@link FormatableChat} that runs a command when clicked.
|
||||
* @return a new {@link FormattableChat} that runs a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableCommand(ComponentLike inner, String commandWithSlash) {
|
||||
public static FormattableChat clickableCommand(ComponentLike inner, String commandWithSlash) {
|
||||
return clickableCommand(inner, commandWithSlash, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that runs a command when clicked.
|
||||
* Creates a {@link FormattableChat} that runs a command when clicked.
|
||||
* <p>
|
||||
* The text on which to click will be the command itself. To configure the clicked text, use
|
||||
* {@link #clickableCommand(ComponentLike, String, HoverEventSource)}.
|
||||
* @param commandWithSlash the command to run. Must start with {@code "/"}.
|
||||
* @param hover the content to display when hovering the component.
|
||||
* @return a new {@link FormatableChat} that runs a command when clicked.
|
||||
* @return a new {@link FormattableChat} that runs a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableCommand(String commandWithSlash, HoverEventSource<?> hover) {
|
||||
public static FormattableChat clickableCommand(String commandWithSlash, HoverEventSource<?> hover) {
|
||||
return clickableCommand(null, commandWithSlash, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that runs a command when clicked.
|
||||
* Creates a {@link FormattableChat} that runs a command when clicked.
|
||||
* <p>
|
||||
* The text on which to click will be the command itself. To configure the clicked text, use
|
||||
* {@link #clickableCommand(ComponentLike, String)}.
|
||||
@@ -409,10 +389,10 @@ public abstract class ChatStatic {
|
||||
* When hovered, the component will display the command itself. To customize the hover content, use
|
||||
* {@link #clickableCommand(String, HoverEventSource)}.
|
||||
* @param commandWithSlash the command to run. Must start with {@code "/"}.
|
||||
* @return a new {@link FormatableChat} that runs a command when clicked.
|
||||
* @return a new {@link FormattableChat} that runs a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableCommand(String commandWithSlash) {
|
||||
public static FormattableChat clickableCommand(String commandWithSlash) {
|
||||
return clickableCommand(null, commandWithSlash, null);
|
||||
}
|
||||
|
||||
@@ -422,14 +402,14 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* Creates a {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @param inner the component to make clickable.
|
||||
* @param commandWithSlash the command to suggest. Must start with {@code "/"}.
|
||||
* @param hover the content to display when hovering the component.
|
||||
* @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @return a new {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableSuggest(ComponentLike inner, String commandWithSlash, HoverEventSource<?> hover) {
|
||||
public static FormattableChat clickableSuggest(ComponentLike inner, String commandWithSlash, HoverEventSource<?> hover) {
|
||||
Objects.requireNonNull(commandWithSlash, "commandWithSlash");
|
||||
if (!commandWithSlash.startsWith("/"))
|
||||
throw new IllegalArgumentException("commandWithSlash must start with a '/' character.");
|
||||
@@ -437,39 +417,39 @@ public abstract class ChatStatic {
|
||||
inner = text(commandWithSlash);
|
||||
if (hover == null)
|
||||
hover = text(ChatUtil.wrapInLimitedPixels(commandWithSlash, 240));
|
||||
return (FormatableChat) chat().clickSuggest(commandWithSlash).commandColor().hover(hover).then(inner);
|
||||
return (FormattableChat) chat().clickSuggest(commandWithSlash).commandColor().hover(hover).then(inner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* Creates a {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* <p>
|
||||
* When hovered, the component will display the command itself. To customize the hover content, use
|
||||
* {@link #clickableSuggest(ComponentLike, String, HoverEventSource)}.
|
||||
* @param inner the component to make clickable.
|
||||
* @param commandWithSlash the command to suggest. Must start with {@code "/"}.
|
||||
* @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @return a new {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableSuggest(ComponentLike inner, String commandWithSlash) {
|
||||
public static FormattableChat clickableSuggest(ComponentLike inner, String commandWithSlash) {
|
||||
return clickableSuggest(inner, commandWithSlash, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* Creates a {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* <p>
|
||||
* The text on which to click will be the command itself. To configure the clicked text, use
|
||||
* {@link #clickableSuggest(ComponentLike, String, HoverEventSource)}.
|
||||
* @param commandWithSlash the command to suggest. Must start with {@code "/"}.
|
||||
* @param hover the content to display when hovering the component.
|
||||
* @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @return a new {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableSuggest(String commandWithSlash, HoverEventSource<?> hover) {
|
||||
public static FormattableChat clickableSuggest(String commandWithSlash, HoverEventSource<?> hover) {
|
||||
return clickableSuggest(null, commandWithSlash, hover);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* Creates a {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* <p>
|
||||
* The text on which to click will be the command itself. To configure the clicked text, use
|
||||
* {@link #clickableSuggest(ComponentLike, String)}.
|
||||
@@ -477,10 +457,10 @@ public abstract class ChatStatic {
|
||||
* When hovered, the component will display the command itself. To customize the hover content, use
|
||||
* {@link #clickableSuggest(String, HoverEventSource)}.
|
||||
* @param commandWithSlash the command to suggest. Must start with {@code "/"}.
|
||||
* @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @return a new {@link FormattableChat} that pre-fill the chat box with a command when clicked.
|
||||
* @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
|
||||
*/
|
||||
public static FormatableChat clickableSuggest(String commandWithSlash) {
|
||||
public static FormattableChat clickableSuggest(String commandWithSlash) {
|
||||
return clickableSuggest(null, commandWithSlash, null);
|
||||
}
|
||||
|
||||
@@ -492,112 +472,112 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with decoration and a left-aligned text.
|
||||
* Creates a {@link FormattableChat} filling a chat line with decoration and a left-aligned text.
|
||||
* @param text the text aligned to the left.
|
||||
* @param decorationChar the character used for decoration around the text.
|
||||
* @param decorationColor the color used for the decoration characters.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with decoration and a left-aligned text.
|
||||
* @return a new {@link FormattableChat} filling a chat line with decoration and a left-aligned text.
|
||||
* @see ChatFilledLine#leftText(ComponentLike)
|
||||
*/
|
||||
public static FormatableChat leftText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
|
||||
public static FormattableChat leftText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
|
||||
return ChatFilledLine.leftText(text).decoChar(decorationChar).decoColor(decorationColor).spacesAroundText().console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with the configured decoration character and
|
||||
* Creates a {@link FormattableChat} filling a chat line with the configured decoration character and
|
||||
* color and a left-aligned text.
|
||||
* @param text the text aligned to the left.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with the configured decoration character
|
||||
* @return a new {@link FormattableChat} filling a chat line with the configured decoration character
|
||||
* and color and a left-aligned text.
|
||||
* @see ChatFilledLine#leftText(ComponentLike)
|
||||
* @see ChatConfig#decorationChar
|
||||
* @see ChatConfig#decorationColor
|
||||
*/
|
||||
public static FormatableChat leftText(ComponentLike text, boolean console) {
|
||||
public static FormattableChat leftText(ComponentLike text, boolean console) {
|
||||
return ChatFilledLine.leftText(text).spacesAroundText().console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with decoration and a right-aligned text.
|
||||
* Creates a {@link FormattableChat} filling a chat line with decoration and a right-aligned text.
|
||||
* @param text the text aligned to the right.
|
||||
* @param decorationChar the character used for decoration around the text.
|
||||
* @param decorationColor the color used for the decoration characters.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with decoration and a right-aligned
|
||||
* @return a new {@link FormattableChat} filling a chat line with decoration and a right-aligned
|
||||
* text.
|
||||
* @see ChatFilledLine#rightText(ComponentLike)
|
||||
*/
|
||||
public static FormatableChat rightText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
|
||||
public static FormattableChat rightText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
|
||||
return ChatFilledLine.rightText(text).decoChar(decorationChar).decoColor(decorationColor).spacesAroundText().console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with the configured decoration character and
|
||||
* Creates a {@link FormattableChat} filling a chat line with the configured decoration character and
|
||||
* color and a right-aligned text.
|
||||
* @param text the text aligned to the right.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with the configured decoration character
|
||||
* @return a new {@link FormattableChat} filling a chat line with the configured decoration character
|
||||
* and color and a right-aligned text.
|
||||
* @see ChatFilledLine#rightText(ComponentLike)
|
||||
* @see ChatConfig#decorationChar
|
||||
* @see ChatConfig#decorationColor
|
||||
*/
|
||||
public static FormatableChat rightText(ComponentLike text, boolean console) {
|
||||
public static FormattableChat rightText(ComponentLike text, boolean console) {
|
||||
return ChatFilledLine.rightText(text).spacesAroundText().console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with decoration and a centered text.
|
||||
* Creates a {@link FormattableChat} filling a chat line with decoration and a centered text.
|
||||
* @param text the text aligned to the center.
|
||||
* @param decorationChar the character used for decoration around the text.
|
||||
* @param decorationColor the color used for the decoration characters.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with decoration and a centered text.
|
||||
* @return a new {@link FormattableChat} filling a chat line with decoration and a centered text.
|
||||
* @see ChatFilledLine#centerText(ComponentLike)
|
||||
*/
|
||||
public static FormatableChat centerText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
|
||||
public static FormattableChat centerText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
|
||||
return ChatFilledLine.centerText(text).decoChar(decorationChar).decoColor(decorationColor).spacesAroundText().console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with the configured decoration character and
|
||||
* Creates a {@link FormattableChat} filling a chat line with the configured decoration character and
|
||||
* color and a centered text.
|
||||
* @param text the text aligned to the center.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with the configured decoration character
|
||||
* @return a new {@link FormattableChat} filling a chat line with the configured decoration character
|
||||
* and color and a centered text.
|
||||
* @see ChatFilledLine#centerText(ComponentLike)
|
||||
* @see ChatConfig#decorationChar
|
||||
* @see ChatConfig#decorationColor
|
||||
*/
|
||||
public static FormatableChat centerText(ComponentLike text, boolean console) {
|
||||
public static FormattableChat centerText(ComponentLike text, boolean console) {
|
||||
return ChatFilledLine.centerText(text).spacesAroundText().console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with a decoration character and color.
|
||||
* Creates a {@link FormattableChat} filling a chat line with a decoration character and color.
|
||||
* @param decorationChar the character used for decoration.
|
||||
* @param decorationColor the color used for the decoration characters.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with a decoration character and color.
|
||||
* @return a new {@link FormattableChat} filling a chat line with a decoration character and color.
|
||||
* @see ChatFilledLine#filled()
|
||||
*/
|
||||
public static FormatableChat filledLine(char decorationChar, TextColor decorationColor, boolean console) {
|
||||
public static FormattableChat filledLine(char decorationChar, TextColor decorationColor, boolean console) {
|
||||
return ChatFilledLine.filled().decoChar(decorationChar).decoColor(decorationColor).console(console).toChat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FormatableChat} filling a chat line with the configured decoration character and
|
||||
* Creates a {@link FormattableChat} filling a chat line with the configured decoration character and
|
||||
* color.
|
||||
* @param console if the line is rendered on console (true) or IG (false).
|
||||
* @return a new {@link FormatableChat} filling a chat line with a decoration character and color.
|
||||
* @return a new {@link FormattableChat} filling a chat line with a decoration character and color.
|
||||
* @see ChatFilledLine#filled()
|
||||
* @see ChatConfig#decorationChar
|
||||
* @see ChatConfig#decorationColor
|
||||
*/
|
||||
public static FormatableChat filledLine(boolean console) {
|
||||
public static FormattableChat filledLine(boolean console) {
|
||||
return ChatFilledLine.filled().console(console).toChat();
|
||||
}
|
||||
|
||||
@@ -632,52 +612,39 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
private static ComponentBuilder<?, ?> componentToBuilder(Component c) {
|
||||
ComponentBuilder<?, ?> builder;
|
||||
if (c instanceof TextComponent) {
|
||||
builder = Component.text()
|
||||
.content(((TextComponent) c).content());
|
||||
}
|
||||
else if (c instanceof TranslatableComponent) {
|
||||
builder = Component.translatable()
|
||||
.key(((TranslatableComponent) c).key())
|
||||
.args(((TranslatableComponent) c).args());
|
||||
}
|
||||
else if (c instanceof SelectorComponent) {
|
||||
builder = Component.selector()
|
||||
.pattern(((SelectorComponent) c).pattern());
|
||||
}
|
||||
else if (c instanceof ScoreComponent) {
|
||||
builder = Component.score()
|
||||
.name(((ScoreComponent) c).name())
|
||||
.objective(((ScoreComponent) c).objective());
|
||||
}
|
||||
else if (c instanceof KeybindComponent) {
|
||||
builder = Component.keybind()
|
||||
.keybind(((KeybindComponent) c).keybind());
|
||||
}
|
||||
else if (c instanceof BlockNBTComponent) {
|
||||
builder = Component.blockNBT()
|
||||
.interpret(((BlockNBTComponent) c).interpret())
|
||||
.nbtPath(((BlockNBTComponent) c).nbtPath())
|
||||
.pos(((BlockNBTComponent) c).pos());
|
||||
}
|
||||
else if (c instanceof EntityNBTComponent) {
|
||||
builder = Component.entityNBT()
|
||||
.interpret(((EntityNBTComponent) c).interpret())
|
||||
.nbtPath(((EntityNBTComponent) c).nbtPath())
|
||||
.selector(((EntityNBTComponent) c).selector());
|
||||
}
|
||||
else if (c instanceof StorageNBTComponent) {
|
||||
builder = Component.storageNBT()
|
||||
.interpret(((StorageNBTComponent) c).interpret())
|
||||
.nbtPath(((StorageNBTComponent) c).nbtPath())
|
||||
.storage(((StorageNBTComponent) c).storage());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown component type " + c.getClass());
|
||||
}
|
||||
ComponentBuilder<?, ?> builder = switch (c) {
|
||||
case TextComponent textComponent -> Component.text()
|
||||
.content(textComponent.content());
|
||||
case TranslatableComponent translatableComponent -> Component.translatable()
|
||||
.key(translatableComponent.key()).arguments(translatableComponent.arguments());
|
||||
case SelectorComponent selectorComponent -> Component.selector()
|
||||
.pattern(selectorComponent.pattern());
|
||||
case ScoreComponent scoreComponent -> Component.score()
|
||||
.name(scoreComponent.name())
|
||||
.objective(scoreComponent.objective());
|
||||
case KeybindComponent keybindComponent -> Component.keybind()
|
||||
.keybind(keybindComponent.keybind());
|
||||
case BlockNBTComponent blockNBTComponent -> Component.blockNBT()
|
||||
.interpret(blockNBTComponent.interpret())
|
||||
.nbtPath(blockNBTComponent.nbtPath())
|
||||
.pos(blockNBTComponent.pos());
|
||||
case EntityNBTComponent entityNBTComponent -> Component.entityNBT()
|
||||
.interpret(entityNBTComponent.interpret())
|
||||
.nbtPath(entityNBTComponent.nbtPath())
|
||||
.selector(entityNBTComponent.selector());
|
||||
case StorageNBTComponent storageNBTComponent -> Component.storageNBT()
|
||||
.interpret(storageNBTComponent.interpret())
|
||||
.nbtPath(storageNBTComponent.nbtPath())
|
||||
.storage(storageNBTComponent.storage());
|
||||
case null, default -> throw new IllegalArgumentException("Unknown component type " + (c == null ? "null" : c.getClass()));
|
||||
};
|
||||
return builder.style(c.style()).append(c.children());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ChatStatic} instance.
|
||||
*/
|
||||
protected ChatStatic() {}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,17 @@
|
||||
package fr.pandacube.lib.chat;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat.FormattableChat;
|
||||
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.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -10,18 +22,6 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
@@ -152,7 +152,7 @@ public class ChatUtil {
|
||||
else
|
||||
first = false;
|
||||
|
||||
FormatableChat pDisplay = Chat.clickableCommand(Chat.text(page), String.format(cmdFormat, page), Chat.text("Aller à la page " + page));
|
||||
FormattableChat pDisplay = Chat.clickableCommand(Chat.text(page), String.format(cmdFormat, page), Chat.text("Aller à la page " + page));
|
||||
if (page == currentPage) {
|
||||
pDisplay.highlightedCommandColor();
|
||||
}
|
||||
@@ -180,12 +180,12 @@ public class ChatUtil {
|
||||
* @param elements the components to join.
|
||||
* @return a new {@link Chat} instance with all the provided {@code component} joined using the separators.
|
||||
*/
|
||||
public static FormatableChat joinGrammatically(ComponentLike regularSeparator, ComponentLike finalSeparator, List<? extends ComponentLike> elements) {
|
||||
public static FormattableChat joinGrammatically(ComponentLike regularSeparator, ComponentLike finalSeparator, List<? extends ComponentLike> elements) {
|
||||
int size = elements == null ? 0 : elements.size();
|
||||
int last = size - 1;
|
||||
return switch (size) {
|
||||
case 0, 1, 2 -> join(finalSeparator, elements);
|
||||
default -> (FormatableChat) join(regularSeparator, elements.subList(0, last))
|
||||
default -> (FormattableChat) join(regularSeparator, elements.subList(0, last))
|
||||
.then(finalSeparator)
|
||||
.then(elements.get(last));
|
||||
};
|
||||
@@ -202,8 +202,8 @@ public class ChatUtil {
|
||||
* @param elements the components to join.
|
||||
* @return a new {@link Chat} instance with all the provided {@code component} joined using the separators.
|
||||
*/
|
||||
public static FormatableChat join(ComponentLike separator, Iterable<? extends ComponentLike> elements) {
|
||||
FormatableChat c = chat();
|
||||
public static FormattableChat join(ComponentLike separator, Iterable<? extends ComponentLike> elements) {
|
||||
FormattableChat c = chat();
|
||||
if (elements == null)
|
||||
return c;
|
||||
boolean first = true;
|
||||
@@ -265,8 +265,8 @@ public class ChatUtil {
|
||||
count += strWidth(((TextComponent)component).content(), console, actuallyBold);
|
||||
}
|
||||
else if (component instanceof TranslatableComponent) {
|
||||
for (Component c : ((TranslatableComponent)component).args())
|
||||
count += componentWidth(c, console, actuallyBold);
|
||||
for (TranslationArgument c : ((TranslatableComponent)component).arguments())
|
||||
count += componentWidth(c.asComponent(), console, actuallyBold);
|
||||
}
|
||||
|
||||
for (Component c : component.children())
|
||||
@@ -350,7 +350,7 @@ public class ChatUtil {
|
||||
|
||||
do {
|
||||
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);
|
||||
c = legacyText.charAt(++index);
|
||||
currentWord.append(c);
|
||||
@@ -480,8 +480,8 @@ public class ChatUtil {
|
||||
spacedRow.thenText(space);
|
||||
}
|
||||
if (!row.isEmpty())
|
||||
spacedRow.then(row.get(row.size() - 1));
|
||||
spacedRows.add(spacedRow.getAdv());
|
||||
spacedRow.then(row.getLast());
|
||||
spacedRows.add(spacedRow.get());
|
||||
}
|
||||
|
||||
return spacedRows;
|
||||
@@ -503,14 +503,14 @@ public class ChatUtil {
|
||||
*/
|
||||
public static Component customWidthSpace(int width, boolean console) {
|
||||
if (console)
|
||||
return Chat.text(" ".repeat(width)).getAdv();
|
||||
return Chat.text(" ".repeat(width)).get();
|
||||
return switch (width) {
|
||||
case 0, 1 -> Component.empty();
|
||||
case 2 -> Chat.text(".").black().getAdv();
|
||||
case 3 -> Chat.text("`").black().getAdv();
|
||||
case 6 -> Chat.text(". ").black().getAdv();
|
||||
case 7 -> Chat.text("` ").black().getAdv();
|
||||
case 11 -> Chat.text("` ").black().getAdv();
|
||||
case 2 -> Chat.text(".").black().get();
|
||||
case 3 -> Chat.text("`").black().get();
|
||||
case 6 -> Chat.text(". ").black().get();
|
||||
case 7 -> Chat.text("` ").black().get();
|
||||
case 11 -> Chat.text("` ").black().get();
|
||||
default -> {
|
||||
int nbSpace = width / 4;
|
||||
int nbBold = width % 4;
|
||||
@@ -519,13 +519,13 @@ public class ChatUtil {
|
||||
if (nbBold > 0) {
|
||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false)
|
||||
.then(Chat.text(" ".repeat(nbBold)).bold(true))
|
||||
.getAdv();
|
||||
.get();
|
||||
}
|
||||
else
|
||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false).getAdv();
|
||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false).get();
|
||||
}
|
||||
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 + ")");
|
||||
}
|
||||
@@ -596,7 +596,7 @@ public class ChatUtil {
|
||||
for (int i = 0; i < sizes.length; i++) {
|
||||
sumSizes += sizes[i];
|
||||
|
||||
FormatableChat subC = ChatStatic.text(repeatedChar(PROGRESS_BAR_FULL_CHAR, sizes[i]));
|
||||
FormattableChat subC = ChatStatic.text(repeatedChar(PROGRESS_BAR_FULL_CHAR, sizes[i]));
|
||||
|
||||
if (colors != null && i < colors.length && colors[i] != null)
|
||||
subC.color(colors[i]);
|
||||
@@ -657,5 +657,6 @@ public class ChatUtil {
|
||||
return str;
|
||||
}
|
||||
|
||||
private ChatUtil() {}
|
||||
|
||||
}
|
||||
|
@@ -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,26 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>minecraft-libraries</id>
|
||||
<name>Minecraft Libraries</name>
|
||||
<url>https://libraries.minecraft.net</url>
|
||||
</repository>
|
||||
<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-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>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-log</artifactId>
|
||||
<version>${bungeecord.version}</version>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-commands</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-config</artifactId>
|
||||
<artifactId>bungeecord-log</artifactId>
|
||||
<version>${bungeecord.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
@@ -1,22 +1,25 @@
|
||||
package fr.pandacube.lib.cli;
|
||||
|
||||
import fr.pandacube.lib.cli.commands.CLIBrigadierDispatcher;
|
||||
import fr.pandacube.lib.cli.log.CLILogger;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import org.jline.reader.EndOfFileException;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.UserInterruptException;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import fr.pandacube.lib.cli.commands.CLIBrigadierDispatcher;
|
||||
import fr.pandacube.lib.cli.log.CLILogger;
|
||||
import jline.console.ConsoleReader;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
|
||||
/**
|
||||
* Class to handle general standard IO operation for a CLI application. It uses Jline’s {@link ConsoleReader} for the
|
||||
* Class to handle general standard IO operation for a CLI application. It uses Jline’s {@link LineReader} for the
|
||||
* console rendering, a JUL {@link Logger} for logging, and Brigadier to handle commands.
|
||||
*/
|
||||
public class CLI extends Thread {
|
||||
|
||||
private final ConsoleReader reader;
|
||||
private final LineReader reader;
|
||||
private final Logger logger;
|
||||
|
||||
|
||||
@@ -28,10 +31,11 @@ public class CLI extends Thread {
|
||||
super("Console Thread");
|
||||
setDaemon(true);
|
||||
|
||||
AnsiConsole.systemInstall();
|
||||
reader = new ConsoleReader();
|
||||
reader.setPrompt(">");
|
||||
reader.addCompleter(CLIBrigadierDispatcher.instance);
|
||||
Terminal terminal = TerminalBuilder.builder().build();
|
||||
reader = LineReaderBuilder.builder().terminal(terminal)
|
||||
.completer(CLIBrigadierDispatcher.instance)
|
||||
.build()
|
||||
;
|
||||
|
||||
// configure logger's formatter
|
||||
System.setProperty("net.md_5.bungee.log-date-format", "yyyy-MM-dd HH:mm:ss");
|
||||
@@ -40,10 +44,10 @@ public class CLI extends Thread {
|
||||
|
||||
|
||||
/**
|
||||
* Gets the Jline {@link ConsoleReader} of this CLI instance.
|
||||
* @return the Jline {@link ConsoleReader} of this CLI instance.
|
||||
* Gets the Jline {@link LineReader} of this CLI instance.
|
||||
* @return the Jline {@link LineReader} of this CLI instance.
|
||||
*/
|
||||
public ConsoleReader getConsoleReader() {
|
||||
public LineReader getConsoleReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
@@ -65,15 +69,14 @@ public class CLI extends Thread {
|
||||
int i = 0;
|
||||
String line;
|
||||
try {
|
||||
while((line = reader.readLine()) != null) {
|
||||
if (line.trim().equals(""))
|
||||
while((line = reader.readLine(">")) != null) {
|
||||
if (line.trim().isEmpty())
|
||||
continue;
|
||||
String cmdLine = line;
|
||||
new Thread(() -> CLIBrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start();
|
||||
Thread.ofVirtual().name("CLICmdThread #"+(i++))
|
||||
.start(() -> CLIBrigadierDispatcher.instance.execute(cmdLine));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
} catch (UserInterruptException | EndOfFileException ignore) { }
|
||||
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,7 @@ public abstract class CLIApplication {
|
||||
/**
|
||||
* Creates a new application instance.
|
||||
*/
|
||||
@SuppressWarnings("CallToPrintStackTrace")
|
||||
protected CLIApplication() {
|
||||
instance = this;
|
||||
CLI tmpCLI = null;
|
||||
|
@@ -38,16 +38,9 @@ public abstract class CLIBrigadierCommand extends BrigadierCommand<CLICommandSen
|
||||
}
|
||||
|
||||
protected abstract LiteralArgumentBuilder<CLICommandSender> buildCommand();
|
||||
|
||||
protected String[] getAliases() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public boolean isPlayer(CLICommandSender sender) {
|
||||
public boolean isPlayer(CLICommandSender sender) {
|
||||
return sender.isPlayer();
|
||||
}
|
||||
|
||||
|
@@ -3,8 +3,11 @@ package fr.pandacube.lib.cli.commands;
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import fr.pandacube.lib.commands.BrigadierDispatcher;
|
||||
import jline.console.completer.Completer;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.Completer;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.ParsedLine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -25,6 +28,9 @@ public class CLIBrigadierDispatcher extends BrigadierDispatcher<CLICommandSender
|
||||
public static final CLICommandSender CLI_CONSOLE_COMMAND_SENDER = new CLIConsoleCommandSender();
|
||||
|
||||
|
||||
private CLIBrigadierDispatcher() {}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the provided command as the console.
|
||||
* @param commandWithoutSlash the command, without the eventual slash at the beginning.
|
||||
@@ -36,17 +42,15 @@ public class CLIBrigadierDispatcher extends BrigadierDispatcher<CLICommandSender
|
||||
|
||||
|
||||
@Override
|
||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
|
||||
String bufferBeforeCursor = buffer.substring(0, cursor);
|
||||
|
||||
public void complete(LineReader lineReader, ParsedLine parsedLine, List<Candidate> candidates) {
|
||||
String bufferBeforeCursor = parsedLine.line().substring(0, parsedLine.cursor());
|
||||
|
||||
Suggestions completeResult = getSuggestions(bufferBeforeCursor);
|
||||
|
||||
|
||||
completeResult.getList().stream()
|
||||
.map(Suggestion::getText)
|
||||
.map(Candidate::new)
|
||||
.forEach(candidates::add);
|
||||
|
||||
return completeResult.getRange().getStart();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -41,6 +41,9 @@ public interface CLICommandSender extends Audience {
|
||||
*/
|
||||
void sendMessage(String message);
|
||||
|
||||
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
|
||||
@Override // force implementation of super-interface default method
|
||||
void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type);
|
||||
|
||||
|
||||
}
|
||||
|
@@ -11,6 +11,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
* The console command sender.
|
||||
*/
|
||||
public class CLIConsoleCommandSender implements CLICommandSender {
|
||||
|
||||
/**
|
||||
* Creates a new console command sender.
|
||||
*/
|
||||
protected CLIConsoleCommandSender() {}
|
||||
|
||||
public String getName() {
|
||||
return "Console";
|
||||
}
|
||||
@@ -32,7 +38,7 @@ public class CLIConsoleCommandSender implements CLICommandSender {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
|
||||
public void sendMessage(@NotNull Identity source, @NotNull Component message, @SuppressWarnings({"UnstableApiUsage", "deprecation"}) @NotNull MessageType type) {
|
||||
sendMessage(Chat.chatComponent(message).getLegacyText());
|
||||
}
|
||||
}
|
||||
|
@@ -14,11 +14,11 @@ import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||
import fr.pandacube.lib.chat.Chat.FormattableChat;
|
||||
import fr.pandacube.lib.chat.ChatTreeNode;
|
||||
import fr.pandacube.lib.cli.CLIApplication;
|
||||
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.Arrays;
|
||||
@@ -36,6 +36,11 @@ import static fr.pandacube.lib.chat.ChatStatic.text;
|
||||
*/
|
||||
public class CommandAdmin extends CLIBrigadierCommand {
|
||||
|
||||
/**
|
||||
* Initializes the admin command.
|
||||
*/
|
||||
public CommandAdmin() {}
|
||||
|
||||
@Override
|
||||
protected LiteralArgumentBuilder<CLICommandSender> buildCommand() {
|
||||
return literal("admin")
|
||||
@@ -187,16 +192,16 @@ 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)
|
||||
throw new IllegalArgumentException("node must not be null");
|
||||
FormatableChat d;
|
||||
FormattableChat d;
|
||||
if (node instanceof RootCommandNode) {
|
||||
d = text("(root)").italic()
|
||||
.hover("Root command node");
|
||||
}
|
||||
else if (node instanceof ArgumentCommandNode) {
|
||||
ArgumentType<?> type = ((ArgumentCommandNode<?, ?>) node).getType();
|
||||
else if (node instanceof ArgumentCommandNode<?, ?> argNode) {
|
||||
ArgumentType<?> type = argNode.getType();
|
||||
String typeStr = type.getClass().getSimpleName();
|
||||
if (type instanceof IntegerArgumentType
|
||||
|| type instanceof LongArgumentType
|
||||
@@ -255,10 +260,10 @@ public class CommandAdmin extends CLIBrigadierCommand {
|
||||
return d.get();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static class DisplayCommandNode {
|
||||
final List<CommandNode<CLICommandSender>> nodes = new ArrayList<>();
|
||||
final List<DisplayCommandNode> children = new ArrayList<>();
|
||||
|
@@ -9,6 +9,11 @@ import fr.pandacube.lib.cli.CLIApplication;
|
||||
*/
|
||||
public class CommandStop extends CLIBrigadierCommand {
|
||||
|
||||
/**
|
||||
* Initializes the admin command.
|
||||
*/
|
||||
public CommandStop() {}
|
||||
|
||||
@Override
|
||||
protected LiteralArgumentBuilder<CLICommandSender> buildCommand() {
|
||||
return literal("stop")
|
||||
|
@@ -97,5 +97,7 @@ public class CLILogger {
|
||||
t.start();
|
||||
return ps;
|
||||
}
|
||||
|
||||
private CLILogger() {}
|
||||
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>brigadier</artifactId>
|
||||
<version>1.0.18</version>
|
||||
<version>${brigadier.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
@@ -3,35 +3,39 @@ package fr.pandacube.lib.commands;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Throw an instance of this exception to indicate to the plugin command handler that the user has missused the command.
|
||||
* The message, if provided, must indicate the reason of the mussusage of the command. It will be displayed on the
|
||||
* Throw an instance of this exception to indicate to the plugin command handler that the user has badly used the command.
|
||||
* The message, if provided, must indicate the reason of the bad usage of the command. It will be displayed on the
|
||||
* screen with eventual indications of how to use the command (help command for example).
|
||||
* If a {@link Throwable} cause is provided, it will be relayed to the plugin {@link Logger}.
|
||||
*
|
||||
*/
|
||||
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() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** Constructs a new runtime exception with the specified cause.
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified cause.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public BadCommandUsage(Throwable 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.
|
||||
*/
|
||||
public BadCommandUsage(String 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 cause the cause.
|
||||
*/
|
||||
|
@@ -25,6 +25,11 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public abstract class BrigadierCommand<S> {
|
||||
|
||||
/**
|
||||
* Creates a Brigadier command.
|
||||
*/
|
||||
public BrigadierCommand() {}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a builder for this command.
|
||||
@@ -218,14 +223,14 @@ public abstract class BrigadierCommand<S> {
|
||||
/**
|
||||
* Wraps the provided {@link SuggestionsSupplier} into a Brigadier’s {@link SuggestionProvider}.
|
||||
* @param suggestions the suggestions to wrap.
|
||||
* @param senderUnwrapper function to convert the command sender provided by brigadier into the command sender
|
||||
* @param senderUnWrapper function to convert the command sender provided by brigadier into the command sender
|
||||
* supported by {@link SuggestionsSupplier}.
|
||||
* @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}.
|
||||
* @param <AS> the type of command sender supported by the {@link SuggestionsSupplier}.
|
||||
*/
|
||||
protected <AS> SuggestionProvider<S> wrapSuggestions(SuggestionsSupplier<AS> suggestions, Function<S, AS> senderUnwrapper) {
|
||||
protected <AS> SuggestionProvider<S> wrapSuggestions(SuggestionsSupplier<AS> suggestions, Function<S, AS> senderUnWrapper) {
|
||||
return (context, builder) -> {
|
||||
AS sender = senderUnwrapper.apply(context.getSource());
|
||||
AS sender = senderUnWrapper.apply(context.getSource());
|
||||
String message = builder.getInput();
|
||||
try {
|
||||
int tokenStartPos = builder.getStart();
|
||||
|
@@ -21,6 +21,11 @@ public abstract class BrigadierDispatcher<S> {
|
||||
|
||||
private final CommandDispatcher<S> dispatcher = new CommandDispatcher<>();
|
||||
|
||||
/**
|
||||
* Creates a new Dispatcher instance.
|
||||
*/
|
||||
public BrigadierDispatcher() {}
|
||||
|
||||
|
||||
/**
|
||||
* Registers the provided command node into this dispatcher.
|
||||
|
@@ -140,4 +140,8 @@ public class BrigadierSuggestionsUtil {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private BrigadierSuggestionsUtil() {}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -241,7 +241,7 @@ public interface SuggestionsSupplier<S> {
|
||||
return (s, ti, token, a) -> {
|
||||
try {
|
||||
List<Long> proposedValues = new ArrayList<>();
|
||||
if (token.length() == 0) {
|
||||
if (token.isEmpty()) {
|
||||
long start = Math.max(Math.max(Math.min(-4, max - 9), min), -9);
|
||||
long end = Math.min(Math.min(start + 9, max), 9);
|
||||
ListUtil.addLongRangeToList(proposedValues, start, end);
|
||||
@@ -399,7 +399,7 @@ public interface SuggestionsSupplier<S> {
|
||||
*/
|
||||
default SuggestionsSupplier<S> quotableString() {
|
||||
return (s, ti, token, a) -> {
|
||||
boolean startWithQuote = token.length() > 0 && (token.charAt(0) == '"' || token.charAt(0) == '\'');
|
||||
boolean startWithQuote = !token.isEmpty() && (token.charAt(0) == '"' || token.charAt(0) == '\'');
|
||||
String realToken = startWithQuote ? unescapeBrigadierQuotable(token.substring(1), token.charAt(0)) : token;
|
||||
String[] argsCopy = Arrays.copyOf(a, a.length);
|
||||
argsCopy[a.length - 1] = realToken;
|
||||
|
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;
|
||||
|
||||
import fr.pandacube.lib.chat.ChatColorUtil;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
package fr.pandacube.lib.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -56,7 +53,7 @@ public abstract class AbstractConfig {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String trimmedLine = line.trim();
|
||||
|
||||
if (ignoreEmpty && trimmedLine.equals(""))
|
||||
if (ignoreEmpty && trimmedLine.isEmpty())
|
||||
continue;
|
||||
|
||||
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.
|
||||
*/
|
@@ -1,4 +1,4 @@
|
||||
package fr.pandacube.lib.core.config;
|
||||
package fr.pandacube.lib.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
@@ -37,6 +37,12 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Cron expression interpreter -->
|
||||
<dependency>
|
||||
<groupId>ch.eitchnet</groupId>
|
||||
@@ -50,7 +56,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<version>3.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package fr.pandacube.lib.core.backup;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -70,6 +70,11 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a backup cleaner.
|
||||
*/
|
||||
public BackupCleaner() {}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link BackupCleaner} that keeps the archives kept by this {@link BackupCleaner} or by the provided
|
||||
@@ -102,14 +107,14 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
||||
if (files == null)
|
||||
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<>();
|
||||
|
||||
for (String filename : files) {
|
||||
File file = new File(archiveDir, filename);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -118,7 +123,7 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
||||
try {
|
||||
ldt = LocalDateTime.parse(dateTimeStr, BackupProcess.dateFileNameFormatter);
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -151,7 +156,7 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
||||
if (testOnly || oneDeleted)
|
||||
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;
|
||||
|
||||
import fc.cron.CronExpression;
|
||||
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||
import fr.pandacube.lib.core.cron.CronScheduler;
|
||||
import fr.pandacube.lib.util.FileUtils;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
@@ -209,7 +209,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
||||
File sourceDir = getSourceDir();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
||||
onBackupStart();
|
||||
|
||||
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);
|
||||
|
||||
@@ -229,7 +229,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
||||
|
||||
success = true;
|
||||
|
||||
Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET);
|
||||
Log.info("[Backup] Finished for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET);
|
||||
|
||||
try {
|
||||
BackupCleaner cleaner = getBackupCleaner();
|
||||
@@ -267,7 +267,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
||||
* Logs the scheduling status of this backup process.
|
||||
*/
|
||||
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())));
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
||||
public void logProgress() {
|
||||
if (compressor == null)
|
||||
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;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -53,7 +53,7 @@ public class RotatedLogsBackupProcess extends BackupProcess {
|
||||
if (!getSourceDir().isDirectory())
|
||||
return;
|
||||
|
||||
Log.info("[Backup] Starting for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " ...");
|
||||
Log.info("[Backup] Starting for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " ...");
|
||||
|
||||
try {
|
||||
// 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;
|
||||
|
||||
Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET);
|
||||
Log.info("[Backup] Finished for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET);
|
||||
} 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 {
|
||||
onBackupEnd(success);
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -117,7 +118,11 @@ public class ZipCompressor {
|
||||
}
|
||||
|
||||
for (Entry entry : entriesToCompress) {
|
||||
entry.zip();
|
||||
try {
|
||||
entry.zip();
|
||||
} catch (NoSuchFileException ignored) {
|
||||
// file has been deleted since
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (stateLock) {
|
||||
|
@@ -54,7 +54,7 @@ public class CronScheduler {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (!tasks.isEmpty()) {
|
||||
CronTask next = tasks.get(0);
|
||||
CronTask next = tasks.getFirst();
|
||||
if (next.nextRun <= now) {
|
||||
next.runAsync();
|
||||
setLastRun(next.taskId, now);
|
||||
@@ -224,5 +224,6 @@ public class CronScheduler {
|
||||
.toEpochMilli();
|
||||
}
|
||||
|
||||
private CronScheduler() {}
|
||||
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ public class Json {
|
||||
boolean isFloat = value.contains(".");
|
||||
|
||||
if (isFloat) {
|
||||
// if float, will only parse to Double
|
||||
// if is float, will only parse to Double
|
||||
// (see org.yaml.snakeyaml.constructor.SafeConstructor.ConstructYamlFloat)
|
||||
try {
|
||||
Double d = Double.valueOf(value);
|
||||
@@ -163,4 +163,6 @@ public class Json {
|
||||
}
|
||||
}*/
|
||||
|
||||
private Json() {}
|
||||
|
||||
}
|
||||
|
@@ -30,6 +30,8 @@ public class ThrowableAdapter implements JsonSerializer<Throwable>, JsonDeserial
|
||||
|
||||
/* package */ static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newTypeHierarchyFactory(Throwable.class, new ThrowableAdapter());
|
||||
|
||||
private ThrowableAdapter() {}
|
||||
|
||||
|
||||
@Override
|
||||
public Throwable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
@@ -132,30 +134,30 @@ public class ThrowableAdapter implements JsonSerializer<Throwable>, JsonDeserial
|
||||
}
|
||||
|
||||
private static <T extends Throwable> ThrowableSubAdapter<T> defaultSubAdapter(Class<T> clazz) {
|
||||
BiFunction<String, Throwable, T> constructor = null;
|
||||
BiFunction<String, Throwable, T> constructionFunction = null;
|
||||
|
||||
// try (String, Throwable) constructor
|
||||
try {
|
||||
Constructor<T> constr = clazz.getConstructor(String.class, Throwable.class);
|
||||
if (constr.canAccess(null)) {
|
||||
constructor = (m, t) -> ThrowableUtil.wrapReflectEx(() -> constr.newInstance(m, t));
|
||||
Constructor<T> constructor = clazz.getConstructor(String.class, Throwable.class);
|
||||
if (constructor.canAccess(null)) {
|
||||
constructionFunction = (m, t) -> ThrowableUtil.wrapReflectEx(() -> constructor.newInstance(m, t));
|
||||
}
|
||||
} catch (ReflectiveOperationException ignore) { }
|
||||
|
||||
// try (String) constructor
|
||||
try {
|
||||
Constructor<T> constr = clazz.getConstructor(String.class);
|
||||
if (constr.canAccess(null)) {
|
||||
constructor = ThrowableSubAdapter.messageOnly((m) -> ThrowableUtil.wrapReflectEx(() -> constr.newInstance(m)));
|
||||
Constructor<T> constructor = clazz.getConstructor(String.class);
|
||||
if (constructor.canAccess(null)) {
|
||||
constructionFunction = ThrowableSubAdapter.messageOnly((m) -> ThrowableUtil.wrapReflectEx(() -> constructor.newInstance(m)));
|
||||
}
|
||||
} catch (ReflectiveOperationException ignore) { }
|
||||
|
||||
if (constructor == null) {
|
||||
if (constructionFunction == null) {
|
||||
Log.warning("Provided Throwable class '" + clazz + "' does not have any of those constructors or are not accessible: (String, Throwable), (String).");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ThrowableSubAdapter<>(constructor);
|
||||
return new ThrowableSubAdapter<>(constructionFunction);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -237,5 +237,8 @@ public class TypeConverter {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private TypeConverter() {}
|
||||
|
||||
}
|
||||
|
@@ -58,6 +58,9 @@ public record MinecraftVersionList(
|
||||
|
||||
private static final TypeToken<Map<String, Integer>> MAP_STR_INT_TYPE = new TypeToken<>() { };
|
||||
private static final TypeToken<Map<Integer, List<String>>> MAP_INT_LIST_STRING_TYPE = new TypeToken<>() { };
|
||||
|
||||
private MinecraftVersionListAdapter() {}
|
||||
|
||||
@Override
|
||||
public MinecraftVersionList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(json instanceof JsonObject jsonObj))
|
||||
|
@@ -37,8 +37,8 @@ public class MinecraftVersionUtil {
|
||||
|
||||
/**
|
||||
* Decompose a version string into a series of integers.
|
||||
* @param v a string representation of a version (eg. 1.19.1).
|
||||
* @return an array of int representing the provided version (eg. [1, 19, 1]).
|
||||
* @param v a string representation of a version (e.g. 1.19.1).
|
||||
* @return an array of int representing the provided version (e.g. [1, 19, 1]).
|
||||
*/
|
||||
public static int[] decomposedVersion(String v) {
|
||||
try {
|
||||
@@ -114,7 +114,7 @@ public class MinecraftVersionUtil {
|
||||
else {
|
||||
// merge
|
||||
if (i - firstConsecutive > 1)
|
||||
keptVersions.add(versions.get(firstConsecutive) + "-" + versions.get(i));
|
||||
keptVersions.add(versions.get(firstConsecutive) + " - " + versions.get(i));
|
||||
else {
|
||||
keptVersions.add(versions.get(firstConsecutive));
|
||||
keptVersions.add(versions.get(i));
|
||||
@@ -135,4 +135,7 @@ public class MinecraftVersionUtil {
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
private MinecraftVersionUtil() {}
|
||||
|
||||
}
|
||||
|
@@ -68,12 +68,12 @@ public class ProtocolVersion implements Comparable<ProtocolVersion> {
|
||||
|
||||
private static void init() {
|
||||
// try online source first
|
||||
try {
|
||||
HttpResponse<String> response = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build()
|
||||
.send(HttpRequest.newBuilder(URI.create(ONLINE_DATA_URL)).build(),
|
||||
BodyHandlers.ofString()
|
||||
try (HttpClient cl = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build()) {
|
||||
HttpResponse<String> response = cl.send(
|
||||
HttpRequest.newBuilder(URI.create(ONLINE_DATA_URL)).build(),
|
||||
BodyHandlers.ofString()
|
||||
);
|
||||
if (response.statusCode() == 200) {
|
||||
MinecraftVersionList data = Json.gson.fromJson(response.body(), MinecraftVersionList.class);
|
||||
@@ -123,7 +123,7 @@ public class ProtocolVersion implements Comparable<ProtocolVersion> {
|
||||
|
||||
/**
|
||||
* Gets the {@link ProtocolVersion} associated with the provided Minecraft version.
|
||||
* @param version The Minecraft version, in the format "X.X[.X]" (eg. "1.17" or "1.8.8").
|
||||
* @param version The Minecraft version, in the format "X.X[.X]" (e.g. "1.17" or "1.8.8").
|
||||
* @return an instance of {@link ProtocolVersion}.
|
||||
*/
|
||||
public static ProtocolVersion ofVersion(String version) {
|
||||
|
@@ -64,7 +64,18 @@
|
||||
"1.20.1": 763,
|
||||
"1.20.2": 764,
|
||||
"1.20.3": 765,
|
||||
"1.20.4": 765
|
||||
"1.20.4": 765,
|
||||
"1.20.5": 766,
|
||||
"1.20.6": 766,
|
||||
"1.21": 767,
|
||||
"1.21.1": 767,
|
||||
"1.21.2": 768,
|
||||
"1.21.3": 768,
|
||||
"1.21.4": 769,
|
||||
"1.21.5": 770,
|
||||
"1.21.6": 771,
|
||||
"1.21.7": 772,
|
||||
"1.21.8": 772
|
||||
},
|
||||
"versionsOfProtocol": {
|
||||
"4": [
|
||||
@@ -211,6 +222,31 @@
|
||||
"765": [
|
||||
"1.20.3",
|
||||
"1.20.4"
|
||||
],
|
||||
"766": [
|
||||
"1.20.5",
|
||||
"1.20.6"
|
||||
],
|
||||
"767": [
|
||||
"1.21",
|
||||
"1.21.1"
|
||||
],
|
||||
"768": [
|
||||
"1.21.2",
|
||||
"1.21.3"
|
||||
],
|
||||
"769": [
|
||||
"1.21.4"
|
||||
],
|
||||
"770": [
|
||||
"1.21.5"
|
||||
],
|
||||
"771": [
|
||||
"1.21.6"
|
||||
],
|
||||
"772": [
|
||||
"1.21.7",
|
||||
"1.21.8"
|
||||
]
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>2.12.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<version>3.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@@ -56,29 +56,32 @@
|
||||
<artifact>org.apache.commons:commons-dbcp2</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||
<exclude>META-INF/versions/9/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.apache.commons:commons-pool2</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||
<exclude>META-INF/versions/9/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>commons-logging:commons-logging</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||
<exclude>META-INF/versions/9/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons</pattern>
|
||||
<shadedPattern>fr.pandacube.lib.db.shaded.commons</shadedPattern>
|
||||
<pattern>org.apache.commons.dbcp2</pattern>
|
||||
<shadedPattern>fr.pandacube.lib.db.shaded.commons.dbcp2</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons</pattern>
|
||||
<shadedPattern>fr.pandacube.lib.db.shaded.commons</shadedPattern>
|
||||
<pattern>org.apache.commons.pool2</pattern>
|
||||
<shadedPattern>fr.pandacube.lib.db.shaded.commons.pool2</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<transformers>
|
||||
|
@@ -227,7 +227,7 @@ public final class DB {
|
||||
*/
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer offset) throws DBException {
|
||||
SQLElementList<E> elements = getAll(elemClass, where, orderBy, 1, offset);
|
||||
return (elements.size() == 0) ? null : elements.get(0);
|
||||
return (elements.isEmpty()) ? null : elements.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,7 +23,8 @@ public class DBConnection {
|
||||
public DBConnection(String host, int port, String dbname, String login, String password) {
|
||||
this("jdbc:mysql://" + host + ":" + port + "/" + dbname
|
||||
+ "?useUnicode=true"
|
||||
+ "&useSSL=false"
|
||||
+ "&sslMode=DISABLED"
|
||||
+ "&allowPublicKeyRetrieval=true"
|
||||
+ "&characterEncoding=utf8"
|
||||
+ "&characterSetResults=utf8"
|
||||
+ "&character_set_server=utf8mb4"
|
||||
|
@@ -16,6 +16,11 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
|
||||
/**
|
||||
* Creates an empty list of sql elements.
|
||||
*/
|
||||
public SQLElementList() {}
|
||||
|
||||
/**
|
||||
* Stores all the values modified by {@link #setCommon(SQLField, Object)}.
|
||||
*/
|
||||
|
@@ -232,12 +232,8 @@ public class SQLField<E extends SQLElement<E>, T> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public Object fromJavaTypeToJDBCType(Object value) throws DBException {
|
||||
/* package */ Object fromJavaTypeToJDBCType(Object value) throws DBException {
|
||||
Object ret = value;
|
||||
if (value != null && type instanceof SQLCustomType customType) {
|
||||
try {
|
||||
@@ -250,7 +246,7 @@ public class SQLField<E extends SQLElement<E>, T> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Collection<Object> fromListJavaTypeToJDBCType(Collection<?> values) throws DBException {
|
||||
/* package */ Collection<Object> fromListJavaTypeToJDBCType(Collection<?> values) throws DBException {
|
||||
if (values == null)
|
||||
return null;
|
||||
List<Object> ret = new ArrayList<>(values.size());
|
||||
|
@@ -42,7 +42,7 @@ public class SQLType<T> {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof SQLType o
|
||||
return obj instanceof SQLType<?> o
|
||||
&& toString().equals(o.toString());
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,11 @@ import fr.pandacube.lib.util.log.Log;
|
||||
*/
|
||||
public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||
|
||||
/**
|
||||
* Creates a SQL WHERE expression.
|
||||
*/
|
||||
protected SQLWhere() {}
|
||||
|
||||
/* package */ abstract ParameterizedSQLString toSQL() throws DBException;
|
||||
|
||||
@Override
|
||||
@@ -69,6 +74,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||
* Create a custom SQL {@code WHERE} expression.
|
||||
* @param whereExpr the raw SQL {@code WHERE} expression.
|
||||
* @return a new SQL {@code WHERE} expression.
|
||||
* @param <E> the table type.
|
||||
*/
|
||||
public static <E extends SQLElement<E>> SQLWhere<E> expression(String whereExpr) {
|
||||
return expression(whereExpr, List.of());
|
||||
@@ -79,6 +85,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||
* @param whereExpr the raw SQL {@code WHERE} expression.
|
||||
* @param params the parameters of the provided expression.
|
||||
* @return a new SQL {@code WHERE} expression.
|
||||
* @param <E> the table type.
|
||||
*/
|
||||
public static <E extends SQLElement<E>> SQLWhere<E> expression(String whereExpr, List<Object> params) {
|
||||
return new SQLWhereCustomExpression<>(whereExpr, params);
|
||||
@@ -89,6 +96,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||
* @param leftExpr the raw SQL left operand.
|
||||
* @param valuesIn the values on the right of the {@code IN} operator.
|
||||
* @return a new SQL {@code WHERE} expression.
|
||||
* @param <E> the table type.
|
||||
*/
|
||||
public static <E extends SQLElement<E>> SQLWhere<E> expressionIn(String leftExpr, Collection<?> valuesIn) {
|
||||
return expressionIn(leftExpr, List.of(), valuesIn);
|
||||
@@ -100,6 +108,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
|
||||
* @param leftParams the parameters of the left operand.
|
||||
* @param valuesIn the values on the right of the {@code IN} operator.
|
||||
* @return a new SQL {@code WHERE} expression.
|
||||
* @param <E> the table type.
|
||||
*/
|
||||
public static <E extends SQLElement<E>> SQLWhere<E> expressionIn(String leftExpr, List<Object> leftParams, Collection<?> valuesIn) {
|
||||
return new SQLWhereInCustom<>(leftExpr, leftParams, valuesIn);
|
||||
|
@@ -21,4 +21,8 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<maven.javadoc.skip>true</maven.javadoc.skip>
|
||||
</properties>
|
||||
|
||||
</project>
|
@@ -16,7 +16,7 @@
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
|
||||
<!-- WorldEdit -->
|
||||
|
@@ -82,6 +82,12 @@ public class PandalibPaperPermissions implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link PandalibPaperPermissions} instance.
|
||||
*/
|
||||
private PandalibPaperPermissions() {}
|
||||
|
||||
/**
|
||||
* Player login event handler.
|
||||
* @param event the event.
|
||||
|
@@ -7,6 +7,7 @@ import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
|
||||
import java.util.List;
|
||||
@@ -120,6 +121,13 @@ import java.util.List;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean playerAdd(Player player, String permission) {
|
||||
// override because the super class sets the permission on the current world of the player, that is probably
|
||||
// not intended by the calling plugin
|
||||
return playerAdd(null, player, permission);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean playerRemove(String world, String player, String permission) {
|
||||
@@ -136,6 +144,14 @@ import java.util.List;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean playerRemove(Player player, String permission) {
|
||||
// to stay coherent with the override of #playerAdd(Player, String), we also override this method.
|
||||
// Will try first to remove the permission on the world itself (like super-method), then if it doesn't exist,
|
||||
// removes on the server level.
|
||||
return super.playerRemove(player, permission) || playerRemove(null, player, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean groupHas(String world, String group, String permission) {
|
||||
checkEnabled();
|
||||
|
@@ -16,12 +16,17 @@
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>fabricmc</id>
|
||||
<url>https://maven.fabricmc.net/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>minecraft-libraries</id>
|
||||
<name>Minecraft Libraries</name>
|
||||
<url>https://libraries.minecraft.net</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@@ -71,6 +76,12 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-bungee-chat</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-paper-permissions</artifactId>
|
||||
@@ -78,25 +89,18 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>datafixerupper</artifactId>
|
||||
<version>${datafixerupper.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Paper -->
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${paper.version}-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-mojangapi</artifactId>
|
||||
<version>${paper.version}-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Needed to read obfuscation mapping file. Already included in Paper -->
|
||||
<dependency>
|
||||
<groupId>net.fabricmc</groupId>
|
||||
<artifactId>mapping-io</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@@ -5,27 +5,47 @@ import fr.pandacube.lib.paper.json.PaperJson;
|
||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Main class for pandalib-paper.
|
||||
*/
|
||||
public class PandaLibPaper {
|
||||
|
||||
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) {
|
||||
PandaLibPaper.plugin = plugin;
|
||||
PaperJson.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to call in plugin's {@link Plugin#onEnable()} method.
|
||||
*/
|
||||
public static void onEnable() {
|
||||
PerformanceAnalysisManager.getInstance(); // initialize
|
||||
ServerStopEvent.init();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Method to call in plugin's {@link Plugin#onDisable()} method.
|
||||
*/
|
||||
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() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private PandaLibPaper() {}
|
||||
|
||||
}
|
||||
|
@@ -6,14 +6,65 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A basic class holding configuration for {@link PaperBackupManager}.
|
||||
*/
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Set to true to enable the backup of logs.
|
||||
* Defaults to 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
|
||||
|
||||
/**
|
||||
* The backup target directory.
|
||||
* Must be set (defaults to 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));
|
||||
|
||||
/**
|
||||
* 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));
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
}
|
||||
|
@@ -23,12 +23,19 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
/**
|
||||
* The backup manager for Paper servers.
|
||||
*/
|
||||
public class PaperBackupManager extends BackupManager implements Listener {
|
||||
|
||||
private final Map<String, PaperWorldProcess> compressWorlds = new HashMap<>();
|
||||
|
||||
PaperBackupConfig config;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate a new backup manager.
|
||||
* @param config the configuration of the backups.
|
||||
*/
|
||||
public PaperBackupManager(PaperBackupConfig config) {
|
||||
super(config.backupDirectory);
|
||||
setConfig(config);
|
||||
@@ -49,13 +56,17 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
||||
super.addProcess(process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the backups config
|
||||
* @param config the new config.
|
||||
*/
|
||||
public void setConfig(PaperBackupConfig config) {
|
||||
this.config = config;
|
||||
backupQueue.forEach(this::updateProcessConfig);
|
||||
}
|
||||
|
||||
|
||||
public void updateProcessConfig(BackupProcess process) {
|
||||
private void updateProcessConfig(BackupProcess process) {
|
||||
if (process instanceof PaperWorkdirProcess) {
|
||||
process.setEnabled(config.workdirBackupEnabled);
|
||||
process.setBackupCleaner(config.workdirBackupCleaner);
|
||||
@@ -119,12 +130,12 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
||||
private final Set<String> dirtyForSave = new HashSet<>();
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
void onWorldLoad(WorldLoadEvent event) {
|
||||
initWorldProcess(event.getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldSave(WorldSaveEvent event) {
|
||||
void onWorldSave(WorldSaveEvent event) {
|
||||
if (event.getWorld().getLoadedChunks().length > 0
|
||||
|| dirtyForSave.contains(event.getWorld().getName())) {
|
||||
compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave();
|
||||
@@ -137,18 +148,18 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
||||
void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
||||
dirtyForSave.add(event.getFrom().getName());
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
void onPlayerJoin(PlayerJoinEvent event) {
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
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 org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* A backup process with specific logic around Paper server.
|
||||
*/
|
||||
public abstract class PaperBackupProcess extends BackupProcess {
|
||||
|
||||
|
||||
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) {
|
||||
super(bm, id);
|
||||
}
|
||||
|
@@ -3,8 +3,15 @@ package fr.pandacube.lib.paper.backup;
|
||||
import java.io.File;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* A backup process with specific logic around Paper server working directory.
|
||||
*/
|
||||
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) {
|
||||
super(bm, "workdir");
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||
import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
||||
import fr.pandacube.lib.paper.world.WorldUtil;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
@@ -11,14 +11,22 @@ import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A backup process with specific logic around Paper server world.
|
||||
*/
|
||||
public class PaperWorldProcess extends PaperBackupProcess {
|
||||
private final String worldName;
|
||||
|
||||
private boolean autoSave = true;
|
||||
|
||||
protected PaperWorldProcess(PaperBackupManager bm, final String n) {
|
||||
super(bm, "worlds/" + n);
|
||||
worldName = n;
|
||||
private boolean autoSave = true;
|
||||
|
||||
/**
|
||||
* Instantiates a new backup process for a world.
|
||||
* @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() {
|
||||
@@ -62,11 +70,11 @@ public class PaperWorldProcess extends PaperBackupProcess {
|
||||
|
||||
public void displayNextSchedule() {
|
||||
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())));
|
||||
}
|
||||
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() {
|
||||
if (!isDirty()) { // don't set dirty if it is already
|
||||
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)
|
||||
.format(new Date(getNext()))
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package fr.pandacube.lib.paper.commands;
|
||||
|
||||
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
@@ -13,116 +13,124 @@ import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.commands.BadCommandUsage;
|
||||
import fr.pandacube.lib.commands.BrigadierCommand;
|
||||
import fr.pandacube.lib.commands.SuggestionsSupplier;
|
||||
import fr.pandacube.lib.paper.permissions.PandalibPaperPermissions;
|
||||
import fr.pandacube.lib.paper.reflect.PandalibPaperReflect;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftVector;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.VanillaCommandWrapper;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.BlockPosArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Commands;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ComponentArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Coordinates;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntityArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntitySelector;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Vec3Argument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.BlockPos;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerPlayer;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.PaperAdventure;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.APICommandMeta;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.BukkitCommandNode;
|
||||
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
||||
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
||||
import fr.pandacube.lib.players.standalone.AbstractPlayerManager;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
import fr.pandacube.lib.reflect.Reflect;
|
||||
import fr.pandacube.lib.reflect.ReflectClass;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.command.defaults.BukkitCommand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerCommandSendEvent;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
||||
import static fr.pandacube.lib.reflect.wrapper.ReflectWrapper.wrap;
|
||||
|
||||
/**
|
||||
* Abstract class to hold a command to be integrated into a Paper server vanilla command dispatcher.
|
||||
*/
|
||||
public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBrigadierCommandSource> implements Listener {
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSourceStack> implements Listener {
|
||||
|
||||
private static final Commands vanillaCommandDispatcher;
|
||||
private static final CommandDispatcher<BukkitBrigadierCommandSource> nmsDispatcher;
|
||||
private static CommandDispatcher<CommandSourceStack> vanillaPaperDispatcher = null;
|
||||
|
||||
static {
|
||||
wrapEx(PandalibPaperReflect::init);
|
||||
vanillaCommandDispatcher = ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class)
|
||||
.getServer()
|
||||
.vanillaCommandDispatcher();
|
||||
nmsDispatcher = vanillaCommandDispatcher.dispatcher();
|
||||
/**
|
||||
* 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() {
|
||||
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() {
|
||||
return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot();
|
||||
}
|
||||
|
||||
private static void updateVanillaPaperDispatcher(CommandDispatcher<CommandSourceStack> newDispatcher) {
|
||||
if (vanillaPaperDispatcher == null || newDispatcher != vanillaPaperDispatcher) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Removes a plugin command that overrides a vanilla command, so the vanilla command functionalities are fully
|
||||
* restored (so, not only the usage, but also the suggestions and the command structure sent to the client).
|
||||
* @param name the name of the command to restore.
|
||||
*/
|
||||
public static void restoreVanillaCommand(String name) {
|
||||
CommandMap bukkitCmdMap = Bukkit.getCommandMap();
|
||||
Command bukkitCommand = bukkitCmdMap.getCommand(name);
|
||||
if (bukkitCommand != null) {
|
||||
if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCommand)) {
|
||||
//Log.info("Command /" + name + " is already a vanilla command.");
|
||||
|
||||
PandaLibPaper.getPlugin().getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS,
|
||||
event -> updateVanillaPaperDispatcher(event.registrar().getDispatcher()));
|
||||
|
||||
|
||||
Bukkit.getServer().getScheduler().runTask(PandaLibPaper.getPlugin(), () -> {
|
||||
if (vanillaPaperDispatcher == null)
|
||||
return;
|
||||
|
||||
CommandNode<CommandSourceStack> targetCommand = vanillaPaperDispatcher.getRoot().getChild("minecraft:" + name);
|
||||
if (targetCommand == null) {
|
||||
Log.warning("There is no vanilla command '" + name + "' to restore.");
|
||||
return;
|
||||
}
|
||||
Log.info("Removing Bukkit command /" + name + " (" + getCommandIdentity(bukkitCommand) + ")");
|
||||
bukkitCmdMap.getKnownCommands().remove(name.toLowerCase(java.util.Locale.ENGLISH));
|
||||
bukkitCommand.unregister(bukkitCmdMap);
|
||||
|
||||
LiteralCommandNode<BukkitBrigadierCommandSource> node = (LiteralCommandNode<BukkitBrigadierCommandSource>) getRootNode().getChild(name);
|
||||
Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance();
|
||||
bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand);
|
||||
newCommand.register(bukkitCmdMap);
|
||||
}
|
||||
CommandNode<CommandSourceStack> eventuallyBadCommandToReplace = vanillaPaperDispatcher.getRoot().getChild(name);
|
||||
Boolean isPluginCommand = isPluginCommand(eventuallyBadCommandToReplace);
|
||||
if (isPluginCommand != null && isPluginCommand) {
|
||||
Log.info(getCommandIdentity(eventuallyBadCommandToReplace) + " found in the dispatcher. Restoring the vanilla command.");
|
||||
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(name));
|
||||
vanillaPaperDispatcher.getRoot().addChild(getAliasNode(targetCommand, name));
|
||||
}
|
||||
/*else if (isPluginCommand == null) {
|
||||
Log.info(getCommandIdentity(eventuallyBadCommandToReplace) + " found in the dispatcher. Unsure if we restore the vanilla command.");
|
||||
}*/
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the vanilla instance of the Brigadier dispatcher.
|
||||
* @return the vanilla instance of the Brigadier dispatcher.
|
||||
*/
|
||||
public static CommandDispatcher<BukkitBrigadierCommandSource> getNMSDispatcher() {
|
||||
return nmsDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root command node of the Brigadier dispatcher.
|
||||
* @return the root command node of the Brigadier dispatcher.
|
||||
*/
|
||||
protected static RootCommandNode<BukkitBrigadierCommandSource> getRootNode() {
|
||||
return nmsDispatcher.getRoot();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -131,7 +139,16 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
/**
|
||||
* The command node of this command.
|
||||
*/
|
||||
protected final LiteralCommandNode<BukkitBrigadierCommandSource> commandNode;
|
||||
protected LiteralCommandNode<CommandSourceStack> commandNode;
|
||||
/**
|
||||
* The command requested aliases.
|
||||
*/
|
||||
protected final String[] aliases;
|
||||
|
||||
/**
|
||||
* The command description.
|
||||
*/
|
||||
protected final String description;
|
||||
|
||||
private final RegistrationPolicy registrationPolicy;
|
||||
|
||||
@@ -146,13 +163,13 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
public PaperBrigadierCommand(Plugin pl, RegistrationPolicy regPolicy) {
|
||||
plugin = pl;
|
||||
registrationPolicy = regPolicy;
|
||||
commandNode = buildCommand().build();
|
||||
postBuildCommand(commandNode);
|
||||
String[] aliasesTmp = getAliases();
|
||||
aliases = aliasesTmp == null ? new String[0] : aliasesTmp;
|
||||
description = getDescription();
|
||||
register();
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
try {
|
||||
PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase());
|
||||
} catch (NoClassDefFoundError ignored) { }
|
||||
//try {
|
||||
// PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase());
|
||||
//} catch (NoClassDefFoundError ignored) { }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,163 +184,193 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
|
||||
|
||||
private void register() {
|
||||
plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
|
||||
updateVanillaPaperDispatcher(event.registrar().getDispatcher());
|
||||
|
||||
String[] aliases = getAliases();
|
||||
if (aliases == null)
|
||||
aliases = new String[0];
|
||||
commandNode = buildCommand().build();
|
||||
postBuildCommand(commandNode);
|
||||
|
||||
String pluginName = plugin.getName().toLowerCase();
|
||||
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.");
|
||||
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(commandNode.getName()));
|
||||
}
|
||||
|
||||
registeredAliases = new HashSet<>();
|
||||
registerNode(commandNode, false);
|
||||
registerAlias(pluginName + ":" + commandNode.getLiteral(), true);
|
||||
registeredAliases = new HashSet<>(event.registrar().register(commandNode, description, List.of(aliases)));
|
||||
doPostRegistrationFixes();
|
||||
|
||||
for (String alias : aliases) {
|
||||
registerAlias(alias, false);
|
||||
registerAlias(pluginName + ":" + alias, true);
|
||||
@SuppressWarnings("unchecked")
|
||||
fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode<CommandSourceStack> registeredNode = wrap(vanillaPaperDispatcher.getRoot().getChild(commandNode.getName()), fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class);
|
||||
|
||||
|
||||
if (registrationPolicy == RegistrationPolicy.ALL) {
|
||||
// enforce registration of aliases
|
||||
for (String alias : aliases) {
|
||||
if (!registeredAliases.contains(alias)) {
|
||||
Log.info("Command /" + commandNode.getName() + ": forcing registration of alias " + alias);
|
||||
registeredAliases.addAll(event.registrar().register(getAliasNode(commandNode, alias), description));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Bukkit.getServer().getScheduler().runTask(plugin, () -> {
|
||||
if (vanillaPaperDispatcher == null)
|
||||
return;
|
||||
|
||||
Set<String> forceRegistrationAgain = new HashSet<>();
|
||||
forceRegistrationAgain.add(commandNode.getName());
|
||||
if (registrationPolicy == RegistrationPolicy.ALL)
|
||||
forceRegistrationAgain.addAll(List.of(aliases));
|
||||
|
||||
for (String aliasToForce : forceRegistrationAgain) {
|
||||
CommandNode<CommandSourceStack> actualNode = vanillaPaperDispatcher.getRoot().getChild(aliasToForce);
|
||||
@SuppressWarnings("unchecked")
|
||||
fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode<CommandSourceStack> wrappedCommandNode = wrap(actualNode, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class);
|
||||
if (actualNode != null) {
|
||||
if (wrappedCommandNode.apiCommandMeta() != null) {
|
||||
APICommandMeta meta = wrappedCommandNode.apiCommandMeta();
|
||||
if (meta.plugin().equals(plugin))
|
||||
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;
|
||||
}
|
||||
Log.warning("Forcing registration of alias /" + aliasToForce + " for command /" + commandNode.getName() + ": replacing " + getCommandIdentity(actualNode));
|
||||
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(aliasToForce));
|
||||
}
|
||||
/*else {
|
||||
Log.info("Forcing registration of alias /" + aliasToForce + " for command /" + commandNode.getName() + ": no command found for alias. Adding alias.");
|
||||
}*/
|
||||
LiteralCommandNode<CommandSourceStack> newPCN = getAliasNode(commandNode, aliasToForce);
|
||||
@SuppressWarnings("unchecked")
|
||||
fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode<CommandSourceStack> wrappedNewPCN = wrap(newPCN, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class);
|
||||
wrappedNewPCN.apiCommandMeta(registeredNode.apiCommandMeta());
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void registerAlias(String alias, boolean prefixed) {
|
||||
LiteralCommandNode<BukkitBrigadierCommandSource> node = literal(alias)
|
||||
private static LiteralCommandNode<CommandSourceStack> getAliasNode(CommandNode<CommandSourceStack> commandNode, String alias) {
|
||||
return LiteralArgumentBuilder.<CommandSourceStack>literal(alias)
|
||||
.requires(commandNode.getRequirement())
|
||||
.executes(commandNode.getCommand())
|
||||
.redirect(commandNode)
|
||||
.build();
|
||||
registerNode(node, prefixed);
|
||||
}
|
||||
|
||||
private static String getCommandIdentity(CommandNode<CommandSourceStack> command) {
|
||||
|
||||
private void registerNode(LiteralCommandNode<BukkitBrigadierCommandSource> node, boolean prefixed) {
|
||||
RootCommandNode<BukkitBrigadierCommandSource> root = getRootNode();
|
||||
String name = node.getLiteral();
|
||||
boolean isAlias = node.getRedirect() == commandNode;
|
||||
boolean forceRegistration = switch (registrationPolicy) {
|
||||
case NONE -> false;
|
||||
case ONLY_BASE_COMMAND -> prefixed || !isAlias;
|
||||
case ALL -> true;
|
||||
};
|
||||
|
||||
// nmsDispatcher integration and conflit resolution
|
||||
boolean nmsRegister = false, nmsRegistered = false;
|
||||
CommandNode<BukkitBrigadierCommandSource> nmsConflicted = root.getChild(name);
|
||||
if (nmsConflicted != null) {
|
||||
|
||||
if (isFromThisCommand(nmsConflicted)) {
|
||||
// this command is already registered in NMS. Don’t need to register again
|
||||
nmsRegistered = true;
|
||||
if (BukkitCommandNode.REFLECT.get().isInstance(command)) {
|
||||
BukkitCommandNode wrappedBCN = wrap(command, BukkitCommandNode.class);
|
||||
Command bukkitCmd = wrappedBCN.getBukkitCommand();
|
||||
if (bukkitCmd instanceof PluginCommand cmd) {
|
||||
return "Node /" + command.getName() + " wrapping Bukkit command /" + bukkitCmd.getName() + " from plugin " + cmd.getPlugin().getName();
|
||||
}
|
||||
else if (forceRegistration) {
|
||||
nmsRegister = true;
|
||||
Log.info("Overwriting Brigadier command /" + name);
|
||||
}
|
||||
else if (prefixed || !isAlias) {
|
||||
Log.severe("/" + name + " already in NMS Brigadier instance."
|
||||
+ " Wont replace it because registration is not forced for prefixed or initial name of a command.");
|
||||
}
|
||||
else { // conflict, won't replace, not forced but only an alias anyway
|
||||
Log.info("/" + name + " already in NMS Brigadier instance."
|
||||
+ " Wont replace it because registration is not forced for a non-prefixed alias.");
|
||||
else if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) {
|
||||
VanillaCommandWrapper vcw = wrap(bukkitCmd, VanillaCommandWrapper.class);
|
||||
CommandNode<CommandSourceStack> vanillaCmd = vcw.vanillaCommand();
|
||||
if (vanillaCmd != command)
|
||||
return "Node /" + command.getName() + " wrapping non-plugin command /" + bukkitCmd.getName() + " wrapping: " + getCommandIdentity(vcw.vanillaCommand());
|
||||
else
|
||||
return "Node /" + command.getName() + " wrapping non-plugin command /" + bukkitCmd.getName() + " wrapping back the node (risk of StackOverflow?)";
|
||||
}
|
||||
else
|
||||
return "Node /" + command.getName() + " wrapping " + bukkitCmd.getClass().getName() + " /" + bukkitCmd.getName();
|
||||
}
|
||||
else {
|
||||
nmsRegister = true;
|
||||
}
|
||||
|
||||
if (nmsRegister) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var rCommandNode = ReflectWrapper.wrapTyped(root, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class);
|
||||
rCommandNode.removeCommand(name);
|
||||
root.addChild(node);
|
||||
nmsRegistered = true;
|
||||
}
|
||||
fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode<CommandSourceStack> wrappedCommandNode = wrap(command, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class);
|
||||
|
||||
if (!nmsRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
registeredAliases.add(name);
|
||||
|
||||
// bukkit dispatcher conflict resolution
|
||||
boolean bukkitRegister = false;
|
||||
CommandMap bukkitCmdMap = Bukkit.getCommandMap();
|
||||
Command bukkitConflicted = bukkitCmdMap.getCommand(name);
|
||||
if (bukkitConflicted != null) {
|
||||
if (!isFromThisCommand(bukkitConflicted)) {
|
||||
if (forceRegistration) {
|
||||
bukkitRegister = true;
|
||||
Log.info("Overwriting Bukkit command /" + name
|
||||
+ " (" + getCommandIdentity(bukkitConflicted) + ")");
|
||||
}
|
||||
else if (prefixed || !isAlias) {
|
||||
Log.severe("/" + name + " already in Bukkit dispatcher (" + getCommandIdentity(bukkitConflicted) + ")." +
|
||||
" Wont replace it because registration is not forced for prefixed or initial name of a command.");
|
||||
}
|
||||
else {
|
||||
Log.info("/" + name + " already in Bukkit dispatcher (" + getCommandIdentity(bukkitConflicted) + ")." +
|
||||
" Wont replace it because registration is not forced for a non-prefixed alias.");
|
||||
}
|
||||
if (wrappedCommandNode.apiCommandMeta() != null) {
|
||||
APICommandMeta meta = wrappedCommandNode.apiCommandMeta();
|
||||
return "Node /" + command.getName() + " from plugin " + meta.plugin().getName();
|
||||
}
|
||||
else {
|
||||
return "Node /" + command.getName() + " (unspecific)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Boolean isPluginCommand(CommandNode<CommandSourceStack> command) {
|
||||
if (BukkitCommandNode.REFLECT.get().isInstance(command)) {
|
||||
BukkitCommandNode wrappedBCN = wrap(command, BukkitCommandNode.class);
|
||||
Command bukkitCmd = wrappedBCN.getBukkitCommand();
|
||||
if (bukkitCmd instanceof PluginCommand) {
|
||||
return true;
|
||||
}
|
||||
else if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) {
|
||||
VanillaCommandWrapper vcw = wrap(bukkitCmd, VanillaCommandWrapper.class);
|
||||
CommandNode<CommandSourceStack> vanillaCmd = vcw.vanillaCommand();
|
||||
if (vanillaCmd != command)
|
||||
return isPluginCommand(vcw.vanillaCommand());
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
bukkitRegister = true;
|
||||
@SuppressWarnings("unchecked")
|
||||
fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode<CommandSourceStack> wrappedCommandNode = wrap(command, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class);
|
||||
return wrappedCommandNode.apiCommandMeta() != null;
|
||||
}
|
||||
|
||||
if (bukkitRegister) {
|
||||
bukkitCmdMap.getKnownCommands().remove(name.toLowerCase());
|
||||
if (bukkitConflicted != null)
|
||||
bukkitConflicted.unregister(bukkitCmdMap);
|
||||
|
||||
Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance();
|
||||
bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand);
|
||||
newCommand.register(bukkitCmdMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean isFromThisCommand(CommandNode<BukkitBrigadierCommandSource> node) {
|
||||
return node == commandNode || node.getRedirect() == commandNode;
|
||||
}
|
||||
|
||||
private boolean isFromThisCommand(Command bukkitCmd) {
|
||||
if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) {
|
||||
return isFromThisCommand(ReflectWrapper.wrapTyped((BukkitCommand) bukkitCmd, VanillaCommandWrapper.class).vanillaCommand());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getCommandIdentity(Command bukkitCmd) {
|
||||
if (bukkitCmd instanceof PluginCommand cmd) {
|
||||
return "Bukkit command: /" + cmd.getName() + " from plugin " + cmd.getPlugin().getName();
|
||||
}
|
||||
else if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) {
|
||||
return "Vanilla command: /" + bukkitCmd.getName();
|
||||
}
|
||||
else
|
||||
return bukkitCmd.getClass().getName() + ": /" + bukkitCmd.getName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Player command sender event handler.
|
||||
* @param event the event.
|
||||
* Gets the aliases that are actually registered in the server.
|
||||
* @return the actually registered aliases.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onPlayerCommandSend(PlayerCommandSendEvent event) {
|
||||
event.getCommands().removeAll(registeredAliases.stream().map(s -> "minecraft:" + s).toList());
|
||||
protected Set<String> getRegisteredAliases() {
|
||||
return Set.copyOf(registeredAliases);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Server load event handler.
|
||||
* @param event the event.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onServerLoad(ServerLoadEvent event) {
|
||||
register();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -342,6 +389,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
*/
|
||||
protected abstract String getTargetPermission();
|
||||
|
||||
/**
|
||||
* Returns the permission that should be tested instead of "minecraft.command.cmdName". The conversion from the
|
||||
* minecraft prefixed permission node to the returned node is done by the {@code pandalib-paper-permissions} if it
|
||||
* is present in the classpath during runtime.
|
||||
* @return the permission that should be tested instead of "minecraft.command.cmdName".
|
||||
*/
|
||||
protected String getDescription() {
|
||||
return "A command from " + plugin.getName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -355,13 +411,17 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
|
||||
|
||||
|
||||
public boolean isConsole(BukkitBrigadierCommandSource wrapper) {
|
||||
|
||||
@Override
|
||||
public boolean isConsole(CommandSourceStack wrapper) {
|
||||
return isConsole(getCommandSender(wrapper));
|
||||
}
|
||||
public boolean isPlayer(BukkitBrigadierCommandSource wrapper) {
|
||||
@Override
|
||||
public boolean isPlayer(CommandSourceStack wrapper) {
|
||||
return isPlayer(getCommandSender(wrapper));
|
||||
}
|
||||
public Predicate<BukkitBrigadierCommandSource> hasPermission(String permission) {
|
||||
@Override
|
||||
public Predicate<CommandSourceStack> hasPermission(String permission) {
|
||||
return wrapper -> getCommandSender(wrapper).hasPermission(permission);
|
||||
}
|
||||
|
||||
@@ -395,7 +455,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* @param context the command context from which to get the Bukkit command sender.
|
||||
* @return the Bukkit command sender.
|
||||
*/
|
||||
public static CommandSender getCommandSender(CommandContext<BukkitBrigadierCommandSource> context) {
|
||||
public static CommandSender getCommandSender(CommandContext<CommandSourceStack> context) {
|
||||
return getCommandSender(context.getSource());
|
||||
}
|
||||
|
||||
@@ -404,8 +464,8 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* @param wrapper the wrapper from which to get the Bukkit command sender.
|
||||
* @return the Bukkit command sender.
|
||||
*/
|
||||
public static CommandSender getCommandSender(BukkitBrigadierCommandSource wrapper) {
|
||||
return wrapper.getBukkitSender();
|
||||
public static CommandSender getCommandSender(CommandSourceStack wrapper) {
|
||||
return wrapper.getSender();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,8 +473,8 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* @param sender the command sender.
|
||||
* @return a new instance of a command sender wrapper for the provided command sender.
|
||||
*/
|
||||
public static BukkitBrigadierCommandSource getBrigadierCommandSource(CommandSender sender) {
|
||||
return VanillaCommandWrapper.getListener(sender);
|
||||
public static CommandSourceStack getBrigadierCommandSource(CommandSender sender) {
|
||||
throw new UnsupportedOperationException("The 1.20.6 Paper API update uses a different wrapper for Brigadier command sender.");
|
||||
}
|
||||
|
||||
|
||||
@@ -443,7 +503,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* @param suggestions the suggestions to wrap.
|
||||
* @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}.
|
||||
*/
|
||||
public SuggestionProvider<BukkitBrigadierCommandSource> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) {
|
||||
public SuggestionProvider<CommandSourceStack> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) {
|
||||
return wrapSuggestions(suggestions, PaperBrigadierCommand::getCommandSender);
|
||||
}
|
||||
|
||||
@@ -456,7 +516,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* @param cmd the command executor to wrap.
|
||||
* @return a wrapper command executor.
|
||||
*/
|
||||
protected static com.mojang.brigadier.Command<BukkitBrigadierCommandSource> wrapCommand(com.mojang.brigadier.Command<BukkitBrigadierCommandSource> cmd) {
|
||||
protected static com.mojang.brigadier.Command<CommandSourceStack> wrapCommand(com.mojang.brigadier.Command<CommandSourceStack> cmd) {
|
||||
return context -> {
|
||||
try {
|
||||
return cmd.run(context);
|
||||
@@ -484,117 +544,6 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* Minecraft's argument type
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Brigadier argument type {@code minecraft:entity}.
|
||||
* @param singleTarget if this argument takes only a single target.
|
||||
* @param playersOnly if this argument takes players only.
|
||||
* @return the {@code minecraft:entity} argument type with the specified parameters.
|
||||
*/
|
||||
public static ArgumentType<Object> argumentMinecraftEntity(boolean singleTarget, boolean playersOnly) {
|
||||
if (playersOnly) {
|
||||
return singleTarget ? EntityArgument.player() : EntityArgument.players();
|
||||
}
|
||||
else {
|
||||
return singleTarget ? EntityArgument.entity() : EntityArgument.entities();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided argument of type {@code minecraft:entity} (list of entities), from the provided context.
|
||||
* @param context the command execution context.
|
||||
* @param argument the argument name.
|
||||
* @return the value of the argument, or null if not found.
|
||||
*/
|
||||
public List<Entity> tryGetMinecraftEntityArgument(CommandContext<BukkitBrigadierCommandSource> context, String argument) {
|
||||
EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class);
|
||||
if (es == null)
|
||||
return null;
|
||||
List<fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity> nmsEntityList = es.findEntities(context.getSource());
|
||||
List<Entity> entityList = new ArrayList<>(nmsEntityList.size());
|
||||
for (fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity : nmsEntityList) {
|
||||
entityList.add(nmsEntity.getBukkitEntity());
|
||||
}
|
||||
return entityList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided argument of type {@code minecraft:entity} (list of players), from the provided context.
|
||||
* @param context the command execution context.
|
||||
* @param argument the argument name.
|
||||
* @return the value of the argument, or null if not found.
|
||||
*/
|
||||
public List<Player> tryGetMinecraftEntityArgumentPlayers(CommandContext<BukkitBrigadierCommandSource> context, String argument) {
|
||||
EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class);
|
||||
if (es == null)
|
||||
return null;
|
||||
List<ServerPlayer> nmsPlayerList = es.findPlayers(context.getSource());
|
||||
List<Player> playerList = new ArrayList<>(nmsPlayerList.size());
|
||||
for (ServerPlayer nmsPlayer : nmsPlayerList) {
|
||||
playerList.add(nmsPlayer.getBukkitEntity());
|
||||
}
|
||||
return playerList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided argument of type {@code minecraft:entity} (one entity), from the provided context.
|
||||
* @param context the command execution context.
|
||||
* @param argument the argument name.
|
||||
* @return the value of the argument, or null if not found.
|
||||
*/
|
||||
public Entity tryGetMinecraftEntityArgumentOneEntity(CommandContext<BukkitBrigadierCommandSource> context, String argument) {
|
||||
EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class);
|
||||
if (es == null)
|
||||
return null;
|
||||
fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity = es.findSingleEntity(context.getSource());
|
||||
return nmsEntity == null ? null : nmsEntity.getBukkitEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided argument of type {@code minecraft:entity} (one player), from the provided context.
|
||||
* @param context the command execution context.
|
||||
* @param argument the argument name.
|
||||
* @return the value of the argument, or null if not found.
|
||||
*/
|
||||
public Player tryGetMinecraftEntityArgumentOnePlayer(CommandContext<BukkitBrigadierCommandSource> context, String argument) {
|
||||
EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class);
|
||||
if (es == null)
|
||||
return null;
|
||||
ServerPlayer nmsPlayer = es.findSinglePlayer(context.getSource());
|
||||
return nmsPlayer == null ? null : nmsPlayer.getBukkitEntity();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Brigadier argument type {@code minecraft:block_pos}.
|
||||
* @return the {@code minecraft:block_pos} argument type.
|
||||
*/
|
||||
public static ArgumentType<Object> argumentMinecraftBlockPosition() {
|
||||
return BlockPosArgument.blockPos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided argument of type {@code minecraft:block_pos}, from the provided context.
|
||||
* @param context the command execution context.
|
||||
* @param argument the argument name.
|
||||
* @param deflt a default value if the argument is not found.
|
||||
* @return the value of the argument.
|
||||
*/
|
||||
public BlockVector tryGetMinecraftBlockPositionArgument(CommandContext<BukkitBrigadierCommandSource> context,
|
||||
String argument, BlockVector deflt) {
|
||||
return tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass(), nmsCoordinate -> {
|
||||
BlockPos bp = ReflectWrapper.wrap(nmsCoordinate, Coordinates.class).getBlockPos(context.getSource());
|
||||
return new BlockVector(bp.getX(), bp.getY(), bp.getZ());
|
||||
}, deflt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Brigadier argument type {@code minecraft:vec3}.
|
||||
* @return the {@code minecraft:vec3} argument type.
|
||||
@@ -610,11 +559,11 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
* @param deflt a default value if the argument is not found.
|
||||
* @return the value of the argument.
|
||||
*/
|
||||
public Vector tryGetMinecraftVec3Argument(CommandContext<BukkitBrigadierCommandSource> context, String argument,
|
||||
public Vector tryGetMinecraftVec3Argument(CommandContext<CommandSourceStack> context, String argument,
|
||||
Vector deflt) {
|
||||
return tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass(),
|
||||
return tryGetArgument(context, argument, Coordinates.REFLECT.get(),
|
||||
nmsCoordinate -> CraftVector.toBukkit(
|
||||
ReflectWrapper.wrap(nmsCoordinate, Coordinates.class).getPosition(context.getSource())
|
||||
wrap(nmsCoordinate, Coordinates.class).getPosition(context.getSource())
|
||||
),
|
||||
deflt);
|
||||
}
|
||||
@@ -622,44 +571,11 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Brigadier argument type {@code minecraft:component}.
|
||||
* @return the {@code minecraft:component} argument type.
|
||||
*/
|
||||
public static ArgumentType<Object> argumentMinecraftChatComponent() {
|
||||
return ComponentArgument.textComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided argument of type {@code minecraft:component}, from the provided context.
|
||||
* @param context the command execution context.
|
||||
* @param argument the argument name.
|
||||
* @param deflt a default value if the argument is not found.
|
||||
* @return the value of the argument.
|
||||
*/
|
||||
public Component tryGetMinecraftChatComponentArgument(CommandContext<BukkitBrigadierCommandSource> context,
|
||||
String argument, Component deflt) {
|
||||
return tryGetArgument(context, argument,
|
||||
fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.MAPPING.runtimeClass(),
|
||||
nmsComp -> PaperAdventure.asAdventure(
|
||||
ReflectWrapper.wrap(nmsComp,
|
||||
fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.class)
|
||||
),
|
||||
deflt);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All possible choices on how to force the registration of a command, based on certain conditions.
|
||||
*/
|
||||
public enum RegistrationPolicy {
|
||||
/**
|
||||
* Do not force to register a command node or an alias if there is already a command with that name in the
|
||||
* vanilla Brigadier dispatcher.
|
||||
* Note that all plugin-name-prefixed aliases will be registered anyway.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Force only the base command (but not the aliases) to be registered, even if a command with that name already
|
||||
* exists in the vanilla Brigadier dispatcher.
|
||||
|
@@ -10,28 +10,38 @@ import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.event.server.ServerEvent;
|
||||
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 {
|
||||
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler list of the event.
|
||||
* @return the handler list of the event.
|
||||
*/
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static boolean hasTriggered = false;
|
||||
private static boolean isInit = false;
|
||||
|
||||
/**
|
||||
* Register the event used to detect the server stop.
|
||||
*/
|
||||
public static void init() {
|
||||
if (isInit)
|
||||
return;
|
||||
|
||||
BukkitEvent.register(new Listener() {
|
||||
|
||||
@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}.
|
||||
* {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically
|
||||
* converted to conform {@link #yaw} and {@link #pitch} specification.
|
||||
* @param l the location.
|
||||
*/
|
||||
public DirectionalVector(Location l) {
|
||||
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,
|
||||
* 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
|
||||
@@ -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() {
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
@@ -135,7 +140,8 @@ public class DirectionalVector {
|
||||
/**
|
||||
* Set the yaw and the pitch of the provided {@link Location}
|
||||
* 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) {
|
||||
/* std : -PI/2 : <0 ? +2PI : MC
|
||||
@@ -148,7 +154,10 @@ public class DirectionalVector {
|
||||
l.setPitch((float) Math.toDegrees(-pitch));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the vector pointing to the opposite direction.
|
||||
* @return the opposite vector.
|
||||
*/
|
||||
public DirectionalVector getOpposite() {
|
||||
return new DirectionalVector(
|
||||
-x,
|
||||
@@ -163,6 +172,7 @@ public class DirectionalVector {
|
||||
* If the current direction is the player face direction,
|
||||
* this method return the direction of the back of the head.
|
||||
* This is an alias of {@link #getOpposite()}
|
||||
* @return the opposite of this vector.
|
||||
*/
|
||||
public DirectionalVector getBackDirection() {
|
||||
return getOpposite();
|
||||
@@ -171,6 +181,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* 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() {
|
||||
return new DirectionalVector(
|
||||
@@ -182,6 +193,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* 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() {
|
||||
return new DirectionalVector(
|
||||
@@ -194,6 +206,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* 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() {
|
||||
return new DirectionalVector(
|
||||
@@ -206,6 +219,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* 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() {
|
||||
return new DirectionalVector(
|
||||
|
@@ -2,24 +2,29 @@ package fr.pandacube.lib.paper.geometry;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
/**
|
||||
* Utility class related to geometry and Minecraft.
|
||||
*/
|
||||
public class GeometryUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 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>.
|
||||
*/
|
||||
public static final double PId2 = PI/2;
|
||||
|
||||
static final double PId2 = PI/2;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* The size of the first layer of the players head.
|
||||
*/
|
||||
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/>
|
||||
* 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 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).
|
||||
@@ -129,27 +145,22 @@ public class GeometryUtil {
|
||||
/**
|
||||
* Check if the path from <i>start</i> location to <i>end</i> pass through
|
||||
* the axis aligned bounding box defined by <i>min</i> and <i>max</i>.
|
||||
* @param start 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) {
|
||||
final double epsilon = 0.0001f;
|
||||
|
||||
Vector d = end.clone().subtract(start).multiply(0.5);
|
||||
Vector e = max.clone().subtract(min).multiply(0.5);
|
||||
Vector c = start.clone().add(d).subtract(min.clone().add(max).multiply(0.5));
|
||||
Vector ad = d.clone();
|
||||
ad.setX(Math.abs(ad.getX()));
|
||||
ad.setY(Math.abs(ad.getY()));
|
||||
ad.setZ(Math.abs(ad.getZ()));
|
||||
|
||||
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
|
||||
);
|
||||
RayTraceResult res = BoundingBox.of(min, max).rayTrace(start, end.clone().subtract(start), end.distance(start));
|
||||
return res != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private GeometryUtil() {}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -2,11 +2,10 @@ package fr.pandacube.lib.paper.geometry.blocks;
|
||||
|
||||
import fr.pandacube.lib.util.RandomUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -30,10 +29,19 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
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) {
|
||||
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) {
|
||||
pos1 = bb.getMin();
|
||||
pos2 = bb.getMax();
|
||||
@@ -41,15 +49,35 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
volume = (int) bb.getVolume();
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
/*
|
||||
* Prends les points extérieurs permettant de former un bounding box englobant
|
||||
@@ -74,22 +102,45 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
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() {
|
||||
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() {
|
||||
return pos2.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link BlockVector} with the lowest coordinates in this bounding box.
|
||||
* @return the minimum block vector.
|
||||
*/
|
||||
public BlockVector getMinBlock() {
|
||||
return pos1.toBlockVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link BlockVector} with the highest coordinates in this bounding box.
|
||||
* @return the maximum block vector.
|
||||
*/
|
||||
public BlockVector getMaxBlock() {
|
||||
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) {
|
||||
return new AABBBlock(this, x, y, z);
|
||||
}
|
||||
@@ -101,7 +152,6 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean overlaps(BoundingBox bb) {
|
||||
return asBukkitBoundingBox().overlaps(bb);
|
||||
}
|
||||
@@ -109,15 +159,23 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
public boolean isInside(Vector 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() {
|
||||
return center.clone();
|
||||
}
|
||||
|
||||
|
||||
public long getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the Bukkit equivalent of this bounding box.
|
||||
* @return a {@link BoundingBox} corresponding to this {@link AABBBlock}.
|
||||
*/
|
||||
public BoundingBox asBukkitBoundingBox() {
|
||||
if (bukkitBoundingBox == null) {
|
||||
bukkitBoundingBox = new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
|
||||
@@ -134,7 +192,7 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockVector> iterator() {
|
||||
public @NotNull Iterator<BlockVector> iterator() {
|
||||
return new Iterator<>() {
|
||||
private int x = pos1.getBlockX(),
|
||||
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
|
||||
|
@@ -12,19 +12,33 @@ import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A group of {@link AABBBlock}.
|
||||
*/
|
||||
public class AABBBlockGroup implements BlockSet {
|
||||
|
||||
|
||||
/**
|
||||
* The list of {@link AABBBlock} contained in this group. This list is unmodifiable.
|
||||
*/
|
||||
public final List<AABBBlock> subAABB;
|
||||
|
||||
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) {
|
||||
if (in.isEmpty())
|
||||
throw new IllegalArgumentException("Provided collection must not be empty.");
|
||||
subAABB = List.copyOf(in);
|
||||
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) {
|
||||
this(Arrays.asList(in));
|
||||
}
|
||||
|
@@ -1,23 +1,58 @@
|
||||
package fr.pandacube.lib.paper.geometry.blocks;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Represents a set of blocks in a world.
|
||||
*/
|
||||
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();
|
||||
|
||||
/**
|
||||
* Gets the volume, in blocks (or cubic meters), of this block set.
|
||||
* @return the volume of this block set.
|
||||
*/
|
||||
long getVolume();
|
||||
|
||||
/**
|
||||
* Gets the englobing bounding box if this block set.
|
||||
* @return the englobing bounding box if this block set.
|
||||
*/
|
||||
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);
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
if (this instanceof AABBBlock b1) {
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
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) {
|
||||
if (!bs1.getEnglobingAABB().overlaps(bs2.getEnglobingAABB()))
|
||||
return false;
|
||||
|
@@ -37,13 +37,21 @@ public class GUIHotBar implements Listener {
|
||||
private final int defaultSlot;
|
||||
|
||||
private final List<Player> currentPlayers = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Set up 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) {
|
||||
this.defaultSlot = Math.max(0, Math.min(8, defaultSlot));
|
||||
|
||||
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) {
|
||||
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
|
||||
* 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 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) {
|
||||
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.
|
||||
*
|
||||
* <br>
|
||||
* 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) {
|
||||
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.
|
||||
* @param p the player to remove.
|
||||
*/
|
||||
public void removePlayer(Player p) {
|
||||
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.
|
||||
* @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) {
|
||||
if (!currentPlayers.contains(p))
|
||||
@@ -106,18 +119,28 @@ public class GUIHotBar implements Listener {
|
||||
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) {
|
||||
return currentPlayers.contains(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Detach all players from this hot bar.
|
||||
*/
|
||||
public void removeAllPlayers() {
|
||||
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) {
|
||||
for (Player p : new ArrayList<>(currentPlayers))
|
||||
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))
|
||||
throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar");
|
||||
if (!currentPlayers.contains(p))
|
||||
@@ -135,7 +158,7 @@ public class GUIHotBar implements Listener {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -144,7 +167,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
@@ -159,7 +182,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
@@ -188,7 +211,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
void onInventoryClick(InventoryClickEvent event) {
|
||||
if (event.getClickedInventory() == null || !(event.getClickedInventory() instanceof PlayerInventory inv))
|
||||
return;
|
||||
|
||||
@@ -213,20 +236,20 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
removePlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
void onPlayerDeath(PlayerDeathEvent event) {
|
||||
if (!currentPlayers.contains(event.getEntity()))
|
||||
return;
|
||||
event.getDrops().removeAll(itemsAndSetters.keySet());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
|
@@ -38,7 +38,7 @@ public class GUIInventory implements Listener {
|
||||
if (title == null)
|
||||
inv = Bukkit.createInventory(null, nbLines * 9);
|
||||
else
|
||||
inv = Bukkit.createInventory(null, nbLines * 9, title.getAdv());
|
||||
inv = Bukkit.createInventory(null, nbLines * 9, title.get());
|
||||
|
||||
setCloseEvent(closeEventAction);
|
||||
|
||||
@@ -49,7 +49,11 @@ public class GUIInventory implements Listener {
|
||||
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) {
|
||||
onCloseEvent = closeEventAction;
|
||||
}
|
||||
@@ -188,13 +192,13 @@ public class GUIInventory implements Listener {
|
||||
onCloseEvent.accept(event);
|
||||
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) {
|
||||
return nb / 9 + ((nb % 9 == 0) ? 0 : 1);
|
||||
}
|
||||
|
@@ -0,0 +1,232 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Total number of item slots in the player inventory.
|
||||
*/
|
||||
public static final int PLAYER_INVENTORY_SIZE = 43; // 36 base inventory + 4 armor slots + 1 off hand + 2 hidden slots (body and saddle)
|
||||
|
||||
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() < PLAYER_INVENTORY_SIZE)
|
||||
throw new IllegalArgumentException("base inventory should have a size of " + PLAYER_INVENTORY_SIZE + " (" + 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item stack in the SADDLE {@link EquipmentSlot}.
|
||||
* @return the SADDLE item stack.
|
||||
*/
|
||||
public ItemStack getSaddle() {
|
||||
return getItem(42);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the provided item stack in the SADDLE {@link EquipmentSlot}.
|
||||
* @param saddle the item.
|
||||
*/
|
||||
public void setSaddle(@Nullable ItemStack saddle) {
|
||||
setItem(42, saddle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item stack in the BODY {@link EquipmentSlot}.
|
||||
* @return the BODY item stack.
|
||||
*/
|
||||
public ItemStack getBody() {
|
||||
return getItem(41);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the provided item stack in the BODY {@link EquipmentSlot}.
|
||||
* @param body the item.
|
||||
*/
|
||||
public void setBody(@Nullable ItemStack body) {
|
||||
setItem(41, body);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@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);
|
||||
case BODY -> this.setBody(item);
|
||||
case SADDLE -> this.setSaddle(item);
|
||||
}
|
||||
}
|
||||
|
||||
@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 -> Objects.requireNonNullElseGet(this.getBody(), () -> new ItemStack(Material.AIR)); // for horses/wolves armor
|
||||
case SADDLE -> Objects.requireNonNullElseGet(this.getSaddle(), () -> new ItemStack(Material.AIR));
|
||||
};
|
||||
}
|
||||
|
||||
@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.Material;
|
||||
@@ -13,12 +13,22 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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 {
|
||||
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) {
|
||||
this.base = base;
|
||||
this.base = Objects.requireNonNull(base, "base inventory cannot be null.");
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,413 @@
|
||||
package fr.pandacube.lib.paper.inventory;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import io.papermc.paper.datacomponent.DataComponentType;
|
||||
import io.papermc.paper.datacomponent.DataComponentType.Valued;
|
||||
import io.papermc.paper.datacomponent.DataComponentTypes;
|
||||
import io.papermc.paper.datacomponent.item.ResolvableProfile;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.Damageable;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static fr.pandacube.lib.chat.ChatStatic.chatComponent;
|
||||
|
||||
/**
|
||||
* A builder for {@link ItemStack}.
|
||||
*/
|
||||
public class ItemStackBuilder {
|
||||
|
||||
/**
|
||||
* Create a builder with a clone of the provided ItemStack.
|
||||
* <p>
|
||||
* The returned builder will not alter the provided ItemStack.
|
||||
* If you want to modify the ItemStack with the builder, please use {@link #wrap(ItemStack)}.
|
||||
* @param base the original ItemStack.
|
||||
* @return the builder
|
||||
*/
|
||||
public static ItemStackBuilder of(ItemStack base) {
|
||||
return wrap(base.clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a builder of a new ItemStack with the specified Material.
|
||||
* @param mat the material of the new built ItemStack
|
||||
* @return the builder
|
||||
*/
|
||||
public static ItemStackBuilder of(Material mat) {
|
||||
return wrap(new ItemStack(mat));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a builder that will alter the data of the provided ItemStack.
|
||||
* <p>
|
||||
* The {@link #build()} method of the returned builder will return the same instance
|
||||
* of ItemStack as the parameter of this method.
|
||||
* <p>
|
||||
* To create a builder that doesn't modify the provided ItemStack, use {@link #of(ItemStack)}.
|
||||
* @param stack the wrapped item stack.
|
||||
* @return the builder
|
||||
*/
|
||||
public static ItemStackBuilder wrap(ItemStack stack) {
|
||||
return new ItemStackBuilder(stack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private final ItemStack stack;
|
||||
|
||||
private ItemStackBuilder(ItemStack base) {
|
||||
stack = base;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
stack.editMeta(metaType, metaUpdater);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount of the built stack.
|
||||
* @param a the new amount.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder amount(int a) {
|
||||
stack.setAmount(a);
|
||||
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) {
|
||||
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) {
|
||||
return rawDisplayName(displayName != null
|
||||
? Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent()
|
||||
: 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) {
|
||||
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) {
|
||||
if (lore != null) {
|
||||
return rawLore(lore.stream()
|
||||
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).get())
|
||||
.toList());
|
||||
}
|
||||
else
|
||||
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) {
|
||||
if (lores != null) {
|
||||
List<Component> baseLore = stack.getItemMeta().lore();
|
||||
if (baseLore == null) baseLore = Collections.emptyList();
|
||||
return rawLore(
|
||||
Streams.concat(
|
||||
baseLore.stream(),
|
||||
lores.stream()
|
||||
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).get())
|
||||
)
|
||||
.toList());
|
||||
}
|
||||
else
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new lore lines to the existing lore of the item.
|
||||
* @param lores the added lore lines.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder addLoreAfter(ComponentLike... lores) {
|
||||
if (lores == null || lores.length == 0)
|
||||
return this;
|
||||
return addLoreAfter(Arrays.asList(lores));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enchant the item.
|
||||
* Supports unsafe enchants.
|
||||
* @param enchantment the enchantment.
|
||||
* @param level the enchant level.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder enchant(Enchantment enchantment, int level) {
|
||||
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) {
|
||||
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) {
|
||||
return add ? meta(m -> m.addItemFlags(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() {
|
||||
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) {
|
||||
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() {
|
||||
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) {
|
||||
return flags(hide, ItemFlag.HIDE_ATTRIBUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the enchantment glint to the item, event if it's not enchant.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder fakeEnchant() {
|
||||
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) {
|
||||
return meta(m -> m.setEnchantmentGlintOverride(apply ? true : null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this item as unbreakable.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder unbreakable() {
|
||||
return unbreakable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unbreakable status of this item.
|
||||
* @param unbreakable the unbreakable status.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder unbreakable(boolean 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) {
|
||||
return meta(m -> m.setDamage(d), Damageable.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a value for a data component of this item.
|
||||
* @param dataType the data component type.
|
||||
* @param dataValue the data component value.
|
||||
* @return itself.
|
||||
* @param <T> the data component API type.
|
||||
*/
|
||||
public <T> ItemStackBuilder data(Valued<T> dataType, T dataValue) {
|
||||
stack.setData(dataType, dataValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset (set to empty) a value for a data component of this item.
|
||||
* @param dataType the data component type.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder unsetData(DataComponentType dataType) {
|
||||
stack.unsetData(dataType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset (act as default) a value for a data component of this item.
|
||||
* @param dataType the data component type.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder resetData(DataComponentType dataType) {
|
||||
stack.resetData(dataType);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the {@code can_break} data component to the provided list of {@link Material}.
|
||||
* @param canBreak a list of {@link Material}.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder canBreakMaterials(Collection<Material> canBreak) {
|
||||
return canBreak(canBreak.stream().map(Material::getKey).toList());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the {@code can_break} data component to the provided list of {@link NamespacedKey}.
|
||||
* @param canBreak a list of block predicate. If empty, unsets the data component. If null, reset to default.
|
||||
* @return itself.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public ItemStackBuilder canBreak(Collection<NamespacedKey> canBreak) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<com.destroystokyo.paper.Namespaced> nsCanBreak = (Collection<com.destroystokyo.paper.Namespaced>) (Collection<?>) canBreak;
|
||||
return meta(m -> m.setDestroyableKeys(nsCanBreak));
|
||||
|
||||
/*
|
||||
if (canBreak == null)
|
||||
return resetData(DataComponentTypes.CAN_BREAK);
|
||||
else if (canBreak.isEmpty())
|
||||
return unsetData(DataComponentTypes.CAN_BREAK);
|
||||
else
|
||||
return data(DataComponentTypes.CAN_BREAK, ItemAdventurePredicate.itemAdventurePredicate(canBreak));*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code can_place_on} data component to the provided list of {@link Material}.
|
||||
* @param canPlaceOn a list of {@link Material}.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder canPlaceOnMaterials(Collection<Material> canPlaceOn) {
|
||||
return canPlaceOn(canPlaceOn.stream().map(Material::getKey).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code can_place_on} data component to the provided list of {@link NamespacedKey}.
|
||||
* @param canPlaceOn a list of block predicate. If empty, unsets the data component. If null, reset to default.
|
||||
* @return itself.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public ItemStackBuilder canPlaceOn(Collection<NamespacedKey> canPlaceOn) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<com.destroystokyo.paper.Namespaced> nsCanPlaceOn = (Collection<com.destroystokyo.paper.Namespaced>) (Collection<?>) canPlaceOn;
|
||||
return meta(m -> m.setPlaceableKeys(nsCanPlaceOn));
|
||||
|
||||
/* if (canPlaceOn == null)
|
||||
return resetData(DataComponentTypes.CAN_PLACE_ON);
|
||||
else if (canPlaceOn.isEmpty())
|
||||
return unsetData(DataComponentTypes.CAN_PLACE_ON);
|
||||
else
|
||||
return data(DataComponentTypes.CAN_PLACE_ON, ItemAdventurePredicate.itemAdventurePredicate(canPlaceOn)); */
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code profile} data component to the provided profile.
|
||||
* @param profile the profile to use as the component value.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder profile(ResolvableProfile profile) {
|
||||
return data(DataComponentTypes.PROFILE, profile);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the {@link ItemStack}.
|
||||
* @return the build item stack.
|
||||
*/
|
||||
public ItemStack build() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
package fr.pandacube.lib.paper.inventory;
|
||||
|
||||
import com.destroystokyo.paper.profile.ProfileProperty;
|
||||
import io.papermc.paper.datacomponent.item.ResolvableProfile;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents some special mob heads, also support creating player skulls and custom skulls.
|
||||
*/
|
||||
public enum Skull {
|
||||
|
||||
/** Jungle wood arrow left. */
|
||||
ARROW_LEFT("http://textures.minecraft.net/texture/3625902b389ed6c147574e422da8f8f361c8eb57e7631676a72777e7b1d"),
|
||||
/** Jungle wood arrow right. */
|
||||
ARROW_RIGHT("http://textures.minecraft.net/texture/d4be8aeec11849697adc6fd1f189b16642dff19f2955c05deaba68c9dff1be"),
|
||||
/** Jungle wood arrow up. */
|
||||
ARROW_UP("http://textures.minecraft.net/texture/88c0f37dec764d6e26b57aa8212572fbace5ee8f27f7b61c1fdaa47dd4c893"),
|
||||
/** Jungle wood arrow down. */
|
||||
ARROW_DOWN("http://textures.minecraft.net/texture/751ced2e647366f8f3ad2dfe415cca85651bfaf9739a95cd57b6f21cba053"),
|
||||
/** Jungle wood question mark. */
|
||||
QUESTION("http://textures.minecraft.net/texture/b4d7cc4dca986a53f1d6b52aaf376dc6acc73b8b287f42dc8fef5808bb5d76"),
|
||||
/** Jungle wood exclamation mark. */
|
||||
EXCLAMATION("http://textures.minecraft.net/texture/e869dc405a3155f281c16a3e8d9ff54afc1599153b4d9385c9b7bab88680f0");
|
||||
|
||||
private final String skinUrl;
|
||||
|
||||
Skull(String skinUrl) {
|
||||
this.skinUrl = skinUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the item based on this Skull enum.
|
||||
* @return the item stack.
|
||||
*/
|
||||
public ItemStack get() {
|
||||
return getFromSkinURL(skinUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an item stack builder already containing the skull.
|
||||
* @return an item stack builder already containing the skull.
|
||||
*/
|
||||
public ItemStackBuilder builder() {
|
||||
return ItemStackBuilder.wrap(get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a skull of a player based on their name.
|
||||
*
|
||||
* @param name player's name
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromPlayerName(String name) {
|
||||
return getFromProfile(ResolvableProfile.resolvableProfile().name(name).build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a skull that has a custom texture specified by url.
|
||||
* @param url skin url.
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromSkinURL(String url) {
|
||||
return getFromProfile(ResolvableProfile.resolvableProfile().addProperty(getTexturesProperty(url)).build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static ItemStack getFromProfile(ResolvableProfile profile) {
|
||||
return ItemStackBuilder.of(Material.PLAYER_HEAD).profile(profile).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The URL prefix for all the player related textures (skin, cape)
|
||||
*/
|
||||
public static final String TEXTURE_URL_PREFIX = "http://textures.minecraft.net/texture/";
|
||||
|
||||
private static final Pattern textureIdMatcher = Pattern.compile("^[0-9a-fA-F]+$");
|
||||
|
||||
/**
|
||||
* Generate the base64 value of the "textures" profile property, based on the provided skin url!
|
||||
* @param skinURL the URL of the skin. The "https" will be replaced by "http" because this is the protocol used in
|
||||
* the profile property url. If only the texture id part is provided, {@link #TEXTURE_URL_PREFIX} is
|
||||
* prepended.
|
||||
* @return the base64 encoded texture data.
|
||||
*/
|
||||
private static String encodeTextureBase64String(String skinURL) {
|
||||
if (skinURL.startsWith("https://")) // secure url is not the url found in texture data (even if it actually works in the browser)
|
||||
skinURL = "http://" + skinURL.substring("https://".length());
|
||||
if (!skinURL.startsWith(TEXTURE_URL_PREFIX)) { // accept taking only the texture id part ()
|
||||
if (textureIdMatcher.matcher(skinURL).matches())
|
||||
skinURL = TEXTURE_URL_PREFIX + skinURL;
|
||||
else
|
||||
throw new IllegalArgumentException("Invalid skin URL. Must be from " + TEXTURE_URL_PREFIX + ".");
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", skinURL).getBytes());
|
||||
}
|
||||
|
||||
|
||||
private static ProfileProperty getTexturesProperty(String skinURL) {
|
||||
return new ProfileProperty("textures", encodeTextureBase64String(skinURL));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import java.util.Map;
|
||||
/**
|
||||
* Gson adapter for ConfigurationSerializable, an interface implemented by several classes in the Bukkit API to ease
|
||||
* serialization to YAML.
|
||||
*
|
||||
* <p>
|
||||
* To not reinvent the wheel, this class uses the Bukkit’s Yaml API to convert the objects from/to json.
|
||||
*/
|
||||
/* package */ class ConfigurationSerializableAdapter implements JsonSerializer<ConfigurationSerializable>, JsonDeserializer<ConfigurationSerializable> {
|
||||
|
@@ -9,10 +9,13 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.Strictness;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.bind.TreeTypeAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -28,16 +31,27 @@ import java.util.Map;
|
||||
private static final TypeToken<Map<String, Object>> MAP_STR_OBJ_TYPE = new TypeToken<>() { };
|
||||
|
||||
/** Gson instance with no custom type adapter */
|
||||
private static final Gson vanillaGson = new GsonBuilder().setLenient().create();
|
||||
private static final Gson vanillaGson = new GsonBuilder().setStrictness(Strictness.LENIENT).create();
|
||||
|
||||
@Override
|
||||
public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(json instanceof JsonObject jsonObj))
|
||||
throw new JsonParseException("Unable to deserialize a ConfigurationSerializable from the provided json structure.");
|
||||
|
||||
// the deserialized json may contain older data compatible with pre 1.21.5 but not compatible after.
|
||||
// if it contains both old and new data, delete the old one introduced for compatibility
|
||||
if (jsonObj.has("DataVersion")) { // it uses the new DataVersion data
|
||||
jsonObj.remove("v");
|
||||
}
|
||||
if (jsonObj.has("id")) {
|
||||
jsonObj.remove("type");
|
||||
}
|
||||
|
||||
if (jsonObj.has(ConfigurationSerialization.SERIALIZED_TYPE_KEY))
|
||||
return context.deserialize(jsonObj, ConfigurationSerializable.class);
|
||||
|
||||
|
||||
|
||||
if (jsonObj.has("meta")
|
||||
&& jsonObj.get("meta") instanceof JsonObject metaJson
|
||||
&& !metaJson.has(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
|
||||
@@ -47,6 +61,8 @@ import java.util.Map;
|
||||
Map<String, Object> map = context.deserialize(jsonObj, MAP_STR_OBJ_TYPE.getType());
|
||||
fixDeserializationVersion(map);
|
||||
map.remove("meta");
|
||||
|
||||
|
||||
ItemStack is = ItemStack.deserialize(map);
|
||||
|
||||
Class<? extends ItemMeta> metaClass = is.getItemMeta().getClass();
|
||||
@@ -63,7 +79,17 @@ import java.util.Map;
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return context.serialize(src.serialize(), MAP_STR_OBJ_TYPE.getType());
|
||||
Map<String, Object> serialized = src.serialize();
|
||||
|
||||
// make the generated json compatible with pre 1.21.5 deserializer (temporary fix during the upgrade of the server)
|
||||
if (serialized.containsKey("DataVersion")) {
|
||||
serialized.put("v", serialized.get("DataVersion"));
|
||||
}
|
||||
if (serialized.containsKey("id")) {
|
||||
serialized.put("type", Registry.MATERIAL.getOrThrow(Key.key((String)serialized.get("id"))).name());
|
||||
}
|
||||
|
||||
return context.serialize(serialized, MAP_STR_OBJ_TYPE.getType());
|
||||
}
|
||||
|
||||
|
||||
|
@@ -14,4 +14,7 @@ public class PaperJson {
|
||||
Json.registerTypeAdapterFactory(ItemStackAdapter.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 fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.chat.ChatColorGradient;
|
||||
import fr.pandacube.lib.chat.ChatColorUtil;
|
||||
import fr.pandacube.lib.chat.ChatConfig.PandaTheme;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.paper.players.PaperOffPlayer;
|
||||
@@ -13,16 +12,16 @@ import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
||||
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar;
|
||||
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar.BarUpdater;
|
||||
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.MemoryUnit;
|
||||
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.Color;
|
||||
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
@@ -38,6 +37,7 @@ import java.lang.management.ThreadMXBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static fr.pandacube.lib.chat.ChatStatic.chat;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.failureText;
|
||||
@@ -45,10 +45,17 @@ import static fr.pandacube.lib.chat.ChatStatic.infoText;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
||||
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 {
|
||||
|
||||
private static PerformanceAnalysisManager instance;
|
||||
|
||||
/**
|
||||
* Gets the instance of {@link PerformanceAnalysisManager}.
|
||||
* @return the instance of {@link PerformanceAnalysisManager}.
|
||||
*/
|
||||
public static synchronized PerformanceAnalysisManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new PerformanceAnalysisManager();
|
||||
@@ -77,14 +84,22 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
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;
|
||||
|
||||
/**
|
||||
* The boss bar that shows in real time the JVM RAM usage.
|
||||
*/
|
||||
public final AutoUpdatedBossBar memoryBar;
|
||||
private final List<Player> barPlayers = 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()
|
||||
.add(0, NamedTextColor.BLACK)
|
||||
.add(1, NamedTextColor.DARK_RED)
|
||||
@@ -95,23 +110,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
.add(21, PandaTheme.CHAT_GREEN_1_NORMAL)
|
||||
.add(26, NamedTextColor.BLUE);
|
||||
|
||||
|
||||
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()
|
||||
private final ChatColorGradient memoryUsageGradient = new ChatColorGradient()
|
||||
.add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL)
|
||||
.add(.70f, NamedTextColor.YELLOW)
|
||||
.add(.80f, NamedTextColor.GOLD)
|
||||
@@ -133,10 +132,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) {
|
||||
return barPlayers.contains(p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows the performance boss bars to the provided player.
|
||||
* @param p the player.
|
||||
*/
|
||||
public synchronized void addPlayerToBars(Player p) {
|
||||
barPlayers.add(p);
|
||||
p.showBossBar(tpsBar.bar);
|
||||
@@ -144,7 +152,11 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
for (BossBar bar : relatedBossBars)
|
||||
p.showBossBar(bar);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hides the performance boss bars from the provided player.
|
||||
* @param p the player.
|
||||
*/
|
||||
public synchronized void removePlayerToBars(Player p) {
|
||||
p.hideBossBar(tpsBar.bar);
|
||||
p.hideBossBar(memoryBar.bar);
|
||||
@@ -152,7 +164,11 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
p.hideBossBar(bar);
|
||||
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) {
|
||||
if (relatedBossBars.contains(bar))
|
||||
return;
|
||||
@@ -160,7 +176,11 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
for (Player p : barPlayers)
|
||||
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) {
|
||||
if (!relatedBossBars.contains(bar))
|
||||
return;
|
||||
@@ -168,8 +188,11 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
for (Player p : barPlayers)
|
||||
p.hideBossBar(bar);
|
||||
}
|
||||
|
||||
public synchronized void cancelInternalBossBar() {
|
||||
|
||||
/**
|
||||
* De-initialize the performance analyzer.
|
||||
*/
|
||||
public synchronized void deinit() {
|
||||
tpsBar.cancel();
|
||||
memoryBar.cancel();
|
||||
}
|
||||
@@ -179,7 +202,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public synchronized void onTickStart(ServerTickStartEvent event) {
|
||||
synchronized void onTickStart(ServerTickStartEvent event) {
|
||||
tickStartNanoTime = System.nanoTime();
|
||||
tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
||||
|
||||
@@ -187,7 +210,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public synchronized void onTickEnd(ServerTickEndEvent event) {
|
||||
synchronized void onTickEnd(ServerTickEndEvent event) {
|
||||
tickEndNanoTime = System.nanoTime();
|
||||
long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
||||
|
||||
@@ -214,7 +237,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
void onPlayerJoin(PlayerJoinEvent event) {
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer> playerManager = (AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer>) AbstractPlayerManager.getInstance();
|
||||
@@ -234,7 +257,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
removePlayerToBars(event.getPlayer());
|
||||
}
|
||||
|
||||
@@ -296,20 +319,25 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
int[] tpsHistory = getTPSHistory();
|
||||
|
||||
// keep the legacy text when generating the bar to save space when converting to component
|
||||
StringBuilder s = new StringBuilder();
|
||||
ChatColor prevC = ChatColor.RESET;
|
||||
|
||||
List<Pair<TextColor, AtomicInteger>> barComponents = new ArrayList<>(60);
|
||||
for (int i = 58; i >= 0; i--) {
|
||||
int t = tpsHistory[i];
|
||||
ChatColor newC = ChatColorUtil.toBungee(tps1sGradient.pickColorAt(t));
|
||||
if (!newC.equals(prevC)) {
|
||||
s.append(newC);
|
||||
prevC = newC;
|
||||
TextColor newC = tps1sGradient.pickColorAt(t);
|
||||
if (barComponents.isEmpty() || !newC.equals(barComponents.get(barComponents.size() - 1).getKey())) {
|
||||
barComponents.add(Pair.of(newC, new AtomicInteger(1)));
|
||||
}
|
||||
else {
|
||||
barComponents.get(barComponents.size() - 1).getValue().incrementAndGet();
|
||||
}
|
||||
s.append("|");
|
||||
}
|
||||
|
||||
Chat history = chat();
|
||||
barComponents.forEach(p -> {
|
||||
history.then(text("|".repeat(p.getValue().get()))
|
||||
.color(p.getKey())
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// tick time measurement
|
||||
@@ -332,7 +360,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
: (avgTickCPUTime1s < 50) ? NamedTextColor.RED
|
||||
: NamedTextColor.DARK_RED;
|
||||
|
||||
float avgTickWaitingTime1s = avgTickDuration1s - avgTickCPUTime1s;
|
||||
float avgTickWaitingTime1s = Math.max(0, avgTickDuration1s - avgTickCPUTime1s);
|
||||
TextColor avgTickWaitingTime1sColor = (avgTickDuration1s < 46 || avgTickWaitingTime1s < 20) ? PandaTheme.CHAT_GREEN_1_NORMAL
|
||||
: (avgTickWaitingTime1s < 30) ? NamedTextColor.YELLOW
|
||||
: (avgTickWaitingTime1s < 40) ? NamedTextColor.GOLD
|
||||
@@ -348,16 +376,16 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
: NamedTextColor.RED;
|
||||
|
||||
timings = text("(R/W/S:")
|
||||
.then(text(Math.round(avgTickCPUTime1s)).color(avgTickCPUTime1sColor))
|
||||
.then(text("%02d".formatted(Math.round(avgTickCPUTime1s))).color(avgTickCPUTime1sColor))
|
||||
.thenText("/")
|
||||
.then(text(Math.round(avgTickWaitingTime1s)).color(avgTickWaitingTime1sColor))
|
||||
.then(text("%02d".formatted(Math.round(avgTickWaitingTime1s))).color(avgTickWaitingTime1sColor))
|
||||
.thenText("/")
|
||||
.then(text(Math.round(avgInterTickDuration1s)).color(avgInterTickDuration1sColor))
|
||||
.then(text("%02d".formatted(Math.round(avgInterTickDuration1s))).color(avgInterTickDuration1sColor))
|
||||
.thenText("ms)");
|
||||
}
|
||||
|
||||
title = infoText("TPS [")
|
||||
.thenLegacyText(s.toString())
|
||||
.then(history)
|
||||
.thenText("] ")
|
||||
.then(text(tps1sDisplay + "/" + getTargetTickRate() + " ").color(tps1sGradient.pickColorAt(tps1s)))
|
||||
.then(timings);
|
||||
@@ -374,28 +402,34 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
}
|
||||
|
||||
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) {
|
||||
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() {
|
||||
return (int) getTPS(1_000);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param nbTicks number of ticks when the avg value is computed from history
|
||||
* @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())
|
||||
return 0;
|
||||
|
||||
@@ -409,7 +443,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
|
||||
* @return the avg number of TPS in the interval
|
||||
*/
|
||||
@@ -428,8 +462,12 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
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() {
|
||||
int[] history = new int[60];
|
||||
|
||||
@@ -447,15 +485,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() {
|
||||
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 garbage collector. Can be null (the report will be sent to the
|
||||
* console)
|
||||
*/
|
||||
public static void gc(CommandSender sender) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
long alloc1 = Runtime.getRuntime().totalMemory();
|
||||
@@ -477,7 +522,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
Log.info(finalMessage.getLegacyText());
|
||||
}
|
||||
|
||||
public static String displayRound10(double val) {
|
||||
private static String displayRound10(double val) {
|
||||
long v = (long) Math.ceil(val * 10);
|
||||
return "" + (v / 10f);
|
||||
}
|
||||
|
@@ -1,14 +1,11 @@
|
||||
package fr.pandacube.lib.paper.players;
|
||||
|
||||
import fr.pandacube.lib.paper.reflect.util.PrimaryWorlds;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.util.ProblemReporter;
|
||||
import fr.pandacube.lib.paper.world.PrimaryWorlds;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.dataconverter.MCDataConverter;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.dataconverter.MCTypeRegistry;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.SharedConstants;
|
||||
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.Tag;
|
||||
import fr.pandacube.lib.paper.util.PlayerDataWrapper;
|
||||
import fr.pandacube.lib.paper.players.PlayerDataWrapper.PlayerDataLoadException;
|
||||
import fr.pandacube.lib.paper.world.WorldUtil;
|
||||
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
@@ -119,26 +116,31 @@ public interface PaperOffPlayer extends AbstractOffPlayer {
|
||||
* Player config
|
||||
*/
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default String getConfig(String key) throws Exception {
|
||||
return PaperPlayerConfigStorage.get(getUniqueId(), key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default String getConfig(String key, String deflt) throws Exception {
|
||||
return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default void setConfig(String key, String value) throws Exception {
|
||||
PaperPlayerConfigStorage.set(getUniqueId(), key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default void updateConfig(String key, String deflt, UnaryOperator<String> updater) throws Exception {
|
||||
PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default void unsetConfig(String key) throws Exception {
|
||||
PaperPlayerConfigStorage.unset(getUniqueId(), key);
|
||||
@@ -154,28 +156,21 @@ public interface PaperOffPlayer extends AbstractOffPlayer {
|
||||
/**
|
||||
* Gets the NBT data from the player-data file.
|
||||
* It will not work if the player is online, because the data on the file are not synchronized with real-time values.
|
||||
* @param convertTag true to convert the data to the current MC version, false to keep the saved version
|
||||
* @return the NBT data from the player-data file.
|
||||
* @return the NBT data from the player-data file, or null if the file does not exist.
|
||||
* @throws IllegalStateException if the player is online.
|
||||
*/
|
||||
default CompoundTag getPlayerData(boolean convertTag) {
|
||||
default CompoundTag getPlayerData() {
|
||||
if (isOnline())
|
||||
throw new IllegalStateException("Cannot access data file of " + getName() + " because they’re online.");
|
||||
CompoundTag data = ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class)
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.playerIo()
|
||||
.getPlayerData(getUniqueId().toString());
|
||||
if (convertTag) {
|
||||
int srcVersion = data.contains("DataVersion", Tag.TAG_ANY_NUMERIC()) ? data.getInt("DataVersion") : -1;
|
||||
int destVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion();
|
||||
try {
|
||||
data = MCDataConverter.convertTag(MCTypeRegistry.PLAYER(), data, srcVersion, destVersion);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to upgrade data format of player " + getName() + " (" + getUniqueId() + ") from version " + destVersion + " to " + destVersion);
|
||||
}
|
||||
throw new IllegalStateException("Cannot access data file of " + getName() + " because they're online.");
|
||||
try {
|
||||
return ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class)
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.playerIo()
|
||||
.load(getName(), getUniqueId().toString(), ProblemReporter.DISCARDING()).orElse(null);
|
||||
} catch (Exception|LinkageError e) {
|
||||
throw new PlayerDataLoadException(getName(), getUniqueId(), e);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +180,7 @@ public interface PaperOffPlayer extends AbstractOffPlayer {
|
||||
* @throws IllegalStateException if the player is online.
|
||||
*/
|
||||
default PlayerDataWrapper getPlayerDataWrapper() {
|
||||
return new PlayerDataWrapper(getPlayerData(true));
|
||||
return new PlayerDataWrapper(getPlayerData());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,7 +206,7 @@ public interface PaperOffPlayer extends AbstractOffPlayer {
|
||||
* @return the file where the player-data is stored.
|
||||
*/
|
||||
default File getPlayerDataFile(boolean old) {
|
||||
File playerDataDir = new File(WorldUtil.worldDir(PrimaryWorlds.PRIMARY_WORLDS.get(0)), "playerdata");
|
||||
File playerDataDir = new File(WorldUtil.worldDir(PrimaryWorlds.PRIMARY_WORLDS.getFirst()), "playerdata");
|
||||
return new File(playerDataDir, getUniqueId() + (old ? ".dat_old" : ".dat"));
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ package fr.pandacube.lib.paper.players;
|
||||
import com.destroystokyo.paper.ClientOption;
|
||||
import com.destroystokyo.paper.ClientOption.ChatVisibility;
|
||||
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.players.standalone.AbstractOnlinePlayer;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
@@ -170,7 +170,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
||||
@Override
|
||||
public boolean isChatFullyVisible() {
|
||||
ChatVisibility v = getChatVisibility();
|
||||
return v == ChatVisibility.FULL || v == ChatVisibility.UNKNOWN;
|
||||
return v == ChatVisibility.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -283,7 +283,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
||||
* @param relZ the relative z coordinate.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
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) {
|
||||
PlayerNonPersistentConfig.unsetData(getUniqueId(), key);
|
||||
}
|
||||
|
@@ -16,6 +16,10 @@ import java.util.UUID;
|
||||
import java.util.function.UnaryOperator;
|
||||
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 {
|
||||
|
||||
static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml");
|
||||
@@ -77,6 +81,8 @@ public class PaperPlayerConfigStorage {
|
||||
|
||||
|
||||
private static synchronized void save() {
|
||||
if (!changed)
|
||||
return;
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
for (UUID pId : playerSortedData.keySet()) {
|
||||
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();
|
||||
ConfigKey cKey = new ConfigKey(player, key);
|
||||
ConfigEntry e = data.get(cKey);
|
||||
if (e != null && newValue == null) { // delete
|
||||
if (e != null && value == null) { // delete
|
||||
data.remove(cKey);
|
||||
if (playerSortedData.containsKey(player))
|
||||
playerSortedData.get(player).remove(e);
|
||||
@@ -121,50 +133,91 @@ public class PaperPlayerConfigStorage {
|
||||
keySortedData.get(key).remove(e);
|
||||
changed = true;
|
||||
}
|
||||
else if (e == null && newValue != null) { // create
|
||||
create(player, key, newValue);
|
||||
else if (e == null && value != null) { // create
|
||||
create(player, key, value);
|
||||
changed = true;
|
||||
}
|
||||
else if (e != null && !newValue.equals(e.value)) { // update
|
||||
e.value = newValue;
|
||||
else if (e != null && !value.equals(e.value)) { // update
|
||||
e.value = value;
|
||||
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) {
|
||||
initIfNeeded();
|
||||
ConfigEntry e = data.get(new ConfigKey(player, key));
|
||||
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;
|
||||
}
|
||||
|
||||
public static synchronized void update(UUID p, String k, String deflt, UnaryOperator<String> updater) {
|
||||
String oldValue = get(p, k, deflt);
|
||||
set(p, k, updater.apply(oldValue));
|
||||
/**
|
||||
* Updates the value of the provided configuration key for the player, using the provided updater.
|
||||
* @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();
|
||||
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) {
|
||||
initIfNeeded();
|
||||
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();
|
||||
return getAllWithKeys(k).stream()
|
||||
return getAllWithKeys(key).stream()
|
||||
.filter(c -> c.value.equals(v))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
@@ -173,25 +226,46 @@ public class PaperPlayerConfigStorage {
|
||||
|
||||
private record ConfigKey(UUID playerId, String key) { }
|
||||
|
||||
/**
|
||||
* Class holding the playerId-key-value triplet.
|
||||
*/
|
||||
public static class ConfigEntry {
|
||||
private final UUID playerId;
|
||||
private final String key;
|
||||
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) {
|
||||
this.playerId = playerId;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player id.
|
||||
* @return the player id.
|
||||
*/
|
||||
public UUID getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config key.
|
||||
* @return the config key.
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config value.
|
||||
* @return the config value.
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -208,4 +282,7 @@ public class PaperPlayerConfigStorage {
|
||||
&& Objects.equals(key, o.key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private PaperPlayerConfigStorage() {}
|
||||
}
|
||||
|
@@ -0,0 +1,237 @@
|
||||
package fr.pandacube.lib.paper.players;
|
||||
|
||||
import fr.pandacube.lib.paper.inventory.DummyPlayerInventory;
|
||||
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.util.ProblemReporter;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStackWithSlot;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.TagValueInput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.TagValueOutput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ValueInput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ValueOutputTypedOutputList;
|
||||
import fr.pandacube.lib.paper.util.ExperienceUtil;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
/**
|
||||
* A wrapper to easily manipulate the player data file.
|
||||
*
|
||||
* @param data The NBT data structure as it is stored in the player file.
|
||||
*/
|
||||
public record PlayerDataWrapper(CompoundTag data) {
|
||||
|
||||
/**
|
||||
* Creates a new wrapper for the provided player data.
|
||||
* @param data the NBT data to wrap.
|
||||
*/
|
||||
public PlayerDataWrapper(CompoundTag data) {
|
||||
this.data = data == null ? new CompoundTag() : data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a snapshot of the inventory of this player.
|
||||
* If the inventory is modified, the {@link #setInventory(PlayerInventory)} method should be called to update the
|
||||
* data in this wrapper.
|
||||
* @return the player inventory.
|
||||
*/
|
||||
public PlayerInventory getInventory() {
|
||||
return new DummyPlayerInventory(
|
||||
getBukkitInventory("Inventory", InventoryType.PLAYER, this::fromNBTtoBukkitInventorySlot),
|
||||
getHeldItemSlot());
|
||||
}
|
||||
|
||||
private int fromNBTtoBukkitInventorySlot(int nbtSlot) {
|
||||
// cat nbEl NBTSlot bukkitSlot NBT->Bukkit
|
||||
// items 36 0-35 ==
|
||||
// armor 4 starts at 100 36-39 -100 + 36
|
||||
// offhand 1 starts at 150 40 -150 + 40
|
||||
if (nbtSlot >= 0 && nbtSlot < 36) { // regular inventory slots
|
||||
return nbtSlot;
|
||||
}
|
||||
if (nbtSlot >= 100 && nbtSlot < 104) { // armor slots
|
||||
return nbtSlot - 100 + 36;
|
||||
}
|
||||
if (nbtSlot == 150) { // second hand
|
||||
return 40;
|
||||
}
|
||||
throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot);
|
||||
setHeldItemSlot(inv.getHeldItemSlot());
|
||||
}
|
||||
|
||||
private int fromBukkitToNBTInventorySlot(int bukkitSlot) {
|
||||
if (bukkitSlot >= 0 && bukkitSlot < 36) { // regular inventory slots
|
||||
return bukkitSlot;
|
||||
}
|
||||
if (bukkitSlot >= 36 && bukkitSlot < 40) { // armor slots
|
||||
return bukkitSlot + 100 - 36;
|
||||
}
|
||||
if (bukkitSlot == 40) { // second hand
|
||||
return 150;
|
||||
}
|
||||
throw new IllegalArgumentException("Unrecognized Bukkit player inventory slot " + bukkitSlot);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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) {
|
||||
setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity());
|
||||
}
|
||||
|
||||
|
||||
private Inventory getBukkitInventory(String nbtKey, InventoryType bukkitType, IntUnaryOperator nbtToBukkitSlotConverter) {
|
||||
Map<Integer, ItemStack> stacks = getRawInventoryContent(nbtKey);
|
||||
Inventory inv = Bukkit.createInventory(null, bukkitType);
|
||||
if (stacks.isEmpty())
|
||||
return inv;
|
||||
for (Entry<Integer, ItemStack> is : stacks.entrySet()) {
|
||||
inv.setItem(nbtToBukkitSlotConverter.applyAsInt(is.getKey()), is.getValue());
|
||||
}
|
||||
return inv;
|
||||
}
|
||||
|
||||
private Map<Integer, ItemStack> getRawInventoryContent(String key) {
|
||||
|
||||
ValueInput vi = TagValueInput.createGlobal(ProblemReporter.DISCARDING(), data);
|
||||
Iterable<?> listNMSItemStackWithSlot = ReflectWrapper.unwrap(vi.listOrEmpty(key, ItemStackWithSlot.CODEC()));
|
||||
|
||||
Map<Integer, ItemStack> stacks = new TreeMap<>();
|
||||
|
||||
for (Object nmsISWS : listNMSItemStackWithSlot) {
|
||||
ItemStackWithSlot isws = ReflectWrapper.wrap(nmsISWS, ItemStackWithSlot.class);
|
||||
|
||||
int nbtSlot = isws.slot() & 255;
|
||||
Optional.of(isws.stack())
|
||||
.map(nms -> filterStack(CraftItemStack.asCraftMirror(nms)))
|
||||
.ifPresent(is -> stacks.put(nbtSlot, is));
|
||||
}
|
||||
return stacks;
|
||||
}
|
||||
|
||||
|
||||
private void setBukkitInventory(String nbtKey, Inventory inv, IntUnaryOperator bukkitToNBTSlotConverter) {
|
||||
Map<Integer, ItemStack> stacks = new TreeMap<>();
|
||||
if (inv == null) {
|
||||
setRawInventoryContent(nbtKey, stacks);
|
||||
return;
|
||||
}
|
||||
for (int bukkitSlot = 0; bukkitSlot < inv.getSize(); bukkitSlot++) {
|
||||
ItemStack is = filterStack(inv.getItem(bukkitSlot));
|
||||
if (is == null)
|
||||
continue;
|
||||
int nbtSlot = bukkitToNBTSlotConverter.applyAsInt(bukkitSlot);
|
||||
stacks.put(nbtSlot, is);
|
||||
}
|
||||
setRawInventoryContent(nbtKey, stacks);
|
||||
}
|
||||
|
||||
private void setRawInventoryContent(String key, Map<Integer, ItemStack> stacks) {
|
||||
|
||||
TagValueOutput vo = TagValueOutput.createWrappingGlobal(ProblemReporter.DISCARDING(), data);
|
||||
ValueOutputTypedOutputList listNMSItemStackWithSlot = vo.list(key, ItemStackWithSlot.CODEC());
|
||||
|
||||
for (Entry<Integer, ItemStack> is : stacks.entrySet()) {
|
||||
ItemStack stack = filterStack(is.getValue());
|
||||
if (stack == null)
|
||||
continue;
|
||||
|
||||
listNMSItemStackWithSlot.add(ReflectWrapper.unwrap(new ItemStackWithSlot(is.getKey(), CraftItemStack.asNMSCopy(is.getValue()))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ItemStack filterStack(ItemStack is) {
|
||||
return is == null || is.isEmpty() || is.getAmount() <= 0 ? null : is;
|
||||
}
|
||||
|
||||
|
||||
private int getHeldItemSlot() {
|
||||
return data.getInt("SelectedItemSlot").orElse(0);
|
||||
}
|
||||
|
||||
private void setHeldItemSlot(int slot) {
|
||||
data.putInt("SelectedItemSlot", slot);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the score of the player, as stored in the data with the key {@code Score}.
|
||||
* @return the value of Score.
|
||||
*/
|
||||
public int getScore() {
|
||||
return data.getInt("Score").orElse(0);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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() {
|
||||
return data.getInt("XpTotal").orElse(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
data.putInt("XpTotal", xp);
|
||||
double levelAndExp = ExperienceUtil.getLevelFromExp(xp);
|
||||
int level = (int) levelAndExp;
|
||||
double expProgress = levelAndExp - level;
|
||||
data.putInt("XpLevel", level);
|
||||
data.putFloat("XpP", (float) expProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an error occurred while loading the data of the player from the file.
|
||||
*/
|
||||
public static class PlayerDataLoadException extends RuntimeException {
|
||||
/* package */ PlayerDataLoadException(String playerName, UUID playerId, Throwable 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.UUID;
|
||||
|
||||
/**
|
||||
* Handles the player related configuration that is not persisted to disk.
|
||||
*/
|
||||
public class PlayerNonPersistentConfig {
|
||||
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"),
|
||||
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"));
|
||||
}
|
||||
|
||||
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"));
|
||||
if (ce == null)
|
||||
return null;
|
||||
if (!ce.expiration.valid(playerId, key)) {
|
||||
if (!ce.expirationPolicy.valid(player, key)) {
|
||||
playerData.remove(key);
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
expirationTick = tick + expirationDelayTick;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean valid(UUID player, String key) {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@@ -103,7 +172,7 @@ public class PlayerNonPersistentConfig {
|
||||
|
||||
|
||||
private static class ConfigListeners implements Listener {
|
||||
public ConfigListeners() {
|
||||
private ConfigListeners() {
|
||||
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
||||
}
|
||||
|
||||
@@ -111,7 +180,7 @@ public class PlayerNonPersistentConfig {
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>())
|
||||
.entrySet()
|
||||
.removeIf(e -> e.getValue().expiration instanceof ExpiresLogout);
|
||||
.removeIf(e -> e.getValue().expirationPolicy instanceof ExpiresLogout);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -119,4 +188,9 @@ public class PlayerNonPersistentConfig {
|
||||
tick++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private PlayerNonPersistentConfig() {}
|
||||
}
|
||||
|
@@ -1,716 +0,0 @@
|
||||
package fr.pandacube.lib.paper.reflect;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import fr.pandacube.lib.reflect.Reflect;
|
||||
import fr.pandacube.lib.reflect.ReflectClass;
|
||||
import fr.pandacube.lib.reflect.ReflectField;
|
||||
import fr.pandacube.lib.reflect.ReflectMember;
|
||||
import fr.pandacube.lib.reflect.ReflectMethod;
|
||||
import net.fabricmc.mappingio.MappingReader;
|
||||
import net.fabricmc.mappingio.format.MappingFormat;
|
||||
import net.fabricmc.mappingio.tree.MappingTree;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
/**
|
||||
* Provides reflection tools related to Minecraft servers internals.
|
||||
* It automatically deals with the obfuscated classes and methods.
|
||||
*/
|
||||
public class NMSReflect {
|
||||
|
||||
|
||||
private static String OBF_NAMESPACE;
|
||||
private static String MOJ_NAMESPACE;
|
||||
|
||||
/* package */ static final Map<String, ClassMapping> CLASSES_BY_OBF = new TreeMap<>();
|
||||
/* package */ static final Map<String, ClassMapping> CLASSES_BY_MOJ = new TreeMap<>();
|
||||
|
||||
private static Boolean IS_SERVER_OBFUSCATED;
|
||||
|
||||
private static boolean isInit = false;
|
||||
|
||||
/**
|
||||
* Initialize all the obfuscation mapping data.
|
||||
*/
|
||||
public static void init() {
|
||||
|
||||
synchronized (NMSReflect.class) {
|
||||
if (isInit)
|
||||
return;
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
Log.info("[NMSReflect] Initializing NMS obfuscation mapping...");
|
||||
|
||||
try {
|
||||
ReflectClass<?> obfHelperClass;
|
||||
try {
|
||||
obfHelperClass = Reflect.ofClass("io.papermc.paper.util.ObfHelper");
|
||||
|
||||
OBF_NAMESPACE = (String) obfHelperClass.field("SPIGOT_NAMESPACE").getStaticValue();
|
||||
MOJ_NAMESPACE = (String) obfHelperClass.field("MOJANG_PLUS_YARN_NAMESPACE").getStaticValue();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new ReflectiveOperationException("Unable to find the Paper obfuscation mapping class or class members.", e);
|
||||
}
|
||||
|
||||
List<ClassMapping> mappings = loadMappings(obfHelperClass);
|
||||
for (ClassMapping clazz : mappings) {
|
||||
CLASSES_BY_OBF.put(clazz.obfName, clazz);
|
||||
CLASSES_BY_MOJ.put(clazz.mojName, clazz);
|
||||
}
|
||||
|
||||
// determine if the runtime server is obfuscated
|
||||
ClassNotFoundException exIfUnableToDetermine = null;
|
||||
for (ClassMapping clazz : CLASSES_BY_OBF.values()) {
|
||||
if (clazz.obfName.equals(clazz.mojName) // avoid direct collision between obf and unobf class names
|
||||
|| CLASSES_BY_MOJ.containsKey(clazz.obfName) // avoid indirect collision
|
||||
|| CLASSES_BY_OBF.containsKey(clazz.mojName))// avoid indirect collision
|
||||
continue;
|
||||
|
||||
try {
|
||||
Class.forName(clazz.obfName);
|
||||
IS_SERVER_OBFUSCATED = true;
|
||||
break;
|
||||
} catch (ClassNotFoundException e) {
|
||||
try {
|
||||
Class.forName(clazz.mojName);
|
||||
IS_SERVER_OBFUSCATED = false;
|
||||
break;
|
||||
} catch (ClassNotFoundException ee) {
|
||||
ee.addSuppressed(e);
|
||||
if (exIfUnableToDetermine == null)
|
||||
exIfUnableToDetermine = ee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_SERVER_OBFUSCATED == null) {
|
||||
throw new IllegalStateException("Unable to determine if this server is obfuscated or not", exIfUnableToDetermine);
|
||||
}
|
||||
if (IS_SERVER_OBFUSCATED) {
|
||||
Log.info("[NMSReflect] NMS runtime classes are obfuscated.");
|
||||
}
|
||||
else {
|
||||
Log.info("[NMSReflect] NMS runtime classes are mojang mapped.");
|
||||
}
|
||||
|
||||
int missingRuntimeClasses = 0;
|
||||
for (ClassMapping clazz : mappings) {
|
||||
try {
|
||||
clazz.cacheReflectClass();
|
||||
} catch (Throwable e) {
|
||||
missingRuntimeClasses++;
|
||||
if (e instanceof ClassNotFoundException cnfe) {
|
||||
Log.warning("[NMSReflect] Missing runtime class " + cnfe.getMessage() + (IS_SERVER_OBFUSCATED ? (" (moj class: " + clazz.mojName + ")") : ""));
|
||||
}
|
||||
else {
|
||||
Log.warning("[NMSReflect] Unable to load runtime class " + (IS_SERVER_OBFUSCATED ? (clazz.obfName + " (moj class: " + clazz.mojName + ")") : clazz.mojName));
|
||||
Log.warning(e); // throwable on separate log message due to sometimes the message not showing at all because of this exception
|
||||
}
|
||||
CLASSES_BY_OBF.remove(clazz.obfName);
|
||||
CLASSES_BY_MOJ.remove(clazz.mojName);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingRuntimeClasses > 0) {
|
||||
Log.warning("[NMSReflect] " + missingRuntimeClasses + " class have been removed from the mapping data due to the previously stated errors.");
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
CLASSES_BY_OBF.clear();
|
||||
CLASSES_BY_MOJ.clear();
|
||||
Log.severe("[NMSReflect] The plugin will not have access to NMS stuff due to an error while loading the obfuscation mapping.", t);
|
||||
}
|
||||
Log.info("[NMSReflect] Obfuscation mapping loaded for " + CLASSES_BY_OBF.size() + " classes.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the class mapping instance for the provided class.
|
||||
* @param mojName the binary name of the desired class, on the mojang mapping.
|
||||
* @return the class mapping instance for the provided class.
|
||||
* @throws NullPointerException if there is no mapping for the provided Mojang mapped class.
|
||||
*/
|
||||
public static ClassMapping mojClass(String mojName) {
|
||||
return Objects.requireNonNull(CLASSES_BY_MOJ.get(mojName), "Unable to find the Mojang mapped class '" + mojName + "'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static List<ClassMapping> loadMappings(ReflectClass<?> obfHelperClass) throws IOException {
|
||||
try (final InputStream mappingsInputStream = obfHelperClass.get().getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
|
||||
if (mappingsInputStream == null) {
|
||||
throw new RuntimeException("Unable to find the obfuscation mapping file in the Paper jar.");
|
||||
}
|
||||
|
||||
MemoryMappingTree tree = new MemoryMappingTree();
|
||||
MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2_FILE, tree);
|
||||
|
||||
List<ClassMapping> classes = new ArrayList<>();
|
||||
for (MappingTree.ClassMapping cls : tree.getClasses()) {
|
||||
classes.add(new ClassMapping(cls));
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prints an HTML rendering of the currently loaded obfuscation mapping, into the provided {@link PrintStream}.
|
||||
* @param out the stream in which to print the HTML content.
|
||||
*/
|
||||
public static void printHTMLMapping(PrintStream out) {
|
||||
String title = "Obfuscation mapping - " + Bukkit.getName() + " version " + Bukkit.getVersion();
|
||||
out.println("<!DOCTYPE html><html><head>\n"
|
||||
+ "<title>" + title + "</title>\n"
|
||||
+ """
|
||||
<style>
|
||||
html {
|
||||
background-color: #2F2F2F;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
a:not(.cl) {
|
||||
color: #1290C3;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
tr:nth-child(2n) {
|
||||
background-color: #373737;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
tr > *:first-child {
|
||||
padding-right: .5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
b.pu {
|
||||
color: #0C0;
|
||||
}
|
||||
b.pt {
|
||||
color: #FC0;
|
||||
}
|
||||
b.pv {
|
||||
color: #F00;
|
||||
}
|
||||
b.pk {
|
||||
color: #66F;
|
||||
}
|
||||
td {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 1.1em;
|
||||
border-top: solid 1px white;
|
||||
}
|
||||
.kw {
|
||||
color: #CC6C1D;
|
||||
}
|
||||
.cl {
|
||||
color: #1290C3;
|
||||
}
|
||||
.mtd {
|
||||
color: #1EB540;
|
||||
}
|
||||
.fld {
|
||||
color: #8DDAF8;
|
||||
}
|
||||
.st {
|
||||
font-style: italic;
|
||||
}
|
||||
.st.fn {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head><body>
|
||||
"""
|
||||
+ "<h1>" + title + "</h1>\n"
|
||||
+ """
|
||||
<p>
|
||||
<b>C</b>: <span class='kw'>class</span>
|
||||
<b>E</b>: <span class='kw'>enum</span>
|
||||
<b>I</b>: <span class='kw'>interface</span>
|
||||
<b>@</b>: <span class='kw'>@interface</span>
|
||||
<b>R</b>: <span class='kw'>record</span><br>
|
||||
<b>●</b>: field
|
||||
<b>c</b>: constructor
|
||||
<b>⬤</b>: method<br>
|
||||
<b class='pu'>⬤</b>: <span class='kw'>public</span>
|
||||
<b class='pt'>⬤</b>: <span class='kw'>protected</span>
|
||||
<b class='pk'>⬤</b>: package
|
||||
<b class='pv'>⬤</b>: <span class='kw'>private</span><br>
|
||||
<sup>S</sup>: <span class='kw'>static</span>
|
||||
<sup>A</sup>: <span class='kw'>abstract</span>
|
||||
<sup>F</sup>: <span class='kw'>final</span>
|
||||
</p>
|
||||
<table>
|
||||
""");
|
||||
out.println("<tr><th>ns</th><th>" + OBF_NAMESPACE + "</th><th>" + MOJ_NAMESPACE + "</th></tr>");
|
||||
for (ClassMapping clazz : CLASSES_BY_OBF.values()) {
|
||||
clazz.printHTML(out);
|
||||
}
|
||||
out.println("</table><p>Generated by <a href='https://github.com/marcbal'>marcbal</a>"
|
||||
+ " using <a href='https://github.com/PandacubeFr/PandaLib/blob/master/Paper/src/main/java/fr/pandacube/lib/paper/reflect/NMSReflect.java'>this tool</a>"
|
||||
+ " running on <a href='https://papermc.io/'>" + Bukkit.getName() + "</a> version " + Bukkit.getVersion() + "</p>"
|
||||
+ "</body></html>");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents the mapping between the obfuscated and Mojang names of a class and all its members.
|
||||
*/
|
||||
public static class ClassMapping {
|
||||
private static int nextID = 0;
|
||||
|
||||
/* package */ final int id = nextID++;
|
||||
/* package */ final String obfName;
|
||||
/* package */ final String mojName;
|
||||
|
||||
private final Map<MethodId, MemberMapping<MethodId, ReflectMethod<?>>> methodsByObf = new TreeMap<>();
|
||||
private final Map<MethodId, MemberMapping<MethodId, ReflectMethod<?>>> methodsByMoj = new TreeMap<>();
|
||||
private final Map<String, MemberMapping<String, ReflectField<?>>> fieldsByObf = new TreeMap<>();
|
||||
private final Map<String, MemberMapping<String, ReflectField<?>>> fieldsByMoj = new TreeMap<>();
|
||||
|
||||
private ReflectClass<?> runtimeReflectClass = null;
|
||||
|
||||
private ClassMapping(MappingTree.ClassMapping cls) {
|
||||
obfName = binaryClassName(cls.getName(OBF_NAMESPACE));
|
||||
mojName = binaryClassName(cls.getName(MOJ_NAMESPACE));
|
||||
|
||||
cls.getMethods().stream().map(MemberMapping::of).forEach(method -> {
|
||||
method.declaringClass = this;
|
||||
methodsByObf.put(method.obfDesc.identifier, method);
|
||||
methodsByMoj.put(method.mojDesc.identifier, method);
|
||||
});
|
||||
cls.getFields().stream().map(MemberMapping::of).forEach(field -> {
|
||||
field.declaringClass = this;
|
||||
fieldsByObf.put(field.obfDesc.identifier, field);
|
||||
fieldsByMoj.put(field.mojDesc.identifier, field);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private synchronized void cacheReflectClass() throws ClassNotFoundException {
|
||||
if (runtimeReflectClass == null)
|
||||
runtimeReflectClass = Reflect.ofClass(IS_SERVER_OBFUSCATED ? obfName : mojName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the actual runtime {@link Class} represented by this {@link ClassMapping}, wrapped into a
|
||||
* {@link ReflectClass}.
|
||||
* @return the actual runtime {@link Class} represented by this {@link ClassMapping}, wrapped into a
|
||||
* * {@link ReflectClass}.
|
||||
*/
|
||||
public ReflectClass<?> runtimeReflect() {
|
||||
return runtimeReflectClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual runtime Class represented by this {@link ClassMapping}.
|
||||
* @return the actual runtime Class represented by this {@link ClassMapping}.
|
||||
*/
|
||||
public Class<?> runtimeClass() {
|
||||
return runtimeReflectClass.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the actual runtime Method that has the provided mojang name and parameter types, wrapped into a
|
||||
* {@link ReflectMethod}.
|
||||
* @param mojName the Mojang mapped name of the method.
|
||||
* @param mojParametersType the list of parameters of the method.
|
||||
* Each parameter type must be an instance of one of the following type:
|
||||
* {@link NMSTypeWrapper}, {@link Class}, {@link ReflectClass} or {@link ClassMapping}.
|
||||
* @return the actual runtime Method that has the provided mojang name and parameter types, wrapped into a
|
||||
* {@link ReflectMethod}.
|
||||
* @throws IllegalArgumentException if one of the parameter has an invalid type
|
||||
* @throws NullPointerException if one of the parameter is null, or if there is no mapping for the provided Mojang mapped method.
|
||||
* @throws ClassNotFoundException if there is no runtime class to represent one of the provided parametersType.
|
||||
* @throws NoSuchMethodException if there is no runtime method to represent the provided method.
|
||||
*/
|
||||
public ReflectMethod<?> mojMethod(String mojName, Object... mojParametersType) throws ClassNotFoundException, NoSuchMethodException {
|
||||
MethodId mId = new MethodId(mojName, NMSTypeWrapper.toTypeList(Arrays.asList(mojParametersType)));
|
||||
MemberMapping<MethodId, ReflectMethod<?>> mm = methodsByMoj.get(mId);
|
||||
Objects.requireNonNull(mm, "Unable to find the Mojang mapped method " + mId);
|
||||
|
||||
try {
|
||||
return mm.getReflectMember();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (e instanceof ClassNotFoundException cnfe)
|
||||
throw cnfe;
|
||||
if (e instanceof NoSuchMethodException nsme)
|
||||
throw nsme;
|
||||
// should not have another exception
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the actual runtime Field that has the provided mojang name, wrapped into a {@link ReflectField}.
|
||||
* @param mojName the Mojang mapped name of the field.
|
||||
* @return the actual runtime Field that has the provided mojang name, wrapped into a {@link ReflectField}.
|
||||
* @throws NullPointerException if there is no mapping for the provided Mojang mapped field.
|
||||
* @throws NoSuchFieldException if there is no runtime field to represent the provided mojang field.
|
||||
*/
|
||||
public ReflectField<?> mojField(String mojName) throws NoSuchFieldException {
|
||||
MemberMapping<String, ReflectField<?>> fm = fieldsByMoj.get(mojName);
|
||||
Objects.requireNonNull(fm, "Unable to find the Mojang mapped field '" + mojName + "'");
|
||||
try {
|
||||
return fm.getReflectMember();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (e instanceof NoSuchFieldException nsfe)
|
||||
throw nsfe;
|
||||
// should not have another exception
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ String toClickableHTML(boolean isObfClass) {
|
||||
String classToPrint = isObfClass ? obfName : mojName;
|
||||
String classSimpleName = classToPrint.substring(classToPrint.lastIndexOf('.') + 1);
|
||||
String htmlTitle = classSimpleName.equals(classToPrint) ? "" : (" title='" + classToPrint + "'");
|
||||
return "<a href='#c" + id + "'" + htmlTitle + " class='cl'>" + classSimpleName + "</a>";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ NMSTypeWrapper toType(boolean obf) {
|
||||
return new NMSTypeWrapper(obf ? obfName : mojName, 0);
|
||||
}
|
||||
|
||||
|
||||
private void printHTML(PrintStream out) {
|
||||
String modifiersHTML = classModifiersToHTML(runtimeClass());
|
||||
out.println("<tr id='c" + id + "'><th>" + modifiersHTML + "</th><th>" + nameToHTML(true) + "</th><th>" + nameToHTML(false) + "</th></tr>");
|
||||
fieldsByObf.values().stream().filter(MemberMapping::isStatic).forEach(f -> f.printHTML(out));
|
||||
methodsByObf.values().stream().filter(MemberMapping::isStatic).forEach(m -> m.printHTML(out));
|
||||
printConstructorsHTML(out);
|
||||
fieldsByObf.values().stream().filter(mm -> !mm.isStatic()).forEach(f -> f.printHTML(out));
|
||||
methodsByObf.values().stream().filter(mm -> !mm.isStatic()).forEach(m -> m.printHTML(out));
|
||||
}
|
||||
|
||||
private String nameToHTML(boolean obf) {
|
||||
String classToPrint = obf ? obfName : mojName;
|
||||
int packageSep = classToPrint.lastIndexOf('.');
|
||||
String classSimpleName = classToPrint.substring(packageSep + 1);
|
||||
String classPackages = classToPrint.substring(0, Math.max(packageSep, 0));
|
||||
String classHTML = (packageSep >= 0 ? (classPackages + ".") : "") + "<b class='cl'>" + classSimpleName + "</b>";
|
||||
|
||||
NMSTypeWrapper superClass = superClass(obf);
|
||||
String superClassHTML = superClass == null ? "" : (" <span class='kw'>extends</span> " + superClass.toHTML(obf));
|
||||
|
||||
List<NMSTypeWrapper> superInterfaces = superInterfaces(obf);
|
||||
String superInterfacesHTML = superInterfaces.isEmpty() ? ""
|
||||
: (" <span class='kw'>implements</span> " + superInterfaces.stream().map(t -> t.toHTML(obf)).collect(Collectors.joining(", ")));
|
||||
|
||||
return classHTML + superClassHTML + superInterfacesHTML;
|
||||
}
|
||||
|
||||
private NMSTypeWrapper superClass(boolean obf) {
|
||||
Class<?> superClass = runtimeClass().getSuperclass();
|
||||
if (superClass == null || superClass.equals(Object.class) || superClass.equals(Enum.class) || superClass.equals(Record.class))
|
||||
return null;
|
||||
ClassMapping cm = (IS_SERVER_OBFUSCATED ? CLASSES_BY_OBF : CLASSES_BY_MOJ).get(superClass.getName());
|
||||
return (cm != null) ? cm.toType(obf) : NMSTypeWrapper.of(superClass);
|
||||
}
|
||||
|
||||
private List<NMSTypeWrapper> superInterfaces(boolean obf) {
|
||||
Class<?>[] interfaces = runtimeClass().getInterfaces();
|
||||
List<NMSTypeWrapper> types = new ArrayList<>(interfaces.length);
|
||||
for (Class<?> i : interfaces) {
|
||||
ClassMapping cm = (IS_SERVER_OBFUSCATED ? CLASSES_BY_OBF : CLASSES_BY_MOJ).get(i.getName());
|
||||
types.add((cm != null) ? cm.toType(obf) : NMSTypeWrapper.of(i));
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
|
||||
private void printConstructorsHTML(PrintStream out) {
|
||||
String classObfSimpleName = obfName.substring(obfName.lastIndexOf('.') + 1);
|
||||
String classMojSimpleName = mojName.substring(mojName.lastIndexOf('.') + 1);
|
||||
for (Constructor<?> ct : runtimeClass().getDeclaredConstructors()) {
|
||||
List<NMSTypeWrapper> obfParams = new ArrayList<>();
|
||||
List<NMSTypeWrapper> mojParams = new ArrayList<>();
|
||||
for (Class<?> param : ct.getParameterTypes()) {
|
||||
ClassMapping cm = (IS_SERVER_OBFUSCATED ? CLASSES_BY_OBF : CLASSES_BY_MOJ).get(param.getName());
|
||||
if (cm == null) {
|
||||
NMSTypeWrapper t = NMSTypeWrapper.of(param);
|
||||
obfParams.add(t);
|
||||
mojParams.add(t);
|
||||
}
|
||||
else {
|
||||
obfParams.add(cm.toType(true));
|
||||
mojParams.add(cm.toType(false));
|
||||
}
|
||||
}
|
||||
out.println("<tr>"
|
||||
+ "<td>" + elementModifiersToHTML("c", ct.getModifiers()) + "</td>"
|
||||
+ "<td><b class='mtd'>" + classObfSimpleName + "</b>(" + obfParams.stream().map(t -> t.toHTML(true)).collect(Collectors.joining(", ")) + ")</td>"
|
||||
+ "<td><b class='mtd'>" + classMojSimpleName + "</b>(" + mojParams.stream().map(t -> t.toHTML(false)).collect(Collectors.joining(", ")) + ")</td>"
|
||||
+ "</tr>");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private record MethodId(String name, List<NMSTypeWrapper> parametersType) implements Comparable<MethodId> {
|
||||
@Override
|
||||
public int compareTo(MethodId o) {
|
||||
int cmp = name.compareTo(o.name);
|
||||
if (cmp != 0)
|
||||
return cmp;
|
||||
return toString().compareTo(o.toString());
|
||||
}
|
||||
|
||||
private String toHTML(boolean isObfClass, boolean isStatic, boolean isFinal) {
|
||||
String paramsHTML = parametersType.stream().map(p -> p.toHTML(isObfClass)).collect(Collectors.joining(", "));
|
||||
String cl = "mtd";
|
||||
if (isStatic)
|
||||
cl += " st";
|
||||
if (isFinal)
|
||||
cl += " fn";
|
||||
return "<span class='" + cl + "'>" + name + "</span>(" + paramsHTML + ")";
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String paramsStr = parametersType.stream().map(NMSTypeWrapper::toString).collect(Collectors.joining(", "));
|
||||
return name + "(" + paramsStr + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private record MemberDesc<I extends Comparable<I>>(I identifier, NMSTypeWrapper returnType) {
|
||||
private String toHTML(boolean isObfClass, boolean isStatic, boolean isFinal) {
|
||||
String identifierHTML = "";
|
||||
if (identifier instanceof MethodId mId)
|
||||
identifierHTML = mId.toHTML(isObfClass, isStatic, isFinal);
|
||||
else if (identifier instanceof String n) {
|
||||
String cl = "fld";
|
||||
if (isStatic)
|
||||
cl += " st";
|
||||
if (isFinal)
|
||||
cl += " fn";
|
||||
identifierHTML = "<span class='" + cl + "'>" + n + "</span>";
|
||||
}
|
||||
return returnType.toHTML(isObfClass) + " " + identifierHTML;
|
||||
}
|
||||
|
||||
private static MemberDesc<MethodId> of(MappingTree.MethodMapping member, String namespace) {
|
||||
String desc = member.getDesc(namespace);
|
||||
try (StringReader descReader = new StringReader(desc)) {
|
||||
char r = (char) descReader.read();
|
||||
if (r != '(')
|
||||
throw new IllegalArgumentException("Invalid method description '" + desc + "'. Must start with '('.");
|
||||
|
||||
List<NMSTypeWrapper> paramsType = new ArrayList<>();
|
||||
|
||||
while (((char) descReader.read()) != ')') {
|
||||
descReader.skip(-1);
|
||||
paramsType.add(NMSTypeWrapper.parse(descReader));
|
||||
}
|
||||
|
||||
NMSTypeWrapper retType = NMSTypeWrapper.parse(descReader);
|
||||
return new MemberDesc<>(new MethodId(member.getName(namespace), Collections.unmodifiableList(paramsType)), retType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("StringReader read error", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static MemberDesc<String> of(MappingTree.FieldMapping member, String namespace) {
|
||||
StringReader descReader = new StringReader(member.getDesc(namespace));
|
||||
return new MemberDesc<>(member.getName(namespace), NMSTypeWrapper.parse(descReader));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static abstract class MemberMapping<I extends Comparable<I>, R extends ReflectMember<?, ?, ?, ?>> {
|
||||
private final String htmlTypeChar;
|
||||
/* package */ final MemberDesc<I> obfDesc, mojDesc;
|
||||
/* package */ ClassMapping declaringClass;
|
||||
private MemberMapping(String htmlType, MemberDesc<I> obfDesc, MemberDesc<I> mojDesc) {
|
||||
htmlTypeChar = htmlType;
|
||||
this.obfDesc = obfDesc;
|
||||
this.mojDesc = mojDesc;
|
||||
}
|
||||
|
||||
/* package */ void printHTML(PrintStream out) {
|
||||
int mod = 0;
|
||||
try {
|
||||
mod = getReflectMember().getModifiers();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// ignore
|
||||
}
|
||||
boolean isStatic = Modifier.isStatic(mod);
|
||||
boolean isFinal = Modifier.isFinal(mod);
|
||||
out.println("<tr>"
|
||||
+ "<td>" + elementModifiersToHTML(htmlTypeChar, mod) + "</td>"
|
||||
+ "<td>" + obfDesc.toHTML(true, isStatic, isFinal) + "</td>"
|
||||
+ "<td>" + mojDesc.toHTML(false, isStatic, isFinal) + "</td>"
|
||||
+ "</tr>");
|
||||
}
|
||||
|
||||
/* package */ MemberDesc<I> getReflectDesc() {
|
||||
return (IS_SERVER_OBFUSCATED ? obfDesc : mojDesc);
|
||||
}
|
||||
|
||||
/* package */ abstract R getReflectMember() throws ReflectiveOperationException;
|
||||
|
||||
/* package */ boolean isStatic() {
|
||||
try {
|
||||
return Modifier.isStatic(getReflectMember().getModifiers());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Log.severe(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static MemberMapping<MethodId, ReflectMethod<?>> of(MappingTree.MethodMapping mioMapping) {
|
||||
return new MemberMapping<>("⬤", MemberDesc.of(mioMapping, OBF_NAMESPACE), MemberDesc.of(mioMapping, MOJ_NAMESPACE)) {
|
||||
@Override
|
||||
ReflectMethod<?> getReflectMember() throws ClassNotFoundException, NoSuchMethodException {
|
||||
MethodId id = getReflectDesc().identifier;
|
||||
return declaringClass.runtimeReflectClass.method(id.name, NMSTypeWrapper.toClassArray(id.parametersType));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static MemberMapping<String, ReflectField<?>> of(MappingTree.FieldMapping mioMapping) {
|
||||
return new MemberMapping<>("●", MemberDesc.of(mioMapping, OBF_NAMESPACE), MemberDesc.of(mioMapping, MOJ_NAMESPACE)) {
|
||||
@Override
|
||||
ReflectField<?> getReflectMember() throws NoSuchFieldException {
|
||||
String id = getReflectDesc().identifier;
|
||||
return declaringClass.runtimeReflectClass.field(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static String binaryClassName(String cl) {
|
||||
return cl.replace('/', '.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static String classModifiersToHTML(Class<?> clazz) {
|
||||
String elementHTMLType;
|
||||
|
||||
if (clazz.isEnum())
|
||||
elementHTMLType = "E";
|
||||
else if (clazz.isAnnotation())
|
||||
elementHTMLType = "@";
|
||||
else if (clazz.isInterface())
|
||||
elementHTMLType = "I";
|
||||
else if (clazz.isRecord())
|
||||
elementHTMLType = "R";
|
||||
else if (clazz.isPrimitive())
|
||||
elementHTMLType = "";
|
||||
else
|
||||
elementHTMLType = "C";
|
||||
|
||||
return elementModifiersToHTML(elementHTMLType, clazz.getModifiers());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static String elementModifiersToHTML(String elementHTMLType, int elModifiers) {
|
||||
String html = "<b class='";
|
||||
|
||||
if (Modifier.isPublic(elModifiers))
|
||||
html += "pu";
|
||||
else if (Modifier.isProtected(elModifiers))
|
||||
html += "pt";
|
||||
else if (Modifier.isPrivate(elModifiers))
|
||||
html += "pv";
|
||||
else
|
||||
html += "pk";
|
||||
|
||||
html += "'>" + elementHTMLType + "</b>";
|
||||
|
||||
boolean isStatic = Modifier.isStatic(elModifiers);
|
||||
boolean isAbstract = Modifier.isAbstract(elModifiers);
|
||||
boolean isFinal = Modifier.isFinal(elModifiers);
|
||||
|
||||
if (isStatic || isAbstract || isFinal) {
|
||||
html += "<sup>";
|
||||
if (isStatic)
|
||||
html += "S";
|
||||
if (isAbstract)
|
||||
html += "A";
|
||||
if (isFinal)
|
||||
html += "F";
|
||||
html += "</sup>";
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// ● (field)
|
||||
// ⬤ (method)
|
||||
|
||||
}
|
@@ -1,194 +0,0 @@
|
||||
package fr.pandacube.lib.paper.reflect;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import fr.pandacube.lib.reflect.ReflectClass;
|
||||
import fr.pandacube.lib.paper.reflect.NMSReflect.ClassMapping;
|
||||
|
||||
/* package */ class NMSTypeWrapper implements Comparable<NMSTypeWrapper> {
|
||||
private final String type;
|
||||
private final int arrayDepth;
|
||||
|
||||
/* package */ NMSTypeWrapper(String type, int arrayDepth) {
|
||||
this.type = type;
|
||||
this.arrayDepth = arrayDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof NMSTypeWrapper ot && type.equals(ot.type) && arrayDepth == ot.arrayDepth;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return type.hashCode() ^ arrayDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(NMSTypeWrapper o) {
|
||||
return toString().compareTo(o.toString());
|
||||
}
|
||||
|
||||
Class<?> toClass() throws ClassNotFoundException {
|
||||
|
||||
Class<?> cl = switch(type) {
|
||||
case "boolean" -> boolean.class;
|
||||
case "byte" -> byte.class;
|
||||
case "char" -> char.class;
|
||||
case "double" -> double.class;
|
||||
case "float" -> float.class;
|
||||
case "int" -> int.class;
|
||||
case "long" -> long.class;
|
||||
case "short" -> short.class;
|
||||
case "void" -> void.class;
|
||||
default -> Class.forName(type);
|
||||
};
|
||||
|
||||
for (int i = 0; i < arrayDepth; i++) {
|
||||
cl = cl.arrayType();
|
||||
}
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
/* package */ static NMSTypeWrapper of(Class<?> cl) {
|
||||
int arrayDepth = 0;
|
||||
while (cl.isArray()) {
|
||||
cl = cl.getComponentType();
|
||||
arrayDepth++;
|
||||
}
|
||||
return new NMSTypeWrapper(cl.getName(), arrayDepth);
|
||||
}
|
||||
|
||||
public static NMSTypeWrapper of(ReflectClass<?> rc) {
|
||||
return arrayOf(rc, 0);
|
||||
}
|
||||
|
||||
public static NMSTypeWrapper arrayOf(ReflectClass<?> rc, int arrayDepth) {
|
||||
return new NMSTypeWrapper(rc.get().getName(), arrayDepth);
|
||||
}
|
||||
|
||||
public static NMSTypeWrapper mojOf(ClassMapping cm) {
|
||||
return arrayMojOf(cm, 0);
|
||||
}
|
||||
|
||||
public static NMSTypeWrapper arrayMojOf(ClassMapping cm, int arrayDepth) {
|
||||
return new NMSTypeWrapper(cm.mojName, arrayDepth);
|
||||
}
|
||||
|
||||
/* package */ static NMSTypeWrapper toType(Object typeObj) {
|
||||
Objects.requireNonNull(typeObj, "typeObj cannot be null");
|
||||
if (typeObj instanceof Class<?> cl) {
|
||||
return of(cl);
|
||||
}
|
||||
else if (typeObj instanceof ClassMapping cm) {
|
||||
return mojOf(cm);
|
||||
}
|
||||
else if (typeObj instanceof ReflectClass<?> rc) {
|
||||
return of(rc);
|
||||
}
|
||||
else if (typeObj instanceof NMSTypeWrapper t) {
|
||||
return t;
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("Unsupported object of type " + typeObj.getClass());
|
||||
}
|
||||
|
||||
/* package */ String toHTML(boolean isObfClass) {
|
||||
ClassMapping clMapping = (isObfClass ? NMSReflect.CLASSES_BY_OBF : NMSReflect.CLASSES_BY_MOJ).get(type);
|
||||
String typeHTML;
|
||||
if (clMapping != null) {
|
||||
typeHTML = clMapping.toClickableHTML(isObfClass);
|
||||
}
|
||||
else {
|
||||
String classToPrint = type;
|
||||
String classSimpleName = classToPrint.substring(classToPrint.lastIndexOf('.') + 1);
|
||||
String htmlTitle = classSimpleName.equals(classToPrint) ? "" : (" title='" + classToPrint + "'");
|
||||
if (!htmlTitle.equals("")) {
|
||||
typeHTML = "<span" + htmlTitle + " class='cl'>" + classSimpleName + "</span>";
|
||||
}
|
||||
else {
|
||||
typeHTML = "<span class='" + (isPrimitive() ? "kw" : "cl") + "'>" + classSimpleName + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return typeHTML + "[]".repeat(arrayDepth);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return type + "[]".repeat(arrayDepth);
|
||||
}
|
||||
|
||||
|
||||
public boolean isPrimitive() {
|
||||
try {
|
||||
return toClass().isPrimitive();
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static NMSTypeWrapper parse(StringReader desc) {
|
||||
try {
|
||||
int arrayDepth = 0;
|
||||
char c;
|
||||
while ((c = (char) desc.read()) == '[') {
|
||||
arrayDepth++;
|
||||
}
|
||||
String type = switch(c) {
|
||||
case 'Z' -> "boolean";
|
||||
case 'B' -> "byte";
|
||||
case 'C' -> "char";
|
||||
case 'D' -> "double";
|
||||
case 'F' -> "float";
|
||||
case 'I' -> "int";
|
||||
case 'J' -> "long";
|
||||
case 'S' -> "short";
|
||||
case 'L' -> {
|
||||
StringBuilder sbClass = new StringBuilder();
|
||||
char r;
|
||||
while ((r = (char) desc.read()) != ';') {
|
||||
sbClass.append(r);
|
||||
}
|
||||
yield NMSReflect.binaryClassName(sbClass.toString());
|
||||
}
|
||||
default -> "void";
|
||||
};
|
||||
return new NMSTypeWrapper(type, arrayDepth);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("StringReader read error", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static List<NMSTypeWrapper> toTypeList(List<Object> paramsType) {
|
||||
List<NMSTypeWrapper> types = new ArrayList<>(paramsType.size());
|
||||
for (int i = 0; i < paramsType.size(); i++) {
|
||||
Object param = paramsType.get(i);
|
||||
try {
|
||||
types.add(NMSTypeWrapper.toType(param));
|
||||
} catch (NullPointerException|IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Invalid parameterType at index " + i, e);
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/* package */ static Class<?>[] toClassArray(List<NMSTypeWrapper> types) throws ClassNotFoundException {
|
||||
Class<?>[] classes = new Class<?>[types.size()];
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
classes[i] = types.get(i).toClass();
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
}
|
@@ -23,4 +23,6 @@ public class OBCReflect {
|
||||
return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass);
|
||||
}
|
||||
|
||||
private OBCReflect() { }
|
||||
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package fr.pandacube.lib.paper.reflect;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftMapView;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftMetaItem;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftNamespacedKey;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
||||
@@ -13,20 +14,16 @@ import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.VanillaCommandWrapper;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.dataconverter.MCDataConverter;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.dataconverter.MCDataType;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.dataconverter.MCTypeRegistry;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.DetectedVersion;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.SharedConstants;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.WorldVersion;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.BlockPosArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.CommandSourceStack;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Commands;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ComponentArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Coordinates;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntityArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntitySelector;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.GameProfileArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ResourceLocationArgument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Vec3Argument;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.BlockPos;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.HolderLookupProvider;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.RegistryAccess;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.Vec3i;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CollectionTag;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||
@@ -55,17 +52,25 @@ import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerGamePacketL
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerLevel;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerPlayer;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.Settings;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.util.ProblemReporter;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.util.ProgressListener;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.AABB;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ChunkPos;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ChunkStorage;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.DataVersion;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStackWithSlot;
|
||||
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.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.TagValueInput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.TagValueOutput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ValueInput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ValueInputTypedInputList;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ValueOutput;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ValueOutputTypedOutputList;
|
||||
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.block.BambooStalkBlock;
|
||||
@@ -73,9 +78,14 @@ 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.Unpooled;
|
||||
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.APICommandMeta;
|
||||
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.ShadowBrigNode;
|
||||
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.spottedleaf.moonrise.ChunkSystemChunkStorage;
|
||||
import fr.pandacube.lib.reflect.ReflectionWrapperBypass;
|
||||
import fr.pandacube.lib.util.ThrowableAccumulator;
|
||||
|
||||
import static fr.pandacube.lib.reflect.wrapper.WrapperRegistry.initWrapper;
|
||||
@@ -92,12 +102,14 @@ public class PandalibPaperReflect {
|
||||
* @throws Exception if a problem occurs when initializing wrapper classes.
|
||||
*/
|
||||
public static void init() throws Exception {
|
||||
NMSReflect.init();
|
||||
synchronized (PandalibPaperReflect.class) {
|
||||
if (isInit)
|
||||
return;
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
ReflectionWrapperBypass.enable();
|
||||
|
||||
initWrapperClasses();
|
||||
}
|
||||
|
||||
@@ -111,6 +123,7 @@ public class PandalibPaperReflect {
|
||||
// craftbukkit
|
||||
thAcc.catchThrowable(() -> initWrapper(CraftItemStack.class, CraftItemStack.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CraftMapView.class, CraftMapView.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CraftMetaItem.class, CraftMetaItem.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CraftNamespacedKey.class, CraftNamespacedKey.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CraftPlayer.class, CraftPlayer.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CraftServer.class, CraftServer.REFLECT.get()));
|
||||
@@ -125,91 +138,104 @@ public class PandalibPaperReflect {
|
||||
thAcc.catchThrowable(() -> initWrapper(MCTypeRegistry.class, MCTypeRegistry.REFLECT.get()));
|
||||
|
||||
// minecraft.commands
|
||||
thAcc.catchThrowable(() -> initWrapper(BlockPosArgument.class, BlockPosArgument.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Commands.class, Commands.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CommandSourceStack.class, CommandSourceStack.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ComponentArgument.class, ComponentArgument.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Coordinates.class, Coordinates.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(EntityArgument.class, EntityArgument.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(EntitySelector.class, EntitySelector.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(GameProfileArgument.class, GameProfileArgument.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ResourceLocationArgument.class, ResourceLocationArgument.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Vec3Argument.class, Vec3Argument.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Commands.class, Commands.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CommandSourceStack.class, CommandSourceStack.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Coordinates.class, Coordinates.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(GameProfileArgument.class, GameProfileArgument.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ResourceLocationArgument.class, ResourceLocationArgument.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Vec3Argument.class, Vec3Argument.REFLECT.get()));
|
||||
// minecraft.core
|
||||
thAcc.catchThrowable(() -> initWrapper(BlockPos.class, BlockPos.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Vec3i.class, Vec3i.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(BlockPos.class, BlockPos.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(HolderLookupProvider.class, HolderLookupProvider.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(RegistryAccess.class, RegistryAccess.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Vec3i.class, Vec3i.REFLECT.get()));
|
||||
// minecraft.nbt
|
||||
thAcc.catchThrowable(() -> initWrapper(CollectionTag.class, CollectionTag.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CompoundTag.class, CompoundTag.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ListTag.class, ListTag.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(NbtAccounter.class, NbtAccounter.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(NbtIo.class, NbtIo.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(StringTag.class, StringTag.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Tag.class, Tag.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CollectionTag.class, CollectionTag.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CompoundTag.class, CompoundTag.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ListTag.class, ListTag.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(NbtAccounter.class, NbtAccounter.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(NbtIo.class, NbtIo.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(StringTag.class, StringTag.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Tag.class, Tag.REFLECT.get()));
|
||||
// minecraft.network.chat
|
||||
thAcc.catchThrowable(() -> initWrapper(Component.class, Component.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Component.class, Component.REFLECT.get()));
|
||||
// minecraft.network.protocol.custom
|
||||
thAcc.catchThrowable(() -> initWrapper(BrandPayload.class, BrandPayload.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CustomPacketPayload.class, CustomPacketPayload.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(BrandPayload.class, BrandPayload.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(CustomPacketPayload.class, CustomPacketPayload.REFLECT.get()));
|
||||
// minecraft.network.protocol
|
||||
thAcc.catchThrowable(() -> initWrapper(ClientboundCustomPayloadPacket.class, ClientboundCustomPayloadPacket.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ClientboundGameEventPacket.class, ClientboundGameEventPacket.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ClientboundGameEventPacket.Type.class, ClientboundGameEventPacket.Type.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Packet.class, Packet.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ClientboundCustomPayloadPacket.class, ClientboundCustomPayloadPacket.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ClientboundGameEventPacket.class, ClientboundGameEventPacket.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ClientboundGameEventPacket.Type.class, ClientboundGameEventPacket.Type.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Packet.class, Packet.REFLECT.get()));
|
||||
// minecraft.network
|
||||
thAcc.catchThrowable(() -> initWrapper(FriendlyByteBuf.class, FriendlyByteBuf.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(FriendlyByteBuf.class, FriendlyByteBuf.REFLECT.get()));
|
||||
// minecraft.resources
|
||||
thAcc.catchThrowable(() -> initWrapper(ResourceLocation.class, ResourceLocation.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ResourceLocation.class, ResourceLocation.REFLECT.get()));
|
||||
// minecraft.server
|
||||
thAcc.catchThrowable(() -> initWrapper(ChunkMap.class, ChunkMap.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DedicatedPlayerList.class, DedicatedPlayerList.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DedicatedServer.class, DedicatedServer.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DedicatedServerProperties.class, DedicatedServerProperties.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(MinecraftServer.class, MinecraftServer.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(PlayerList.class, PlayerList.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerChunkCache.class, ServerChunkCache.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerCommonPacketListenerImpl.class, ServerCommonPacketListenerImpl.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerGamePacketListenerImpl.class, ServerGamePacketListenerImpl.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerLevel.class, ServerLevel.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerPlayer.class, ServerPlayer.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Settings.class, Settings.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ChunkMap.class, ChunkMap.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DedicatedPlayerList.class, DedicatedPlayerList.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DedicatedServer.class, DedicatedServer.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DedicatedServerProperties.class, DedicatedServerProperties.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(MinecraftServer.class, MinecraftServer.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(PlayerList.class, PlayerList.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerChunkCache.class, ServerChunkCache.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerCommonPacketListenerImpl.class, ServerCommonPacketListenerImpl.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerGamePacketListenerImpl.class, ServerGamePacketListenerImpl.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerLevel.class, ServerLevel.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ServerPlayer.class, ServerPlayer.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Settings.class, Settings.REFLECT.get()));
|
||||
// minecraft.util
|
||||
thAcc.catchThrowable(() -> initWrapper(ProgressListener.class, ProgressListener.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ProblemReporter.class, ProblemReporter.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ProgressListener.class, ProgressListener.REFLECT.get()));
|
||||
// minecraft.world.block
|
||||
thAcc.catchThrowable(() -> initWrapper(Block.class, Block.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(BambooStalkBlock.class, BambooStalkBlock.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Block.class, Block.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(BambooStalkBlock.class, BambooStalkBlock.REFLECT.get()));
|
||||
// minecraft.world
|
||||
thAcc.catchThrowable(() -> initWrapper(AABB.class, AABB.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ChunkPos.class, ChunkPos.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ChunkStorage.class, ChunkStorage.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(DataVersion.class, DataVersion.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Entity.class, Entity.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ItemStack.class, ItemStack.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Level.class, Level.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(MapItemSavedData.class, MapItemSavedData.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(PlayerDataStorage.class, PlayerDataStorage.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(SavedData.class, SavedData.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Vec3.class, Vec3.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(VoxelShape.class, VoxelShape.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(AABB.class, AABB.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ChunkPos.class, ChunkPos.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ChunkStorage.class, ChunkStorage.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Entity.class, Entity.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ItemStack.class, ItemStack.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ItemStackWithSlot.class, ItemStackWithSlot.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Level.class, Level.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(MapItemSavedData.class, MapItemSavedData.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(TagValueInput.class, TagValueInput.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(TagValueOutput.class, TagValueOutput.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ValueInput.class, ValueInput.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ValueInputTypedInputList.class, ValueInputTypedInputList.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ValueOutput.class, ValueOutput.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ValueOutputTypedOutputList.class, ValueOutputTypedOutputList.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Vec3.class, Vec3.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(VoxelShape.class, VoxelShape.REFLECT.get()));
|
||||
// minecraft
|
||||
thAcc.catchThrowable(() -> initWrapper(DetectedVersion.class, DetectedVersion.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(SharedConstants.class, SharedConstants.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(WorldVersion.class, WorldVersion.MAPPING.runtimeClass()));
|
||||
thAcc.catchThrowable(() -> initWrapper(SharedConstants.class, SharedConstants.REFLECT.get()));
|
||||
|
||||
// netty
|
||||
thAcc.catchThrowable(() -> initWrapper(ByteBuf.class, ByteBuf.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(Unpooled.class, Unpooled.REFLECT.get()));
|
||||
|
||||
// paper.commands
|
||||
thAcc.catchThrowable(() -> initWrapper(APICommandMeta.class, APICommandMeta.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(BukkitCommandNode.class, BukkitCommandNode.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(PaperBrigadier.class, PaperBrigadier.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(ShadowBrigNode.class, ShadowBrigNode.REFLECT.get()));
|
||||
// paper.configuration
|
||||
thAcc.catchThrowable(() -> initWrapper(FallbackValue_Int.class, FallbackValue_Int.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(WorldConfiguration.class, WorldConfiguration.REFLECT.get()));
|
||||
thAcc.catchThrowable(() -> initWrapper(WorldConfiguration.Chunks.class, WorldConfiguration.Chunks.REFLECT.get()));
|
||||
// paper
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
private PandalibPaperReflect() {}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user