Refactor PlayerManager API + little fixes in Chat API
This commit is contained in:
parent
d4471f2845
commit
f4d436671c
@ -20,12 +20,6 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
|
@ -38,8 +38,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
||||
protected boolean console = false;
|
||||
|
||||
/* package */ Chat(ComponentBuilder<?, ?> b) {
|
||||
Objects.requireNonNull(b, "Provided component builder must not be null");
|
||||
builder = b;
|
||||
builder = Objects.requireNonNull(b, "Provided component builder must not be null");
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,18 +3,18 @@ package fr.pandacube.lib.chat;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
public abstract class ChatStatic {
|
||||
|
||||
|
||||
|
||||
public static FormatableChat chatComponent(Component c) {
|
||||
private static FormatableChat chatComponent(Component c) {
|
||||
return new FormatableChat(Chat.componentToBuilder(c));
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ public abstract class ChatStatic {
|
||||
return new FormatableChat(Chat.componentToBuilder(Chat.toAdventure(c)));
|
||||
}
|
||||
|
||||
public static FormatableChat chatComponent(Chat c) {
|
||||
return chatComponent(c.getAdv());
|
||||
public static FormatableChat chatComponent(ComponentLike c) {
|
||||
return chatComponent(c.asComponent());
|
||||
}
|
||||
|
||||
public static FormatableChat chat() {
|
||||
@ -34,26 +34,38 @@ public abstract class ChatStatic {
|
||||
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) {
|
||||
if (plainText instanceof Chat) {
|
||||
Log.warning("Using Chat instance as plain text. Please use proper API method. I’ll properly use your Chat instance this time...", new Throwable());
|
||||
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);
|
||||
if (plainText instanceof ComponentLike) {
|
||||
throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + plainText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
|
||||
}
|
||||
return new FormatableChat(Component.text().content(Objects.toString(plainText)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (legacyText instanceof Chat) {
|
||||
Log.warning("Using Chat instance as legacy text. Please use proper API method. I’ll properly use your Chat instance this time...", new Throwable());
|
||||
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);
|
||||
if (legacyText instanceof ComponentLike) {
|
||||
throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + legacyText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
|
||||
}
|
||||
return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
|
||||
}
|
||||
@ -116,4 +128,19 @@ public abstract class ChatStatic {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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,19 +56,11 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-network-api</artifactId>
|
||||
<artifactId>pandalib-players-standalone</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fathzer</groupId>
|
||||
<artifactId>javaluator</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.geysermc.floodgate</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
|
@ -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.function.Consumer;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
@ -19,11 +20,7 @@ import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.core.players.IPlayerManager;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import fr.pandacube.lib.paper.util.ItemStackBuilder;
|
||||
|
||||
public class GUIInventory implements Listener {
|
||||
@ -43,10 +40,6 @@ public class GUIInventory implements Listener {
|
||||
else
|
||||
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);
|
||||
|
||||
onClickEvents = new HashMap<>();
|
||||
|
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.OptionalLong;
|
||||
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.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
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,10 +22,9 @@ public interface IOnlinePlayer extends IOffPlayer {
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
String getServerName();
|
||||
|
||||
@ -38,122 +33,10 @@ public interface IOnlinePlayer extends IOffPlayer {
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* they allows to display CHAT messages
|
||||
@ -220,20 +80,12 @@ public interface IOnlinePlayer extends IOffPlayer {
|
||||
* @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(ComponentLike message, UUID sender) {
|
||||
sendMessage(message.asComponent(), 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);
|
||||
sendMessage(message.asComponent(), () -> sender == null ? Identity.nil() : Identity.identity(sender));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,17 +93,8 @@ public interface IOnlinePlayer extends IOffPlayer {
|
||||
* activated, prepended with the server prefix.
|
||||
* @param message the message to display
|
||||
*/
|
||||
default void sendPrefixedMessage(Component message) {
|
||||
sendMessage(IPlayerManager.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());
|
||||
default void sendPrefixedMessage(ComponentLike message) {
|
||||
sendMessage(ChatStatic.prefixedAndColored(message));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,8 +115,8 @@ public interface IOnlinePlayer extends IOffPlayer {
|
||||
* @param stay Stay time in tick
|
||||
* @param fadeOut Fade out time in tick
|
||||
*/
|
||||
default void sendTitle(Chat title, Chat subtitle, int fadeIn, int stay, int fadeOut) {
|
||||
sendTitle(title.getAdv(), subtitle.getAdv(), fadeIn, stay, fadeOut);
|
||||
default void sendTitle(ComponentLike title, ComponentLike subtitle, int fadeIn, int stay, int 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-netapi</module>
|
||||
<module>pandalib-paper</module>
|
||||
<module>pandalib-paper-reflect</module>
|
||||
<module>pandalib-permissions</module>
|
||||
<module>pandalib-players-standalone</module>
|
||||
<module>pandalib-reflect</module>
|
||||
<module>pandalib-util</module>
|
||||
<module>pandalib-paper-reflect</module>
|
||||
<module>pandalib-players-permissible</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
@ -69,39 +71,7 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.name}-${build.number}</finalName>
|
||||
<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>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
|
Loading…
Reference in New Issue
Block a user