Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions API/src/main/java/fr/maxlego08/essentials/api/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,4 +266,19 @@ public interface Configuration extends ConfigurationFile {
* @return a map of options and their default values
*/
Map<Option, Boolean> getDefaultOptionValues();

/**
* Retrieves the server name used for cross-server communication.
* This is used with BungeeCord/Velocity for cross-server teleportation.
*
* @return the server name, or "default" if not configured
*/
String getServerName();

/**
* Checks if cross-server teleportation is enabled.
*
* @return true if cross-server teleportation is enabled
*/
boolean isCrossServerTeleportEnabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,52 @@ public ExpiringCache(long expiryDurationMillis) {
* Retrieves the value associated with the specified key from the cache. If the key is not found,
* or the entry has expired, the provided Loader is used to load the value, store it in the cache,
* and then return it.
* <p>
* Supports soft expiration: if the entry is soft-expired but not hard-expired, it returns the old value
* and refreshes the cache asynchronously.
*
* @param key the key whose associated value is to be returned
* @param loader the loader used to generate the value if it is not present or expired in the cache
* @return the value associated with the specified key or the newly loaded value if the key
* was not found in the cache or if the entry expired
*/
public V get(K key, Loader<V> loader) {
return cache.compute(key, (k, v) -> {
long currentTime = System.currentTimeMillis();
if (v == null || v.expiryTime < currentTime) {
V newValue = loader.load();
return new CacheEntry<>(newValue, currentTime + expiryDurationMillis);
}
return v;
}).value;
CacheEntry<V> entry = cache.get(key);
long currentTime = System.currentTimeMillis();

// 1. Hard Expiration or Not Present -> Synchronous Load
if (entry == null || entry.expiryTime < currentTime) {
return cache.compute(key, (k, v) -> {
// Double-check inside lock
long now = System.currentTimeMillis();
if (v == null || v.expiryTime < now) {
V newValue = loader.load();
long expiry = now + expiryDurationMillis;
long softExpiry = now + (long) (expiryDurationMillis * 0.8); // Refresh at 80% of ttl
return new CacheEntry<>(newValue, expiry, softExpiry);
}
return v;
}).value;
}

// 2. Soft Expiration -> Return Old Value & Asynchronous Refresh
if (entry.softExpiryTime < currentTime && entry.isUpdating.compareAndSet(false, true)) {
java.util.concurrent.CompletableFuture.runAsync(() -> {
try {
V newValue = loader.load();
long now = System.currentTimeMillis();
long expiry = now + expiryDurationMillis;
long softExpiry = now + (long) (expiryDurationMillis * 0.8);
cache.put(key, new CacheEntry<>(newValue, expiry, softExpiry));
} catch (Exception e) {
e.printStackTrace();
// Reset flag on failure so we can try again later
entry.isUpdating.set(false);
}
});
}

return entry.value;
}

/**
Expand Down Expand Up @@ -75,6 +106,16 @@ public interface Loader<V> {
/**
* A simple cache entry that holds the value and its expiry time.
*/
private record CacheEntry<V>(V value, long expiryTime) {
private static class CacheEntry<V> {
final V value;
final long expiryTime;
final long softExpiryTime;
final java.util.concurrent.atomic.AtomicBoolean isUpdating = new java.util.concurrent.atomic.AtomicBoolean(false);

CacheEntry(V value, long expiryTime, long softExpiryTime) {
this.value = value;
this.expiryTime = expiryTime;
this.softExpiryTime = softExpiryTime;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public enum Permission {
ESSENTIALS_TPA_ACCEPT,
ESSENTIALS_TPA_DENY,
ESSENTIALS_TPA_CANCEL,
ESSENTIALS_TRADE_USE,
ESSENTIALS_TRADE_ACCEPT,
ESSENTIALS_TRADE_DENY,
ESSENTIALS_BYPASS_COOLDOWN("Allows not to have a cooldown on all commands"),
ESSENTIALS_MORE,
ESSENTIALS_TP_WORLD,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package fr.maxlego08.essentials.api.event.events.trade;

import fr.maxlego08.essentials.api.event.EssentialsEvent;
import org.bukkit.entity.Player;

public class TradeCancelEvent extends EssentialsEvent {

private final Player player1;
private final Player player2;
private final Player canceller;

public TradeCancelEvent(Player player1, Player player2, Player canceller) {
this.player1 = player1;
this.player2 = player2;
this.canceller = canceller;
}

public Player getPlayer1() {
return player1;
}

public Player getPlayer2() {
return player2;
}

public Player getCanceller() {
return canceller;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fr.maxlego08.essentials.api.event.events.trade;

import fr.maxlego08.essentials.api.event.EssentialsEvent;
import org.bukkit.entity.Player;

public class TradeCompleteEvent extends EssentialsEvent {

private final Player player1;
private final Player player2;

public TradeCompleteEvent(Player player1, Player player2) {
this.player1 = player1;
this.player2 = player2;
}

public Player getPlayer1() {
return player1;
}

public Player getPlayer2() {
return player2;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fr.maxlego08.essentials.api.event.events.trade;

import fr.maxlego08.essentials.api.event.CancellableEssentialsEvent;
import org.bukkit.entity.Player;

public class TradeStartEvent extends CancellableEssentialsEvent {

private final Player player1;
private final Player player2;

public TradeStartEvent(Player player1, Player player2) {
this.player1 = player1;
this.player2 = player2;
}

public Player getPlayer1() {
return player1;
}

public Player getPlayer2() {
return player2;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public enum Message {
COMMAND_NO_ARG("<error>Impossible to find the command with its arguments."),
COMMAND_RESTRICTED("<error>You cannot use this command here."),
COMMAND_SYNTAXE_HELP("&f%syntax% &7» &7%description%"),
DESCRIPTION_TRADE("Manage trade requests"),

COMMAND_RELOAD("<success>You have just reloaded the configuration files."),
COMMAND_RELOAD_MODULE("<success>You have just reloaded the configuration files of the module &f%module%<success>."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import fr.maxlego08.essentials.api.commands.Permission;
import fr.maxlego08.essentials.api.messages.Message;
import fr.maxlego08.essentials.api.teleport.CrossServerLocation;
import fr.maxlego08.essentials.api.teleport.TeleportType;
import fr.maxlego08.essentials.api.user.Option;
import fr.maxlego08.essentials.api.user.PrivateMessage;
import fr.maxlego08.essentials.api.user.User;
Expand Down Expand Up @@ -142,4 +144,47 @@ public interface EssentialsServer {
* @param message The message to send.
*/
void pub(Player player, String message);

/**
* Sends a player to another server via BungeeCord/Velocity.
*
* @param player The player to send.
* @param serverName The target server name.
*/
void sendToServer(Player player, String serverName);

/**
* Requests a cross-server teleport. The player will be sent to the target server
* and then teleported to the destination location.
*
* @param player The player to teleport.
* @param teleportType The type of teleportation.
* @param destination The destination location including server name.
*/
void crossServerTeleport(Player player, TeleportType teleportType, CrossServerLocation destination);

/**
* Requests a cross-server teleport to another player.
*
* @param player The player requesting the teleport.
* @param teleportType The type of teleportation (TPA or TPA_HERE).
* @param targetPlayerName The name of the target player.
* @param targetServer The server where the target player is.
*/
void crossServerTeleportToPlayer(Player player, TeleportType teleportType, String targetPlayerName, String targetServer);

/**
* Gets the current server name from BungeeCord configuration.
*
* @return The server name or null if not configured.
*/
String getServerName();

/**
* Finds which server a player is on.
*
* @param playerName The player name to find.
* @return The server name or null if not found.
*/
String findPlayerServer(String playerName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package fr.maxlego08.essentials.api.server.messages;

import fr.maxlego08.essentials.api.teleport.CrossServerLocation;
import fr.maxlego08.essentials.api.teleport.TeleportType;

import java.util.UUID;

/**
* Redis message for cross-server teleportation requests.
*/
public class CrossServerTeleport {

private final UUID playerUuid;
private final String playerName;
private final TeleportType teleportType;
private final CrossServerLocation destination;
private final String targetName; // For TPA - the target player name
private final long timestamp;

public CrossServerTeleport(UUID playerUuid, String playerName, TeleportType teleportType, CrossServerLocation destination) {
this(playerUuid, playerName, teleportType, destination, null);
}

public CrossServerTeleport(UUID playerUuid, String playerName, TeleportType teleportType, CrossServerLocation destination, String targetName) {
this.playerUuid = playerUuid;
this.playerName = playerName;
this.teleportType = teleportType;
this.destination = destination;
this.targetName = targetName;
this.timestamp = System.currentTimeMillis();
}

public UUID getPlayerUuid() {
return playerUuid;
}

public String getPlayerName() {
return playerName;
}

public TeleportType getTeleportType() {
return teleportType;
}

public CrossServerLocation getDestination() {
return destination;
}

public String getTargetName() {
return targetName;
}

public long getTimestamp() {
return timestamp;
}

/**
* Check if this request is still valid (not expired).
* Default timeout is 30 seconds.
*/
public boolean isValid() {
return System.currentTimeMillis() - timestamp < 30000;
}
}

Loading