From 87d38712108f9d2c34b82e95f3732429217ccd0d Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 12:27:08 +0300 Subject: [PATCH 01/50] Whitespaces removed --- .../command/CheckpointCommand.java | 10 +-- .../listener/CheckpointListener.java | 82 +++++++++---------- .../manager/CheckpointManager.java | 20 ++--- .../manager/ConfigManager.java | 2 +- .../model/RespawnPriority.java | 10 +-- 5 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index 37eb78d..8bafd13 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -36,7 +36,7 @@ public CheckpointCommand(@NotNull CampfireCheckpoints plugin) { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - + if (!(sender instanceof Player player)) { sender.sendMessage("This command can only be used by players."); return true; @@ -92,11 +92,11 @@ private void handleList(@NotNull Player player) { String limitDisplay = max > 0 ? " &7(" + checkpoints.size() + "/" + max + ")" : ""; MessageUtil.send(player, "&6&l=== Your Checkpoints ===" + limitDisplay); - + for (int i = 0; i < checkpoints.size(); i++) { Checkpoint cp = checkpoints.get(i); Location loc = cp.getBlockLocation(); - + String status = cp.isLit() ? "&a✓ Lit" : "&c✗ Extinguished"; String coords = loc != null ? String.format("&f%d, %d, %d", loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()) : @@ -171,7 +171,7 @@ private void handleReload(@NotNull Player player) { private void handleInfo(@NotNull Player player) { CheckpointManager manager = plugin.getCheckpointManager(); ConfigManager configManager = plugin.getConfigManager(); - + int playerCount = manager.getPlayerCheckpoints(player.getUniqueId()).size(); int totalCount = manager.getTotalCheckpointCount(); int max = configManager.getMaxCheckpointsPerPlayer(); @@ -212,7 +212,7 @@ private void handleInfo(@NotNull Player player) { if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { int checkpointCount = plugin.getCheckpointManager() .getPlayerCheckpoints(player.getUniqueId()).size(); - + return IntStream.range(0, checkpointCount) .mapToObj(String::valueOf) .filter(s -> s.startsWith(args[1])) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index ae3b31f..4f94c57 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -59,7 +59,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { } Player player = event.getPlayer(); - + if (!player.hasPermission("campfirecheckpoints.use")) { return; } @@ -80,7 +80,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { UUID playerUUID = player.getUniqueId(); Location blockLocation = clickedBlock.getLocation(); - + CheckpointManager checkpointManager = plugin.getCheckpointManager(); ConfigManager configManager = plugin.getConfigManager(); @@ -109,7 +109,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { String.format("(%d, %d, %d)", existingLoc.getBlockX(), existingLoc.getBlockY(), existingLoc.getBlockZ()) : "(unknown)"; - + int timeout = configManager.getOverrideConfirmationTimeout(); MessageUtil.send(player, "&cWarning: &eYou have a checkpoint at " + existingCoords + " within " + radius + " blocks!"); @@ -153,7 +153,7 @@ private boolean isInteractItem(@NotNull Material material) { private void playCheckpointEffects(@NotNull Player player, @NotNull Location location) { ConfigManager configManager = plugin.getConfigManager(); World world = location.getWorld(); - + if (world == null) { return; } @@ -188,7 +188,7 @@ private void playCheckpointEffects(@NotNull Player player, @NotNull Location loc public void onPlayerDeath(@NotNull PlayerDeathEvent event) { Player player = event.getEntity(); Location deathLoc = player.getLocation(); - + if (deathLoc.getWorld() != null) { deathLocations.put(player.getUniqueId(), deathLoc.clone()); } @@ -198,7 +198,7 @@ public void onPlayerDeath(@NotNull PlayerDeathEvent event) { public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { Player player = event.getPlayer(); UUID playerUUID = player.getUniqueId(); - + Location deathLocation = deathLocations.remove(playerUUID); if (deathLocation == null) { return; @@ -206,12 +206,12 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { CheckpointManager checkpointManager = plugin.getCheckpointManager(); ConfigManager configManager = plugin.getConfigManager(); - + Checkpoint closestCheckpoint = checkpointManager.findClosestCheckpoint(playerUUID, deathLocation); - + Location bedSpawn = player.getRespawnLocation(); boolean hasBedSpawn = bedSpawn != null && event.isBedSpawn(); - + RespawnResult respawnResult = determineRespawnLocation( closestCheckpoint, bedSpawn, @@ -220,7 +220,7 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { configManager.getRespawnPriority(), configManager.getRadius() ); - + if (respawnResult == null || respawnResult.location == null) { // No valid respawn location, let vanilla handle it return; @@ -235,7 +235,7 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { final boolean usedCheckpoint = respawnResult.isCheckpoint; final boolean usedBed = respawnResult.isBed; final boolean extinguished = respawnResult.isCheckpoint && configManager.isExtinguishOnRespawn(); - + Bukkit.getScheduler().runTaskLater(plugin, () -> { if (player.isOnline()) { if (usedCheckpoint) { @@ -259,39 +259,39 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { @NotNull Location deathLocation, @NotNull RespawnPriority priority, double radius) { - + // Get checkpoint spawn location if available Location checkpointSpawn = null; boolean hasValidCheckpoint = false; - + if (checkpoint != null) { checkpointSpawn = checkpoint.getSpawnLocation(); hasValidCheckpoint = checkpointSpawn != null && checkpointSpawn.getWorld() != null; } - + // If neither is available, return null if (!hasValidCheckpoint && !hasBedSpawn) { return null; } - + // If only checkpoint is available if (hasValidCheckpoint && !hasBedSpawn) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); } - + // If only bed is available if (!hasValidCheckpoint && hasBedSpawn) { return new RespawnResult(bedSpawn, false, true, null); } - + // Both are available - use priority setting switch (priority) { case CHECKPOINT: return new RespawnResult(checkpointSpawn, true, false, checkpoint); - + case BED: return new RespawnResult(bedSpawn, false, true, null); - + case CLOSEST: return determineClosestRespawn( checkpoint, checkpointSpawn, @@ -299,7 +299,7 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { deathLocation, radius ); - + default: return new RespawnResult(checkpointSpawn, true, false, checkpoint); } @@ -314,25 +314,25 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { @NotNull Location bedSpawn, @NotNull Location deathLocation, double radius) { - + double checkpointDistSq = checkpoint.distanceSquared(deathLocation); double bedDistSq = calculateDistanceSquared(bedSpawn, deathLocation); - + // Check if bed is in a different world if (bedSpawn.getWorld() == null || !bedSpawn.getWorld().equals(deathLocation.getWorld())) { // Bed is in different world, use checkpoint return new RespawnResult(checkpointSpawn, true, false, checkpoint); } - + // Check if bed is within radius double radiusSquared = radius * radius; boolean bedWithinRadius = bedDistSq <= radiusSquared; - + // If bed is not within radius, use checkpoint if (!bedWithinRadius) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); } - + // Both are within radius, use the closer one if (checkpointDistSq <= bedDistSq) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); @@ -348,7 +348,7 @@ private double calculateDistanceSquared(@NotNull Location a, @NotNull Location b if (!a.getWorld().equals(b.getWorld())) { return Double.MAX_VALUE; } - + double dx = a.getX() - b.getX(); double dy = a.getY() - b.getY(); double dz = a.getZ() - b.getZ(); @@ -359,13 +359,13 @@ private void handleCheckpointRespawn( @NotNull Checkpoint checkpoint, @NotNull CheckpointManager checkpointManager, @NotNull ConfigManager configManager) { - + if (configManager.isExtinguishOnRespawn()) { Location blockLoc = checkpoint.getBlockLocation(); if (blockLoc != null && blockLoc.getWorld() != null) { Block campfireBlock = blockLoc.getBlock(); BlockData blockData = campfireBlock.getBlockData(); - + if (blockData instanceof Lightable lightable) { lightable.setLit(false); campfireBlock.setBlockData(lightable); @@ -380,7 +380,7 @@ private static final class RespawnResult { final boolean isCheckpoint; final boolean isBed; final @Nullable Checkpoint checkpoint; - + RespawnResult(@Nullable Location location, boolean isCheckpoint, boolean isBed, @Nullable Checkpoint checkpoint) { this.location = location; @@ -401,16 +401,16 @@ public void onPlayerQuit(@NotNull PlayerQuitEvent event) { public void onBlockBreak(@NotNull BlockBreakEvent event) { Block block = event.getBlock(); Material type = block.getType(); - + if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) { return; } Location blockLocation = block.getLocation(); CheckpointManager checkpointManager = plugin.getCheckpointManager(); - + Checkpoint checkpoint = checkpointManager.removeCheckpointAt(blockLocation); - + if (checkpoint != null) { Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); if (owner != null && owner.isOnline()) { @@ -419,7 +419,7 @@ public void onBlockBreak(@NotNull BlockBreakEvent event) { blockLocation.getBlockY() + ", " + blockLocation.getBlockZ() + ") &chas been destroyed!"); } - + Player breaker = event.getPlayer(); if (!breaker.getUniqueId().equals(checkpoint.getOwnerUUID())) { MessageUtil.send(breaker, "&7You destroyed a checkpoint belonging to another player."); @@ -432,7 +432,7 @@ public void onCampfireExtinguish(@NotNull PlayerInteractEvent event) { if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { return; } - + Block clickedBlock = event.getClickedBlock(); if (clickedBlock == null) { return; @@ -445,7 +445,7 @@ public void onCampfireExtinguish(@NotNull PlayerInteractEvent event) { Player player = event.getPlayer(); Material itemInHand = player.getInventory().getItemInMainHand().getType(); - + if (!isShovel(itemInHand) && itemInHand != Material.WATER_BUCKET) { return; } @@ -459,13 +459,13 @@ public void onCampfireExtinguish(@NotNull PlayerInteractEvent event) { Bukkit.getScheduler().runTaskLater(plugin, () -> { Block block = blockLoc.getBlock(); BlockData newBlockData = block.getBlockData(); - + if (newBlockData instanceof Lightable newLightable && !newLightable.isLit()) { Checkpoint checkpoint = plugin.getCheckpointManager().getCheckpointAt(blockLoc); if (checkpoint != null && checkpoint.isLit()) { checkpoint.setLit(false); plugin.getCheckpointManager().setCheckpointLit(checkpoint, false); - + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); if (owner != null && owner.isOnline()) { MessageUtil.send(owner, "&eYour campfire checkpoint at &f(" + @@ -483,7 +483,7 @@ public void onCampfireLight(@NotNull PlayerInteractEvent event) { if (event.getAction() != Action.RIGHT_CLICK_BLOCK) { return; } - + Block clickedBlock = event.getClickedBlock(); if (clickedBlock == null) { return; @@ -496,7 +496,7 @@ public void onCampfireLight(@NotNull PlayerInteractEvent event) { Player player = event.getPlayer(); Material itemInHand = player.getInventory().getItemInMainHand().getType(); - + if (itemInHand != Material.FLINT_AND_STEEL && itemInHand != Material.FIRE_CHARGE) { return; } @@ -510,13 +510,13 @@ public void onCampfireLight(@NotNull PlayerInteractEvent event) { Bukkit.getScheduler().runTaskLater(plugin, () -> { Block block = blockLoc.getBlock(); BlockData newBlockData = block.getBlockData(); - + if (newBlockData instanceof Lightable newLightable && newLightable.isLit()) { Checkpoint checkpoint = plugin.getCheckpointManager().getCheckpointAt(blockLoc); if (checkpoint != null && !checkpoint.isLit()) { checkpoint.setLit(true); plugin.getCheckpointManager().setCheckpointLit(checkpoint, true); - + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); if (owner != null && owner.isOnline()) { MessageUtil.send(owner, "&aYour campfire checkpoint at &f(" + diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 509847b..ae3bf00 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -65,7 +65,7 @@ public void loadCheckpoints() { try (FileReader reader = new FileReader(dataFile)) { JsonObject root = JsonParser.parseReader(reader).getAsJsonObject(); - + if (root.has("checkpoints")) { JsonArray checkpointsArray = root.getAsJsonArray("checkpoints"); int loaded = 0; @@ -102,7 +102,7 @@ private void markDirty() { if (saveTask != null && !saveTask.isCancelled()) { saveTask.cancel(); } - + saveTask = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> { if (dirty.compareAndSet(true, false)) { saveCheckpointsInternal(); @@ -115,7 +115,7 @@ public void shutdown() { if (saveTask != null && !saveTask.isCancelled()) { saveTask.cancel(); } - + if (dirty.get()) { saveCheckpointsInternal(); @@ -201,7 +201,7 @@ public boolean removeCheckpoint(@NotNull UUID playerUUID, int index) { if (checkpoints == null) { return false; } - + Checkpoint removed; synchronized (checkpoints) { if (index < 0 || index >= checkpoints.size()) { @@ -212,7 +212,7 @@ public boolean removeCheckpoint(@NotNull UUID playerUUID, int index) { playerCheckpoints.remove(playerUUID); } } - + if (removed != null) { locationIndex.remove(removed.getLocationKey()); } @@ -223,7 +223,7 @@ public boolean removeCheckpoint(@NotNull UUID playerUUID, int index) { public @Nullable Checkpoint removeCheckpointAt(@NotNull Location location) { String locationKey = createLocationKey(location); Checkpoint checkpoint = locationIndex.remove(locationKey); - + if (checkpoint != null) { List checkpoints = playerCheckpoints.get(checkpoint.getOwnerUUID()); if (checkpoints != null) { @@ -236,7 +236,7 @@ public boolean removeCheckpoint(@NotNull UUID playerUUID, int index) { } markDirty(); } - + return checkpoint; } @@ -354,7 +354,7 @@ private void executeOverrideInternal(@NotNull UUID playerUUID, @NotNull PendingO } locationIndex.remove(pending.toOverride.getLocationKey()); } - + // Add new checkpoint Checkpoint newCheckpoint = new Checkpoint(playerUUID, pending.newLocation); addCheckpoint(playerUUID, newCheckpoint); @@ -367,7 +367,7 @@ public boolean hasPendingOverride(@NotNull UUID playerUUID) { if (pending == null) { return false; } - + long timeout = plugin.getConfigManager().getOverrideConfirmationTimeoutMillis(); if (System.currentTimeMillis() - pending.timestamp > timeout) { pendingOverrides.remove(playerUUID); @@ -386,7 +386,7 @@ public boolean hasPendingOverride(@NotNull UUID playerUUID) { if (pending == null) { return null; } - + long timeout = plugin.getConfigManager().getOverrideConfirmationTimeoutMillis(); if (System.currentTimeMillis() - pending.timestamp > timeout) { pendingOverrides.remove(playerUUID); diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index c8308d4..17dd282 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -12,7 +12,7 @@ public final class ConfigManager { private final @NotNull CampfireCheckpoints plugin; - + // Cached config values private int radius; private boolean extinguishOnRespawn; diff --git a/src/main/java/com/campfirecheckpoints/model/RespawnPriority.java b/src/main/java/com/campfirecheckpoints/model/RespawnPriority.java index d673897..9acfc6a 100644 --- a/src/main/java/com/campfirecheckpoints/model/RespawnPriority.java +++ b/src/main/java/com/campfirecheckpoints/model/RespawnPriority.java @@ -6,17 +6,17 @@ public enum RespawnPriority { - + /** * Campfire checkpoint always takes priority over bed */ CHECKPOINT("checkpoint"), - + /** * Bed spawn always takes priority over checkpoint */ BED("bed"), - + /** * Whichever is closer to the death location takes priority */ @@ -37,14 +37,14 @@ public enum RespawnPriority { if (value == null) { return CHECKPOINT; } - + String lowercaseValue = value.toLowerCase().trim(); for (RespawnPriority priority : values()) { if (priority.configValue.equals(lowercaseValue)) { return priority; } } - + return CHECKPOINT; } From 2dd60898d901ea682c903b98aafa843f1426f900 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 13:19:33 +0300 Subject: [PATCH 02/50] Added toggle for campfire types --- .../command/CheckpointCommand.java | 2 ++ .../listener/CheckpointListener.java | 33 ++++++++++++++++++- .../manager/ConfigManager.java | 24 ++++++++++++-- src/main/resources/config.yml | 4 +++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index 8bafd13..fc04f5b 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -182,6 +182,8 @@ private void handleInfo(@NotNull Player player) { MessageUtil.send(player, "&eYour Checkpoints: &f" + playerCount + (max > 0 ? " / " + max : "")); MessageUtil.send(player, "&eTotal Checkpoints: &f" + totalCount); + MessageUtil.send(player, "&eRegular campfires enabled: &f" + configManager.RegularCampfiresEnabled()); + MessageUtil.send(player, "&eSoul campfires enabled: &f" + configManager.SoulCampfiresEnabled()); MessageUtil.send(player, "&eRadius: &f" + configManager.getRadius() + " blocks"); MessageUtil.send(player, "&eExtinguish on Respawn: &f" + configManager.isExtinguishOnRespawn()); MessageUtil.send(player, "&eConfirmation Timeout: &f" + diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 4f94c57..3395bf2 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -58,6 +58,14 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { return; } + ConfigManager configManager = plugin.getConfigManager(); + if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { + return; + }; + if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { + return; + }; + Player player = event.getPlayer(); if (!player.hasPermission("campfirecheckpoints.use")) { @@ -82,7 +90,6 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { Location blockLocation = clickedBlock.getLocation(); CheckpointManager checkpointManager = plugin.getCheckpointManager(); - ConfigManager configManager = plugin.getConfigManager(); if (checkpointManager.hasCheckpointAt(playerUUID, blockLocation)) { MessageUtil.send(player, "&eYou already have a checkpoint at this campfire!"); @@ -406,6 +413,14 @@ public void onBlockBreak(@NotNull BlockBreakEvent event) { return; } + ConfigManager configManager = plugin.getConfigManager(); + if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { + return; + }; + if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { + return; + }; + Location blockLocation = block.getLocation(); CheckpointManager checkpointManager = plugin.getCheckpointManager(); @@ -443,6 +458,14 @@ public void onCampfireExtinguish(@NotNull PlayerInteractEvent event) { return; } + ConfigManager configManager = plugin.getConfigManager(); + if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { + return; + }; + if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { + return; + }; + Player player = event.getPlayer(); Material itemInHand = player.getInventory().getItemInMainHand().getType(); @@ -494,6 +517,14 @@ public void onCampfireLight(@NotNull PlayerInteractEvent event) { return; } + ConfigManager configManager = plugin.getConfigManager(); + if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { + return; + }; + if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { + return; + }; + Player player = event.getPlayer(); Material itemInHand = player.getInventory().getItemInMainHand().getType(); diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 17dd282..4ba7ec3 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -14,6 +14,8 @@ public final class ConfigManager { private final @NotNull CampfireCheckpoints plugin; // Cached config values + private boolean enableRegularCampfires; + private boolean enableSoulCampfires; private int radius; private boolean extinguishOnRespawn; private @NotNull Sound soundOnSet; @@ -22,6 +24,8 @@ public final class ConfigManager { private @NotNull RespawnPriority respawnPriority; // Default values + private static final boolean DEFAULT_ENABLE_REGULAR = true; + private static final boolean DEFAULT_ENABLE_SOUL = true; private static final int DEFAULT_RADIUS = 500; private static final boolean DEFAULT_EXTINGUISH = true; private static final Sound DEFAULT_SOUND = Sound.BLOCK_RESPAWN_ANCHOR_SET_SPAWN; @@ -39,6 +43,9 @@ public ConfigManager(@NotNull CampfireCheckpoints plugin) { public void reload() { FileConfiguration config = plugin.getConfig(); + this.enableRegularCampfires = config.GetBoolean("enable-regular-campfires", DEFAULT_ENABLE_REGULAR); + this.enableSoulCampfires = config.GetBoolean("enable-soul-campfires", DEFAULT_ENABLE_SOUL); + // Load radius this.radius = config.getInt("radius", DEFAULT_RADIUS); if (radius <= 0) { @@ -72,19 +79,30 @@ public void reload() { try { this.soundOnSet = Sound.valueOf(soundName.toUpperCase()); } catch (IllegalArgumentException e) { - plugin.getLogger().log(Level.WARNING, + plugin.getLogger().log(Level.WARNING, "Invalid sound '" + soundName + "' in config. Using default."); this.soundOnSet = DEFAULT_SOUND; } - plugin.getLogger().info("Configuration loaded - Radius: " + radius + - ", Extinguish: " + extinguishOnRespawn + + plugin.getLogger().info("Configuration loaded " + + "- Regular campfires: " + enableRegularCampfires + + ", Soul campfires: " + enableSoulCampfires + + ", Radius: " + radius + + ", Extinguish: " + extinguishOnRespawn + ", Timeout: " + overrideConfirmationTimeout + "s" + ", MaxCheckpoints: " + (maxCheckpointsPerPlayer == 0 ? "unlimited" : maxCheckpointsPerPlayer) + ", RespawnPriority: " + respawnPriority.getConfigValue() + ", Sound: " + soundOnSet.name()); } + public boolean RegularCampfiresEnabled() { + return enableRegularCampfires; + } + + public boolean SoulCampfiresEnabled() { + return enableSoulCampfires; + } + public int getRadius() { return radius; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1621755..2803762 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,6 +3,10 @@ # Campfire Checkpoints Configuration # ================================ + +enable-regular-campfires: true +enable-soul-campfires: true + # The maximum radius (in blocks) for checkpoint operations: # - When setting a checkpoint, checks for existing ones within this radius # - When respawning, finds the closest checkpoint within this radius of death From 9492af823b0ee7f7362c23e99413bbfbe9202cb5 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 13:19:58 +0300 Subject: [PATCH 03/50] Gitignore added --- .gitignore | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4f1e69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Игнорируем файлы IDE +/.idea/ +/.vscode/ +*.iml +*.ipr +*.iws + +# Игнорируем сгенерированные файлы Maven +/target/ +/logs/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties + +# Игнорируем временные файлы +*.tmp +*.bak +*.swp +*~.nib +*.orig + +# Игнорируем системные файлы +.DS_Store +Thumbs.db + +# Игнорируем локальные настройки +/settings/ +*.prefs + +# Игнорируем билды плагина (кроме финального jar) +!target/*.jar +target/*.jar.* + +# Игнорируем кэш Maven (не нужно коммитить) +/.mvn/ +mvnw +mvnw.cmd + +# Игнорируем тестовые серверные файлы +/server/ +/run/ +/plugins/ +/world*/ +/logs/ +*.log \ No newline at end of file From 125cb8a75ba3bdf149f7e746a9da2ddeebf56888 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 15:08:15 +0300 Subject: [PATCH 04/50] Version tweak --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 656e508..60cbf74 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.campfirecheckpoints CampfireCheckpoints - 1.1.0 + 1.2.0-parar jar CampfireCheckpoints From 9c8f427f8e8db496d107cdb357d2c1e15c2c291a Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 15:08:22 +0300 Subject: [PATCH 05/50] small fix --- .../java/com/campfirecheckpoints/manager/ConfigManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 4ba7ec3..b90d1b1 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -43,8 +43,8 @@ public ConfigManager(@NotNull CampfireCheckpoints plugin) { public void reload() { FileConfiguration config = plugin.getConfig(); - this.enableRegularCampfires = config.GetBoolean("enable-regular-campfires", DEFAULT_ENABLE_REGULAR); - this.enableSoulCampfires = config.GetBoolean("enable-soul-campfires", DEFAULT_ENABLE_SOUL); + this.enableRegularCampfires = config.getBoolean("enable-regular-campfires", DEFAULT_ENABLE_REGULAR); + this.enableSoulCampfires = config.getBoolean("enable-soul-campfires", DEFAULT_ENABLE_SOUL); // Load radius this.radius = config.getInt("radius", DEFAULT_RADIUS); From 85e9d7266242ba9ae0a70bb99443242fac0189b3 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 15:24:07 +0300 Subject: [PATCH 06/50] Removed checks for extinguish or remove --- .../listener/CheckpointListener.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 3395bf2..709d8dc 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -413,14 +413,6 @@ public void onBlockBreak(@NotNull BlockBreakEvent event) { return; } - ConfigManager configManager = plugin.getConfigManager(); - if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { - return; - }; - if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { - return; - }; - Location blockLocation = block.getLocation(); CheckpointManager checkpointManager = plugin.getCheckpointManager(); @@ -458,14 +450,6 @@ public void onCampfireExtinguish(@NotNull PlayerInteractEvent event) { return; } - ConfigManager configManager = plugin.getConfigManager(); - if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { - return; - }; - if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { - return; - }; - Player player = event.getPlayer(); Material itemInHand = player.getInventory().getItemInMainHand().getType(); From 41d657f912b97c75288ab235bca46a252876e11b Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sun, 8 Mar 2026 15:49:20 +0300 Subject: [PATCH 07/50] Min_distance parameter split --- .../command/CheckpointCommand.java | 1 + .../listener/CheckpointListener.java | 6 +++--- .../campfirecheckpoints/manager/ConfigManager.java | 13 +++++++++++++ src/main/resources/config.yml | 9 ++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index fc04f5b..cbe8f69 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -184,6 +184,7 @@ private void handleInfo(@NotNull Player player) { MessageUtil.send(player, "&eTotal Checkpoints: &f" + totalCount); MessageUtil.send(player, "&eRegular campfires enabled: &f" + configManager.RegularCampfiresEnabled()); MessageUtil.send(player, "&eSoul campfires enabled: &f" + configManager.SoulCampfiresEnabled()); + MessageUtil.send(player, "&eMinimal distance between checkpoints: &f" + configManager.getMinDistance() + " blocks"); MessageUtil.send(player, "&eRadius: &f" + configManager.getRadius() + " blocks"); MessageUtil.send(player, "&eExtinguish on Respawn: &f" + configManager.isExtinguishOnRespawn()); MessageUtil.send(player, "&eConfirmation Timeout: &f" + diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 709d8dc..4056226 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -104,9 +104,9 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { return; } - int radius = configManager.getRadius(); + int min_distance = configManager.getMinDistance(); Checkpoint existingNearby = checkpointManager.findCheckpointWithinRadius( - playerUUID, blockLocation, radius + playerUUID, blockLocation, min_distance ); if (existingNearby != null) { @@ -119,7 +119,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { int timeout = configManager.getOverrideConfirmationTimeout(); MessageUtil.send(player, "&cWarning: &eYou have a checkpoint at " + existingCoords + - " within " + radius + " blocks!"); + " within " + min_distance + " blocks!"); MessageUtil.send(player, "&eRight-click again within " + timeout + " seconds to override it."); event.setCancelled(true); return; diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index b90d1b1..25f291f 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -17,6 +17,7 @@ public final class ConfigManager { private boolean enableRegularCampfires; private boolean enableSoulCampfires; private int radius; + private int minDistance; private boolean extinguishOnRespawn; private @NotNull Sound soundOnSet; private int overrideConfirmationTimeout; @@ -27,6 +28,7 @@ public final class ConfigManager { private static final boolean DEFAULT_ENABLE_REGULAR = true; private static final boolean DEFAULT_ENABLE_SOUL = true; private static final int DEFAULT_RADIUS = 500; + private static final int DEFAULT_MIN_DISTANCE = 500; private static final boolean DEFAULT_EXTINGUISH = true; private static final Sound DEFAULT_SOUND = Sound.BLOCK_RESPAWN_ANCHOR_SET_SPAWN; private static final int DEFAULT_OVERRIDE_TIMEOUT = 5; @@ -53,6 +55,12 @@ public void reload() { this.radius = DEFAULT_RADIUS; } + this.minDistance = config.getInt("min-distance", DEFAULT_MIN_DISTANCE); + if (minDistance <= 0) { + plugin.getLogger().warning("Invalid min distance in config. Using default: " + DEFAULT_MIN_DISTANCE); + this.minDistance = DEFAULT_MIN_DISTANCE; + } + // Load extinguish-on-respawn this.extinguishOnRespawn = config.getBoolean("extinguish-on-respawn", DEFAULT_EXTINGUISH); @@ -87,6 +95,7 @@ public void reload() { plugin.getLogger().info("Configuration loaded " + "- Regular campfires: " + enableRegularCampfires + ", Soul campfires: " + enableSoulCampfires + + ", Min. distance: " + minDistance + ", Radius: " + radius + ", Extinguish: " + extinguishOnRespawn + ", Timeout: " + overrideConfirmationTimeout + "s" + @@ -107,6 +116,10 @@ public int getRadius() { return radius; } + public int getMinDistance() { + return minDistance; + } + public boolean isExtinguishOnRespawn() { return extinguishOnRespawn; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2803762..3fc0e2c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,13 +3,16 @@ # Campfire Checkpoints Configuration # ================================ - +# Configure the campfire types used in the plugin enable-regular-campfires: true enable-soul-campfires: true +# The minimal distance (in blocks) between checkpoints: +# When setting a checkpoint, checks for existing ones within this radius +min-distance: 500 + # The maximum radius (in blocks) for checkpoint operations: -# - When setting a checkpoint, checks for existing ones within this radius -# - When respawning, finds the closest checkpoint within this radius of death +# When respawning, finds the closest checkpoint within this radius of death radius: 500 # If true, the campfire will be extinguished after a player respawns at it. From 28bfd595f1a8a28816f62b383a6691703e55d890 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 11:41:39 +0300 Subject: [PATCH 08/50] Different radius for soul campfires --- .../command/CheckpointCommand.java | 6 ++-- .../listener/CheckpointListener.java | 5 +++ .../manager/CheckpointManager.java | 5 ++- .../manager/ConfigManager.java | 30 ++++++++++++++-- .../campfirecheckpoints/model/Checkpoint.java | 34 ++++++++++++++++--- src/main/resources/config.yml | 16 ++++++--- 6 files changed, 82 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index cbe8f69..ed7aa74 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -184,8 +184,10 @@ private void handleInfo(@NotNull Player player) { MessageUtil.send(player, "&eTotal Checkpoints: &f" + totalCount); MessageUtil.send(player, "&eRegular campfires enabled: &f" + configManager.RegularCampfiresEnabled()); MessageUtil.send(player, "&eSoul campfires enabled: &f" + configManager.SoulCampfiresEnabled()); - MessageUtil.send(player, "&eMinimal distance between checkpoints: &f" + configManager.getMinDistance() + " blocks"); - MessageUtil.send(player, "&eRadius: &f" + configManager.getRadius() + " blocks"); + MessageUtil.send(player, "&eRadius (regular campfires): &f" + configManager.getRadius() + " blocks"); + MessageUtil.send(player, "&eRadius (soul campfires): &f" + configManager.getSoulRadius() + " blocks"); + MessageUtil.send(player, "&eMin. distance between checkpoints: &f" + configManager.getMinDistance() + " blocks"); + MessageUtil.send(player, "&eMin. distance between checkpoints (soul): &f" + configManager.getSoulMinDistance() + " blocks"); MessageUtil.send(player, "&eExtinguish on Respawn: &f" + configManager.isExtinguishOnRespawn()); MessageUtil.send(player, "&eConfirmation Timeout: &f" + configManager.getOverrideConfirmationTimeout() + "s"); diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 4056226..69a5e54 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -105,6 +105,11 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { } int min_distance = configManager.getMinDistance(); + + if (type == Material.SOUL_CAMPFIRE) { + min_distance = configManager.getSoulMinDistance(); + }; + Checkpoint existingNearby = checkpointManager.findCheckpointWithinRadius( playerUUID, blockLocation, min_distance ); diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index ae3bf00..56d998f 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -268,10 +268,13 @@ public boolean removeCheckpoint(@NotNull UUID playerUUID, int index) { double radius = plugin.getConfigManager().getRadius(); double radiusSquared = radius * radius; + double radiusSoul = plugin.getConfigManager().getSoulRadius(); + double radiusSoulSquared = radiusSoul * radiusSoul; + synchronized (checkpoints) { return checkpoints.stream() .filter(Checkpoint::isLit) - .filter(cp -> cp.distanceSquared(deathLocation) <= radiusSquared) + .filter(cp -> cp.isWithinRespawnRadius(deathLocation, radiusSquared, radiusSoulSquared)) .min(Comparator.comparingDouble(cp -> cp.distanceSquared(deathLocation))) .orElse(null); } diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 25f291f..99164e2 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -17,7 +17,9 @@ public final class ConfigManager { private boolean enableRegularCampfires; private boolean enableSoulCampfires; private int radius; + private int soulRadius; private int minDistance; + private int soulMinDistance; private boolean extinguishOnRespawn; private @NotNull Sound soundOnSet; private int overrideConfirmationTimeout; @@ -28,7 +30,9 @@ public final class ConfigManager { private static final boolean DEFAULT_ENABLE_REGULAR = true; private static final boolean DEFAULT_ENABLE_SOUL = true; private static final int DEFAULT_RADIUS = 500; - private static final int DEFAULT_MIN_DISTANCE = 500; + private static final int DEFAULT_SOUL_RADIUS = 1000; + private static final int DEFAULT_MIN_DISTANCE = 250; + private static final int DEFAULT_SOUL_MIN_DISTANCE = 500; private static final boolean DEFAULT_EXTINGUISH = true; private static final Sound DEFAULT_SOUND = Sound.BLOCK_RESPAWN_ANCHOR_SET_SPAWN; private static final int DEFAULT_OVERRIDE_TIMEOUT = 5; @@ -55,12 +59,24 @@ public void reload() { this.radius = DEFAULT_RADIUS; } + this.soulRadius = config.getInt("soul-campfire-radius", DEFAULT_SOUL_RADIUS); + if (radius <= 0) { + plugin.getLogger().warning("Invalid soul campfire radius in config. Using default: " + DEFAULT_SOUL_RADIUS); + this.soulRadius = DEFAULT_SOUL_RADIUS; + } + this.minDistance = config.getInt("min-distance", DEFAULT_MIN_DISTANCE); if (minDistance <= 0) { plugin.getLogger().warning("Invalid min distance in config. Using default: " + DEFAULT_MIN_DISTANCE); this.minDistance = DEFAULT_MIN_DISTANCE; } + this.soulMinDistance = config.getInt("soul-campfire-min-distance", DEFAULT_SOUL_MIN_DISTANCE); + if (minDistance <= 0) { + plugin.getLogger().warning("Invalid min distance in config. Using default: " + DEFAULT_SOUL_MIN_DISTANCE); + this.soulMinDistance = DEFAULT_SOUL_MIN_DISTANCE; + } + // Load extinguish-on-respawn this.extinguishOnRespawn = config.getBoolean("extinguish-on-respawn", DEFAULT_EXTINGUISH); @@ -95,8 +111,10 @@ public void reload() { plugin.getLogger().info("Configuration loaded " + "- Regular campfires: " + enableRegularCampfires + ", Soul campfires: " + enableSoulCampfires + - ", Min. distance: " + minDistance + ", Radius: " + radius + + ", Radius (soul campfires): " + soulRadius + + ", Min. distance: " + minDistance + + ", Min. distance (soul campfires): " + soulMinDistance + ", Extinguish: " + extinguishOnRespawn + ", Timeout: " + overrideConfirmationTimeout + "s" + ", MaxCheckpoints: " + (maxCheckpointsPerPlayer == 0 ? "unlimited" : maxCheckpointsPerPlayer) + @@ -116,10 +134,18 @@ public int getRadius() { return radius; } + public int getSoulRadius() { + return soulRadius; + } + public int getMinDistance() { return minDistance; } + public int getSoulMinDistance() { + return soulMinDistance; + } + public boolean isExtinguishOnRespawn() { return extinguishOnRespawn; } diff --git a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java index 148a93b..2c0d418 100644 --- a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java +++ b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java @@ -3,6 +3,7 @@ import com.google.gson.JsonObject; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.Location; import org.bukkit.World; import org.jetbrains.annotations.NotNull; @@ -20,6 +21,7 @@ public final class Checkpoint { private final int z; private final long createdAt; private volatile boolean lit; + private volatile boolean soul; public Checkpoint(@NotNull UUID ownerUUID, @NotNull Location location) { Objects.requireNonNull(ownerUUID, "Owner UUID cannot be null"); @@ -33,10 +35,13 @@ public Checkpoint(@NotNull UUID ownerUUID, @NotNull Location location) { this.z = location.getBlockZ(); this.createdAt = System.currentTimeMillis(); this.lit = true; + + this.soul = (location.getBlock().getType() == Material.SOUL_CAMPFIRE); } - private Checkpoint(@NotNull UUID ownerUUID, @NotNull String worldName, - int x, int y, int z, long createdAt, boolean lit) { + private Checkpoint(@NotNull UUID ownerUUID, @NotNull String worldName, + int x, int y, int z, long createdAt, boolean lit, + boolean soul) { this.ownerUUID = ownerUUID; this.worldName = worldName; this.x = x; @@ -44,6 +49,7 @@ private Checkpoint(@NotNull UUID ownerUUID, @NotNull String worldName, this.z = z; this.createdAt = createdAt; this.lit = lit; + this.soul = soul; } public @NotNull UUID getOwnerUUID() { @@ -78,6 +84,14 @@ public void setLit(boolean lit) { this.lit = lit; } + public boolean isSoul() { + return soul; + } + + public void setSoul(boolean soul) { + this.soul = soul; + } + public @Nullable Location getSpawnLocation() { World world = Bukkit.getWorld(worldName); @@ -111,6 +125,14 @@ public double distanceSquared(@Nullable Location location) { } + public boolean isWithinRespawnRadius(@Nullable Location location, + double radiusSquaredRegular, + double radiusSquaredSoul) { + double radiusSquared = soul ? radiusSquaredSoul : radiusSquaredRegular; + return (distanceSquared(location) <= radiusSquared); + } + + public boolean isWithinRadius(@Nullable Location location, double radius) { if (location == null) { return false; @@ -130,6 +152,7 @@ public boolean isWithinRadius(@Nullable Location location, double radius) { json.addProperty("z", z); json.addProperty("createdAt", createdAt); json.addProperty("lit", lit); + json.addProperty("soul", soul); return json; } @@ -147,8 +170,9 @@ public boolean isWithinRadius(@Nullable Location location, double radius) { long createdAt = json.has("createdAt") ? json.get("createdAt").getAsLong() : System.currentTimeMillis(); boolean lit = !json.has("lit") || json.get("lit").getAsBoolean(); + boolean soul = json.has("soul") && json.get("soul").getAsBoolean(); - return new Checkpoint(ownerUUID, world, x, y, z, createdAt, lit); + return new Checkpoint(ownerUUID, world, x, y, z, createdAt, lit, soul); } catch (Exception e) { return null; } @@ -178,7 +202,7 @@ public int hashCode() { @Override public String toString() { - return String.format("Checkpoint{owner=%s, world=%s, pos=[%d, %d, %d], lit=%s}", - ownerUUID, worldName, x, y, z, lit); + return String.format("Checkpoint{owner=%s, world=%s, pos=[%d, %d, %d], lit=%s, soul=%s}", + ownerUUID, worldName, x, y, z, lit, soul); } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3fc0e2c..a3426a6 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -7,13 +7,21 @@ enable-regular-campfires: true enable-soul-campfires: true -# The minimal distance (in blocks) between checkpoints: +# The maximum radius (in blocks) for checkpoint respawn (with regular campfires): +# When respawning, finds the closest checkpoint within this radius of death +radius: 500 + +# The minimal distance (in blocks) between checkpoints (with regular campfires): # When setting a checkpoint, checks for existing ones within this radius -min-distance: 500 +min-distance: 250 -# The maximum radius (in blocks) for checkpoint operations: +# The maximum radius (in blocks) for checkpoint respawn (with soul campfires) # When respawning, finds the closest checkpoint within this radius of death -radius: 500 +soul-campfire-radius: 1000 + +# The minimal distance (in blocks) between checkpoints (with soul campfires): +# When setting a checkpoint, checks for existing ones within this radius +soul-campfire-min-distance: 500 # If true, the campfire will be extinguished after a player respawns at it. # The player must re-light it with Flint & Steel to use it again. From 2bfcf6ddc990d51bda4df4e5ea5c9c7a9bfdccca Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 11:55:10 +0300 Subject: [PATCH 09/50] Forbid overrides --- .../listener/CheckpointListener.java | 17 +++++++++++++---- src/main/resources/config.yml | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 69a5e54..ccf06ed 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -115,17 +115,26 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { ); if (existingNearby != null) { - checkpointManager.setPendingOverride(playerUUID, existingNearby, blockLocation); + int timeout = configManager.getOverrideConfirmationTimeout(); + + if (timeout > 0) { + checkpointManager.setPendingOverride(playerUUID, existingNearby, blockLocation); + } + Location existingLoc = existingNearby.getBlockLocation(); String existingCoords = existingLoc != null ? String.format("(%d, %d, %d)", existingLoc.getBlockX(), existingLoc.getBlockY(), existingLoc.getBlockZ()) : "(unknown)"; - int timeout = configManager.getOverrideConfirmationTimeout(); - MessageUtil.send(player, "&cWarning: &eYou have a checkpoint at " + existingCoords + + if (timeout > 0) { + MessageUtil.send(player, "&cWarning: &eYou have a checkpoint at " + existingCoords + + " within " + min_distance + " blocks!"); + MessageUtil.send(player, "&eRight-click again within " + timeout + " seconds to override it."); + } else { + MessageUtil.send(player, "&cFail: &eYou already have a checkpoint nearby at " + existingCoords + " within " + min_distance + " blocks!"); - MessageUtil.send(player, "&eRight-click again within " + timeout + " seconds to override it."); + } event.setCancelled(true); return; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a3426a6..e7fdc23 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -34,6 +34,8 @@ sound-on-set: BLOCK_RESPAWN_ANCHOR_SET_SPAWN # Time in seconds that players have to confirm overriding an existing checkpoint # When a player tries to set a checkpoint within radius of an existing one, # they must right-click again within this time to confirm +# +# Set to 0 to forbid overriding checkpoints override-confirmation-timeout: 5 # Maximum number of checkpoints a player can have (0 = unlimited) From 44835f71d9f9218aa102c435840f598f3242fd49 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 14:57:32 +0300 Subject: [PATCH 10/50] Better compile --- .gitignore | 12 +++--------- compile.sh | 20 ++++++++++++++++++++ pom.xml | 8 ++++++++ 3 files changed, 31 insertions(+), 9 deletions(-) create mode 100755 compile.sh diff --git a/.gitignore b/.gitignore index d4f1e69..aef5026 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ -# Игнорируем файлы IDE /.idea/ /.vscode/ *.iml *.ipr *.iws -# Игнорируем сгенерированные файлы Maven /target/ /logs/ pom.xml.tag @@ -14,34 +12,30 @@ pom.xml.versionsBackup pom.xml.next release.properties -# Игнорируем временные файлы *.tmp *.bak *.swp *~.nib *.orig -# Игнорируем системные файлы .DS_Store Thumbs.db -# Игнорируем локальные настройки /settings/ *.prefs -# Игнорируем билды плагина (кроме финального jar) !target/*.jar target/*.jar.* -# Игнорируем кэш Maven (не нужно коммитить) /.mvn/ mvnw mvnw.cmd -# Игнорируем тестовые серверные файлы /server/ /run/ /plugins/ /world*/ /logs/ -*.log \ No newline at end of file +*.log + +.build_number \ No newline at end of file diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..2ab5d8f --- /dev/null +++ b/compile.sh @@ -0,0 +1,20 @@ +#! /bin/bash + +#CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +#NEW_VERSION=$(echo $CURRENT_VERSION | perl -pe 's/(parar-test)(\d+)/$1.($2+1)/ge') +#mvn versions:set -DnewVersion=$NEW_VERSION + +NUMBER_FILE=".build_number" + +if [ -f "$NUMBER_FILE" ]; then + test_number=$(cat $NUMBER_FILE) +else + test_number=1 +fi + +mvn package -Dbuild_name="-test$test_number" + + +if [ $? -eq 0 ]; then + echo $((test_number + 1)) > $NUMBER_FILE +fi \ No newline at end of file diff --git a/pom.xml b/pom.xml index 60cbf74..539678c 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 21 UTF-8 + @@ -49,6 +50,13 @@ + + maven-jar-plugin + 3.3.0 + + ${project.artifactId}-${project.version}${build_name} + + org.apache.maven.plugins maven-compiler-plugin From 8baca6a2046b16b7e62dee7f38163667a3913939 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 15:29:44 +0300 Subject: [PATCH 11/50] Fix: allow disabling override --- .../java/com/campfirecheckpoints/manager/ConfigManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 99164e2..cd411a4 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -82,7 +82,7 @@ public void reload() { // Load override confirmation timeout this.overrideConfirmationTimeout = config.getInt("override-confirmation-timeout", DEFAULT_OVERRIDE_TIMEOUT); - if (overrideConfirmationTimeout <= 0) { + if (overrideConfirmationTimeout < 0) { plugin.getLogger().warning("Invalid override-confirmation-timeout in config. Using default: " + DEFAULT_OVERRIDE_TIMEOUT); this.overrideConfirmationTimeout = DEFAULT_OVERRIDE_TIMEOUT; } From a1586763bb9a8d1876415f7783959f02eb6042a2 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 15:32:39 +0300 Subject: [PATCH 12/50] Remove all nearby checkpoints during override --- .../listener/CheckpointListener.java | 7 ++- .../manager/CheckpointManager.java | 45 +++++++++++++++---- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index ccf06ed..83c1fba 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -99,7 +99,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { if (checkpointManager.tryExecuteOverride(playerUUID, blockLocation)) { playCheckpointEffects(player, blockLocation); - MessageUtil.send(player, "&aCheckpoint override confirmed! Previous checkpoint removed."); + MessageUtil.send(player, "&aCheckpoint override confirmed!"); event.setCancelled(true); return; } @@ -118,7 +118,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { int timeout = configManager.getOverrideConfirmationTimeout(); if (timeout > 0) { - checkpointManager.setPendingOverride(playerUUID, existingNearby, blockLocation); + checkpointManager.setPendingOverride(playerUUID, blockLocation, min_distance); } Location existingLoc = existingNearby.getBlockLocation(); @@ -132,8 +132,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { " within " + min_distance + " blocks!"); MessageUtil.send(player, "&eRight-click again within " + timeout + " seconds to override it."); } else { - MessageUtil.send(player, "&cFail: &eYou already have a checkpoint nearby at " + existingCoords + - " within " + min_distance + " blocks!"); + MessageUtil.send(player, "&cYou already have a checkpoint nearby within " + min_distance + " blocks!"); } event.setCancelled(true); return; diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 56d998f..953b6e9 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -11,6 +11,8 @@ import com.google.gson.JsonParser; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.entity.Player; +import com.campfirecheckpoints.util.MessageUtil; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -304,12 +306,12 @@ public void setCheckpointLit(@NotNull Checkpoint checkpoint, boolean lit) { } - public void setPendingOverride(@NotNull UUID playerUUID, @NotNull Checkpoint toOverride, - @NotNull Location newLocation) { + public void setPendingOverride(@NotNull UUID playerUUID, @NotNull Location newLocation, double radius) { overrideLock.lock(); try { - pendingOverrides.put(playerUUID, new PendingOverride(toOverride, newLocation, - System.currentTimeMillis())); + pendingOverrides.put(playerUUID, + new PendingOverride(newLocation, radius, + System.currentTimeMillis())); } finally { overrideLock.unlock(); } @@ -349,13 +351,38 @@ private void executeOverrideInternal(@NotNull UUID playerUUID, @NotNull PendingO // Remove old checkpoint List checkpoints = playerCheckpoints.get(playerUUID); if (checkpoints != null) { + List toRemove = new ArrayList<>(); + List keysToRemove = new ArrayList<>(); + synchronized (checkpoints) { - checkpoints.remove(pending.toOverride); + for (Checkpoint checkpoint : checkpoints) { + if (checkpoint.isWithinRadius(pending.newLocation, pending.radius)) { + toRemove.add(checkpoint); + keysToRemove.add(checkpoint.getLocationKey()); + } + } + } + + synchronized (checkpoints) { + for (Checkpoint checkpoint : toRemove) { + checkpoints.remove(checkpoint); + } + if (checkpoints.isEmpty()) { playerCheckpoints.remove(playerUUID); } } - locationIndex.remove(pending.toOverride.getLocationKey()); + + Player owner = Bukkit.getPlayer(playerUUID); + + synchronized(locationIndex) { + for (String key : keysToRemove) { + locationIndex.remove(key); + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&fPrevious checkpoint removed: &e(" + key + ")!"); + } + } + } } // Add new checkpoint @@ -418,13 +445,13 @@ private boolean locationEquals(@NotNull Location a, @NotNull Location b) { } public static final class PendingOverride { - public final @NotNull Checkpoint toOverride; + public final @NotNull double radius; public final @NotNull Location newLocation; public final long timestamp; - public PendingOverride(@NotNull Checkpoint toOverride, @NotNull Location newLocation, + public PendingOverride(@NotNull Location newLocation, double radius, long timestamp) { - this.toOverride = toOverride; + this.radius = radius; this.newLocation = newLocation; this.timestamp = timestamp; } From d9c1c1bce36a81a0c02fe1befe9e8a48d1fe88e7 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 17:19:08 +0300 Subject: [PATCH 13/50] Per-dimention toggle --- .../command/CheckpointCommand.java | 8 ++- .../listener/CheckpointListener.java | 55 +++++++++++++----- .../manager/ConfigManager.java | 56 ++++++++++++++----- src/main/resources/config.yml | 11 +++- 4 files changed, 98 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index ed7aa74..ea9b5d3 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -182,8 +182,12 @@ private void handleInfo(@NotNull Player player) { MessageUtil.send(player, "&eYour Checkpoints: &f" + playerCount + (max > 0 ? " / " + max : "")); MessageUtil.send(player, "&eTotal Checkpoints: &f" + totalCount); - MessageUtil.send(player, "&eRegular campfires enabled: &f" + configManager.RegularCampfiresEnabled()); - MessageUtil.send(player, "&eSoul campfires enabled: &f" + configManager.SoulCampfiresEnabled()); + MessageUtil.send(player, "&eRegular campfires enabled (overworld): &f" + configManager.isDimentionEnabledOverworld()); + MessageUtil.send(player, "&eRegular campfires enabled (nether): &f" + configManager.isDimentionEnabledNether()); + MessageUtil.send(player, "&eRegular campfires enabled (the end): &f" + configManager.isDimentionEnabledEnd()); + MessageUtil.send(player, "&eSoul campfires enabled (overworld): &f" + configManager.isDimentionEnabledOverworldSoul()); + MessageUtil.send(player, "&eSoul campfires enabled (nether): &f" + configManager.isDimentionEnabledNetherSoul()); + MessageUtil.send(player, "&eSoul campfires enabled (the end): &f" + configManager.isDimentionEnabledEndSoul()); MessageUtil.send(player, "&eRadius (regular campfires): &f" + configManager.getRadius() + " blocks"); MessageUtil.send(player, "&eRadius (soul campfires): &f" + configManager.getSoulRadius() + " blocks"); MessageUtil.send(player, "&eMin. distance between checkpoints: &f" + configManager.getMinDistance() + " blocks"); diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 83c1fba..5f2ebc1 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -54,17 +54,30 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { } Material type = clickedBlock.getType(); - if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) { + boolean isRegularCampfire = (type == Material.CAMPFIRE); + boolean isSoulCampfire = (type == Material.SOUL_CAMPFIRE); + + if (!isRegularCampfire && !isSoulCampfire) { return; } ConfigManager configManager = plugin.getConfigManager(); - if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { - return; - }; - if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { - return; - }; + World.Environment env = event.getPlayer().getWorld().getEnvironment(); + + switch (env) { + case NORMAL: + if (isRegularCampfire && !configManager.isDimentionEnabledOverworld()) return; + if (isSoulCampfire && !configManager.isDimentionEnabledOverworldSoul()) return; + break; + case NETHER: + if (isRegularCampfire && !configManager.isDimentionEnabledNether()) return; + if (isSoulCampfire && !configManager.isDimentionEnabledNetherSoul()) return; + break; + case THE_END: + if (isRegularCampfire && !configManager.isDimentionEnabledEnd()) return; + if (isSoulCampfire && !configManager.isDimentionEnabledEndSoul()) return; + break; + } Player player = event.getPlayer(); @@ -510,17 +523,31 @@ public void onCampfireLight(@NotNull PlayerInteractEvent event) { } Material type = clickedBlock.getType(); - if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) { + + boolean isRegularCampfire = (type == Material.CAMPFIRE); + boolean isSoulCampfire = (type == Material.SOUL_CAMPFIRE); + + if (!isRegularCampfire && !isSoulCampfire) { return; } ConfigManager configManager = plugin.getConfigManager(); - if (type == Material.CAMPFIRE && !configManager.RegularCampfiresEnabled()) { - return; - }; - if (type == Material.SOUL_CAMPFIRE && !configManager.SoulCampfiresEnabled()) { - return; - }; + World.Environment env = event.getPlayer().getWorld().getEnvironment(); + + switch (env) { + case NORMAL: + if (isRegularCampfire && !configManager.isDimentionEnabledOverworld()) return; + if (isSoulCampfire && !configManager.isDimentionEnabledOverworldSoul()) return; + break; + case NETHER: + if (isRegularCampfire && !configManager.isDimentionEnabledNether()) return; + if (isSoulCampfire && !configManager.isDimentionEnabledNetherSoul()) return; + break; + case THE_END: + if (isRegularCampfire && !configManager.isDimentionEnabledEnd()) return; + if (isSoulCampfire && !configManager.isDimentionEnabledEndSoul()) return; + break; + } Player player = event.getPlayer(); Material itemInHand = player.getInventory().getItemInMainHand().getType(); diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index cd411a4..d779707 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -14,8 +14,14 @@ public final class ConfigManager { private final @NotNull CampfireCheckpoints plugin; // Cached config values - private boolean enableRegularCampfires; - private boolean enableSoulCampfires; + + private boolean overworldEnableRegularCampfires; + private boolean netherEnableRegularCampfires; + private boolean endEnableRegularCampfires; + private boolean overworldEnableSoulCampfires; + private boolean netherEnableSoulCampfires; + private boolean endEnableSoulCampfires; + private int radius; private int soulRadius; private int minDistance; @@ -27,8 +33,13 @@ public final class ConfigManager { private @NotNull RespawnPriority respawnPriority; // Default values - private static final boolean DEFAULT_ENABLE_REGULAR = true; - private static final boolean DEFAULT_ENABLE_SOUL = true; + private static final boolean DEFAULT_DIMENTION_OVERWORLD = true; + private static final boolean DEFAULT_DIMENTION_NETHER = false; + private static final boolean DEFAULT_DIMENTION_END = false; + private static final boolean DEFAULT_DIMENTION_OVERWORLD_SOUL = true; + private static final boolean DEFAULT_DIMENTION_NETHER_SOUL = true; + private static final boolean DEFAULT_DIMENTION_END_SOUL = false; + private static final int DEFAULT_RADIUS = 500; private static final int DEFAULT_SOUL_RADIUS = 1000; private static final int DEFAULT_MIN_DISTANCE = 250; @@ -49,8 +60,12 @@ public ConfigManager(@NotNull CampfireCheckpoints plugin) { public void reload() { FileConfiguration config = plugin.getConfig(); - this.enableRegularCampfires = config.getBoolean("enable-regular-campfires", DEFAULT_ENABLE_REGULAR); - this.enableSoulCampfires = config.getBoolean("enable-soul-campfires", DEFAULT_ENABLE_SOUL); + this.overworldEnableRegularCampfires = config.getBoolean("enable-overworld", DEFAULT_DIMENTION_OVERWORLD); + this.netherEnableRegularCampfires = config.getBoolean("enable-nether", DEFAULT_DIMENTION_NETHER); + this.endEnableRegularCampfires = config.getBoolean("enable-end", DEFAULT_DIMENTION_END); + this.overworldEnableSoulCampfires = config.getBoolean("enable-soul-overworld", DEFAULT_DIMENTION_OVERWORLD_SOUL); + this.netherEnableSoulCampfires = config.getBoolean("enable-soul-nether", DEFAULT_DIMENTION_NETHER_SOUL); + this.endEnableSoulCampfires = config.getBoolean("enable-soul-end", DEFAULT_DIMENTION_END_SOUL); // Load radius this.radius = config.getInt("radius", DEFAULT_RADIUS); @@ -109,8 +124,12 @@ public void reload() { } plugin.getLogger().info("Configuration loaded " + - "- Regular campfires: " + enableRegularCampfires + - ", Soul campfires: " + enableSoulCampfires + + "- Regular campfires enabled (overworld): " + overworldEnableRegularCampfires + + ", Regular campfires enabled (nether): " + netherEnableRegularCampfires + + ", Regular campfires enabled (end): " + endEnableRegularCampfires + + ", Soul campfires enabled (overworld): " + overworldEnableSoulCampfires + + ", Soul campfires enabled (nether): " + netherEnableSoulCampfires + + ", Soul campfires enabled (end): " + endEnableSoulCampfires + ", Radius: " + radius + ", Radius (soul campfires): " + soulRadius + ", Min. distance: " + minDistance + @@ -122,12 +141,23 @@ public void reload() { ", Sound: " + soundOnSet.name()); } - public boolean RegularCampfiresEnabled() { - return enableRegularCampfires; + public boolean isDimentionEnabledOverworld() { + return overworldEnableRegularCampfires; } - - public boolean SoulCampfiresEnabled() { - return enableSoulCampfires; + public boolean isDimentionEnabledOverworldSoul() { + return overworldEnableSoulCampfires; + } + public boolean isDimentionEnabledNether() { + return netherEnableRegularCampfires; + } + public boolean isDimentionEnabledNetherSoul() { + return netherEnableSoulCampfires; + } + public boolean isDimentionEnabledEnd() { + return endEnableRegularCampfires; + } + public boolean isDimentionEnabledEndSoul() { + return endEnableSoulCampfires; } public int getRadius() { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e7fdc23..27232e4 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,9 +3,14 @@ # Campfire Checkpoints Configuration # ================================ -# Configure the campfire types used in the plugin -enable-regular-campfires: true -enable-soul-campfires: true +# Configure the campfire types used in the dimentions +enable-regular-overworld: true +enable-regular-nether: false +enable-regular-end: false + +enable-soul-overworld: true +enable-soul-nether: true +enable-soul-end: false # The maximum radius (in blocks) for checkpoint respawn (with regular campfires): # When respawning, finds the closest checkpoint within this radius of death From e0a67595b6d828958fd73c0c2e9b1b0f5beabe5d Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 20:21:12 +0300 Subject: [PATCH 14/50] Validate checkpoints: track campfire states made without player --- .../listener/CheckpointListener.java | 2 + .../manager/CheckpointManager.java | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 5f2ebc1..948660a 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -240,6 +240,8 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { CheckpointManager checkpointManager = plugin.getCheckpointManager(); ConfigManager configManager = plugin.getConfigManager(); + checkpointManager.validateAllCheckpoints(playerUUID); + Checkpoint closestCheckpoint = checkpointManager.findClosestCheckpoint(playerUUID, deathLocation); Location bedSpawn = player.getRespawnLocation(); diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 953b6e9..6f24448 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -460,4 +460,73 @@ public PendingOverride(@NotNull Location newLocation, double radius, public int getTotalCheckpointCount() { return locationIndex.size(); } + + public void validateAllCheckpoints(@Nullable UUID playerUUID) { + List toRemove = new ArrayList<>(); + int extinguished = 0; + if (playerUUID == null) { + for (List checkpoints : playerCheckpoints.values()) { + CheckpointValidationResult result = checkAndCollectInvalid(checkpoints); + toRemove.addAll(result.toRemove); + extinguished += result.extinguished; + } + } else { + List checkpoints = playerCheckpoints.get(playerUUID); + if (checkpoints != null) { + CheckpointValidationResult result = checkAndCollectInvalid(checkpoints); + toRemove.addAll(result.toRemove); + extinguished += result.extinguished; + } + } + for (Checkpoint checkpoint : toRemove) { + removeCheckpointAt(checkpoint.getBlockLocation()); + } + if (!toRemove.isEmpty() || extinguished > 0) { + markDirty(); + String prefix = (playerUUID == null) ? "[All]" : "[Player]"; + plugin.getLogger().info(prefix + " Removed " + toRemove.size() + " invalid checkpoints, extinguished " + extinguished + "."); + } + } + + private static class CheckpointValidationResult { + List toRemove = new ArrayList<>(); + int extinguished = 0; + } + + private CheckpointValidationResult checkAndCollectInvalid(List checkpoints) { + CheckpointValidationResult result = new CheckpointValidationResult(); + synchronized (checkpoints) { + for (Checkpoint checkpoint : checkpoints) { + Location loc = checkpoint.getBlockLocation(); + if (loc == null || loc.getWorld() == null) { + result.toRemove.add(checkpoint); + continue; + } + org.bukkit.block.Block block = loc.getWorld().getBlockAt(loc); + org.bukkit.Material type = block.getType(); + boolean isCampfire = (type == org.bukkit.Material.CAMPFIRE); + boolean isSoulCampfire = (type == org.bukkit.Material.SOUL_CAMPFIRE); + if (!isCampfire && !isSoulCampfire) { + result.toRemove.add(checkpoint); + continue; + } + org.bukkit.block.data.BlockData data = block.getBlockData(); + if (data instanceof org.bukkit.block.data.Lightable lightable) { + if (!lightable.isLit()) { + if (checkpoint.isLit()) { + setCheckpointLit(checkpoint, false); + result.extinguished++; + } + } else { + if (!checkpoint.isLit()) { + setCheckpointLit(checkpoint, true); + } + } + } else { + result.toRemove.add(checkpoint); + } + } + } + return result; + } } \ No newline at end of file From 74fa93e54e16f9118188cc182d2f4f3a3ad484f6 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Mon, 9 Mar 2026 20:21:22 +0300 Subject: [PATCH 15/50] Version tweak --- src/main/resources/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3a2b022..c0330ed 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ # src/main/resources/plugin.yml name: CampfireCheckpoints -version: '1.1.0' +version: '1.2.0-parar' main: com.campfirecheckpoints.CampfireCheckpoints api-version: '1.21' description: A respawn system using campfires as checkpoints From a6ed4c5e6c853f9c8fb4b59f449f5f09d05e3fba Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 13:05:12 +0300 Subject: [PATCH 16/50] Better list output --- .../com/campfirecheckpoints/command/CheckpointCommand.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index ea9b5d3..cc3c492 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -97,14 +97,15 @@ private void handleList(@NotNull Player player) { Checkpoint cp = checkpoints.get(i); Location loc = cp.getBlockLocation(); - String status = cp.isLit() ? "&a✓ Lit" : "&c✗ Extinguished"; + String status = cp.isLit() ? "&a✓ Lit" : "&c✗ Put out"; String coords = loc != null ? String.format("&f%d, %d, %d", loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()) : "&cUnknown"; - String world = cp.getWorldName(); + String world = cp.getWorldName().replace("world_", ""); String date = dateFormat.format(new Date(cp.getCreatedAt())); + String type = cp.isSoul() ? " &9(soul)" : ""; - MessageUtil.send(player, "&e[" + i + "] " + status + " &7| " + coords + + MessageUtil.send(player, "&e[" + i + "] " + status + type + " &7| " + coords + " &7(" + world + ")"); MessageUtil.send(player, " &7Created: " + date); } From f8feed3de49e8035bfbef2d183d6edcebda25a17 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 13:50:33 +0300 Subject: [PATCH 17/50] Validate all checkpoints more often & notifications --- .../command/CheckpointCommand.java | 2 ++ .../listener/CheckpointListener.java | 2 ++ .../manager/CheckpointManager.java | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index cc3c492..968f846 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -81,6 +81,8 @@ private void handleList(@NotNull Player player) { CheckpointManager manager = plugin.getCheckpointManager(); List checkpoints = manager.getPlayerCheckpoints(player.getUniqueId()); + manager.validateAllCheckpoints(player.getUniqueId()); + if (checkpoints.isEmpty()) { MessageUtil.send(player, "&eYou have no checkpoints set."); MessageUtil.send(player, "&7Right-click a lit campfire to set one!"); diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 948660a..f711990 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -104,6 +104,8 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { CheckpointManager checkpointManager = plugin.getCheckpointManager(); + checkpointManager.validateAllCheckpoints(playerUUID); + if (checkpointManager.hasCheckpointAt(playerUUID, blockLocation)) { MessageUtil.send(player, "&eYou already have a checkpoint at this campfire!"); event.setCancelled(true); diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 6f24448..c4478fa 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -480,6 +480,11 @@ public void validateAllCheckpoints(@Nullable UUID playerUUID) { } for (Checkpoint checkpoint : toRemove) { removeCheckpointAt(checkpoint.getBlockLocation()); + // Notify owner if online + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &chas been broken."); + } } if (!toRemove.isEmpty() || extinguished > 0) { markDirty(); @@ -516,10 +521,20 @@ private CheckpointValidationResult checkAndCollectInvalid(List check if (checkpoint.isLit()) { setCheckpointLit(checkpoint, false); result.extinguished++; + // Notify owner about extinguished checkpoint + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &chas been extinguished."); + } } } else { if (!checkpoint.isLit()) { setCheckpointLit(checkpoint, true); + // Notify owner about relit checkpoint + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &ahas been relit."); + } } } } else { From 677afdaec48b582d04f68323ca491fa7998da84a Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 15:01:03 +0300 Subject: [PATCH 18/50] Empty hand or sneak required --- .../listener/CheckpointListener.java | 9 +++++++++ .../com/campfirecheckpoints/manager/ConfigManager.java | 10 ++++++++++ src/main/resources/config.yml | 6 +++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index f711990..06c3c3a 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -81,6 +81,15 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { Player player = event.getPlayer(); + // Only allow checkpoint creation if player is sneaking (crouching) or has an empty hand + // This is required to fix food cooking on campfire and extinguishing it with a splash water bottle + if (configManager.isEmptyHandOrSneakRequired()) { + boolean isEmptyHand = player.getInventory().getItemInMainHand().getType() == Material.AIR; + if (!player.isSneaking() && !isEmptyHand) { + return; + } + } + if (!player.hasPermission("campfirecheckpoints.use")) { return; } diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index d779707..21d0082 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -31,6 +31,7 @@ public final class ConfigManager { private int overrideConfirmationTimeout; private int maxCheckpointsPerPlayer; private @NotNull RespawnPriority respawnPriority; + private boolean emptyHandOrSneakRequired; // Default values private static final boolean DEFAULT_DIMENTION_OVERWORLD = true; @@ -49,6 +50,7 @@ public final class ConfigManager { private static final int DEFAULT_OVERRIDE_TIMEOUT = 5; private static final int DEFAULT_MAX_CHECKPOINTS = 0; // 0 = unlimited private static final RespawnPriority DEFAULT_RESPAWN_PRIORITY = RespawnPriority.CHECKPOINT; + private static final boolean DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED = true; public ConfigManager(@NotNull CampfireCheckpoints plugin) { this.plugin = plugin; @@ -123,6 +125,10 @@ public void reload() { this.soundOnSet = DEFAULT_SOUND; } + // Load empty-hand-or-sneak-required + this.emptyHandOrSneakRequired = config.getBoolean("require-empty-hand-or-sneak", + DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED); + plugin.getLogger().info("Configuration loaded " + "- Regular campfires enabled (overworld): " + overworldEnableRegularCampfires + ", Regular campfires enabled (nether): " + netherEnableRegularCampfires + @@ -203,4 +209,8 @@ public boolean hasCheckpointLimit() { public @NotNull RespawnPriority getRespawnPriority() { return respawnPriority; } + + public boolean isEmptyHandOrSneakRequired() { + return emptyHandOrSneakRequired; + } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 27232e4..e218f4f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -55,4 +55,8 @@ max-checkpoints-per-player: 0 # # Note: This only applies when BOTH a valid bed spawn AND a lit checkpoint # are within the configured radius of the death location. -respawn-priority: checkpoint \ No newline at end of file +respawn-priority: checkpoint + +# If true, players must be sneaking (crouching) or have an empty hand to set a checkpoint. +# This prevents accidental checkpoint creation when cooking food or extinguishing a campfire with a splash water bottle. +require-empty-hand-or-sneak: true \ No newline at end of file From a158e3f54f6bf70715cd4672a70a6ac3f50bf960 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 15:25:23 +0300 Subject: [PATCH 19/50] Setting disables the delete command --- .../command/CheckpointCommand.java | 33 ++++++++++++++----- .../manager/ConfigManager.java | 9 +++++ src/main/resources/config.yml | 5 ++- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index 968f846..641eb45 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -42,8 +42,10 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command return true; } + ConfigManager configManager = plugin.getConfigManager(); + if (args.length == 0) { - showHelp(player); + showHelp(player, configManager); return true; } @@ -51,19 +53,27 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command switch (subCommand) { case "list" -> handleList(player); - case "delete" -> handleDelete(player, args); + case "delete" -> { + if (configManager.isDeleteCommandAllowed()) { + handleDelete(player, args); + } else { + MessageUtil.send(player, "&cThe delete command is disabled by the server configuration."); + } + } case "reload" -> handleReload(player); case "info" -> handleInfo(player); - default -> showHelp(player); + default -> showHelp(player, configManager); } return true; } - private void showHelp(@NotNull Player player) { + private void showHelp(@NotNull Player player, ConfigManager configManager) { MessageUtil.send(player, "&6&l=== Campfire Checkpoints ==="); MessageUtil.send(player, "&e/cc list &7- List your checkpoints"); - MessageUtil.send(player, "&e/cc delete &7- Delete a checkpoint"); + if (configManager.isDeleteCommandAllowed()) { + MessageUtil.send(player, "&e/cc delete &7- Delete a checkpoint"); + } MessageUtil.send(player, "&e/cc info &7- Show plugin info"); if (player.hasPermission("campfirecheckpoints.reload")) { MessageUtil.send(player, "&e/cc reload &7- Reload configuration"); @@ -112,7 +122,9 @@ private void handleList(@NotNull Player player) { MessageUtil.send(player, " &7Created: " + date); } - MessageUtil.send(player, "&7Use &e/cc delete &7to remove a checkpoint."); + if (plugin.getConfigManager().isDeleteCommandAllowed()) { + MessageUtil.send(player, "&7Use &e/cc delete &7to remove a checkpoint."); + } } private void handleDelete(@NotNull Player player, @NotNull String[] args) { @@ -211,8 +223,13 @@ private void handleInfo(@NotNull Player player) { return null; } + ConfigManager configManager = plugin.getConfigManager(); + if (args.length == 1) { - List completions = new ArrayList<>(Arrays.asList("list", "delete", "info")); + List completions = new ArrayList<>(List.of("list", "info")); + if (configManager.isDeleteCommandAllowed()) { + completions.add("delete"); + } if (player.hasPermission("campfirecheckpoints.reload")) { completions.add("reload"); } @@ -221,7 +238,7 @@ private void handleInfo(@NotNull Player player) { .collect(Collectors.toList()); } - if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { + if (args.length == 2 && args[0].equalsIgnoreCase("delete") && configManager.isDeleteCommandAllowed()) { int checkpointCount = plugin.getCheckpointManager() .getPlayerCheckpoints(player.getUniqueId()).size(); diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 21d0082..3c8ee3e 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -32,6 +32,7 @@ public final class ConfigManager { private int maxCheckpointsPerPlayer; private @NotNull RespawnPriority respawnPriority; private boolean emptyHandOrSneakRequired; + private boolean deleteCommandAllowed; // Default values private static final boolean DEFAULT_DIMENTION_OVERWORLD = true; @@ -51,6 +52,7 @@ public final class ConfigManager { private static final int DEFAULT_MAX_CHECKPOINTS = 0; // 0 = unlimited private static final RespawnPriority DEFAULT_RESPAWN_PRIORITY = RespawnPriority.CHECKPOINT; private static final boolean DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED = true; + private static final boolean DEFAULT_DELETE_COMMAND_ALLOWED = true; public ConfigManager(@NotNull CampfireCheckpoints plugin) { this.plugin = plugin; @@ -129,6 +131,9 @@ public void reload() { this.emptyHandOrSneakRequired = config.getBoolean("require-empty-hand-or-sneak", DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED); + // Load delete-command-allowed + this.deleteCommandAllowed = config.getBoolean("allow-delete-command", DEFAULT_DELETE_COMMAND_ALLOWED); + plugin.getLogger().info("Configuration loaded " + "- Regular campfires enabled (overworld): " + overworldEnableRegularCampfires + ", Regular campfires enabled (nether): " + netherEnableRegularCampfires + @@ -213,4 +218,8 @@ public boolean hasCheckpointLimit() { public boolean isEmptyHandOrSneakRequired() { return emptyHandOrSneakRequired; } + + public boolean isDeleteCommandAllowed() { + return deleteCommandAllowed; + } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e218f4f..c55ab3e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -59,4 +59,7 @@ respawn-priority: checkpoint # If true, players must be sneaking (crouching) or have an empty hand to set a checkpoint. # This prevents accidental checkpoint creation when cooking food or extinguishing a campfire with a splash water bottle. -require-empty-hand-or-sneak: true \ No newline at end of file +require-empty-hand-or-sneak: true + +# If false, the /cc delete command will be disabled and hidden from the command list. +allow-delete-command: true \ No newline at end of file From c32e003cb0f14450b28c50c017d9a129bd04888f Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 16:06:29 +0300 Subject: [PATCH 20/50] Started work on respawn anchors --- .../listener/CheckpointListener.java | 19 +++++++++++++++---- .../manager/ConfigManager.java | 7 +++++++ src/main/resources/config.yml | 13 +++++++++---- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 06c3c3a..2ea6c1b 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -56,13 +56,26 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { Material type = clickedBlock.getType(); boolean isRegularCampfire = (type == Material.CAMPFIRE); boolean isSoulCampfire = (type == Material.SOUL_CAMPFIRE); + boolean isRespawnAnchor = (type == Material.RESPAWN_ANCHOR); + + ConfigManager configManager = plugin.getConfigManager(); + World.Environment env = event.getPlayer().getWorld().getEnvironment(); + Player player = event.getPlayer(); + + // Deny respawn anchor spawnpoint in Nether unless enabled, but allow charging with glowstone + if (isRespawnAnchor && configManager.RespawnAnchorsEnabled()) { + Material itemInHand = player.getInventory().getItemInMainHand().getType(); + if (env == World.Environment.NETHER && itemInHand != Material.GLOWSTONE) { + event.setCancelled(true); + MessageUtil.send(player, "&cSetting respawn anchor spawnpoint in Nether is disabled by this plugin!"); + return; + } + } if (!isRegularCampfire && !isSoulCampfire) { return; } - ConfigManager configManager = plugin.getConfigManager(); - World.Environment env = event.getPlayer().getWorld().getEnvironment(); switch (env) { case NORMAL: @@ -79,8 +92,6 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { break; } - Player player = event.getPlayer(); - // Only allow checkpoint creation if player is sneaking (crouching) or has an empty hand // This is required to fix food cooking on campfire and extinguishing it with a splash water bottle if (configManager.isEmptyHandOrSneakRequired()) { diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 3c8ee3e..9a3b7ae 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -21,6 +21,7 @@ public final class ConfigManager { private boolean overworldEnableSoulCampfires; private boolean netherEnableSoulCampfires; private boolean endEnableSoulCampfires; + private boolean respawnAnchorsEnabled; private int radius; private int soulRadius; @@ -53,6 +54,7 @@ public final class ConfigManager { private static final RespawnPriority DEFAULT_RESPAWN_PRIORITY = RespawnPriority.CHECKPOINT; private static final boolean DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED = true; private static final boolean DEFAULT_DELETE_COMMAND_ALLOWED = true; + private static final boolean DEFAULT_RESPAWN_ANCHORS_ENABLED = false; public ConfigManager(@NotNull CampfireCheckpoints plugin) { this.plugin = plugin; @@ -70,6 +72,7 @@ public void reload() { this.overworldEnableSoulCampfires = config.getBoolean("enable-soul-overworld", DEFAULT_DIMENTION_OVERWORLD_SOUL); this.netherEnableSoulCampfires = config.getBoolean("enable-soul-nether", DEFAULT_DIMENTION_NETHER_SOUL); this.endEnableSoulCampfires = config.getBoolean("enable-soul-end", DEFAULT_DIMENTION_END_SOUL); + this.respawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors", DEFAULT_RESPAWN_ANCHORS_ENABLED); // Load radius this.radius = config.getInt("radius", DEFAULT_RADIUS); @@ -222,4 +225,8 @@ public boolean isEmptyHandOrSneakRequired() { public boolean isDeleteCommandAllowed() { return deleteCommandAllowed; } + + public boolean RespawnAnchorsEnabled() { + return respawnAnchorsEnabled; + } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c55ab3e..7493f94 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -57,9 +57,14 @@ max-checkpoints-per-player: 0 # are within the configured radius of the death location. respawn-priority: checkpoint -# If true, players must be sneaking (crouching) or have an empty hand to set a checkpoint. -# This prevents accidental checkpoint creation when cooking food or extinguishing a campfire with a splash water bottle. +# If true, players must be crouching or have an empty hand to set a checkpoint. +# This prevents accidental checkpoint creation when cooking food or +# extinguishing a campfire with a splash water bottle. require-empty-hand-or-sneak: true -# If false, the /cc delete command will be disabled and hidden from the command list. -allow-delete-command: true \ No newline at end of file +# If false, the /cc delete command will be disabled and hidden from the list. +allow-delete-command: true + +# Make respawn anchors function as checkpoints, allowing players to set their +# spawn point without losing the spawnpoint set by a bed. +enable-respawn-anchors: false \ No newline at end of file From ee35e158369b5e715c64921ed10c5128a8343a1c Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 17:12:36 +0300 Subject: [PATCH 21/50] Config for respawn anchors --- .../manager/ConfigManager.java | 32 +++++++++++++++++++ src/main/resources/config.yml | 14 ++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index 9a3b7ae..aa29a00 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -21,7 +21,11 @@ public final class ConfigManager { private boolean overworldEnableSoulCampfires; private boolean netherEnableSoulCampfires; private boolean endEnableSoulCampfires; + private boolean respawnAnchorsEnabled; + private boolean overworldRespawnAnchorsEnabled; + private boolean netherRespawnAnchorsEnabled; + private boolean endRespawnAnchorsEnabled; private int radius; private int soulRadius; @@ -42,6 +46,9 @@ public final class ConfigManager { private static final boolean DEFAULT_DIMENTION_OVERWORLD_SOUL = true; private static final boolean DEFAULT_DIMENTION_NETHER_SOUL = true; private static final boolean DEFAULT_DIMENTION_END_SOUL = false; + private static final boolean DEFAULT_DIMENTION_OVERWORLD_ANCHOR = false; + private static final boolean DEFAULT_DIMENTION_NETHER_ANCHOR = true; + private static final boolean DEFAULT_DIMENTION_END_ANCHOR = false; private static final int DEFAULT_RADIUS = 500; private static final int DEFAULT_SOUL_RADIUS = 1000; @@ -69,11 +76,23 @@ public void reload() { this.overworldEnableRegularCampfires = config.getBoolean("enable-overworld", DEFAULT_DIMENTION_OVERWORLD); this.netherEnableRegularCampfires = config.getBoolean("enable-nether", DEFAULT_DIMENTION_NETHER); this.endEnableRegularCampfires = config.getBoolean("enable-end", DEFAULT_DIMENTION_END); + this.overworldEnableSoulCampfires = config.getBoolean("enable-soul-overworld", DEFAULT_DIMENTION_OVERWORLD_SOUL); this.netherEnableSoulCampfires = config.getBoolean("enable-soul-nether", DEFAULT_DIMENTION_NETHER_SOUL); this.endEnableSoulCampfires = config.getBoolean("enable-soul-end", DEFAULT_DIMENTION_END_SOUL); + this.respawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors", DEFAULT_RESPAWN_ANCHORS_ENABLED); + if (this.respawnAnchorsEnabled) { + this.overworldRespawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors-overworld", DEFAULT_DIMENTION_OVERWORLD_ANCHOR); + this.netherRespawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors-nether", DEFAULT_DIMENTION_NETHER_ANCHOR); + this.endRespawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors-end", DEFAULT_DIMENTION_END_ANCHOR); + } else { + this.overworldRespawnAnchorsEnabled = false; + this.netherRespawnAnchorsEnabled = false; + this.endRespawnAnchorsEnabled = false; + } + // Load radius this.radius = config.getInt("radius", DEFAULT_RADIUS); if (radius <= 0) { @@ -144,6 +163,9 @@ public void reload() { ", Soul campfires enabled (overworld): " + overworldEnableSoulCampfires + ", Soul campfires enabled (nether): " + netherEnableSoulCampfires + ", Soul campfires enabled (end): " + endEnableSoulCampfires + + ", Respawn anchors enabled (overworld): " + overworldRespawnAnchorsEnabled + + ", Respawn anchors enabled (nether): " + netherRespawnAnchorsEnabled + + ", Respawn anchors enabled (end): " + endRespawnAnchorsEnabled + ", Radius: " + radius + ", Radius (soul campfires): " + soulRadius + ", Min. distance: " + minDistance + @@ -174,6 +196,16 @@ public boolean isDimentionEnabledEndSoul() { return endEnableSoulCampfires; } + public boolean isRespawnAnchorEnabledOverworld() { + return respawnAnchorsEnabled && overworldRespawnAnchorsEnabled; + } + public boolean isRespawnAnchorEnabledNether() { + return respawnAnchorsEnabled && netherRespawnAnchorsEnabled; + } + public boolean isRespawnAnchorEnabledEnd() { + return respawnAnchorsEnabled && endRespawnAnchorsEnabled; + } + public int getRadius() { return radius; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7493f94..bf77a3b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -65,6 +65,14 @@ require-empty-hand-or-sneak: true # If false, the /cc delete command will be disabled and hidden from the list. allow-delete-command: true -# Make respawn anchors function as checkpoints, allowing players to set their -# spawn point without losing the spawnpoint set by a bed. -enable-respawn-anchors: false \ No newline at end of file +# Override respawn anchors, making them function as checkpoints to allow +# setting global spawnpoints without losing the spawnpoint set by a bed. +# Respawn anchors don't have radius and minimal distance checks. +# You can only have one respawn anchor checkpoint per player. +# If another respawn anchor checkpoint is set, the old one will be deleted. +enable-respawn-anchors: false + +# Per-dimension overrides for respawn anchors. Only work if enable-respawn-anchors is true. +enable-respawn-anchors-overworld: false +enable-respawn-anchors-nether: true +enable-respawn-anchors-end: false From 25c66e3999532be23def39dc8f87109f9dd55a5a Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 17:13:19 +0300 Subject: [PATCH 22/50] "anchor" type of checkpoint class --- .../campfirecheckpoints/model/Checkpoint.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java index 2c0d418..ad423e1 100644 --- a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java +++ b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java @@ -22,6 +22,7 @@ public final class Checkpoint { private final long createdAt; private volatile boolean lit; private volatile boolean soul; + private volatile boolean anchor; public Checkpoint(@NotNull UUID ownerUUID, @NotNull Location location) { Objects.requireNonNull(ownerUUID, "Owner UUID cannot be null"); @@ -36,12 +37,14 @@ public Checkpoint(@NotNull UUID ownerUUID, @NotNull Location location) { this.createdAt = System.currentTimeMillis(); this.lit = true; - this.soul = (location.getBlock().getType() == Material.SOUL_CAMPFIRE); + Material blockType = location.getBlock().getType(); + this.soul = (blockType == Material.SOUL_CAMPFIRE); + this.anchor = (blockType == Material.RESPAWN_ANCHOR); } private Checkpoint(@NotNull UUID ownerUUID, @NotNull String worldName, int x, int y, int z, long createdAt, boolean lit, - boolean soul) { + boolean soul, boolean anchor) { this.ownerUUID = ownerUUID; this.worldName = worldName; this.x = x; @@ -50,6 +53,7 @@ private Checkpoint(@NotNull UUID ownerUUID, @NotNull String worldName, this.createdAt = createdAt; this.lit = lit; this.soul = soul; + this.anchor = anchor; } public @NotNull UUID getOwnerUUID() { @@ -92,6 +96,13 @@ public void setSoul(boolean soul) { this.soul = soul; } + public boolean isAnchor() { + return anchor; + } + + public void setAnchor(boolean anchor) { + this.anchor = anchor; + } public @Nullable Location getSpawnLocation() { World world = Bukkit.getWorld(worldName); @@ -128,6 +139,11 @@ public double distanceSquared(@Nullable Location location) { public boolean isWithinRespawnRadius(@Nullable Location location, double radiusSquaredRegular, double radiusSquaredSoul) { + + if (anchor) { + return false; // Respawn anchors are not affected by radius checks + } + double radiusSquared = soul ? radiusSquaredSoul : radiusSquaredRegular; return (distanceSquared(location) <= radiusSquared); } @@ -153,6 +169,7 @@ public boolean isWithinRadius(@Nullable Location location, double radius) { json.addProperty("createdAt", createdAt); json.addProperty("lit", lit); json.addProperty("soul", soul); + json.addProperty("anchor", anchor); return json; } @@ -171,8 +188,9 @@ public boolean isWithinRadius(@Nullable Location location, double radius) { json.get("createdAt").getAsLong() : System.currentTimeMillis(); boolean lit = !json.has("lit") || json.get("lit").getAsBoolean(); boolean soul = json.has("soul") && json.get("soul").getAsBoolean(); + boolean anchor = json.has("anchor") && json.get("anchor").getAsBoolean(); - return new Checkpoint(ownerUUID, world, x, y, z, createdAt, lit, soul); + return new Checkpoint(ownerUUID, world, x, y, z, createdAt, lit, soul, anchor); } catch (Exception e) { return null; } @@ -202,7 +220,7 @@ public int hashCode() { @Override public String toString() { - return String.format("Checkpoint{owner=%s, world=%s, pos=[%d, %d, %d], lit=%s, soul=%s}", - ownerUUID, worldName, x, y, z, lit, soul); + return String.format("Checkpoint{owner=%s, world=%s, pos=[%d, %d, %d], lit=%s, soul=%s, anchor=%s}", + ownerUUID, worldName, x, y, z, lit, soul, anchor); } } \ No newline at end of file From 167cd7f79f26a01f955c40f9671d6c04236ca98b Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 17:15:20 +0300 Subject: [PATCH 23/50] Anchors in list --- .../command/CheckpointCommand.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index 641eb45..5ddc457 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -115,7 +115,13 @@ private void handleList(@NotNull Player player) { "&cUnknown"; String world = cp.getWorldName().replace("world_", ""); String date = dateFormat.format(new Date(cp.getCreatedAt())); - String type = cp.isSoul() ? " &9(soul)" : ""; + + String type = ""; + if (cp.isSoul()) { + type += " &9(soul)"; + } else if (cp.isAnchor()) { + type += " &d(anchor)"; + } MessageUtil.send(player, "&e[" + i + "] " + status + type + " &7| " + coords + " &7(" + world + ")"); @@ -197,16 +203,25 @@ private void handleInfo(@NotNull Player player) { MessageUtil.send(player, "&eYour Checkpoints: &f" + playerCount + (max > 0 ? " / " + max : "")); MessageUtil.send(player, "&eTotal Checkpoints: &f" + totalCount); + MessageUtil.send(player, "&eRegular campfires enabled (overworld): &f" + configManager.isDimentionEnabledOverworld()); MessageUtil.send(player, "&eRegular campfires enabled (nether): &f" + configManager.isDimentionEnabledNether()); MessageUtil.send(player, "&eRegular campfires enabled (the end): &f" + configManager.isDimentionEnabledEnd()); + MessageUtil.send(player, "&eSoul campfires enabled (overworld): &f" + configManager.isDimentionEnabledOverworldSoul()); MessageUtil.send(player, "&eSoul campfires enabled (nether): &f" + configManager.isDimentionEnabledNetherSoul()); MessageUtil.send(player, "&eSoul campfires enabled (the end): &f" + configManager.isDimentionEnabledEndSoul()); + + MessageUtil.send(player, "&eRespawn anchors enabled (overworld): &f" + configManager.isDimentionEnabledOverworldAnchor()); + MessageUtil.send(player, "&eRespawn anchors enabled (nether): &f" + configManager.isDimentionEnabledNetherAnchor()); + MessageUtil.send(player, "&eRespawn anchors enabled (the end): &f" + configManager.isDimentionEnabledEndAnchor()); + MessageUtil.send(player, "&eRadius (regular campfires): &f" + configManager.getRadius() + " blocks"); MessageUtil.send(player, "&eRadius (soul campfires): &f" + configManager.getSoulRadius() + " blocks"); + MessageUtil.send(player, "&eMin. distance between checkpoints: &f" + configManager.getMinDistance() + " blocks"); MessageUtil.send(player, "&eMin. distance between checkpoints (soul): &f" + configManager.getSoulMinDistance() + " blocks"); + MessageUtil.send(player, "&eExtinguish on Respawn: &f" + configManager.isExtinguishOnRespawn()); MessageUtil.send(player, "&eConfirmation Timeout: &f" + configManager.getOverrideConfirmationTimeout() + "s"); From ae2087cc73e7b3430c37115eac66845dccd00044 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 18:34:09 +0300 Subject: [PATCH 24/50] Anchor status in lisst --- .../java/com/campfirecheckpoints/command/CheckpointCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java index 5ddc457..e3a4623 100644 --- a/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java +++ b/src/main/java/com/campfirecheckpoints/command/CheckpointCommand.java @@ -121,6 +121,7 @@ private void handleList(@NotNull Player player) { type += " &9(soul)"; } else if (cp.isAnchor()) { type += " &d(anchor)"; + status = cp.isLit() ? "&a✓ Charged" : "&c✗ Disabled"; } MessageUtil.send(player, "&e[" + i + "] " + status + type + " &7| " + coords + From c1322a7c7e00ca3c612bdcd490e6c2500a058cf6 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 18:34:24 +0300 Subject: [PATCH 25/50] Anchor top priority --- src/main/java/com/campfirecheckpoints/model/Checkpoint.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java index ad423e1..ee96ee6 100644 --- a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java +++ b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java @@ -125,6 +125,12 @@ public double distanceSquared(@Nullable Location location) { if (location == null || location.getWorld() == null) { return Double.MAX_VALUE; } + + if (anchor) { + // If we have an active anchor, it is prioritized over distance checks + return 0; + } + if (!location.getWorld().getName().equals(worldName)) { return Double.MAX_VALUE; } From ad6097a2fe4a2de4a5e52a9ee1c7621812ad95f3 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 21:16:27 +0300 Subject: [PATCH 26/50] Compilation fix --- pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pom.xml b/pom.xml index 539678c..e62a915 100644 --- a/pom.xml +++ b/pom.xml @@ -69,18 +69,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - true - - - - From ac4bc57b8a485bfe96a4cb1215f0cdc6964853c9 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 21:16:49 +0300 Subject: [PATCH 27/50] Radius check fix for anchors --- .../java/com/campfirecheckpoints/model/Checkpoint.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java index ee96ee6..0d19937 100644 --- a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java +++ b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java @@ -147,7 +147,7 @@ public boolean isWithinRespawnRadius(@Nullable Location location, double radiusSquaredSoul) { if (anchor) { - return false; // Respawn anchors are not affected by radius checks + return true; // Respawn anchors are not affected by radius checks } double radiusSquared = soul ? radiusSquaredSoul : radiusSquaredRegular; @@ -156,9 +156,14 @@ public boolean isWithinRespawnRadius(@Nullable Location location, public boolean isWithinRadius(@Nullable Location location, double radius) { + if (anchor) { + return true; // Respawn anchors are not affected by radius checks + } + if (location == null) { return false; } + return distanceSquared(location) <= radius * radius; } From 904d41de7c1ae39c0df57f5a0cc1871d7377a3cb Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 21:19:40 +0300 Subject: [PATCH 28/50] Anchors functionality --- .../listener/CheckpointListener.java | 134 ++++++++++++++++-- .../manager/CheckpointManager.java | 93 +++++++++--- .../manager/ConfigManager.java | 6 +- 3 files changed, 199 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 2ea6c1b..8c363c7 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -11,6 +11,7 @@ import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Lightable; +import org.bukkit.block.data.type.RespawnAnchor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -65,11 +66,67 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { // Deny respawn anchor spawnpoint in Nether unless enabled, but allow charging with glowstone if (isRespawnAnchor && configManager.RespawnAnchorsEnabled()) { Material itemInHand = player.getInventory().getItemInMainHand().getType(); - if (env == World.Environment.NETHER && itemInHand != Material.GLOWSTONE) { + + if (itemInHand == Material.GLOWSTONE) { + return; + } + + if (env == World.Environment.NORMAL && !configManager.isDimentionEnabledOverworldAnchor()) { + MessageUtil.send(player, "&cSetting respawn anchors as checkpoints is not allowed in the Overworld."); + event.setCancelled(true); + return; + } + + if (env == World.Environment.NETHER && !configManager.isDimentionEnabledNetherAnchor()) { + MessageUtil.send(player, "&cSetting respawn anchors as checkpoints is not allowed in the Nether."); event.setCancelled(true); - MessageUtil.send(player, "&cSetting respawn anchor spawnpoint in Nether is disabled by this plugin!"); return; } + + if (env == World.Environment.THE_END && !configManager.isDimentionEnabledEndAnchor()) { + MessageUtil.send(player, "&cSetting respawn anchors as checkpoints is not allowed in the End."); + event.setCancelled(true); + return; + } + + // Create checkpoint on respawn anchor + UUID playerUUID = player.getUniqueId(); + Location blockLocation = clickedBlock.getLocation(); + CheckpointManager checkpointManager = plugin.getCheckpointManager(); + + checkpointManager.validateAllCheckpoints(playerUUID); + + if (checkpointManager.hasCheckpointAt(playerUUID, blockLocation)) { + MessageUtil.send(player, "&eYou already have a checkpoint at this respawn anchor!"); + event.setCancelled(true); + return; + } + + // Check for charge state and prevent setting checkpoint on uncharged anchor + BlockData blockData = clickedBlock.getBlockData(); + if (blockData instanceof RespawnAnchor anchorData) { + if (anchorData.getCharges() <= 0) { + MessageUtil.send(player, "&cThis respawn anchor is uncharged! Charge it with glowstone to set a checkpoint."); + event.setCancelled(true); + return; + } + } + + // Remove all other anchor checkpoints, there can be only one per player + checkpointManager.removeAnchorCheckpoints(playerUUID); + + Checkpoint newCheckpoint = new Checkpoint(playerUUID, blockLocation); + checkpointManager.addCheckpoint(playerUUID, newCheckpoint); + + String worldName = blockLocation.getWorld() != null ? blockLocation.getWorld().getName().replace("world_", "") : "unknown"; + MessageUtil.send(player, "&aCheckpoint set at respawn anchor &f(" + + blockLocation.getBlockX() + ", " + + blockLocation.getBlockY() + ", " + + blockLocation.getBlockZ() + ", " + + worldName + ")&a!"); + + event.setCancelled(true); + } if (!isRegularCampfire && !isSoulCampfire) { @@ -92,21 +149,21 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { break; } + + + Material itemInHand = player.getInventory().getItemInMainHand().getType(); + // Only allow checkpoint creation if player is sneaking (crouching) or has an empty hand // This is required to fix food cooking on campfire and extinguishing it with a splash water bottle if (configManager.isEmptyHandOrSneakRequired()) { - boolean isEmptyHand = player.getInventory().getItemInMainHand().getType() == Material.AIR; - if (!player.isSneaking() && !isEmptyHand) { + if (!player.isSneaking() && itemInHand != Material.AIR) { return; } - } - - if (!player.hasPermission("campfirecheckpoints.use")) { + } else if (isInteractItem(itemInHand)) { return; } - Material itemInHand = player.getInventory().getItemInMainHand().getType(); - if (isInteractItem(itemInHand)) { + if (!player.hasPermission("campfirecheckpoints.use")) { return; } @@ -176,7 +233,12 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { if (!checkpointManager.canCreateCheckpoint(playerUUID)) { int max = configManager.getMaxCheckpointsPerPlayer(); MessageUtil.send(player, "&cYou have reached the maximum number of checkpoints (" + max + ")!"); - MessageUtil.send(player, "&7Use &e/cc delete &7to remove an old checkpoint first."); + + if (configManager.isDeleteCommandAllowed()) { + MessageUtil.send(player, "&7Use &e/cc delete &7to remove an old checkpoint first."); + } else { + MessageUtil.send(player, "&7You need to break an old checkpoint before creating a new one."); + } event.setCancelled(true); return; } @@ -412,12 +474,53 @@ private double calculateDistanceSquared(@NotNull Location a, @NotNull Location b return dx * dx + dy * dy + dz * dz; } + @EventHandler(priority = EventPriority.MONITOR) + public void onAnchorChargeOrDischarge(org.bukkit.event.block.BlockPhysicsEvent event) { + Block block = event.getBlock(); + + if (block.getType() != Material.RESPAWN_ANCHOR) { + return; + } + + Location blockLoc = block.getLocation(); + Checkpoint checkpoint = plugin.getCheckpointManager().getCheckpointAt(blockLoc); + if (checkpoint == null) return; + + BlockData data = block.getBlockData(); + int charge = -1; + if (data instanceof org.bukkit.block.data.type.RespawnAnchor anchorData) { + charge = anchorData.getCharges(); + } + + boolean wasLit = checkpoint.isLit(); + boolean isNowLit = charge > 0; + + if (wasLit != isNowLit) { + checkpoint.setLit(isNowLit); + plugin.getCheckpointManager().setCheckpointLit(checkpoint, isNowLit); + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + if (owner != null && owner.isOnline()) { + if (isNowLit) { + MessageUtil.send(owner, "&aYour respawn anchor checkpoint at &f(" + + blockLoc.getBlockX() + ", " + + blockLoc.getBlockY() + ", " + + blockLoc.getBlockZ() + ") &ahas been charged!"); + } else { + MessageUtil.send(owner, "&eYour respawn anchor checkpoint at &f(" + + blockLoc.getBlockX() + ", " + + blockLoc.getBlockY() + ", " + + blockLoc.getBlockZ() + ") &ehas no charge left!"); + } + } + } + } + private void handleCheckpointRespawn( @NotNull Checkpoint checkpoint, @NotNull CheckpointManager checkpointManager, @NotNull ConfigManager configManager) { - if (configManager.isExtinguishOnRespawn()) { + if (configManager.isExtinguishOnRespawn() || checkpoint.isAnchor()) { Location blockLoc = checkpoint.getBlockLocation(); if (blockLoc != null && blockLoc.getWorld() != null) { Block campfireBlock = blockLoc.getBlock(); @@ -427,6 +530,15 @@ private void handleCheckpointRespawn( lightable.setLit(false); campfireBlock.setBlockData(lightable); checkpointManager.setCheckpointLit(checkpoint, false); + } else if (checkpoint.isAnchor() && blockData instanceof RespawnAnchor anchorData) { + int charges = anchorData.getCharges() - 1; + anchorData.setCharges(charges); + campfireBlock.setBlockData(anchorData); + + if (charges <= 0) { + checkpoint.setLit(false); + checkpointManager.setCheckpointLit(checkpoint, false); + } } } } diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index c4478fa..e98f408 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -464,11 +464,15 @@ public int getTotalCheckpointCount() { public void validateAllCheckpoints(@Nullable UUID playerUUID) { List toRemove = new ArrayList<>(); int extinguished = 0; + int unchargedAnchors = 0; + if (playerUUID == null) { for (List checkpoints : playerCheckpoints.values()) { CheckpointValidationResult result = checkAndCollectInvalid(checkpoints); toRemove.addAll(result.toRemove); + extinguished += result.extinguished; + unchargedAnchors += result.unchargedAnchors; } } else { List checkpoints = playerCheckpoints.get(playerUUID); @@ -476,26 +480,31 @@ public void validateAllCheckpoints(@Nullable UUID playerUUID) { CheckpointValidationResult result = checkAndCollectInvalid(checkpoints); toRemove.addAll(result.toRemove); extinguished += result.extinguished; + unchargedAnchors += result.unchargedAnchors; } } for (Checkpoint checkpoint : toRemove) { + String type = checkpoint.isAnchor() ? "anchor" : "campfire"; removeCheckpointAt(checkpoint.getBlockLocation()); // Notify owner if online Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &chas been broken."); + MessageUtil.send(owner, "&eYour " + type + " checkpoint at &f" + checkpoint.getLocationKey() + " &chas been broken."); } } if (!toRemove.isEmpty() || extinguished > 0) { markDirty(); String prefix = (playerUUID == null) ? "[All]" : "[Player]"; - plugin.getLogger().info(prefix + " Removed " + toRemove.size() + " invalid checkpoints, extinguished " + extinguished + "."); + plugin.getLogger().info(prefix + " Removed " + toRemove.size() + + " invalid checkpoints, extinguished " + extinguished + + ", uncharged anchors " + unchargedAnchors + "."); } } private static class CheckpointValidationResult { List toRemove = new ArrayList<>(); int extinguished = 0; + int unchargedAnchors = 0; } private CheckpointValidationResult checkAndCollectInvalid(List checkpoints) { @@ -511,30 +520,46 @@ private CheckpointValidationResult checkAndCollectInvalid(List check org.bukkit.Material type = block.getType(); boolean isCampfire = (type == org.bukkit.Material.CAMPFIRE); boolean isSoulCampfire = (type == org.bukkit.Material.SOUL_CAMPFIRE); - if (!isCampfire && !isSoulCampfire) { + boolean isRespawnAnchor = (type == org.bukkit.Material.RESPAWN_ANCHOR); + + if (!isCampfire && !isSoulCampfire && !isRespawnAnchor) { result.toRemove.add(checkpoint); continue; } + org.bukkit.block.data.BlockData data = block.getBlockData(); + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + if (data instanceof org.bukkit.block.data.Lightable lightable) { - if (!lightable.isLit()) { - if (checkpoint.isLit()) { - setCheckpointLit(checkpoint, false); - result.extinguished++; - // Notify owner about extinguished checkpoint - Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); - if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &chas been extinguished."); - } + if (!lightable.isLit() && checkpoint.isLit()) { + setCheckpointLit(checkpoint, false); + result.extinguished++; + + // Notify owner about extinguished checkpoint + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &chas been extinguished."); } - } else { - if (!checkpoint.isLit()) { - setCheckpointLit(checkpoint, true); - // Notify owner about relit checkpoint - Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); - if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &ahas been relit."); - } + } else if (lightable.isLit() && !checkpoint.isLit()) { + setCheckpointLit(checkpoint, true); + + // Notify owner about relit checkpoint + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour campfire checkpoint at &f" + checkpoint.getLocationKey() + " &ahas been relit."); + } + } + } else if (data instanceof org.bukkit.block.data.type.RespawnAnchor anchorData) { + if (anchorData.getCharges() <= 0 && checkpoint.isLit()) { + setCheckpointLit(checkpoint, false); + result.unchargedAnchors++; + + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + checkpoint.getLocationKey() + " &eis not charged."); + } + } else if (anchorData.getCharges() > 0 && !checkpoint.isLit()) { + setCheckpointLit(checkpoint, true); + + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + checkpoint.getLocationKey() + " &ahas been recharged."); } } } else { @@ -544,4 +569,32 @@ private CheckpointValidationResult checkAndCollectInvalid(List check } return result; } + + + public void removeAnchorCheckpoints(@NotNull UUID playerUUID) { + List checkpoints = playerCheckpoints.get(playerUUID); + if (checkpoints == null) { + return; + } + List toRemove = new ArrayList<>(); + synchronized (checkpoints) { + for (Checkpoint checkpoint : checkpoints) { + if (checkpoint.isAnchor()) { + toRemove.add(checkpoint); + } + } + } + for (Checkpoint checkpoint : toRemove) { + removeCheckpointAt(checkpoint.getBlockLocation()); + + Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + checkpoint.getLocationKey() + " &chas been removed."); + plugin.getLogger().info("[Player] Removed anchor checkpoint at &f" + checkpoint.getLocationKey() + " &7for " + playerUUID + "."); + } + } + if (!toRemove.isEmpty()) { + markDirty(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index aa29a00..c38abfb 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -196,13 +196,13 @@ public boolean isDimentionEnabledEndSoul() { return endEnableSoulCampfires; } - public boolean isRespawnAnchorEnabledOverworld() { + public boolean isDimentionEnabledOverworldAnchor() { return respawnAnchorsEnabled && overworldRespawnAnchorsEnabled; } - public boolean isRespawnAnchorEnabledNether() { + public boolean isDimentionEnabledNetherAnchor() { return respawnAnchorsEnabled && netherRespawnAnchorsEnabled; } - public boolean isRespawnAnchorEnabledEnd() { + public boolean isDimentionEnabledEndAnchor() { return respawnAnchorsEnabled && endRespawnAnchorsEnabled; } From 23a3b6608c8cf9af3742ba92decf5cdc2f4be2a0 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 23:48:20 +0300 Subject: [PATCH 29/50] Play sounds --- .../listener/CheckpointListener.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 8c363c7..d99f402 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -125,6 +125,8 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { + blockLocation.getBlockZ() + ", " + worldName + ")&a!"); + player.playSound(blockLocation, Sound.RESPAWN_ANCHOR_SET_SPAWN, SoundCategory.BLOCKS, 1.0f, 1.0f); + event.setCancelled(true); } @@ -332,10 +334,10 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { boolean hasBedSpawn = bedSpawn != null && event.isBedSpawn(); RespawnResult respawnResult = determineRespawnLocation( - closestCheckpoint, - bedSpawn, - hasBedSpawn, - deathLocation, + closestCheckpoint, + bedSpawn, + hasBedSpawn, + deathLocation, configManager.getRespawnPriority(), configManager.getRadius() ); @@ -355,12 +357,22 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { final boolean usedBed = respawnResult.isBed; final boolean extinguished = respawnResult.isCheckpoint && configManager.isExtinguishOnRespawn(); + Checkpoint respawnCheckpoint = respawnResult.checkpoint; + boolean anchor = respawnCheckpoint.isAnchor(); + + Bukkit.getScheduler().runTaskLater(plugin, () -> { if (player.isOnline()) { if (usedCheckpoint) { - MessageUtil.send(player, "&aYou respawned at your campfire checkpoint!"); - if (extinguished) { - MessageUtil.send(player, "&7The campfire has been extinguished. Use Flint & Steel to relight it."); + if (anchor) { + MessageUtil.send(player, "&aYou respawned at your respawn anchor!"); + // Play respawn anchor spawn sound + player.playSound(player.getLocation(), Sound.RESPAWN_ANCHOR_SPAWN, SoundCategory.BLOCKS, 1.0f, 1.0f); /// + } else { + MessageUtil.send(player, "&aYou respawned at your campfire checkpoint!"); + if (extinguished) { + MessageUtil.send(player, "&7The campfire has been extinguished. Use Flint & Steel to relight it."); + } } } else if (usedBed) { } @@ -520,7 +532,9 @@ private void handleCheckpointRespawn( @NotNull CheckpointManager checkpointManager, @NotNull ConfigManager configManager) { - if (configManager.isExtinguishOnRespawn() || checkpoint.isAnchor()) { + boolean anchor = checkpoint.isAnchor(); + + if (configManager.isExtinguishOnRespawn() || anchor) { Location blockLoc = checkpoint.getBlockLocation(); if (blockLoc != null && blockLoc.getWorld() != null) { Block campfireBlock = blockLoc.getBlock(); @@ -530,7 +544,8 @@ private void handleCheckpointRespawn( lightable.setLit(false); campfireBlock.setBlockData(lightable); checkpointManager.setCheckpointLit(checkpoint, false); - } else if (checkpoint.isAnchor() && blockData instanceof RespawnAnchor anchorData) { + + } else if (anchor && blockData instanceof RespawnAnchor anchorData) { int charges = anchorData.getCharges() - 1; anchorData.setCharges(charges); campfireBlock.setBlockData(anchorData); @@ -538,6 +553,10 @@ private void handleCheckpointRespawn( if (charges <= 0) { checkpoint.setLit(false); checkpointManager.setCheckpointLit(checkpoint, false); + + if (blockLoc.getWorld() != null) { + blockLoc.getWorld().playSound(blockLoc, Sound.RESPAWN_ANCHOR_DEPLETED, SoundCategory.BLOCKS, 1.0f, 1.0f); + } } } } From 6ba6db31965084146ada11a03d0307750a66a4d2 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Fri, 13 Mar 2026 23:48:50 +0300 Subject: [PATCH 30/50] Code alignment and default overworld / end behavior --- .../listener/CheckpointListener.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index d99f402..5a12d7a 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -73,7 +73,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { if (env == World.Environment.NORMAL && !configManager.isDimentionEnabledOverworldAnchor()) { MessageUtil.send(player, "&cSetting respawn anchors as checkpoints is not allowed in the Overworld."); - event.setCancelled(true); + //event.setCancelled(true); return; } @@ -85,7 +85,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { if (env == World.Environment.THE_END && !configManager.isDimentionEnabledEndAnchor()) { MessageUtil.send(player, "&cSetting respawn anchors as checkpoints is not allowed in the End."); - event.setCancelled(true); + //event.setCancelled(true); return; } @@ -106,7 +106,8 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { BlockData blockData = clickedBlock.getBlockData(); if (blockData instanceof RespawnAnchor anchorData) { if (anchorData.getCharges() <= 0) { - MessageUtil.send(player, "&cThis respawn anchor is uncharged! Charge it with glowstone to set a checkpoint."); + MessageUtil.send(player, "&cThis respawn anchor is uncharged!" + + " Charge it with glowstone to set a checkpoint."); event.setCancelled(true); return; } @@ -118,7 +119,9 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { Checkpoint newCheckpoint = new Checkpoint(playerUUID, blockLocation); checkpointManager.addCheckpoint(playerUUID, newCheckpoint); - String worldName = blockLocation.getWorld() != null ? blockLocation.getWorld().getName().replace("world_", "") : "unknown"; + String worldName = blockLocation.getWorld() != null ? + blockLocation.getWorld().getName().replace("world_", "") : "unknown"; + MessageUtil.send(player, "&aCheckpoint set at respawn anchor &f(" + blockLocation.getBlockX() + ", " + blockLocation.getBlockY() + ", " @@ -367,11 +370,13 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { if (anchor) { MessageUtil.send(player, "&aYou respawned at your respawn anchor!"); // Play respawn anchor spawn sound - player.playSound(player.getLocation(), Sound.RESPAWN_ANCHOR_SPAWN, SoundCategory.BLOCKS, 1.0f, 1.0f); /// + player.playSound(player.getLocation(), Sound.RESPAWN_ANCHOR_SPAWN, + SoundCategory.BLOCKS, 1.0f, 1.0f); } else { MessageUtil.send(player, "&aYou respawned at your campfire checkpoint!"); if (extinguished) { - MessageUtil.send(player, "&7The campfire has been extinguished. Use Flint & Steel to relight it."); + MessageUtil.send(player, "&7The campfire has been extinguished." + + " Use Flint & Steel to relight it."); } } } else if (usedBed) { From abd15a4d6ff09447d4a85205b57c7865bd2b75f4 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 00:05:58 +0300 Subject: [PATCH 31/50] Proper sounds --- .../campfirecheckpoints/listener/CheckpointListener.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 5a12d7a..2f83a67 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -128,7 +128,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { + blockLocation.getBlockZ() + ", " + worldName + ")&a!"); - player.playSound(blockLocation, Sound.RESPAWN_ANCHOR_SET_SPAWN, SoundCategory.BLOCKS, 1.0f, 1.0f); + player.playSound(blockLocation, Sound.valueOf("BLOCK_RESPAWN_ANCHOR_SET_SPAWN"), SoundCategory.BLOCKS, 1.0f, 1.0f); event.setCancelled(true); @@ -369,9 +369,6 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { if (usedCheckpoint) { if (anchor) { MessageUtil.send(player, "&aYou respawned at your respawn anchor!"); - // Play respawn anchor spawn sound - player.playSound(player.getLocation(), Sound.RESPAWN_ANCHOR_SPAWN, - SoundCategory.BLOCKS, 1.0f, 1.0f); } else { MessageUtil.send(player, "&aYou respawned at your campfire checkpoint!"); if (extinguished) { @@ -560,7 +557,7 @@ private void handleCheckpointRespawn( checkpointManager.setCheckpointLit(checkpoint, false); if (blockLoc.getWorld() != null) { - blockLoc.getWorld().playSound(blockLoc, Sound.RESPAWN_ANCHOR_DEPLETED, SoundCategory.BLOCKS, 1.0f, 1.0f); + blockLoc.getWorld().playSound(blockLoc, Sound.valueOf("BLOCK_RESPAWN_ANCHOR_DEPLETE"), SoundCategory.BLOCKS, 1.0f, 1.0f); } } } From c651327d8df1f7c4c2475fd6f03f687ff4ba42b6 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 00:18:34 +0300 Subject: [PATCH 32/50] Fixes --- .../listener/CheckpointListener.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 2f83a67..383647e 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -360,9 +360,8 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { final boolean usedBed = respawnResult.isBed; final boolean extinguished = respawnResult.isCheckpoint && configManager.isExtinguishOnRespawn(); - Checkpoint respawnCheckpoint = respawnResult.checkpoint; - boolean anchor = respawnCheckpoint.isAnchor(); - + final @Nullable Checkpoint respawnCheckpoint = respawnResult.checkpoint; + final boolean anchor = respawnCheckpoint != null && respawnCheckpoint.isAnchor(); Bukkit.getScheduler().runTaskLater(plugin, () -> { if (player.isOnline()) { @@ -552,13 +551,14 @@ private void handleCheckpointRespawn( anchorData.setCharges(charges); campfireBlock.setBlockData(anchorData); + if (blockLoc.getWorld() != null) { + blockLoc.getWorld().playSound(blockLoc, Sound.valueOf("BLOCK_RESPAWN_ANCHOR_DEPLETE"), + SoundCategory.BLOCKS, 1.0f, 1.0f); + } + if (charges <= 0) { checkpoint.setLit(false); checkpointManager.setCheckpointLit(checkpoint, false); - - if (blockLoc.getWorld() != null) { - blockLoc.getWorld().playSound(blockLoc, Sound.valueOf("BLOCK_RESPAWN_ANCHOR_DEPLETE"), SoundCategory.BLOCKS, 1.0f, 1.0f); - } } } } From 5829f248c6520be4ce8305502fa7db1b3154c411 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 01:53:50 +0300 Subject: [PATCH 33/50] findAnchorCheckpoint --- .../manager/CheckpointManager.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index e98f408..73bde48 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -597,4 +597,53 @@ public void removeAnchorCheckpoints(@NotNull UUID playerUUID) { markDirty(); } } + + /* + * Returns the player's respawn-anchor checkpoint (if any). + * If multiple anchor checkpoints somehow exist, this method will keep the newest one + * (by createdAt) and remove the rest. + */ + public @Nullable Checkpoint findAnchorCheckpoint(@NotNull UUID playerUUID) { + List checkpoints = playerCheckpoints.get(playerUUID); + if (checkpoints == null) { + return null; + } + + Checkpoint best = null; + boolean removedAny = false; + + synchronized (checkpoints) { + for (Iterator it = checkpoints.iterator(); it.hasNext(); ) { + Checkpoint cp = it.next(); + if (cp == null || !cp.isAnchor()) { + continue; + } + + if (best == null) { + best = cp; + continue; + } + + // Keep the newest anchor checkpoint + if (cp.getCreatedAt() > best.getCreatedAt()) { + // remove previous best + locationIndex.remove(best.getLocationKey()); + it.remove(); + removedAny = true; + best = cp; + } else { + // remove current cp + locationIndex.remove(cp.getLocationKey()); + it.remove(); + removedAny = true; + } + } + } + + if (removedAny) { + markDirty(); + } + + return best; + } } \ No newline at end of file From 204986ff415c2392297f0bca1d490981d55824f8 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 01:57:08 +0300 Subject: [PATCH 34/50] Proper respawn location selection with anchors --- .../listener/CheckpointListener.java | 75 +++++++++++++++---- .../campfirecheckpoints/model/Checkpoint.java | 5 -- src/main/resources/config.yml | 2 +- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 383647e..61fdd8d 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -333,16 +333,29 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { Checkpoint closestCheckpoint = checkpointManager.findClosestCheckpoint(playerUUID, deathLocation); + double checkpointRadius = configManager.getRadius(); + if (closestCheckpoint && closestCheckpoint.isSoul()) { + checkpointRadius = configManager.getSoulRadius(); + } + Location bedSpawn = player.getRespawnLocation(); boolean hasBedSpawn = bedSpawn != null && event.isBedSpawn(); + // Find anchor checkpoint for this player, if any + Checkpoint anchorCheckpoint = checkpointManager.findAnchorCheckpoint(playerUUID); + Location anchorSpawn = (anchorCheckpoint != null) ? anchorCheckpoint.getSpawnLocation() : null; + boolean hasAnchorSpawn = anchorSpawn != null && anchorSpawn.getWorld() != null; + RespawnResult respawnResult = determineRespawnLocation( closestCheckpoint, bedSpawn, hasBedSpawn, + anchorCheckpoint, + anchorSpawn, + hasAnchorSpawn, deathLocation, configManager.getRespawnPriority(), - configManager.getRadius() + checkpointRadius ); if (respawnResult == null || respawnResult.location == null) { @@ -388,9 +401,12 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { @Nullable Checkpoint checkpoint, @Nullable Location bedSpawn, boolean hasBedSpawn, + @Nullable Checkpoint anchorCheckpoint, + @Nullable Location anchorSpawn, + boolean hasAnchorSpawn, @NotNull Location deathLocation, @NotNull RespawnPriority priority, - double radius) { + double checkpointRadius) { // Get checkpoint spawn location if available Location checkpointSpawn = null; @@ -402,34 +418,48 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { } // If neither is available, return null - if (!hasValidCheckpoint && !hasBedSpawn) { + if (!hasValidCheckpoint && !hasBedSpawn && !hasAnchorSpawn) { return null; } // If only checkpoint is available - if (hasValidCheckpoint && !hasBedSpawn) { + if (hasValidCheckpoint && !hasBedSpawn && !hasAnchorSpawn) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); } + // If only anchor is available + // If no checkpoint is available, but bed and anchor are, prioritize anchor + if (!hasValidCheckpoint && hasAnchorSpawn) { + // we have to treat anchor as a checkpoint to process its charge + return new RespawnResult(anchorSpawn, true, false, anchorCheckpoint); + } + // If only bed is available if (!hasValidCheckpoint && hasBedSpawn) { return new RespawnResult(bedSpawn, false, true, null); } - // Both are available - use priority setting + // Several options are available - use priority setting switch (priority) { case CHECKPOINT: return new RespawnResult(checkpointSpawn, true, false, checkpoint); case BED: + if (hasAnchorSpawn) { + // prioritize anchor over bed anyway + return new RespawnResult(anchorSpawn, true, false, anchorCheckpoint); + } return new RespawnResult(bedSpawn, false, true, null); case CLOSEST: return determineClosestRespawn( - checkpoint, checkpointSpawn, - bedSpawn, - deathLocation, - radius + checkpoint, + checkpointSpawn, + bedSpawn, + anchorCheckpoint, + anchorSpawn, + deathLocation, + checkpointRadius ); default: @@ -444,11 +474,14 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { @NotNull Checkpoint checkpoint, @NotNull Location checkpointSpawn, @NotNull Location bedSpawn, + @NotNull Checkpoint anchorCheckpoint, + @Nullable Location anchorSpawn, @NotNull Location deathLocation, - double radius) { + double checkpointRadius) { double checkpointDistSq = checkpoint.distanceSquared(deathLocation); double bedDistSq = calculateDistanceSquared(bedSpawn, deathLocation); + double anchorDistSq = calculateDistanceSquared(anchorSpawn, deathLocation); // Check if bed is in a different world if (bedSpawn.getWorld() == null || !bedSpawn.getWorld().equals(deathLocation.getWorld())) { @@ -456,20 +489,32 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); } + // Check if anchor is in a different world + if (anchorSpawn.getWorld() == null || !anchorSpawn.getWorld().equals(deathLocation.getWorld())) { + // Anchor is in different world, use checkpoint + return new RespawnResult(checkpointSpawn, true, false, checkpoint); + } + // Check if bed is within radius - double radiusSquared = radius * radius; + double radiusSquared = checkpointRadius * checkpointRadius; boolean bedWithinRadius = bedDistSq <= radiusSquared; + boolean anchorWithinRadius = anchorDistSq <= radiusSquared; // If bed is not within radius, use checkpoint - if (!bedWithinRadius) { + if (!bedWithinRadius && !anchorWithinRadius) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); } + boolean bedOrAnchorCloser = bedDistSq < anchorDistSq; + Location closerBedAnchorSpawn = bedOrAnchorCloser ? bedSpawn : anchorSpawn; + Checkpoint closerBedAnchorCheckpoint = bedOrAnchorCloser ? null : anchorCheckpoint; + double closerBedAnchorDistSq = Math.min(bedDistSq, anchorDistSq); + // Both are within radius, use the closer one - if (checkpointDistSq <= bedDistSq) { + if (checkpointDistSq <= closerBedAnchorDistSq) { return new RespawnResult(checkpointSpawn, true, false, checkpoint); } else { - return new RespawnResult(bedSpawn, false, true, null); + return new RespawnResult(closerBedAnchorSpawn, false, true, closerBedAnchorCheckpoint); } } @@ -571,7 +616,7 @@ private static final class RespawnResult { final boolean isBed; final @Nullable Checkpoint checkpoint; - RespawnResult(@Nullable Location location, boolean isCheckpoint, boolean isBed, + RespawnResult(@Nullable Location location, boolean isCheckpoint, boolean isBed, @Nullable Checkpoint checkpoint) { this.location = location; this.isCheckpoint = isCheckpoint; diff --git a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java index 0d19937..5206dd3 100644 --- a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java +++ b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java @@ -126,11 +126,6 @@ public double distanceSquared(@Nullable Location location) { return Double.MAX_VALUE; } - if (anchor) { - // If we have an active anchor, it is prioritized over distance checks - return 0; - } - if (!location.getWorld().getName().equals(worldName)) { return Double.MAX_VALUE; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index bf77a3b..90ab8eb 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -50,7 +50,7 @@ max-checkpoints-per-player: 0 # Priority when both bed spawn and campfire checkpoint are available within radius # Options: # "checkpoint" - Campfire checkpoint always takes priority over bed (default) -# "bed" - Bed spawn always takes priority over checkpoint +# "bed" - Bed spawn or respawn anchor always take priority over checkpoint # "closest" - Whichever is closer to the death location takes priority # # Note: This only applies when BOTH a valid bed spawn AND a lit checkpoint From 821ada096ba44b111946ae8ee1c5a9de6aa56b05 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 01:57:52 +0300 Subject: [PATCH 35/50] Small fix --- .../com/campfirecheckpoints/listener/CheckpointListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 61fdd8d..356dfe5 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -334,7 +334,7 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { Checkpoint closestCheckpoint = checkpointManager.findClosestCheckpoint(playerUUID, deathLocation); double checkpointRadius = configManager.getRadius(); - if (closestCheckpoint && closestCheckpoint.isSoul()) { + if (closestCheckpoint != null && closestCheckpoint.isSoul()) { checkpointRadius = configManager.getSoulRadius(); } From 9e55b89658422780ede1eb3710e4af71ce4c2a7f Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 02:03:18 +0300 Subject: [PATCH 36/50] Better removeAnchorCheckpoints function --- .../manager/CheckpointManager.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 73bde48..1f94764 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -571,29 +571,44 @@ private CheckpointValidationResult checkAndCollectInvalid(List check } + /* + * Deletes all respawn-anchor checkpoints for the given player. + * This is done in one pass and keeps {@link #locationIndex} in sync. + */ public void removeAnchorCheckpoints(@NotNull UUID playerUUID) { List checkpoints = playerCheckpoints.get(playerUUID); if (checkpoints == null) { return; } - List toRemove = new ArrayList<>(); + + boolean removedAny = false; + synchronized (checkpoints) { - for (Checkpoint checkpoint : checkpoints) { - if (checkpoint.isAnchor()) { - toRemove.add(checkpoint); + for (Iterator it = checkpoints.iterator(); it.hasNext(); ) { + Checkpoint cp = it.next(); + if (cp == null || !cp.isAnchor()) { + continue; + } + + locationIndex.remove(cp.getLocationKey()); + it.remove(); + removedAny = true; + + Player owner = Bukkit.getPlayer(playerUUID); + if (owner != null && owner.isOnline()) { + MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + + cp.getLocationKey() + " &chas been removed."); + plugin.getLogger().info("[Player] Removed anchor checkpoint at &f" + + cp.getLocationKey() + " &7for " + playerUUID + "."); } } - } - for (Checkpoint checkpoint : toRemove) { - removeCheckpointAt(checkpoint.getBlockLocation()); - Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); - if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + checkpoint.getLocationKey() + " &chas been removed."); - plugin.getLogger().info("[Player] Removed anchor checkpoint at &f" + checkpoint.getLocationKey() + " &7for " + playerUUID + "."); + if (checkpoints.isEmpty()) { + playerCheckpoints.remove(playerUUID); } } - if (!toRemove.isEmpty()) { + + if (removedAny) { markDirty(); } } From 0239c9464d958c8a1ee967eb3f9f545ab9991032 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 02:09:36 +0300 Subject: [PATCH 37/50] Bed resets spawn anchors --- .../campfirecheckpoints/listener/CheckpointListener.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 356dfe5..c18e71c 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -54,6 +54,13 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { return; } + // If player interacts with a bed, clear their respawn-anchor checkpoint(s), + // but do not cancel the bed interaction. + if (clickedBlock.getBlockData() instanceof org.bukkit.block.data.type.Bed) { + plugin.getCheckpointManager().removeAnchorCheckpoints(event.getPlayer().getUniqueId()); + return; + } + Material type = clickedBlock.getType(); boolean isRegularCampfire = (type == Material.CAMPFIRE); boolean isSoulCampfire = (type == Material.SOUL_CAMPFIRE); From daa0efc00639c093049ece6e201ccbe6f60815a2 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 02:14:07 +0300 Subject: [PATCH 38/50] Ignore unlit anchors --- .../listener/CheckpointListener.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index c18e71c..4eebeb4 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -350,8 +350,17 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { // Find anchor checkpoint for this player, if any Checkpoint anchorCheckpoint = checkpointManager.findAnchorCheckpoint(playerUUID); - Location anchorSpawn = (anchorCheckpoint != null) ? anchorCheckpoint.getSpawnLocation() : null; - boolean hasAnchorSpawn = anchorSpawn != null && anchorSpawn.getWorld() != null; + Location anchorSpawn = null; + boolean hasAnchorSpawn = false; + + if (anchorCheckpoint.isLit()) { + // if anchor is lit, we can consider it as a respawn point + anchorSpawn = (anchorCheckpoint != null) ? anchorCheckpoint.getSpawnLocation() : null; + hasAnchorSpawn = anchorSpawn != null && anchorSpawn.getWorld() != null; + } else { + // if anchor is not lit, we should ignore it for respawn purposes + anchorCheckpoint = null; + } RespawnResult respawnResult = determineRespawnLocation( closestCheckpoint, From aef2059096497a47fdfb1916d8944c56928efe57 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 02:22:00 +0300 Subject: [PATCH 39/50] Anchors destroyed message --- .../listener/CheckpointListener.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 4eebeb4..d0c9852 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -653,7 +653,8 @@ public void onBlockBreak(@NotNull BlockBreakEvent event) { Block block = event.getBlock(); Material type = block.getType(); - if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) { + // Handle campfire and respawn anchor checkpoints + if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE && type != Material.RESPAWN_ANCHOR) { return; } @@ -664,10 +665,11 @@ public void onBlockBreak(@NotNull BlockBreakEvent event) { if (checkpoint != null) { Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); + String checkpointType = checkpoint.isAnchor() ? "respawn anchor" : "campfire checkpoint"; if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&cYour campfire checkpoint at &f(" + - blockLocation.getBlockX() + ", " + - blockLocation.getBlockY() + ", " + + MessageUtil.send(owner, "&cYour " + checkpointType + " at &f(" + + blockLocation.getBlockX() + ", " + + blockLocation.getBlockY() + ", " + blockLocation.getBlockZ() + ") &chas been destroyed!"); } From 64e590965443eb96c80cf22f8bb446cd200373fa Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 02:27:24 +0300 Subject: [PATCH 40/50] Better messages --- .../listener/CheckpointListener.java | 8 ++++---- .../manager/CheckpointManager.java | 17 +++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index d0c9852..3788019 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -57,7 +57,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { // If player interacts with a bed, clear their respawn-anchor checkpoint(s), // but do not cancel the bed interaction. if (clickedBlock.getBlockData() instanceof org.bukkit.block.data.type.Bed) { - plugin.getCheckpointManager().removeAnchorCheckpoints(event.getPlayer().getUniqueId()); + plugin.getCheckpointManager().removeAnchorCheckpoints(event.getPlayer().getUniqueId(), true); return; } @@ -121,7 +121,7 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { } // Remove all other anchor checkpoints, there can be only one per player - checkpointManager.removeAnchorCheckpoints(playerUUID); + checkpointManager.removeAnchorCheckpoints(playerUUID, false); Checkpoint newCheckpoint = new Checkpoint(playerUUID, blockLocation); checkpointManager.addCheckpoint(playerUUID, newCheckpoint); @@ -575,12 +575,12 @@ public void onAnchorChargeOrDischarge(org.bukkit.event.block.BlockPhysicsEvent e Player owner = Bukkit.getPlayer(checkpoint.getOwnerUUID()); if (owner != null && owner.isOnline()) { if (isNowLit) { - MessageUtil.send(owner, "&aYour respawn anchor checkpoint at &f(" + + MessageUtil.send(owner, "&aYour respawn anchor at &f(" + blockLoc.getBlockX() + ", " + blockLoc.getBlockY() + ", " + blockLoc.getBlockZ() + ") &ahas been charged!"); } else { - MessageUtil.send(owner, "&eYour respawn anchor checkpoint at &f(" + + MessageUtil.send(owner, "&eYour respawn anchor at &f(" + blockLoc.getBlockX() + ", " + blockLoc.getBlockY() + ", " + blockLoc.getBlockZ() + ") &ehas no charge left!"); diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 1f94764..ade127e 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -553,13 +553,13 @@ private CheckpointValidationResult checkAndCollectInvalid(List check result.unchargedAnchors++; if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + checkpoint.getLocationKey() + " &eis not charged."); + MessageUtil.send(owner, "&eYour anchor at &f" + checkpoint.getLocationKey() + " &eis not charged."); } } else if (anchorData.getCharges() > 0 && !checkpoint.isLit()) { setCheckpointLit(checkpoint, true); if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour anchor checkpoint at &f" + checkpoint.getLocationKey() + " &ahas been recharged."); + MessageUtil.send(owner, "&eYour anchor at &f" + checkpoint.getLocationKey() + " &ahas been recharged."); } } } else { @@ -575,7 +575,7 @@ private CheckpointValidationResult checkAndCollectInvalid(List check * Deletes all respawn-anchor checkpoints for the given player. * This is done in one pass and keeps {@link #locationIndex} in sync. */ - public void removeAnchorCheckpoints(@NotNull UUID playerUUID) { + public void removeAnchorCheckpoints(@NotNull UUID playerUUID, boolean byBed) { List checkpoints = playerCheckpoints.get(playerUUID); if (checkpoints == null) { return; @@ -596,9 +596,14 @@ public void removeAnchorCheckpoints(@NotNull UUID playerUUID) { Player owner = Bukkit.getPlayer(playerUUID); if (owner != null && owner.isOnline()) { - MessageUtil.send(owner, "&eYour anchor checkpoint at &f" - + cp.getLocationKey() + " &chas been removed."); - plugin.getLogger().info("[Player] Removed anchor checkpoint at &f" + + if (byBed) { + MessageUtil.send(owner, "&eYour respawn anchor at &f" + + cp.getLocationKey() + " &ehas been disabled."); + } else { + MessageUtil.send(owner, "&eYour respawn anchor at &f" + + cp.getLocationKey() + " &chas been removed."); + } + plugin.getLogger().info("[Player] Removed respawn anchor at &f" + cp.getLocationKey() + " &7for " + playerUUID + "."); } } From 7918da9311e41ad29bb3476294d94124aa84214c Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 11:13:29 +0300 Subject: [PATCH 41/50] Comment fix --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e62a915..7c1127c 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 21 UTF-8 - + From dae5956d9609bfe0bdfcb86192a4a3a669bc706f Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 11:16:54 +0300 Subject: [PATCH 42/50] No override anchors --- .../com/campfirecheckpoints/manager/CheckpointManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index ade127e..4ac3221 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -357,6 +357,10 @@ private void executeOverrideInternal(@NotNull UUID playerUUID, @NotNull PendingO synchronized (checkpoints) { for (Checkpoint checkpoint : checkpoints) { if (checkpoint.isWithinRadius(pending.newLocation, pending.radius)) { + if (checkpoint.isAnchor()) { + // Don't allow override to remove anchor checkpoints + continue; + } toRemove.add(checkpoint); keysToRemove.add(checkpoint.getLocationKey()); } From 788751f44b0dcc1d4bd97d77f6aea37b7a7674a9 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 11:34:12 +0300 Subject: [PATCH 43/50] Fixed anchors always within radius --- .../campfirecheckpoints/manager/CheckpointManager.java | 8 ++------ .../java/com/campfirecheckpoints/model/Checkpoint.java | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java index 4ac3221..84ffc28 100644 --- a/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/CheckpointManager.java @@ -252,7 +252,7 @@ public boolean removeCheckpoint(@NotNull UUID playerUUID, int index) { synchronized (checkpoints) { for (Checkpoint checkpoint : checkpoints) { - if (checkpoint.isWithinRadius(location, radius)) { + if (checkpoint.isWithinRadius(location, radius, false)) { return checkpoint; } } @@ -356,11 +356,7 @@ private void executeOverrideInternal(@NotNull UUID playerUUID, @NotNull PendingO synchronized (checkpoints) { for (Checkpoint checkpoint : checkpoints) { - if (checkpoint.isWithinRadius(pending.newLocation, pending.radius)) { - if (checkpoint.isAnchor()) { - // Don't allow override to remove anchor checkpoints - continue; - } + if (checkpoint.isWithinRadius(pending.newLocation, pending.radius, false)) { toRemove.add(checkpoint); keysToRemove.add(checkpoint.getLocationKey()); } diff --git a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java index 5206dd3..2a03d2b 100644 --- a/src/main/java/com/campfirecheckpoints/model/Checkpoint.java +++ b/src/main/java/com/campfirecheckpoints/model/Checkpoint.java @@ -150,9 +150,9 @@ public boolean isWithinRespawnRadius(@Nullable Location location, } - public boolean isWithinRadius(@Nullable Location location, double radius) { + public boolean isWithinRadius(@Nullable Location location, double radius, boolean includeAnchors) { if (anchor) { - return true; // Respawn anchors are not affected by radius checks + return includeAnchors; // Respawn anchors are not affected by radius checks } if (location == null) { From 47f2df190d11ff298eaddddb2e593bfb84675603 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 14:03:01 +0300 Subject: [PATCH 44/50] Some NULL fixes --- .../campfirecheckpoints/listener/CheckpointListener.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 3788019..39352ab 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -353,7 +353,7 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { Location anchorSpawn = null; boolean hasAnchorSpawn = false; - if (anchorCheckpoint.isLit()) { + if (anchorCheckpoint != null && anchorCheckpoint.isLit()) { // if anchor is lit, we can consider it as a respawn point anchorSpawn = (anchorCheckpoint != null) ? anchorCheckpoint.getSpawnLocation() : null; hasAnchorSpawn = anchorSpawn != null && anchorSpawn.getWorld() != null; @@ -483,7 +483,7 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { } } - /** + /* * Determines which respawn point is closer to death location */ private @NotNull RespawnResult determineClosestRespawn( @@ -497,7 +497,10 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { double checkpointDistSq = checkpoint.distanceSquared(deathLocation); double bedDistSq = calculateDistanceSquared(bedSpawn, deathLocation); - double anchorDistSq = calculateDistanceSquared(anchorSpawn, deathLocation); + double anchorDistSq = Double.MAX_VALUE; + + if (anchorSpawn != null) + anchorDistSq = calculateDistanceSquared(anchorSpawn, deathLocation); // Check if bed is in a different world if (bedSpawn.getWorld() == null || !bedSpawn.getWorld().equals(deathLocation.getWorld())) { From 5a4e4df88fad6fd367cf4a395a1d3c9ec8cd0be4 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 14:22:44 +0300 Subject: [PATCH 45/50] Fixed and improved selection of respawn place --- .../listener/CheckpointListener.java | 282 ++++++++---------- 1 file changed, 126 insertions(+), 156 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 39352ab..215201b 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -339,39 +339,19 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { checkpointManager.validateAllCheckpoints(playerUUID); Checkpoint closestCheckpoint = checkpointManager.findClosestCheckpoint(playerUUID, deathLocation); + final RespawnOption spawnAtCheckpoint = new RespawnOption(closestCheckpoint, configManager); - double checkpointRadius = configManager.getRadius(); - if (closestCheckpoint != null && closestCheckpoint.isSoul()) { - checkpointRadius = configManager.getSoulRadius(); - } - - Location bedSpawn = player.getRespawnLocation(); - boolean hasBedSpawn = bedSpawn != null && event.isBedSpawn(); + final RespawnOption spawnAtBed = new RespawnOption(player.getRespawnLocation(), + null, false, true, event.isBedSpawn(), configManager); // Find anchor checkpoint for this player, if any Checkpoint anchorCheckpoint = checkpointManager.findAnchorCheckpoint(playerUUID); - Location anchorSpawn = null; - boolean hasAnchorSpawn = false; - - if (anchorCheckpoint != null && anchorCheckpoint.isLit()) { - // if anchor is lit, we can consider it as a respawn point - anchorSpawn = (anchorCheckpoint != null) ? anchorCheckpoint.getSpawnLocation() : null; - hasAnchorSpawn = anchorSpawn != null && anchorSpawn.getWorld() != null; - } else { - // if anchor is not lit, we should ignore it for respawn purposes - anchorCheckpoint = null; - } - - RespawnResult respawnResult = determineRespawnLocation( - closestCheckpoint, - bedSpawn, - hasBedSpawn, - anchorCheckpoint, - anchorSpawn, - hasAnchorSpawn, - deathLocation, - configManager.getRespawnPriority(), - checkpointRadius + final RespawnOption spawnAtAnchor = new RespawnOption(anchorCheckpoint, configManager); + + // Select best option + RespawnOption respawnResult = determineRespawnLocation( + spawnAtCheckpoint, spawnAtAnchor, spawnAtBed, deathLocation, + configManager.getRespawnPriority() ); if (respawnResult == null || respawnResult.location == null) { @@ -387,15 +367,13 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { final boolean usedCheckpoint = respawnResult.isCheckpoint; final boolean usedBed = respawnResult.isBed; + final boolean usedAnchor = respawnResult.isAnchor; final boolean extinguished = respawnResult.isCheckpoint && configManager.isExtinguishOnRespawn(); - final @Nullable Checkpoint respawnCheckpoint = respawnResult.checkpoint; - final boolean anchor = respawnCheckpoint != null && respawnCheckpoint.isAnchor(); - Bukkit.getScheduler().runTaskLater(plugin, () -> { if (player.isOnline()) { if (usedCheckpoint) { - if (anchor) { + if (usedAnchor) { MessageUtil.send(player, "&aYou respawned at your respawn anchor!"); } else { MessageUtil.send(player, "&aYou respawned at your campfire checkpoint!"); @@ -413,142 +391,45 @@ public void onPlayerRespawn(@NotNull PlayerRespawnEvent event) { /** * Determines the respawn location based on priority settings */ - private @Nullable RespawnResult determineRespawnLocation( - @Nullable Checkpoint checkpoint, - @Nullable Location bedSpawn, - boolean hasBedSpawn, - @Nullable Checkpoint anchorCheckpoint, - @Nullable Location anchorSpawn, - boolean hasAnchorSpawn, + private @Nullable RespawnOption determineRespawnLocation( + final @NotNull RespawnOption checkpointSpawn, + final @NotNull RespawnOption anchorSpawn, + final @NotNull RespawnOption bedSpawn, @NotNull Location deathLocation, - @NotNull RespawnPriority priority, - double checkpointRadius) { - - // Get checkpoint spawn location if available - Location checkpointSpawn = null; - boolean hasValidCheckpoint = false; - - if (checkpoint != null) { - checkpointSpawn = checkpoint.getSpawnLocation(); - hasValidCheckpoint = checkpointSpawn != null && checkpointSpawn.getWorld() != null; - } + @NotNull RespawnPriority priority) { - // If neither is available, return null - if (!hasValidCheckpoint && !hasBedSpawn && !hasAnchorSpawn) { - return null; - } + boolean hasBedSpawn = bedSpawn.isValidWithinRadius(deathLocation); + boolean hasAnchorSpawn = anchorSpawn.isValidWithinRadius(deathLocation); + boolean hasValidCheckpoint = checkpointSpawn.isValidWithinRadius(deathLocation); - // If only checkpoint is available - if (hasValidCheckpoint && !hasBedSpawn && !hasAnchorSpawn) { - return new RespawnResult(checkpointSpawn, true, false, checkpoint); - } + boolean hasBedOrAnchorSpawn = hasBedSpawn || hasAnchorSpawn; + RespawnOption bedOrAnchorSpawn = hasAnchorSpawn ? anchorSpawn : bedSpawn; - // If only anchor is available - // If no checkpoint is available, but bed and anchor are, prioritize anchor - if (!hasValidCheckpoint && hasAnchorSpawn) { - // we have to treat anchor as a checkpoint to process its charge - return new RespawnResult(anchorSpawn, true, false, anchorCheckpoint); + if (!hasBedOrAnchorSpawn) { + return hasValidCheckpoint ? checkpointSpawn : null; } - // If only bed is available - if (!hasValidCheckpoint && hasBedSpawn) { - return new RespawnResult(bedSpawn, false, true, null); + if (!hasValidCheckpoint) { + return bedOrAnchorSpawn; } - // Several options are available - use priority setting + // several options available - use priority setting switch (priority) { case CHECKPOINT: - return new RespawnResult(checkpointSpawn, true, false, checkpoint); + return checkpointSpawn; case BED: - if (hasAnchorSpawn) { - // prioritize anchor over bed anyway - return new RespawnResult(anchorSpawn, true, false, anchorCheckpoint); - } - return new RespawnResult(bedSpawn, false, true, null); + return bedOrAnchorSpawn; case CLOSEST: - return determineClosestRespawn( - checkpoint, - checkpointSpawn, - bedSpawn, - anchorCheckpoint, - anchorSpawn, - deathLocation, - checkpointRadius - ); - - default: - return new RespawnResult(checkpointSpawn, true, false, checkpoint); - } - } + double checkpointDistSq = checkpointSpawn.distanceSquared(deathLocation); + double bedOrAnchorDistSq = bedOrAnchorSpawn.distanceSquared(deathLocation); - /* - * Determines which respawn point is closer to death location - */ - private @NotNull RespawnResult determineClosestRespawn( - @NotNull Checkpoint checkpoint, - @NotNull Location checkpointSpawn, - @NotNull Location bedSpawn, - @NotNull Checkpoint anchorCheckpoint, - @Nullable Location anchorSpawn, - @NotNull Location deathLocation, - double checkpointRadius) { - - double checkpointDistSq = checkpoint.distanceSquared(deathLocation); - double bedDistSq = calculateDistanceSquared(bedSpawn, deathLocation); - double anchorDistSq = Double.MAX_VALUE; - - if (anchorSpawn != null) - anchorDistSq = calculateDistanceSquared(anchorSpawn, deathLocation); - - // Check if bed is in a different world - if (bedSpawn.getWorld() == null || !bedSpawn.getWorld().equals(deathLocation.getWorld())) { - // Bed is in different world, use checkpoint - return new RespawnResult(checkpointSpawn, true, false, checkpoint); - } - - // Check if anchor is in a different world - if (anchorSpawn.getWorld() == null || !anchorSpawn.getWorld().equals(deathLocation.getWorld())) { - // Anchor is in different world, use checkpoint - return new RespawnResult(checkpointSpawn, true, false, checkpoint); - } - - // Check if bed is within radius - double radiusSquared = checkpointRadius * checkpointRadius; - boolean bedWithinRadius = bedDistSq <= radiusSquared; - boolean anchorWithinRadius = anchorDistSq <= radiusSquared; - - // If bed is not within radius, use checkpoint - if (!bedWithinRadius && !anchorWithinRadius) { - return new RespawnResult(checkpointSpawn, true, false, checkpoint); - } - - boolean bedOrAnchorCloser = bedDistSq < anchorDistSq; - Location closerBedAnchorSpawn = bedOrAnchorCloser ? bedSpawn : anchorSpawn; - Checkpoint closerBedAnchorCheckpoint = bedOrAnchorCloser ? null : anchorCheckpoint; - double closerBedAnchorDistSq = Math.min(bedDistSq, anchorDistSq); - - // Both are within radius, use the closer one - if (checkpointDistSq <= closerBedAnchorDistSq) { - return new RespawnResult(checkpointSpawn, true, false, checkpoint); - } else { - return new RespawnResult(closerBedAnchorSpawn, false, true, closerBedAnchorCheckpoint); - } - } + return bedOrAnchorDistSq < checkpointDistSq ? bedOrAnchorSpawn : checkpointSpawn; - private double calculateDistanceSquared(@NotNull Location a, @NotNull Location b) { - if (a.getWorld() == null || b.getWorld() == null) { - return Double.MAX_VALUE; - } - if (!a.getWorld().equals(b.getWorld())) { - return Double.MAX_VALUE; + default: + return checkpointSpawn; } - - double dx = a.getX() - b.getX(); - double dy = a.getY() - b.getY(); - double dz = a.getZ() - b.getZ(); - return dx * dx + dy * dy + dz * dz; } @EventHandler(priority = EventPriority.MONITOR) @@ -629,18 +510,107 @@ private void handleCheckpointRespawn( } } - private static final class RespawnResult { + private static final class RespawnOption { final @Nullable Location location; + final @Nullable Checkpoint checkpoint; final boolean isCheckpoint; final boolean isBed; - final @Nullable Checkpoint checkpoint; + final boolean isAnchor; + final boolean valid; + final double radiusSq; + private final ConfigManager configManager; - RespawnResult(@Nullable Location location, boolean isCheckpoint, boolean isBed, - @Nullable Checkpoint checkpoint) { + RespawnOption(@Nullable Location location, @Nullable Checkpoint checkpoint, + boolean isCheckpoint, boolean isBed, boolean valid, @NotNull ConfigManager configManager) { this.location = location; + this.checkpoint = checkpoint; this.isCheckpoint = isCheckpoint; this.isBed = isBed; + this.valid = valid; + this.configManager = configManager; + + this.isAnchor = isCheckpoint && checkpoint != null && checkpoint.isAnchor(); + this.radiusSq = calcRadiusSq(); + } + + RespawnOption(@Nullable Checkpoint checkpoint, @NotNull ConfigManager configManager) { + this.configManager = configManager; + if (checkpoint == null || !checkpoint.isLit()) { + this.location = null; + this.checkpoint = checkpoint; + this.isCheckpoint = true; + this.isBed = false; + this.valid = false; + this.isAnchor = false; + this.radiusSq = 0; + return; + } + + this.location = checkpoint.getSpawnLocation(); this.checkpoint = checkpoint; + this.isCheckpoint = true; + this.isBed = false; + this.valid = location != null && location.getWorld() != null; + + this.isAnchor = checkpoint.isAnchor(); + this.radiusSq = calcRadiusSq(); + } + + private double calcRadiusSq() { + if (isBed || isAnchor) { + return Double.MAX_VALUE; + } + if (!isCheckpoint) { + // neither a bed, an anchor nor a checkpoint, return 0 radius just in case + return 0; + } + + double radius; + if (checkpoint != null && checkpoint.isSoul()) { + radius = configManager.getSoulRadius(); + } else { + radius = configManager.getRadius(); + } + return radius * radius; + } + + public boolean isValid() { + if (!valid) { + return false; + } + if (location == null || location.getWorld() == null) { + return false; + } + if (isCheckpoint && checkpoint == null) { + return false; + } + return true; + } + + public boolean isValidWithinRadius(@NotNull Location other) { + if (!this.isValid()) { + return false; + } + if (radiusSq == Double.MAX_VALUE) { + // if it is an anchor or a bed, radiusSq is Double.MAX_VALUE + return true; + } + // if the dimensions differ, distanceSquared returns Double.MAX_VALUE + return distanceSquared(other) <= radiusSq; + } + + public double distanceSquared(@NotNull Location other) { + if (!this.isValid() || other.getWorld() == null) { + return Double.MAX_VALUE; + } + if (!location.getWorld().equals(other.getWorld())) { + return Double.MAX_VALUE; + } + + double dx = location.getX() - other.getX(); + double dy = location.getY() - other.getY(); + double dz = location.getZ() - other.getZ(); + return dx * dx + dy * dy + dz * dz; } } From 47a0a1b37bba270054feaa871a2cc4a1b43a345f Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 14:48:15 +0300 Subject: [PATCH 46/50] Respawn sound --- .../listener/CheckpointListener.java | 13 ++++++++---- .../manager/ConfigManager.java | 20 ++++++++++++++++++- src/main/resources/config.yml | 5 +++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 215201b..fb31b55 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -491,15 +491,20 @@ private void handleCheckpointRespawn( campfireBlock.setBlockData(lightable); checkpointManager.setCheckpointLit(checkpoint, false); + // Play configured respawn sound + Sound respawnSound = configManager.getSoundOnRespawn(); + if (respawnSound != null) { + blockLoc.getWorld().playSound(blockLoc, Sound.valueOf(respawnSound.name()), + SoundCategory.BLOCKS, 1.0f, 1.0f); + } + } else if (anchor && blockData instanceof RespawnAnchor anchorData) { int charges = anchorData.getCharges() - 1; anchorData.setCharges(charges); campfireBlock.setBlockData(anchorData); - if (blockLoc.getWorld() != null) { - blockLoc.getWorld().playSound(blockLoc, Sound.valueOf("BLOCK_RESPAWN_ANCHOR_DEPLETE"), - SoundCategory.BLOCKS, 1.0f, 1.0f); - } + blockLoc.getWorld().playSound(blockLoc, Sound.valueOf("BLOCK_RESPAWN_ANCHOR_DEPLETE"), + SoundCategory.BLOCKS, 1.0f, 1.0f); if (charges <= 0) { checkpoint.setLit(false); diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index c38abfb..e2e8b36 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -33,6 +33,7 @@ public final class ConfigManager { private int soulMinDistance; private boolean extinguishOnRespawn; private @NotNull Sound soundOnSet; + private @NotNull Sound soundOnRespawn; private int overrideConfirmationTimeout; private int maxCheckpointsPerPlayer; private @NotNull RespawnPriority respawnPriority; @@ -56,6 +57,7 @@ public final class ConfigManager { private static final int DEFAULT_SOUL_MIN_DISTANCE = 500; private static final boolean DEFAULT_EXTINGUISH = true; private static final Sound DEFAULT_SOUND = Sound.BLOCK_RESPAWN_ANCHOR_SET_SPAWN; + private static final Sound DEFAULT_SOUND_ON_RESPAWN = Sound.BLOCK_RESPAWN_ANCHOR_DEPLETE; private static final int DEFAULT_OVERRIDE_TIMEOUT = 5; private static final int DEFAULT_MAX_CHECKPOINTS = 0; // 0 = unlimited private static final RespawnPriority DEFAULT_RESPAWN_PRIORITY = RespawnPriority.CHECKPOINT; @@ -66,6 +68,7 @@ public final class ConfigManager { public ConfigManager(@NotNull CampfireCheckpoints plugin) { this.plugin = plugin; this.soundOnSet = DEFAULT_SOUND; + this.soundOnRespawn = DEFAULT_SOUND_ON_RESPAWN; this.respawnPriority = DEFAULT_RESPAWN_PRIORITY; reload(); } @@ -149,6 +152,16 @@ public void reload() { this.soundOnSet = DEFAULT_SOUND; } + // Load respawn sound + String respawnSoundName = config.getString("sound-on-respawn", DEFAULT_SOUND_ON_RESPAWN.name()); + try { + this.soundOnRespawn = Sound.valueOf(respawnSoundName.toUpperCase()); + } catch (IllegalArgumentException e) { + plugin.getLogger().log(Level.WARNING, + "Invalid sound-on-respawn '" + respawnSoundName + "' in config. Using default."); + this.soundOnRespawn = DEFAULT_SOUND_ON_RESPAWN; + } + // Load empty-hand-or-sneak-required this.emptyHandOrSneakRequired = config.getBoolean("require-empty-hand-or-sneak", DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED); @@ -174,7 +187,8 @@ public void reload() { ", Timeout: " + overrideConfirmationTimeout + "s" + ", MaxCheckpoints: " + (maxCheckpointsPerPlayer == 0 ? "unlimited" : maxCheckpointsPerPlayer) + ", RespawnPriority: " + respawnPriority.getConfigValue() + - ", Sound: " + soundOnSet.name()); + ", Sound: " + soundOnSet.name() + + ", SoundOnRespawn: " + soundOnRespawn.name()); } public boolean isDimentionEnabledOverworld() { @@ -230,6 +244,10 @@ public boolean isExtinguishOnRespawn() { return soundOnSet; } + public @NotNull Sound getSoundOnRespawn() { + return soundOnRespawn; + } + public int getOverrideConfirmationTimeout() { return overrideConfirmationTimeout; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 90ab8eb..b3e2556 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -33,9 +33,14 @@ soul-campfire-min-distance: 500 extinguish-on-respawn: true # The sound played when a checkpoint is successfully set. +# Default: BLOCK_RESPAWN_ANCHOR_SET_SPAWN # Valid sounds: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html sound-on-set: BLOCK_RESPAWN_ANCHOR_SET_SPAWN +# The sound played when a player respawns using a checkpoint (campfire or respawn anchor). +# Default: BLOCK_RESPAWN_ANCHOR_DEPLETE +sound-on-respawn: BLOCK_RESPAWN_ANCHOR_DEPLETE + # Time in seconds that players have to confirm overriding an existing checkpoint # When a player tries to set a checkpoint within radius of an existing one, # they must right-click again within this time to confirm From 0cbaef58372a5ff19f28895ed99a593d65c7c12e Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 14:51:21 +0300 Subject: [PATCH 47/50] Fixed respawn anchors old behaviour with glowstone in nether --- .../listener/CheckpointListener.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index fb31b55..9a3707e 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -75,6 +75,17 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { Material itemInHand = player.getInventory().getItemInMainHand().getType(); if (itemInHand == Material.GLOWSTONE) { + // Allow charging with glowstone, but prevent setting spawn if fully charged in Nether + if (env == World.Environment.NETHER) { + BlockData blockData = clickedBlock.getBlockData(); + if (blockData instanceof RespawnAnchor anchorData) { + if (anchorData.getCharges() >= ((RespawnAnchor) blockData).getMaximumCharges()) { + // Anchor is fully charged, prevent further charging and setting spawn + event.setCancelled(true); + return; + } + } + } return; } From 692c8c125357e804a4876760a6ebd73633c06838 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 15:11:19 +0300 Subject: [PATCH 48/50] Fixed block placing with anchor --- .../com/campfirecheckpoints/listener/CheckpointListener.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 9a3707e..88dd5cb 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -72,6 +72,11 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { // Deny respawn anchor spawnpoint in Nether unless enabled, but allow charging with glowstone if (isRespawnAnchor && configManager.RespawnAnchorsEnabled()) { + // Prevent any respawn anchor logic if player is sneaking + if (player.isSneaking()) { + return; + } + Material itemInHand = player.getInventory().getItemInMainHand().getType(); if (itemInHand == Material.GLOWSTONE) { From ae3ad36eac0a0327a3212ad53810886685f10c85 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 15:12:49 +0300 Subject: [PATCH 49/50] Version tweak --- pom.xml | 2 +- src/main/resources/plugin.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7c1127c..39b0f0b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.campfirecheckpoints CampfireCheckpoints - 1.2.0-parar + 1.2.0 jar CampfireCheckpoints diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c0330ed..9fc5afa 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,10 +1,10 @@ # src/main/resources/plugin.yml name: CampfireCheckpoints -version: '1.2.0-parar' +version: '1.2.0' main: com.campfirecheckpoints.CampfireCheckpoints api-version: '1.21' description: A respawn system using campfires as checkpoints -author: fbi +author: fbi + tweaks by parar020100 commands: cc: From 7706c7f8776e2e5074e66c156652f4ef92658dc6 Mon Sep 17 00:00:00 2001 From: Artiom Parygin Date: Sat, 14 Mar 2026 15:59:39 +0300 Subject: [PATCH 50/50] Config fix --- .../listener/CheckpointListener.java | 8 +++++++ .../manager/ConfigManager.java | 21 ++++++++++++++++--- src/main/resources/config.yml | 8 +++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java index 88dd5cb..66c9a2d 100644 --- a/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java +++ b/src/main/java/com/campfirecheckpoints/listener/CheckpointListener.java @@ -72,6 +72,14 @@ public void onPlayerInteract(@NotNull PlayerInteractEvent event) { // Deny respawn anchor spawnpoint in Nether unless enabled, but allow charging with glowstone if (isRespawnAnchor && configManager.RespawnAnchorsEnabled()) { + + // If vanilla respawn anchors are enabled in the Nether, don't intercept anchor interaction there + if (env == World.Environment.NETHER + && !configManager.isDimentionEnabledNetherAnchor() + && configManager.vanillaAnchorsEnabledInNether()) { + return; + } + // Prevent any respawn anchor logic if player is sneaking if (player.isSneaking()) { return; diff --git a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java index e2e8b36..f4ad519 100644 --- a/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java +++ b/src/main/java/com/campfirecheckpoints/manager/ConfigManager.java @@ -27,6 +27,8 @@ public final class ConfigManager { private boolean netherRespawnAnchorsEnabled; private boolean endRespawnAnchorsEnabled; + private boolean vanillaRespawnAnchorsNether; + private int radius; private int soulRadius; private int minDistance; @@ -64,21 +66,23 @@ public final class ConfigManager { private static final boolean DEFAULT_EMPTY_HAND_OR_SNEAK_REQUIRED = true; private static final boolean DEFAULT_DELETE_COMMAND_ALLOWED = true; private static final boolean DEFAULT_RESPAWN_ANCHORS_ENABLED = false; + private static final boolean DEFAULT_VANILLA_RESPAWN_ANCHORS_NETHER = false; public ConfigManager(@NotNull CampfireCheckpoints plugin) { this.plugin = plugin; this.soundOnSet = DEFAULT_SOUND; this.soundOnRespawn = DEFAULT_SOUND_ON_RESPAWN; this.respawnPriority = DEFAULT_RESPAWN_PRIORITY; + this.vanillaRespawnAnchorsNether = DEFAULT_VANILLA_RESPAWN_ANCHORS_NETHER; reload(); } public void reload() { FileConfiguration config = plugin.getConfig(); - this.overworldEnableRegularCampfires = config.getBoolean("enable-overworld", DEFAULT_DIMENTION_OVERWORLD); - this.netherEnableRegularCampfires = config.getBoolean("enable-nether", DEFAULT_DIMENTION_NETHER); - this.endEnableRegularCampfires = config.getBoolean("enable-end", DEFAULT_DIMENTION_END); + this.overworldEnableRegularCampfires = config.getBoolean("enable-regular-overworld", DEFAULT_DIMENTION_OVERWORLD); + this.netherEnableRegularCampfires = config.getBoolean("enable-regular-nether", DEFAULT_DIMENTION_NETHER); + this.endEnableRegularCampfires = config.getBoolean("enable-regular-end", DEFAULT_DIMENTION_END); this.overworldEnableSoulCampfires = config.getBoolean("enable-soul-overworld", DEFAULT_DIMENTION_OVERWORLD_SOUL); this.netherEnableSoulCampfires = config.getBoolean("enable-soul-nether", DEFAULT_DIMENTION_NETHER_SOUL); @@ -86,6 +90,12 @@ public void reload() { this.respawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors", DEFAULT_RESPAWN_ANCHORS_ENABLED); + // Vanilla respawn anchor mechanics toggle for Nether (only makes sense if anchors are enabled) + this.vanillaRespawnAnchorsNether = config.getBoolean( + "vanilla-respawn-anchors-in-nether", + DEFAULT_VANILLA_RESPAWN_ANCHORS_NETHER + ); + if (this.respawnAnchorsEnabled) { this.overworldRespawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors-overworld", DEFAULT_DIMENTION_OVERWORLD_ANCHOR); this.netherRespawnAnchorsEnabled = config.getBoolean("enable-respawn-anchors-nether", DEFAULT_DIMENTION_NETHER_ANCHOR); @@ -179,6 +189,7 @@ public void reload() { ", Respawn anchors enabled (overworld): " + overworldRespawnAnchorsEnabled + ", Respawn anchors enabled (nether): " + netherRespawnAnchorsEnabled + ", Respawn anchors enabled (end): " + endRespawnAnchorsEnabled + + ", Vanilla respawn anchors (nether): " + vanillaRespawnAnchorsNether + ", Radius: " + radius + ", Radius (soul campfires): " + soulRadius + ", Min. distance: " + minDistance + @@ -279,4 +290,8 @@ public boolean isDeleteCommandAllowed() { public boolean RespawnAnchorsEnabled() { return respawnAnchorsEnabled; } + + public boolean vanillaAnchorsEnabledInNether() { + return vanillaRespawnAnchorsNether; + } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b3e2556..67aa49b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -81,3 +81,11 @@ enable-respawn-anchors: false enable-respawn-anchors-overworld: false enable-respawn-anchors-nether: true enable-respawn-anchors-end: false + +# By default, this plugin removes the vanilla respawn anchor mechanics +# Set this to true if you want respawn anchors to function in the Nether +# with vanilla-style (overriding the bed spawn point) +# +# NOTE: This doesn't work if enable-respawn-anchors-nether is true +# NOTE: This doesn't matter if enable-respawn-anchors is false +vanilla-respawn-anchors-in-nether: false