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.
This commit is contained in:
parent
0603096f60
commit
bb2e47a917
8 changed files with 195 additions and 16 deletions
|
|
@ -8,6 +8,7 @@ import dev.loki.lomines.data.config.block.FillMode;
|
|||
import dev.loki.lomines.data.config.region.RegionConfig;
|
||||
import dev.loki.lomines.data.config.reset.ResetConfig;
|
||||
import dev.loki.lomines.data.config.reward.RewardConfig;
|
||||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.data.config.teleport.TeleportConfig;
|
||||
import dev.loki.lomines.data.config.ui.UIConfig;
|
||||
import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
|
||||
|
|
@ -15,6 +16,7 @@ import dev.loki.lomines.util.location.Cuboid;
|
|||
import dev.loki.lomines.util.location.LocationParser;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -76,6 +78,7 @@ public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
|
|||
.teleport(TeleportConfig.disabled())
|
||||
.ui(UIConfig.defaults())
|
||||
.worldGuard(wgConfig)
|
||||
.playerSpawn(PlayerSpawnConfig.disabled())
|
||||
.build();
|
||||
|
||||
// Save using new loader
|
||||
|
|
@ -160,6 +163,7 @@ public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
|
|||
.teleport(config.teleport())
|
||||
.ui(config.ui())
|
||||
.worldGuard(config.worldGuard())
|
||||
.playerSpawn(config.playerSpawn())
|
||||
.build();
|
||||
|
||||
configLoader.save(updated);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public final class ConfigLoader {
|
|||
private TeleportConfigLoader teleportLoader;
|
||||
private UIConfigLoader uiLoader;
|
||||
private WorldGuardConfigLoader worldGuardLoader;
|
||||
private PlayerSpawnConfigLoader playerSpawnLoader;
|
||||
|
||||
public ConfigLoader(Path dataFolder) {
|
||||
this.dataFolder = dataFolder;
|
||||
|
|
@ -66,6 +67,7 @@ public final class ConfigLoader {
|
|||
yaml.set("rewards", List.of());
|
||||
|
||||
worldGuardLoader.setDefaults(yaml);
|
||||
playerSpawnLoader.setDefaults(yaml);
|
||||
}
|
||||
|
||||
private void initLoaders() {
|
||||
|
|
@ -76,6 +78,7 @@ public final class ConfigLoader {
|
|||
teleportLoader = new TeleportConfigLoader();
|
||||
uiLoader = new UIConfigLoader(defaults);
|
||||
worldGuardLoader = new WorldGuardConfigLoader(defaults);
|
||||
playerSpawnLoader = new PlayerSpawnConfigLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,6 +103,7 @@ public final class ConfigLoader {
|
|||
.teleport(teleportLoader.parse(yaml))
|
||||
.ui(uiLoader.parse(yaml))
|
||||
.worldGuard(worldGuardLoader.parse(yaml))
|
||||
.playerSpawn(playerSpawnLoader.parse(yaml))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
throw new ConfigLoadException("Failed to load mine '" + mineName + "': " + e.getMessage(), e);
|
||||
|
|
@ -120,6 +124,7 @@ public final class ConfigLoader {
|
|||
teleportLoader.save(yaml, config.teleport());
|
||||
uiLoader.save(yaml, config.ui());
|
||||
worldGuardLoader.save(yaml, config.worldGuard());
|
||||
playerSpawnLoader.save(yaml, config.playerSpawn());
|
||||
|
||||
try {
|
||||
Files.createDirectories(configPath.getParent());
|
||||
|
|
|
|||
|
|
@ -4,11 +4,15 @@ import dev.loki.lomines.data.config.block.BlockConfig;
|
|||
import dev.loki.lomines.data.config.region.RegionConfig;
|
||||
import dev.loki.lomines.data.config.reset.ResetConfig;
|
||||
import dev.loki.lomines.data.config.reward.RewardConfig;
|
||||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.data.config.teleport.TeleportConfig;
|
||||
import dev.loki.lomines.data.config.ui.UIConfig;
|
||||
import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Clean, type-safe, section-based mine configuration.
|
||||
|
|
@ -22,7 +26,8 @@ public record MineConfig(
|
|||
RewardConfig rewards,
|
||||
TeleportConfig teleport,
|
||||
UIConfig ui,
|
||||
WorldGuardConfig worldGuard
|
||||
WorldGuardConfig worldGuard,
|
||||
PlayerSpawnConfig playerSpawn
|
||||
) {
|
||||
|
||||
public MineConfig {
|
||||
|
|
@ -38,7 +43,10 @@ public record MineConfig(
|
|||
Objects.requireNonNull(rewards, "Reward config cannot be null");
|
||||
Objects.requireNonNull(teleport, "Teleport config cannot be null");
|
||||
Objects.requireNonNull(ui, "UI config cannot be null");
|
||||
// worldGuard can be null (disabled by default)
|
||||
// worldGuard and playerSpawn can be null (disabled by default)
|
||||
if (playerSpawn == null) {
|
||||
playerSpawn = PlayerSpawnConfig.disabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,10 +82,21 @@ public record MineConfig(
|
|||
RewardConfig.empty(),
|
||||
TeleportConfig.disabled(),
|
||||
UIConfig.defaults(),
|
||||
WorldGuardConfig.disabled()
|
||||
WorldGuardConfig.disabled(),
|
||||
PlayerSpawnConfig.disabled()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spawn location for stuck players.
|
||||
* If playerSpawn is not set, falls back to teleport location.
|
||||
*/
|
||||
public Optional<Location> getSpawnForStuckPlayer() {
|
||||
return playerSpawn.enabled()
|
||||
? playerSpawn.getLocation()
|
||||
: teleport.getLocation();
|
||||
}
|
||||
|
||||
// --- Builder ---
|
||||
|
||||
public static class Builder {
|
||||
|
|
@ -89,6 +108,7 @@ public record MineConfig(
|
|||
private TeleportConfig teleport = TeleportConfig.disabled();
|
||||
private UIConfig ui = UIConfig.defaults();
|
||||
private WorldGuardConfig worldGuard = WorldGuardConfig.disabled();
|
||||
private PlayerSpawnConfig playerSpawn = PlayerSpawnConfig.disabled();
|
||||
|
||||
private Builder(String name) {
|
||||
this.name = name;
|
||||
|
|
@ -129,8 +149,13 @@ public record MineConfig(
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder playerSpawn(PlayerSpawnConfig playerSpawn) {
|
||||
this.playerSpawn = playerSpawn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MineConfig build() {
|
||||
return new MineConfig(name, region, blocks, reset, rewards, teleport, ui, worldGuard);
|
||||
return new MineConfig(name, region, blocks, reset, rewards, teleport, ui, worldGuard, playerSpawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
|
||||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.util.location.LocationParser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
/**
|
||||
* Loader for player spawn configuration section.
|
||||
*/
|
||||
public final class PlayerSpawnConfigLoader {
|
||||
|
||||
/**
|
||||
* Parses player spawn configuration from YAML.
|
||||
*/
|
||||
public PlayerSpawnConfig parse(YamlConfiguration yaml) {
|
||||
boolean enabled = yaml.getBoolean("player-spawn.enabled", false);
|
||||
if (!enabled) {
|
||||
return PlayerSpawnConfig.disabled();
|
||||
}
|
||||
|
||||
String locStr = yaml.getString("player-spawn.location");
|
||||
if (locStr == null || locStr.isBlank()) {
|
||||
return PlayerSpawnConfig.disabled();
|
||||
}
|
||||
|
||||
return PlayerSpawnConfig.at(parseLocation(locStr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves player spawn configuration to YAML.
|
||||
*/
|
||||
public void save(YamlConfiguration yaml, PlayerSpawnConfig spawn) {
|
||||
yaml.set("player-spawn.enabled", spawn.enabled());
|
||||
spawn.getLocation().ifPresent(loc ->
|
||||
yaml.set("player-spawn.location", LocationParser.format(loc)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default values for player spawn configuration.
|
||||
*/
|
||||
public void setDefaults(YamlConfiguration yaml) {
|
||||
yaml.set("player-spawn.enabled", false);
|
||||
yaml.set("player-spawn.location", "");
|
||||
}
|
||||
|
||||
private Location parseLocation(String str) {
|
||||
String[] parts = str.split(";");
|
||||
if (parts.length < 4) {
|
||||
throw new IllegalArgumentException("Invalid location format: " + str);
|
||||
}
|
||||
|
||||
World world = Bukkit.getWorld(parts[0]);
|
||||
if (world == null) {
|
||||
throw new IllegalArgumentException("Unknown world: " + parts[0]);
|
||||
}
|
||||
|
||||
double x = Double.parseDouble(parts[1]);
|
||||
double y = Double.parseDouble(parts[2]);
|
||||
double z = Double.parseDouble(parts[3]);
|
||||
float yaw = parts.length > 4 ? Float.parseFloat(parts[4]) : 0;
|
||||
float pitch = parts.length > 5 ? Float.parseFloat(parts[5]) : 0;
|
||||
|
||||
return new Location(world, x, y, z, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package dev.loki.lomines.data.config.spawn;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Configuration for player spawn location when stuck in blocks.
|
||||
* Used to teleport players out when they are suffocating after mine reset.
|
||||
*/
|
||||
public record PlayerSpawnConfig(
|
||||
boolean enabled,
|
||||
Location location
|
||||
) {
|
||||
|
||||
public PlayerSpawnConfig {
|
||||
if (!enabled) {
|
||||
location = null;
|
||||
} else if (location != null) {
|
||||
// Defensive copy
|
||||
location = location.clone();
|
||||
Objects.requireNonNull(location.getWorld(), "Spawn world cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spawn location if enabled.
|
||||
*/
|
||||
public Optional<Location> getLocation() {
|
||||
return Optional.ofNullable(enabled ? location : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabled spawn config (default).
|
||||
*/
|
||||
public static PlayerSpawnConfig disabled() {
|
||||
return new PlayerSpawnConfig(false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates enabled config with location.
|
||||
*/
|
||||
public static PlayerSpawnConfig at(Location location) {
|
||||
return new PlayerSpawnConfig(true, location);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,11 +171,13 @@ public final class MineResetHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Teleports players standing inside the mine to a safe location near the configured destination.
|
||||
* 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() {
|
||||
var destOpt = mine.getConfig().teleport().getLocation();
|
||||
// Get spawn location for stuck players (player-spawn config) or fall back to teleport location
|
||||
var destOpt = mine.getConfig().getSpawnForStuckPlayer();
|
||||
if (destOpt.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -184,8 +186,8 @@ public final class MineResetHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
// Find a safe teleport location to prevent suffocation
|
||||
Location safeDest = BlockUpdateUtil.findSafeTeleportLocation(dest);
|
||||
// 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())) {
|
||||
|
|
|
|||
|
|
@ -130,11 +130,13 @@ public final class BlockUpdateUtil {
|
|||
/**
|
||||
* Finds a safe teleport location near the given destination.
|
||||
* Checks for suffocation hazards (blocks at head/body level).
|
||||
* <p>Will not teleport too high - limited to maxUpOffset blocks above original.</p>
|
||||
*
|
||||
* @param destination the desired destination
|
||||
* @param maxUpOffset maximum blocks to search upward (to avoid teleporting too high)
|
||||
* @return a safe location (may be the same as destination if safe)
|
||||
*/
|
||||
public static Location findSafeTeleportLocation(Location destination) {
|
||||
public static Location findSafeTeleportLocation(Location destination, int maxUpOffset) {
|
||||
if (destination == null || destination.getWorld() == null) {
|
||||
return destination;
|
||||
}
|
||||
|
|
@ -146,29 +148,40 @@ public final class BlockUpdateUtil {
|
|||
float yaw = destination.getYaw();
|
||||
float pitch = destination.getPitch();
|
||||
|
||||
// Clamp maxUpOffset to reasonable range (2-10)
|
||||
maxUpOffset = Math.max(2, Math.min(maxUpOffset, 10));
|
||||
|
||||
// Check if original location is safe
|
||||
if (isSafeLocation(world, x, y, z)) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
// Search upward for a safe spot (priority: don't drop player down)
|
||||
for (int offset = 1; offset <= 5; offset++) {
|
||||
// Search nearby blocks first (same Y level, closer distance)
|
||||
int[][] nearby = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (int[] offset : nearby) {
|
||||
if (isSafeLocation(world, x + offset[0], y, z + offset[1])) {
|
||||
return new Location(world, x + offset[0] + 0.5, y, z + offset[1] + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Search upward (limited by maxUpOffset to avoid teleporting too high)
|
||||
for (int offset = 1; offset <= maxUpOffset; offset++) {
|
||||
if (isSafeLocation(world, x, y + offset, z)) {
|
||||
return new Location(world, x + 0.5, y + offset, z + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Search downward if no safe spot above
|
||||
// Search downward
|
||||
for (int offset = 1; offset <= 5 && y - offset >= world.getMinHeight(); offset++) {
|
||||
if (isSafeLocation(world, x, y - offset, z)) {
|
||||
return new Location(world, x + 0.5, y - offset, z + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Search nearby blocks
|
||||
int[][] nearby = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
|
||||
for (int[] offset : nearby) {
|
||||
for (int yOffset = 0; yOffset <= 3; yOffset++) {
|
||||
// Search nearby with small Y offset
|
||||
int[][] nearbyDiagonal = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
|
||||
for (int[] offset : nearbyDiagonal) {
|
||||
for (int yOffset = -1; yOffset <= maxUpOffset; yOffset++) {
|
||||
int newY = y + yOffset;
|
||||
if (newY < world.getMinHeight() || newY >= world.getMaxHeight()) {
|
||||
continue;
|
||||
|
|
@ -183,6 +196,13 @@ public final class BlockUpdateUtil {
|
|||
return new Location(world, x + 0.5, y, z + 0.5, yaw, pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a safe teleport location with default max up offset of 3 blocks.
|
||||
*/
|
||||
public static Location findSafeTeleportLocation(Location destination) {
|
||||
return findSafeTeleportLocation(destination, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a location is safe for teleport (no suffocation).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ teleport:
|
|||
enabled: false
|
||||
location: ""
|
||||
|
||||
# Player spawn location for stuck players
|
||||
# Used when a player gets stuck in blocks after mine reset
|
||||
# If not set, falls back to 'teleport.location'
|
||||
player-spawn:
|
||||
enabled: false
|
||||
location: ""
|
||||
# Example: "world;100.5;64;200.5;90;0"
|
||||
|
||||
# WorldGuard integration
|
||||
worldguard:
|
||||
# Enable automatic region creation when mines are created
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue