This commit is contained in:
itqop 2025-11-12 12:12:58 +03:00
parent 6bf526305b
commit eccf850d11
17 changed files with 459 additions and 326 deletions

View File

@ -4,7 +4,9 @@
"Bash(./gradlew tasks:*)", "Bash(./gradlew tasks:*)",
"Bash(./gradlew build:*)", "Bash(./gradlew build:*)",
"Bash(./gradlew compileJava:*)", "Bash(./gradlew compileJava:*)",
"Bash(./gradlew clean build:*)" "Bash(./gradlew clean build:*)",
"Bash(.gradlew clean build)",
"Bash(gradlew.bat clean build)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@ -12,6 +12,8 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/** /**
* HTTP client for HubGW API integration. * HTTP client for HubGW API integration.
@ -22,9 +24,9 @@ public class HubGWClient {
private static final Logger LOGGER = LogUtils.getLogger(); private static final Logger LOGGER = LogUtils.getLogger();
private static final Gson GSON = new GsonBuilder().create(); private static final Gson GSON = new GsonBuilder().create();
// Circuit breaker to prevent log spam // Circuit breaker to prevent log spam (thread-safe with atomics)
private volatile long lastErrorLogTime = 0; private final AtomicLong lastErrorLogTime = new AtomicLong(0);
private volatile int consecutiveErrors = 0; private final AtomicInteger consecutiveErrors = new AtomicInteger(0);
private static final long ERROR_LOG_COOLDOWN_MS = 60_000; // 1 minute private static final long ERROR_LOG_COOLDOWN_MS = 60_000; // 1 minute
private static final int ERROR_THRESHOLD = 3; private static final int ERROR_THRESHOLD = 3;
@ -215,52 +217,54 @@ public class HubGWClient {
} }
/** /**
* Log HTTP error with circuit breaker logic. * Log HTTP error with circuit breaker logic (thread-safe).
*/ */
private void logHttpError(String endpoint, int statusCode, String responseBody) { private void logHttpError(String endpoint, int statusCode, String responseBody) {
consecutiveErrors++; int errorCount = consecutiveErrors.incrementAndGet();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (consecutiveErrors == 1 || (now - lastErrorLogTime) > ERROR_LOG_COOLDOWN_MS) { long lastLog = lastErrorLogTime.get();
if (errorCount == 1 || (now - lastLog) > ERROR_LOG_COOLDOWN_MS) {
if (Config.enableDebugLogging) { if (Config.enableDebugLogging) {
LOGGER.warn("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody); LOGGER.warn("API request failed: {} returned {} - Response: {}", endpoint, statusCode, responseBody);
} else { } else {
LOGGER.warn("API request failed: {} returned {}", endpoint, statusCode); LOGGER.warn("API request failed: {} returned {}", endpoint, statusCode);
} }
lastErrorLogTime = now; lastErrorLogTime.set(now);
if (consecutiveErrors > ERROR_THRESHOLD) { if (errorCount > ERROR_THRESHOLD) {
LOGGER.warn("API has failed {} times consecutively. Further errors will be throttled.", consecutiveErrors); LOGGER.warn("API has failed {} times consecutively. Further errors will be throttled.", errorCount);
} }
} }
} }
/** /**
* Log API connection error with circuit breaker logic. * Log API connection error with circuit breaker logic (thread-safe).
*/ */
private void logApiError(String message, Throwable ex) { private void logApiError(String message, Throwable ex) {
consecutiveErrors++; int errorCount = consecutiveErrors.incrementAndGet();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (consecutiveErrors == 1 || (now - lastErrorLogTime) > ERROR_LOG_COOLDOWN_MS) { long lastLog = lastErrorLogTime.get();
LOGGER.warn("{}: {} (consecutive errors: {})", message, ex.getMessage(), consecutiveErrors);
lastErrorLogTime = now;
if (consecutiveErrors > ERROR_THRESHOLD) { if (errorCount == 1 || (now - lastLog) > ERROR_LOG_COOLDOWN_MS) {
LOGGER.warn("API has failed {} times consecutively. Further errors will be throttled.", consecutiveErrors); LOGGER.warn("{}: {} (consecutive errors: {})", message, ex.getMessage(), errorCount);
lastErrorLogTime.set(now);
if (errorCount > ERROR_THRESHOLD) {
LOGGER.warn("API has failed {} times consecutively. Further errors will be throttled.", errorCount);
} }
} }
} }
/** /**
* Reset error counter after successful request. * Reset error counter after successful request (thread-safe).
*/ */
private void resetErrorCounter() { private void resetErrorCounter() {
if (consecutiveErrors > 0) { int previousErrors = consecutiveErrors.getAndSet(0);
if (consecutiveErrors > ERROR_THRESHOLD) { if (previousErrors > ERROR_THRESHOLD) {
LOGGER.info("API connection restored after {} consecutive errors", consecutiveErrors); LOGGER.info("API connection restored after {} consecutive errors", previousErrors);
}
consecutiveErrors = 0;
} }
} }
} }

View File

@ -19,6 +19,8 @@ import org.itqop.HubmcEssentials.util.LocationUtil;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /goto command - Custom teleport command with cooldowns and logging. * /goto command - Custom teleport command with cooldowns and logging.
* *
@ -82,55 +84,60 @@ public class GotoCommand {
String cooldownType = "tp|" + target.getGameProfile().getName(); String cooldownType = "tp|" + target.getGameProfile().getName();
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, cooldownType).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, cooldownType).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Save current location for /back // Set cooldown FIRST
LocationStorage.saveLocation(player); return CooldownService.createCooldown(playerUuid, cooldownType, Config.cooldownGoto).thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Store from location for teleport history // NOW perform action - save location and teleport
Vec3 fromPos = player.position(); LocationStorage.saveLocation(player);
String fromWorld = LocationUtil.getWorldId(player.level());
// Teleport to target // Store from location for teleport history
boolean success = LocationUtil.teleportPlayer( Vec3 fromPos = player.position();
player, String fromWorld = LocationUtil.getWorldId(player.level());
target.serverLevel(),
target.getX(),
target.getY(),
target.getZ(),
target.getYRot(),
target.getXRot()
);
if (success) { // Teleport to target
MessageUtil.sendSuccess(player, "Телепортация к игроку §6" + target.getGameProfile().getName()); boolean teleportSuccess = LocationUtil.teleportPlayer(
player,
// Log teleport history target.serverLevel(),
TeleportHistoryRequest historyRequest = new TeleportHistoryRequest( target.getX(),
playerUuid, target.getY(),
fromWorld, target.getZ(),
fromPos.x, fromPos.y, fromPos.z, target.getYRot(),
LocationUtil.getWorldId(target.level()), target.getXRot()
target.getX(), target.getY(), target.getZ(),
"to_player",
target.getGameProfile().getName()
); );
TeleportService.logTeleport(historyRequest);
// Set cooldown if (teleportSuccess) {
CooldownService.createCooldown(playerUuid, cooldownType, Config.cooldownGoto); MessageUtil.sendSuccess(player, "Телепортация к игроку §6" + target.getGameProfile().getName());
} else {
MessageUtil.sendError(player, "Ошибка телепортации"); // Log teleport history
} TeleportHistoryRequest historyRequest = new TeleportHistoryRequest(
playerUuid,
fromWorld,
fromPos.x, fromPos.y, fromPos.z,
LocationUtil.getWorldId(target.level()),
target.getX(), target.getY(), target.getZ(),
"to_player",
target.getGameProfile().getName()
);
TeleportService.logTeleport(historyRequest);
} else {
MessageUtil.sendError(player, "Ошибка телепортации");
}
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;
@ -227,54 +234,59 @@ public class GotoCommand {
String cooldownType = "tp|coords"; String cooldownType = "tp|coords";
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, cooldownType).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, cooldownType).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Save current location for /back // Set cooldown FIRST
LocationStorage.saveLocation(player); return CooldownService.createCooldown(playerUuid, cooldownType, Config.cooldownGoto).thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Store from location for teleport history // NOW perform action - save location and teleport
Vec3 fromPos = player.position(); LocationStorage.saveLocation(player);
String fromWorld = LocationUtil.getWorldId(player.level());
// Teleport to coordinates // Store from location for teleport history
boolean success = LocationUtil.teleportPlayer( Vec3 fromPos = player.position();
player, String fromWorld = LocationUtil.getWorldId(player.level());
player.serverLevel(),
location.x,
location.y,
location.z
);
if (success) { // Teleport to coordinates
MessageUtil.sendSuccess(player, "Телепортация на координаты: " + boolean teleportSuccess = LocationUtil.teleportPlayer(
MessageUtil.formatCoords(location.x, location.y, location.z)); player,
player.serverLevel(),
// Log teleport history location.x,
TeleportHistoryRequest historyRequest = new TeleportHistoryRequest( location.y,
playerUuid, location.z
fromWorld,
fromPos.x, fromPos.y, fromPos.z,
LocationUtil.getWorldId(player.level()),
location.x, location.y, location.z,
"to_coords",
MessageUtil.formatCoords(location.x, location.y, location.z)
); );
TeleportService.logTeleport(historyRequest);
// Set cooldown if (teleportSuccess) {
CooldownService.createCooldown(playerUuid, cooldownType, Config.cooldownGoto); MessageUtil.sendSuccess(player, "Телепортация на координаты: " +
} else { MessageUtil.formatCoords(location.x, location.y, location.z));
MessageUtil.sendError(player, "Ошибка телепортации");
} // Log teleport history
TeleportHistoryRequest historyRequest = new TeleportHistoryRequest(
playerUuid,
fromWorld,
fromPos.x, fromPos.y, fromPos.z,
LocationUtil.getWorldId(player.level()),
location.x, location.y, location.z,
"to_coords",
MessageUtil.formatCoords(location.x, location.y, location.z)
);
TeleportService.logTeleport(historyRequest);
} else {
MessageUtil.sendError(player, "Ошибка телепортации");
}
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -20,6 +20,7 @@ import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/** /**
* /pot command - Apply potion effects to player. * /pot command - Apply potion effects to player.
@ -82,38 +83,43 @@ public class PotCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Apply effect // Set cooldown FIRST
MobEffect effect = effectHolder.get().value(); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownPot).thenAccept(success -> {
int durationTicks = durationSeconds * 20; // Convert seconds to ticks if (!success) {
MobEffectInstance effectInstance = new MobEffectInstance( MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
effectHolder.get(), return;
durationTicks, }
amplifier,
false, // ambient
true, // visible particles
true // show icon
);
player.addEffect(effectInstance); // NOW perform action - apply effect
MobEffect effect = effectHolder.get().value();
int durationTicks = durationSeconds * 20; // Convert seconds to ticks
MobEffectInstance effectInstance = new MobEffectInstance(
effectHolder.get(),
durationTicks,
amplifier,
false, // ambient
true, // visible particles
true // show icon
);
// Get effect display name player.addEffect(effectInstance);
String effectDisplayName = effect.getDisplayName().getString();
MessageUtil.sendSuccess(player, "Эффект применен: §6" + effectDisplayName +
"§a (Уровень " + (amplifier + 1) + ", " + durationSeconds + " сек.)");
// Set cooldown // Get effect display name
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownPot); String effectDisplayName = effect.getDisplayName().getString();
MessageUtil.sendSuccess(player, "Эффект применен: §6" + effectDisplayName +
"§a (Уровень " + (amplifier + 1) + ", " + durationSeconds + " сек.)");
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -13,6 +13,8 @@ import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* Time control commands - Set world time. * Time control commands - Set world time.
* Commands: /day, /night, /morning, /evening * Commands: /day, /night, /morning, /evening
@ -66,27 +68,32 @@ public class TimeCommand {
String cooldownType = "time|" + timeType; String cooldownType = "time|" + timeType;
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, cooldownType).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, cooldownType).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Set world time // Set cooldown FIRST
ServerLevel level = player.serverLevel(); return CooldownService.createCooldown(playerUuid, cooldownType, Config.cooldownTime).thenAccept(success -> {
level.setDayTime(timeValue); if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Send success message // NOW perform action - set world time
String timeDisplayName = getTimeDisplayName(timeType); ServerLevel level = player.serverLevel();
MessageUtil.sendSuccess(player, "Время установлено: §6" + timeDisplayName); level.setDayTime(timeValue);
// Set cooldown // Send success message
CooldownService.createCooldown(playerUuid, cooldownType, Config.cooldownTime); String timeDisplayName = getTimeDisplayName(timeType);
MessageUtil.sendSuccess(player, "Время установлено: §6" + timeDisplayName);
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -17,6 +17,8 @@ import org.itqop.HubmcEssentials.util.LocationUtil;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /top command - Teleport to the highest block above player. * /top command - Teleport to the highest block above player.
* Permission: hubmc.cmd.top * Permission: hubmc.cmd.top
@ -48,50 +50,56 @@ public class TopCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
MessageUtil.sendInfo(player, "Поиск самого высокого блока..."); // Set cooldown FIRST
return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownTop).thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Find highest block // NOW perform action - find highest block and teleport
ServerLevel level = player.serverLevel(); MessageUtil.sendInfo(player, "Поиск самого высокого блока...");
BlockPos currentPos = player.blockPosition();
int highestY = findHighestBlock(level, currentPos.getX(), currentPos.getZ());
if (highestY == -1) { // Find highest block
MessageUtil.sendError(player, "Не удалось найти безопасную локацию"); ServerLevel level = player.serverLevel();
return; BlockPos currentPos = player.blockPosition();
} int highestY = findHighestBlock(level, currentPos.getX(), currentPos.getZ());
// Save current location for /back if (highestY == -1) {
LocationStorage.saveLocation(player); MessageUtil.sendError(player, "Не удалось найти безопасную локацию");
return;
}
// Teleport player // Save current location for /back
boolean success = LocationUtil.teleportPlayer( LocationStorage.saveLocation(player);
player,
level,
currentPos.getX() + 0.5,
highestY + 1,
currentPos.getZ() + 0.5
);
if (success) { // Teleport player
MessageUtil.sendSuccess(player, "Телепортация на самый высокий блок: " + boolean teleportSuccess = LocationUtil.teleportPlayer(
MessageUtil.formatCoords(currentPos.getX(), highestY + 1, currentPos.getZ())); player,
level,
currentPos.getX() + 0.5,
highestY + 1,
currentPos.getZ() + 0.5
);
// Set cooldown if (teleportSuccess) {
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownTop); MessageUtil.sendSuccess(player, "Телепортация на самый высокий блок: " +
} else { MessageUtil.formatCoords(currentPos.getX(), highestY + 1, currentPos.getZ()));
MessageUtil.sendError(player, "Ошибка телепортации"); } else {
} MessageUtil.sendError(player, "Ошибка телепортации");
}
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -8,6 +8,8 @@ import net.minecraft.server.level.ServerPlayer;
import org.itqop.HubmcEssentials.Config; import org.itqop.HubmcEssentials.Config;
import org.itqop.HubmcEssentials.api.dto.cooldown.CooldownCheckResponse; import org.itqop.HubmcEssentials.api.dto.cooldown.CooldownCheckResponse;
import org.itqop.HubmcEssentials.api.service.CooldownService; import org.itqop.HubmcEssentials.api.service.CooldownService;
import java.util.concurrent.CompletableFuture;
import org.itqop.HubmcEssentials.permission.PermissionManager; import org.itqop.HubmcEssentials.permission.PermissionManager;
import org.itqop.HubmcEssentials.permission.PermissionNodes; import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
@ -24,7 +26,7 @@ public class ClearCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) { public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal("clear") dispatcher.register(Commands.literal("clear")
.requires(source -> source.isPlayer()) .requires(CommandSourceStack::isPlayer)
.executes(ClearCommand::execute)); .executes(ClearCommand::execute));
} }
@ -43,23 +45,29 @@ public class ClearCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Clear inventory // Set cooldown FIRST to prevent race condition
player.getInventory().clearContent(); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownClear)
MessageUtil.sendSuccess(player, "Инвентарь очищен"); .thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Set cooldown // NOW perform action (cooldown already set)
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownClear); player.getInventory().clearContent();
MessageUtil.sendSuccess(player, "Инвентарь очищен");
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -16,6 +16,8 @@ import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /ec command - Open player's ender chest. * /ec command - Open player's ender chest.
* Permission: hubmc.cmd.ec * Permission: hubmc.cmd.ec
@ -46,25 +48,30 @@ public class EcCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Open ender chest // Set cooldown FIRST
player.openMenu(new SimpleMenuProvider( return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownEc).thenAccept(success -> {
(id, playerInventory, p) -> ChestMenu.threeRows(id, playerInventory, player.getEnderChestInventory()), if (!success) {
Component.literal("Эндер-сундук") MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
)); return;
}
// Set cooldown // NOW perform action - open ender chest
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownEc); player.openMenu(new SimpleMenuProvider(
(id, playerInventory, p) -> ChestMenu.threeRows(id, playerInventory, player.getEnderChestInventory()),
Component.literal("Эндер-сундук")
));
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -15,6 +15,8 @@ import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /hat command - Wear held item as a hat. * /hat command - Wear held item as a hat.
* Permission: hubmc.cmd.hat * Permission: hubmc.cmd.hat
@ -52,28 +54,33 @@ public class HatCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Get current helmet // Set cooldown FIRST
ItemStack currentHelmet = player.getItemBySlot(EquipmentSlot.HEAD); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownHat).thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Swap hand item with helmet // NOW perform action - swap hand item with helmet
player.setItemSlot(EquipmentSlot.HEAD, handItem.copy()); ItemStack currentHelmet = player.getItemBySlot(EquipmentSlot.HEAD);
player.setItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND, currentHelmet);
MessageUtil.sendSuccess(player, "Предмет надет на голову"); // Swap hand item with helmet
player.setItemSlot(EquipmentSlot.HEAD, handItem.copy());
player.setItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND, currentHelmet);
// Set cooldown MessageUtil.sendSuccess(player, "Предмет надет на голову");
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownHat); });
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -13,6 +13,8 @@ import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /repair all command - Repair all items in inventory and armor. * /repair all command - Repair all items in inventory and armor.
* Permission: hubmc.cmd.repair.all * Permission: hubmc.cmd.repair.all
@ -46,46 +48,51 @@ public class RepairAllCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Repair all items // Set cooldown FIRST
int repairedCount = 0; return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownRepairAll).thenAccept(success -> {
if (!success) {
// Repair inventory items MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
for (int i = 0; i < player.getInventory().getContainerSize(); i++) { return;
ItemStack item = player.getInventory().getItem(i);
if (!item.isEmpty() && item.isDamageableItem() && item.isDamaged()) {
item.setDamageValue(0);
repairedCount++;
} }
}
// Repair armor // NOW perform action - repair all items
for (ItemStack armorItem : player.getArmorSlots()) { int repairedCount = 0;
if (!armorItem.isEmpty() && armorItem.isDamageableItem() && armorItem.isDamaged()) {
armorItem.setDamageValue(0); // Repair inventory items
repairedCount++; for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
ItemStack item = player.getInventory().getItem(i);
if (!item.isEmpty() && item.isDamageableItem() && item.isDamaged()) {
item.setDamageValue(0);
repairedCount++;
}
} }
}
if (repairedCount == 0) { // Repair armor
MessageUtil.sendInfo(player, "Нет поврежденных предметов"); for (ItemStack armorItem : player.getArmorSlots()) {
return; if (!armorItem.isEmpty() && armorItem.isDamageableItem() && armorItem.isDamaged()) {
} armorItem.setDamageValue(0);
repairedCount++;
}
}
MessageUtil.sendSuccess(player, "Отремонтировано предметов: " + repairedCount); if (repairedCount == 0) {
MessageUtil.sendInfo(player, "Нет поврежденных предметов");
return;
}
// Set cooldown MessageUtil.sendSuccess(player, "Отремонтировано предметов: " + repairedCount);
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownRepairAll); });
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -18,6 +18,8 @@ import org.itqop.HubmcEssentials.util.LocationUtil;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /back command - Teleport to last location. * /back command - Teleport to last location.
* Permission: hubmc.cmd.back * Permission: hubmc.cmd.back
@ -55,64 +57,69 @@ public class BackCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Get last location // Set cooldown FIRST
LocationStorage.LastLocation lastLoc = LocationStorage.getLastLocation(player); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownBack).thenAccept(success -> {
if (lastLoc == null) { if (!success) {
MessageUtil.sendError(player, "Нет сохраненной позиции для возврата"); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return;
} }
// Parse world dimension // NOW perform action - get last location and teleport
ResourceLocation worldLocation = ResourceLocation.tryParse(lastLoc.getWorldId()); LocationStorage.LastLocation lastLoc = LocationStorage.getLastLocation(player);
if (worldLocation == null) { if (lastLoc == null) {
MessageUtil.sendError(player, "Неверный мир"); MessageUtil.sendError(player, "Нет сохраненной позиции для возврата");
return; return;
} }
ResourceKey<net.minecraft.world.level.Level> worldKey = ResourceKey.create( // Parse world dimension
Registries.DIMENSION, ResourceLocation worldLocation = ResourceLocation.tryParse(lastLoc.getWorldId());
worldLocation if (worldLocation == null) {
); MessageUtil.sendError(player, "Неверный мир");
return;
}
ServerLevel targetLevel = context.getSource().getServer().getLevel(worldKey); ResourceKey<net.minecraft.world.level.Level> worldKey = ResourceKey.create(
if (targetLevel == null) { Registries.DIMENSION,
MessageUtil.sendError(player, "Мир не найден"); worldLocation
return; );
}
// Save current location before teleporting ServerLevel targetLevel = context.getSource().getServer().getLevel(worldKey);
LocationStorage.saveLocation(player); if (targetLevel == null) {
MessageUtil.sendError(player, "Мир не найден");
return;
}
// Teleport player // Save current location before teleporting
boolean success = LocationUtil.teleportPlayer( LocationStorage.saveLocation(player);
player,
targetLevel,
lastLoc.getX(),
lastLoc.getY(),
lastLoc.getZ(),
lastLoc.getYaw(),
lastLoc.getPitch()
);
if (success) { // Teleport player
MessageUtil.sendSuccess(player, "Телепортация на последнюю позицию"); boolean teleportSuccess = LocationUtil.teleportPlayer(
} else { player,
MessageUtil.sendError(player, "Ошибка телепортации"); targetLevel,
} lastLoc.getX(),
lastLoc.getY(),
lastLoc.getZ(),
lastLoc.getYaw(),
lastLoc.getPitch()
);
// Set cooldown if (teleportSuccess) {
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownBack); MessageUtil.sendSuccess(player, "Телепортация на последнюю позицию");
} else {
MessageUtil.sendError(player, "Ошибка телепортации");
}
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -7,6 +7,8 @@ import net.minecraft.commands.Commands;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.itqop.HubmcEssentials.Config; import org.itqop.HubmcEssentials.Config;
import org.itqop.HubmcEssentials.api.service.CooldownService; import org.itqop.HubmcEssentials.api.service.CooldownService;
import java.util.concurrent.CompletableFuture;
import org.itqop.HubmcEssentials.permission.PermissionManager; import org.itqop.HubmcEssentials.permission.PermissionManager;
import org.itqop.HubmcEssentials.permission.PermissionNodes; import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
@ -43,25 +45,31 @@ public class FeedCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Feed player // Set cooldown FIRST to prevent race condition
player.getFoodData().setFoodLevel(20); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownFeed)
player.getFoodData().setSaturation(20.0f); .thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
MessageUtil.sendSuccess(player, "Вы сыты"); // NOW perform action (cooldown already set)
player.getFoodData().setFoodLevel(20);
player.getFoodData().setSaturation(20.0f);
// Set cooldown MessageUtil.sendSuccess(player, "Вы сыты");
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownFeed); });
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -6,6 +6,8 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.itqop.HubmcEssentials.Config; import org.itqop.HubmcEssentials.Config;
import java.util.concurrent.CompletableFuture;
import org.itqop.HubmcEssentials.api.service.CooldownService; import org.itqop.HubmcEssentials.api.service.CooldownService;
import org.itqop.HubmcEssentials.permission.PermissionManager; import org.itqop.HubmcEssentials.permission.PermissionManager;
import org.itqop.HubmcEssentials.permission.PermissionNodes; import org.itqop.HubmcEssentials.permission.PermissionNodes;
@ -43,29 +45,35 @@ public class HealCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Heal player // Set cooldown FIRST to prevent race condition
player.setHealth(player.getMaxHealth()); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownHeal)
player.getFoodData().setFoodLevel(20); .thenAccept(success -> {
player.getFoodData().setSaturation(20.0f); if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Remove negative effects (optional) // NOW perform action (cooldown already set)
player.removeAllEffects(); player.setHealth(player.getMaxHealth());
player.getFoodData().setFoodLevel(20);
player.getFoodData().setSaturation(20.0f);
MessageUtil.sendSuccess(player, "Вы полностью исцелены"); // Remove negative effects (optional)
player.removeAllEffects();
// Set cooldown MessageUtil.sendSuccess(player, "Вы полностью исцелены");
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownHeal); });
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -14,6 +14,7 @@ import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
/** /**
* /near [radius] command - Show nearby players. * /near [radius] command - Show nearby players.
@ -52,34 +53,39 @@ public class NearCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Find nearby players // Set cooldown FIRST
List<ServerPlayer> nearbyPlayers = PlayerUtil.getPlayersNear(player, radius); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownNear).thenAccept(success -> {
if (!success) {
if (nearbyPlayers.isEmpty()) { MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
MessageUtil.sendInfo(player, "Поблизости нет игроков (радиус: " + radius + " блоков)"); return;
} else {
MessageUtil.sendInfo(player, "§eИгроки поблизости (радиус: " + radius + " блоков):");
for (ServerPlayer nearby : nearbyPlayers) {
double distance = player.distanceTo(nearby);
String distanceStr = MessageUtil.formatDistance(distance);
MessageUtil.sendInfo(player, " §7- §f" + nearby.getName().getString() + " §7(" + distanceStr + ")");
} }
}
// Set cooldown // NOW perform action - find nearby players
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownNear); List<ServerPlayer> nearbyPlayers = PlayerUtil.getPlayersNear(player, radius);
if (nearbyPlayers.isEmpty()) {
MessageUtil.sendInfo(player, "Поблизости нет игроков (радиус: " + radius + " блоков)");
} else {
MessageUtil.sendInfo(player, "§eИгроки поблизости (радиус: " + radius + " блоков):");
for (ServerPlayer nearby : nearbyPlayers) {
double distance = player.distanceTo(nearby);
String distanceStr = MessageUtil.formatDistance(distance);
MessageUtil.sendInfo(player, " §7- §f" + nearby.getName().getString() + " §7(" + distanceStr + ")");
}
}
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -13,6 +13,8 @@ import org.itqop.HubmcEssentials.permission.PermissionNodes;
import org.itqop.HubmcEssentials.util.MessageUtil; import org.itqop.HubmcEssentials.util.MessageUtil;
import org.itqop.HubmcEssentials.util.PlayerUtil; import org.itqop.HubmcEssentials.util.PlayerUtil;
import java.util.concurrent.CompletableFuture;
/** /**
* /repair command - Repair item in hand. * /repair command - Repair item in hand.
* Permission: hubmc.cmd.repair * Permission: hubmc.cmd.repair
@ -63,24 +65,28 @@ public class RepairCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
// Repair item // Set cooldown FIRST
handItem.setDamageValue(0); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownRepair).thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
MessageUtil.sendSuccess(player, "Предмет отремонтирован"); // NOW perform action - repair item
handItem.setDamageValue(0);
// Set cooldown MessageUtil.sendSuccess(player, "Предмет отремонтирован");
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownRepair); });
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -7,6 +7,8 @@ import net.minecraft.commands.Commands;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.itqop.HubmcEssentials.Config; import org.itqop.HubmcEssentials.Config;
import java.util.concurrent.CompletableFuture;
import org.itqop.HubmcEssentials.api.service.CooldownService; import org.itqop.HubmcEssentials.api.service.CooldownService;
import org.itqop.HubmcEssentials.permission.PermissionManager; import org.itqop.HubmcEssentials.permission.PermissionManager;
import org.itqop.HubmcEssentials.permission.PermissionNodes; import org.itqop.HubmcEssentials.permission.PermissionNodes;
@ -53,51 +55,58 @@ public class RtpCommand {
String playerUuid = PlayerUtil.getUUIDString(player); String playerUuid = PlayerUtil.getUUIDString(player);
// Check cooldown // Check cooldown
CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenAccept(cooldownResponse -> { CooldownService.checkCooldown(playerUuid, COOLDOWN_TYPE).thenCompose(cooldownResponse -> {
if (cooldownResponse == null) { if (cooldownResponse == null) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return; return CompletableFuture.completedFuture(null);
} }
if (cooldownResponse.isActive()) { if (cooldownResponse.isActive()) {
MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds()); MessageUtil.sendCooldownMessage(player, cooldownResponse.getRemainingSeconds());
return; return CompletableFuture.completedFuture(null);
} }
MessageUtil.sendInfo(player, "Поиск безопасной локации..."); MessageUtil.sendInfo(player, "Поиск безопасной локации...");
// Find random safe location // Find random safe location (synchronous, fast operation)
ServerLevel level = player.serverLevel(); ServerLevel level = player.serverLevel();
Optional<RandomLocation> randomLoc = findSafeRandomLocation(level); Optional<RandomLocation> randomLoc = findSafeRandomLocation(level);
if (randomLoc.isEmpty()) { if (randomLoc.isEmpty()) {
MessageUtil.sendError(player, "Не удалось найти безопасную локацию"); MessageUtil.sendError(player, "Не удалось найти безопасную локацию");
return; return CompletableFuture.completedFuture(null);
} }
RandomLocation loc = randomLoc.get(); RandomLocation loc = randomLoc.get();
// Save current location for /back // Set cooldown FIRST to prevent race condition (only if location found)
LocationStorage.saveLocation(player); return CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownRtp)
.thenAccept(success -> {
if (!success) {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return;
}
// Teleport player // NOW perform teleport (cooldown already set)
boolean success = LocationUtil.teleportPlayer( // Save current location for /back
player, LocationStorage.saveLocation(player);
level,
loc.x,
loc.y,
loc.z
);
if (success) { // Teleport player
MessageUtil.sendSuccess(player, "Телепортация на случайную локацию: " + boolean tpSuccess = LocationUtil.teleportPlayer(
MessageUtil.formatCoords(loc.x, loc.y, loc.z)); player,
} else { level,
MessageUtil.sendError(player, "Ошибка телепортации"); loc.x,
} loc.y,
loc.z
);
// Set cooldown if (tpSuccess) {
CooldownService.createCooldown(playerUuid, COOLDOWN_TYPE, Config.cooldownRtp); MessageUtil.sendSuccess(player, "Телепортация на случайную локацию: " +
MessageUtil.formatCoords(loc.x, loc.y, loc.z));
} else {
MessageUtil.sendError(player, "Ошибка телепортации");
}
});
}).exceptionally(ex -> { }).exceptionally(ex -> {
MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE); MessageUtil.sendError(player, MessageUtil.API_UNAVAILABLE);
return null; return null;

View File

@ -155,6 +155,27 @@ public final class LocationUtil {
if (feet.is(Blocks.FIRE) || head.is(Blocks.FIRE)) { if (feet.is(Blocks.FIRE) || head.is(Blocks.FIRE)) {
return false; return false;
} }
if (ground.is(Blocks.MAGMA_BLOCK)) {
return false;
}
if (ground.is(Blocks.CAMPFIRE) || ground.is(Blocks.SOUL_CAMPFIRE)) {
return false;
}
if (feet.is(Blocks.CACTUS) || head.is(Blocks.CACTUS) || ground.is(Blocks.CACTUS)) {
return false;
}
if (feet.is(Blocks.SWEET_BERRY_BUSH) || ground.is(Blocks.SWEET_BERRY_BUSH)) {
return false;
}
if (feet.is(Blocks.WITHER_ROSE)) {
return false;
}
if (feet.is(Blocks.POWDER_SNOW) || head.is(Blocks.POWDER_SNOW)) {
return false;
}
if (feet.is(Blocks.POINTED_DRIPSTONE) || head.is(Blocks.POINTED_DRIPSTONE)) {
return false;
}
return true; return true;
} }