Refactor PlayerManager API + little fixes in Chat API
This commit is contained in:
parent
d4471f2845
commit
f4d436671c
@ -20,12 +20,6 @@
|
|||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
|
||||||
<artifactId>pandalib-util</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -38,8 +38,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
protected boolean console = false;
|
protected boolean console = false;
|
||||||
|
|
||||||
/* package */ Chat(ComponentBuilder<?, ?> b) {
|
/* package */ Chat(ComponentBuilder<?, ?> b) {
|
||||||
Objects.requireNonNull(b, "Provided component builder must not be null");
|
builder = Objects.requireNonNull(b, "Provided component builder must not be null");
|
||||||
builder = b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,18 +3,18 @@ package fr.pandacube.lib.chat;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
|
|
||||||
public abstract class ChatStatic {
|
public abstract class ChatStatic {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static FormatableChat chatComponent(Component c) {
|
private static FormatableChat chatComponent(Component c) {
|
||||||
return new FormatableChat(Chat.componentToBuilder(c));
|
return new FormatableChat(Chat.componentToBuilder(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,8 +22,8 @@ public abstract class ChatStatic {
|
|||||||
return new FormatableChat(Chat.componentToBuilder(Chat.toAdventure(c)));
|
return new FormatableChat(Chat.componentToBuilder(Chat.toAdventure(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FormatableChat chatComponent(Chat c) {
|
public static FormatableChat chatComponent(ComponentLike c) {
|
||||||
return chatComponent(c.getAdv());
|
return chatComponent(c.asComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FormatableChat chat() {
|
public static FormatableChat chat() {
|
||||||
@ -34,26 +34,38 @@ public abstract class ChatStatic {
|
|||||||
return chatComponent(Chat.toAdventure(c));
|
return chatComponent(Chat.toAdventure(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Chat instance with the provided plain text as its main text content.
|
||||||
|
*
|
||||||
|
* @param plainText the text to use as he content of the new Chat instance.
|
||||||
|
* @return a Chat instance 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 FormatableChat text(Object plainText) {
|
||||||
if (plainText instanceof Chat) {
|
if (plainText instanceof ComponentLike) {
|
||||||
Log.warning("Using Chat instance as plain text. Please use proper API method. I’ll properly use your Chat instance this time...", new Throwable());
|
throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + plainText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
|
||||||
return (FormatableChat) plainText;
|
|
||||||
}
|
|
||||||
if (plainText instanceof Component) {
|
|
||||||
Log.warning("Using Component instance as plain text. Please use proper API method. I’ll properly use your Component this time...", new Throwable());
|
|
||||||
return chatComponent((Component) plainText);
|
|
||||||
}
|
}
|
||||||
return new FormatableChat(Component.text().content(Objects.toString(plainText)));
|
return new FormatableChat(Component.text().content(Objects.toString(plainText)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Chat instance with the provided legacy text as its main text content.
|
||||||
|
*
|
||||||
|
* @param legacyText the text to use as he content of the new Chat instance.
|
||||||
|
* @return a Chat instance 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 legacyText(Object legacyText) {
|
public static FormatableChat legacyText(Object legacyText) {
|
||||||
if (legacyText instanceof Chat) {
|
if (legacyText instanceof ComponentLike) {
|
||||||
Log.warning("Using Chat instance as legacy text. Please use proper API method. I’ll properly use your Chat instance this time...", new Throwable());
|
throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + legacyText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
|
||||||
return (FormatableChat) legacyText;
|
|
||||||
}
|
|
||||||
if (legacyText instanceof Component) {
|
|
||||||
Log.warning("Using Component instance as legacy text. Please use proper API method. I’ll properly use your Component this time...", new Throwable());
|
|
||||||
return chatComponent((Component) legacyText);
|
|
||||||
}
|
}
|
||||||
return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
|
return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
|
||||||
}
|
}
|
||||||
@ -113,6 +125,21 @@ public abstract class ChatStatic {
|
|||||||
.objective(objective)
|
.objective(objective)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static Component prefixedAndColored(ComponentLike message) {
|
||||||
|
return prefixedAndColored(Chat.chatComponent(message)).getAdv();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Chat prefixedAndColored(Chat message) {
|
||||||
|
return Chat.chat()
|
||||||
|
.broadcastColor()
|
||||||
|
.then(Chat.getConfig().prefix.get())
|
||||||
|
.then(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,17 +56,9 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
<artifactId>pandalib-network-api</artifactId>
|
<artifactId>pandalib-players-standalone</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fathzer</groupId>
|
|
||||||
<artifactId>javaluator</artifactId>
|
|
||||||
<version>3.0.3</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -1,460 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.OptionalLong;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.LongStream;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
|
||||||
import fr.pandacube.lib.chat.ChatColorUtil;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import fr.pandacube.lib.permissions.PermPlayer;
|
|
||||||
import fr.pandacube.lib.permissions.Permissions;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
|
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.dataText;
|
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.text;
|
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.warningText;
|
|
||||||
|
|
||||||
public interface IOffPlayer {
|
|
||||||
|
|
||||||
/** From how long the last web activity should be before considering the user offline (in ms)? */
|
|
||||||
long TIMEOUT_WEB_SESSION = 10000; // msec
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* General data and state
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the ID of the minecraft account.
|
|
||||||
*
|
|
||||||
* @return the id of the player
|
|
||||||
*/
|
|
||||||
UUID getUniqueId();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if the current account is an alt account generated by Pandacube.
|
|
||||||
*
|
|
||||||
* An alt account uses a specific bit in the UUID to distinguish themselves from the original account.
|
|
||||||
*/
|
|
||||||
default boolean isAltAccount() {
|
|
||||||
return (getUniqueId().getMostSignificantBits() & 0x8000L) == 0x8000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the index of the current alt account generated by Pandacube.
|
|
||||||
*
|
|
||||||
* The first generated alt account will be numbered 1, the second 2, ...
|
|
||||||
*
|
|
||||||
* This method will return undetermined value if {@link #isAltAccount()} is false.
|
|
||||||
*/
|
|
||||||
default int getAltIndex() {
|
|
||||||
return (int) (getUniqueId().getMostSignificantBits() >> 8) & 0xF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the last known player name of this player, or null if this player never joined the network.
|
|
||||||
*/
|
|
||||||
default String getName() {
|
|
||||||
return PlayerFinder.getLastKnownName(getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate if this player is connected to the current node (server or proxy, depending on interface implementation)
|
|
||||||
* @return wether the player is online or not
|
|
||||||
*/
|
|
||||||
boolean isOnline();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides informations of the online status of the player:
|
|
||||||
* online in game, online on the website, or offline.
|
|
||||||
* If the player is online in game, it provides the current server they are
|
|
||||||
* connected.
|
|
||||||
*/
|
|
||||||
default PlayerStatusOnServer getPlayerStatus() {
|
|
||||||
|
|
||||||
IOnlinePlayer op = getOnlineInstance();
|
|
||||||
if (op != null && !op.isVanished())
|
|
||||||
return new PlayerStatusOnServer(PlayerStatusOnServer.PlayerStatus.ONLINE_IG, op.getServerName());
|
|
||||||
|
|
||||||
try {
|
|
||||||
SQLPlayer webSession = getDbPlayer();
|
|
||||||
|
|
||||||
if (webSession != null) {
|
|
||||||
long lastWebActivity = webSession.get(SQLPlayer.lastWebActivity);
|
|
||||||
|
|
||||||
if (System.currentTimeMillis() - lastWebActivity < TIMEOUT_WEB_SESSION)
|
|
||||||
return new PlayerStatusOnServer(PlayerStatusOnServer.PlayerStatus.ONLINE_WEB, null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.severe(e);
|
|
||||||
}
|
|
||||||
return new PlayerStatusOnServer(PlayerStatusOnServer.PlayerStatus.OFFLINE, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
record PlayerStatusOnServer(PlayerStatus status, String server) {
|
|
||||||
public Chat toComponent() {
|
|
||||||
if (status == PlayerStatus.ONLINE_IG)
|
|
||||||
return successText("En ligne, " + server);
|
|
||||||
if (status == PlayerStatus.ONLINE_WEB)
|
|
||||||
return warningText("En ligne, web");
|
|
||||||
if (status == PlayerStatus.OFFLINE)
|
|
||||||
return dataText("Hors ligne");
|
|
||||||
return text("N/A");
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PlayerStatus {
|
|
||||||
ONLINE_IG, ONLINE_WEB, OFFLINE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Floodgate related stuff
|
|
||||||
*/
|
|
||||||
|
|
||||||
default boolean isBedrockAccount() {
|
|
||||||
int v = getUniqueId().version();
|
|
||||||
return v == 0 || v == 8; // also 8 if one day we supports alt accounts for floodgate players
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean isJavaAccount() {
|
|
||||||
return !isBedrockAccount();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Related class instances
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the online instance of this player, if any exists.
|
|
||||||
* May return itself if the current instance already represent an online player.
|
|
||||||
*/
|
|
||||||
IOnlinePlayer getOnlineInstance();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the database entry of this player, or null if the player never joined the network.
|
|
||||||
*/
|
|
||||||
default SQLPlayer getDbPlayer() throws DBException {
|
|
||||||
return SQLPlayer.getPlayerFromUUID(getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the permission instance of this player. This will never return null.
|
|
||||||
* @return the permission instance of this player
|
|
||||||
*/
|
|
||||||
default PermPlayer getPermissionUser() {
|
|
||||||
return Permissions.getPlayer(getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Display name
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the player (if any), with eventual prefix and suffix depending on permission groups
|
|
||||||
* (and team for bukkit implementation)
|
|
||||||
* @return the display name of the player
|
|
||||||
*/
|
|
||||||
String getDisplayName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an updated display name of the user,
|
|
||||||
* generated using eventual permission’s prefix(es) and suffix(es) of the player,
|
|
||||||
* and with color codes translated to Minecraft’s native {@code §}.
|
|
||||||
*/
|
|
||||||
default String getDisplayNameFromPermissionSystem() {
|
|
||||||
PermPlayer permU = getPermissionUser();
|
|
||||||
return ChatColorUtil.translateAlternateColorCodes('&',
|
|
||||||
permU.getPrefix() + getName() + permU.getSuffix());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Permissions and groups
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this player has the specified permission.
|
|
||||||
* If the player is online, this will redirect the
|
|
||||||
* method call to the {@link IOnlinePlayer} instance,
|
|
||||||
* that MUST override this current method to avoid recussive
|
|
||||||
* loop.
|
|
||||||
* If the player is offline, it just call the Pandacube
|
|
||||||
* permission system.
|
|
||||||
* @param permission the permission node to test
|
|
||||||
* @return whether this player has the provided permission
|
|
||||||
*/
|
|
||||||
default boolean hasPermission(String permission) {
|
|
||||||
IOnlinePlayer online = getOnlineInstance();
|
|
||||||
|
|
||||||
if (online != null)
|
|
||||||
return online.hasPermission(permission);
|
|
||||||
|
|
||||||
// at this point, the player is offline
|
|
||||||
return getPermissionUser().hasPermissionOr(permission, null, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this player has the permission resulted from the provided expression.
|
|
||||||
* If the player is online, this will redirect the
|
|
||||||
* method call to the {@link IOnlinePlayer} instance,
|
|
||||||
* that MUST override this current method to avoid recussive
|
|
||||||
* loop.
|
|
||||||
* If the player is offline, it just call the Pandacube
|
|
||||||
* permission system.
|
|
||||||
* @param permissionExpression the permission node to test
|
|
||||||
* @return whether this player has the provided permission
|
|
||||||
*/
|
|
||||||
default boolean hasPermissionExpression(String permissionExpression) {
|
|
||||||
IOnlinePlayer online = getOnlineInstance();
|
|
||||||
|
|
||||||
if (online != null)
|
|
||||||
return online.hasPermissionExpression(permissionExpression);
|
|
||||||
|
|
||||||
// at this point, the player is offline
|
|
||||||
return getPermissionUser().hasPermissionExpression(permissionExpression, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all the values for a set of permission indicating an integer in a range.
|
|
||||||
* <p>
|
|
||||||
* A permission range is used to easily attribute a number to a group or player,
|
|
||||||
* like the maximum number of homes allowed. For instance, if the player has the permission
|
|
||||||
* {@code essentials.home.12}, this method would return a stream containing the value 12,
|
|
||||||
* if the parameter {@code permissionPrefix} is {@code "essentials.home."}.
|
|
||||||
* <p>
|
|
||||||
* The use of a stream allow the caller to get either the maximum, the minimum, or do any
|
|
||||||
* other treatment to the values.
|
|
||||||
* @param permissionPrefix the permission prefix to search for.
|
|
||||||
* @return a LongStream containing all the values found for the specified permission prefix.
|
|
||||||
*/
|
|
||||||
default LongStream getPermissionRangeValues(String permissionPrefix) {
|
|
||||||
IOnlinePlayer online = getOnlineInstance();
|
|
||||||
|
|
||||||
if (online != null)
|
|
||||||
return online.getPermissionRangeValues(permissionPrefix);
|
|
||||||
|
|
||||||
// at this point, the player is offline
|
|
||||||
return getPermissionUser().getPermissionRangeValues(permissionPrefix, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum value returned by {@link IOffPlayer#getPermissionRangeValues(String)}.
|
|
||||||
*/
|
|
||||||
default OptionalLong getPermissionRangeMax(String permissionPrefix) {
|
|
||||||
IOnlinePlayer online = getOnlineInstance();
|
|
||||||
|
|
||||||
if (online != null)
|
|
||||||
return online.getPermissionRangeMax(permissionPrefix);
|
|
||||||
|
|
||||||
// at this point, the player is offline
|
|
||||||
return getPermissionUser().getPermissionRangeMax(permissionPrefix, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if the this player is part of the specified group
|
|
||||||
*
|
|
||||||
* @param group the permissions group
|
|
||||||
* @return <i>true</i> if this player is part of the group,
|
|
||||||
* <i>false</i> otherwise
|
|
||||||
*/
|
|
||||||
default boolean isInGroup(String group) {
|
|
||||||
return getPermissionUser().isInGroup(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this player is part of the staff, based on permission groups
|
|
||||||
*/
|
|
||||||
default boolean isInStaff() {
|
|
||||||
return getPermissionUser().inheritsFromGroup("staff-base", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ignore
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this player have the right to ignore the provided player
|
|
||||||
* @param ignored the player that is potentially ignored by this player.
|
|
||||||
* If this parameter is null, this method returns false.
|
|
||||||
*/
|
|
||||||
default boolean canIgnore(IOffPlayer ignored) {
|
|
||||||
if (ignored == null)
|
|
||||||
return false;
|
|
||||||
if (equals(ignored))
|
|
||||||
return false;
|
|
||||||
if (!isInStaff() && !ignored.isInStaff())
|
|
||||||
return true;
|
|
||||||
return hasPermission("pandacube.ignore.bypassfor." + ignored.getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if the provided player have the right to ignore this player
|
|
||||||
* @param ignorer the player that potentially ignore this player
|
|
||||||
* If this parameter is null, this method returns false.
|
|
||||||
* @implNote the default implementation just calls {@link #canIgnore(IOffPlayer) ignorer.canIgnore(this)}.
|
|
||||||
*/
|
|
||||||
default boolean canBeIgnoredBy(IOffPlayer ignorer) {
|
|
||||||
if (ignorer == null)
|
|
||||||
return false;
|
|
||||||
return ignorer.canIgnore(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if this player ignore the provided player.
|
|
||||||
* @param ignored the player that is potentially ignored by this player.
|
|
||||||
* If this parameter is null, this method returns false.
|
|
||||||
* @return true if this player have to right to ignore the provided player and is actually ignoring him.
|
|
||||||
*/
|
|
||||||
default boolean isIgnoring(IOffPlayer ignored) {
|
|
||||||
if (!canIgnore(ignored))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return SQLPlayerIgnore.isPlayerIgnoringPlayer(getUniqueId(), ignored.getUniqueId());
|
|
||||||
} catch (DBException e) {
|
|
||||||
Log.severe("Can't determine if a player ignore another player, because we can't access to the database", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the provided player ignore this player, taking into account the exception permissions.
|
|
||||||
* @param ignorer the player that potentially ignore this player
|
|
||||||
* If this parameter is null, this method returns false.
|
|
||||||
* @return true if the provided player have to right to ignore this player and is actually ignoring him.
|
|
||||||
* @implNote the default implementation just calls {@link #isIgnoring(IOffPlayer) ignorer.isIgnoring(this)}.
|
|
||||||
*/
|
|
||||||
default boolean isIgnoredBy(IOffPlayer ignorer) {
|
|
||||||
return ignorer.isIgnoring(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Modération
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the time when the player will be unmuted, or null if the player is not muted.
|
|
||||||
* @return the timestamp in millisecond of when the player will be unmuted
|
|
||||||
*/
|
|
||||||
default Long getMuteTimeout() {
|
|
||||||
try {
|
|
||||||
Long muteTimeout = getDbPlayer().get(SQLPlayer.muteTimeout);
|
|
||||||
if (muteTimeout == null || muteTimeout <= System.currentTimeMillis())
|
|
||||||
return null;
|
|
||||||
return muteTimeout;
|
|
||||||
} catch (DBException e) {
|
|
||||||
Log.severe(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if the player is currently muted, meaning that they cannot communicate
|
|
||||||
* through the chat or private messages.
|
|
||||||
*/
|
|
||||||
default boolean isMuted() {
|
|
||||||
return getMuteTimeout() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Birthday
|
|
||||||
*/
|
|
||||||
|
|
||||||
default void setBirthday(int day, int month, Integer year) {
|
|
||||||
try {
|
|
||||||
SQLPlayer dbPlayer = getDbPlayer();
|
|
||||||
dbPlayer.setBirthday(day, month, year);
|
|
||||||
dbPlayer.save();
|
|
||||||
} catch (DBException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default Calendar getBirthday() {
|
|
||||||
try {
|
|
||||||
return getDbPlayer().getBirthday();
|
|
||||||
} catch (DBException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Player config
|
|
||||||
*/
|
|
||||||
|
|
||||||
default String getConfig(String key) throws DBException {
|
|
||||||
return SQLPlayerConfig.get(getUniqueId(), key);
|
|
||||||
}
|
|
||||||
|
|
||||||
default String getConfig(String key, String deflt) throws DBException {
|
|
||||||
return SQLPlayerConfig.get(getUniqueId(), key, deflt);
|
|
||||||
}
|
|
||||||
|
|
||||||
default void setConfig(String key, String value) throws DBException {
|
|
||||||
SQLPlayerConfig.set(getUniqueId(), key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
default void unsetConfig(String key) throws DBException {
|
|
||||||
SQLPlayerConfig.unset(getUniqueId(), key);
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean isWelcomeQuizzDone() {
|
|
||||||
try {
|
|
||||||
return Boolean.parseBoolean(getConfig("welcome.quizz.done", "false"));
|
|
||||||
} catch (DBException e) {
|
|
||||||
Log.severe("Error knowing if player has already done the quizz. Assuming they did for now.", e);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,376 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.ComponentLike;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
|
||||||
import fr.pandacube.lib.db.DB;
|
|
||||||
import fr.pandacube.lib.db.DBInitTableException;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
|
|
||||||
public abstract class IPlayerManager<OP extends IOnlinePlayer, OF extends IOffPlayer> {
|
|
||||||
private static IPlayerManager<?, ?> instance;
|
|
||||||
|
|
||||||
public static synchronized IPlayerManager<?, ?> getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
try {
|
|
||||||
new StandalonePlayerManager(); // will set the instance value itself (see IPlayerManager constructor)
|
|
||||||
} catch (DBInitTableException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static synchronized void setInstance(IPlayerManager<?, ?> newInstance) {
|
|
||||||
if (instance != null && !(instance instanceof StandalonePlayerManager)) {
|
|
||||||
throw new IllegalStateException("only one instance of playerManager is possible");
|
|
||||||
}
|
|
||||||
instance = newInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final Map<UUID, OP> onlinePlayers = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
|
|
||||||
private final LoadingCache<UUID, OF> offlinePlayers = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
|
||||||
.build(CacheLoader.from(this::newOffPlayerInstance));
|
|
||||||
|
|
||||||
|
|
||||||
public IPlayerManager() throws DBInitTableException {
|
|
||||||
setInstance(this);
|
|
||||||
|
|
||||||
DB.initTable(SQLPlayer.class);
|
|
||||||
DB.initTable(SQLPlayerConfig.class);
|
|
||||||
DB.initTable(SQLPlayerIgnore.class);
|
|
||||||
DB.initTable(SQLPlayerNameHistory.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected void addPlayer(OP p) {
|
|
||||||
onlinePlayers.put(p.getUniqueId(), p);
|
|
||||||
offlinePlayers.invalidate(p.getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected OP removePlayer(UUID p) {
|
|
||||||
return onlinePlayers.remove(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OP get(UUID p) {
|
|
||||||
return onlinePlayers.get(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOnline(UUID p) {
|
|
||||||
return onlinePlayers.containsKey(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPlayerCount() {
|
|
||||||
return onlinePlayers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OP> getAll() {
|
|
||||||
return new ArrayList<>(onlinePlayers.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OP> getAllNotVanished() {
|
|
||||||
List<OP> players = getAll();
|
|
||||||
players.removeIf(IOnlinePlayer::isVanished);
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OF getOffline(UUID p) {
|
|
||||||
if (p == null)
|
|
||||||
return null;
|
|
||||||
OP online = get(p);
|
|
||||||
if (online != null) {
|
|
||||||
offlinePlayers.invalidate(p);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
OF ret = (OF) online;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
// if not online
|
|
||||||
try {
|
|
||||||
return offlinePlayers.get(p); // load and cache new instance if necessary
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.severe("Cannot cache Offline player instance", e);
|
|
||||||
return newOffPlayerInstance(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OP> getOnlyVisibleFor(OF viewer) {
|
|
||||||
List<OP> players = getAll();
|
|
||||||
if (viewer != null)
|
|
||||||
players.removeIf(op -> op.isVanishedFor(viewer));
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OP> getOnlyVisibleFor(OP viewer, boolean sameServerOnly) {
|
|
||||||
if (sameServerOnly && (viewer == null || viewer.getServerName() == null))
|
|
||||||
return Collections.emptyList();
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<OP> players = getOnlyVisibleFor((OF)viewer);
|
|
||||||
|
|
||||||
if (sameServerOnly)
|
|
||||||
players.removeIf(op -> !viewer.getServerName().equals(op.getServerName()));
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getNamesOnlyVisibleFor(OP viewer, boolean sameServerOnly) {
|
|
||||||
return getOnlyVisibleFor(viewer, sameServerOnly).stream()
|
|
||||||
.map(IOnlinePlayer::getName)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getNamesOnlyVisibleFor(UUID viewer, boolean sameServerOnly) {
|
|
||||||
return getNamesOnlyVisibleFor(get(viewer), sameServerOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract OF newOffPlayerInstance(UUID p);
|
|
||||||
|
|
||||||
protected abstract void sendMessageToConsole(Component message);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static BaseComponent prefixedAndColored(BaseComponent message) {
|
|
||||||
return prefixedAndColored(Chat.chatComponent(message)).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Component prefixedAndColored(Component message) {
|
|
||||||
return prefixedAndColored(Chat.chatComponent(message)).getAdv();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Chat prefixedAndColored(Chat message) {
|
|
||||||
return Chat.chat()
|
|
||||||
.broadcastColor()
|
|
||||||
.then(Chat.getConfig().prefix.get())
|
|
||||||
.then(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Message broadcasting
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ComponentLike message
|
|
||||||
// boolean prefix
|
|
||||||
// boolean console = (permission == null)
|
|
||||||
// String permission = null
|
|
||||||
// UUID sourcePlayer = null
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to some or all players, and eventually to the console.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param console if the message must be displayed in the console.
|
|
||||||
* @param permission if not null, the message is only sent to player with this permission.
|
|
||||||
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
|
||||||
* If null, the message will be sent as a SYSTEM chat message.
|
|
||||||
* If not null, the message will be sent as a CHAT message, and will not be sent
|
|
||||||
* to players ignoring the provided player.
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
|
|
||||||
Objects.requireNonNull(message, "message cannot be null");
|
|
||||||
|
|
||||||
IOffPlayer oSourcePlayer = getInstance().getOffline(sourcePlayer);
|
|
||||||
|
|
||||||
if (prefix)
|
|
||||||
message = prefixedAndColored(message.asComponent());
|
|
||||||
|
|
||||||
for (IOnlinePlayer op : getInstance().getAll()) {
|
|
||||||
if (permission != null && !(op.hasPermission(permission))) continue;
|
|
||||||
if (sourcePlayer != null && op.isIgnoring(oSourcePlayer))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (sourcePlayer != null) {
|
|
||||||
if (op.canIgnore(oSourcePlayer)) {
|
|
||||||
op.sendMessage(message, sourcePlayer); // CHAT message with UUID
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
op.sendMessage(message, new UUID(0, 0)); // CHAT message without UUID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
op.sendMessage(message); // SYSTEM message
|
|
||||||
}
|
|
||||||
|
|
||||||
if (console)
|
|
||||||
getInstance().sendMessageToConsole(message.asComponent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to some or all players, and eventually to the console.
|
|
||||||
* <p>
|
|
||||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param console if the message must be displayed in the console.
|
|
||||||
* @param permission if not null, the message is only sent to player with this permission.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission) {
|
|
||||||
broadcast(message, prefix, console, permission, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to all players, and eventually to the console.
|
|
||||||
* <p>
|
|
||||||
* This method does not restrict the reception of the message to a specific permission. If you
|
|
||||||
* want to specify a permission, use {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param console if the message must be displayed in the console.
|
|
||||||
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
|
||||||
* If null, the message will be sent as a SYSTEM chat message.
|
|
||||||
* If not null, the message will be sent as a CHAT message, and will not be sent
|
|
||||||
* to players ignoring the provided player.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, boolean console, UUID sourcePlayer) {
|
|
||||||
broadcast(message, prefix, console, null, sourcePlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to all players, and eventually to the console.
|
|
||||||
* <p>
|
|
||||||
* This method does not restrict the reception of the message to a specific permission. If you
|
|
||||||
* want to specify a permission, use {@link #broadcast(ComponentLike, boolean, boolean, String)}.
|
|
||||||
* <p>
|
|
||||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param console if the message must be displayed in the console.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, boolean console) {
|
|
||||||
broadcast(message, prefix, console, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to some or all players, and eventually to the console.
|
|
||||||
* <p>
|
|
||||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, String, UUID)}.
|
|
||||||
* <p>
|
|
||||||
* This method decides to send the message to the console depending on whether {@code permission}
|
|
||||||
* is null (will send to console) or not (will not send to console). To specify this behaviour, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, boolean, String)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param permission if not null, the message is only sent to player with this permission (but not to console).
|
|
||||||
* If null, the message will be sent to all players and to console.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, String permission) {
|
|
||||||
broadcast(message, prefix, (permission == null), permission, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to all players, and to the console.
|
|
||||||
* <p>
|
|
||||||
* This method does not restrict the reception of the message to a specific permission. If you
|
|
||||||
* want to specify a permission, use {@link #broadcast(ComponentLike, boolean, String, UUID)}.
|
|
||||||
* <p>
|
|
||||||
* This method sends the message to the console. To change this behaviour, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
|
||||||
* If null, the message will be sent as a SYSTEM chat message.
|
|
||||||
* If not null, the message will be sent as a CHAT message, and will not be sent
|
|
||||||
* to players ignoring the provided player.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, UUID sourcePlayer) {
|
|
||||||
broadcast(message, prefix, true, null, sourcePlayer);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Broadcast a message to all players, and to the console.
|
|
||||||
* <p>
|
|
||||||
* This method sends the message to the console. To change this behaviour, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @param permission if not null, the message is only sent to player with this permission (but not to console).
|
|
||||||
* If null, the message will be sent to all players and to console.
|
|
||||||
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
|
||||||
* If null, the message will be sent as a SYSTEM chat message.
|
|
||||||
* If not null, the message will be sent as a CHAT message, and will not be sent
|
|
||||||
* to players ignoring the provided player.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix, String permission, UUID sourcePlayer) {
|
|
||||||
broadcast(message, prefix, true, permission, sourcePlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast a message to all players, and to the console.
|
|
||||||
* <p>
|
|
||||||
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, UUID)}.
|
|
||||||
* <p>
|
|
||||||
* This method does not restrict the reception of the message to a specific permission. If you
|
|
||||||
* want to specify a permission, use {@link #broadcast(ComponentLike, boolean, String)}.
|
|
||||||
* <p>
|
|
||||||
* This method sends the message to the console. To change this behaviour, use
|
|
||||||
* {@link #broadcast(ComponentLike, boolean, boolean)}.
|
|
||||||
*
|
|
||||||
* @param message the message to send.
|
|
||||||
* @param prefix if the server prefix will be prepended to the message.
|
|
||||||
* @throws IllegalArgumentException if message is null.
|
|
||||||
*/
|
|
||||||
public static void broadcast(ComponentLike message, boolean prefix) {
|
|
||||||
broadcast(message, prefix, true, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,343 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.ToIntBiFunction;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.core.commands.SuggestionsSupplier;
|
|
||||||
import fr.pandacube.lib.db.DB;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import fr.pandacube.lib.db.SQLOrderBy;
|
|
||||||
import fr.pandacube.lib.util.LevenshteinDistance;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Etape de recherche de joueur :
|
|
||||||
* utiliser directement la table pandacube_player
|
|
||||||
* chercher dans l'historique de login
|
|
||||||
*/
|
|
||||||
public class PlayerFinder {
|
|
||||||
|
|
||||||
private static final Cache<UUID, String> playerLastKnownName = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
|
||||||
.maximumSize(1000)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
record PlayerIdCacheKey(String pName, boolean old) { }
|
|
||||||
private static final Cache<PlayerIdCacheKey, UUID> playerId = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
|
||||||
.maximumSize(1000)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public static void clearCacheEntry(UUID pId, String pName) {
|
|
||||||
playerLastKnownName.invalidate(pId);
|
|
||||||
playerId.invalidate(new PlayerIdCacheKey(pName.toLowerCase(), true));
|
|
||||||
playerId.invalidate(new PlayerIdCacheKey(pName.toLowerCase(), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLastKnownName(UUID id) {
|
|
||||||
if (id == null) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return playerLastKnownName.get(id, () -> {
|
|
||||||
try {
|
|
||||||
return getDBPlayer(id).get(SQLPlayer.playerName); // eventual NPE will be ignored
|
|
||||||
} catch (NullPointerException|DBException e) {
|
|
||||||
Log.severe("Can't search for player name from uuid in database", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
// ignored (ORM Exception)
|
|
||||||
} catch (UncheckedExecutionException e) {
|
|
||||||
Log.severe("Can’t retrieve player last known name of " + id, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cherche un UUID de compte en se basant sur le pseudo passé en
|
|
||||||
* paramètre. La méthode
|
|
||||||
* cherchera d'abord dans les derniers pseudos connus. Puis, cherchera la
|
|
||||||
* dernière personne à
|
|
||||||
* s'être connecté avec ce pseudo sur le serveur.
|
|
||||||
*
|
|
||||||
* @param exactName le pseudo complet, insensible à la casse, et dans un
|
|
||||||
* format de pseudo valide
|
|
||||||
* @param old si on doit chercher dans les anciens pseudos de joueurs
|
|
||||||
* @return l'UUID du joueur si trouvé, null sinon
|
|
||||||
*/
|
|
||||||
public static UUID getPlayerId(String exactName, boolean old) {
|
|
||||||
if (!isValidPlayerName(exactName))
|
|
||||||
return null; // évite une recherche inutile dans la base de donnée
|
|
||||||
|
|
||||||
try {
|
|
||||||
return playerId.get(new PlayerIdCacheKey(exactName.toLowerCase(), old), () -> {
|
|
||||||
try {
|
|
||||||
SQLPlayer el = DB.getFirst(SQLPlayer.class,
|
|
||||||
SQLPlayer.playerName.like(exactName.replace("_", "\\_")),
|
|
||||||
SQLOrderBy.desc(SQLPlayer.lastTimeInGame));
|
|
||||||
/*
|
|
||||||
* Si il n'y a pas 1 élément, alors soit le pseudo n'a jamais été attribué
|
|
||||||
* soit il a été changé, et nous avons l'ancien possesseur et le nouveau possesseur du pseudo.
|
|
||||||
*/
|
|
||||||
if (el != null)
|
|
||||||
return el.get(SQLPlayer.playerId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.severe("Can't search for uuid from player name in database", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (old) {
|
|
||||||
try {
|
|
||||||
SQLPlayerNameHistory el = DB.getFirst(SQLPlayerNameHistory.class,
|
|
||||||
SQLPlayerNameHistory.playerName.like(exactName.replace("_", "\\_")),
|
|
||||||
SQLOrderBy.desc(SQLPlayerNameHistory.timeChanged));
|
|
||||||
if (el != null) return el.get(SQLPlayerNameHistory.playerId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.severe("Can't search for uuid from old player name in database", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception(); // ignored
|
|
||||||
});
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a player name or a player ID from the provided string, and returns the UUID of the player, if found.
|
|
||||||
* @param nameOrId a valid player name, or a UUID in the format of {@link UUID#toString()}
|
|
||||||
* @return the id of the player, or null if not found or if the input is invalid.
|
|
||||||
*/
|
|
||||||
public static UUID parsePlayer(String nameOrId) {
|
|
||||||
if (nameOrId == null)
|
|
||||||
return null;
|
|
||||||
if (isValidPlayerName(nameOrId))
|
|
||||||
return getPlayerId(nameOrId, true);
|
|
||||||
try {
|
|
||||||
return UUID.fromString(nameOrId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isValidPlayerName(String name) {
|
|
||||||
if (name == null) return false;
|
|
||||||
return name.matches("[\\da-zA-Z_.]{2,20}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SQLPlayer getDBPlayer(UUID id) throws DBException {
|
|
||||||
if (id == null) return null;
|
|
||||||
return SQLPlayer.getPlayerFromUUID(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final SuggestionsSupplier<?> TAB_PLAYER_OFFLINE = (sender, tokenIndex, token, args) -> {
|
|
||||||
if (token.length() < 3) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
List<SearchResponseProfile> list = findPlayer(token, 10).profiles;
|
|
||||||
if (!list.isEmpty() && list.get(0).d == 0)
|
|
||||||
return Collections.singletonList(list.get(0).name);
|
|
||||||
return list.stream().map(p -> p.name).collect(Collectors.toList());
|
|
||||||
};
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <S> SuggestionsSupplier<S> TAB_PLAYER_OFFLINE() {
|
|
||||||
return (SuggestionsSupplier<S>) TAB_PLAYER_OFFLINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static SearchResponse findPlayer(String query, int resultsCount) {
|
|
||||||
SearchResponse cacheData = searchCache.getUnchecked(query.toLowerCase());
|
|
||||||
cacheData = new SearchResponse(cacheData.profiles.subList(0, Math.min(resultsCount, cacheData.profiles.size())));
|
|
||||||
return cacheData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static int SEARCH_MAX_DISTANCE = 20;
|
|
||||||
public static int MISSING_CHAR_DISTANCE = 1;
|
|
||||||
public static int SURPLUS_CHAR_DISTANCE = 8;
|
|
||||||
public static int DIFF_CHAR_DISTANCE = 8;
|
|
||||||
public static int CLOSE_CHAR_DISTANCE = 4;
|
|
||||||
|
|
||||||
public static int OLD_NICK_MULTIPLIER = 2;
|
|
||||||
|
|
||||||
|
|
||||||
private static final List<List<Character>> CONFUSABLE_CHARACTERS = ImmutableList.of(
|
|
||||||
ImmutableList.of('o', '0'),
|
|
||||||
ImmutableList.of('i', '1', 'l'),
|
|
||||||
ImmutableList.of('b', '8')
|
|
||||||
);
|
|
||||||
private static final ToIntBiFunction<Character, Character> CHAR_DISTANCE = (c1, c2) -> {
|
|
||||||
if (c1.equals(c2))
|
|
||||||
return 0;
|
|
||||||
for (List<Character> charTab : CONFUSABLE_CHARACTERS) {
|
|
||||||
if (charTab.contains(c1) && charTab.contains(c2))
|
|
||||||
return CLOSE_CHAR_DISTANCE;
|
|
||||||
}
|
|
||||||
return DIFF_CHAR_DISTANCE;
|
|
||||||
};
|
|
||||||
|
|
||||||
record NamesCacheResult(String name, String lowercaseName, UUID id) { } // Java 16
|
|
||||||
|
|
||||||
private static final LoadingCache<String, List<NamesCacheResult>> namesCache = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
|
||||||
.maximumSize(1)
|
|
||||||
.build(CacheLoader.from((String k) -> {
|
|
||||||
List<NamesCacheResult> cached = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
DB.forEach(SQLPlayerNameHistory.class, el -> {
|
|
||||||
String name = el.get(SQLPlayerNameHistory.playerName);
|
|
||||||
cached.add(new NamesCacheResult(name, name.toLowerCase(), el.get(SQLPlayerNameHistory.playerId)));
|
|
||||||
});
|
|
||||||
} catch (DBException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return cached;
|
|
||||||
}));
|
|
||||||
|
|
||||||
private static final LoadingCache<String, SearchResponse> searchCache = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
|
||||||
.maximumSize(100)
|
|
||||||
.build(CacheLoader.from((String query) -> {
|
|
||||||
List<FoundName> foundNames = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
namesCache.get("").forEach(el -> {
|
|
||||||
int dist = new LevenshteinDistance(el.lowercaseName(), query, SURPLUS_CHAR_DISTANCE, MISSING_CHAR_DISTANCE, CHAR_DISTANCE).getCurrentDistance();
|
|
||||||
if (dist <= SEARCH_MAX_DISTANCE) {
|
|
||||||
FoundName n = new FoundName();
|
|
||||||
n.dist = dist;
|
|
||||||
n.id = el.id();
|
|
||||||
n.name = el.name();
|
|
||||||
foundNames.add(n);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<UUID, SearchResponseProfile> profiles = new HashMap<>();
|
|
||||||
|
|
||||||
foundNames.forEach(foundName -> {
|
|
||||||
SearchResponseProfile profile = profiles.getOrDefault(foundName.id, new SearchResponseProfile());
|
|
||||||
if (profile.id == null) {
|
|
||||||
profile.id = foundName.id.toString();
|
|
||||||
profile.names = new ArrayList<>();
|
|
||||||
profiles.put(foundName.id, profile);
|
|
||||||
}
|
|
||||||
profile.names.add(foundName);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
DB.forEach(SQLPlayer.class, SQLPlayer.playerId.in(profiles.keySet()), el -> {
|
|
||||||
SearchResponseProfile profile = profiles.get(el.get(SQLPlayer.playerId));
|
|
||||||
if (profile == null)
|
|
||||||
return;
|
|
||||||
profile.displayName = el.get(SQLPlayer.playerDisplayName);
|
|
||||||
profile.name = el.get(SQLPlayer.playerName);
|
|
||||||
FoundName currentName = null;
|
|
||||||
for (FoundName foundName : profile.names) {
|
|
||||||
if (foundName.name.equals(profile.name)) {
|
|
||||||
currentName = foundName;
|
|
||||||
profile.d = foundName.dist;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentName != null) {
|
|
||||||
profile.names.remove(currentName);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int min = Integer.MAX_VALUE;
|
|
||||||
for (FoundName foundName : profile.names) {
|
|
||||||
if (foundName.dist < min) {
|
|
||||||
min = foundName.dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
profile.d = min * OLD_NICK_MULTIPLIER + 1;
|
|
||||||
|
|
||||||
if (profile.d > SEARCH_MAX_DISTANCE)
|
|
||||||
profiles.remove(el.get(SQLPlayer.playerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// unset id field in old names entries to save memory and network activity
|
|
||||||
profile.names.forEach(n -> n.id = null);
|
|
||||||
});
|
|
||||||
} catch (DBException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
List<SearchResponseProfile> searchResponseList = new ArrayList<>(profiles.values());
|
|
||||||
searchResponseList.sort(null);
|
|
||||||
|
|
||||||
searchResponseList.removeIf(p -> {
|
|
||||||
if (p.name == null) { // if the current name was not found in the database
|
|
||||||
Log.warning("[PlayerFinder] Name found in history table for id " + p.id + " but the current name was not found in the player table.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return new SearchResponse(searchResponseList);
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
public static class SearchResponseProfile implements Comparable<SearchResponseProfile> {
|
|
||||||
public int d;
|
|
||||||
public String id;
|
|
||||||
public String name;
|
|
||||||
public String displayName;
|
|
||||||
public List<FoundName> names;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(SearchResponseProfile o) {
|
|
||||||
return Integer.compare(d, o.d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FoundName {
|
|
||||||
public UUID id;
|
|
||||||
public String name;
|
|
||||||
public int dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class SearchResponse {
|
|
||||||
public final List<SearchResponseProfile> profiles;
|
|
||||||
private SearchResponse(List<SearchResponseProfile> p) {
|
|
||||||
profiles = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.sql.Date;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.db.DB;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import fr.pandacube.lib.db.SQLElement;
|
|
||||||
import fr.pandacube.lib.db.SQLElementList;
|
|
||||||
import fr.pandacube.lib.db.SQLField;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
|
|
||||||
public class SQLPlayer extends SQLElement<SQLPlayer> {
|
|
||||||
|
|
||||||
/** If the player birth year is internally 1800, it is considered as a non disclosed age.
|
|
||||||
* All player with an age below 13 should have their age automatically hidden.
|
|
||||||
*/
|
|
||||||
public static final int UNDISCLOSED_AGE_YEAR = 1800;
|
|
||||||
|
|
||||||
|
|
||||||
public SQLPlayer() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SQLPlayer(int id) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Nom de la table
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected String tableName() {
|
|
||||||
return "player";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Champs de la table
|
|
||||||
*/
|
|
||||||
public static final SQLField<SQLPlayer, UUID> playerId = field(CHAR36_UUID, false);
|
|
||||||
public static final SQLField<SQLPlayer, String> playerName = field(VARCHAR(16), false);
|
|
||||||
public static final SQLField<SQLPlayer, String> token = field(CHAR(36), true);
|
|
||||||
public static final SQLField<SQLPlayer, String> mailCheck = field(VARCHAR(255), true);
|
|
||||||
public static final SQLField<SQLPlayer, String> password = field(VARCHAR(255), true);
|
|
||||||
public static final SQLField<SQLPlayer, String> mail = field(VARCHAR(255), true);
|
|
||||||
public static final SQLField<SQLPlayer, String> playerDisplayName = field(VARCHAR(255),
|
|
||||||
false);
|
|
||||||
public static final SQLField<SQLPlayer, Long> firstTimeInGame = field(BIGINT, false, 0L);
|
|
||||||
public static final SQLField<SQLPlayer, Long> timeWebRegister = field(BIGINT, true);
|
|
||||||
public static final SQLField<SQLPlayer, Long> lastTimeInGame = field(BIGINT, true);
|
|
||||||
public static final SQLField<SQLPlayer, Long> lastWebActivity = field(BIGINT, false, 0L);
|
|
||||||
public static final SQLField<SQLPlayer, String> onlineInServer = field(VARCHAR(32), true);
|
|
||||||
public static final SQLField<SQLPlayer, String> skinURL = field(VARCHAR(255), true);
|
|
||||||
public static final SQLField<SQLPlayer, Boolean> isVanish = field(BOOLEAN, false,
|
|
||||||
(Boolean) false);
|
|
||||||
public static final SQLField<SQLPlayer, Date> birthday = field(DATE, true);
|
|
||||||
public static final SQLField<SQLPlayer, Integer> lastYearCelebratedBirthday = field(INT,
|
|
||||||
false, 0);
|
|
||||||
public static final SQLField<SQLPlayer, Long> banTimeout = field(BIGINT, true);
|
|
||||||
public static final SQLField<SQLPlayer, Long> muteTimeout = field(BIGINT, true);
|
|
||||||
public static final SQLField<SQLPlayer, Boolean> isWhitelisted = field(BOOLEAN, false,
|
|
||||||
(Boolean) false);
|
|
||||||
public static final SQLField<SQLPlayer, Long> bambou = field(BIGINT, false, 0L);
|
|
||||||
public static final SQLField<SQLPlayer, String> grade = field(VARCHAR(36), false, "default");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Calendar getBirthday() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
Date birthday = get(SQLPlayer.birthday);
|
|
||||||
if (birthday == null) // le joueur n'a pas de date d'anniversaire
|
|
||||||
return null;
|
|
||||||
GregorianCalendar cal = new GregorianCalendar();
|
|
||||||
cal.setTime(birthday);
|
|
||||||
return cal;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.severe(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBirthday(int day, int month, Integer year) {
|
|
||||||
if (year == null)
|
|
||||||
year = UNDISCLOSED_AGE_YEAR;
|
|
||||||
|
|
||||||
GregorianCalendar cal = new GregorianCalendar();
|
|
||||||
cal.set(year, month, day, 0, 0, 0);
|
|
||||||
|
|
||||||
set(SQLPlayer.birthday, new java.sql.Date(cal.getTimeInMillis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isWebRegistered() {
|
|
||||||
return get(password) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static SQLPlayer getPlayerFromUUID(UUID pId) throws DBException {
|
|
||||||
if (pId == null)
|
|
||||||
return null;
|
|
||||||
return DB.getFirst(SQLPlayer.class, playerId.eq(pId));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static SQLElementList<SQLPlayer> getPlayersFromUUIDs(Set<UUID> playerIds) throws DBException {
|
|
||||||
|
|
||||||
if (playerIds == null || playerIds.isEmpty()) {
|
|
||||||
return new SQLElementList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return DB.getAll(SQLPlayer.class, playerId.in(playerIds));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.db.DB;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import fr.pandacube.lib.db.SQLElement;
|
|
||||||
import fr.pandacube.lib.db.SQLElementList;
|
|
||||||
import fr.pandacube.lib.db.SQLFKField;
|
|
||||||
import fr.pandacube.lib.db.SQLField;
|
|
||||||
|
|
||||||
public class SQLPlayerConfig extends SQLElement<SQLPlayerConfig> {
|
|
||||||
|
|
||||||
public SQLPlayerConfig() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SQLPlayerConfig(int id) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Nom de la table
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected String tableName() {
|
|
||||||
return "player_config";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Champs de la table
|
|
||||||
*/
|
|
||||||
public static final SQLFKField<SQLPlayerConfig, UUID, SQLPlayer> playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
|
||||||
public static final SQLField<SQLPlayerConfig, String> key = field(VARCHAR(255), false);
|
|
||||||
public static final SQLField<SQLPlayerConfig, String> value = field(VARCHAR(8192), false);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static String get(UUID p, String k, String deflt) throws DBException {
|
|
||||||
SQLPlayerConfig res = DB.getFirst(SQLPlayerConfig.class, playerId.eq(p).and(key.eq(k)));
|
|
||||||
return res == null ? deflt : res.get(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String get(UUID p, String k) throws DBException {
|
|
||||||
return get(p, k, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void set(UUID p, String k, String v) throws DBException {
|
|
||||||
if (v == null) {
|
|
||||||
unset(p, k);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLPlayerConfig entry = DB.getFirst(SQLPlayerConfig.class, playerId.eq(p).and(key.eq(k)));
|
|
||||||
|
|
||||||
if (entry == null) {
|
|
||||||
entry = new SQLPlayerConfig();
|
|
||||||
entry.set(playerId, p);
|
|
||||||
entry.set(key, k);
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.set(value, v);
|
|
||||||
entry.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unset(UUID p, String k) throws DBException {
|
|
||||||
|
|
||||||
SQLPlayerConfig entry = DB.getFirst(SQLPlayerConfig.class, playerId.eq(p).and(key.eq(k)));
|
|
||||||
|
|
||||||
if (entry != null)
|
|
||||||
entry.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static SQLElementList<SQLPlayerConfig> getAllFromPlayer(UUID p, String likeQuery) throws DBException {
|
|
||||||
return DB.getAll(SQLPlayerConfig.class, playerId.eq(p).and(key.like(likeQuery)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SQLElementList<SQLPlayerConfig> getAllWithKeys(String likeQuery) throws DBException {
|
|
||||||
return DB.getAll(SQLPlayerConfig.class, key.like(likeQuery));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SQLElementList<SQLPlayerConfig> getAllWithKeyValue(String k, String v) throws DBException {
|
|
||||||
return DB.getAll(SQLPlayerConfig.class, key.eq(k).and(value.eq(v)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.db.DB;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import fr.pandacube.lib.db.SQLElement;
|
|
||||||
import fr.pandacube.lib.db.SQLFKField;
|
|
||||||
|
|
||||||
public class SQLPlayerIgnore extends SQLElement<SQLPlayerIgnore> {
|
|
||||||
|
|
||||||
public SQLPlayerIgnore() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SQLPlayerIgnore(int id) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String tableName() {
|
|
||||||
return "player_ignore";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final SQLFKField<SQLPlayerIgnore, UUID, SQLPlayer> ignorer = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
|
||||||
public static final SQLFKField<SQLPlayerIgnore, UUID, SQLPlayer> ignored = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
|
||||||
|
|
||||||
|
|
||||||
public static SQLPlayerIgnore getPlayerIgnoringPlayer(UUID ignorer, UUID ignored) throws DBException {
|
|
||||||
return DB.getFirst(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer).and(SQLPlayerIgnore.ignored.eq(ignored)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isPlayerIgnoringPlayer(UUID ignorer, UUID ignored) throws DBException {
|
|
||||||
return getPlayerIgnoringPlayer(ignorer, ignored) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setPlayerIgnorePlayer(UUID ignorer, UUID ignored, boolean newIgnoreState) throws DBException {
|
|
||||||
SQLPlayerIgnore el = getPlayerIgnoringPlayer(ignorer, ignored);
|
|
||||||
if (el == null && newIgnoreState) {
|
|
||||||
el = new SQLPlayerIgnore();
|
|
||||||
el.set(SQLPlayerIgnore.ignorer, ignorer);
|
|
||||||
el.set(SQLPlayerIgnore.ignored, ignored);
|
|
||||||
el.save();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (el != null && !newIgnoreState) {
|
|
||||||
el.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<UUID, SQLPlayer> getIgnoredPlayer(UUID ignorer) throws DBException {
|
|
||||||
return DB.getAll(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer))
|
|
||||||
.getReferencedEntriesInGroups(SQLPlayerIgnore.ignored);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.db.DB;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import fr.pandacube.lib.db.SQLElement;
|
|
||||||
import fr.pandacube.lib.db.SQLFKField;
|
|
||||||
import fr.pandacube.lib.db.SQLField;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
|
|
||||||
public class SQLPlayerNameHistory extends SQLElement<SQLPlayerNameHistory> {
|
|
||||||
|
|
||||||
public SQLPlayerNameHistory() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SQLPlayerNameHistory(int id) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String tableName() {
|
|
||||||
return "player_name_history";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final SQLFKField<SQLPlayerNameHistory, UUID, SQLPlayer> playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId);
|
|
||||||
public static final SQLField<SQLPlayerNameHistory, String> playerName = field(VARCHAR(16), false);
|
|
||||||
public static final SQLField<SQLPlayerNameHistory, Long> timeChanged = field(BIGINT, true);
|
|
||||||
|
|
||||||
|
|
||||||
public static void updateIfNeeded(UUID player, String name, long time) {
|
|
||||||
SQLPlayerNameHistory histEl;
|
|
||||||
try {
|
|
||||||
histEl = DB.getFirst(SQLPlayerNameHistory.class, playerId.eq(player).and(playerName.eq(name)));
|
|
||||||
|
|
||||||
if (histEl == null) {
|
|
||||||
histEl = new SQLPlayerNameHistory();
|
|
||||||
histEl.set(playerId, player);
|
|
||||||
histEl.set(playerName, name);
|
|
||||||
histEl.set(timeChanged, time);
|
|
||||||
histEl.save();
|
|
||||||
}
|
|
||||||
else if (time < histEl.get(timeChanged)) {
|
|
||||||
histEl.set(timeChanged, time);
|
|
||||||
histEl.save();
|
|
||||||
}
|
|
||||||
} catch (DBException e) {
|
|
||||||
Log.severe(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/* package */ class StandaloneOffPlayer implements IOffPlayer {
|
|
||||||
|
|
||||||
private final UUID uniqueId;
|
|
||||||
|
|
||||||
public StandaloneOffPlayer(UUID id) {
|
|
||||||
if (id == null) throw new IllegalArgumentException("id cannot be null");
|
|
||||||
uniqueId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UUID getUniqueId() {
|
|
||||||
return uniqueId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnline() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IOnlinePlayer getOnlineInstance() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String displayName = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayName() {
|
|
||||||
if (displayName == null)
|
|
||||||
displayName = getDisplayNameFromPermissionSystem();
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
|
||||||
import fr.pandacube.lib.db.DBInitTableException;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A standalone player manager, using an implementation of {@link IPlayerManager}
|
|
||||||
* that does not manage online players. This is used to ease access to players data
|
|
||||||
* on standalone applications (web api, discord bot, ...)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
/* package */ class StandalonePlayerManager extends IPlayerManager<IOnlinePlayer, StandaloneOffPlayer> {
|
|
||||||
|
|
||||||
public StandalonePlayerManager() throws DBInitTableException {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected StandaloneOffPlayer newOffPlayerInstance(UUID p) {
|
|
||||||
return new StandaloneOffPlayer(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void sendMessageToConsole(Component message) {
|
|
||||||
Log.info(Chat.chatComponent(message).getLegacyText());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.enchantments.Enchantment;
|
import org.bukkit.enchantments.Enchantment;
|
||||||
@ -19,11 +20,7 @@ import org.bukkit.inventory.Inventory;
|
|||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.chat.Chat;
|
||||||
import fr.pandacube.lib.core.players.IPlayerManager;
|
|
||||||
import fr.pandacube.lib.util.Log;
|
|
||||||
import fr.pandacube.lib.paper.util.ItemStackBuilder;
|
import fr.pandacube.lib.paper.util.ItemStackBuilder;
|
||||||
|
|
||||||
public class GUIInventory implements Listener {
|
public class GUIInventory implements Listener {
|
||||||
@ -42,10 +39,6 @@ public class GUIInventory implements Listener {
|
|||||||
inv = Bukkit.createInventory(null, nbLines * 9);
|
inv = Bukkit.createInventory(null, nbLines * 9);
|
||||||
else
|
else
|
||||||
inv = Bukkit.createInventory(null, nbLines * 9, title.getAdv());
|
inv = Bukkit.createInventory(null, nbLines * 9, title.getAdv());
|
||||||
|
|
||||||
if (IPlayerManager.getInstance().get(p.getUniqueId()).isBedrockClient()) {
|
|
||||||
Log.warning("Opening GUI inventory for player on Bedrock client " + p.getName() + " (" + p.getUniqueId() + "). Please use a Form instead.", new Throwable());
|
|
||||||
}
|
|
||||||
|
|
||||||
setCloseEvent(closeEventAction);
|
setCloseEvent(closeEventAction);
|
||||||
|
|
||||||
|
31
pandalib-players-permissible/pom.xml
Normal file
31
pandalib-players-permissible/pom.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?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>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>pandalib-players-permissible</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-permissions</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-players-standalone</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,153 @@
|
|||||||
|
package fr.pandacube.lib.players.permissible;
|
||||||
|
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.chat.ChatColorUtil;
|
||||||
|
import fr.pandacube.lib.permissions.PermPlayer;
|
||||||
|
import fr.pandacube.lib.permissions.Permissions;
|
||||||
|
import fr.pandacube.lib.players.standalone.StandaloneOffPlayer;
|
||||||
|
|
||||||
|
public interface PermissibleOffPlayer extends StandaloneOffPlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Related class instances
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the online instance of this player, if any exists.
|
||||||
|
* May return itself if the current instance already represent an online player.
|
||||||
|
*/
|
||||||
|
PermissibleOnlinePlayer getOnlineInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the permission instance of this player. This will never return null.
|
||||||
|
* @return the permission instance of this player
|
||||||
|
*/
|
||||||
|
default PermPlayer getPermissionUser() {
|
||||||
|
return Permissions.getPlayer(getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display name
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an updated display name of the user,
|
||||||
|
* generated using eventual permission’s prefix(es) and suffix(es) of the player,
|
||||||
|
* and with color codes translated to Minecraft’s native {@code §}.
|
||||||
|
*/
|
||||||
|
default String getDisplayNameFromPermissionSystem() {
|
||||||
|
PermPlayer permU = getPermissionUser();
|
||||||
|
return ChatColorUtil.translateAlternateColorCodes('&',
|
||||||
|
permU.getPrefix() + getName() + permU.getSuffix());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Permissions and groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this player has the specified permission.
|
||||||
|
* If the player is online, this will redirect the
|
||||||
|
* method call to the {@link PermissibleOnlinePlayer} instance,
|
||||||
|
* that MUST override this current method to avoid recussive
|
||||||
|
* loop.
|
||||||
|
* If the player is offline, it just call the Pandacube
|
||||||
|
* permission system.
|
||||||
|
* @param permission the permission node to test
|
||||||
|
* @return whether this player has the provided permission
|
||||||
|
*/
|
||||||
|
default boolean hasPermission(String permission) {
|
||||||
|
PermissibleOnlinePlayer online = getOnlineInstance();
|
||||||
|
|
||||||
|
if (online != null)
|
||||||
|
return online.hasPermission(permission);
|
||||||
|
|
||||||
|
// at this point, the player is offline
|
||||||
|
return getPermissionUser().hasPermissionOr(permission, null, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this player has the permission resulted from the provided expression.
|
||||||
|
* If the player is online, this will redirect the
|
||||||
|
* method call to the {@link PermissibleOnlinePlayer} instance,
|
||||||
|
* that MUST override this current method to avoid recussive
|
||||||
|
* loop.
|
||||||
|
* If the player is offline, it just call the Pandacube
|
||||||
|
* permission system.
|
||||||
|
* @param permissionExpression the permission node to test
|
||||||
|
* @return whether this player has the provided permission
|
||||||
|
*/
|
||||||
|
default boolean hasPermissionExpression(String permissionExpression) {
|
||||||
|
PermissibleOnlinePlayer online = getOnlineInstance();
|
||||||
|
|
||||||
|
if (online != null)
|
||||||
|
return online.hasPermissionExpression(permissionExpression);
|
||||||
|
|
||||||
|
// at this point, the player is offline
|
||||||
|
return getPermissionUser().hasPermissionExpression(permissionExpression, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the values for a set of permission indicating an integer in a range.
|
||||||
|
* <p>
|
||||||
|
* A permission range is used to easily attribute a number to a group or player,
|
||||||
|
* like the maximum number of homes allowed. For instance, if the player has the permission
|
||||||
|
* {@code essentials.home.12}, this method would return a stream containing the value 12,
|
||||||
|
* if the parameter {@code permissionPrefix} is {@code "essentials.home."}.
|
||||||
|
* <p>
|
||||||
|
* The use of a stream allow the caller to get either the maximum, the minimum, or do any
|
||||||
|
* other treatment to the values.
|
||||||
|
* @param permissionPrefix the permission prefix to search for.
|
||||||
|
* @return a LongStream containing all the values found for the specified permission prefix.
|
||||||
|
*/
|
||||||
|
default LongStream getPermissionRangeValues(String permissionPrefix) {
|
||||||
|
PermissibleOnlinePlayer online = getOnlineInstance();
|
||||||
|
|
||||||
|
if (online != null)
|
||||||
|
return online.getPermissionRangeValues(permissionPrefix);
|
||||||
|
|
||||||
|
// at this point, the player is offline
|
||||||
|
return getPermissionUser().getPermissionRangeValues(permissionPrefix, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum value returned by {@link PermissibleOffPlayer#getPermissionRangeValues(String)}.
|
||||||
|
*/
|
||||||
|
default OptionalLong getPermissionRangeMax(String permissionPrefix) {
|
||||||
|
PermissibleOnlinePlayer online = getOnlineInstance();
|
||||||
|
|
||||||
|
if (online != null)
|
||||||
|
return online.getPermissionRangeMax(permissionPrefix);
|
||||||
|
|
||||||
|
// at this point, the player is offline
|
||||||
|
return getPermissionUser().getPermissionRangeMax(permissionPrefix, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the this player is part of the specified group
|
||||||
|
*
|
||||||
|
* @param group the permissions group
|
||||||
|
* @return <i>true</i> if this player is part of the group,
|
||||||
|
* <i>false</i> otherwise
|
||||||
|
*/
|
||||||
|
default boolean isInGroup(String group) {
|
||||||
|
return getPermissionUser().isInGroup(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package fr.pandacube.lib.players.permissible;
|
||||||
|
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.players.standalone.StandaloneOnlinePlayer;
|
||||||
|
|
||||||
|
public interface PermissibleOnlinePlayer extends PermissibleOffPlayer, StandaloneOnlinePlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General data and state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current name of this player
|
||||||
|
* @implSpec The implementation is expected to call the environment API
|
||||||
|
* (Bukkit/Bungee) to get the name of the player.
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Permissions and groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this online player has the specified permission.
|
||||||
|
* @implSpec the implementation of this method must not directly or
|
||||||
|
* indirectly call the method {@link PermissibleOffPlayer#hasPermission(String)},
|
||||||
|
* or it may result in a {@link StackOverflowError}.
|
||||||
|
*/
|
||||||
|
boolean hasPermission(String permission);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this online player has the permission resulted from the provided expression.
|
||||||
|
* @implSpec the implementation of this method must not directly or
|
||||||
|
* indirectly call the method {@link PermissibleOffPlayer#hasPermissionExpression(String)},
|
||||||
|
* or it may result in a {@link StackOverflowError}.
|
||||||
|
*/
|
||||||
|
boolean hasPermissionExpression(String permission);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the values for a set of permission indicating an integer in a range.
|
||||||
|
* <p>
|
||||||
|
* A permission range is used to easily attribute a number to a group or player,
|
||||||
|
* like the maximum number of homes allowed. For instance, if the player has the permission
|
||||||
|
* {@code essentials.home.12}, this method would return a stream containing the value 12,
|
||||||
|
* if the parameter {@code permissionPrefix} is {@code "essentials.home."}.
|
||||||
|
* <p>
|
||||||
|
* The use of a stream allow the caller to get either the maximum, the minimum, or do any
|
||||||
|
* other treatment to the values.
|
||||||
|
* @param permissionPrefix the permission prefix to search for.
|
||||||
|
* @return a LongStream containing all the values found for the specified permission prefix.
|
||||||
|
*/
|
||||||
|
LongStream getPermissionRangeValues(String permissionPrefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum value returned by {@link PermissibleOffPlayer#getPermissionRangeValues(String)}.
|
||||||
|
*/
|
||||||
|
OptionalLong getPermissionRangeMax(String permissionPrefix);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
package fr.pandacube.lib.players.permissible;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.chat.ChatStatic;
|
||||||
|
import fr.pandacube.lib.players.standalone.StandalonePlayerManager;
|
||||||
|
|
||||||
|
public abstract class PermissiblePlayerManager<OP extends PermissibleOnlinePlayer, OF extends PermissibleOffPlayer> extends StandalonePlayerManager<OP, OF> {
|
||||||
|
private static PermissiblePlayerManager<?, ?> instance;
|
||||||
|
|
||||||
|
public static synchronized PermissiblePlayerManager<?, ?> getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void setInstance(PermissiblePlayerManager<?, ?> newInstance) {
|
||||||
|
if (instance != null) {
|
||||||
|
throw new IllegalStateException("cannot have multiple instance of PlayerManager");
|
||||||
|
}
|
||||||
|
instance = newInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PermissiblePlayerManager() {
|
||||||
|
super();
|
||||||
|
setInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void broadcastMessage(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
|
||||||
|
Objects.requireNonNull(message, "message cannot be null");
|
||||||
|
|
||||||
|
if (prefix)
|
||||||
|
message = ChatStatic.prefixedAndColored(message.asComponent());
|
||||||
|
|
||||||
|
for (PermissibleOnlinePlayer op : getAll()) {
|
||||||
|
if (permission != null && !(op.hasPermission(permission))) continue;
|
||||||
|
|
||||||
|
if (sourcePlayer != null)
|
||||||
|
op.sendMessage(message, sourcePlayer); // CHAT message with UUID
|
||||||
|
else
|
||||||
|
op.sendMessage(message); // SYSTEM message
|
||||||
|
}
|
||||||
|
if (console)
|
||||||
|
sendMessageToConsole(message.asComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Message broadcasting
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ComponentLike message
|
||||||
|
// boolean prefix
|
||||||
|
// boolean console = (permission == null)
|
||||||
|
// String permission = null
|
||||||
|
// UUID sourcePlayer = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player (if implemented).
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
|
||||||
|
getInstance().broadcastMessage(message, prefix, console, permission, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission) {
|
||||||
|
broadcast(message, prefix, console, permission, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, String, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method decides to send the message to the console depending on whether {@code permission}
|
||||||
|
* is null (will send to console) or not (will not send to console). To specify this behaviour, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, boolean, String)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission (but not to console).
|
||||||
|
* If null, the message will be sent to all players and to console.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, String permission) {
|
||||||
|
broadcast(message, prefix, (permission == null), permission, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param permission if not null, the message is only sent to player with this permission (but not to console).
|
||||||
|
* If null, the message will be sent to all players and to console.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player (if implemented).
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, String permission, UUID sourcePlayer) {
|
||||||
|
broadcast(message, prefix, true, permission, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
31
pandalib-players-standalone/pom.xml
Normal file
31
pandalib-players-standalone/pom.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?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>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>pandalib-players-standalone</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-chat</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,70 @@
|
|||||||
|
package fr.pandacube.lib.players.standalone;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface StandaloneOffPlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General data and state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ID of the minecraft account.
|
||||||
|
*
|
||||||
|
* @return the id of the player
|
||||||
|
*/
|
||||||
|
UUID getUniqueId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the last known player name of this player, or null if this player never joined the network.
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate if this player is connected to the current node (server or proxy, depending on interface implementation)
|
||||||
|
* @return wether the player is online or not
|
||||||
|
*/
|
||||||
|
boolean isOnline();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Related class instances
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the online instance of this player, if any exists.
|
||||||
|
* May return itself if the current instance already represent an online player.
|
||||||
|
*/
|
||||||
|
StandaloneOnlinePlayer getOnlineInstance();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display name
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the player (if any), with eventual prefix and suffix depending on permission groups
|
||||||
|
* (and team for bukkit implementation)
|
||||||
|
* @return the display name of the player
|
||||||
|
*/
|
||||||
|
String getDisplayName();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,21 +1,17 @@
|
|||||||
package fr.pandacube.lib.core.players;
|
package fr.pandacube.lib.players.standalone;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.OptionalLong;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.LongStream;
|
|
||||||
|
|
||||||
import org.geysermc.floodgate.api.FloodgateApi;
|
|
||||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
|
||||||
import fr.pandacube.lib.db.DBException;
|
|
||||||
import net.kyori.adventure.identity.Identified;
|
import net.kyori.adventure.identity.Identified;
|
||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.ComponentLike;
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
|
||||||
public interface IOnlinePlayer extends IOffPlayer {
|
import fr.pandacube.lib.chat.Chat;
|
||||||
|
import fr.pandacube.lib.chat.ChatStatic;
|
||||||
|
|
||||||
|
public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -26,137 +22,24 @@ public interface IOnlinePlayer extends IOffPlayer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current name of this player
|
* @return The current name of this player
|
||||||
* @implSpec The implementation is expected to call the environment API
|
* @apiNote The implementation is expected to call the environment API
|
||||||
* (Bukkit/Bungee) to get the name of the player.
|
* (Bukkit/Bungee) to get the name of the player.
|
||||||
*/
|
*/
|
||||||
String getName();
|
|
||||||
|
|
||||||
String getServerName();
|
String getServerName();
|
||||||
|
|
||||||
String getWorldName();
|
String getWorldName();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Floodgate related
|
|
||||||
*/
|
|
||||||
|
|
||||||
default boolean isBedrockClient() {
|
|
||||||
try {
|
|
||||||
return FloodgateApi.getInstance().isFloodgatePlayer(getUniqueId());
|
|
||||||
} catch (NoClassDefFoundError e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default FloodgatePlayer getBedrockClient() {
|
|
||||||
return FloodgateApi.getInstance().getPlayer(getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean isJavaClient() {
|
|
||||||
return !isBedrockClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Related class instances
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalStateException if the player was not found in the database (should never happen)
|
|
||||||
* @throws DBException if a database access error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
default SQLPlayer getDbPlayer() throws DBException {
|
|
||||||
SQLPlayer p = SQLPlayer.getPlayerFromUUID(getUniqueId());
|
|
||||||
if (p == null)
|
|
||||||
throw new IllegalStateException("The player was not found in the database: " + getUniqueId());
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Permissions and groups
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this online player has the specified permission.
|
|
||||||
* @implSpec the implementation of this method must not directly or
|
|
||||||
* indirectly call the method {@link IOffPlayer#hasPermission(String)},
|
|
||||||
* or it may result in a {@link StackOverflowError}.
|
|
||||||
*/
|
|
||||||
boolean hasPermission(String permission);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if this online player has the permission resulted from the provided expression.
|
|
||||||
* @implSpec the implementation of this method must not directly or
|
|
||||||
* indirectly call the method {@link IOffPlayer#hasPermissionExpression(String)},
|
|
||||||
* or it may result in a {@link StackOverflowError}.
|
|
||||||
*/
|
|
||||||
boolean hasPermissionExpression(String permission);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all the values for a set of permission indicating an integer in a range.
|
|
||||||
* <p>
|
|
||||||
* A permission range is used to easily attribute a number to a group or player,
|
|
||||||
* like the maximum number of homes allowed. For instance, if the player has the permission
|
|
||||||
* {@code essentials.home.12}, this method would return a stream containing the value 12,
|
|
||||||
* if the parameter {@code permissionPrefix} is {@code "essentials.home."}.
|
|
||||||
* <p>
|
|
||||||
* The use of a stream allow the caller to get either the maximum, the minimum, or do any
|
|
||||||
* other treatment to the values.
|
|
||||||
* @param permissionPrefix the permission prefix to search for.
|
|
||||||
* @return a LongStream containing all the values found for the specified permission prefix.
|
|
||||||
*/
|
|
||||||
LongStream getPermissionRangeValues(String permissionPrefix);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum value returned by {@link IOffPlayer#getPermissionRangeValues(String)}.
|
|
||||||
*/
|
|
||||||
OptionalLong getPermissionRangeMax(String permissionPrefix);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Vanish
|
|
||||||
*/
|
|
||||||
|
|
||||||
boolean isVanished();
|
|
||||||
|
|
||||||
default boolean isVanishedFor(IOffPlayer other) {
|
|
||||||
if (!isVanished())
|
|
||||||
return false; // can see unvanished
|
|
||||||
|
|
||||||
if (getUniqueId().equals(other.getUniqueId()))
|
|
||||||
return false; // can see themself
|
|
||||||
|
|
||||||
if (!isInStaff() && other.isInStaff())
|
|
||||||
return false; // can see non-staff as a staff
|
|
||||||
|
|
||||||
if (other.hasPermission("pandacube.vanish.see." + getUniqueId()))
|
|
||||||
return false; // can see if has a specific permission
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -179,29 +62,6 @@ public interface IOnlinePlayer extends IOffPlayer {
|
|||||||
sendMessage(message.asComponent());
|
sendMessage(message.asComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the provided message in the player’s chat, if
|
|
||||||
* the chat is activated
|
|
||||||
* @param message the message to display
|
|
||||||
*/
|
|
||||||
default void sendMessage(Chat message) {
|
|
||||||
sendMessage(message.getAdv());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the provided message in the player’s chat, if
|
|
||||||
* they allows to display CHAT messages
|
|
||||||
* @param message the message to display.
|
|
||||||
* @param sender the player causing the send of this message. Client side filtering may occur.
|
|
||||||
* May be null if we don’t want client filtering, but still consider the message as CHAT message.
|
|
||||||
* @implNote implementation of this method should not filter the send of the message, based on
|
|
||||||
* the sender. This parameter is only there to be transmitted to the client, so client side filtering can
|
|
||||||
* be processed.
|
|
||||||
*/
|
|
||||||
default void sendMessage(Component message, UUID sender) {
|
|
||||||
sendMessage(message, () -> sender == null ? Identity.nil() : Identity.identity(sender));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the provided message in the player’s chat, if
|
* Display the provided message in the player’s chat, if
|
||||||
* they allows to display CHAT messages
|
* they allows to display CHAT messages
|
||||||
@ -220,20 +80,12 @@ public interface IOnlinePlayer extends IOffPlayer {
|
|||||||
* @param message the message to display
|
* @param message the message to display
|
||||||
* @param sender the player causing the send of this message. Client side filtering may occur.
|
* @param sender the player causing the send of this message. Client side filtering may occur.
|
||||||
* May be null if we don’t want client filtering, but still consider the message as CHAT message.
|
* May be null if we don’t want client filtering, but still consider the message as CHAT message.
|
||||||
|
* @implNote implementation of this method should not filter the send of the message, based on
|
||||||
|
* the sender. This parameter is only there to be transmitted to the client, so client side filtering can
|
||||||
|
* be processed.
|
||||||
*/
|
*/
|
||||||
default void sendMessage(ComponentLike message, UUID sender) {
|
default void sendMessage(ComponentLike message, UUID sender) {
|
||||||
sendMessage(message.asComponent(), sender);
|
sendMessage(message.asComponent(), () -> sender == null ? Identity.nil() : Identity.identity(sender));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the provided message in the player’s chat, if
|
|
||||||
* they allows to display CHAT messages
|
|
||||||
* @param message the message to display
|
|
||||||
* @param sender the player causing the send of this message. Client side filtering may occur.
|
|
||||||
* May be null if we don’t want client filtering, but still consider the message as CHAT message.
|
|
||||||
*/
|
|
||||||
default void sendMessage(Chat message, UUID sender) {
|
|
||||||
sendMessage(message.getAdv(), sender);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,17 +93,8 @@ public interface IOnlinePlayer extends IOffPlayer {
|
|||||||
* activated, prepended with the server prefix.
|
* activated, prepended with the server prefix.
|
||||||
* @param message the message to display
|
* @param message the message to display
|
||||||
*/
|
*/
|
||||||
default void sendPrefixedMessage(Component message) {
|
default void sendPrefixedMessage(ComponentLike message) {
|
||||||
sendMessage(IPlayerManager.prefixedAndColored(message));
|
sendMessage(ChatStatic.prefixedAndColored(message));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the provided message in the player’s chat, if the chat is
|
|
||||||
* activated, prepended with the server prefix.
|
|
||||||
* @param message the message to display
|
|
||||||
*/
|
|
||||||
default void sendPrefixedMessage(Chat message) {
|
|
||||||
sendPrefixedMessage(message.getAdv());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,8 +115,8 @@ public interface IOnlinePlayer extends IOffPlayer {
|
|||||||
* @param stay Stay time in tick
|
* @param stay Stay time in tick
|
||||||
* @param fadeOut Fade out time in tick
|
* @param fadeOut Fade out time in tick
|
||||||
*/
|
*/
|
||||||
default void sendTitle(Chat title, Chat subtitle, int fadeIn, int stay, int fadeOut) {
|
default void sendTitle(ComponentLike title, ComponentLike subtitle, int fadeIn, int stay, int fadeOut) {
|
||||||
sendTitle(title.getAdv(), subtitle.getAdv(), fadeIn, stay, fadeOut);
|
sendTitle(title.asComponent(), subtitle.asComponent(), fadeIn, stay, fadeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -0,0 +1,216 @@
|
|||||||
|
package fr.pandacube.lib.players.standalone;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.chat.ChatStatic;
|
||||||
|
|
||||||
|
public abstract class StandalonePlayerManager<OP extends StandaloneOnlinePlayer, OF extends StandaloneOffPlayer> {
|
||||||
|
private static StandalonePlayerManager<?, ?> instance;
|
||||||
|
|
||||||
|
public static synchronized StandalonePlayerManager<?, ?> getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void setInstance(StandalonePlayerManager<?, ?> newInstance) {
|
||||||
|
if (instance != null) {
|
||||||
|
throw new IllegalStateException("cannot have multiple instance of PlayerManager");
|
||||||
|
}
|
||||||
|
instance = newInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private final Map<UUID, OP> onlinePlayers = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
private final LoadingCache<UUID, OF> offlinePlayers = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||||
|
.build(CacheLoader.from(this::newOffPlayerInstance));
|
||||||
|
|
||||||
|
|
||||||
|
public StandalonePlayerManager() {
|
||||||
|
setInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected void addPlayer(OP p) {
|
||||||
|
onlinePlayers.put(p.getUniqueId(), p);
|
||||||
|
offlinePlayers.invalidate(p.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OP removePlayer(UUID p) {
|
||||||
|
return onlinePlayers.remove(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OP get(UUID p) {
|
||||||
|
return onlinePlayers.get(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnline(UUID p) {
|
||||||
|
return onlinePlayers.containsKey(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayerCount() {
|
||||||
|
return onlinePlayers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OP> getAll() {
|
||||||
|
return new ArrayList<>(onlinePlayers.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public OF getOffline(UUID p) {
|
||||||
|
if (p == null)
|
||||||
|
return null;
|
||||||
|
OP online = get(p);
|
||||||
|
if (online != null) {
|
||||||
|
offlinePlayers.invalidate(p);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
OF ret = (OF) online;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// if not online
|
||||||
|
try {
|
||||||
|
return offlinePlayers.get(p); // load and cache new instance if necessary
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new UncheckedExecutionException(e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract OF newOffPlayerInstance(UUID p);
|
||||||
|
|
||||||
|
protected abstract void sendMessageToConsole(Component message);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void broadcastMessage(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
|
||||||
|
Objects.requireNonNull(message, "message cannot be null");
|
||||||
|
|
||||||
|
if (prefix)
|
||||||
|
message = ChatStatic.prefixedAndColored(message.asComponent());
|
||||||
|
|
||||||
|
for (StandaloneOnlinePlayer op : getAll()) {
|
||||||
|
if (sourcePlayer != null)
|
||||||
|
op.sendMessage(message, sourcePlayer); // CHAT message without UUID
|
||||||
|
else
|
||||||
|
op.sendMessage(message); // SYSTEM message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (console)
|
||||||
|
getInstance().sendMessageToConsole(message.asComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Message broadcasting
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ComponentLike message
|
||||||
|
// boolean prefix
|
||||||
|
// boolean console = (permission == null)
|
||||||
|
// UUID sourcePlayer = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player (if implemented).
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, boolean console, UUID sourcePlayer) {
|
||||||
|
getInstance().broadcastMessage(message, prefix, console, null, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to some or all players, and eventually to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param console if the message must be displayed in the console.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, boolean console) {
|
||||||
|
broadcast(message, prefix, console, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @param sourcePlayer specifiy the eventual player that is the source of the message.
|
||||||
|
* If null, the message will be sent as a SYSTEM chat message.
|
||||||
|
* If not null, the message will be sent as a CHAT message, and will not be sent
|
||||||
|
* to players ignoring the provided player (if implemented).
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix, UUID sourcePlayer) {
|
||||||
|
broadcast(message, prefix, true, sourcePlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a message to all players, and to the console.
|
||||||
|
* <p>
|
||||||
|
* This method assumes this message is not caused by a specific player. To specify the source player, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, UUID)}.
|
||||||
|
* <p>
|
||||||
|
* This method sends the message to the console. To change this behaviour, use
|
||||||
|
* {@link #broadcast(ComponentLike, boolean, boolean)}.
|
||||||
|
*
|
||||||
|
* @param message the message to send.
|
||||||
|
* @param prefix if the server prefix will be prepended to the message.
|
||||||
|
* @throws IllegalArgumentException if message is null.
|
||||||
|
*/
|
||||||
|
public static void broadcast(ComponentLike message, boolean prefix) {
|
||||||
|
broadcast(message, prefix, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
36
pom.xml
36
pom.xml
@ -53,10 +53,12 @@
|
|||||||
<module>pandalib-net</module>
|
<module>pandalib-net</module>
|
||||||
<module>pandalib-netapi</module>
|
<module>pandalib-netapi</module>
|
||||||
<module>pandalib-paper</module>
|
<module>pandalib-paper</module>
|
||||||
|
<module>pandalib-paper-reflect</module>
|
||||||
<module>pandalib-permissions</module>
|
<module>pandalib-permissions</module>
|
||||||
|
<module>pandalib-players-standalone</module>
|
||||||
<module>pandalib-reflect</module>
|
<module>pandalib-reflect</module>
|
||||||
<module>pandalib-util</module>
|
<module>pandalib-util</module>
|
||||||
<module>pandalib-paper-reflect</module>
|
<module>pandalib-players-permissible</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -69,39 +71,7 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>${project.name}-${build.number}</finalName>
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>net.md-5</groupId>
|
|
||||||
<artifactId>scriptus</artifactId>
|
|
||||||
<version>0.4.1</version>
|
|
||||||
<configuration>
|
|
||||||
<format>git:${project.name}:${project.version}:%s:${build.number}</format>
|
|
||||||
<override>true</override> <!-- Hide warnings about parameters already set -->
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>initialize</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>describe</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<version>3.2.2</version>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifestEntries>
|
|
||||||
<!--suppress UnresolvedMavenProperty -->
|
|
||||||
<Implementation-Version>${describe}</Implementation-Version>
|
|
||||||
<Specification-Version>${maven.build.timestamp}</Specification-Version>
|
|
||||||
</manifestEntries>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
Loading…
Reference in New Issue
Block a user