Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/java/com/artillexstudios/axgraves/AxGraves.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import com.artillexstudios.axgraves.grave.SpawnedGraves;
import com.artillexstudios.axgraves.listeners.DeathListener;
import com.artillexstudios.axgraves.listeners.PlayerInteractListener;
import com.artillexstudios.axgraves.listeners.PlayerMoveListener;
import com.artillexstudios.axgraves.schedulers.SaveGraves;
import com.artillexstudios.axgraves.schedulers.TickGraves;
import com.artillexstudios.axgraves.utils.LocationUtils;
import com.artillexstudios.axgraves.utils.UpdateNotifier;
import org.bstats.bukkit.Metrics;
import revxrsal.commands.bukkit.BukkitCommandHandler;
Expand Down Expand Up @@ -49,6 +51,7 @@ public void enable() {

getServer().getPluginManager().registerEvents(new DeathListener(), this);
getServer().getPluginManager().registerEvents(new PlayerInteractListener(), this);
getServer().getPluginManager().registerEvents(new PlayerMoveListener(), this);

final BukkitCommandHandler handler = BukkitCommandHandler.create(instance);
handler.register(new Commands());
Expand Down Expand Up @@ -81,6 +84,8 @@ public void disable() {
SpawnedGraves.saveToFile();
}

LocationUtils.clearAllLastSolidLocations();

EXECUTOR.shutdownNow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.artillexstudios.axgraves.grave.Grave;
import com.artillexstudios.axgraves.grave.SpawnedGraves;
import com.artillexstudios.axgraves.utils.ExperienceUtils;
import com.artillexstudios.axgraves.utils.LocationUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
Expand Down Expand Up @@ -39,8 +40,7 @@ public void onDeath(@NotNull PlayerDeathEvent event) {
xp = Math.round(ExperienceUtils.getExp(player) * CONFIG.getFloat("xp-keep-percentage", 1f));
}

Location location = player.getLocation();
location.add(0, -0.5, 0);
Location location = LocationUtils.findIntelligentGraveLocation(player, player.getLocation());

List<ItemStack> drops = null;
if (!event.getKeepInventory()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.artillexstudios.axgraves.listeners;

import com.artillexstudios.axgraves.utils.LocationUtils;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;

public class PlayerMoveListener implements Listener {

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerMove(@NotNull PlayerMoveEvent event) {
Player player = event.getPlayer();

if (player.getGameMode() == GameMode.SPECTATOR || player.isFlying()) return;

if (!hasPlayerMovedBlocks(event)) return;

Location playerLoc = player.getLocation();

if (isLocationSafeForTracking(playerLoc)) {
LocationUtils.setLastSolidLocation(player, LocationUtils.roundLocation(playerLoc));
}
}

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
LocationUtils.removeLastSolidLocation(event.getPlayer());
}

private boolean hasPlayerMovedBlocks(@NotNull PlayerMoveEvent event) {
Location to = event.getTo();
if (to == null) return false;

Location from = event.getFrom();
return to.getBlockX() != from.getBlockX() ||
to.getBlockY() != from.getBlockY() ||
to.getBlockZ() != from.getBlockZ();
}

private boolean isLocationSafeForTracking(@NotNull Location location) {
World world = location.getWorld();
if (world == null) return false;

int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();

// Check void first
if (y <= world.getMinHeight()) return false;

// Get current block and block below
Block currentBlock = world.getBlockAt(x, y, z);
Block blockBelow = world.getBlockAt(x, y - 1, z);

Material currentType = currentBlock.getType();
Material belowType = blockBelow.getType();

// Must have solid ground below (not lava)
if (!belowType.isSolid() || belowType == Material.LAVA) return false;

// Must not be standing in lava
if (currentType == Material.LAVA) return false;

return true;
}
}
230 changes: 230 additions & 0 deletions src/main/java/com/artillexstudios/axgraves/utils/LocationUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@

import com.artillexstudios.axapi.libs.boostedyaml.block.implementation.Section;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static com.artillexstudios.axgraves.AxGraves.CONFIG;

public class LocationUtils {

private static final Map<UUID, Location> lastSolidLocations = new HashMap<>();

// Spiral search pattern with pre-calculated offsets
private static final int[][] SPIRAL_OFFSETS = {
{0, 0}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1},
{2, 0}, {2, 1}, {2, 2}, {1, 2}, {0, 2}, {-1, 2}, {-2, 2}, {-2, 1}, {-2, 0},
{-2, -1}, {-2, -2}, {-1, -2}, {0, -2}, {1, -2}, {2, -2}, {2, -1}
};

@NotNull
public static Location getCenterOf(@NotNull Location location, boolean keepPitchYaw) {
Expand Down Expand Up @@ -43,4 +60,217 @@ public static void clampLocation(Location location) {
}
location.setY(Math.clamp(location.getY(), min, max));
}

@NotNull
public static Location roundLocation(@NotNull Location location) {
return new Location(
location.getWorld(),
location.getBlockX() + 0.5,
location.getBlockY(),
location.getBlockZ() + 0.5,
location.getYaw(),
location.getPitch()
);
}

public static void setLastSolidLocation(@NotNull Player player, @NotNull Location location) {
lastSolidLocations.put(player.getUniqueId(), new Location(
location.getWorld(),
location.getX(),
location.getY(),
location.getZ(),
location.getYaw(),
location.getPitch()
));
}

@Nullable
public static Location getLastSolidLocation(@NotNull Player player) {
Location location = lastSolidLocations.get(player.getUniqueId());

if (location == null) return null;

World locationWorld = location.getWorld();
if (locationWorld == null || !locationWorld.equals(player.getWorld())) {
return null;
}

Block blockBelow = locationWorld.getBlockAt(
location.getBlockX(),
location.getBlockY() - 1,
location.getBlockZ()
);

if (!blockBelow.getType().isSolid()) {
return null;
}

return new Location(
locationWorld,
location.getX(),
location.getY(),
location.getZ(),
location.getYaw(),
location.getPitch()
);
}

public static void removeLastSolidLocation(@NotNull Player player) {
lastSolidLocations.remove(player.getUniqueId());
}

public static void clearAllLastSolidLocations() {
lastSolidLocations.clear();
}

public static boolean isLocationSafe(@NotNull Location location) {
World world = location.getWorld();
if (world == null) return false;

int blockX = location.getBlockX();
int blockY = location.getBlockY();
int blockZ = location.getBlockZ();

Block currentBlock = world.getBlockAt(blockX, blockY, blockZ);
Block blockBelow = world.getBlockAt(blockX, blockY - 1, blockZ);

// Location is safe if current block is not solid and block below is solid
return !currentBlock.getType().isSolid() && blockBelow.getType().isSolid();
}

public static boolean isInAir(@NotNull Location location) {
World world = location.getWorld();
if (world == null) return false;

int blockX = location.getBlockX();
int blockY = location.getBlockY();
int blockZ = location.getBlockZ();

// Check if current block is air
if (world.getBlockAt(blockX, blockY, blockZ).getType() != Material.AIR) {
return false;
}

// Check if no solid ground within 5 blocks below
int minY = world.getMinHeight();
int maxChecks = Math.min(5, blockY - minY);

for (int i = 1; i <= maxChecks; i++) {
if (world.getBlockAt(blockX, blockY - i, blockZ).getType().isSolid()) {
return false;
}
}

return true;
}

@NotNull
public static Location findIntelligentGraveLocation(@NotNull Player player, @NotNull Location deathLocation) {
boolean isDangerous = false;
World world = deathLocation.getWorld();

if (world == null) {
isDangerous = true;
} else {
double y = deathLocation.getY();
if (y <= world.getMinHeight()) {
isDangerous = true; // void
} else {
Material blockType = world.getBlockAt(
deathLocation.getBlockX(),
deathLocation.getBlockY(),
deathLocation.getBlockZ()
).getType();

if (blockType == Material.LAVA) {
isDangerous = true; // lava
} else if (blockType.isSolid()) {
isDangerous = true; // emmured in solid block
} else if (blockType == Material.AIR && isInAir(deathLocation)) {
isDangerous = true; // floating in air
}
}
}

// If dangerous, try to use last solid location
if (isDangerous) {
Location lastSolid = getLastSolidLocation(player);
if (lastSolid != null && isLocationSafe(lastSolid)) {
lastSolid.add(0, -0.5, 0);
return lastSolid;
}
}

// Try to find a safe location near the death location
Location safeLocation = findNearestSafeLocation(deathLocation);
if (safeLocation != null) {
safeLocation.add(0, -0.5, 0);
return safeLocation;
}

// Fallback to original behavior
Location fallback = new Location(
deathLocation.getWorld(),
deathLocation.getX(),
deathLocation.getY() - 0.5,
deathLocation.getZ(),
deathLocation.getYaw(),
deathLocation.getPitch()
);
clampLocation(fallback);
return fallback;
}

@Nullable
private static Location findNearestSafeLocation(@NotNull Location center) {
World world = center.getWorld();
if (world == null) return null;

int centerX = center.getBlockX();
int centerZ = center.getBlockZ();

// First check center location
if (isLocationSafeAtCoords(world, centerX, centerZ, center.getBlockY())) {
return roundLocation(center);
}

for (int[] offset : SPIRAL_OFFSETS) {
int testX = centerX + offset[0];
int testZ = centerZ + offset[1];

Location groundLoc = findSolidGroundAtCoords(world, testX, testZ, center.getBlockY());
if (groundLoc != null && isLocationSafeAtCoords(world, testX, testZ, groundLoc.getBlockY())) {
return groundLoc;
}
}

return null;
}

private static boolean isLocationSafeAtCoords(@NotNull World world, int x, int z, int y) {
Block currentBlock = world.getBlockAt(x, y, z);
Block blockBelow = world.getBlockAt(x, y - 1, z);

return !currentBlock.getType().isSolid() && blockBelow.getType().isSolid();
}

@Nullable
private static Location findSolidGroundAtCoords(@NotNull World world, int x, int z, int startY) {
int maxSearchDistance = Math.min(50, world.getMaxHeight() - startY);
int minY = world.getMinHeight();

for (int i = 0; i < maxSearchDistance; i++) {
int currentY = startY - i;
if (currentY <= minY) break;

Block blockBelow = world.getBlockAt(x, currentY - 1, z);
Material type = blockBelow.getType();

if (type.isSolid() && type != Material.LAVA) {
return new Location(world, x + 0.5, currentY, z + 0.5);
}
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;

import static com.artillexstudios.axgraves.AxGraves.CONFIG;
Expand Down