LoMines/src/main/java/dev/loki/lomines/handler/MineResetHandler.java
loki5512344 bb2e47a917 Add player-spawn config for stuck players and limit teleport height
- Add PlayerSpawnConfig for separate spawn location when players are stuck in blocks
- Add PlayerSpawnConfigLoader for YAML parsing
- Update MineConfig with playerSpawn section and getSpawnForStuckPlayer() helper
- Update ConfigLoader to include playerSpawn configuration
- Update MineFileManager to preserve playerSpawn when saving
- Update MineResetHandler.teleportPlayers() to use player-spawn location
- Update defaults.yml with player-spawn documentation

- Limit findSafeTeleportLocation() to max 3 blocks upward search
  - Priority: same level nearby, then up (max 3), then down, then diagonal
  - Prevents teleporting players too high in the air

If player-spawn is not configured, falls back to teleport.location for backward compatibility.
2026-05-31 23:03:40 +02:00

198 lines
6.4 KiB
Java

package dev.loki.lomines.handler;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.Mine;
import dev.loki.lomines.data.config.block.FillMode;
import dev.loki.lomines.util.block.BlockUpdateUtil;
import dev.loki.lomines.util.location.Cuboid;
import dev.loki.lomines.util.location.LocationParser;
import dev.lolib.scheduler.Scheduler;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Handles mine reset operations.
* Ensures thread-safe reset execution and prevents concurrent resets.
*
* <p>Reset process:</p>
* <ol>
* <li>Check if reset is already running (atomic check)</li>
* <li>Fill all regions asynchronously via BlockSetter</li>
* <li>Wait for all regions to complete</li>
* <li>Execute post-reset actions in main thread</li>
* </ol>
*
* <p>Updated for new configuration system (v2).</p>
*/
public final class MineResetHandler {
private final Mine mine;
private final LoMinesPlugin plugin;
private final AtomicBoolean running = new AtomicBoolean(false);
public MineResetHandler(Mine mine, LoMinesPlugin plugin) {
this.mine = mine;
this.plugin = plugin;
}
/**
* Resets the mine by filling all regions with blocks.
* If a reset is already in progress, this call is ignored.
*
* @param silent If true, suppresses broadcast messages
*/
public void reset(boolean silent) {
if (!running.compareAndSet(false, true)) {
return;
}
try {
resetAllRegions(silent);
} catch (Exception e) {
running.set(false);
plugin.loLogger().error("Failed to reset mine " + mine.getName() + ": " + e.getMessage());
e.printStackTrace();
}
}
/**
* Fills all regions asynchronously and coordinates completion.
*/
private void resetAllRegions(boolean silent) {
List<Cuboid> regions = mine.getRegions();
AtomicInteger completed = new AtomicInteger(0);
AtomicInteger totalBlocks = new AtomicInteger(0);
if (mine.getBlockSetter() == null) {
plugin.loLogger().warn("BlockSetter not initialized for mine " + mine.getName());
running.set(false);
return;
}
// Handle MASK fill mode
if (mine.getConfig().blocks().fillMode() == FillMode.MASK) {
List<Location> maskPositions = getMaskPositions();
if (maskPositions.isEmpty()) {
plugin.loLogger().warn("No mask positions found for mine " + mine.getName());
running.set(false);
return;
}
mine.getBlockSetter().fillAtLocations(maskPositions, placed ->
Scheduler.get(plugin).run(() -> onResetComplete(placed, silent)));
return;
}
// Handle CUBOID fill mode
for (Cuboid region : regions) {
mine.getBlockSetter().fill(region, blocksSet -> {
totalBlocks.addAndGet(blocksSet);
if (completed.incrementAndGet() == regions.size()) {
Scheduler.get(plugin).run(() -> {
onResetComplete(totalBlocks.get(), silent);
});
}
});
}
}
/**
* Gets mask positions from config.
*/
private List<Location> getMaskPositions() {
List<Location> positions = new ArrayList<>();
var mask = mine.getConfig().blocks().mask();
if (mask == null) {
return positions;
}
for (String posStr : mask.positions().keySet()) {
try {
Location loc = LocationParser.parse(posStr);
if (loc != null) {
positions.add(loc);
}
} catch (Exception e) {
plugin.loLogger().warn("Invalid mask position in mine " + mine.getName() + ": " + posStr);
}
}
return positions;
}
/**
* Called when all regions have been filled.
* Executes post-reset actions in the main thread.
*/
private void onResetComplete(int totalBlocks, boolean silent) {
mine.getBlocksAtomic().set(totalBlocks);
mine.getTicksAtomic().set(0);
executeResetCommands();
if (!silent) {
broadcastReset();
}
if (mine.getConfig().teleport().enabled()) {
teleportPlayers();
}
running.set(false);
}
/**
* Executes commands configured for reset.
*/
private void executeResetCommands() {
List<String> commands = mine.getConfig().reset().commands();
for (String command : commands) {
String parsed = command.replace("%mine%", mine.getName());
plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), parsed);
}
}
/**
* Broadcasts reset message to players.
*/
private void broadcastReset() {
String message = mine.getConfig().reset().broadcastMessage();
if (message != null && !message.isEmpty()) {
String formatted = message.replace("%mine%", mine.getName());
Component component = MiniMessage.miniMessage().deserialize(formatted);
plugin.getServer().broadcast(component);
}
}
/**
* Teleports players standing inside the mine to a safe location.
* Uses player-spawn location if configured, otherwise uses teleport location.
* Prevents players from suffocating in blocks by finding a safe teleport spot.
*/
private void teleportPlayers() {
// Get spawn location for stuck players (player-spawn config) or fall back to teleport location
var destOpt = mine.getConfig().getSpawnForStuckPlayer();
if (destOpt.isEmpty()) {
return;
}
Location dest = destOpt.get();
if (dest.getWorld() == null) {
return;
}
// Find a safe teleport location (max 3 blocks up to avoid teleporting too high)
Location safeDest = BlockUpdateUtil.findSafeTeleportLocation(dest, 3);
for (Player p : dest.getWorld().getPlayers()) {
if (mine.contains(p.getLocation())) {
p.teleport(safeDest);
}
}
}
}