refactor: decompose god-classes, add lint gates, restructure packages

- Decompose all 11 god-classes (>200 lines) into facade + collaborators
  (command/, schematic/, config/, generator/, mode/, papi/, adaptive/,
  storage/, player/, leaderboard/) — every file is now <=200 lines
- Add PMD 7 (report-only) + stricter checkstyle rules (NPath, IllegalCatch,
  NestedDepth, Whitespace, MissingOverride); fix real bugs found
  (printStackTrace, resource leaks in ConfigLoader/SQLStatsStorage)
- docs/ARCHITECTURE.md: target structure + 'one folder = one purpose' rule
- Pilot restructure: command/core|util/, mode/impl/solo|multi|barrier/,
  hook/papi/resolver/; cross-package classes made public
- TODO.md: track warning cleanup (132-138) + restructure progress
This commit is contained in:
loki5512344 2026-08-05 18:33:21 +02:00
parent 1d57972038
commit 81788e71ed
Signed by: boba
GPG key ID: 253067914055423B
70 changed files with 3428 additions and 1701 deletions

View file

@ -1,28 +1,13 @@
package dev.loki.loparkour.command.admin;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.api.core.Registry;
import dev.loki.loparkour.command.player.PlayerCommandHandler;
import dev.loki.loparkour.command.schematic.SchematicCommandHandler;
import dev.loki.loparkour.config.locale.Locales;
import dev.loki.loparkour.menu.core.ParkourOption;
import dev.loki.loparkour.mode.base.Mode;
import dev.loki.loparkour.mode.base.Modes;
import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.player.core.ParkourUser;
import dev.loki.loparkour.player.data.InventoryData;
import dev.loki.loparkour.util.text.ColorUtil;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* Handles admin-only /LoParkour sub-commands:
* forcejoin, forceleave, reset, recoverinventory.
@ -30,9 +15,17 @@ import java.util.UUID;
public class AdminCommandHandler {
private final PlayerCommandHandler base;
private final ForceJoinExecutor forceJoin;
private final ForceLeaveExecutor forceLeave;
private final ResetExecutor reset;
private final RecoverInventoryExecutor recoverInventory;
public AdminCommandHandler(PlayerCommandHandler base) {
this.base = base;
this.forceJoin = new ForceJoinExecutor(base);
this.forceLeave = new ForceLeaveExecutor(base);
this.reset = new ResetExecutor(base);
this.recoverInventory = new RecoverInventoryExecutor(base);
}
public void handle(@NotNull String arg1, @NotNull String arg2,
@ -42,173 +35,37 @@ public class AdminCommandHandler {
return;
}
dispatchAdmin(arg1, arg2, sender);
dispatchSchematic(arg1, arg2, sender, player);
}
private void dispatchAdmin(@NotNull String arg1, @NotNull String arg2, @NotNull CommandSender sender) {
switch (arg1.toLowerCase()) {
case "forcejoin" -> handleForceJoin(arg2, sender);
case "forceleave" -> handleForceLeave(arg2, sender);
case "reset" -> handleReset(arg2, sender);
case "recoverinventory" -> handleRecoverInventory(arg2, sender);
case "forcejoin" -> {
forceJoin.execute(arg2, sender);
}
case "forceleave" -> {
forceLeave.execute(arg2, sender);
}
case "reset" -> {
reset.execute(arg2, sender);
}
case "recoverinventory" -> {
recoverInventory.execute(arg2, sender);
}
default -> {
/* unknown admin subcommand: handle() falls through to schematic */
}
}
}
private void dispatchSchematic(@NotNull String arg1, @NotNull String arg2,
@NotNull CommandSender sender, @Nullable Player player) {
if (player == null) {
return;
}
if ("schematic".equals(arg1)) {
SchematicCommandHandler.handleSubcommand(arg2, sender, player, base);
}
}
// ── forcejoin ──────────────────────────────────────────────────────────────
private void handleForceJoin(String target, CommandSender sender) {
if (!base.cooldown(sender, "forcejoin", 2500, Locales.getString(sender, "admin.cooldown"))) {
return;
}
if ("everyone".equalsIgnoreCase(target)) {
Bukkit.getOnlinePlayers().forEach(p -> Modes.DEFAULT.create(p));
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.force_join_everyone"));
return;
}
if ("nearest".equalsIgnoreCase(target)) {
Player closest = findNearest(sender);
if (closest == null) {
return;
}
Modes.DEFAULT.create(closest);
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.force_join_player").formatted(closest.getName()));
return;
}
Player other = Bukkit.getPlayer(target);
if (other == null) {
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.player_not_online"));
return;
}
Modes.DEFAULT.create(other);
}
// ── forceleave ─────────────────────────────────────────────────────────────
private void handleForceLeave(String target, CommandSender sender) {
if (!base.cooldown(sender, "forceleave", 2500, Locales.getString(sender, "admin.cooldown"))) {
return;
}
if ("everyone".equalsIgnoreCase(target)) {
ParkourPlayer.getPlayers().forEach(ParkourUser::leave);
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.force_leave_everyone"));
return;
}
Player other = Bukkit.getPlayer(target);
if (other == null) {
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.player_not_online"));
return;
}
ParkourUser user = ParkourUser.getUser(other);
if (user == null) {
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.player_not_playing"));
return;
}
ParkourUser.leave(user);
}
// ── reset ──────────────────────────────────────────────────────────────────
private void handleReset(String target, CommandSender sender) {
if (!base.cooldown(sender, "reset", 2500, Locales.getString(sender, "admin.cooldown"))) {
return;
}
if ("everyone".equalsIgnoreCase(target)) {
Registry.getModes().stream()
.map(Mode::getLeaderboard)
.filter(lb -> lb != null)
.forEach(lb -> {
lb.resetAll();
lb.write(true);
});
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.reset_all"));
return;
}
UUID uuid = resolveUUID(target);
String name = resolvePlayerName(target, uuid);
Registry.getModes().stream()
.map(Mode::getLeaderboard)
.filter(lb -> lb != null)
.forEach(lb -> {
lb.remove(uuid);
lb.write(true);
});
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.reset_player").formatted(name));
}
// ── recoverinventory ───────────────────────────────────────────────────────
private void handleRecoverInventory(String target, CommandSender sender) {
if (!base.cooldown(sender, "recoverinventory", 2500, Locales.getString(sender, "admin.cooldown"))) {
return;
}
Player other = Bukkit.getPlayer(target);
if (other == null) {
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.player_not_online"));
return;
}
new InventoryData(other).load(result -> {
if (result != null) {
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.recover_inventory").formatted(other.getName()));
} else {
send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.recover_inventory_missing").formatted(other.getName()));
}
});
}
// ── helpers ────────────────────────────────────────────────────────────────
private @Nullable Player findNearest(CommandSender sender) {
Location from = sender instanceof Player p ? p.getLocation()
: sender instanceof BlockCommandSender b ? b.getBlock().getLocation()
: null;
if (from == null || from.getWorld() == null) {
return null;
}
return from.getWorld().getPlayers().stream()
.min((a, b) -> Double.compare(a.getLocation().distance(from), b.getLocation().distance(from)))
.orElse(null);
}
@SuppressWarnings("deprecation") // Bukkit: name-based OfflinePlayer lookup (admin-only)
private UUID resolveUUID(String input) {
Player online = Bukkit.getPlayerExact(input);
if (online != null) {
return online.getUniqueId();
}
if (input.contains("-")) {
try {
return UUID.fromString(input);
} catch (IllegalArgumentException ignored) {
// fall through to offline lookup by name
}
}
return Bukkit.getOfflinePlayer(input).getUniqueId();
}
private String resolvePlayerName(String input, UUID uuid) {
Player online = Bukkit.getPlayerExact(input);
if (online != null) {
return online.getName();
}
OfflinePlayer op = Bukkit.getOfflinePlayer(uuid);
return op.getName() != null ? op.getName() : input;
}
private void send(CommandSender sender, String msg) {
sender.sendMessage(ColorUtil.color(msg));
}
}