fixed placement issues in BuildBattle and added isInArea methode to Position.java

This commit is contained in:
jannis committed 2026-10-25 00:17:33 +02:00
1 parent 8dc165952a
commit a18a6b0dca
4 files changed
+1227 -9

No files matched your search

@@ -0,0 +1,896 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.buildBattle;
import net.minestom.server.coordinate.BlockVec;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockInteractEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.utils.Direction;
import java.util.Map;
import java.util.Set;
public final class BlockPlacementHelper {
private BlockPlacementHelper() {}
private static final Set<String> SIX_DIR = Set.of(
"observer", "dispenser", "dropper", "piston", "sticky_piston",
"amethyst_cluster", "small_amethyst_bud", "medium_amethyst_bud", "large_amethyst_bud",
"end_rod", "lightning_rod", "shulker_box"
);
public static void apply(PlayerBlockPlaceEvent event) {
Block block = event.getBlock();
BlockFace face = event.getBlockFace();
BlockVec pos = event.getBlockPosition();
Point cursor = event.getCursorPosition();
Player player = event.getPlayer();
Instance instance = event.getInstance();
String path = block.key().value();
Block existing = instance.getBlock(pos);
if ((path.endsWith("_candle") || path.equals("candle")) && existing.compare(block)) {
int n = parseIntOr(existing.getProperty("candles"), 1);
event.setBlock(n < 4 ? existing.withProperty("candles", String.valueOf(n + 1)) : existing);
return;
}
if (path.equals("snow") && existing.compare(block)) {
int n = parseIntOr(existing.getProperty("layers"), 1);
event.setBlock(n < 8 ? existing.withProperty("layers", String.valueOf(n + 1)) : existing);
return;
}
if (path.equals("sea_pickle") && existing.compare(block)) {
int n = parseIntOr(existing.getProperty("pickles"), 1);
event.setBlock(n < 4 ? existing.withProperty("pickles", String.valueOf(n + 1)) : existing);
return;
}
if (path.equals("turtle_egg") && existing.compare(block)) {
int n = parseIntOr(existing.getProperty("eggs"), 1);
event.setBlock(n < 4 ? existing.withProperty("eggs", String.valueOf(n + 1)) : existing);
return;
}
Block oriented = orient(block, path, face, cursor, player, instance, pos);
if (oriented != block) event.setBlock(oriented);
Block finalOriented = oriented;
String finalPath = finalOriented.key().value();
if (path.endsWith("_door")) {
Block lower = event.getBlock();
String facing = lower.getProperty("facing");
String hinge = lower.getProperty("hinge");
Block upper = lower.withProperty("half", "upper").withProperty("facing", facing).withProperty("hinge", hinge);
instance.scheduler().scheduleNextTick(() -> instance.setBlock(pos.add(0, 1, 0), upper));
}
if (path.endsWith("_bed")) {
String facing = finalOriented.getProperty("facing");
Direction d = facingToDirection(facing);
Block head = finalOriented.withProperty("part", "head");
BlockVec headPos = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
instance.scheduler().scheduleNextTick(() -> instance.setBlock(headPos, head));
}
if (path.equals("big_dripleaf")) {
Block upper = finalOriented;
Block lower = Block.BIG_DRIPLEAF_STEM.withProperty("facing", finalOriented.getProperty("facing"));
instance.scheduler().scheduleNextTick(() -> instance.setBlock(pos.add(0, 1, 0), upper.withProperty("half", "upper")));
}
if (isConnecting(path)) {
instance.scheduler().scheduleNextTick(() -> {
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
if (isConnecting(nb.key().value())) instance.setBlock(np, computeConnections(nb, nb.key().value(), instance, np));
}
Block self = instance.getBlock(pos);
if (isConnecting(self.key().value())) instance.setBlock(pos, computeConnections(self, self.key().value(), instance, pos));
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
if (nb.key().value().endsWith("_fence_gate")) {
instance.setBlock(np, updateFenceGateInWall(nb, instance, np));
}
}
});
}
if (path.endsWith("_fence_gate")) {
instance.scheduler().scheduleNextTick(() -> instance.setBlock(pos, updateFenceGateInWall(instance.getBlock(pos), instance, pos)));
}
if (path.equals("redstone_wire")) {
instance.scheduler().scheduleNextTick(() -> {
updateRedstoneWire(instance, pos);
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
if (instance.getBlock(np).key().value().equals("redstone_wire")) updateRedstoneWire(instance, np);
}
});
}
if (isRail(path)) {
instance.scheduler().scheduleNextTick(() -> updateRailShape(instance, pos));
}
if (path.equals("scaffolding")) {
instance.scheduler().scheduleNextTick(() -> updateScaffolding(instance, pos));
}
if (finalPath.equals("chest") || finalPath.equals("trapped_chest")) {
instance.scheduler().scheduleNextTick(() -> updateChestPair(instance, pos));
}
}
private static Block orient(Block block, String path, BlockFace face, Point cursor, Player player,
Instance instance, BlockVec pos) {
Map<String, String> props = block.properties();
Block result = block;
if (props.containsKey("axis")) {
result = result.withProperty("axis", switch (face) {
case TOP, BOTTOM -> "y";
case NORTH, SOUTH -> "z";
case EAST, WEST -> "x";
});
}
if (props.containsKey("type") && path.endsWith("_slab")) {
Block existing = instance.getBlock(pos);
if (existing.compare(block)) {
result = result.withProperty("type", "double");
} else {
boolean top = face == BlockFace.BOTTOM || (face != BlockFace.TOP && cursor.y() > 0.5);
result = result.withProperty("type", top ? "top" : "bottom");
}
}
if (path.endsWith("_stairs")) {
String facing = yawToFacing(player.getPosition().yaw());
boolean top = face == BlockFace.BOTTOM || (face != BlockFace.TOP && cursor.y() > 0.5);
result = result.withProperty("facing", facing).withProperty("half", top ? "top" : "bottom");
result = applyStairShape(result, instance, pos, facing, top);
}
if (path.endsWith("_trapdoor")) {
String facing;
boolean top;
if (face == BlockFace.TOP) { top = false; facing = yawToFacing(player.getPosition().yaw() + 180); }
else if (face == BlockFace.BOTTOM) { top = true; facing = yawToFacing(player.getPosition().yaw() + 180); }
else { top = cursor.y() > 0.5; facing = oppositeFacing(faceName(face)); }
result = result.withProperty("facing", facing).withProperty("half", top ? "top" : "bottom")
.withProperty("open", "false").withProperty("powered", "false");
}
if (path.endsWith("_door")) {
String facing = yawToFacing(player.getPosition().yaw());
String hinge = doorHinge(facing, cursor, instance, pos, path);
result = result.withProperty("facing", facing)
.withProperty("half", "lower")
.withProperty("hinge", hinge)
.withProperty("open", "false")
.withProperty("powered", "false");
}
if (path.endsWith("_fence_gate")) {
String facing = yawToFacing(player.getPosition().yaw());
result = result.withProperty("facing", facing).withProperty("open", "false")
.withProperty("powered", "false").withProperty("in_wall", "false");
}
if (path.endsWith("_bed")) {
String facing = yawToFacing(player.getPosition().yaw());
result = result.withProperty("facing", facing).withProperty("part", "foot").withProperty("occupied", "false");
}
if (path.endsWith("_button") || path.equals("lever") || path.equals("grindstone")) {
String attachFace; String facing;
if (face == BlockFace.TOP) { attachFace = "floor"; facing = yawToFacing(player.getPosition().yaw()); }
else if (face == BlockFace.BOTTOM) { attachFace = "ceiling"; facing = yawToFacing(player.getPosition().yaw()); }
else { attachFace = "wall"; facing = oppositeFacing(faceName(face)); }
result = result.withProperty("face", attachFace).withProperty("facing", facing);
}
if (path.equals("bell")) {
String attachment; String facing;
if (face == BlockFace.TOP) { attachment = "floor"; facing = yawToFacing(player.getPosition().yaw()); }
else if (face == BlockFace.BOTTOM) { attachment = "ceiling"; facing = yawToFacing(player.getPosition().yaw()); }
else {
attachment = "single_wall";
facing = oppositeFacing(faceName(face));
}
result = result.withProperty("attachment", attachment).withProperty("facing", facing);
}
if (path.equals("lantern") || path.equals("soul_lantern")) {
result = result.withProperty("hanging", face == BlockFace.BOTTOM ? "true" : "false");
}
if (path.equals("tripwire_hook")) {
String facing = face != BlockFace.TOP && face != BlockFace.BOTTOM
? oppositeFacing(faceName(face))
: yawToFacing(player.getPosition().yaw());
result = result.withProperty("facing", facing).withProperty("attached", "false").withProperty("powered", "false");
}
if (path.equals("hopper")) {
String facing = face == BlockFace.TOP ? "down" : (face == BlockFace.BOTTOM ? "down" : oppositeFacing(faceName(face)));
result = result.withProperty("facing", facing).withProperty("enabled", "true");
}
if (path.equals("campfire") || path.equals("soul_campfire")) {
Block below = instance.getBlock(new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ()));
String signalFire = below.key().value().equals("hay_block") ? "true" : "false";
result = result.withProperty("facing", yawToFacing(player.getPosition().yaw()))
.withProperty("lit", "true").withProperty("signal_fire", signalFire);
}
if (path.equals("pointed_dripstone")) {
String dir = face == BlockFace.BOTTOM ? "down" : "up";
result = result.withProperty("vertical_direction", dir).withProperty("thickness", "tip");
}
if (path.equals("chorus_flower")) {
result = result.withProperty("age", "0");
}
if (path.equals("end_portal_frame") || path.equals("respawn_anchor")) {
if (props.containsKey("facing")) {
result = result.withProperty("facing", yawToFacing(player.getPosition().yaw()));
}
if (props.containsKey("eye")) result = result.withProperty("eye", "false");
}
if ((path.equals("torch") || path.equals("soul_torch") || path.equals("redstone_torch") || path.equals("copper_torch"))
&& face != BlockFace.TOP && face != BlockFace.BOTTOM) {
Block wall = switch (path) {
case "torch" -> Block.WALL_TORCH;
case "soul_torch" -> Block.SOUL_WALL_TORCH;
case "redstone_torch" -> Block.REDSTONE_WALL_TORCH;
case "copper_torch" -> Block.COPPER_WALL_TORCH;
default -> null;
};
if (wall != null) result = wall.withProperty("facing", oppositeFacing(faceName(face)));
}
if (path.endsWith("_skull") || path.endsWith("_head")) {
if (face != BlockFace.TOP && face != BlockFace.BOTTOM) {
String wallName = path.endsWith("_skull") ? path.replace("_skull", "_wall_skull") : path.replace("_head", "_wall_head");
Block wall = Block.fromKey("minecraft:" + wallName);
if (wall != null) result = wall.withProperty("facing", oppositeFacing(faceName(face)));
} else if (face == BlockFace.TOP && result.properties().containsKey("rotation")) {
int rot = (int) Math.floor(((player.getPosition().yaw() + 180.0) * 16.0 / 360.0) + 0.5) & 15;
result = result.withProperty("rotation", String.valueOf(rot));
}
}
if (path.endsWith("_sign") && !path.endsWith("_wall_sign") && !path.contains("hanging")
&& face != BlockFace.TOP && face != BlockFace.BOTTOM) {
Block wallSign = Block.fromKey("minecraft:" + path.replace("_sign", "_wall_sign"));
if (wallSign != null) result = wallSign.withProperty("facing", oppositeFacing(faceName(face)));
} else if (path.endsWith("_sign") && !path.endsWith("_wall_sign") && !path.contains("hanging")
&& face == BlockFace.TOP) {
int rot = (int) Math.floor(((player.getPosition().yaw() + 180.0) * 16.0 / 360.0) + 0.5) & 15;
if (result.properties().containsKey("rotation")) result = result.withProperty("rotation", String.valueOf(rot));
}
if (path.endsWith("_hanging_sign") && !path.contains("_wall_")
&& face != BlockFace.TOP && face != BlockFace.BOTTOM) {
Block wallHang = Block.fromKey("minecraft:" + path.replace("_hanging_sign", "_wall_hanging_sign"));
if (wallHang != null) result = wallHang.withProperty("facing", oppositeFacing(faceName(face)));
} else if (path.endsWith("_hanging_sign") && !path.contains("_wall_")) {
int rot = (int) Math.floor(((player.getPosition().yaw() + 180.0) * 16.0 / 360.0) + 0.5) & 15;
if (result.properties().containsKey("rotation")) result = result.withProperty("rotation", String.valueOf(rot));
if (result.properties().containsKey("attached")) result = result.withProperty("attached", face == BlockFace.BOTTOM ? "true" : "false");
}
if (isBanner(path)) {
if (face != BlockFace.TOP && face != BlockFace.BOTTOM) {
Block wallBanner = Block.fromKey("minecraft:" + path.replace("_banner", "_wall_banner"));
if (wallBanner != null) result = wallBanner.withProperty("facing", oppositeFacing(faceName(face)));
} else if (face == BlockFace.TOP) {
int rot = (int) Math.floor(((player.getPosition().yaw() + 180.0) * 16.0 / 360.0) + 0.5) & 15;
if (result.properties().containsKey("rotation")) result = result.withProperty("rotation", String.valueOf(rot));
}
}
if (path.equals("vine")) {
Block existing = instance.getBlock(pos);
Block base = existing.compare(block) ? existing : result.withProperty("up", "false")
.withProperty("north", "false").withProperty("south", "false")
.withProperty("east", "false").withProperty("west", "false");
String side = switch (face) {
case BOTTOM -> "up";
case NORTH -> "south";
case SOUTH -> "north";
case EAST -> "west";
case WEST -> "east";
default -> null;
};
result = side != null ? base.withProperty(side, "true") : base;
}
if (path.equals("glow_lichen") || path.equals("sculk_vein") || path.equals("resin_clump")) {
Block existing = instance.getBlock(pos);
Block base = existing.compare(block) ? existing : result
.withProperty("up", "false").withProperty("down", "false")
.withProperty("north", "false").withProperty("south", "false")
.withProperty("east", "false").withProperty("west", "false");
String side = switch (face) {
case TOP -> "down";
case BOTTOM -> "up";
case NORTH -> "south";
case SOUTH -> "north";
case EAST -> "west";
case WEST -> "east";
};
result = base.withProperty(side, "true");
}
if (path.equals("red_mushroom_block") || path.equals("brown_mushroom_block") || path.equals("mushroom_stem")) {
result = result.withProperty("up", "true").withProperty("down", "true")
.withProperty("north", "true").withProperty("south", "true")
.withProperty("east", "true").withProperty("west", "true");
String hiddenFace = faceName(face);
if (result.properties().containsKey(hiddenFace)) result = result.withProperty(hiddenFace, "false");
}
if (props.containsKey("facing") && !path.endsWith("_stairs") && !path.endsWith("_door")
&& !path.endsWith("_trapdoor") && !path.endsWith("_fence_gate") && !path.endsWith("_button")
&& !path.equals("lever") && !path.equals("grindstone") && !path.equals("bell")
&& !path.equals("hopper") && !path.equals("campfire") && !path.equals("soul_campfire")
&& !path.equals("tripwire_hook")
&& !path.endsWith("wall_torch") && !path.endsWith("_bed") && !path.endsWith("_sign")
&& !path.endsWith("_hanging_sign") && !path.endsWith("_banner") && !path.endsWith("_wall_banner")
&& !path.endsWith("_skull") && !path.endsWith("_head")
&& !path.equals("end_portal_frame") && !path.equals("respawn_anchor")) {
Direction fdir = faceDirection(face);
String facing;
if (fdir != null && SIX_DIR.contains(path)) {
facing = fdir.opposite().name().toLowerCase();
} else if (path.equals("repeater") || path.equals("comparator")) {
facing = oppositeFacing(yawToFacing(player.getPosition().yaw()));
} else {
facing = yawToFacing(player.getPosition().yaw());
}
try { result = result.withProperty("facing", facing); } catch (Exception ignored) {}
}
if (isConnecting(path)) result = computeConnections(result, path, instance, pos);
if (props.containsKey("waterlogged") && Block.WATER.compare(instance.getBlock(pos))) {
try { result = result.withProperty("waterlogged", "true"); } catch (Exception ignored) {}
}
return result;
}
private static boolean isBanner(String path) {
return path.endsWith("_banner") && !path.endsWith("_wall_banner");
}
private static Block applyStairShape(Block stair, Instance instance, BlockVec pos, String facing, boolean top) {
Direction f = facingToDirection(facing);
Block front = instance.getBlock(new BlockVec(pos.blockX() + f.normalX(), pos.blockY(), pos.blockZ() + f.normalZ()));
if (front.key().value().endsWith("_stairs") && top == isTopStair(front)) {
Direction frontFacing = facingToDirection(front.getProperty("facing"));
if (frontFacing != f && frontFacing != f.opposite()) {
if (frontFacing == rotateCW(f)) return stair.withProperty("shape", "outer_right");
else return stair.withProperty("shape", "outer_left");
}
}
Block back = instance.getBlock(new BlockVec(pos.blockX() - f.normalX(), pos.blockY(), pos.blockZ() - f.normalZ()));
if (back.key().value().endsWith("_stairs") && top == isTopStair(back)) {
Direction backFacing = facingToDirection(back.getProperty("facing"));
if (backFacing != f && backFacing != f.opposite()) {
if (backFacing == rotateCW(f)) return stair.withProperty("shape", "inner_right");
else return stair.withProperty("shape", "inner_left");
}
}
return stair.withProperty("shape", "straight");
}
private static boolean isTopStair(Block b) { return "top".equals(b.getProperty("half")); }
private static Block computeConnections(Block block, String path, Instance instance, BlockVec pos) {
boolean isWall = path.endsWith("_wall") && !path.endsWith("head_wall") && !path.endsWith("_wall_sign") && !path.endsWith("_wall_banner");
if (path.endsWith("_fence_gate")) return block;
Block result = block;
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
boolean connect = canConnectTo(nb, path, d);
String propName = d.name().toLowerCase();
if (isWall) {
boolean tall = connect && isSolidAbove(instance, pos);
result = result.withProperty(propName, connect ? (tall ? "tall" : "low") : "none");
} else {
result = result.withProperty(propName, connect ? "true" : "false");
}
}
if (isWall && result.properties().containsKey("up")) {
boolean up = computeWallUp(result, instance, pos);
result = result.withProperty("up", up ? "true" : "false");
}
return result;
}
private static boolean isSolidAbove(Instance instance, BlockVec pos) {
Block above = instance.getBlock(new BlockVec(pos.blockX(), pos.blockY() + 1, pos.blockZ()));
return above.isSolid();
}
private static boolean computeWallUp(Block wall, Instance instance, BlockVec pos) {
boolean n = !"none".equals(wall.getProperty("north"));
boolean s = !"none".equals(wall.getProperty("south"));
boolean e = !"none".equals(wall.getProperty("east"));
boolean w = !"none".equals(wall.getProperty("west"));
boolean straight = (n && s && !e && !w) || (!n && !s && e && w);
if (!straight) return true;
return isSolidAbove(instance, pos);
}
private static boolean canConnectTo(Block neighbor, String selfPath, Direction toward) {
if (neighbor.isAir()) return false;
String np = neighbor.key().value();
if (np.endsWith("_fence_gate")) {
String facing = neighbor.getProperty("facing");
if (facing != null) {
Direction f = facingToDirection(facing);
return toward != f && toward != f.opposite();
}
return true;
}
if (selfPath.endsWith("_fence") && np.endsWith("_fence")) {
boolean selfNether = selfPath.contains("nether_brick");
boolean nNether = np.contains("nether_brick");
return selfNether == nNether;
}
if (selfPath.endsWith("_wall") && np.endsWith("_wall")) return true;
if ((selfPath.equals("glass_pane") || selfPath.endsWith("_stained_glass_pane") || selfPath.equals("iron_bars"))
&& (np.equals("glass_pane") || np.endsWith("_stained_glass_pane") || np.equals("iron_bars"))) return true;
return neighbor.isSolid();
}
private static boolean isConnecting(String path) {
return path.endsWith("_fence") || (path.endsWith("_wall") && !path.endsWith("head_wall")
&& !path.endsWith("_wall_sign") && !path.endsWith("_wall_banner") && !path.endsWith("_wall_torch")
&& !path.endsWith("_wall_skull") && !path.endsWith("_wall_head") && !path.endsWith("_wall_hanging_sign"))
|| path.equals("iron_bars") || path.equals("glass_pane") || path.endsWith("_stained_glass_pane");
}
private static Block updateFenceGateInWall(Block gate, Instance instance, BlockVec pos) {
if (!gate.key().value().endsWith("_fence_gate")) return gate;
String facing = gate.getProperty("facing");
if (facing == null) return gate;
Direction f = facingToDirection(facing);
Direction left = rotateCCW(f);
Direction right = rotateCW(f);
Block l = instance.getBlock(new BlockVec(pos.blockX() + left.normalX(), pos.blockY(), pos.blockZ() + left.normalZ()));
Block r = instance.getBlock(new BlockVec(pos.blockX() + right.normalX(), pos.blockY(), pos.blockZ() + right.normalZ()));
boolean inWall = l.key().value().endsWith("_wall") || r.key().value().endsWith("_wall");
return gate.withProperty("in_wall", inWall ? "true" : "false");
}
private static void updateRedstoneWire(Instance instance, BlockVec pos) {
Block wire = instance.getBlock(pos);
if (!wire.key().value().equals("redstone_wire")) return;
Block result = wire;
for (Direction d : Direction.HORIZONTAL) {
BlockVec side = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block sideBlock = instance.getBlock(side);
Block upBlock = instance.getBlock(new BlockVec(side.blockX(), side.blockY() + 1, side.blockZ()));
String state;
if (upBlock.key().value().equals("redstone_wire")) state = "up";
else if (isRedstoneConnectable(sideBlock)
|| instance.getBlock(new BlockVec(side.blockX(), side.blockY() - 1, side.blockZ())).key().value().equals("redstone_wire")) state = "side";
else state = "none";
result = result.withProperty(d.name().toLowerCase(), state);
}
instance.setBlock(pos, result);
}
private static boolean isRedstoneConnectable(Block b) {
String p = b.key().value();
return p.equals("redstone_wire") || p.equals("lever") || p.endsWith("_button")
|| p.equals("repeater") || p.equals("comparator") || p.equals("redstone_torch")
|| p.equals("redstone_wall_torch") || p.equals("observer") || p.equals("daylight_detector")
|| p.endsWith("_pressure_plate") || p.equals("target") || p.equals("redstone_block")
|| p.equals("tripwire_hook");
}
private static boolean isRail(String path) {
return path.equals("rail") || path.equals("powered_rail") || path.equals("detector_rail") || path.equals("activator_rail");
}
private static void updateRailShape(Instance instance, BlockVec pos) {
Block rail = instance.getBlock(pos);
String path = rail.key().value();
if (!isRail(path)) return;
boolean canCurve = path.equals("rail");
boolean[] adj = new boolean[4];
boolean[] ascAdj = new boolean[4];
Direction[] dirs = {Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
for (int i = 0; i < 4; i++) {
BlockVec np = new BlockVec(pos.blockX() + dirs[i].normalX(), pos.blockY(), pos.blockZ() + dirs[i].normalZ());
adj[i] = isRail(instance.getBlock(np).key().value());
BlockVec upNp = new BlockVec(np.blockX(), np.blockY() + 1, np.blockZ());
ascAdj[i] = isRail(instance.getBlock(upNp).key().value());
}
String shape;
if (canCurve) {
if (adj[0] && adj[1]) shape = "north_east";
else if (adj[1] && adj[2]) shape = "south_east";
else if (adj[2] && adj[3]) shape = "south_west";
else if (adj[3] && adj[0]) shape = "north_west";
else if (ascAdj[0]) shape = "ascending_north";
else if (ascAdj[1]) shape = "ascending_east";
else if (ascAdj[2]) shape = "ascending_south";
else if (ascAdj[3]) shape = "ascending_west";
else if (adj[1] || adj[3]) shape = "east_west";
else shape = "north_south";
} else {
if (ascAdj[0]) shape = "ascending_north";
else if (ascAdj[1]) shape = "ascending_east";
else if (ascAdj[2]) shape = "ascending_south";
else if (ascAdj[3]) shape = "ascending_west";
else if (adj[1] || adj[3]) shape = "east_west";
else shape = "north_south";
}
instance.setBlock(pos, rail.withProperty("shape", shape));
}
private static void updateScaffolding(Instance instance, BlockVec pos) {
Block s = instance.getBlock(pos);
if (!s.key().value().equals("scaffolding")) return;
Block below = instance.getBlock(new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ()));
boolean bottom;
int distance;
if (below.isSolid() && !below.key().value().equals("scaffolding")) { bottom = false; distance = 0; }
else if (below.key().value().equals("scaffolding")) { bottom = false; distance = 0; }
else {
int min = 7;
for (Direction d : Direction.HORIZONTAL) {
Block n = instance.getBlock(new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ()));
if (n.key().value().equals("scaffolding")) min = Math.min(min, parseIntOr(n.getProperty("distance"), 7) + 1);
}
bottom = true;
distance = min;
}
instance.setBlock(pos, s.withProperty("bottom", String.valueOf(bottom)).withProperty("distance", String.valueOf(distance)));
}
private static void updateChestPair(Instance instance, BlockVec pos) {
Block chest = instance.getBlock(pos);
String path = chest.key().value();
if (!(path.equals("chest") || path.equals("trapped_chest"))) return;
String facing = chest.getProperty("facing");
if (facing == null) return;
Direction f = facingToDirection(facing);
Direction left = rotateCCW(f);
Direction right = rotateCW(f);
BlockVec leftPos = new BlockVec(pos.blockX() + left.normalX(), pos.blockY(), pos.blockZ() + left.normalZ());
BlockVec rightPos = new BlockVec(pos.blockX() + right.normalX(), pos.blockY(), pos.blockZ() + right.normalZ());
Block leftBlock = instance.getBlock(leftPos);
Block rightBlock = instance.getBlock(rightPos);
if (leftBlock.key().value().equals(path) && facing.equals(leftBlock.getProperty("facing"))
&& "single".equals(leftBlock.getProperty("type"))) {
instance.setBlock(pos, chest.withProperty("type", "right"));
instance.setBlock(leftPos, leftBlock.withProperty("type", "left"));
return;
}
if (rightBlock.key().value().equals(path) && facing.equals(rightBlock.getProperty("facing"))
&& "single".equals(rightBlock.getProperty("type"))) {
instance.setBlock(pos, chest.withProperty("type", "left"));
instance.setBlock(rightPos, rightBlock.withProperty("type", "right"));
}
}
private static String yawToFacing(float yaw) {
float y = ((yaw % 360) + 360) % 360;
if (y >= 315 || y < 45) return "south";
if (y < 135) return "west";
if (y < 225) return "north";
return "east";
}
private static String faceName(BlockFace face) {
return switch (face) {
case NORTH -> "north";
case SOUTH -> "south";
case EAST -> "east";
case WEST -> "west";
case TOP -> "up";
case BOTTOM -> "down";
};
}
private static String oppositeFacing(String f) {
return switch (f) {
case "north" -> "south";
case "south" -> "north";
case "east" -> "west";
case "west" -> "east";
case "up" -> "down";
case "down" -> "up";
default -> f;
};
}
private static Direction faceDirection(BlockFace face) {
return switch (face) {
case NORTH -> Direction.NORTH;
case SOUTH -> Direction.SOUTH;
case EAST -> Direction.EAST;
case WEST -> Direction.WEST;
case TOP -> Direction.UP;
case BOTTOM -> Direction.DOWN;
};
}
private static Direction facingToDirection(String f) {
if (f == null) return Direction.NORTH;
return switch (f) {
case "north" -> Direction.NORTH;
case "south" -> Direction.SOUTH;
case "east" -> Direction.EAST;
case "west" -> Direction.WEST;
case "up" -> Direction.UP;
case "down" -> Direction.DOWN;
default -> Direction.NORTH;
};
}
private static Direction rotateCW(Direction d) {
return switch (d) {
case NORTH -> Direction.EAST;
case EAST -> Direction.SOUTH;
case SOUTH -> Direction.WEST;
case WEST -> Direction.NORTH;
default -> d;
};
}
private static Direction rotateCCW(Direction d) {
return switch (d) {
case NORTH -> Direction.WEST;
case WEST -> Direction.SOUTH;
case SOUTH -> Direction.EAST;
case EAST -> Direction.NORTH;
default -> d;
};
}
private static String doorHinge(String facing, Point cursor, Instance instance, BlockVec pos, String path) {
Direction f = facingToDirection(facing);
Direction left = rotateCCW(f);
BlockVec leftPos = new BlockVec(pos.blockX() + left.normalX(), pos.blockY(), pos.blockZ() + left.normalZ());
Block leftBlock = instance.getBlock(leftPos);
if (leftBlock.key().value().equals(path) && facing.equals(leftBlock.getProperty("facing"))) {
return "right";
}
double x = cursor.x() - 0.5;
double z = cursor.z() - 0.5;
return switch (facing) {
case "north" -> x < 0 ? "right" : "left";
case "south" -> x > 0 ? "right" : "left";
case "west" -> z > 0 ? "right" : "left";
case "east" -> z < 0 ? "right" : "left";
default -> "left";
};
}
public static void onInteract(PlayerBlockInteractEvent event) {
Block block = event.getBlock();
String path = block.key().value();
Instance instance = event.getInstance();
BlockVec pos = event.getBlockPosition();
if (path.endsWith("_door")) {
String half = block.getProperty("half");
BlockVec lowerPos = "upper".equals(half)
? new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ())
: pos;
BlockVec upperPos = new BlockVec(lowerPos.blockX(), lowerPos.blockY() + 1, lowerPos.blockZ());
Block lower = instance.getBlock(lowerPos);
Block upper = instance.getBlock(upperPos);
if (!lower.key().value().equals(path) || !upper.key().value().equals(path)) return;
boolean open = !"true".equals(lower.getProperty("open"));
instance.setBlock(lowerPos, lower.withProperty("open", String.valueOf(open)));
instance.setBlock(upperPos, upper.withProperty("open", String.valueOf(open)));
String facing = lower.getProperty("facing");
String hinge = lower.getProperty("hinge");
if (facing != null && hinge != null) {
Direction f = facingToDirection(facing);
Direction side = "left".equals(hinge) ? rotateCW(f) : rotateCCW(f);
BlockVec partnerPos = new BlockVec(lowerPos.blockX() + side.normalX(), lowerPos.blockY(), lowerPos.blockZ() + side.normalZ());
Block partner = instance.getBlock(partnerPos);
if (partner.key().value().equals(path) && facing.equals(partner.getProperty("facing"))
&& !hinge.equals(partner.getProperty("hinge"))) {
Block partnerUpper = instance.getBlock(new BlockVec(partnerPos.blockX(), partnerPos.blockY() + 1, partnerPos.blockZ()));
instance.setBlock(partnerPos, partner.withProperty("open", String.valueOf(open)));
if (partnerUpper.key().value().equals(path)) {
instance.setBlock(new BlockVec(partnerPos.blockX(), partnerPos.blockY() + 1, partnerPos.blockZ()),
partnerUpper.withProperty("open", String.valueOf(open)));
}
}
}
event.setBlockingItemUse(true);
return;
}
if (path.endsWith("_fence_gate")) {
boolean open = !"true".equals(block.getProperty("open"));
Block updated = block.withProperty("open", String.valueOf(open));
if (open) {
String facing = block.getProperty("facing");
if (facing != null) {
Direction f = facingToDirection(facing);
Direction playerDir = horizontalFacingToDirection(yawToFacing(event.getPlayer().getPosition().yaw()));
if (playerDir == f) updated = updated.withProperty("facing", oppositeFacing(facing));
}
}
instance.setBlock(pos, updated);
event.setBlockingItemUse(true);
return;
}
if (path.endsWith("_trapdoor")) {
boolean open = !"true".equals(block.getProperty("open"));
instance.setBlock(pos, block.withProperty("open", String.valueOf(open)));
event.setBlockingItemUse(true);
}
}
private static Direction horizontalFacingToDirection(String f) {
return facingToDirection(f);
}
public static void onBreak(PlayerBlockBreakEvent event) {
Instance instance = event.getInstance();
BlockVec pos = event.getBlockPosition();
Block broken = event.getBlock();
String path = broken.key().value();
if (path.endsWith("_door")) {
String half = broken.getProperty("half");
BlockVec other = "upper".equals(half)
? new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ())
: new BlockVec(pos.blockX(), pos.blockY() + 1, pos.blockZ());
instance.scheduler().scheduleNextTick(() -> {
Block ob = instance.getBlock(other);
if (ob.key().value().equals(path)) instance.setBlock(other, Block.AIR);
});
}
if (path.endsWith("_bed")) {
String part = broken.getProperty("part");
String facing = broken.getProperty("facing");
if (facing != null) {
Direction d = facingToDirection(facing);
if ("foot".equals(part)) {
BlockVec headPos = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
instance.scheduler().scheduleNextTick(() -> {
if (instance.getBlock(headPos).key().value().equals(path)) instance.setBlock(headPos, Block.AIR);
});
} else if ("head".equals(part)) {
BlockVec footPos = new BlockVec(pos.blockX() - d.normalX(), pos.blockY(), pos.blockZ() - d.normalZ());
instance.scheduler().scheduleNextTick(() -> {
if (instance.getBlock(footPos).key().value().equals(path)) instance.setBlock(footPos, Block.AIR);
});
}
}
}
if (path.equals("big_dripleaf") || path.equals("big_dripleaf_stem")) {
String half = broken.getProperty("half");
BlockVec other = "upper".equals(half)
? new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ())
: new BlockVec(pos.blockX(), pos.blockY() + 1, pos.blockZ());
instance.scheduler().scheduleNextTick(() -> {
String op = instance.getBlock(other).key().value();
if (op.equals("big_dripleaf") || op.equals("big_dripleaf_stem")) instance.setBlock(other, Block.AIR);
});
}
instance.scheduler().scheduleNextTick(() -> {
if (isConnecting(path)) {
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
if (isConnecting(nb.key().value())) instance.setBlock(np, computeConnections(nb, nb.key().value(), instance, np));
if (nb.key().value().endsWith("_fence_gate")) instance.setBlock(np, updateFenceGateInWall(nb, instance, np));
}
}
if (path.endsWith("_wall")) {
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
if (nb.key().value().endsWith("_fence_gate")) instance.setBlock(np, updateFenceGateInWall(nb, instance, np));
}
}
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
String npath = nb.key().value();
if (npath.equals("redstone_wire")) updateRedstoneWire(instance, np);
if (isRail(npath)) updateRailShape(instance, np);
}
BlockVec above = new BlockVec(pos.blockX(), pos.blockY() + 1, pos.blockZ());
if (instance.getBlock(above).key().value().equals("redstone_wire")) updateRedstoneWire(instance, above);
BlockVec belowRail = new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ());
if (isRail(instance.getBlock(belowRail).key().value())) updateRailShape(instance, belowRail);
if (path.equals("scaffolding") || isAnyScaffoldingAround(instance, pos)) {
recomputeScaffoldingColumn(instance, pos);
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
if (instance.getBlock(np).key().value().equals("scaffolding")) recomputeScaffoldingColumn(instance, np);
}
}
if (path.equals("chest") || path.equals("trapped_chest")) {
String facing = broken.getProperty("facing");
if (facing != null) {
Direction f = facingToDirection(facing);
for (Direction d : new Direction[]{rotateCW(f), rotateCCW(f)}) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
if (nb.key().value().equals(path) && facing.equals(nb.getProperty("facing"))) {
String t = nb.getProperty("type");
if ("left".equals(t) || "right".equals(t)) instance.setBlock(np, nb.withProperty("type", "single"));
}
}
}
}
if (isStairLike(path)) {
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = instance.getBlock(np);
if (nb.key().value().endsWith("_stairs")) {
String nf = nb.getProperty("facing");
boolean nt = isTopStair(nb);
instance.setBlock(np, applyStairShape(nb, instance, np, nf, nt));
}
}
}
});
}
private static boolean isStairLike(String path) { return path.endsWith("_stairs"); }
private static boolean isAnyScaffoldingAround(Instance instance, BlockVec pos) {
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
if (instance.getBlock(np).key().value().equals("scaffolding")) return true;
}
BlockVec above = new BlockVec(pos.blockX(), pos.blockY() + 1, pos.blockZ());
return instance.getBlock(above).key().value().equals("scaffolding");
}
private static void recomputeScaffoldingColumn(Instance instance, BlockVec start) {
int x = start.blockX(), z = start.blockZ();
for (int y = start.blockY(); y < start.blockY() + 32; y++) {
BlockVec p = new BlockVec(x, y, z);
if (!instance.getBlock(p).key().value().equals("scaffolding")) break;
updateScaffolding(instance, p);
}
}
private static int parseIntOr(String s, int d) {
if (s == null) return d;
try { return Integer.parseInt(s); } catch (NumberFormatException e) { return d; }
}
}
@@ -11,8 +11,10 @@ import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockInteractEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerUseItemEvent;
import net.minestom.server.event.player.PlayerUseItemOnBlockEvent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
@@ -61,6 +63,10 @@ public class BuildBattle extends StatelessGame {
this.seconds = seconds;
this.setGenerator(new BuildBattleChunkgenerator(height));
this.eventNode().addListener(PlayerUseItemEvent.class, this::onRatingItemUse);
this.eventNode().addListener(PlayerUseItemOnBlockEvent.class, e -> FluidFlowHelper.onBucketUse(e, p -> this.isInArea(p, e.getPlayer())));
this.eventNode().addListener(PlayerBlockInteractEvent.class, e -> {
if (this.isInArea(e.getBlockPosition(), e.getPlayer())) BlockPlacementHelper.onInteract(e);
});
}
@Override
@@ -76,6 +82,21 @@ public class BuildBattle extends StatelessGame {
this.getPlayers().forEach(player -> player.setGameMode(GameMode.CREATIVE));
this.scheduler().scheduleTask(this::endBuildPhase, TaskSchedule.seconds(this.seconds), TaskSchedule.stop());
this.scheduleCountdown();
}
private void scheduleCountdown() {
int[] marks = {30, 10, 5, 4, 3, 2, 1};
for (int m : marks) {
if (this.seconds <= m) continue;
int remaining = m;
this.scheduler().scheduleTask(() -> {
NamedTextColor color = remaining <= 5 ? NamedTextColor.RED : (remaining <= 10 ? NamedTextColor.GOLD : NamedTextColor.YELLOW);
this.sendMessage(Component.text("Noch ", NamedTextColor.GRAY)
.append(Component.text(remaining, color))
.append(Component.text(" Sekunde" + (remaining == 1 ? "" : "n") + " Bauzeit!", NamedTextColor.GRAY)));
}, TaskSchedule.seconds(this.seconds - m), TaskSchedule.stop());
}
}
private void endBuildPhase() {
@@ -109,12 +130,34 @@ public class BuildBattle extends StatelessGame {
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent event) {
event.setCancelled(!this.isInArea(event.getBlockPosition(), event.getPlayer()));
if (!this.isInArea(event.getBlockPosition(), event.getPlayer())) {
event.setCancelled(true);
return;
}
BlockPlacementHelper.onBreak(event);
FluidFlowHelper.onBreak(event);
}
@Override
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent event) {
event.setCancelled(!this.isInArea(event.getBlockPosition(), event.getPlayer()));
if (!this.isInArea(event.getBlockPosition(), event.getPlayer())) {
event.setCancelled(true);
return;
}
BlockPlacementHelper.apply(event);
FluidFlowHelper.onPlace(event);
}
private Pos safeSpawn(Player owner) {
Pos center = this.spawnPositions.get(owner);
int topY = center.blockY();
for (int y = 300; y >= center.blockY(); y--) {
if (!this.getBlock(center.blockX(), y, center.blockZ()).isAir()) {
topY = y;
break;
}
}
return new Pos(center.x(), topY + 3, center.z()).withView(center.yaw(), center.pitch());
}
private void rating() {
@@ -136,7 +179,7 @@ public class BuildBattle extends StatelessGame {
this.currentOwner = owner;
this.currentVotes.clear();
Pos plot = this.spawnPositions.get(owner).add(0, 1.5, 0);
Pos plot = this.safeSpawn(owner);
this.sendMessage(Component.text("Bewerte das Grundstück von " + owner.getUsername() + "!"));
this.getPlayers().forEach(p -> {
p.teleport(plot);
@@ -176,11 +219,9 @@ public class BuildBattle extends StatelessGame {
private boolean isInArea(Point block, Player player) {
Pos center = this.spawnPositions.get(player);
double dx = block.x() - center.x();
double dy = block.y() - center.y();
double dz = block.z() - center.z();
return dx >= -plotHalf && dx < plotHalf
&& dy >= 1 && dy <= 300
&& dz >= -plotHalf && dz < plotHalf;
if (center == null) return false;
Pos min = new Pos(center.x() - plotHalf, center.y() + 1, center.z() - plotHalf);
Pos max = new Pos(center.x() + plotHalf - 1, 300, center.z() + plotHalf - 1);
return eu.mhsl.minenet.minigames.util.Position.isInArea(block, min, max);
}
}
@@ -0,0 +1,268 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.buildBattle;
import net.minestom.server.coordinate.BlockVec;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerUseItemOnBlockEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.utils.Direction;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
public final class FluidFlowHelper {
private FluidFlowHelper() {}
private static final int WATER_TICK = 5;
private static final int LAVA_TICK = 30;
private static final int WATER_RANGE = 7;
private static final int LAVA_RANGE = 3;
private static final int FALLING = 8;
public static void onBucketUse(PlayerUseItemOnBlockEvent event, Predicate<Point> inArea) {
Material m = event.getItemStack().material();
Instance inst = event.getPlayer().getInstance();
if (inst == null) return;
Point hit = event.getPosition();
var face = event.getBlockFace();
BlockVec placePos = new BlockVec(
hit.blockX() + face.toDirection().normalX(),
hit.blockY() + face.toDirection().normalY(),
hit.blockZ() + face.toDirection().normalZ()
);
if (!inArea.test(placePos)) return;
if (m == Material.WATER_BUCKET) {
inst.setBlock(placePos, Block.WATER.withProperty("level", "0"));
scheduleFlow(inst, placePos, true);
} else if (m == Material.LAVA_BUCKET) {
inst.setBlock(placePos, Block.LAVA.withProperty("level", "0"));
scheduleFlow(inst, placePos, false);
} else if (m == Material.BUCKET) {
Block existing = inst.getBlock(placePos);
if (isWater(existing) || isLava(existing)) inst.setBlock(placePos, Block.AIR);
}
}
public static void onPlace(PlayerBlockPlaceEvent event) {
Block b = event.getBlock();
if (isWater(b) || isLava(b)) {
Instance inst = event.getInstance();
BlockVec pos = event.getBlockPosition();
scheduleFlow(inst, pos, isWater(b));
}
triggerNeighbors(event.getInstance(), event.getBlockPosition());
}
public static void onBreak(PlayerBlockBreakEvent event) {
triggerNeighbors(event.getInstance(), event.getBlockPosition());
}
private static void triggerNeighbors(Instance inst, BlockVec pos) {
for (Direction d : Direction.values()) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY() + d.normalY(), pos.blockZ() + d.normalZ());
Block nb = inst.getBlock(np);
if (isWater(nb) || isLava(nb)) scheduleFlow(inst, np, isWater(nb));
}
}
private static void scheduleFlow(Instance inst, BlockVec pos, boolean water) {
int delay = water ? WATER_TICK : LAVA_TICK;
inst.scheduler().buildTask(() -> step(inst, pos, water)).delay(TaskSchedule.tick(delay)).schedule();
}
private static void step(Instance inst, BlockVec pos, boolean water) {
Block self = inst.getBlock(pos);
if (water ? !isWater(self) : !isLava(self)) return;
int level = parseLevel(self);
boolean source = level == 0;
boolean falling = level >= FALLING;
int flowLevel = source ? 1 : (falling ? 1 : level + 1);
int maxRange = water ? WATER_RANGE : LAVA_RANGE;
if (!source && !falling) {
int newLevel = computeIncomingLevel(inst, pos, water, maxRange);
if (newLevel == -1) {
inst.setBlock(pos, Block.AIR);
triggerNeighbors(inst, pos);
return;
}
if (newLevel != level) {
inst.setBlock(pos, (water ? Block.WATER : Block.LAVA).withProperty("level", String.valueOf(newLevel)));
level = newLevel;
flowLevel = level + 1;
}
}
if (water && !source && countAdjacentSources(inst, pos, true) >= 2
&& hasSolidBelow(inst, pos)) {
inst.setBlock(pos, Block.WATER.withProperty("level", "0"));
scheduleFlow(inst, pos, true);
triggerNeighbors(inst, pos);
return;
}
BlockVec below = new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ());
Block belowBlock = inst.getBlock(below);
if (isReplaceable(belowBlock) || (water ? isWater(belowBlock) : isLava(belowBlock))) {
if (handleFluidCollision(inst, below, water)) {
triggerNeighbors(inst, below);
return;
}
Block target = (water ? Block.WATER : Block.LAVA).withProperty("level", String.valueOf(FALLING));
if (!belowBlock.compare(target) || !target.getProperty("level").equals(belowBlock.getProperty("level"))) {
if (!(isWater(belowBlock) && parseLevel(belowBlock) == 0)
&& !(isLava(belowBlock) && parseLevel(belowBlock) == 0)) {
inst.setBlock(below, target);
scheduleFlow(inst, below, water);
triggerNeighbors(inst, below);
}
}
if (!hasSolidBelow(inst, pos) && !source) return;
}
if (flowLevel > maxRange) return;
Set<Direction> targets = findFlowTargets(inst, pos, water, maxRange - (source ? 0 : level));
for (Direction d : Direction.HORIZONTAL) {
if (!targets.contains(d)) continue;
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = inst.getBlock(np);
if (handleFluidCollision(inst, np, water)) continue;
if (!isReplaceable(nb) && !(water ? isWater(nb) : isLava(nb))) continue;
int existingLevel = (water ? isWater(nb) : isLava(nb)) ? parseLevel(nb) : Integer.MAX_VALUE;
if (existingLevel == 0) continue;
if (flowLevel < existingLevel) {
inst.setBlock(np, (water ? Block.WATER : Block.LAVA).withProperty("level", String.valueOf(flowLevel)));
scheduleFlow(inst, np, water);
triggerNeighbors(inst, np);
}
}
}
private static int computeIncomingLevel(Instance inst, BlockVec pos, boolean water, int maxRange) {
int min = Integer.MAX_VALUE;
BlockVec above = new BlockVec(pos.blockX(), pos.blockY() + 1, pos.blockZ());
Block ab = inst.getBlock(above);
if ((water ? isWater(ab) : isLava(ab))) return FALLING;
for (Direction d : Direction.HORIZONTAL) {
BlockVec np = new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ());
Block nb = inst.getBlock(np);
if (water ? isWater(nb) : isLava(nb)) {
int nl = parseLevel(nb);
if (nl >= FALLING) nl = 0;
if (nl < min) min = nl;
}
}
if (min == Integer.MAX_VALUE) return -1;
int result = min + 1;
if (result > maxRange) return -1;
return result;
}
private static Set<Direction> findFlowTargets(Instance inst, BlockVec pos, boolean water, int remaining) {
Set<Direction> result = new HashSet<>();
if (remaining <= 0) return result;
int bestDist = Integer.MAX_VALUE;
int[] dist = new int[4];
Direction[] dirs = {Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
for (int i = 0; i < 4; i++) {
BlockVec np = new BlockVec(pos.blockX() + dirs[i].normalX(), pos.blockY(), pos.blockZ() + dirs[i].normalZ());
Block nb = inst.getBlock(np);
if (!isReplaceable(nb) && !(water ? isWater(nb) : isLava(nb))) { dist[i] = Integer.MAX_VALUE; continue; }
dist[i] = bfsDownhill(inst, np, water, remaining);
if (dist[i] < bestDist) bestDist = dist[i];
}
if (bestDist == Integer.MAX_VALUE) {
for (int i = 0; i < 4; i++) {
if (dist[i] < Integer.MAX_VALUE) result.add(dirs[i]);
}
} else {
for (int i = 0; i < 4; i++) {
if (dist[i] == bestDist) result.add(dirs[i]);
}
}
return result;
}
private static int bfsDownhill(Instance inst, BlockVec start, boolean water, int remaining) {
ArrayDeque<int[]> queue = new ArrayDeque<>();
Set<Long> visited = new HashSet<>();
queue.add(new int[]{start.blockX(), start.blockY(), start.blockZ(), 0});
visited.add(key(start.blockX(), start.blockY(), start.blockZ()));
while (!queue.isEmpty()) {
int[] c = queue.poll();
BlockVec cp = new BlockVec(c[0], c[1], c[2]);
Block below = inst.getBlock(new BlockVec(c[0], c[1] - 1, c[2]));
if (isReplaceable(below) && !(water ? isWater(below) : isLava(below))) return c[3];
if (c[3] >= remaining) continue;
for (Direction d : Direction.HORIZONTAL) {
int nx = c[0] + d.normalX(), nz = c[2] + d.normalZ();
long k = key(nx, c[1], nz);
if (visited.contains(k)) continue;
Block nb = inst.getBlock(new BlockVec(nx, c[1], nz));
if (!isReplaceable(nb) && !(water ? isWater(nb) : isLava(nb))) continue;
visited.add(k);
queue.add(new int[]{nx, c[1], nz, c[3] + 1});
}
}
return Integer.MAX_VALUE;
}
private static long key(int x, int y, int z) {
return ((long) x & 0x3FFFFFFL) | (((long) z & 0x3FFFFFFL) << 26) | (((long) y & 0xFFFL) << 52);
}
private static int countAdjacentSources(Instance inst, BlockVec pos, boolean water) {
int c = 0;
for (Direction d : Direction.HORIZONTAL) {
Block nb = inst.getBlock(new BlockVec(pos.blockX() + d.normalX(), pos.blockY(), pos.blockZ() + d.normalZ()));
if ((water ? isWater(nb) : isLava(nb)) && parseLevel(nb) == 0) c++;
}
return c;
}
private static boolean hasSolidBelow(Instance inst, BlockVec pos) {
Block below = inst.getBlock(new BlockVec(pos.blockX(), pos.blockY() - 1, pos.blockZ()));
return below.isSolid();
}
private static boolean handleFluidCollision(Instance inst, BlockVec pos, boolean incomingIsWater) {
Block target = inst.getBlock(pos);
if (incomingIsWater && isLava(target)) {
boolean lavaSource = parseLevel(target) == 0;
inst.setBlock(pos, lavaSource ? Block.OBSIDIAN : Block.COBBLESTONE);
return true;
}
if (!incomingIsWater && isWater(target)) {
inst.setBlock(pos, Block.STONE);
return true;
}
return false;
}
private static boolean isReplaceable(Block b) {
if (b.isAir()) return true;
String p = b.key().value();
return p.equals("short_grass") || p.equals("tall_grass") || p.equals("fern") || p.equals("large_fern")
|| p.equals("dead_bush") || p.equals("seagrass") || p.equals("tall_seagrass") || p.equals("vine")
|| p.equals("snow") || p.equals("fire") || p.equals("soul_fire");
}
private static boolean isWater(Block b) { return b.compare(Block.WATER); }
private static boolean isLava(Block b) { return b.compare(Block.LAVA); }
private static int parseLevel(Block b) {
String s = b.getProperty("level");
if (s == null) return 0;
try { return Integer.parseInt(s); } catch (NumberFormatException e) { return 0; }
}
}
@@ -44,4 +44,17 @@ public class Position {
public static boolean hasPositionChanged(Pos oldPos, Pos newPos) {
return !oldPos.withView(0, 0).equals(newPos.withView(0, 0));
}
public static boolean isInArea(Point point, Point pos1, Point pos2) {
double minX = Math.min(pos1.x(), pos2.x());
double maxX = Math.max(pos1.x(), pos2.x());
double minY = Math.min(pos1.y(), pos2.y());
double maxY = Math.max(pos1.y(), pos2.y());
double minZ = Math.min(pos1.z(), pos2.z());
double maxZ = Math.max(pos1.z(), pos2.z());
return point.x() >= minX && point.x() <= maxX
&& point.y() >= minY && point.y() <= maxY
&& point.z() >= minZ && point.z() <= maxZ;
}
}