Backup before major refactor of AdminMode based on user feedback

This commit is contained in:
2026-03-26 22:38:29 +07:00
parent b439a42134
commit ef128cc08d
25 changed files with 2130 additions and 582 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -3,15 +3,23 @@ package org.blz.adminmode;
import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.dialog.AdminDialogService;
import org.blz.adminmode.integrations.CoreProtectIntegration;
import org.blz.adminmode.integrations.LuckPermsIntegration;
import org.blz.adminmode.listeners.AdminModeListener;
import org.blz.adminmode.listeners.GameModeChangeListener;
import org.blz.adminmode.listeners.MobTargetListener;
import org.blz.adminmode.listeners.PlayerDeathListener;
import org.blz.adminmode.listeners.PlayerSessionListener;
import org.blz.adminmode.moderation.ModeManager;
import org.blz.adminmode.moderation.ModeSessionLogger;
import org.blz.adminmode.session.SessionStorage;
import org.bukkit.plugin.java.JavaPlugin;
public class AdminMode extends JavaPlugin {
private static AdminMode instance;
private ConfigManager configManager;
private ModeManager modeManager;
private AdminModeCommand adminModeCommand;
private AdminDialogService dialogService;
@@ -21,29 +29,40 @@ public class AdminMode extends JavaPlugin {
saveDefaultConfig();
configManager = new ConfigManager(this);
adminModeCommand = new AdminModeCommand(this, configManager);
SessionStorage sessionStorage = new SessionStorage(this);
LuckPermsIntegration luckPermsIntegration = new LuckPermsIntegration(this, configManager);
CoreProtectIntegration coreProtectIntegration = new CoreProtectIntegration(this, configManager);
ModeSessionLogger sessionLogger = new ModeSessionLogger(this, configManager);
modeManager = new ModeManager(this, configManager, luckPermsIntegration, sessionStorage, sessionLogger);
adminModeCommand = new AdminModeCommand(configManager, modeManager, coreProtectIntegration);
dialogService = new AdminDialogService(this, configManager, adminModeCommand);
adminModeCommand.setDialogService(dialogService);
getCommand("adminmode").setExecutor(adminModeCommand);
getCommand("admfix").setExecutor(new org.blz.adminmode.commands.AdminFixCommand(adminModeCommand));
getServer().getPluginManager().registerEvents(new AdminModeListener(this, adminModeCommand, configManager),
getServer().getPluginManager().registerEvents(new AdminModeListener(adminModeCommand, configManager, modeManager, dialogService),
this);
getServer().getPluginManager().registerEvents(new MobTargetListener(adminModeCommand, configManager), this);
getServer().getPluginManager().registerEvents(new GameModeChangeListener(adminModeCommand, configManager),
this);
getServer().getPluginManager().registerEvents(new PlayerSessionListener(adminModeCommand, modeManager), this);
getServer().getPluginManager().registerEvents(new PlayerDeathListener(modeManager), this);
for (org.bukkit.entity.Player onlinePlayer : getServer().getOnlinePlayers()) {
if (modeManager.hasSession(onlinePlayer.getUniqueId())) {
getServer().getScheduler().runTaskLater(this, () -> adminModeCommand.recoverSession(onlinePlayer), 1L);
}
}
// Load Pocket Dimension World
new org.blz.adminmode.utils.WorldLoader(this, "pocket_dimension").loadWorld();
// Register Abilities
belzeManager = new org.blz.adminmode.managers.BelzeBoolManager(this);
new org.blz.adminmode.managers.BelzeBoolManager(this);
getLogger().info("AdminMode успешно включен!");
}
private org.blz.adminmode.managers.BelzeBoolManager belzeManager; // Field to store manager
@Override
public void onDisable() {
getLogger().info("AdminMode отключен!");
@@ -57,6 +76,10 @@ public class AdminMode extends JavaPlugin {
return configManager;
}
public ModeManager getModeManager() {
return modeManager;
}
public AdminModeCommand getAdminModeCommand() {
return adminModeCommand;
}

View File

@@ -1,47 +1,36 @@
package org.blz.adminmode.commands;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node;
import java.util.List;
import java.util.UUID;
import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.dialog.AdminDialogService;
import org.blz.adminmode.integrations.CoreProtectIntegration;
import org.blz.adminmode.moderation.ModeManager;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.blz.adminmode.moderation.PlayerListAction;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.blz.adminmode.dialog.AdminDialogService;
import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.data.AdminModeDataManager;
import org.blz.adminmode.data.AdminModeDataManager.PlayerStateData;
import org.blz.adminmode.moderation.ModeratorProfile;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.kyori.adventure.text.Component;
public class AdminModeCommand implements CommandExecutor {
private final Plugin plugin;
private final ConfigManager configManager;
private final Map<UUID, PlayerState> savedStates = new HashMap<>();
private final Map<UUID, ModeratorProfile> activeProfiles = new HashMap<>();
private final AdminModeDataManager dataManager;
private final ModeManager modeManager;
private final CoreProtectIntegration coreProtectIntegration;
private AdminDialogService dialogService;
public AdminModeCommand(Plugin plugin, ConfigManager configManager) {
this.plugin = plugin;
public AdminModeCommand(
ConfigManager configManager,
ModeManager modeManager,
CoreProtectIntegration coreProtectIntegration) {
this.configManager = configManager;
this.dataManager = new AdminModeDataManager(plugin);
loadAllStates();
this.modeManager = modeManager;
this.coreProtectIntegration = coreProtectIntegration;
}
public void setDialogService(AdminDialogService dialogService) {
@@ -52,23 +41,22 @@ public class AdminModeCommand implements CommandExecutor {
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase("reload")) {
if (!sender.hasPermission("adminmode.reload")) {
sender.sendMessage(ChatColor.RED + configManager.getMsgNoPermission());
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return true;
}
configManager.loadConfig();
sender.sendMessage(ChatColor.GREEN + configManager.getMsgReloaded());
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgReloaded()));
return true;
}
// Команда disable <игрок> - принудительно отключить способности
if (args.length > 0 && args[0].equalsIgnoreCase("disable")) {
if (!canUseHardActions(sender)) {
sender.sendMessage(ChatColor.RED + configManager.getMsgNoPermission());
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return true;
}
if (args.length < 2) {
sender.sendMessage(ChatColor.RED + "Использование: /adminmode disable <игрок>");
sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode disable <игрок>"));
return true;
}
@@ -76,132 +64,121 @@ public class AdminModeCommand implements CommandExecutor {
return true;
}
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "Эта команда доступна только игрокам!");
if (!(sender instanceof Player player)) {
sender.sendMessage("Эта команда доступна только игрокам!");
return true;
}
Player player = (Player) sender;
if (!configManager.isEnabled()) {
player.sendMessage(ChatColor.RED + "Режим администратора отключен!");
player.sendMessage(configManager.getPrefixedMessage("§cРежим администратора отключен!"));
return true;
}
if (args.length > 0 && args[0].equalsIgnoreCase("dialog")) {
if (!openRelevantDialog(player)) {
player.sendMessage(ChatColor.YELLOW + "Dialog-панель недоступна. Используйте /adminmode enable <soft|hard>.");
player.sendMessage(configManager.getPrefixedMessage("§eDialog-панель сейчас недоступна."));
}
return true;
}
if (args.length > 0 && (args[0].equalsIgnoreCase("enable") || args[0].equalsIgnoreCase("profile"))) {
if (args.length < 2) {
if (!openRelevantDialog(player)) {
player.sendMessage(ChatColor.YELLOW + "Использование: /adminmode enable <soft|hard>");
}
openRelevantDialog(player);
return true;
}
ModeratorProfile profile = ModeratorProfile.fromInput(args[1]).orElse(null);
if (profile == null) {
player.sendMessage(ChatColor.RED + "Неизвестный профиль. Доступно: soft, hard.");
player.sendMessage(configManager.getPrefixedMessage("§cНеизвестный профиль. Доступно: soft, hard."));
return true;
}
toggleAdminMode(player, profile);
activateProfile(player, profile);
return true;
}
if (!isInAdminMode(player.getUniqueId()) && getAvailableProfiles(player).isEmpty()) {
player.sendMessage(ChatColor.RED + configManager.getMsgNoPermission());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return true;
}
toggleAdminMode(player);
openRelevantDialog(player);
return true;
}
public void toggleAdminMode(Player player) {
if (isInAdminMode(player.getUniqueId())) {
disableAdminMode(player);
return;
}
List<ModeratorProfile> availableProfiles = getAvailableProfiles(player);
if (availableProfiles.isEmpty()) {
player.sendMessage(ChatColor.RED + configManager.getMsgNoPermission());
return;
}
if (availableProfiles.size() == 1) {
enableAdminMode(player, availableProfiles.get(0));
return;
}
if (!openRelevantDialog(player)) {
player.sendMessage(ChatColor.YELLOW + "Доступно несколько профилей. Используйте /adminmode enable <soft|hard>.");
}
}
public void toggleAdminMode(Player player, ModeratorProfile profile) {
public boolean activateProfile(Player player, ModeratorProfile profile) {
if (profile == null) {
toggleAdminMode(player);
return;
}
if (isInAdminMode(player.getUniqueId())) {
ModeratorProfile activeProfile = getActiveProfile(player.getUniqueId());
if (activeProfile == profile) {
disableAdminMode(player);
} else {
player.sendMessage(ChatColor.YELLOW + "Сначала выключите текущий режим модерации, затем включите новый профиль.");
}
return;
return openRelevantDialog(player);
}
if (!profile.isAvailableFor(player)) {
player.sendMessage(ChatColor.RED + configManager.getMsgNoPermission());
return;
}
enableAdminMode(player, profile);
}
public List<ModeratorProfile> getAvailableProfiles(Player player) {
return ModeratorProfile.availableFor(player);
}
public ModeratorProfile getActiveProfile(UUID playerId) {
return activeProfiles.get(playerId);
}
public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean godMode) {
if (!isInAdminMode(player.getUniqueId())) {
return;
}
player.setAllowFlight(true);
player.setFlying(true);
player.setFlySpeed(clampSpeed(flySpeed, 0.1f));
player.setWalkSpeed(clampSpeed(walkSpeed, 0.1f));
player.setInvulnerable(godMode);
player.sendMessage(ChatColor.GREEN + "✓ Настройки admin mode обновлены.");
}
public boolean teleportToPlayer(Player moderator, String targetName) {
if (!validateHardActionPlayer(moderator)) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return false;
}
Player target = resolveOnlinePlayer(targetName, moderator);
if (modeManager.hasSession(player.getUniqueId())) {
ModeratorProfile activeProfile = getActiveProfile(player.getUniqueId());
if (activeProfile == profile) {
return openRelevantDialog(player);
}
player.sendMessage(configManager.getPrefixedMessage(
"§eУ вас уже активен профиль §f" + activeProfile.getDisplayName() + "§e. Сначала выйдите из режима."));
return false;
}
return modeManager.enterMode(player, profile);
}
public boolean exitMode(Player player) {
return modeManager.exitMode(player);
}
public boolean recoverSession(Player player) {
return modeManager.recoverSession(player);
}
public List<ModeratorProfile> getAvailableProfiles(Player player) {
return modeManager.getAvailableProfiles(player);
}
public ModeratorProfile getActiveProfile(UUID playerId) {
return modeManager.getProfile(playerId);
}
public boolean isInAdminMode(UUID playerId) {
return modeManager.hasSession(playerId);
}
public List<Component> getOnlineSummary(Player player) {
return modeManager.buildOnlineSummary(player);
}
public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean godMode) {
if (!modeManager.hasSession(player.getUniqueId())) {
return;
}
modeManager.applyAdminSettings(player, flySpeed, walkSpeed, godMode);
player.sendMessage(configManager.getPrefixedMessage("§aНастройки режима обновлены."));
}
public boolean performPlayerListAction(Player moderator, PlayerListAction action, String targetName) {
Player target = validateActiveTarget(moderator, targetName);
if (target == null) {
return false;
}
moderator.teleport(target.getLocation());
moderator.sendMessage(ChatColor.GREEN + "✓ Вы телепортированы к игроку " + target.getName());
return true;
return switch (action) {
case SPECTATE -> modeManager.spectatePlayer(moderator, target);
case TELEPORT -> modeManager.teleportToPlayer(moderator, target);
case INVSEE -> modeManager.openInvsee(moderator, target);
};
}
public boolean teleportToPlayer(Player moderator, String targetName) {
Player target = validateActiveTarget(moderator, targetName);
return target != null && modeManager.teleportToPlayer(moderator, target);
}
public boolean teleportPlayerHere(Player moderator, String targetName) {
@@ -210,19 +187,12 @@ public class AdminModeCommand implements CommandExecutor {
}
Player target = resolveOnlinePlayer(targetName, moderator);
if (target == null) {
return false;
}
target.teleport(moderator.getLocation());
moderator.sendMessage(ChatColor.GREEN + "✓ Игрок " + target.getName() + " телепортирован к вам.");
target.sendMessage(ChatColor.YELLOW + "Вас телепортировал модератор " + moderator.getName());
return true;
return target != null && modeManager.teleportPlayerHere(moderator, target);
}
public boolean forceDisableTarget(CommandSender sender, String targetName) {
if (!canUseHardActions(sender)) {
sender.sendMessage(ChatColor.RED + configManager.getMsgNoPermission());
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return false;
}
@@ -232,296 +202,81 @@ public class AdminModeCommand implements CommandExecutor {
}
forceDisableAbilities(target);
sender.sendMessage(ChatColor.GREEN + "Все способности отключены для игрока " + target.getName());
target.sendMessage(ChatColor.YELLOW + "Ваши способности были сброшены модератором");
sender.sendMessage(configManager.getPrefixedMessage("§aРежим и способности игрока §f" + target.getName() + "§a сброшены."));
target.sendMessage(configManager.getPrefixedMessage("§eВаш режим модерации был принудительно отключен."));
return true;
}
public boolean executeCPLookup(Player player, String targetPlayer, int radius, String time) {
if (!validateHardActionPlayer(player)) {
return false;
}
return coreProtectIntegration.lookup(player, targetPlayer, radius, time);
}
public boolean executeCPRollback(Player player, int radius, String time, String targetPlayer) {
if (!validateHardActionPlayer(player)) {
return false;
}
return coreProtectIntegration.rollback(player, radius, time, targetPlayer);
}
public boolean toggleInspector(Player player) {
if (!validateHardActionPlayer(player)) {
return false;
}
return coreProtectIntegration.toggleInspector(player);
}
public void forceDisableAbilities(Player player) {
if (modeManager.hasSession(player.getUniqueId())) {
modeManager.exitMode(player);
return;
}
player.setInvulnerable(false);
player.setFlying(false);
player.setAllowFlight(false);
player.setFlySpeed(0.1f);
player.setWalkSpeed(0.2f);
}
public boolean canUseHardActions(CommandSender sender) {
return modeManager.canUseHardActions(sender);
}
private boolean openRelevantDialog(Player player) {
if (dialogService == null || !configManager.isDialogsEnabled()) {
return false;
}
if (isInAdminMode(player.getUniqueId())) {
return dialogService.openModerationPanel(player);
if (modeManager.hasSession(player.getUniqueId())) {
return dialogService.openCurrentPanel(player);
}
return dialogService.openProfileSelectionDialog(player);
}
private void enableAdminMode(Player player, ModeratorProfile profile) {
savePlayerState(player, profile);
activeProfiles.put(player.getUniqueId(), profile);
clearPlayerState(player);
givePresetBlocks(player);
setAdminGameMode(player);
savePlayerStateToFile(player);
grantLuckPermsPermissions(player, profile);
player.sendMessage(ChatColor.AQUA + configManager.getMsgEnabled());
player.sendMessage(ChatColor.GRAY + configManager.getMsgEnabledSubtitle());
player.sendMessage(ChatColor.YELLOW + configManager.getMsgPermissionsGranted());
player.sendMessage(ChatColor.GRAY + "Профиль: " + profile.getDisplayName());
}
private void disableAdminMode(Player player) {
UUID playerId = player.getUniqueId();
ModeratorProfile profile = activeProfiles.getOrDefault(playerId, ModeratorProfile.SOFT_MODER);
removeLuckPermsPermissions(player, profile);
restorePlayerState(player);
dataManager.deletePlayerState(playerId);
player.sendMessage(ChatColor.GREEN + configManager.getMsgDisabled());
player.sendMessage(ChatColor.GRAY + configManager.getMsgDisabledSubtitle());
}
private void savePlayerState(Player player, ModeratorProfile profile) {
PlayerState state = new PlayerState();
state.inventory = player.getInventory().getContents().clone();
state.armorContents = player.getInventory().getArmorContents().clone();
state.offHand = player.getInventory().getItemInOffHand().clone();
state.location = player.getLocation().clone();
state.health = player.getHealth();
state.foodLevel = player.getFoodLevel();
state.saturation = player.getSaturation();
state.exhaustion = player.getExhaustion();
state.exp = player.getExp();
state.level = player.getLevel();
state.totalExperience = player.getTotalExperience();
state.gameMode = player.getGameMode();
state.allowFlight = player.getAllowFlight();
state.flying = player.isFlying();
state.flySpeed = player.getFlySpeed();
state.walkSpeed = player.getWalkSpeed();
state.potionEffects = player.getActivePotionEffects();
state.fireTicks = player.getFireTicks();
state.profile = profile;
savedStates.put(player.getUniqueId(), state);
}
private void clearPlayerState(Player player) {
player.getInventory().clear();
player.getInventory().setArmorContents(new ItemStack[4]);
player.getInventory().setItemInOffHand(new ItemStack(Material.AIR));
player.setHealth(20.0);
player.setFoodLevel(20);
player.setSaturation(5.0f);
player.setExhaustion(0.0f);
player.setExp(0);
player.setLevel(0);
player.setTotalExperience(0);
for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
player.setFireTicks(0);
}
private void givePresetBlocks(Player player) {
if (!configManager.isGivePresetBlocks())
return;
List<String> presetBlocks = configManager.getPresetBlocks();
for (String blockStr : presetBlocks) {
try {
String[] parts = blockStr.split(":");
Material material = Material.valueOf(parts[0].toUpperCase());
int amount = parts.length > 1 ? Integer.parseInt(parts[1]) : 64;
ItemStack item = new ItemStack(material, amount);
player.getInventory().addItem(item);
} catch (Exception e) {
plugin.getLogger().warning("Неверный блок в preset_blocks: " + blockStr);
}
}
}
private void setAdminGameMode(Player player) {
if (configManager.isForceSpectator()) {
player.setGameMode(GameMode.SPECTATOR);
} else {
// ВАЖНО: Не используем CREATIVE для модеров, только SURVIVAL!
player.setGameMode(GameMode.SURVIVAL);
}
// Всегда разрешаем полет в админ моде (даже в выживании)
player.setAllowFlight(true);
player.setFlying(true);
// Дополнительные возможности
if (configManager.isGodMode()) {
player.setInvulnerable(true);
}
if (configManager.getCustomFlySpeed() > 0) {
player.setFlySpeed(configManager.getCustomFlySpeed());
}
if (configManager.getCustomWalkSpeed() > 0) {
player.setWalkSpeed(configManager.getCustomWalkSpeed());
}
}
private void savePlayerStateToFile(Player player) {
UUID playerId = player.getUniqueId();
PlayerState state = savedStates.get(playerId);
if (state == null)
return;
PlayerStateData data = new PlayerStateData();
data.inventory = state.inventory;
data.armorContents = state.armorContents;
data.offHand = state.offHand;
data.location = state.location;
data.health = state.health;
data.foodLevel = state.foodLevel;
data.saturation = state.saturation;
data.exhaustion = state.exhaustion;
data.exp = state.exp;
data.level = state.level;
data.totalExperience = state.totalExperience;
data.gameMode = state.gameMode;
data.allowFlight = state.allowFlight;
data.flying = state.flying;
data.flySpeed = state.flySpeed;
data.walkSpeed = state.walkSpeed;
data.potionEffects = state.potionEffects;
data.fireTicks = state.fireTicks;
data.profileKey = state.profile == null ? null : state.profile.getConfigKey();
dataManager.savePlayerState(playerId, data);
}
private void loadAllStates() {
for (UUID playerId : dataManager.getAllSavedStates()) {
PlayerStateData data = dataManager.loadPlayerState(playerId);
if (data == null)
continue;
PlayerState state = new PlayerState();
state.inventory = data.inventory;
state.armorContents = data.armorContents;
state.offHand = data.offHand;
state.location = data.location;
state.health = data.health;
state.foodLevel = data.foodLevel;
state.saturation = data.saturation;
state.exhaustion = data.exhaustion;
state.exp = data.exp;
state.level = data.level;
state.totalExperience = data.totalExperience;
state.gameMode = data.gameMode;
state.allowFlight = data.allowFlight;
state.flying = data.flying;
state.flySpeed = data.flySpeed;
state.walkSpeed = data.walkSpeed;
state.potionEffects = data.potionEffects;
state.fireTicks = data.fireTicks;
state.profile = ModeratorProfile.fromConfigKey(data.profileKey).orElse(ModeratorProfile.SOFT_MODER);
savedStates.put(playerId, state);
activeProfiles.put(playerId, state.profile);
plugin.getLogger().info("Загружено состояние админ мода для: " + playerId);
}
}
private void restorePlayerState(Player player) {
UUID playerId = player.getUniqueId();
PlayerState state = savedStates.get(playerId);
if (state == null)
return;
// КРИТИЧНО: Удаляем из savedStates ДО восстановления режима!
// Это предотвратит срабатывание GameModeChangeListener, который мог бы снова
// включить полет
savedStates.remove(playerId);
activeProfiles.remove(playerId);
// Восстанавливаем инвентарь
player.getInventory().setContents(state.inventory);
player.getInventory().setArmorContents(state.armorContents);
player.getInventory().setItemInOffHand(state.offHand);
if (state.location != null) {
player.teleport(state.location);
}
player.setHealth(state.health);
player.setFoodLevel(state.foodLevel);
player.setSaturation(state.saturation);
player.setExhaustion(state.exhaustion);
player.setExp(state.exp);
player.setLevel(state.level);
player.setTotalExperience(state.totalExperience);
// ВАЖНО: Сначала восстанавливаем gameMode
player.setGameMode(state.gameMode);
// КРИТИЧНО: ПОТОМ применяем железную очистку способностей!
// Это гарантирует, что даже в SPECTATOR режиме у игрока не будет полета
forceDisableAbilities(player);
for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
for (PotionEffect effect : state.potionEffects) {
player.addPotionEffect(effect);
}
player.setFireTicks(state.fireTicks);
}
/**
* Железное отключение всех админ способностей
* Используется при выходе из режима и в команде /admfix
*/
public void forceDisableAbilities(Player player) {
player.setInvulnerable(false); // Убираем бессмертие
player.setFlying(false); // Принудительно отключаем полет
player.setAllowFlight(false); // Запрещаем полет
player.setFlySpeed(0.1f); // Сброс скорости полета на дефолт
player.setWalkSpeed(0.2f); // Сброс скорости ходьбы на дефолт
}
public boolean isInAdminMode(UUID playerId) {
return savedStates.containsKey(playerId);
}
private boolean canUseHardActions(CommandSender sender) {
if (sender.hasPermission("adminmode.admin")) {
return true;
}
if (!(sender instanceof Player player)) {
return false;
}
return isInAdminMode(player.getUniqueId()) && getActiveProfile(player.getUniqueId()) == ModeratorProfile.HARD_MODER;
return dialogService.openModeSelectDialog(player);
}
private boolean validateHardActionPlayer(Player moderator) {
if (!canUseHardActions(moderator)) {
moderator.sendMessage(ChatColor.RED + "Для этого действия нужен активный профиль Hard Moder.");
moderator.sendMessage(configManager.getPrefixedMessage("§cДля этого действия нужен активный профиль Hard Moder."));
return false;
}
return true;
}
private Player validateActiveTarget(Player moderator, String targetName) {
if (!modeManager.hasSession(moderator.getUniqueId())) {
moderator.sendMessage(configManager.getPrefixedMessage("§cСначала войдите в режим модерации."));
return null;
}
return resolveOnlinePlayer(targetName, moderator);
}
private Player resolveOnlinePlayer(String targetName, CommandSender sender) {
if (targetName == null || targetName.isBlank()) {
sender.sendMessage(ChatColor.RED + "Укажите ник игрока.");
sender.sendMessage(configManager.getPrefixedMessage("§cУкажите ник игрока."));
return null;
}
@@ -531,102 +286,10 @@ public class AdminModeCommand implements CommandExecutor {
}
if (target == null) {
sender.sendMessage(ChatColor.RED + "Игрок не найден!");
sender.sendMessage(configManager.getPrefixedMessage("§cИгрок не найден!"));
return null;
}
return target;
}
private float clampSpeed(float value, float min) {
return Math.max(min, Math.min(1.0f, value));
}
private void grantLuckPermsPermissions(Player player, ModeratorProfile profile) {
if (!configManager.isUseLuckPerms()) {
player.setOp(true);
return;
}
try {
LuckPerms luckPerms = LuckPermsProvider.get();
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) {
player.sendMessage(ChatColor.RED + "Не удалось получить данные LuckPerms!");
return;
}
List<String> groups = configManager.getLuckPermsGroups(profile);
for (String group : groups) {
Node groupNode = Node.builder("group." + group).build();
user.data().add(groupNode);
}
luckPerms.getUserManager().saveUser(user);
player.sendMessage(ChatColor.GREEN + "✓ Права администратора выданы!");
} catch (IllegalStateException e) {
player.setOp(true);
player.sendMessage(ChatColor.YELLOW + "⚠ LuckPerms не найден, используется стандартный OP");
}
}
private void removeLuckPermsPermissions(Player player, ModeratorProfile profile) {
if (!configManager.isUseLuckPerms()) {
player.setOp(false);
return;
}
if (!configManager.isRemoveGroupsOnDisable()) {
return;
}
try {
LuckPerms luckPerms = LuckPermsProvider.get();
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) {
player.sendMessage(ChatColor.RED + "Не удалось получить данные LuckPerms!");
return;
}
List<String> groups = configManager.getLuckPermsGroups(profile);
for (String group : groups) {
Node groupNode = Node.builder("group." + group).build();
user.data().remove(groupNode);
}
luckPerms.getUserManager().saveUser(user);
player.sendMessage(ChatColor.GREEN + configManager.getMsgPermissionsRemoved());
} catch (IllegalStateException e) {
player.setOp(false);
player.sendMessage(ChatColor.YELLOW + "⚠ LuckPerms не найден, убран стандартный OP");
}
}
private static class PlayerState {
ItemStack[] inventory;
ItemStack[] armorContents;
ItemStack offHand;
Location location;
double health;
int foodLevel;
float saturation;
float exhaustion;
float exp;
int level;
int totalExperience;
GameMode gameMode;
boolean allowFlight;
boolean flying;
float flySpeed;
float walkSpeed;
Collection<PotionEffect> potionEffects;
int fireTicks;
ModeratorProfile profile;
}
}

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.Plugin;
@@ -19,6 +20,17 @@ public class ConfigManager {
private Map<ModeratorProfile, List<String>> luckPermsGroups;
private boolean removeGroupsOnDisable;
private boolean dialogsEnabled;
private String baseGroup;
private String softGroup;
private String hardGroup;
private Material softPanelMaterial;
private Material hardPanelMaterial;
private Material cpInspectorMaterial;
private boolean coreProtectEnabled;
private int coreProtectDefaultRadius;
private String coreProtectDefaultTime;
private boolean sessionLoggingEnabled;
private String sessionLogFile;
private boolean preventItemDrop;
private boolean preventItemPickup;
@@ -50,6 +62,12 @@ public class ConfigManager {
private String msgItemUseDenied;
private String msgBlockPlaceDenied;
private String msgReloaded;
private String msgPrefix;
private String msgEnterSoft;
private String msgEnterHard;
private String msgExitMode;
private String msgCoreProtectUnavailable;
private String msgSessionRecovered;
public ConfigManager(Plugin plugin) {
this.plugin = plugin;
@@ -66,14 +84,37 @@ public class ConfigManager {
removeGroupsOnDisable = config.getBoolean("admin_mode.luckperms.remove_on_disable", true);
dialogsEnabled = config.getBoolean("admin_mode.dialogs.enabled", true);
baseGroup = config.getString("groups.base", "moder");
softGroup = config.getString("groups.soft", "soft_moder_adm");
hardGroup = config.getString("groups.hard", "hard_moder_adm");
softPanelMaterial = readMaterial("items.soft_panel_material", Material.COMPASS);
hardPanelMaterial = readMaterial("items.hard_panel_material", Material.NETHER_STAR);
cpInspectorMaterial = readMaterial("items.cp_inspector_material", Material.STICK);
coreProtectEnabled = config.getBoolean("coreprotect.enabled", true);
coreProtectDefaultRadius = config.getInt("coreprotect.default_radius", 10);
coreProtectDefaultTime = config.getString("coreprotect.default_time", "24h");
sessionLoggingEnabled = config.getBoolean("logging.enabled", true);
sessionLogFile = config.getString("logging.log_file", "logs/sessions.log");
List<String> legacyGroups = config.getStringList("admin_mode.luckperms.groups");
List<String> softGroups = config.getStringList("admin_mode.luckperms.profile_groups.soft_moder");
List<String> hardGroups = config.getStringList("admin_mode.luckperms.profile_groups.hard_moder");
if (softGroups.isEmpty()) {
if (!legacyGroups.isEmpty()) {
softGroups = new ArrayList<>(legacyGroups);
} else {
softGroups = new ArrayList<>(List.of(softGroup));
}
}
if (hardGroups.isEmpty()) {
hardGroups = legacyGroups.isEmpty() ? new ArrayList<>(softGroups) : new ArrayList<>(legacyGroups);
if (!legacyGroups.isEmpty()) {
hardGroups = new ArrayList<>(legacyGroups);
} else {
hardGroups = new ArrayList<>(List.of(hardGroup));
}
}
luckPermsGroups = new EnumMap<>(ModeratorProfile.class);
luckPermsGroups.put(ModeratorProfile.SOFT_MODER, new ArrayList<>(softGroups));
@@ -117,6 +158,15 @@ public class ConfigManager {
msgBlockPlaceDenied = config.getString("admin_mode.messages.block_place_denied",
"✖ Вы можете ставить только разрешенные блоки!");
msgReloaded = config.getString("admin_mode.messages.reloaded", "✓ Конфигурация AdminMode перезагружена!");
msgPrefix = config.getString("messages.prefix", "§8[§bAdminMode§8] ");
msgEnterSoft = config.getString("messages.enter_soft", "§aВошёл в режим §bSOFT MODER");
msgEnterHard = config.getString("messages.enter_hard", "§aВошёл в режим §cHARD MODER");
msgExitMode = config.getString("messages.exit", "§7Режим деактивирован. Состояние восстановлено.");
msgCoreProtectUnavailable = config.getString("messages.cp_unavailable",
"§cCoreProtect не найден. Hard режим работает без CP.");
msgSessionRecovered = config.getString("messages.session_recovered",
"§eВаша активная сессия модерации была восстановлена и корректно завершена.");
}
public boolean isEnabled() {
@@ -143,6 +193,54 @@ public class ConfigManager {
return dialogsEnabled;
}
public String getBaseGroup() {
return baseGroup;
}
public String getSoftGroup() {
return softGroup;
}
public String getHardGroup() {
return hardGroup;
}
public String getActiveGroup(ModeratorProfile profile) {
return profile != null && profile.isHardProfile() ? hardGroup : softGroup;
}
public Material getSoftPanelMaterial() {
return softPanelMaterial;
}
public Material getHardPanelMaterial() {
return hardPanelMaterial;
}
public Material getCpInspectorMaterial() {
return cpInspectorMaterial;
}
public boolean isCoreProtectEnabled() {
return coreProtectEnabled;
}
public int getCoreProtectDefaultRadius() {
return coreProtectDefaultRadius;
}
public String getCoreProtectDefaultTime() {
return coreProtectDefaultTime;
}
public boolean isSessionLoggingEnabled() {
return sessionLoggingEnabled;
}
public String getSessionLogFile() {
return sessionLogFile;
}
public boolean isPreventItemDrop() {
return preventItemDrop;
}
@@ -254,4 +352,49 @@ public class ConfigManager {
public String getMsgReloaded() {
return msgReloaded;
}
public String getMsgPrefix() {
return msgPrefix;
}
public String getMsgEnterSoft() {
return msgEnterSoft;
}
public String getMsgEnterHard() {
return msgEnterHard;
}
public String getMsgExitMode() {
return msgExitMode;
}
public String getMsgCoreProtectUnavailable() {
return msgCoreProtectUnavailable;
}
public String getMsgSessionRecovered() {
return msgSessionRecovered;
}
public String getPrefixedMessage(String message) {
if (message == null || message.isEmpty()) {
return msgPrefix;
}
return msgPrefix + message;
}
private Material readMaterial(String path, Material fallback) {
String value = config.getString(path);
if (value == null || value.isBlank()) {
return fallback;
}
try {
return Material.valueOf(value.trim().toUpperCase());
} catch (IllegalArgumentException exception) {
plugin.getLogger().warning("Неизвестный material в конфиге " + path + ": " + value);
return fallback;
}
}
}

View File

@@ -13,6 +13,9 @@ import net.kyori.adventure.text.event.ClickCallback;
import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.blz.adminmode.moderation.PlayerListAction;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
@@ -21,6 +24,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@SuppressWarnings("UnstableApiUsage")
public class AdminDialogService {
private static final ClickCallback.Options CALLBACK_OPTIONS = ClickCallback.Options.builder()
@@ -38,19 +42,19 @@ public class AdminDialogService {
this.adminModeCommand = adminModeCommand;
}
public boolean openProfileSelectionDialog(Player player) {
public boolean openModeSelectDialog(Player player) {
if (!configManager.isDialogsEnabled()) {
return false;
}
List<ModeratorProfile> profiles = adminModeCommand.getAvailableProfiles(player);
if (profiles.isEmpty()) {
player.sendMessage(configManager.getMsgNoPermission());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
return false;
}
if (profiles.size() == 1) {
adminModeCommand.toggleAdminMode(player, profiles.get(0));
adminModeCommand.activateProfile(player, profiles.get(0));
return true;
}
@@ -58,31 +62,37 @@ public class AdminDialogService {
List<ActionButton> buttons = new ArrayList<>();
for (ModeratorProfile profile : profiles) {
buttons.add(actionButton(
profile.getDisplayName(),
profile.isHardProfile() ? "🔨 Hard Moder" : "🔍 Soft Moder",
profile.isHardProfile()
? "Полный режим модерации с сильными действиями"
: "Базовый режим модерации для повседневной работы",
? "Полный режим модерации, CoreProtect и сильные действия"
: "Наблюдение, spectate, invsee и безопасные действия",
(response, audience) -> withPlayer(audience, moderator -> {
adminModeCommand.toggleAdminMode(moderator, profile);
adminModeCommand.activateProfile(moderator, profile);
plugin.getServer().getScheduler().runTask(plugin, () -> {
if (adminModeCommand.isInAdminMode(moderator.getUniqueId())) {
openModerationPanel(moderator);
openCurrentPanel(moderator);
}
});
})));
}
DialogBase base = DialogBase.builder(Component.text("Выбор профиля модерации"))
buttons.add(actionButton(
"Закрыть",
"Закрыть выбор режима",
(response, audience) -> {
}));
DialogBase base = DialogBase.builder(Component.text("⚙ Режим модерации"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(Component.text(
"Выберите профиль, с которым хотите войти в admin mode. Soft — для обычной модерации, Hard — для телепортов и сильных действий."),
"Выберите режим входа. Soft — наблюдение и безопасная модерация. Hard — полный контроль, телепорты и CoreProtect."),
360)))
.inputs(List.of())
.build();
showDialog(player, base, DialogType.multiAction(buttons, closeButton(), 2));
showDialog(player, base, DialogType.multiAction(buttons, closeButton(), 1));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть dialog выбора профиля: " + exception.getMessage());
@@ -90,7 +100,21 @@ public class AdminDialogService {
}
}
public boolean openModerationPanel(Player player) {
public boolean openCurrentPanel(Player player) {
UUID playerId = player.getUniqueId();
ModeratorProfile profile = adminModeCommand.getActiveProfile(playerId);
if (profile == null) {
return false;
}
if (profile.isHardProfile()) {
return openHardPanel(player);
}
return openSoftPanel(player);
}
public boolean openSoftPanel(Player player) {
if (!configManager.isDialogsEnabled()) {
return false;
}
@@ -100,70 +124,194 @@ public class AdminDialogService {
}
try {
UUID playerId = player.getUniqueId();
ModeratorProfile profile = adminModeCommand.getActiveProfile(playerId);
boolean hardProfile = profile != null && profile.isHardProfile();
List<ActionButton> buttons = new ArrayList<>();
buttons.add(actionButton(
"Скорости и режим",
"Открыть слайдеры скорости и переключатель бессмертия",
"👁 Spectate игрока",
"Выбрать игрока для режима наблюдения",
(response, audience) -> withPlayer(audience, moderator -> schedule(
() -> openPlayerListDialog(moderator, PlayerListAction.SPECTATE)))));
buttons.add(actionButton(
"📍 TP к игроку",
"Выбрать игрока для телепортации",
(response, audience) -> withPlayer(audience, moderator -> schedule(
() -> openPlayerListDialog(moderator, PlayerListAction.TELEPORT)))));
buttons.add(actionButton(
"🎒 Invsee игрока",
"Открыть инвентарь игрока только для просмотра",
(response, audience) -> withPlayer(audience, moderator -> schedule(
() -> openPlayerListDialog(moderator, PlayerListAction.INVSEE)))));
buttons.add(actionButton(
"📋 Список онлайн",
"Показать онлайн-игроков и их координаты",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openOnlineListDialog(moderator)))));
buttons.add(actionButton(
"🎛 Настройки режима",
"Скорость полёта, ходьбы и бессмертие",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openSpeedPanel(moderator)))));
buttons.add(actionButton(
"Выключить admin mode",
"🚪 Выйти из режима",
"Восстановить сохраненное состояние и снять временные права",
(response, audience) -> withPlayer(audience, adminModeCommand::toggleAdminMode)));
if (hardProfile) {
buttons.add(actionButton(
"Телепорт к игроку",
"Телепортировать себя к цели из поля ниже",
(response, audience) -> withPlayer(audience, moderator -> {
String targetName = normalizeTarget(response.getText("target_player"));
adminModeCommand.teleportToPlayer(moderator, targetName);
})));
buttons.add(actionButton(
"Игрок ко мне",
"Телепортировать цель к себе",
(response, audience) -> withPlayer(audience, moderator -> {
String targetName = normalizeTarget(response.getText("target_player"));
adminModeCommand.teleportPlayerHere(moderator, targetName);
})));
buttons.add(actionButton(
"Сбросить способности",
"Отключить полет, бессмертие и скорости у цели",
(response, audience) -> withPlayer(audience, moderator -> {
String targetName = normalizeTarget(response.getText("target_player"));
adminModeCommand.forceDisableTarget(moderator, targetName);
})));
}
(response, audience) -> withPlayer(audience, moderator -> adminModeCommand.exitMode(moderator))));
List<Component> lines = new ArrayList<>();
lines.add(Component.text("Активный профиль: " + (profile == null ? "unknown" : profile.getDisplayName())));
lines.add(Component.text(hardProfile
? "Hard профиль открывает телепорты и сильные действия."
: "Soft профиль оставляет только безопасные действия и настройку режима."));
lines.add(Component.text("Для действий по цели используйте поле с ником игрока."));
lines.add(Component.text("Soft Moder предназначен для наблюдения, инвентарей и безопасной телепортации."));
lines.add(Component.text("Используйте компас в руке как panel item или /adminmode для повторного открытия."));
DialogBase base = DialogBase.builder(Component.text("Панель модератора"))
DialogBase base = DialogBase.builder(Component.text("🔍 Soft Moder"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(joinLines(lines), 360)))
.inputs(List.of(DialogInput.text(
"target_player",
320,
Component.text("Ник цели"),
true,
"",
16,
null)))
.inputs(List.of())
.build();
showDialog(player, base, DialogType.multiAction(buttons, closeButton(), 2));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть moderation dialog: " + exception.getMessage());
plugin.getLogger().warning("Не удалось открыть soft panel dialog: " + exception.getMessage());
return false;
}
}
public boolean openHardPanel(Player player) {
if (!configManager.isDialogsEnabled()) {
return false;
}
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return false;
}
try {
List<ActionButton> buttons = new ArrayList<>();
buttons.add(actionButton(
"👁 Spectate игрока",
"Выбрать игрока для режима наблюдения",
(response, audience) -> withPlayer(audience, moderator -> schedule(
() -> openPlayerListDialog(moderator, PlayerListAction.SPECTATE)))));
buttons.add(actionButton(
"📍 TP к игроку",
"Телепорт к выбранному игроку",
(response, audience) -> withPlayer(audience, moderator -> schedule(
() -> openPlayerListDialog(moderator, PlayerListAction.TELEPORT)))));
buttons.add(actionButton(
"🎒 Invsee игрока",
"Открыть инвентарь игрока только для просмотра",
(response, audience) -> withPlayer(audience, moderator -> schedule(
() -> openPlayerListDialog(moderator, PlayerListAction.INVSEE)))));
buttons.add(actionButton(
"📋 Список онлайн",
"Показать онлайн-игроков и координаты",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openOnlineListDialog(moderator)))));
buttons.add(actionButton(
"🔍 Lookup логи CP",
"Открыть форму поиска по логам CoreProtect",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openLookupDialog(moderator)))));
buttons.add(actionButton(
"↩ Откат области CP",
"Настроить rollback с подтверждением",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openRollbackDialog(moderator)))));
buttons.add(actionButton(
"🎛 Настройки режима",
"Скорость полёта, ходьбы и бессмертие",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openSpeedPanel(moderator)))));
buttons.add(actionButton(
"🚪 Выйти из режима",
"Восстановить сохраненное состояние и выйти из hard режима",
(response, audience) -> withPlayer(audience, moderator -> adminModeCommand.exitMode(moderator))));
List<Component> lines = new ArrayList<>();
lines.add(Component.text("Hard Moder включает полный доступ к сильным действиям и CoreProtect."));
lines.add(Component.text("Используйте Nether Star для панели и Stick для переключения co inspect."));
DialogBase base = DialogBase.builder(Component.text("🔨 Hard Moder"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(joinLines(lines), 360)))
.inputs(List.of())
.build();
showDialog(player, base, DialogType.multiAction(buttons, closeButton(), 2));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть hard panel dialog: " + exception.getMessage());
return false;
}
}
public boolean openPlayerListDialog(Player player, PlayerListAction action) {
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return false;
}
try {
List<ActionButton> buttons = new ArrayList<>();
for (Player target : Bukkit.getOnlinePlayers()) {
if (target.getUniqueId().equals(player.getUniqueId())) {
continue;
}
buttons.add(actionButton(
target.getName(),
target.getWorld().getName() + " | " + target.getLocation().getBlockX() + ", "
+ target.getLocation().getBlockY() + ", " + target.getLocation().getBlockZ(),
(response, audience) -> withPlayer(audience, moderator ->
adminModeCommand.performPlayerListAction(moderator, action, target.getName()))));
}
if (buttons.isEmpty()) {
buttons.add(actionButton(
"Нет целей",
"Кроме вас сейчас никого нет онлайн",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openCurrentPanel(moderator)))));
}
ActionButton backButton = actionButton(
"Назад",
"Вернуться к основной панели",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openCurrentPanel(moderator))));
DialogBase base = DialogBase.builder(Component.text(dialogTitleForAction(action)))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(Component.text(dialogBodyForAction(action)), 360)))
.inputs(List.of())
.build();
showDialog(player, base, DialogType.multiAction(buttons, backButton, 2));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть список игроков: " + exception.getMessage());
return false;
}
}
public boolean openOnlineListDialog(Player player) {
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return false;
}
try {
List<Component> lines = new ArrayList<>(adminModeCommand.getOnlineSummary(player));
ActionButton backButton = actionButton(
"Назад",
"Вернуться к панели режима",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openCurrentPanel(moderator))));
DialogBase base = DialogBase.builder(Component.text("📋 Онлайн список"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(joinLines(lines), 360)))
.inputs(List.of())
.build();
showDialog(player, base, DialogType.multiAction(List.of(), backButton, 1));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть онлайн список: " + exception.getMessage());
return false;
}
}
@@ -193,20 +341,20 @@ public class AdminDialogService {
flySpeed == null ? currentFly : flySpeed,
walkSpeed == null ? currentWalk : walkSpeed,
godMode != null && godMode);
schedule(() -> openModerationPanel(moderator));
schedule(() -> openCurrentPanel(moderator));
}));
ActionButton backButton = actionButton(
"Назад",
"Вернуться в основную панель модератора",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openModerationPanel(moderator))));
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openCurrentPanel(moderator))));
DialogBase base = DialogBase.builder(Component.text("Настройка admin mode"))
DialogBase base = DialogBase.builder(Component.text("🎛 Настройка режима"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(Component.text(
"Настройте полет, скорость передвижения и режим бессмертия через новые элементы dialog API."),
"Настройте скорость полёта, скорость ходьбы и бессмертие для текущей сессии модерации."),
360)))
.inputs(List.of(
DialogInput.numberRange(
@@ -243,7 +391,135 @@ public class AdminDialogService {
}
}
public boolean openLookupDialog(Player player) {
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return false;
}
try {
ActionButton lookupButton = actionButton(
"🔍 Найти",
"Выполнить co lookup с указанными параметрами",
(response, audience) -> withPlayer(audience, moderator -> {
String targetName = normalizeTarget(response.getText("cp_player"));
Integer radius = parseInteger(response.getText("cp_radius"), configManager.getCoreProtectDefaultRadius());
String time = normalizeTime(response.getText("cp_time"), configManager.getCoreProtectDefaultTime());
adminModeCommand.executeCPLookup(moderator, targetName, radius, time);
schedule(() -> openHardPanel(moderator));
}));
ActionButton backButton = actionButton(
"Назад",
"Вернуться в Hard Moder panel",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openHardPanel(moderator))));
DialogBase base = DialogBase.builder(Component.text("🔍 Поиск в логах CP"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(Component.text(
"Фильтр по игроку опционален. Радиус и время можно оставить стандартными."), 360)))
.inputs(List.of(
DialogInput.text("cp_player", 300, Component.text("Игрок (пусто = все)"), false, "", 16, null),
DialogInput.text("cp_radius", 180, Component.text("Радиус"), true,
String.valueOf(configManager.getCoreProtectDefaultRadius()), 3, null),
DialogInput.text("cp_time", 180, Component.text("Время"), true,
configManager.getCoreProtectDefaultTime(), 4, null)))
.build();
showDialog(player, base, DialogType.multiAction(List.of(lookupButton), backButton, 1));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть lookup dialog: " + exception.getMessage());
return false;
}
}
public boolean openRollbackDialog(Player player) {
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return false;
}
try {
ActionButton confirmButton = actionButton(
"✅ Далее к подтверждению",
"Открыть подтверждение rollback",
(response, audience) -> withPlayer(audience, moderator -> {
int radius = parseInteger(response.getText("rollback_radius"), configManager.getCoreProtectDefaultRadius());
String time = normalizeTime(response.getText("rollback_time"), configManager.getCoreProtectDefaultTime());
String targetPlayer = normalizeTarget(response.getText("rollback_player"));
schedule(() -> openRollbackConfirmDialog(moderator, radius, time, targetPlayer));
}));
ActionButton backButton = actionButton(
"Назад",
"Вернуться в Hard Moder panel",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openHardPanel(moderator))));
DialogBase base = DialogBase.builder(Component.text("↩ Rollback CoreProtect"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(Component.text(
"Укажите радиус, временной диапазон и при необходимости игрока. Затем подтвердите откат."), 360)))
.inputs(List.of(
DialogInput.text("rollback_radius", 180, Component.text("Радиус"), true,
String.valueOf(configManager.getCoreProtectDefaultRadius()), 3, null),
DialogInput.text("rollback_time", 180, Component.text("Время"), true,
configManager.getCoreProtectDefaultTime(), 4, null),
DialogInput.text("rollback_player", 300, Component.text("Игрок (опционально)"), false,
"", 16, null)))
.build();
showDialog(player, base, DialogType.multiAction(List.of(confirmButton), backButton, 1));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть rollback dialog: " + exception.getMessage());
return false;
}
}
public boolean openRollbackConfirmDialog(Player player, int radius, String time, String targetPlayer) {
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
return false;
}
try {
List<Component> lines = new ArrayList<>();
lines.add(Component.text("Радиус: " + radius + " блоков"));
lines.add(Component.text("Время: " + time));
lines.add(Component.text(targetPlayer.isEmpty() ? "Игрок: все" : "Игрок: " + targetPlayer));
ActionButton confirmButton = actionButton(
"✅ Откатить",
"Подтвердить rollback по указанным параметрам",
(response, audience) -> withPlayer(audience, moderator -> {
adminModeCommand.executeCPRollback(moderator, radius, time, targetPlayer);
schedule(() -> openHardPanel(moderator));
}));
ActionButton cancelButton = actionButton(
"❌ Отмена",
"Отменить rollback и вернуться назад",
(response, audience) -> withPlayer(audience, moderator -> schedule(() -> openRollbackDialog(moderator))));
DialogBase base = DialogBase.builder(Component.text("⚠ Подтвердить откат?"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.CLOSE)
.body(List.of(DialogBody.plainMessage(joinLines(lines), 360)))
.inputs(List.of())
.build();
showDialog(player, base, DialogType.multiAction(List.of(confirmButton, cancelButton), closeButton(), 1));
return true;
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось открыть подтверждение rollback: " + exception.getMessage());
return false;
}
}
private void showDialog(Player player, DialogBase base, io.papermc.paper.registry.data.dialog.type.DialogType type) {
player.closeInventory();
Dialog dialog = Dialog.create(factory -> {
var builder = factory.empty();
builder.base(base);
@@ -291,6 +567,46 @@ public class AdminDialogService {
return targetName == null ? "" : targetName.trim();
}
private String normalizeTime(String input, String fallback) {
if (input == null || input.isBlank()) {
return fallback;
}
String normalized = input.trim().toLowerCase();
if (normalized.matches("\\d+[smhdw]")) {
return normalized;
}
return fallback;
}
private Integer parseInteger(String value, int fallback) {
if (value == null || value.isBlank()) {
return fallback;
}
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException exception) {
return fallback;
}
}
private String dialogTitleForAction(PlayerListAction action) {
return switch (action) {
case SPECTATE -> "👁 Spectate игрока";
case TELEPORT -> "📍 TP к игроку";
case INVSEE -> "🎒 Invsee игрока";
};
}
private String dialogBodyForAction(PlayerListAction action) {
return switch (action) {
case SPECTATE -> "Выберите игрока, за которым хотите наблюдать.";
case TELEPORT -> "Выберите игрока, к которому хотите телепортироваться.";
case INVSEE -> "Выберите игрока, чей инвентарь хотите открыть.";
};
}
private float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}

View File

@@ -0,0 +1,92 @@
package org.blz.adminmode.integrations;
import org.blz.adminmode.config.ConfigManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class CoreProtectIntegration {
private final Plugin plugin;
private final ConfigManager configManager;
public CoreProtectIntegration(Plugin plugin, ConfigManager configManager) {
this.plugin = plugin;
this.configManager = configManager;
}
public boolean isAvailable() {
return configManager.isCoreProtectEnabled() && plugin.getServer().getPluginManager().getPlugin("CoreProtect") != null;
}
public boolean toggleInspector(Player player) {
if (!isAvailable()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
return false;
}
return Bukkit.dispatchCommand(player, "co inspect");
}
public boolean lookup(Player player, String targetPlayer, int radius, String time) {
if (!isAvailable()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
return false;
}
StringBuilder command = new StringBuilder("co lookup");
appendPlayerFilter(command, targetPlayer);
command.append(" r:").append(normalizeRadius(radius));
command.append(" t:").append(normalizeTime(time));
return Bukkit.dispatchCommand(player, command.toString());
}
public boolean rollback(Player player, int radius, String time, String targetPlayer) {
if (!isAvailable()) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
return false;
}
StringBuilder command = new StringBuilder("co rollback");
appendPlayerFilter(command, targetPlayer);
command.append(" r:").append(normalizeRadius(radius));
command.append(" t:").append(normalizeTime(time));
return Bukkit.dispatchCommand(player, command.toString());
}
private void appendPlayerFilter(StringBuilder command, String targetPlayer) {
String sanitized = sanitizePlayerName(targetPlayer);
if (!sanitized.isEmpty()) {
command.append(" u:").append(sanitized);
}
}
private int normalizeRadius(int radius) {
return Math.max(1, Math.min(100, radius));
}
private String normalizeTime(String time) {
String fallback = configManager.getCoreProtectDefaultTime();
if (time == null || time.isBlank()) {
return fallback;
}
String normalized = time.trim().toLowerCase();
if (!normalized.matches("\\d+[smhdw]")) {
return fallback;
}
return normalized;
}
private String sanitizePlayerName(String targetPlayer) {
if (targetPlayer == null || targetPlayer.isBlank()) {
return "";
}
String normalized = targetPlayer.trim();
if (normalized.matches("[A-Za-z0-9_]{1,16}")) {
return normalized;
}
return "";
}
}

View File

@@ -0,0 +1,107 @@
package org.blz.adminmode.integrations;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node;
import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.moderation.ModeSession;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class LuckPermsIntegration {
private final Plugin plugin;
private final ConfigManager configManager;
public LuckPermsIntegration(Plugin plugin, ConfigManager configManager) {
this.plugin = plugin;
this.configManager = configManager;
}
public String resolveCurrentGroup(Player player) {
if (!configManager.isUseLuckPerms()) {
return configManager.getBaseGroup();
}
try {
User user = resolveUser(player);
if (user == null) {
return configManager.getBaseGroup();
}
return user.getPrimaryGroup();
} catch (IllegalStateException exception) {
return configManager.getBaseGroup();
}
}
public void enterMode(Player player, ModeratorProfile profile) {
if (!configManager.isUseLuckPerms()) {
player.setOp(true);
return;
}
try {
User user = resolveUser(player);
if (user == null) {
return;
}
removeKnownGroups(user);
user.data().add(groupNode(configManager.getActiveGroup(profile)));
saveUser(user);
} catch (IllegalStateException exception) {
player.setOp(true);
plugin.getLogger().warning("LuckPerms не найден, используется OP для " + player.getName());
}
}
public void exitMode(Player player, ModeSession session) {
if (!configManager.isUseLuckPerms()) {
player.setOp(false);
return;
}
try {
User user = resolveUser(player);
if (user == null) {
return;
}
removeKnownGroups(user);
String restoreGroup = session.getPreviousGroup();
if (restoreGroup == null || restoreGroup.isBlank() || "op".equalsIgnoreCase(restoreGroup)) {
restoreGroup = configManager.getBaseGroup();
}
user.data().add(groupNode(restoreGroup));
saveUser(user);
} catch (IllegalStateException exception) {
player.setOp(false);
plugin.getLogger().warning("LuckPerms не найден, снят OP у " + player.getName());
}
}
private User resolveUser(Player player) {
LuckPerms luckPerms = LuckPermsProvider.get();
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) {
user = luckPerms.getUserManager().loadUser(player.getUniqueId()).join();
}
return user;
}
private void removeKnownGroups(User user) {
user.data().remove(groupNode(configManager.getBaseGroup()));
user.data().remove(groupNode(configManager.getSoftGroup()));
user.data().remove(groupNode(configManager.getHardGroup()));
}
private Node groupNode(String group) {
return Node.builder("group." + group).build();
}
private void saveUser(User user) {
LuckPermsProvider.get().getUserManager().saveUser(user);
}
}

View File

@@ -1,32 +1,39 @@
package org.blz.adminmode.listeners;
import java.util.List;
import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.config.ConfigManager;
import org.bukkit.ChatColor;
import org.blz.adminmode.dialog.AdminDialogService;
import org.blz.adminmode.moderation.ModeManager;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.plugin.Plugin;
import java.util.List;
public class AdminModeListener implements Listener {
private final Plugin plugin;
private final AdminModeCommand adminModeCommand;
private final ConfigManager configManager;
private final ModeManager modeManager;
private final AdminDialogService dialogService;
public AdminModeListener(Plugin plugin, AdminModeCommand adminModeCommand, ConfigManager configManager) {
this.plugin = plugin;
public AdminModeListener(
AdminModeCommand adminModeCommand,
ConfigManager configManager,
ModeManager modeManager,
AdminDialogService dialogService) {
this.adminModeCommand = adminModeCommand;
this.configManager = configManager;
this.modeManager = modeManager;
this.dialogService = dialogService;
}
@EventHandler(priority = EventPriority.HIGHEST)
@@ -37,7 +44,7 @@ public class AdminModeListener implements Listener {
if (!configManager.isPreventItemDrop()) return;
e.setCancelled(true);
player.sendMessage(ChatColor.RED + configManager.getMsgItemDropDenied());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgItemDropDenied()));
}
@EventHandler(priority = EventPriority.HIGHEST)
@@ -58,6 +65,11 @@ public class AdminModeListener implements Listener {
Player player = (Player) e.getWhoClicked();
if (modeManager.isReadOnlyViewedInventory(player, e.getView().getTopInventory())) {
e.setCancelled(true);
return;
}
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
if (!configManager.isPreventContainerTransfer()) return;
@@ -65,7 +77,7 @@ public class AdminModeListener implements Listener {
if (e.getClickedInventory().getHolder() != null &&
!e.getClickedInventory().getHolder().equals(player)) {
e.setCancelled(true);
player.sendMessage(ChatColor.RED + configManager.getMsgContainerTransferDenied());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgContainerTransferDenied()));
return;
}
}
@@ -74,7 +86,7 @@ public class AdminModeListener implements Listener {
if (e.getView().getTopInventory().getHolder() != null &&
!e.getView().getTopInventory().getHolder().equals(player)) {
e.setCancelled(true);
player.sendMessage(ChatColor.RED + configManager.getMsgContainerTransferDenied());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgContainerTransferDenied()));
}
}
}
@@ -84,9 +96,25 @@ public class AdminModeListener implements Listener {
Player player = e.getPlayer();
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
if ((e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK)
&& e.getItem() != null
&& modeManager.isPanelItem(e.getItem())) {
e.setCancelled(true);
String panelType = modeManager.getPanelType(e.getItem());
if (ModeManager.PANEL_CP_INSPECTOR.equals(panelType)) {
adminModeCommand.toggleInspector(player);
return;
}
dialogService.openCurrentPanel(player);
return;
}
if (!configManager.isPreventItemUse()) return;
if (e.getAction() == org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK) {
if (e.getAction() == Action.RIGHT_CLICK_BLOCK) {
if (e.getClickedBlock() != null) {
Material blockType = e.getClickedBlock().getType();
@@ -102,7 +130,7 @@ public class AdminModeListener implements Listener {
if (e.getItem() != null && e.getItem().getType() != Material.AIR) {
e.setCancelled(true);
player.sendMessage(ChatColor.RED + configManager.getMsgItemUseDenied());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgItemUseDenied()));
}
}
@@ -124,6 +152,6 @@ public class AdminModeListener implements Listener {
}
e.setCancelled(true);
player.sendMessage(ChatColor.RED + configManager.getMsgBlockPlaceDenied());
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgBlockPlaceDenied()));
}
}

View File

@@ -1,8 +1,10 @@
package org.blz.adminmode.listeners;
import java.util.List;
import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.config.ConfigManager;
import org.bukkit.ChatColor;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -11,8 +13,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent;
import java.util.List;
public class GameModeChangeListener implements Listener {
private final AdminModeCommand adminModeCommand;
@@ -31,11 +31,16 @@ public class GameModeChangeListener implements Listener {
return;
}
// КРИТИЧНО: Блокируем CREATIVE для всех в админ моде!
if (event.getNewGameMode() == GameMode.CREATIVE) {
ModeratorProfile activeProfile = adminModeCommand.getActiveProfile(player.getUniqueId());
boolean hardProfile = activeProfile != null && activeProfile.isHardProfile();
if (event.getNewGameMode() == GameMode.CREATIVE && !hardProfile) {
event.setCancelled(true);
player.sendMessage(ChatColor.RED + "В режиме модератора КРЕАТИВ запрещен!");
player.sendMessage(ChatColor.GRAY + "Доступные режимы: SURVIVAL, SPECTATOR");
player.sendMessage(configManager.getPrefixedMessage("§cВ режиме Soft Moder креатив запрещен!"));
return;
}
if (event.getNewGameMode() == GameMode.CREATIVE && hardProfile) {
return;
}
@@ -46,8 +51,8 @@ public class GameModeChangeListener implements Listener {
if (!allowedModes.contains(newMode)) {
event.setCancelled(true);
player.sendMessage(ChatColor.RED + "В админ моде можно переключаться только на разрешенные режимы!");
player.sendMessage(ChatColor.GRAY + "Разрешенные режимы: " + String.join(", ", allowedModes));
player.sendMessage(configManager.getPrefixedMessage("§cВ админ моде можно переключаться только на разрешенные режимы!"));
player.sendMessage(configManager.getPrefixedMessage("§7Разрешенные режимы: " + String.join(", ", allowedModes)));
return;
}
}

View File

@@ -0,0 +1,20 @@
package org.blz.adminmode.listeners;
import org.blz.adminmode.moderation.ModeManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
public class PlayerDeathListener implements Listener {
private final ModeManager modeManager;
public PlayerDeathListener(ModeManager modeManager) {
this.modeManager = modeManager;
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
modeManager.handleHardModeDeath(event);
}
}

View File

@@ -0,0 +1,38 @@
package org.blz.adminmode.listeners;
import org.blz.adminmode.commands.AdminModeCommand;
import org.blz.adminmode.moderation.ModeManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerSessionListener implements Listener {
private final AdminModeCommand adminModeCommand;
private final ModeManager modeManager;
public PlayerSessionListener(AdminModeCommand adminModeCommand, ModeManager modeManager) {
this.adminModeCommand = adminModeCommand;
this.modeManager = modeManager;
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
modeManager.markQuit(event.getPlayer());
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (!modeManager.hasSession(player.getUniqueId())) {
return;
}
player.getServer().getScheduler().runTaskLater(
org.blz.adminmode.AdminMode.getInstance(),
() -> adminModeCommand.recoverSession(player),
1L);
}
}

View File

@@ -0,0 +1,410 @@
package org.blz.adminmode.moderation;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.blz.adminmode.config.ConfigManager;
import org.blz.adminmode.integrations.LuckPermsIntegration;
import org.blz.adminmode.session.SessionStorage;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.attribute.Attribute;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class ModeManager {
public static final String PANEL_SOFT = "soft";
public static final String PANEL_HARD = "hard";
public static final String PANEL_CP_INSPECTOR = "cp_inspector";
private final Plugin plugin;
private final ConfigManager configManager;
private final LuckPermsIntegration luckPermsIntegration;
private final SessionStorage sessionStorage;
private final ModeSessionLogger sessionLogger;
private final NamespacedKey panelTypeKey;
private final Map<UUID, ModeSession> activeSessions = new HashMap<>();
public ModeManager(
Plugin plugin,
ConfigManager configManager,
LuckPermsIntegration luckPermsIntegration,
SessionStorage sessionStorage,
ModeSessionLogger sessionLogger) {
this.plugin = plugin;
this.configManager = configManager;
this.luckPermsIntegration = luckPermsIntegration;
this.sessionStorage = sessionStorage;
this.sessionLogger = sessionLogger;
this.panelTypeKey = new NamespacedKey(plugin, "panel_type");
this.activeSessions.putAll(sessionStorage.loadSessions());
}
public boolean hasSession(UUID playerId) {
return activeSessions.containsKey(playerId);
}
public ModeSession getSession(UUID playerId) {
return activeSessions.get(playerId);
}
public ModeSession getSession(Player player) {
return player == null ? null : getSession(player.getUniqueId());
}
public ModeratorProfile getProfile(UUID playerId) {
ModeSession session = getSession(playerId);
return session == null ? null : session.getProfile();
}
public List<ModeratorProfile> getAvailableProfiles(Player player) {
return ModeratorProfile.availableFor(player);
}
public Collection<ModeSession> getActiveSessions() {
return new ArrayList<>(activeSessions.values());
}
public boolean enterMode(Player player, ModeratorProfile profile) {
if (player == null || profile == null || hasSession(player.getUniqueId())) {
return false;
}
String previousGroup = luckPermsIntegration.resolveCurrentGroup(player);
String activeGroup = configManager.getActiveGroup(profile);
ModeSession session = ModeSession.capture(player, profile, previousGroup, activeGroup);
activeSessions.put(player.getUniqueId(), session);
persistSessions();
preparePlayerForModeration(player, profile);
luckPermsIntegration.enterMode(player, profile);
sessionLogger.logEnter(session);
String enterMessage = profile.isHardProfile() ? configManager.getMsgEnterHard() : configManager.getMsgEnterSoft();
player.sendMessage(configManager.getPrefixedMessage(enterMessage));
return true;
}
public boolean exitMode(Player player) {
return finishSession(player, false);
}
public boolean recoverSession(Player player) {
return finishSession(player, true);
}
public void markQuit(Player player) {
if (player != null && hasSession(player.getUniqueId())) {
persistSessions();
}
}
public boolean canUseHardActions(CommandSender sender) {
if (sender.hasPermission("adminmode.admin")) {
return true;
}
if (!(sender instanceof Player player)) {
return false;
}
ModeSession session = getSession(player);
return session != null && session.getProfile().isHardProfile();
}
public boolean teleportToPlayer(Player moderator, Player target) {
if (moderator == null || target == null) {
return false;
}
moderator.teleport(target.getLocation());
moderator.sendMessage(configManager.getPrefixedMessage("§aТелепортация к §f" + target.getName() + "§a выполнена."));
return true;
}
public boolean teleportPlayerHere(Player moderator, Player target) {
if (moderator == null || target == null) {
return false;
}
target.teleport(moderator.getLocation());
moderator.sendMessage(configManager.getPrefixedMessage("§aИгрок §f" + target.getName() + "§a телепортирован к вам."));
target.sendMessage(configManager.getPrefixedMessage("§eВас телепортировал модератор §f" + moderator.getName()));
return true;
}
public boolean spectatePlayer(Player moderator, Player target) {
if (moderator == null || target == null) {
return false;
}
if (moderator.equals(target)) {
moderator.sendMessage(configManager.getPrefixedMessage("§cНельзя спектейтить самого себя."));
return false;
}
moderator.setGameMode(GameMode.SPECTATOR);
moderator.setSpectatorTarget((Entity) target);
moderator.sendMessage(configManager.getPrefixedMessage("§bВы наблюдаете за §f" + target.getName()));
return true;
}
public boolean openInvsee(Player moderator, Player target) {
if (moderator == null || target == null) {
return false;
}
moderator.openInventory(target.getInventory());
moderator.sendMessage(configManager.getPrefixedMessage("§7Открыт инвентарь игрока §f" + target.getName()));
return true;
}
public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean invulnerable) {
if (!hasSession(player.getUniqueId())) {
return;
}
player.setAllowFlight(true);
player.setFlying(true);
player.setFlySpeed(clampSpeed(flySpeed, 0.1f));
player.setWalkSpeed(clampSpeed(walkSpeed, 0.1f));
player.setInvulnerable(invulnerable);
}
public List<Player> listOtherOnlinePlayers(Player viewer) {
List<Player> players = new ArrayList<>(plugin.getServer().getOnlinePlayers());
players.removeIf(player -> viewer != null && player.getUniqueId().equals(viewer.getUniqueId()));
players.sort(Comparator.comparing(Player::getName, String.CASE_INSENSITIVE_ORDER));
return players;
}
public List<Component> buildOnlineSummary(Player viewer) {
List<Component> lines = new ArrayList<>();
for (Player target : listOtherOnlinePlayers(viewer)) {
Location location = target.getLocation();
lines.add(Component.text(
target.getName() + " | " + target.getWorld().getName() + " | " +
location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ(),
NamedTextColor.GRAY));
}
if (lines.isEmpty()) {
lines.add(Component.text("Нет других игроков онлайн.", NamedTextColor.DARK_GRAY));
}
return lines;
}
public void handleHardModeDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
ModeSession session = getSession(player);
if (session == null || !session.getProfile().isHardProfile()) {
return;
}
event.setKeepInventory(true);
event.setKeepLevel(true);
event.getDrops().clear();
Location restoreLocation = session.getLocation();
if (restoreLocation != null) {
plugin.getServer().getScheduler().runTask(plugin, () -> player.teleport(restoreLocation));
}
}
public boolean isPanelItem(ItemStack itemStack) {
return getPanelType(itemStack) != null;
}
public String getPanelType(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR || !itemStack.hasItemMeta()) {
return null;
}
ItemMeta itemMeta = itemStack.getItemMeta();
PersistentDataContainer container = itemMeta.getPersistentDataContainer();
if (!container.has(panelTypeKey, PersistentDataType.STRING)) {
return null;
}
return container.get(panelTypeKey, PersistentDataType.STRING);
}
public boolean isReadOnlyViewedInventory(Player viewer, org.bukkit.inventory.Inventory inventory) {
if (viewer == null || inventory == null || inventory.getHolder() == null) {
return false;
}
if (!(inventory.getHolder() instanceof Player target)) {
return false;
}
return hasSession(viewer.getUniqueId()) && !viewer.getUniqueId().equals(target.getUniqueId());
}
private boolean finishSession(Player player, boolean recovered) {
if (player == null) {
return false;
}
ModeSession session = activeSessions.remove(player.getUniqueId());
if (session == null) {
return false;
}
restorePlayerState(player, session);
luckPermsIntegration.exitMode(player, session);
persistSessions();
if (recovered) {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgSessionRecovered()));
sessionLogger.logRecovered(session);
} else {
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgExitMode()));
sessionLogger.logExit(session);
}
return true;
}
private void preparePlayerForModeration(Player player, ModeratorProfile profile) {
PlayerInventory inventory = player.getInventory();
inventory.clear();
inventory.setArmorContents(new ItemStack[4]);
inventory.setItemInOffHand(new ItemStack(Material.AIR));
player.closeInventory();
player.setSpectatorTarget(null);
for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
player.setFireTicks(0);
player.setHealth(resolveMaxHealth(player));
player.setFoodLevel(20);
player.setSaturation(20.0f);
player.setExhaustion(0.0f);
player.setExp(0.0f);
player.setLevel(0);
player.setTotalExperience(0);
player.setAllowFlight(true);
player.setFlying(true);
player.setFlySpeed(clampSpeed(configManager.getCustomFlySpeed(), 0.1f));
player.setWalkSpeed(clampSpeed(configManager.getCustomWalkSpeed(), 0.1f));
player.setInvulnerable(true);
player.setGameMode(profile.isHardProfile() ? GameMode.CREATIVE : GameMode.SPECTATOR);
inventory.setItem(0, createPanelItem(profile.isHardProfile() ? configManager.getHardPanelMaterial() : configManager.getSoftPanelMaterial(),
profile.isHardProfile() ? "Hard Panel" : "Soft Panel",
profile.isHardProfile() ? NamedTextColor.RED : NamedTextColor.AQUA,
profile.isHardProfile() ? PANEL_HARD : PANEL_SOFT));
if (profile.isHardProfile()) {
inventory.setItem(1, createPanelItem(
configManager.getCpInspectorMaterial(),
"CP Inspector",
NamedTextColor.YELLOW,
PANEL_CP_INSPECTOR));
}
if (configManager.isGivePresetBlocks()) {
givePresetBlocks(player);
}
}
private void restorePlayerState(Player player, ModeSession session) {
PlayerInventory inventory = player.getInventory();
inventory.clear();
inventory.setContents(session.getInventoryContents());
inventory.setArmorContents(session.getArmorContents());
inventory.setItemInOffHand(session.getOffHandItem());
player.setSpectatorTarget(null);
player.setInvulnerable(false);
player.setAllowFlight(false);
player.setFlying(false);
player.setFlySpeed(0.1f);
player.setWalkSpeed(0.2f);
if (player.getAttribute(Attribute.MAX_HEALTH) != null) {
player.getAttribute(Attribute.MAX_HEALTH).setBaseValue(session.getMaxHealth());
}
player.setGameMode(session.getGameMode());
if (session.getLocation() != null) {
player.teleport(session.getLocation());
}
player.setHealth(Math.min(resolveMaxHealth(player), session.getHealth()));
player.setFoodLevel(session.getFoodLevel());
player.setSaturation(session.getSaturation());
player.setExhaustion(session.getExhaustion());
player.setExp(session.getExp());
player.setLevel(session.getExpLevel());
player.setTotalExperience(session.getTotalExperience());
for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
for (PotionEffect effect : session.getPotionEffects()) {
player.addPotionEffect(effect);
}
player.setFireTicks(session.getFireTicks());
player.setAllowFlight(session.isAllowFlight());
player.setFlying(session.isFlying());
player.setFlySpeed(clampSpeed(session.getFlySpeed(), 0.1f));
player.setWalkSpeed(clampSpeed(session.getWalkSpeed(), 0.1f));
}
private void givePresetBlocks(Player player) {
for (String blockString : configManager.getPresetBlocks()) {
try {
String[] parts = blockString.split(":");
Material material = Material.valueOf(parts[0].trim().toUpperCase());
int amount = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 64;
player.getInventory().addItem(new ItemStack(material, Math.max(1, amount)));
} catch (Exception exception) {
plugin.getLogger().warning("Неверный блок в preset_blocks: " + blockString);
}
}
}
private ItemStack createPanelItem(Material material, String title, NamedTextColor color, String panelType) {
ItemStack itemStack = new ItemStack(material);
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(Component.text("[" + title + "]", color));
itemMeta.getPersistentDataContainer().set(panelTypeKey, PersistentDataType.STRING, panelType);
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private void persistSessions() {
sessionStorage.saveSessions(activeSessions);
}
private double resolveMaxHealth(Player player) {
if (player.getAttribute(Attribute.MAX_HEALTH) != null) {
return player.getAttribute(Attribute.MAX_HEALTH).getValue();
}
return 20.0D;
}
private float clampSpeed(float value, float min) {
return Math.max(min, Math.min(1.0f, value));
}
}

View File

@@ -0,0 +1,253 @@
package org.blz.adminmode.moderation;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
public final class ModeSession {
private final UUID playerId;
private final String playerName;
private final ModeratorProfile profile;
private final long startTimestamp;
private final String previousGroup;
private final String activeGroup;
private final ItemStack[] inventoryContents;
private final ItemStack[] armorContents;
private final ItemStack offHandItem;
private final Location location;
private final GameMode gameMode;
private final double health;
private final double maxHealth;
private final int foodLevel;
private final float saturation;
private final float exhaustion;
private final int expLevel;
private final float exp;
private final int totalExperience;
private final Collection<PotionEffect> potionEffects;
private final boolean flying;
private final boolean allowFlight;
private final float flySpeed;
private final float walkSpeed;
private final int fireTicks;
public ModeSession(
UUID playerId,
String playerName,
ModeratorProfile profile,
long startTimestamp,
String previousGroup,
String activeGroup,
ItemStack[] inventoryContents,
ItemStack[] armorContents,
ItemStack offHandItem,
Location location,
GameMode gameMode,
double health,
double maxHealth,
int foodLevel,
float saturation,
float exhaustion,
int expLevel,
float exp,
int totalExperience,
Collection<PotionEffect> potionEffects,
boolean flying,
boolean allowFlight,
float flySpeed,
float walkSpeed,
int fireTicks) {
this.playerId = playerId;
this.playerName = playerName;
this.profile = profile;
this.startTimestamp = startTimestamp;
this.previousGroup = previousGroup;
this.activeGroup = activeGroup;
this.inventoryContents = cloneItems(inventoryContents);
this.armorContents = cloneItems(armorContents);
this.offHandItem = cloneItem(offHandItem);
this.location = location == null ? null : location.clone();
this.gameMode = gameMode;
this.health = health;
this.maxHealth = maxHealth;
this.foodLevel = foodLevel;
this.saturation = saturation;
this.exhaustion = exhaustion;
this.expLevel = expLevel;
this.exp = exp;
this.totalExperience = totalExperience;
this.potionEffects = potionEffects == null ? List.of() : new ArrayList<>(potionEffects);
this.flying = flying;
this.allowFlight = allowFlight;
this.flySpeed = flySpeed;
this.walkSpeed = walkSpeed;
this.fireTicks = fireTicks;
}
public static ModeSession capture(Player player, ModeratorProfile profile, String previousGroup, String activeGroup) {
double maxHealth = 20.0D;
if (player.getAttribute(Attribute.MAX_HEALTH) != null) {
maxHealth = player.getAttribute(Attribute.MAX_HEALTH).getBaseValue();
}
return new ModeSession(
player.getUniqueId(),
player.getName(),
profile,
System.currentTimeMillis(),
previousGroup,
activeGroup,
player.getInventory().getContents(),
player.getInventory().getArmorContents(),
player.getInventory().getItemInOffHand(),
player.getLocation(),
player.getGameMode(),
player.getHealth(),
maxHealth,
player.getFoodLevel(),
player.getSaturation(),
player.getExhaustion(),
player.getLevel(),
player.getExp(),
player.getTotalExperience(),
player.getActivePotionEffects(),
player.isFlying(),
player.getAllowFlight(),
player.getFlySpeed(),
player.getWalkSpeed(),
player.getFireTicks());
}
public UUID getPlayerId() {
return playerId;
}
public String getPlayerName() {
return playerName;
}
public ModeratorProfile getProfile() {
return profile;
}
public long getStartTimestamp() {
return startTimestamp;
}
public String getPreviousGroup() {
return previousGroup;
}
public String getActiveGroup() {
return activeGroup;
}
public ItemStack[] getInventoryContents() {
return cloneItems(inventoryContents);
}
public ItemStack[] getArmorContents() {
return cloneItems(armorContents);
}
public ItemStack getOffHandItem() {
return cloneItem(offHandItem);
}
public Location getLocation() {
return location == null ? null : location.clone();
}
public GameMode getGameMode() {
return gameMode;
}
public double getHealth() {
return health;
}
public double getMaxHealth() {
return maxHealth;
}
public int getFoodLevel() {
return foodLevel;
}
public float getSaturation() {
return saturation;
}
public float getExhaustion() {
return exhaustion;
}
public int getExpLevel() {
return expLevel;
}
public float getExp() {
return exp;
}
public int getTotalExperience() {
return totalExperience;
}
public Collection<PotionEffect> getPotionEffects() {
return new ArrayList<>(potionEffects);
}
public boolean isFlying() {
return flying;
}
public boolean isAllowFlight() {
return allowFlight;
}
public float getFlySpeed() {
return flySpeed;
}
public float getWalkSpeed() {
return walkSpeed;
}
public int getFireTicks() {
return fireTicks;
}
public long getDurationMillis() {
return Math.max(0L, System.currentTimeMillis() - startTimestamp);
}
private static ItemStack[] cloneItems(ItemStack[] source) {
if (source == null) {
return new ItemStack[0];
}
ItemStack[] clone = new ItemStack[source.length];
for (int i = 0; i < source.length; i++) {
clone[i] = cloneItem(source[i]);
}
return clone;
}
private static ItemStack cloneItem(ItemStack item) {
if (item == null || item.getType() == Material.AIR) {
return new ItemStack(Material.AIR);
}
return item.clone();
}
}

View File

@@ -0,0 +1,80 @@
package org.blz.adminmode.moderation;
import org.blz.adminmode.config.ConfigManager;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ModeSessionLogger {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final Plugin plugin;
private final ConfigManager configManager;
public ModeSessionLogger(Plugin plugin, ConfigManager configManager) {
this.plugin = plugin;
this.configManager = configManager;
}
public void logEnter(ModeSession session) {
writeLine("ENTER_" + modeSuffix(session), session, "-");
}
public void logExit(ModeSession session) {
writeLine("EXIT_" + modeSuffix(session), session, formatDuration(session.getDurationMillis()));
}
public void logRecovered(ModeSession session) {
writeLine("RECOVER_" + modeSuffix(session), session, formatDuration(session.getDurationMillis()));
}
private void writeLine(String action, ModeSession session, String duration) {
if (!configManager.isSessionLoggingEnabled()) {
return;
}
File logFile = new File(plugin.getDataFolder(), configManager.getSessionLogFile());
File parent = logFile.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
String line = String.format(
"[%s] %s | Player: %s | UUID: %s | Duration: %s%n",
LocalDateTime.now().format(DATE_TIME_FORMATTER),
action,
session.getPlayerName(),
session.getPlayerId(),
duration);
try (FileWriter writer = new FileWriter(logFile, true)) {
writer.write(line);
} catch (IOException exception) {
plugin.getLogger().warning("Не удалось записать сессионный лог: " + exception.getMessage());
}
}
private String modeSuffix(ModeSession session) {
return session.getProfile().isHardProfile() ? "HARD" : "SOFT";
}
private String formatDuration(long millis) {
Duration duration = Duration.ofMillis(Math.max(0L, millis));
long hours = duration.toHours();
long minutes = duration.toMinutesPart();
long seconds = duration.toSecondsPart();
if (hours > 0) {
return hours + "h " + minutes + "m " + seconds + "s";
}
if (minutes > 0) {
return minutes + "m " + seconds + "s";
}
return seconds + "s";
}
}

View File

@@ -0,0 +1,7 @@
package org.blz.adminmode.moderation;
public enum PlayerListAction {
SPECTATE,
TELEPORT,
INVSEE
}

View File

@@ -0,0 +1,335 @@
package org.blz.adminmode.session;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.blz.adminmode.moderation.ModeSession;
import org.blz.adminmode.moderation.ModeratorProfile;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class SessionStorage {
private final Plugin plugin;
private final File storageFile;
private final Gson gson;
public SessionStorage(Plugin plugin) {
this.plugin = plugin;
this.storageFile = new File(plugin.getDataFolder(), "sessions.json");
this.gson = new GsonBuilder().setPrettyPrinting().create();
}
public Map<UUID, ModeSession> loadSessions() {
Map<UUID, ModeSession> sessions = new LinkedHashMap<>();
if (!storageFile.exists()) {
return sessions;
}
try (FileReader reader = new FileReader(storageFile)) {
JsonElement root = JsonParser.parseReader(reader);
if (!root.isJsonArray()) {
return sessions;
}
for (JsonElement element : root.getAsJsonArray()) {
if (!element.isJsonObject()) {
continue;
}
ModeSession session = deserializeSession(element.getAsJsonObject());
if (session != null) {
sessions.put(session.getPlayerId(), session);
}
}
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось загрузить sessions.json: " + exception.getMessage());
}
return sessions;
}
public void saveSessions(Map<UUID, ModeSession> sessions) {
File parent = storageFile.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
JsonArray array = new JsonArray();
for (ModeSession session : sessions.values()) {
array.add(serializeSession(session));
}
try (FileWriter writer = new FileWriter(storageFile)) {
writer.write(gson.toJson(array));
} catch (IOException exception) {
plugin.getLogger().warning("Не удалось сохранить sessions.json: " + exception.getMessage());
}
}
private JsonObject serializeSession(ModeSession session) {
JsonObject json = new JsonObject();
json.addProperty("playerId", session.getPlayerId().toString());
json.addProperty("playerName", session.getPlayerName());
json.addProperty("profile", session.getProfile().getConfigKey());
json.addProperty("startTimestamp", session.getStartTimestamp());
json.addProperty("previousGroup", session.getPreviousGroup());
json.addProperty("activeGroup", session.getActiveGroup());
json.addProperty("inventory", serializeItemArray(session.getInventoryContents()));
json.addProperty("armorContents", serializeItemArray(session.getArmorContents()));
json.addProperty("offHand", serializeItem(session.getOffHandItem()));
json.add("location", serializeLocation(session.getLocation()));
json.addProperty("gameMode", session.getGameMode().name());
json.addProperty("health", session.getHealth());
json.addProperty("maxHealth", session.getMaxHealth());
json.addProperty("foodLevel", session.getFoodLevel());
json.addProperty("saturation", session.getSaturation());
json.addProperty("exhaustion", session.getExhaustion());
json.addProperty("expLevel", session.getExpLevel());
json.addProperty("exp", session.getExp());
json.addProperty("totalExperience", session.getTotalExperience());
json.addProperty("potionEffects", serializePotionEffects(session.getPotionEffects()));
json.addProperty("flying", session.isFlying());
json.addProperty("allowFlight", session.isAllowFlight());
json.addProperty("flySpeed", session.getFlySpeed());
json.addProperty("walkSpeed", session.getWalkSpeed());
json.addProperty("fireTicks", session.getFireTicks());
return json;
}
private ModeSession deserializeSession(JsonObject json) {
try {
UUID playerId = UUID.fromString(json.get("playerId").getAsString());
String playerName = json.get("playerName").getAsString();
ModeratorProfile profile = ModeratorProfile.fromConfigKey(json.get("profile").getAsString())
.orElse(ModeratorProfile.SOFT_MODER);
long startTimestamp = json.get("startTimestamp").getAsLong();
String previousGroup = getString(json, "previousGroup");
String activeGroup = getString(json, "activeGroup");
ItemStack[] inventory = deserializeItemArray(getString(json, "inventory"));
ItemStack[] armorContents = deserializeItemArray(getString(json, "armorContents"));
ItemStack offHand = deserializeItem(getString(json, "offHand"));
Location location = deserializeLocation(json.getAsJsonObject("location"));
GameMode gameMode = GameMode.valueOf(json.get("gameMode").getAsString());
double health = json.get("health").getAsDouble();
double maxHealth = json.get("maxHealth").getAsDouble();
int foodLevel = json.get("foodLevel").getAsInt();
float saturation = json.get("saturation").getAsFloat();
float exhaustion = json.get("exhaustion").getAsFloat();
int expLevel = json.get("expLevel").getAsInt();
float exp = json.get("exp").getAsFloat();
int totalExperience = json.get("totalExperience").getAsInt();
Collection<PotionEffect> potionEffects = deserializePotionEffects(getString(json, "potionEffects"));
boolean flying = json.get("flying").getAsBoolean();
boolean allowFlight = json.get("allowFlight").getAsBoolean();
float flySpeed = json.get("flySpeed").getAsFloat();
float walkSpeed = json.get("walkSpeed").getAsFloat();
int fireTicks = json.get("fireTicks").getAsInt();
return new ModeSession(
playerId,
playerName,
profile,
startTimestamp,
previousGroup,
activeGroup,
inventory,
armorContents,
offHand,
location,
gameMode,
health,
maxHealth,
foodLevel,
saturation,
exhaustion,
expLevel,
exp,
totalExperience,
potionEffects,
flying,
allowFlight,
flySpeed,
walkSpeed,
fireTicks);
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать session entry: " + exception.getMessage());
return null;
}
}
private JsonObject serializeLocation(Location location) {
JsonObject json = new JsonObject();
if (location == null || location.getWorld() == null) {
return json;
}
json.addProperty("world", location.getWorld().getName());
json.addProperty("x", location.getX());
json.addProperty("y", location.getY());
json.addProperty("z", location.getZ());
json.addProperty("yaw", location.getYaw());
json.addProperty("pitch", location.getPitch());
return json;
}
private Location deserializeLocation(JsonObject json) {
if (json == null || !json.has("world")) {
return null;
}
World world = Bukkit.getWorld(json.get("world").getAsString());
if (world == null) {
return null;
}
return new Location(
world,
json.get("x").getAsDouble(),
json.get("y").getAsDouble(),
json.get("z").getAsDouble(),
json.get("yaw").getAsFloat(),
json.get("pitch").getAsFloat());
}
private String getString(JsonObject json, String key) {
return json.has(key) && !json.get(key).isJsonNull() ? json.get(key).getAsString() : "";
}
private String serializeItemArray(ItemStack[] items) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
dataOutput.writeInt(items.length);
for (ItemStack item : items) {
dataOutput.writeObject(item);
}
}
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось сериализовать item array: " + exception.getMessage());
return "";
}
}
private ItemStack[] deserializeItemArray(String data) {
if (data == null || data.isEmpty()) {
return new ItemStack[0];
}
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
int length = dataInput.readInt();
ItemStack[] items = new ItemStack[length];
for (int i = 0; i < length; i++) {
items[i] = (ItemStack) dataInput.readObject();
}
return items;
}
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать item array: " + exception.getMessage());
return new ItemStack[0];
}
}
private String serializeItem(ItemStack item) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
dataOutput.writeObject(item);
}
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось сериализовать item: " + exception.getMessage());
return "";
}
}
private ItemStack deserializeItem(String data) {
if (data == null || data.isEmpty()) {
return new ItemStack(Material.AIR);
}
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
return (ItemStack) dataInput.readObject();
}
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать item: " + exception.getMessage());
return new ItemStack(Material.AIR);
}
}
private String serializePotionEffects(Collection<PotionEffect> effects) {
StringBuilder builder = new StringBuilder();
for (PotionEffect effect : effects) {
builder.append(effect.getType().getName()).append(':')
.append(effect.getDuration()).append(':')
.append(effect.getAmplifier()).append(':')
.append(effect.isAmbient()).append(':')
.append(effect.hasParticles()).append(':')
.append(effect.hasIcon()).append(';');
}
return builder.toString();
}
private Collection<PotionEffect> deserializePotionEffects(String data) {
List<PotionEffect> effects = new ArrayList<>();
if (data == null || data.isEmpty()) {
return effects;
}
String[] effectStrings = data.split(";");
for (String effectString : effectStrings) {
if (effectString.isEmpty()) {
continue;
}
try {
String[] parts = effectString.split(":");
PotionEffectType type = PotionEffectType.getByName(parts[0]);
if (type == null) {
continue;
}
effects.add(new PotionEffect(
type,
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Boolean.parseBoolean(parts[3]),
Boolean.parseBoolean(parts[4]),
Boolean.parseBoolean(parts[5])));
} catch (Exception exception) {
plugin.getLogger().warning("Не удалось десериализовать potion effect: " + effectString);
}
}
return effects;
}
}

View File

@@ -13,12 +13,12 @@ admin_mode:
# Группы которые выдаются при активации режима
# Можно указать несколько групп через запятую
groups:
- "soft_moder"
- "soft_moder_adm"
profile_groups:
soft_moder:
- "soft_moder"
- "soft_moder_adm"
hard_moder:
- "hard_moder"
- "hard_moder_adm"
# Удалять ли группы при деактивации (если false - группы останутся)
remove_on_disable: true
@@ -122,3 +122,30 @@ admin_mode:
item_use_denied: "✖ В режиме администратора нельзя использовать предметы!"
block_place_denied: "✖ Вы можете ставить только разрешенные блоки!"
reloaded: "✓ Конфигурация AdminMode перезагружена!"
groups:
base: "moder"
soft: "soft_moder_adm"
hard: "hard_moder_adm"
items:
soft_panel_material: COMPASS
hard_panel_material: NETHER_STAR
cp_inspector_material: STICK
coreprotect:
enabled: true
default_radius: 10
default_time: "24h"
messages:
prefix: "§8[§bAdminMode§8] "
enter_soft: "§aВошёл в режим §bSOFT MODER"
enter_hard: "§aВошёл в режим §cHARD MODER"
exit: "§7Режим деактивирован. Состояние восстановлено."
cp_unavailable: "§cCoreProtect не найден. Hard режим работает без CP."
session_recovered: "§eВаша активная сессия модерации была восстановлена и корректно завершена."
logging:
enabled: true
log_file: "logs/sessions.log"

View File

@@ -4,7 +4,8 @@ main: org.blz.adminmode.AdminMode
api-version: '1.21'
author: BelzeBool, Kilka_v_HJIebe
description: Плагин режима администратора с сохранением состояния игрока
softdepend: [LuckPerms]
depend: [LuckPerms]
softdepend: [CoreProtect]
commands:
adminmode: