Uniformization of how permission check is handled between pandalib-(.*-)players, Paper/Bungee and pandalib-permissions.

When an *OnlinePlayer class ask for permission, it always ask for Paper/Bungee API. The pandalib-permissions system will be called by Paper/Bungee only if it used and integrated into Paper/Bungee using the appropriate modules.
This commit reduces inter-dependencies between pandalib-(.*-)permissions modules and pandalib-(.*-)players (except pandalib-players-permissible that has to depends on pandalib-permissions)
This commit is contained in:
2022-08-08 03:16:00 +02:00
parent a885c224a6
commit f976350ee1
14 changed files with 95 additions and 40 deletions

View File

@@ -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();
}

View File

@@ -0,0 +1,256 @@
package fr.pandacube.lib.players.standalone;
import fr.pandacube.lib.chat.ChatStatic;
import net.kyori.adventure.identity.Identified;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import java.util.Locale;
import java.util.UUID;
public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
/*
* General data and state
*/
/**
* @return The current name of this player
* @apiNote The implementation is expected to call the environment API
* (Bukkit/Bungee) to get the name of the player.
*/
String getServerName();
String getWorldName();
/*
* Permissions and groups
*/
/**
* Tells if this online player has the specified permission.
* @implSpec Implementation of this method should call the permission system of their environment (paper/bungee),
* so this method will work independently of the usage of the 'pandalib-permissions' module.
*/
boolean hasPermission(String permission);
/*
* Sending packet and stuff to player
*/
/**
* Display the provided message in the players chat, if
* the chat is activated.
* @param message the message to display.
*/
void sendMessage(Component message);
/**
* Display the provided message in the players chat, if
* the chat is activated.
* @param message the message to display.
*/
default void sendMessage(ComponentLike message) {
sendMessage(message.asComponent());
}
/**
* Display the provided message in the players 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 dont 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.
*/
void sendMessage(Component message, Identified sender);
/**
* Display the provided message in the players 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 dont 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 == null ? Identity.nil() : Identity.identity(sender));
}
/**
* Display the provided message in the players chat, if the chat is
* activated, prepended with the server prefix.
* @param message the message to display
*/
default void sendPrefixedMessage(ComponentLike message) {
sendMessage(ChatStatic.prefixedAndColored(message));
}
/**
* Display a title in the middle of the screen.
* @param title The big text
* @param subtitle The less big text
* @param fadeIn Fade in time in tick
* @param stay Stay time in tick
* @param fadeOut Fade out time in tick
*/
void sendTitle(Component title, Component subtitle, int fadeIn, int stay, int fadeOut);
/**
* Display a title in the middle of the screen.
* @param title The big text
* @param subtitle The less big text
* @param fadeIn Fade in time in tick
* @param stay Stay time in tick
* @param fadeOut Fade out time in tick
*/
default void sendTitle(ComponentLike title, ComponentLike subtitle, int fadeIn, int stay, int fadeOut) {
sendTitle(title.asComponent(), subtitle.asComponent(), fadeIn, stay, fadeOut);
}
/**
* Update the server brand field in the debug menu (F3) of the player
* (third line in 1.15 debug screen). Supports ChatColor codes but no
* line break.
* @param brand the server brand to send to the client.
*/
void sendServerBrand(String brand);
/*
* Client options
*/
ClientOptions getClientOptions();
interface ClientOptions {
Locale getLocale();
int getViewDistance();
boolean hasChatColorEnabled();
/**
* Tells if the client is configured to completely hide the chat to the
* player. When this is the case, nothing is displayed in the chat box,
* and the player cant send any message or command.
* @implSpec if the value is unknown, it is assumed that the chat is
* fully visible.
*/
boolean isChatHidden();
/**
* Tells if the client is configured to display the chat normally.
* When this is the case, chat messages and system messages are
* displayed in the chat box, and the player can send messages and
* commands.
* @implSpec if the value is unknown, it is assumed that the chat is
* fully visible.
*/
boolean isChatFullyVisible();
/**
* Tells if the client is configured to only display system messages
* in the chat.
* When this is the case, chat messages are hidden but system messages
* are visible in the chat box, and the player can only send commands.
* @implSpec if the value is unknown, it is assumed that the chat is
* fully visible.
*/
boolean isChatOnlyDisplayingSystemMessages();
/**
* Tells if the client has configured the main hand on the left.
* @implSpec if the value is unknown, it is assumed that the main hand
* is on the right.
*/
boolean isLeftHanded();
/**
* Tells if the client has configured the main hand on the right.
* @implSpec if the value is unknown, it is assumed that the main hand
* is on the right.
*/
boolean isRightHanded();
/**
* Tells if the client has enabled the filtering of texts on sign and book titles.
* Always false as of MC 1.18.
*/
boolean isTextFilteringEnabled();
/**
* Tells if the client allows the server to list their player name in the
* multiplayer menu.
*/
boolean allowsServerListing();
boolean hasSkinCapeEnabled();
boolean hasSkinJacketEnabled();
boolean hasSkinLeftSleeveEnabled();
boolean hasSkinRightSleeveEnabled();
boolean hasSkinLeftPantsEnabled();
boolean hasSkinRightPantsEnabled();
boolean hasSkinHatsEnabled();
}
/**
* Tells if the player can send chat messages or receive chat messages from
* other players, according to their client configuration.
* <br>
* Chat messages represent public communication between players. By default,
* it only include actual chat message. This method may be used in commands
* like /me, /afk or the login/logout broadcasted messages
*/
default boolean canChat() {
return getClientOptions().isChatFullyVisible();
}
}

View File

@@ -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);
}
}