From f4d436671cc0922a4013be092ae018da73437f37 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Thu, 21 Jul 2022 02:19:28 +0200 Subject: [PATCH] Refactor PlayerManager API + little fixes in Chat API --- pandalib-chat/pom.xml | 6 - .../main/java/fr/pandacube/lib/chat/Chat.java | 3 +- .../fr/pandacube/lib/chat/ChatStatic.java | 63 ++- pandalib-core/pom.xml | 10 +- .../lib/core/players/IOffPlayer.java | 460 ------------------ .../lib/core/players/IPlayerManager.java | 376 -------------- .../lib/core/players/PlayerFinder.java | 343 ------------- .../pandacube/lib/core/players/SQLPlayer.java | 124 ----- .../lib/core/players/SQLPlayerConfig.java | 90 ---- .../lib/core/players/SQLPlayerIgnore.java | 58 --- .../core/players/SQLPlayerNameHistory.java | 53 -- .../lib/core/players/StandaloneOffPlayer.java | 38 -- .../core/players/StandalonePlayerManager.java | 32 -- .../pandacube/lib/paper/gui/GUIInventory.java | 9 +- pandalib-players-permissible/pom.xml | 31 ++ .../permissible/PermissibleOffPlayer.java | 153 ++++++ .../permissible/PermissibleOnlinePlayer.java | 72 +++ .../permissible/PermissiblePlayerManager.java | 140 ++++++ pandalib-players-standalone/pom.xml | 31 ++ .../standalone/StandaloneOffPlayer.java | 70 +++ .../standalone/StandaloneOnlinePlayer.java | 191 +------- .../standalone/StandalonePlayerManager.java | 216 ++++++++ pom.xml | 36 +- 23 files changed, 781 insertions(+), 1824 deletions(-) delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerConfig.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandaloneOffPlayer.java delete mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandalonePlayerManager.java create mode 100644 pandalib-players-permissible/pom.xml create mode 100644 pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java create mode 100644 pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java create mode 100644 pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java create mode 100644 pandalib-players-standalone/pom.xml create mode 100644 pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java rename pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java => pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java (53%) create mode 100644 pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java diff --git a/pandalib-chat/pom.xml b/pandalib-chat/pom.xml index f21548b..2af9fa9 100644 --- a/pandalib-chat/pom.xml +++ b/pandalib-chat/pom.xml @@ -20,12 +20,6 @@ - - fr.pandacube.lib - pandalib-util - ${project.version} - compile - diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java index 965b143..4e7b654 100644 --- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java +++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java @@ -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"); } diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java index d999ece..a15c539 100644 --- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java +++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java @@ -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))); } @@ -113,6 +125,21 @@ public abstract class ChatStatic { .objective(objective) ); } + + + + + + public static Component prefixedAndColored(ComponentLike message) { + return prefixedAndColored(Chat.chatComponent(message)).getAdv(); + } + + public static Chat prefixedAndColored(Chat message) { + return Chat.chat() + .broadcastColor() + .then(Chat.getConfig().prefix.get()) + .then(message); + } diff --git a/pandalib-core/pom.xml b/pandalib-core/pom.xml index c751d3f..4d402a6 100644 --- a/pandalib-core/pom.xml +++ b/pandalib-core/pom.xml @@ -56,17 +56,9 @@ fr.pandacube.lib - pandalib-network-api + pandalib-players-standalone ${project.version} - - - - - com.fathzer - javaluator - 3.0.3 - diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java deleted file mode 100644 index 4792dfb..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java +++ /dev/null @@ -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. - *

- * 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."}. - *

- * 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 true if this player is part of the group, - * false 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; - } - } - - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java deleted file mode 100644 index 640279f..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IPlayerManager.java +++ /dev/null @@ -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 { - 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 onlinePlayers = Collections.synchronizedMap(new HashMap<>()); - - private final LoadingCache 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 getAll() { - return new ArrayList<>(onlinePlayers.values()); - } - - public List getAllNotVanished() { - List 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 getOnlyVisibleFor(OF viewer) { - List players = getAll(); - if (viewer != null) - players.removeIf(op -> op.isVanishedFor(viewer)); - return players; - } - - public List getOnlyVisibleFor(OP viewer, boolean sameServerOnly) { - if (sameServerOnly && (viewer == null || viewer.getServerName() == null)) - return Collections.emptyList(); - - @SuppressWarnings("unchecked") - List players = getOnlyVisibleFor((OF)viewer); - - if (sameServerOnly) - players.removeIf(op -> !viewer.getServerName().equals(op.getServerName())); - return players; - } - - public List getNamesOnlyVisibleFor(OP viewer, boolean sameServerOnly) { - return getOnlyVisibleFor(viewer, sameServerOnly).stream() - .map(IOnlinePlayer::getName) - .collect(Collectors.toList()); - } - - public List 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. - *

- * 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. - *

- * 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. - *

- * 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)}. - *

- * 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. - *

- * This method assumes this message is not caused by a specific player. To specify the source player, use - * {@link #broadcast(ComponentLike, boolean, String, UUID)}. - *

- * 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. - *

- * 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)}. - *

- * 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. - *

- * 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. - *

- * This method assumes this message is not caused by a specific player. To specify the source player, use - * {@link #broadcast(ComponentLike, boolean, UUID)}. - *

- * 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)}. - *

- * 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); - } - - - - - - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java deleted file mode 100644 index 8a01984..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/PlayerFinder.java +++ /dev/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 playerLastKnownName = CacheBuilder.newBuilder() - .expireAfterWrite(10, TimeUnit.MINUTES) - .maximumSize(1000) - .build(); - - record PlayerIdCacheKey(String pName, boolean old) { } - private static final Cache 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 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 SuggestionsSupplier TAB_PLAYER_OFFLINE() { - return (SuggestionsSupplier) 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> CONFUSABLE_CHARACTERS = ImmutableList.of( - ImmutableList.of('o', '0'), - ImmutableList.of('i', '1', 'l'), - ImmutableList.of('b', '8') - ); - private static final ToIntBiFunction CHAR_DISTANCE = (c1, c2) -> { - if (c1.equals(c2)) - return 0; - for (List 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> namesCache = CacheBuilder.newBuilder() - .expireAfterWrite(2, TimeUnit.MINUTES) - .maximumSize(1) - .build(CacheLoader.from((String k) -> { - List 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 searchCache = CacheBuilder.newBuilder() - .expireAfterWrite(2, TimeUnit.MINUTES) - .maximumSize(100) - .build(CacheLoader.from((String query) -> { - List 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 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 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 { - public int d; - public String id; - public String name; - public String displayName; - public List 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 profiles; - private SearchResponse(List p) { - profiles = p; - } - } - - - - - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java deleted file mode 100644 index 7e9643c..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayer.java +++ /dev/null @@ -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 { - - /** 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 playerId = field(CHAR36_UUID, false); - public static final SQLField playerName = field(VARCHAR(16), false); - public static final SQLField token = field(CHAR(36), true); - public static final SQLField mailCheck = field(VARCHAR(255), true); - public static final SQLField password = field(VARCHAR(255), true); - public static final SQLField mail = field(VARCHAR(255), true); - public static final SQLField playerDisplayName = field(VARCHAR(255), - false); - public static final SQLField firstTimeInGame = field(BIGINT, false, 0L); - public static final SQLField timeWebRegister = field(BIGINT, true); - public static final SQLField lastTimeInGame = field(BIGINT, true); - public static final SQLField lastWebActivity = field(BIGINT, false, 0L); - public static final SQLField onlineInServer = field(VARCHAR(32), true); - public static final SQLField skinURL = field(VARCHAR(255), true); - public static final SQLField isVanish = field(BOOLEAN, false, - (Boolean) false); - public static final SQLField birthday = field(DATE, true); - public static final SQLField lastYearCelebratedBirthday = field(INT, - false, 0); - public static final SQLField banTimeout = field(BIGINT, true); - public static final SQLField muteTimeout = field(BIGINT, true); - public static final SQLField isWhitelisted = field(BOOLEAN, false, - (Boolean) false); - public static final SQLField bambou = field(BIGINT, false, 0L); - public static final SQLField 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 getPlayersFromUUIDs(Set playerIds) throws DBException { - - if (playerIds == null || playerIds.isEmpty()) { - return new SQLElementList<>(); - } - - return DB.getAll(SQLPlayer.class, playerId.in(playerIds)); - - } -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerConfig.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerConfig.java deleted file mode 100644 index a14e949..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerConfig.java +++ /dev/null @@ -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 { - - 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 playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId); - public static final SQLField key = field(VARCHAR(255), false); - public static final SQLField 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 getAllFromPlayer(UUID p, String likeQuery) throws DBException { - return DB.getAll(SQLPlayerConfig.class, playerId.eq(p).and(key.like(likeQuery))); - } - - public static SQLElementList getAllWithKeys(String likeQuery) throws DBException { - return DB.getAll(SQLPlayerConfig.class, key.like(likeQuery)); - } - - public static SQLElementList getAllWithKeyValue(String k, String v) throws DBException { - return DB.getAll(SQLPlayerConfig.class, key.eq(k).and(value.eq(v))); - } - - - - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java deleted file mode 100644 index 88a0bc0..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerIgnore.java +++ /dev/null @@ -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 { - - public SQLPlayerIgnore() { - super(); - } - - public SQLPlayerIgnore(int id) { - super(id); - } - - @Override - protected String tableName() { - return "player_ignore"; - } - - public static final SQLFKField ignorer = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId); - public static final SQLFKField 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 getIgnoredPlayer(UUID ignorer) throws DBException { - return DB.getAll(SQLPlayerIgnore.class, SQLPlayerIgnore.ignorer.eq(ignorer)) - .getReferencedEntriesInGroups(SQLPlayerIgnore.ignored); - } - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java deleted file mode 100644 index 6f36053..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/SQLPlayerNameHistory.java +++ /dev/null @@ -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 { - - public SQLPlayerNameHistory() { - super(); - } - - public SQLPlayerNameHistory(int id) { - super(id); - } - - @Override - protected String tableName() { - return "player_name_history"; - } - - public static final SQLFKField playerId = foreignKey(false, SQLPlayer.class, SQLPlayer.playerId); - public static final SQLField playerName = field(VARCHAR(16), false); - public static final SQLField 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); - } - } - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandaloneOffPlayer.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandaloneOffPlayer.java deleted file mode 100644 index f13db0e..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandaloneOffPlayer.java +++ /dev/null @@ -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; - } - -} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandalonePlayerManager.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandalonePlayerManager.java deleted file mode 100644 index 2df17ea..0000000 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/StandalonePlayerManager.java +++ /dev/null @@ -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 { - - 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()); - } - -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java index 33e4e5c..8ec165f 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java @@ -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 { @@ -42,10 +39,6 @@ public class GUIInventory implements Listener { inv = Bukkit.createInventory(null, nbLines * 9); 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); diff --git a/pandalib-players-permissible/pom.xml b/pandalib-players-permissible/pom.xml new file mode 100644 index 0000000..842c621 --- /dev/null +++ b/pandalib-players-permissible/pom.xml @@ -0,0 +1,31 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-players-permissible + + + + + fr.pandacube.lib + pandalib-permissions + ${project.version} + + + + fr.pandacube.lib + pandalib-players-standalone + ${project.version} + provided + + + + + \ No newline at end of file diff --git a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java new file mode 100644 index 0000000..74f12c7 --- /dev/null +++ b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java @@ -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. + *

+ * 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."}. + *

+ * 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 true if this player is part of the group, + * false otherwise + */ + default boolean isInGroup(String group) { + return getPermissionUser().isInGroup(group); + } + + +} diff --git a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java new file mode 100644 index 0000000..5fa0551 --- /dev/null +++ b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java @@ -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. + *

+ * 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."}. + *

+ * 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); + + + +} diff --git a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java new file mode 100644 index 0000000..baf0b41 --- /dev/null +++ b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java @@ -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 extends StandalonePlayerManager { + 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. + *

+ * 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. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(ComponentLike, boolean, String, UUID)}. + *

+ * 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. + *

+ * 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); + } + + + + + + +} diff --git a/pandalib-players-standalone/pom.xml b/pandalib-players-standalone/pom.xml new file mode 100644 index 0000000..41ce89c --- /dev/null +++ b/pandalib-players-standalone/pom.xml @@ -0,0 +1,31 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-players-standalone + + + + + fr.pandacube.lib + pandalib-chat + ${project.version} + + + + fr.pandacube.lib + pandalib-util + ${project.version} + provided + + + + + \ No newline at end of file diff --git a/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java b/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java new file mode 100644 index 0000000..bed6f78 --- /dev/null +++ b/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java @@ -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(); + + + + + + + +} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java b/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java similarity index 53% rename from pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java rename to pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java index 7bf11da..0e55428 100644 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java +++ b/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java @@ -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,137 +22,24 @@ 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(); String getWorldName(); + + - /* - * Floodgate related - */ - - default boolean isBedrockClient() { - try { - return FloodgateApi.getInstance().isFloodgatePlayer(getUniqueId()); - } catch (NoClassDefFoundError e) { - return false; - } - } - - default FloodgatePlayer getBedrockClient() { - return FloodgateApi.getInstance().getPlayer(getUniqueId()); - } - - default boolean isJavaClient() { - return !isBedrockClient(); - } - - /* - * Related class instances - */ + - /** - * @throws IllegalStateException if the player was not found in the database (should never happen) - * @throws DBException if a database access error occurs - */ - @Override - default SQLPlayer getDbPlayer() throws DBException { - SQLPlayer p = SQLPlayer.getPlayerFromUUID(getUniqueId()); - if (p == null) - throw new IllegalStateException("The player was not found in the database: " + getUniqueId()); - return p; - } - - - - - - - - /* - * Permissions and groups - */ - - /** - * Tells if this online player has the specified permission. - * @implSpec the implementation of this method must not directly or - * indirectly call the method {@link IOffPlayer#hasPermission(String)}, - * or it may result in a {@link StackOverflowError}. - */ - boolean hasPermission(String permission); - - /** - * Tells if this online player has the permission resulted from the provided expression. - * @implSpec the implementation of this method must not directly or - * indirectly call the method {@link IOffPlayer#hasPermissionExpression(String)}, - * or it may result in a {@link StackOverflowError}. - */ - boolean hasPermissionExpression(String permission); - - /** - * Lists all the values for a set of permission indicating an integer in a range. - *

- * 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."}. - *

- * 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); } /** diff --git a/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java b/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java new file mode 100644 index 0000000..1869e59 --- /dev/null +++ b/pandalib-players-standalone/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java @@ -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 { + 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 onlinePlayers = Collections.synchronizedMap(new HashMap<>()); + + private final LoadingCache 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 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. + *

+ * 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. + *

+ * 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. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(ComponentLike, boolean, UUID)}. + *

+ * 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); + } + + + + + + +} diff --git a/pom.xml b/pom.xml index 951cf75..453f81a 100644 --- a/pom.xml +++ b/pom.xml @@ -53,10 +53,12 @@ pandalib-net pandalib-netapi pandalib-paper + pandalib-paper-reflect pandalib-permissions + pandalib-players-standalone pandalib-reflect pandalib-util - pandalib-paper-reflect + pandalib-players-permissible @@ -69,39 +71,7 @@ - ${project.name}-${build.number} - - net.md-5 - scriptus - 0.4.1 - - git:${project.name}:${project.version}:%s:${build.number} - true - - - - initialize - - describe - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.2 - - - - - ${describe} - ${maven.build.timestamp} - - - - org.apache.maven.plugins maven-source-plugin