461 lines
12 KiB
Java
461 lines
12 KiB
Java
package fr.pandacube.lib.core.players;
|
||
|
||
import java.util.Calendar;
|
||
import java.util.OptionalLong;
|
||
import java.util.UUID;
|
||
import java.util.stream.LongStream;
|
||
|
||
import fr.pandacube.lib.chat.Chat;
|
||
import fr.pandacube.lib.chat.ChatColorUtil;
|
||
import fr.pandacube.lib.db.DBException;
|
||
import fr.pandacube.lib.permissions.PermPlayer;
|
||
import fr.pandacube.lib.permissions.Permissions;
|
||
import fr.pandacube.lib.util.Log;
|
||
|
||
import static fr.pandacube.lib.chat.ChatStatic.dataText;
|
||
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
||
import static fr.pandacube.lib.chat.ChatStatic.text;
|
||
import static fr.pandacube.lib.chat.ChatStatic.warningText;
|
||
|
||
public interface IOffPlayer {
|
||
|
||
/** From how long the last web activity should be before considering the user offline (in ms)? */
|
||
long TIMEOUT_WEB_SESSION = 10000; // msec
|
||
|
||
|
||
|
||
|
||
/*
|
||
* General data and state
|
||
*/
|
||
|
||
/**
|
||
* Return the ID of the minecraft account.
|
||
*
|
||
* @return the id of the player
|
||
*/
|
||
UUID getUniqueId();
|
||
|
||
|
||
/**
|
||
* Tells if the current account is an alt account generated by Pandacube.
|
||
*
|
||
* An alt account uses a specific bit in the UUID to distinguish themselves from the original account.
|
||
*/
|
||
default boolean isAltAccount() {
|
||
return (getUniqueId().getMostSignificantBits() & 0x8000L) == 0x8000L;
|
||
}
|
||
|
||
/**
|
||
* Gets the index of the current alt account generated by Pandacube.
|
||
*
|
||
* The first generated alt account will be numbered 1, the second 2, ...
|
||
*
|
||
* This method will return undetermined value if {@link #isAltAccount()} is false.
|
||
*/
|
||
default int getAltIndex() {
|
||
return (int) (getUniqueId().getMostSignificantBits() >> 8) & 0xF;
|
||
}
|
||
|
||
/**
|
||
* @return the last known player name of this player, or null if this player never joined the network.
|
||
*/
|
||
default String getName() {
|
||
return PlayerFinder.getLastKnownName(getUniqueId());
|
||
}
|
||
|
||
/**
|
||
* Indicate if this player is connected to the current node (server or proxy, depending on interface implementation)
|
||
* @return wether the player is online or not
|
||
*/
|
||
boolean isOnline();
|
||
|
||
|
||
|
||
|
||
|
||
/**
|
||
* Provides informations of the online status of the player:
|
||
* online in game, online on the website, or offline.
|
||
* If the player is online in game, it provides the current server they are
|
||
* connected.
|
||
*/
|
||
default PlayerStatusOnServer getPlayerStatus() {
|
||
|
||
IOnlinePlayer op = getOnlineInstance();
|
||
if (op != null && !op.isVanished())
|
||
return new PlayerStatusOnServer(PlayerStatusOnServer.PlayerStatus.ONLINE_IG, op.getServerName());
|
||
|
||
try {
|
||
SQLPlayer webSession = getDbPlayer();
|
||
|
||
if (webSession != null) {
|
||
long lastWebActivity = webSession.get(SQLPlayer.lastWebActivity);
|
||
|
||
if (System.currentTimeMillis() - lastWebActivity < TIMEOUT_WEB_SESSION)
|
||
return new PlayerStatusOnServer(PlayerStatusOnServer.PlayerStatus.ONLINE_WEB, null);
|
||
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
Log.severe(e);
|
||
}
|
||
return new PlayerStatusOnServer(PlayerStatusOnServer.PlayerStatus.OFFLINE, null);
|
||
}
|
||
|
||
record PlayerStatusOnServer(PlayerStatus status, String server) {
|
||
public Chat toComponent() {
|
||
if (status == PlayerStatus.ONLINE_IG)
|
||
return successText("En ligne, " + server);
|
||
if (status == PlayerStatus.ONLINE_WEB)
|
||
return warningText("En ligne, web");
|
||
if (status == PlayerStatus.OFFLINE)
|
||
return dataText("Hors ligne");
|
||
return text("N/A");
|
||
}
|
||
|
||
public enum PlayerStatus {
|
||
ONLINE_IG, ONLINE_WEB, OFFLINE
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Floodgate related stuff
|
||
*/
|
||
|
||
default boolean isBedrockAccount() {
|
||
int v = getUniqueId().version();
|
||
return v == 0 || v == 8; // also 8 if one day we supports alt accounts for floodgate players
|
||
}
|
||
|
||
default boolean isJavaAccount() {
|
||
return !isBedrockAccount();
|
||
}
|
||
|
||
|
||
|
||
/*
|
||
* Related class instances
|
||
*/
|
||
|
||
/**
|
||
* Return the online instance of this player, if any exists.
|
||
* May return itself if the current instance already represent an online player.
|
||
*/
|
||
IOnlinePlayer getOnlineInstance();
|
||
|
||
/**
|
||
* Get the database entry of this player, or null if the player never joined the network.
|
||
*/
|
||
default SQLPlayer getDbPlayer() throws DBException {
|
||
return SQLPlayer.getPlayerFromUUID(getUniqueId());
|
||
}
|
||
|
||
/**
|
||
* Get the permission instance of this player. This will never return null.
|
||
* @return the permission instance of this player
|
||
*/
|
||
default PermPlayer getPermissionUser() {
|
||
return Permissions.getPlayer(getUniqueId());
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Display name
|
||
*/
|
||
|
||
/**
|
||
* Returns the name of the player (if any), with eventual prefix and suffix depending on permission groups
|
||
* (and team for bukkit implementation)
|
||
* @return the display name of the player
|
||
*/
|
||
String getDisplayName();
|
||
|
||
/**
|
||
* Get an updated display name of the user,
|
||
* generated using eventual permission’s prefix(es) and suffix(es) of the player,
|
||
* and with color codes translated to Minecraft’s native {@code §}.
|
||
*/
|
||
default String getDisplayNameFromPermissionSystem() {
|
||
PermPlayer permU = getPermissionUser();
|
||
return ChatColorUtil.translateAlternateColorCodes('&',
|
||
permU.getPrefix() + getName() + permU.getSuffix());
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Permissions and groups
|
||
*/
|
||
|
||
/**
|
||
* Tells if this player has the specified permission.
|
||
* If the player is online, this will redirect the
|
||
* method call to the {@link IOnlinePlayer} instance,
|
||
* that MUST override this current method to avoid recussive
|
||
* loop.
|
||
* If the player is offline, it just call the Pandacube
|
||
* permission system.
|
||
* @param permission the permission node to test
|
||
* @return whether this player has the provided permission
|
||
*/
|
||
default boolean hasPermission(String permission) {
|
||
IOnlinePlayer online = getOnlineInstance();
|
||
|
||
if (online != null)
|
||
return online.hasPermission(permission);
|
||
|
||
// at this point, the player is offline
|
||
return getPermissionUser().hasPermissionOr(permission, null, null, false);
|
||
}
|
||
|
||
/**
|
||
* Tells if this player has the permission resulted from the provided expression.
|
||
* If the player is online, this will redirect the
|
||
* method call to the {@link IOnlinePlayer} instance,
|
||
* that MUST override this current method to avoid recussive
|
||
* loop.
|
||
* If the player is offline, it just call the Pandacube
|
||
* permission system.
|
||
* @param permissionExpression the permission node to test
|
||
* @return whether this player has the provided permission
|
||
*/
|
||
default boolean hasPermissionExpression(String permissionExpression) {
|
||
IOnlinePlayer online = getOnlineInstance();
|
||
|
||
if (online != null)
|
||
return online.hasPermissionExpression(permissionExpression);
|
||
|
||
// at this point, the player is offline
|
||
return getPermissionUser().hasPermissionExpression(permissionExpression, null, null);
|
||
}
|
||
|
||
/**
|
||
* Lists all the values for a set of permission indicating an integer in a range.
|
||
* <p>
|
||
* A permission range is used to easily attribute a number to a group or player,
|
||
* like the maximum number of homes allowed. For instance, if the player has the permission
|
||
* {@code essentials.home.12}, this method would return a stream containing the value 12,
|
||
* if the parameter {@code permissionPrefix} is {@code "essentials.home."}.
|
||
* <p>
|
||
* The use of a stream allow the caller to get either the maximum, the minimum, or do any
|
||
* other treatment to the values.
|
||
* @param permissionPrefix the permission prefix to search for.
|
||
* @return a LongStream containing all the values found for the specified permission prefix.
|
||
*/
|
||
default LongStream getPermissionRangeValues(String permissionPrefix) {
|
||
IOnlinePlayer online = getOnlineInstance();
|
||
|
||
if (online != null)
|
||
return online.getPermissionRangeValues(permissionPrefix);
|
||
|
||
// at this point, the player is offline
|
||
return getPermissionUser().getPermissionRangeValues(permissionPrefix, null, null);
|
||
}
|
||
|
||
/**
|
||
* Returns the maximum value returned by {@link IOffPlayer#getPermissionRangeValues(String)}.
|
||
*/
|
||
default OptionalLong getPermissionRangeMax(String permissionPrefix) {
|
||
IOnlinePlayer online = getOnlineInstance();
|
||
|
||
if (online != null)
|
||
return online.getPermissionRangeMax(permissionPrefix);
|
||
|
||
// at this point, the player is offline
|
||
return getPermissionUser().getPermissionRangeMax(permissionPrefix, null, null);
|
||
}
|
||
|
||
/**
|
||
* Tells if the this player is part of the specified group
|
||
*
|
||
* @param group the permissions group
|
||
* @return <i>true</i> if this player is part of the group,
|
||
* <i>false</i> otherwise
|
||
*/
|
||
default boolean isInGroup(String group) {
|
||
return getPermissionUser().isInGroup(group);
|
||
}
|
||
|
||
/**
|
||
* Tells if this player is part of the staff, based on permission groups
|
||
*/
|
||
default boolean isInStaff() {
|
||
return getPermissionUser().inheritsFromGroup("staff-base", true);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Ignore
|
||
*/
|
||
|
||
/**
|
||
* Tells if this player have the right to ignore the provided player
|
||
* @param ignored the player that is potentially ignored by this player.
|
||
* If this parameter is null, this method returns false.
|
||
*/
|
||
default boolean canIgnore(IOffPlayer ignored) {
|
||
if (ignored == null)
|
||
return false;
|
||
if (equals(ignored))
|
||
return false;
|
||
if (!isInStaff() && !ignored.isInStaff())
|
||
return true;
|
||
return hasPermission("pandacube.ignore.bypassfor." + ignored.getUniqueId());
|
||
}
|
||
|
||
/**
|
||
* Tells if the provided player have the right to ignore this player
|
||
* @param ignorer the player that potentially ignore this player
|
||
* If this parameter is null, this method returns false.
|
||
* @implNote the default implementation just calls {@link #canIgnore(IOffPlayer) ignorer.canIgnore(this)}.
|
||
*/
|
||
default boolean canBeIgnoredBy(IOffPlayer ignorer) {
|
||
if (ignorer == null)
|
||
return false;
|
||
return ignorer.canIgnore(this);
|
||
}
|
||
|
||
/**
|
||
* Determine if this player ignore the provided player.
|
||
* @param ignored the player that is potentially ignored by this player.
|
||
* If this parameter is null, this method returns false.
|
||
* @return true if this player have to right to ignore the provided player and is actually ignoring him.
|
||
*/
|
||
default boolean isIgnoring(IOffPlayer ignored) {
|
||
if (!canIgnore(ignored))
|
||
return false;
|
||
|
||
try {
|
||
return SQLPlayerIgnore.isPlayerIgnoringPlayer(getUniqueId(), ignored.getUniqueId());
|
||
} catch (DBException e) {
|
||
Log.severe("Can't determine if a player ignore another player, because we can't access to the database", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Determine if the provided player ignore this player, taking into account the exception permissions.
|
||
* @param ignorer the player that potentially ignore this player
|
||
* If this parameter is null, this method returns false.
|
||
* @return true if the provided player have to right to ignore this player and is actually ignoring him.
|
||
* @implNote the default implementation just calls {@link #isIgnoring(IOffPlayer) ignorer.isIgnoring(this)}.
|
||
*/
|
||
default boolean isIgnoredBy(IOffPlayer ignorer) {
|
||
return ignorer.isIgnoring(this);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Modération
|
||
*/
|
||
|
||
/**
|
||
* Retrieve the time when the player will be unmuted, or null if the player is not muted.
|
||
* @return the timestamp in millisecond of when the player will be unmuted
|
||
*/
|
||
default Long getMuteTimeout() {
|
||
try {
|
||
Long muteTimeout = getDbPlayer().get(SQLPlayer.muteTimeout);
|
||
if (muteTimeout == null || muteTimeout <= System.currentTimeMillis())
|
||
return null;
|
||
return muteTimeout;
|
||
} catch (DBException e) {
|
||
Log.severe(e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Tells if the player is currently muted, meaning that they cannot communicate
|
||
* through the chat or private messages.
|
||
*/
|
||
default boolean isMuted() {
|
||
return getMuteTimeout() != null;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Birthday
|
||
*/
|
||
|
||
default void setBirthday(int day, int month, Integer year) {
|
||
try {
|
||
SQLPlayer dbPlayer = getDbPlayer();
|
||
dbPlayer.setBirthday(day, month, year);
|
||
dbPlayer.save();
|
||
} catch (DBException e) {
|
||
throw new RuntimeException(e);
|
||
}
|
||
}
|
||
|
||
default Calendar getBirthday() {
|
||
try {
|
||
return getDbPlayer().getBirthday();
|
||
} catch (DBException e) {
|
||
throw new RuntimeException(e);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Player config
|
||
*/
|
||
|
||
default String getConfig(String key) throws DBException {
|
||
return SQLPlayerConfig.get(getUniqueId(), key);
|
||
}
|
||
|
||
default String getConfig(String key, String deflt) throws DBException {
|
||
return SQLPlayerConfig.get(getUniqueId(), key, deflt);
|
||
}
|
||
|
||
default void setConfig(String key, String value) throws DBException {
|
||
SQLPlayerConfig.set(getUniqueId(), key, value);
|
||
}
|
||
|
||
default void unsetConfig(String key) throws DBException {
|
||
SQLPlayerConfig.unset(getUniqueId(), key);
|
||
}
|
||
|
||
default boolean isWelcomeQuizzDone() {
|
||
try {
|
||
return Boolean.parseBoolean(getConfig("welcome.quizz.done", "false"));
|
||
} catch (DBException e) {
|
||
Log.severe("Error knowing if player has already done the quizz. Assuming they did for now.", e);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
|
||
}
|