From 535d06d6941ed6bda675dac845c2c26cf99a8969 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Sun, 26 Apr 2026 17:42:04 +0200 Subject: [PATCH 01/18] feature: added a rail generator command including a custom undo and redo feature: added a first version for the convex rail generator --- .../generator/commands/GeneratorCommand.java | 27 +- .../generator/components/rail/Rail.java | 28 +- .../components/rail/RailScripts.java | 594 +++++++++++++++--- .../modules/generator/menu/GeneratorMenu.java | 171 +++-- .../modules/generator/model/History.java | 148 ++++- 5 files changed, 779 insertions(+), 189 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java index b3fe253c..c14bba39 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java @@ -6,8 +6,6 @@ import net.buildtheearth.buildteamtools.modules.generator.model.History; import net.buildtheearth.buildteamtools.modules.network.model.Permissions; import net.buildtheearth.buildteamtools.utils.Utils; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -16,54 +14,44 @@ public class GeneratorCommand implements CommandExecutor { - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String cmdLabel, String @NotNull [] args) { if (!(sender instanceof Player p)) { sender.sendMessage("§cOnly players can execute this command."); return true; } - if(!p.hasPermission(Permissions.GENERATOR_USE)) { + if (!p.hasPermission(Permissions.GENERATOR_USE)) { p.sendMessage(ChatHelper.getErrorString("You don't have permission to use this command!")); return true; } - // Command Usage: /gen if (args.length == 0) { new GeneratorMenu(p, true); return true; } - - // Command Usage: /gen house ... - switch (args[0]) { + switch (args[0].toLowerCase()) { case "house": GeneratorModule.getInstance().getHouse().analyzeCommand(p, args); return true; - // Command Usage: /gen road ... case "road": GeneratorModule.getInstance().getRoad().analyzeCommand(p, args); return true; - // Command Usage: /gen rail ... case "rail": - p.sendMessage(Component.text("This generator have some serious issues and is currently disabled.", NamedTextColor.DARK_RED)); - //GeneratorModule.getInstance().getRail().analyzeCommand(p, args); + case "railway": + GeneratorModule.getInstance().getRail().analyzeCommand(p, args); return true; - // Command Usage: /gen tree ... case "tree": GeneratorModule.getInstance().getTree().analyzeCommand(p, args); return true; - // Command Usage: /gen field ... case "field": - p.sendMessage(Component.text("This generator have some serious issues and is currently disabled.", NamedTextColor.DARK_RED)); - //GeneratorModule.getInstance().getField().analyzeCommand(p, args); + p.sendMessage("§cThis generator has serious issues and is currently disabled."); return true; - // Command Usage: /gen history case "history": if (GeneratorModule.getInstance().getPlayerHistory(p).getHistoryEntries().isEmpty()) { p.sendMessage("§cYou didn't generate any structures yet. Use /gen to create one."); @@ -85,6 +73,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N case "redo": GeneratorModule.getInstance().getPlayerHistory(p).redoCommand(p); return true; + default: sendHelp(p); return true; @@ -93,7 +82,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N public static void sendHelp(CommandSender sender) { ChatHelper.sendMessageBox(sender, "Generator Command", () -> { - sender.sendMessage("§eHouse Generator:§7 /gen house help"); sender.sendMessage("§eRoad Generator:§7 /gen road help"); sender.sendMessage("§eRail Generator:§7 /gen rail help"); @@ -103,7 +91,6 @@ public static void sendHelp(CommandSender sender) { sender.sendMessage("§eGenerator History:§7 /gen history"); sender.sendMessage("§eUndo last command:§7 /gen undo"); sender.sendMessage("§eRedo last command:§7 /gen redo"); - }); } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index b0a7b913..90265618 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -1,6 +1,10 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; @@ -13,15 +17,29 @@ public Rail() { } @Override - public boolean checkForPlayer(Player p) { - return !GeneratorUtils.checkForNoWorldEditSelection(p); + public boolean checkForPlayer(Player player) { + if (GeneratorUtils.checkForNoWorldEditSelection(player)) { + player.sendMessage("§cRail Generator requires an active WorldEdit selection."); + return false; + } + + Region region = GeneratorUtils.getWorldEditSelection(player); + + if (!(region instanceof CuboidRegion) + && !(region instanceof Polygonal2DRegion) + && !(region instanceof ConvexPolyhedralRegion)) { + player.sendMessage("§cRail Generator only supports cuboid, polygonal and convex WorldEdit selections."); + return false; + } + + return true; } @Override - public void generate(Player p) { - if (!GeneratorModule.getInstance().getRail().checkForPlayer(p)) + public void generate(Player player) { + if (!GeneratorModule.getInstance().getRail().checkForPlayer(player)) return; - new RailScripts(p, this); + new RailScripts(player, this); } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index ab1156d2..4f84409e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -1,127 +1,571 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import net.buildtheearth.buildteamtools.BuildTeamTools; +import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; +import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; +import net.buildtheearth.buildteamtools.modules.generator.model.History; import net.buildtheearth.buildteamtools.modules.generator.model.Script; -import org.bukkit.block.Block; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; public class RailScripts extends Script { + private static final Material[] CENTER_MATERIALS = new Material[]{ + Material.DEAD_FIRE_CORAL_BLOCK, + Material.STONE, + Material.COBBLESTONE + }; + + private static final Material SIDE_MATERIAL = Material.ANVIL; + public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); - throw new UnsupportedOperationException("RailScripts is currently broken."); - //getPlayer().chat("/clearhistory"); - //Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), this::railScript_v_1_3); + Thread thread = new Thread(this::generateRail); + thread.start(); } - public void railScript_v_1_3() { - List commands = new ArrayList<>(); - //HashMap flags = rail.getPlayerSettings().get(p.getUniqueId()).getValues(); + private void generateRail() { + try { + List controlPoints = getControlPoints(); - int xPos = getPlayer().getLocation().getBlockX(); - int zPos = getPlayer().getLocation().getBlockZ(); + if (controlPoints.size() < 2) { + getPlayer().sendMessage("§cRail Generator needs at least two usable points in the selection."); + return; + } - int operations = 0; + List centerPath = createCenterPath(controlPoints); - int railWidth = 5; + if (centerPath.size() < 2) { + getPlayer().sendMessage("§cRail Generator could not derive a valid path from this selection."); + return; + } + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeSampleTrack(centerPath)); + } catch (Exception exception) { + getPlayer().sendMessage("§cRail Generator failed while reading the WorldEdit selection."); + exception.printStackTrace(); + } + } - // TODO START TEMP + private List getControlPoints() { + if (getRegion() instanceof CuboidRegion cuboidRegion) + return getCuboidCenterLine(cuboidRegion); - Block[][][] regionBlocks = GeneratorUtils.analyzeRegion(getPlayer(), getPlayer().getWorld()); List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - createCuboidSelection(points.get(0), points.get(1)); - createCommand("//set redstone_block"); - createBreakPoint(); - createCommand("//set gold_block"); - finish(regionBlocks, points); + if (points == null || points.size() < 2) + return new ArrayList<>(); + List blockPoints = new ArrayList<>(); - // TODO END TEMP + for (Vector point : points) + blockPoints.add(toBlockVector(point)); - /* - // Get the points of the region - List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - points = GeneratorUtils.populatePoints(points, 5); + return orderPointsAsPath(blockPoints); + } + + private List getCuboidCenterLine(CuboidRegion cuboidRegion) { + int minX = cuboidRegion.getMinimumPoint().x(); + int maxX = cuboidRegion.getMaximumPoint().x(); + int minY = cuboidRegion.getMinimumPoint().y(); + int minZ = cuboidRegion.getMinimumPoint().z(); + int maxZ = cuboidRegion.getMaximumPoint().z(); + + int centerX = (minX + maxX) / 2; + int centerZ = (minZ + maxZ) / 2; + + int widthX = Math.abs(maxX - minX); + int widthZ = Math.abs(maxZ - minZ); + + List points = new ArrayList<>(); + + if (widthX >= widthZ) { + points.add(new Vector(minX, minY, centerZ)); + points.add(new Vector(maxX, minY, centerZ)); + } else { + points.add(new Vector(centerX, minY, minZ)); + points.add(new Vector(centerX, minY, maxZ)); + } + + return points; + } + + private List orderPointsAsPath(List points) { + List remaining = new ArrayList<>(); + List ordered = new ArrayList<>(); + + for (Vector point : points) { + if (!containsBlock(remaining, point)) + remaining.add(point); + } + + if (remaining.isEmpty()) + return ordered; - // ----------- PREPARATION 01 ---------- - // Replace all unnecessary blocks with air + Vector current = findStartPoint(remaining); + ordered.add(current); + remaining.remove(current); - List polyRegionLine = new ArrayList<>(points); - polyRegionLine = GeneratorUtils.extendPolyLine(polyRegionLine); - List polyRegionPoints = GeneratorUtils.shiftPoints(polyRegionLine, railWidth + 2, true); + while (!remaining.isEmpty()) { + Vector next = findNearestPoint(current, remaining); + ordered.add(next); + remaining.remove(next); + current = next; + } + + return ordered; + } + + private Vector findStartPoint(List points) { + Vector best = points.get(0); + + for (Vector point : points) { + if (point.getBlockX() < best.getBlockX()) { + best = point; + continue; + } + + if (point.getBlockX() == best.getBlockX() && point.getBlockZ() < best.getBlockZ()) + best = point; + } + + return best; + } - // Create a region from the points - GeneratorUtils.createPolySelection(getPlayer(), polyRegionPoints, null); + private Vector findNearestPoint(Vector from, List points) { + Vector best = points.get(0); + double bestDistance = distanceSquared2D(from, best); - getPlayer().chat("//expand 30 up"); - getPlayer().chat("//expand 10 down"); + for (Vector point : points) { + double distance = distanceSquared2D(from, point); - // Remove non-solid blocks - getPlayer().chat("//gmask !#solid"); - getPlayer().chat("//replace 0"); - operations++; + if (distance < bestDistance) { + best = point; + bestDistance = distance; + } + } - // Remove all trees and pumpkins - getPlayer().chat("//gmask"); - getPlayer().chat("//replace leaves,log,pumpkin 0"); - operations++; + return best; + } - getPlayer().chat("//gmask"); + private double distanceSquared2D(Vector a, Vector b) { + double dx = a.getBlockX() - b.getBlockX(); + double dz = a.getBlockZ() - b.getBlockZ(); + return dx * dx + dz * dz; + } - Block[][][] regionBlocks = GeneratorUtils.analyzeRegion(getPlayer(), getPlayer().getWorld()); - GeneratorUtils.adjustHeight(points, regionBlocks); + private boolean containsBlock(List points, Vector target) { + for (Vector point : points) { + if (sameBlock(point, target)) + return true; + } + return false; + } - // ----------- RAILWAY ---------- + private List createCenterPath(List controlPoints) { + List centerPath = new ArrayList<>(); - // Draw the railway curve + for (int i = 0; i < controlPoints.size() - 1; i++) { + Vector from = controlPoints.get(i); + Vector to = controlPoints.get(i + 1); - GeneratorUtils.createConvexSelection(commands, points); - commands.add("//gmask !solid"); - commands.add("//curve 42"); - operations++; - commands.add("//gmask"); + appendEightDirectionalLine(centerPath, from, to); + } + return removeOnlyConsecutiveDuplicates(repairGaps(centerPath)); + } - // Create the railway - GeneratorUtils.createPolySelection(commands, polyRegionPoints); + private void appendEightDirectionalLine(List path, Vector from, Vector to) { + int startX = from.getBlockX(); + int startY = from.getBlockY(); + int startZ = from.getBlockZ(); - commands.add("//replace \"0 !>42 =queryRel(0,-1,-1,42,-1)||queryRel(0,-1,1,42,-1)\" 145:1"); - operations++; - commands.add("//replace \"0 !>42 =queryRel(-1,-1,0,42,-1)||queryRel(1,-1,0,42,-1)\" 145:0"); - operations++; + int endX = to.getBlockX(); + int endY = to.getBlockY(); + int endZ = to.getBlockZ(); - commands.add("//gmask =(sqrt((x-(" + xPos + "))^2+(z-(" + zPos + "))^2)%3)-2"); - commands.add("//replace \"0 =queryRel(0,0,1,145,-1)||queryRel(0,0,-1,145,-1)||queryRel(1,0,0,145,-1)||queryRel(-1,0,0,145,-1)\" 44:0"); - operations++; - commands.add("//replace \"!145 !0 <145\" 43:0"); - operations++; - commands.add("//gmask"); - commands.add("//replace 42 2"); - operations++; + int dx = endX - startX; + int dy = endY - startY; + int dz = endZ - startZ; - commands.add("//gmask"); + int steps = Math.max(Math.abs(dx), Math.abs(dz)); - // Depending on the selection type, the selection needs to be restored correctly - if(getRegion() instanceof Polygonal2DRegion || getRegion() instanceof ConvexPolyhedralRegion) - GeneratorUtils.createConvexSelection(commands, points); - else if(getRegion() instanceof CuboidRegion){ - CuboidRegion cuboidRegion = (CuboidRegion) getRegion(); - Vector pos1 = new Vector(cuboidRegion.getPos1().getX(), cuboidRegion.getPos1().getY(), cuboidRegion.getPos1().getZ()); - Vector pos2 = new Vector(cuboidRegion.getPos2().getX(), cuboidRegion.getPos2().getY(), cuboidRegion.getPos2().getZ()); - GeneratorUtils.createCuboidSelection(commands, pos1, pos2); + if (steps == 0) { + addPointIfNew(path, new Vector(startX, startY, startZ)); + return; } - GeneratorModule.getInstance().getGeneratorCommands().add(new Command(getPlayer(), getGeneratorComponent(), commands, operations, regionBlocks)); - GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, operations));*/ + for (int step = 0; step <= steps; step++) { + double t = step / (double) steps; + + int x = (int) Math.round(startX + dx * t); + int y = (int) Math.round(startY + dy * t); + int z = (int) Math.round(startZ + dz * t); + + addPointIfNew(path, new Vector(x, y, z)); + } + } + + private List repairGaps(List path) { + if (path.size() < 2) + return path; + + List repaired = new ArrayList<>(); + repaired.add(path.get(0)); + + for (int i = 1; i < path.size(); i++) { + Vector previous = repaired.get(repaired.size() - 1); + Vector current = path.get(i); + + if (getChebyshevDistance(previous, current) <= 1) { + addPointIfNew(repaired, current); + continue; + } + + appendEightDirectionalLine(repaired, previous, current); + } + + return repaired; + } + + private int getChebyshevDistance(Vector a, Vector b) { + int dx = Math.abs(a.getBlockX() - b.getBlockX()); + int dz = Math.abs(a.getBlockZ() - b.getBlockZ()); + + return Math.max(dx, dz); + } + + private void addPointIfNew(List path, Vector point) { + if (path.isEmpty()) { + path.add(point); + return; + } + + Vector last = path.get(path.size() - 1); + + if (!sameBlock(last, point)) + path.add(point); + } + + private List removeOnlyConsecutiveDuplicates(List path) { + List result = new ArrayList<>(); + + for (Vector point : path) + addPointIfNew(result, point); + + return result; + } + + private Vector toBlockVector(Vector vector) { + return new Vector( + Math.round(vector.getX()), + Math.round(vector.getY()), + Math.round(vector.getZ()) + ); + } + + private boolean sameBlock(Vector a, Vector b) { + return a.getBlockX() == b.getBlockX() + && a.getBlockY() == b.getBlockY() + && a.getBlockZ() == b.getBlockZ(); + } + + private void placeSampleTrack(List centerPath) { + Map blockMap = new LinkedHashMap<>(); + LinkedHashSet centerPositions = new LinkedHashSet<>(); + Map sideBlocks = new LinkedHashMap<>(); + + for (Vector center : centerPath) + centerPositions.add(toBlockVector3(center)); + + createSideBlocksFromCenterEdges(centerPath, centerPositions, sideBlocks); + placeSideBlocks(blockMap, sideBlocks); + placeCenterBlocks(blockMap, centerPositions); + + boolean placedWithWorldEdit = tryPlaceWithWorldEdit(blockMap); + + if (!placedWithWorldEdit) { + getPlayer().sendMessage("§eWorldEdit history is unavailable. Falling back to Bukkit placement."); + getPlayer().sendMessage("§eUse §6/gen undo§e instead of §6//undo§e for this generation."); + placeWithBukkitFallback(blockMap); + return; + } + + GeneratorModule.getInstance() + .getPlayerHistory(getPlayer()) + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, 1)); + + getGeneratorComponent().sendSuccessMessage(getPlayer()); + } + + private void createSideBlocksFromCenterEdges( + List centerPath, + LinkedHashSet centerPositions, + Map sideBlocks + ) { + for (int i = 0; i < centerPath.size() - 1; i++) { + Vector from = centerPath.get(i); + Vector to = centerPath.get(i + 1); + Vector direction = getDirection(from, to); + + if (direction == null) + continue; + + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + if (dx != 0 && dz != 0) { + placeDiagonalEdgeSideBlocks(sideBlocks, centerPositions, from, dx, dz); + } else { + placeStraightEdgeSideBlocks(sideBlocks, centerPositions, from, to, direction); + } + } + } + + private void placeStraightEdgeSideBlocks( + Map sideBlocks, + LinkedHashSet centerPositions, + Vector from, + Vector to, + Vector direction + ) { + Vector leftOffset = getLeftOffset(direction); + Vector rightOffset = getRightOffset(direction); + + addSideBlock(sideBlocks, centerPositions, offset(from, leftOffset), direction); + addSideBlock(sideBlocks, centerPositions, offset(to, leftOffset), direction); + addSideBlock(sideBlocks, centerPositions, offset(from, rightOffset), direction); + addSideBlock(sideBlocks, centerPositions, offset(to, rightOffset), direction); + } + + private void placeDiagonalEdgeSideBlocks( + Map sideBlocks, + LinkedHashSet centerPositions, + Vector from, + int dx, + int dz + ) { + Vector direction = new Vector(dx, 0, dz); + + BlockVector3 horizontalSide = BlockVector3.at( + from.getBlockX() + dx, + from.getBlockY(), + from.getBlockZ() + ); + + BlockVector3 verticalSide = BlockVector3.at( + from.getBlockX(), + from.getBlockY(), + from.getBlockZ() + dz + ); + + addSideBlock(sideBlocks, centerPositions, horizontalSide, direction); + addSideBlock(sideBlocks, centerPositions, verticalSide, direction); + } + + private BlockVector3 offset(Vector center, Vector offset) { + return BlockVector3.at( + center.getBlockX() + offset.getBlockX(), + center.getBlockY(), + center.getBlockZ() + offset.getBlockZ() + ); + } + + private void addSideBlock( + Map sideBlocks, + LinkedHashSet centerPositions, + BlockVector3 position, + Vector direction + ) { + if (centerPositions.contains(position)) + return; + + SideBlock sideBlock = sideBlocks.computeIfAbsent(position, SideBlock::new); + sideBlock.addDirection(direction); + } + + private Vector getLeftOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + return new Vector(-dz, 0, dx); + } + + private Vector getRightOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + return new Vector(dz, 0, -dx); + } + + private void placeSideBlocks(Map blockMap, Map sideBlocks) { + for (SideBlock sideBlock : sideBlocks.values()) { + Vector direction = getBestAnvilDirection(sideBlock, sideBlocks); + blockMap.put(sideBlock.position, getAnvilBlockData(direction)); + } + } + + private Vector getBestAnvilDirection(SideBlock sideBlock, Map sideBlocks) { + boolean eastWest = sideBlocks.containsKey(sideBlock.position.add(1, 0, 0)) + || sideBlocks.containsKey(sideBlock.position.add(-1, 0, 0)); + + boolean northSouth = sideBlocks.containsKey(sideBlock.position.add(0, 0, 1)) + || sideBlocks.containsKey(sideBlock.position.add(0, 0, -1)); + + if (eastWest && !northSouth) + return new Vector(1, 0, 0); + + if (northSouth && !eastWest) + return new Vector(0, 0, 1); + + return sideBlock.getAverageDirection(); + } + + private Vector getDirection(Vector from, Vector to) { + int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); + int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); + + if (dx == 0 && dz == 0) + return null; + + return new Vector(dx, 0, dz); + } + + private void placeCenterBlocks(Map blockMap, LinkedHashSet centerPositions) { + for (BlockVector3 center : centerPositions) + blockMap.put(center, getCenterBlockData(center)); + } + + private boolean tryPlaceWithWorldEdit(Map blockMap) { + try (EditSession editSession = WorldEdit.getInstance() + .newEditSessionBuilder() + .world(getWeWorld()) + .actor(getActor()) + .build()) { + + for (Map.Entry entry : blockMap.entrySet()) + editSession.setBlock(entry.getKey(), BukkitAdapter.adapt(entry.getValue())); + + editSession.flushQueue(); + getLocalSession().remember(editSession); + return true; + } catch (Throwable throwable) { + throwable.printStackTrace(); + return false; + } + } + + private void placeWithBukkitFallback(Map blockMap) { + List changes = new ArrayList<>(); + + for (Map.Entry entry : blockMap.entrySet()) { + BlockVector3 position = entry.getKey(); + BlockData newData = entry.getValue(); + + org.bukkit.block.Block block = getPlayer().getWorld().getBlockAt(position.x(), position.y(), position.z()); + + changes.add(new History.BlockChange( + getPlayer().getWorld().getName(), + position.x(), + position.y(), + position.z(), + block.getBlockData().getAsString(), + newData.getAsString() + )); + + block.setBlockData(newData, false); + } + + GeneratorModule.getInstance() + .getPlayerHistory(getPlayer()) + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); + + getGeneratorComponent().sendSuccessMessage(getPlayer()); + } + + private BlockData getCenterBlockData(BlockVector3 position) { + int index = Math.floorMod(position.x() * 31 + position.z() * 17, CENTER_MATERIALS.length); + return CENTER_MATERIALS[index].createBlockData(); + } + + private BlockData getAnvilBlockData(Vector direction) { + BlockData data = SIDE_MATERIAL.createBlockData(); + + if (data instanceof Directional directional) + directional.setFacing(toBlockFace(direction)); + + return data; + } + + private BlockFace toBlockFace(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + if (Math.abs(dx) >= Math.abs(dz)) { + if (dx > 0) + return BlockFace.EAST; + + if (dx < 0) + return BlockFace.WEST; + } + + if (dz > 0) + return BlockFace.SOUTH; + + if (dz < 0) + return BlockFace.NORTH; + + return BlockFace.EAST; + } + + private BlockVector3 toBlockVector3(Vector vector) { + return BlockVector3.at(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); + } + + private static class SideBlock { + + private final BlockVector3 position; + private int directionX; + private int directionZ; + + private SideBlock(BlockVector3 position) { + this.position = position; + } + + private void addDirection(Vector direction) { + directionX += direction.getBlockX(); + directionZ += direction.getBlockZ(); + } + + private Vector getAverageDirection() { + int dx = Integer.compare(directionX, 0); + int dz = Integer.compare(directionZ, 0); + + if (dx == 0 && dz == 0) + return new Vector(1, 0, 0); + + return new Vector(dx, 0, dz); + } } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java index ef738425..0c07f34f 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java @@ -8,6 +8,8 @@ import net.buildtheearth.buildteamtools.modules.generator.components.house.HouseSettings; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; import net.buildtheearth.buildteamtools.modules.generator.components.house.menu.WallColorMenu; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.Rail; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailSettings; import net.buildtheearth.buildteamtools.modules.generator.components.road.Road; import net.buildtheearth.buildteamtools.modules.generator.components.road.RoadSettings; import net.buildtheearth.buildteamtools.modules.generator.components.road.menu.RoadColorMenu; @@ -45,81 +47,126 @@ public class GeneratorMenu extends AbstractMenu { public static final int FIELD_ITEM_SLOT = 17; - public GeneratorMenu(Player player, boolean autoLoad) { super(3, GENERATOR_INV_NAME, player, autoLoad); } @Override protected void setPreviewItems() { - // HOUSE ITEM - ArrayList houseLore = ListUtil.createList("", "§eDescription:", "Generate basic building shells", "with multiple floors, windows and roofs", "", "§eFeatures:", "- " + RoofType.values().length + " Roof Types", "- Custom Wall, Base and Roof Color", "- Custom Floor and Window Sizes", "", "§8Left-click to generate", "§8Right-click for Tutorial"); + ArrayList houseLore = ListUtil.createList( + "", + "§eDescription:", + "Generate basic building shells", + "with multiple floors, windows and roofs", + "", + "§eFeatures:", + "- " + RoofType.values().length + " Roof Types", + "- Custom Wall, Base and Roof Color", + "- Custom Floor and Window Sizes", + "", + "§8Left-click to generate", + "§8Right-click for Tutorial" + ); ItemStack houseItem = Item.create(XMaterial.BIRCH_DOOR.get(), "§cGenerate House", houseLore); - - // Set navigator item getMenu().getSlot(HOUSE_ITEM_SLOT).setItem(houseItem); + ArrayList roadLore = ListUtil.createList( + "", + "§eDescription:", + "Generate roads and highways", + "with multiple lanes and sidewalks", + "", + "§eFeatures:", + "- Custom Road Width and Color", + "- Custom Sidewalk Width and Color", + "- Custom Lane Count", + "", + "§8Left-click to generate", + "§8Right-click for Tutorial" + ); + + ItemStack roadItem = new Item(XMaterial.SMOOTH_STONE_SLAB.parseItem()) + .setDisplayName("§bGenerate Road") + .setLore(roadLore) + .build(); - // ROAD ITEM - ArrayList roadLore = ListUtil.createList("", "§eDescription:", "Generate roads and highways", "with multiple lanes and sidewalks", "", "§eFeatures:", "- Custom Road Width and Color", "- Custom Sidewalk Width and Color", "- Custom Lane Count", "", "§8Left-click to generate", "§8Right-click for Tutorial"); - - - ItemStack roadItem = new Item(XMaterial.SMOOTH_STONE_SLAB.parseItem()).setDisplayName("§bGenerate Road").setLore(roadLore).build(); - - // Set navigator item getMenu().getSlot(ROAD_ITEM_SLOT).setItem(roadItem); - - // RAILWAY ITEM - ArrayList railwayLore = ListUtil.createList("", "§eDescription:", "Generate railways with multiple tracks", "and many different designs", "", "§eFeatures:", "- Custom Railway Width and Color (TODO)", "- Custom Track Count (TODO)", "", "§8Left-click to generate", "§8Right-click for Tutorial"); - - railwayLore = ListUtil.createList("", "§cThis §eGenerator §cis currently broken", "§cRailway Generator is disabled", "", "§8If you want to help fixing ask on Dev Hub!"); - - ItemStack railwayItem = Item.create(XMaterial.RAIL.get(), "§9Generate Railway §c(DISABLED)", railwayLore); - - // Set navigator item + ArrayList railwayLore = ListUtil.createList( + "", + "§eDescription:", + "Generate a predefined railway", + "from your active WorldEdit selection", + "", + "§eSupported selections:", + "- Cuboid", + "- Polygonal", + "- Convex", + "", + "§eFeatures:", + "- Straight rail sections", + "- Curves and corners", + "- Automatic rail orientation", + "", + "§8Left-click to generate", + "§8Right-click for Tutorial" + ); + + ItemStack railwayItem = Item.create(XMaterial.RAIL.get(), "§9Generate Railway", railwayLore); getMenu().getSlot(RAILWAY_ITEM_SLOT).setItem(railwayItem); - if (!CommonModule.getInstance().getDependencyComponent().isSchematicBrushEnabled()) { - // TREE ITEM - ArrayList treeLore = ListUtil.createList("", "§cPlugin §eSchematicBrush §cis not installed", "§cTree Generator is disabled", "", "§8Leftclick for Installation Instructions"); + ArrayList treeLore = ListUtil.createList( + "", + "§cPlugin §eSchematicBrush §cis not installed", + "§cTree Generator is disabled", + "", + "§8Leftclick for Installation Instructions" + ); ItemStack treeItem = Item.create(XMaterial.OAK_SAPLING.get(), "§aGenerate Tree & Forest §c(DISABLED)", treeLore); - - // Set navigator item getMenu().getSlot(TREE_ITEM_SLOT).setItem(treeItem); } else if (!GeneratorCollections.hasUpdatedGeneratorCollections(getMenuPlayer())) { - // TREE ITEM - ArrayList treeLore = ListUtil.createList("", "§cThe §eTree Pack " + Tree.TREE_PACK_VERSION + " §cis not installed", "§cTree Generator is disabled", "", "§8Leftclick for Installation Instructions"); + ArrayList treeLore = ListUtil.createList( + "", + "§cThe §eTree Pack " + Tree.TREE_PACK_VERSION + " §cis not installed", + "§cTree Generator is disabled", + "", + "§8Leftclick for Installation Instructions" + ); ItemStack treeItem = Item.create(XMaterial.OAK_SAPLING.get(), "§aGenerate Tree & Forest §c(DISABLED)", treeLore); - - // Set navigator item getMenu().getSlot(TREE_ITEM_SLOT).setItem(treeItem); } else { - // TREE ITEM - ArrayList treeLore = ListUtil.createList("", "§eDescription:", "Generate trees from a set of", "hundreds of different types", "", "§eFeatures:", "- Custom Tree Type", "", "§8Left-click to generate", "§8Right-click for Tutorial"); + ArrayList treeLore = ListUtil.createList( + "", + "§eDescription:", + "Generate trees from a set of", + "hundreds of different types", + "", + "§eFeatures:", + "- Custom Tree Type", + "", + "§8Left-click to generate", + "§8Right-click for Tutorial" + ); ItemStack treeItem = Item.create(XMaterial.OAK_SAPLING.get(), "§aGenerate Tree & Forest", treeLore); - - // Set navigator item getMenu().getSlot(TREE_ITEM_SLOT).setItem(treeItem); } - - // FIELD ITEM - ArrayList fieldLore = ListUtil.createList("", "§eDescription:", "Generate fields with different", "crops and plants", "", "§eFeatures:", "- Custom Crop Type", "- Custom Crop Size", "", "§8Left-click to generate", "§8Right-click for Tutorial"); - - fieldLore = ListUtil.createList("", "§cThis §eGenerator §cis currently broken", "§cField Generator is disabled", "", "§8If you want to help fixing ask on Dev Hub!"); + ArrayList fieldLore = ListUtil.createList( + "", + "§cThis §eGenerator §cis currently broken", + "§cField Generator is disabled", + "", + "§8If you want to help fixing ask on Dev Hub!" + ); ItemStack fieldItem = Item.create(XMaterial.WHEAT.get(), "§6Generate Field §c(DISABLED)", fieldLore); - - // Set navigator item getMenu().getSlot(FIELD_ITEM_SLOT).setItem(fieldItem); - super.setPreviewItems(); } @@ -130,7 +177,6 @@ protected void setMenuItemsAsync() { @Override protected void setItemClickEventsAsync() { - // Set click event for house item getMenu().getSlot(HOUSE_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { sendMoreInformation(clickPlayer, GeneratorType.HOUSE); @@ -140,14 +186,14 @@ protected void setItemClickEventsAsync() { House house = GeneratorModule.getInstance().getHouse(); house.getPlayerSettings().put(clickPlayer.getUniqueId(), new HouseSettings(clickPlayer)); - if (!house.checkForPlayer(clickPlayer)) return; + if (!house.checkForPlayer(clickPlayer)) + return; clickPlayer.closeInventory(); clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); new WallColorMenu(clickPlayer, true); })); - // Set click event for road item getMenu().getSlot(ROAD_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { sendMoreInformation(clickPlayer, GeneratorType.ROAD); @@ -157,34 +203,31 @@ protected void setItemClickEventsAsync() { Road road = GeneratorModule.getInstance().getRoad(); road.getPlayerSettings().put(clickPlayer.getUniqueId(), new RoadSettings(clickPlayer)); - if (!road.checkForPlayer(clickPlayer)) return; + if (!road.checkForPlayer(clickPlayer)) + return; clickPlayer.closeInventory(); clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); new RoadColorMenu(clickPlayer, true); })); - // Set click event for railway item getMenu().getSlot(RAILWAY_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { sendMoreInformation(clickPlayer, GeneratorType.RAILWAY); return; } - sendMoreInformation(clickPlayer, GeneratorType.RAILWAY); - /*Rail rail = GeneratorModule.getInstance().getRail(); + Rail rail = GeneratorModule.getInstance().getRail(); rail.getPlayerSettings().put(clickPlayer.getUniqueId(), new RailSettings(clickPlayer)); - if(!rail.checkForPlayer(clickPlayer)) + if (!rail.checkForPlayer(clickPlayer)) return; clickPlayer.closeInventory(); clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); - - GeneratorModule.getInstance().getRail().generate(clickPlayer);*/ + rail.generate(clickPlayer); })); - // Set click event for tree item getMenu().getSlot(TREE_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { sendMoreInformation(clickPlayer, GeneratorType.TREE); @@ -194,30 +237,21 @@ protected void setItemClickEventsAsync() { Tree tree = GeneratorModule.getInstance().getTree(); tree.getPlayerSettings().put(clickPlayer.getUniqueId(), new TreeSettings(clickPlayer)); - if (!tree.checkForPlayer(clickPlayer)) return; + if (!tree.checkForPlayer(clickPlayer)) + return; clickPlayer.closeInventory(); clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); new TreeTypeMenu(clickPlayer, true); })); - // Set click event for field item getMenu().getSlot(FIELD_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { sendMoreInformation(clickPlayer, GeneratorType.FIELD); return; } - sendMoreInformation(clickPlayer, GeneratorType.FIELD); - - /*Field field = GeneratorModule.getInstance().getField(); - field.getPlayerSettings().put(clickPlayer.getUniqueId(), new FieldSettings(clickPlayer)); - - if(!field.checkForPlayer(clickPlayer)) - return; - clickPlayer.closeInventory(); - clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); - new CropTypeMenu(clickPlayer, true);*/ + sendMoreInformation(clickPlayer, GeneratorType.FIELD); })); } @@ -227,6 +261,11 @@ private void sendMoreInformation(@NonNull Player clickPlayer, @NonNull Generator @Override protected Mask getMask() { - return BinaryMask.builder(getMenu()).item(MenuItems.ITEM_BACKGROUND).pattern("111111111").pattern("010101010").pattern("111111111").build(); + return BinaryMask.builder(getMenu()) + .item(MenuItems.ITEM_BACKGROUND) + .pattern("111111111") + .pattern("010101010") + .pattern("111111111") + .build(); } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java index 02e8473f..207dfab7 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java @@ -5,6 +5,7 @@ import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.extension.platform.Actor; import lombok.Getter; +import lombok.Setter; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -12,9 +13,13 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import java.util.ArrayList; +import java.util.List; public class History { @@ -27,36 +32,43 @@ public class History { @Getter private final ArrayList undoHistoryEntries; - public History(Player p){ + public History(Player p) { this.p = p; this.historyEntries = new ArrayList<>(); this.undoHistoryEntries = new ArrayList<>(); } - public void addHistoryEntry(HistoryEntry entry){ + public void addHistoryEntry(HistoryEntry entry) { historyEntries.add(entry); + undoHistoryEntries.clear(); } - public void undoCommand(Player p){ - if(getHistoryEntries().isEmpty()){ + public void undoCommand(Player p) { + if (getHistoryEntries().isEmpty()) { p.sendMessage("§cYou didn't generate any structures yet. Use /gen to create one. You can only undo the last structure."); return; } - LocalSession session = getHistoryEntries().get(0).getScript().getLocalSession(); - Actor actor = getHistoryEntries().get(0).getScript().getActor(); - int worldEditCommandCount = getHistoryEntries().get(0).getWorldEditCommandCount(); + HistoryEntry entry = getHistoryEntries().get(getHistoryEntries().size() - 1); - GeneratorUtils.undo(session, p, actor, worldEditCommandCount); + if (entry.hasBlockChanges()) { + entry.applyUndo(); + } else { + LocalSession session = entry.getScript().getLocalSession(); + Actor actor = entry.getScript().getActor(); + int worldEditCommandCount = entry.getWorldEditCommandCount(); - getUndoHistoryEntries().add(getHistoryEntries().get(0)); - getHistoryEntries().clear(); + GeneratorUtils.undo(session, p, actor, worldEditCommandCount); + } + + getUndoHistoryEntries().add(entry); + getHistoryEntries().remove(entry); p.playSound(p.getLocation(), Sound.ENTITY_ZOMBIE_DESTROY_EGG, 1.0F, 1.0F); Bukkit.getScheduler().scheduleSyncDelayedTask(BuildTeamTools.getInstance(), () -> { ChatHelper.sendSuccessfulMessage(p, "Successfully %s the last structure.", "undid"); - p.sendMessage(ChatHelper.getStandardComponent(true,"Use %s to undo it.", "/gen redo") + p.sendMessage(ChatHelper.getStandardComponent(true, "Use %s to redo it.", "/gen redo") .clickEvent(ClickEvent.runCommand("/gen redo")) .hoverEvent(HoverEvent.showText(Component.text("Click to redo the last structure.", NamedTextColor.GRAY))) ); @@ -64,27 +76,32 @@ public void undoCommand(Player p){ }, 20L); } - public void redoCommand(Player p){ - if(getUndoHistoryEntries().isEmpty()){ + public void redoCommand(Player p) { + if (getUndoHistoryEntries().isEmpty()) { p.sendMessage("§cYou didn't undo any structures yet. Use /gen undo to undo one. You can only redo the last structure."); return; } - LocalSession session = getUndoHistoryEntries().get(0).getScript().getLocalSession(); - Actor actor = getUndoHistoryEntries().get(0).getScript().getActor(); - int worldEditCommandCount = getUndoHistoryEntries().get(0).getWorldEditCommandCount(); + HistoryEntry entry = getUndoHistoryEntries().get(getUndoHistoryEntries().size() - 1); + + if (entry.hasBlockChanges()) { + entry.applyRedo(); + } else { + LocalSession session = entry.getScript().getLocalSession(); + Actor actor = entry.getScript().getActor(); + int worldEditCommandCount = entry.getWorldEditCommandCount(); - GeneratorUtils.redo(session, p, actor, worldEditCommandCount); + GeneratorUtils.redo(session, p, actor, worldEditCommandCount); + } - getHistoryEntries().add(getUndoHistoryEntries().get(0)); - getUndoHistoryEntries().clear(); + getHistoryEntries().add(entry); + getUndoHistoryEntries().remove(entry); p.playSound(p.getLocation(), Sound.ENTITY_ZOMBIE_DESTROY_EGG, 1.0F, 1.0F); - Bukkit.getScheduler().scheduleSyncDelayedTask(BuildTeamTools.getInstance(), () -> { - ChatHelper.sendSuccessfulMessage(p,"Successfully %s the last structure.", "redid"); - p.sendMessage(ChatHelper.getStandardComponent(true,"Use %s to undo it.", "/gen undo") + ChatHelper.sendSuccessfulMessage(p, "Successfully %s the last structure.", "redid"); + p.sendMessage(ChatHelper.getStandardComponent(true, "Use %s to undo it.", "/gen undo") .clickEvent(ClickEvent.runCommand("/gen undo")) .hoverEvent(HoverEvent.showText(Component.text("Click to undo the last structure.", NamedTextColor.GRAY))) ); @@ -96,20 +113,105 @@ public static class HistoryEntry { @Getter private final GeneratorType generatorType; + @Getter private final long timeCreated; + @Getter private final Script script; + @Getter private final int worldEditCommandCount; + @Getter + private final List blockChanges; + public HistoryEntry(GeneratorType generatorType, Script script) { this.generatorType = generatorType; this.timeCreated = System.currentTimeMillis(); this.worldEditCommandCount = script.getChanges(); this.script = script; + this.blockChanges = new ArrayList<>(); + } + + public HistoryEntry(GeneratorType generatorType, Script script, int worldEditCommandCount) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = worldEditCommandCount; + this.script = script; + this.blockChanges = new ArrayList<>(); + } + + public HistoryEntry(GeneratorType generatorType, Script script, List blockChanges) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = 0; + this.script = script; + this.blockChanges = blockChanges == null ? new ArrayList<>() : blockChanges; + } + + public boolean hasBlockChanges() { + return blockChanges != null && !blockChanges.isEmpty(); + } + + public void applyUndo() { + for (int i = blockChanges.size() - 1; i >= 0; i--) + blockChanges.get(i).applyOld(); + } + + public void applyRedo() { + for (BlockChange blockChange : blockChanges) + blockChange.applyNew(); } } -} + public static class BlockChange { + + @Getter + private final String worldName; + + @Getter + private final int x; + + @Getter + private final int y; + + @Getter + private final int z; + + @Getter + private final String oldBlockData; + @Getter + @Setter + private String newBlockData; + + public BlockChange(String worldName, int x, int y, int z, String oldBlockData, String newBlockData) { + this.worldName = worldName; + this.x = x; + this.y = y; + this.z = z; + this.oldBlockData = oldBlockData; + this.newBlockData = newBlockData; + } + + public void applyOld() { + apply(oldBlockData); + } + + public void applyNew() { + apply(newBlockData); + } + + private void apply(String blockDataString) { + World world = Bukkit.getWorld(worldName); + + if (world == null) + return; + + Block block = world.getBlockAt(x, y, z); + BlockData blockData = Bukkit.createBlockData(blockDataString); + block.setBlockData(blockData, false); + } + } +} \ No newline at end of file From c0c98ceea6f41ba62085088ce7478845abe4cb76 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Mon, 27 Apr 2026 19:38:06 +0200 Subject: [PATCH 02/18] feature: added a first version for the cuboid and poly rail generator --- .../components/rail/RailScripts.java | 77 ++++++++++++++----- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 4f84409e..33158d76 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -4,8 +4,10 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; @@ -68,7 +70,10 @@ private void generateRail() { private List getControlPoints() { if (getRegion() instanceof CuboidRegion cuboidRegion) - return getCuboidCenterLine(cuboidRegion); + return getCuboidControlPoints(cuboidRegion); + + if (getRegion() instanceof Polygonal2DRegion polygonalRegion) + return getPolygonalControlPoints(polygonalRegion); List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); @@ -83,32 +88,68 @@ private List getControlPoints() { return orderPointsAsPath(blockPoints); } - private List getCuboidCenterLine(CuboidRegion cuboidRegion) { - int minX = cuboidRegion.getMinimumPoint().x(); - int maxX = cuboidRegion.getMaximumPoint().x(); - int minY = cuboidRegion.getMinimumPoint().y(); - int minZ = cuboidRegion.getMinimumPoint().z(); - int maxZ = cuboidRegion.getMaximumPoint().z(); + private List getCuboidControlPoints(CuboidRegion cuboidRegion) { + List points = new ArrayList<>(); - int centerX = (minX + maxX) / 2; - int centerZ = (minZ + maxZ) / 2; + BlockVector3 pos1 = cuboidRegion.getPos1(); + BlockVector3 pos2 = cuboidRegion.getPos2(); - int widthX = Math.abs(maxX - minX); - int widthZ = Math.abs(maxZ - minZ); + Vector start = new Vector(pos1.x(), pos1.y(), pos1.z()); + Vector end = new Vector(pos2.x(), pos2.y(), pos2.z()); - List points = new ArrayList<>(); + if (sameBlock(start, end)) { + start = new Vector( + cuboidRegion.getMinimumPoint().x(), + cuboidRegion.getMinimumPoint().y(), + cuboidRegion.getMinimumPoint().z() + ); - if (widthX >= widthZ) { - points.add(new Vector(minX, minY, centerZ)); - points.add(new Vector(maxX, minY, centerZ)); - } else { - points.add(new Vector(centerX, minY, minZ)); - points.add(new Vector(centerX, minY, maxZ)); + end = new Vector( + cuboidRegion.getMaximumPoint().x(), + cuboidRegion.getMinimumPoint().y(), + cuboidRegion.getMaximumPoint().z() + ); } + points.add(start); + points.add(end); + return points; } + private List getPolygonalControlPoints(Polygonal2DRegion polygonalRegion) { + List points = new ArrayList<>(); + + int minY = polygonalRegion.getMinimumY(); + int maxY = polygonalRegion.getMaximumY(); + + for (BlockVector2 point : polygonalRegion.getPoints()) { + int y = findBestYForPolygonPoint(point.x(), point.z(), minY, maxY); + points.add(new Vector(point.x(), y, point.z())); + } + + if (points.size() < 2) + return points; + + return removeOnlyConsecutiveDuplicates(points); + } + + private int findBestYForPolygonPoint(int x, int z, int minY, int maxY) { + org.bukkit.World world = getPlayer().getWorld(); + + int safeMinY = Math.max(world.getMinHeight(), Math.min(minY, maxY)); + int safeMaxY = Math.min(world.getMaxHeight() - 1, Math.max(minY, maxY)); + + for (int y = safeMaxY; y >= safeMinY; y--) { + Material material = world.getBlockAt(x, y, z).getType(); + + if (material.isSolid()) + return y; + } + + return safeMaxY; + } + private List orderPointsAsPath(List points) { List remaining = new ArrayList<>(); List ordered = new ArrayList<>(); From 738a7f5d2a85e8c93009b25a793ec090bb1e4750 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Wed, 29 Apr 2026 21:00:57 +0200 Subject: [PATCH 03/18] feature: - Added a railway generator entry to the generator menu - Made railway documentation links clickable in chat - Added RailSelectionPointReader for cuboid, polygonal, and convex selections - Validated supported rail selections before generation - Added RailPath and RailPathBuilder for center path generation - Moved center path generation out of RailScripts - Kept RailScripts focused on generation flow and placement --- .../generator/components/rail/Rail.java | 19 +- .../components/rail/RailScripts.java | 280 +----------------- .../components/rail/path/RailPath.java | 28 ++ .../components/rail/path/RailPathBuilder.java | 115 +++++++ .../selection/RailSelectionPointReader.java | 230 ++++++++++++++ .../modules/generator/menu/GeneratorMenu.java | 25 +- 6 files changed, 413 insertions(+), 284 deletions(-) create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index 90265618..4d244bd4 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -1,14 +1,15 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; -import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; -import com.sk89q.worldedit.regions.CuboidRegion; -import com.sk89q.worldedit.regions.Polygonal2DRegion; import com.sk89q.worldedit.regions.Region; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.selection.RailSelectionPointReader; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.List; public class Rail extends GeneratorComponent { @@ -24,14 +25,20 @@ public boolean checkForPlayer(Player player) { } Region region = GeneratorUtils.getWorldEditSelection(player); + RailSelectionPointReader reader = new RailSelectionPointReader(player, region); - if (!(region instanceof CuboidRegion) - && !(region instanceof Polygonal2DRegion) - && !(region instanceof ConvexPolyhedralRegion)) { + if (!reader.isSupportedSelection()) { player.sendMessage("§cRail Generator only supports cuboid, polygonal and convex WorldEdit selections."); return false; } + List controlPoints = reader.readControlPoints(); + + if (controlPoints.size() < 2) { + player.sendMessage("§cRail Generator could not read enough points from this selection."); + return false; + } + return true; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 33158d76..a0717797 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -1,15 +1,14 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; -import com.alpsbte.alpslib.utils.GeneratorUtils; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.regions.CuboidRegion; -import com.sk89q.worldedit.regions.Polygonal2DRegion; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPathBuilder; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.selection.RailSelectionPointReader; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; import net.buildtheearth.buildteamtools.modules.generator.model.History; @@ -54,285 +53,28 @@ private void generateRail() { return; } - List centerPath = createCenterPath(controlPoints); + RailPath railPath = new RailPathBuilder().build(controlPoints); - if (centerPath.size() < 2) { + if (!railPath.isValid()) { getPlayer().sendMessage("§cRail Generator could not derive a valid path from this selection."); return; } - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeSampleTrack(centerPath)); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeSampleTrack(railPath)); } catch (Exception exception) { - getPlayer().sendMessage("§cRail Generator failed while reading the WorldEdit selection."); + getPlayer().sendMessage("§cRail Generator failed while generating from the WorldEdit selection."); exception.printStackTrace(); } } private List getControlPoints() { - if (getRegion() instanceof CuboidRegion cuboidRegion) - return getCuboidControlPoints(cuboidRegion); - - if (getRegion() instanceof Polygonal2DRegion polygonalRegion) - return getPolygonalControlPoints(polygonalRegion); - - List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - - if (points == null || points.size() < 2) - return new ArrayList<>(); - - List blockPoints = new ArrayList<>(); - - for (Vector point : points) - blockPoints.add(toBlockVector(point)); - - return orderPointsAsPath(blockPoints); - } - - private List getCuboidControlPoints(CuboidRegion cuboidRegion) { - List points = new ArrayList<>(); - - BlockVector3 pos1 = cuboidRegion.getPos1(); - BlockVector3 pos2 = cuboidRegion.getPos2(); - - Vector start = new Vector(pos1.x(), pos1.y(), pos1.z()); - Vector end = new Vector(pos2.x(), pos2.y(), pos2.z()); - - if (sameBlock(start, end)) { - start = new Vector( - cuboidRegion.getMinimumPoint().x(), - cuboidRegion.getMinimumPoint().y(), - cuboidRegion.getMinimumPoint().z() - ); - - end = new Vector( - cuboidRegion.getMaximumPoint().x(), - cuboidRegion.getMinimumPoint().y(), - cuboidRegion.getMaximumPoint().z() - ); - } - - points.add(start); - points.add(end); - - return points; - } - - private List getPolygonalControlPoints(Polygonal2DRegion polygonalRegion) { - List points = new ArrayList<>(); - - int minY = polygonalRegion.getMinimumY(); - int maxY = polygonalRegion.getMaximumY(); - - for (BlockVector2 point : polygonalRegion.getPoints()) { - int y = findBestYForPolygonPoint(point.x(), point.z(), minY, maxY); - points.add(new Vector(point.x(), y, point.z())); - } - - if (points.size() < 2) - return points; - - return removeOnlyConsecutiveDuplicates(points); - } - - private int findBestYForPolygonPoint(int x, int z, int minY, int maxY) { - org.bukkit.World world = getPlayer().getWorld(); - - int safeMinY = Math.max(world.getMinHeight(), Math.min(minY, maxY)); - int safeMaxY = Math.min(world.getMaxHeight() - 1, Math.max(minY, maxY)); - - for (int y = safeMaxY; y >= safeMinY; y--) { - Material material = world.getBlockAt(x, y, z).getType(); - - if (material.isSolid()) - return y; - } - - return safeMaxY; - } - - private List orderPointsAsPath(List points) { - List remaining = new ArrayList<>(); - List ordered = new ArrayList<>(); - - for (Vector point : points) { - if (!containsBlock(remaining, point)) - remaining.add(point); - } - - if (remaining.isEmpty()) - return ordered; - - Vector current = findStartPoint(remaining); - ordered.add(current); - remaining.remove(current); - - while (!remaining.isEmpty()) { - Vector next = findNearestPoint(current, remaining); - ordered.add(next); - remaining.remove(next); - current = next; - } - - return ordered; - } - - private Vector findStartPoint(List points) { - Vector best = points.get(0); - - for (Vector point : points) { - if (point.getBlockX() < best.getBlockX()) { - best = point; - continue; - } - - if (point.getBlockX() == best.getBlockX() && point.getBlockZ() < best.getBlockZ()) - best = point; - } - - return best; - } - - private Vector findNearestPoint(Vector from, List points) { - Vector best = points.get(0); - double bestDistance = distanceSquared2D(from, best); - - for (Vector point : points) { - double distance = distanceSquared2D(from, point); - - if (distance < bestDistance) { - best = point; - bestDistance = distance; - } - } - - return best; + RailSelectionPointReader reader = new RailSelectionPointReader(getPlayer(), getRegion()); + return reader.readControlPoints(); } - private double distanceSquared2D(Vector a, Vector b) { - double dx = a.getBlockX() - b.getBlockX(); - double dz = a.getBlockZ() - b.getBlockZ(); - - return dx * dx + dz * dz; - } - - private boolean containsBlock(List points, Vector target) { - for (Vector point : points) { - if (sameBlock(point, target)) - return true; - } - - return false; - } - - private List createCenterPath(List controlPoints) { - List centerPath = new ArrayList<>(); - - for (int i = 0; i < controlPoints.size() - 1; i++) { - Vector from = controlPoints.get(i); - Vector to = controlPoints.get(i + 1); - - appendEightDirectionalLine(centerPath, from, to); - } - - return removeOnlyConsecutiveDuplicates(repairGaps(centerPath)); - } - - private void appendEightDirectionalLine(List path, Vector from, Vector to) { - int startX = from.getBlockX(); - int startY = from.getBlockY(); - int startZ = from.getBlockZ(); - - int endX = to.getBlockX(); - int endY = to.getBlockY(); - int endZ = to.getBlockZ(); - - int dx = endX - startX; - int dy = endY - startY; - int dz = endZ - startZ; - - int steps = Math.max(Math.abs(dx), Math.abs(dz)); - - if (steps == 0) { - addPointIfNew(path, new Vector(startX, startY, startZ)); - return; - } - - for (int step = 0; step <= steps; step++) { - double t = step / (double) steps; - - int x = (int) Math.round(startX + dx * t); - int y = (int) Math.round(startY + dy * t); - int z = (int) Math.round(startZ + dz * t); - - addPointIfNew(path, new Vector(x, y, z)); - } - } - - private List repairGaps(List path) { - if (path.size() < 2) - return path; - - List repaired = new ArrayList<>(); - repaired.add(path.get(0)); - - for (int i = 1; i < path.size(); i++) { - Vector previous = repaired.get(repaired.size() - 1); - Vector current = path.get(i); - - if (getChebyshevDistance(previous, current) <= 1) { - addPointIfNew(repaired, current); - continue; - } - - appendEightDirectionalLine(repaired, previous, current); - } - - return repaired; - } - - private int getChebyshevDistance(Vector a, Vector b) { - int dx = Math.abs(a.getBlockX() - b.getBlockX()); - int dz = Math.abs(a.getBlockZ() - b.getBlockZ()); - - return Math.max(dx, dz); - } - - private void addPointIfNew(List path, Vector point) { - if (path.isEmpty()) { - path.add(point); - return; - } - - Vector last = path.get(path.size() - 1); - - if (!sameBlock(last, point)) - path.add(point); - } - - private List removeOnlyConsecutiveDuplicates(List path) { - List result = new ArrayList<>(); - - for (Vector point : path) - addPointIfNew(result, point); - - return result; - } - - private Vector toBlockVector(Vector vector) { - return new Vector( - Math.round(vector.getX()), - Math.round(vector.getY()), - Math.round(vector.getZ()) - ); - } - - private boolean sameBlock(Vector a, Vector b) { - return a.getBlockX() == b.getBlockX() - && a.getBlockY() == b.getBlockY() - && a.getBlockZ() == b.getBlockZ(); - } + private void placeSampleTrack(RailPath railPath) { + List centerPath = railPath.getCenterPath(); - private void placeSampleTrack(List centerPath) { Map blockMap = new LinkedHashMap<>(); LinkedHashSet centerPositions = new LinkedHashSet<>(); Map sideBlocks = new LinkedHashMap<>(); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java new file mode 100644 index 00000000..5ffec594 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java @@ -0,0 +1,28 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.path; + +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RailPath { + + private final List centerPath; + + public RailPath(List centerPath) { + this.centerPath = new ArrayList<>(centerPath); + } + + public List getCenterPath() { + return Collections.unmodifiableList(centerPath); + } + + public int size() { + return centerPath.size(); + } + + public boolean isValid() { + return centerPath.size() >= 2; + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java new file mode 100644 index 00000000..74f42180 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java @@ -0,0 +1,115 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.path; + +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; + +public class RailPathBuilder { + + public RailPath build(List controlPoints) { + if (controlPoints == null || controlPoints.size() < 2) + return new RailPath(new ArrayList<>()); + + List centerPath = new ArrayList<>(); + + for (int i = 0; i < controlPoints.size() - 1; i++) { + Vector from = controlPoints.get(i); + Vector to = controlPoints.get(i + 1); + + appendEightDirectionalLine(centerPath, from, to); + } + + centerPath = repairGaps(centerPath); + centerPath = removeOnlyConsecutiveDuplicates(centerPath); + + return new RailPath(centerPath); + } + + private void appendEightDirectionalLine(List path, Vector from, Vector to) { + int startX = from.getBlockX(); + int startY = from.getBlockY(); + int startZ = from.getBlockZ(); + + int endX = to.getBlockX(); + int endY = to.getBlockY(); + int endZ = to.getBlockZ(); + + int dx = endX - startX; + int dy = endY - startY; + int dz = endZ - startZ; + + int steps = Math.max(Math.abs(dx), Math.abs(dz)); + + if (steps == 0) { + addPointIfNew(path, new Vector(startX, startY, startZ)); + return; + } + + for (int step = 0; step <= steps; step++) { + double t = step / (double) steps; + + int x = (int) Math.round(startX + dx * t); + int y = (int) Math.round(startY + dy * t); + int z = (int) Math.round(startZ + dz * t); + + addPointIfNew(path, new Vector(x, y, z)); + } + } + + private List repairGaps(List path) { + if (path.size() < 2) + return path; + + List repaired = new ArrayList<>(); + repaired.add(path.get(0)); + + for (int i = 1; i < path.size(); i++) { + Vector previous = repaired.get(repaired.size() - 1); + Vector current = path.get(i); + + if (getChebyshevDistance(previous, current) <= 1) { + addPointIfNew(repaired, current); + continue; + } + + appendEightDirectionalLine(repaired, previous, current); + } + + return repaired; + } + + private int getChebyshevDistance(Vector a, Vector b) { + int dx = Math.abs(a.getBlockX() - b.getBlockX()); + int dz = Math.abs(a.getBlockZ() - b.getBlockZ()); + + return Math.max(dx, dz); + } + + private void addPointIfNew(List path, Vector point) { + if (path.isEmpty()) { + path.add(point); + return; + } + + Vector last = path.get(path.size() - 1); + + if (!sameBlock(last, point)) + path.add(point); + } + + private List removeOnlyConsecutiveDuplicates(List path) { + List result = new ArrayList<>(); + + for (Vector point : path) + addPointIfNew(result, point); + + return result; + } + + private boolean sameBlock(Vector a, Vector b) { + return a.getBlockX() == b.getBlockX() + && a.getBlockY() == b.getBlockY() + && a.getBlockZ() == b.getBlockZ(); + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java new file mode 100644 index 00000000..3559228f --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java @@ -0,0 +1,230 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.selection; + +import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; + +public class RailSelectionPointReader { + + private final Player player; + private final Region region; + + public RailSelectionPointReader(Player player, Region region) { + this.player = player; + this.region = region; + } + + public boolean isSupportedSelection() { + return region instanceof CuboidRegion + || region instanceof Polygonal2DRegion + || region instanceof ConvexPolyhedralRegion; + } + + public List readControlPoints() { + if (region instanceof CuboidRegion cuboidRegion) + return readCuboidPoints(cuboidRegion); + + if (region instanceof Polygonal2DRegion polygonalRegion) + return readPolygonalPoints(polygonalRegion); + + if (region instanceof ConvexPolyhedralRegion convexRegion) + return readConvexPoints(convexRegion); + + return new ArrayList<>(); + } + + private List readCuboidPoints(CuboidRegion cuboidRegion) { + List points = new ArrayList<>(); + + BlockVector3 pos1 = cuboidRegion.getPos1(); + BlockVector3 pos2 = cuboidRegion.getPos2(); + + Vector start = new Vector(pos1.x(), pos1.y(), pos1.z()); + Vector end = new Vector(pos2.x(), pos2.y(), pos2.z()); + + if (sameBlock(start, end)) { + start = new Vector( + cuboidRegion.getMinimumPoint().x(), + cuboidRegion.getMinimumPoint().y(), + cuboidRegion.getMinimumPoint().z() + ); + + end = new Vector( + cuboidRegion.getMaximumPoint().x(), + cuboidRegion.getMinimumPoint().y(), + cuboidRegion.getMaximumPoint().z() + ); + } + + points.add(start); + points.add(end); + + return points; + } + + private List readPolygonalPoints(Polygonal2DRegion polygonalRegion) { + List points = new ArrayList<>(); + + int minY = polygonalRegion.getMinimumY(); + int maxY = polygonalRegion.getMaximumY(); + + for (BlockVector2 point : polygonalRegion.getPoints()) { + int y = findBestY(point.x(), point.z(), minY, maxY); + points.add(new Vector(point.x(), y, point.z())); + } + + return removeOnlyConsecutiveDuplicates(points); + } + + private List readConvexPoints(ConvexPolyhedralRegion convexRegion) { + List points = new ArrayList<>(); + + for (BlockVector3 point : convexRegion.getVertices()) { + Vector vector = new Vector(point.x(), point.y(), point.z()); + + if (!containsBlock(points, vector)) + points.add(vector); + } + + if (points.size() < 2) { + List fallbackPoints = GeneratorUtils.getSelectionPointsFromRegion(region); + + if (fallbackPoints != null) { + for (Vector point : fallbackPoints) { + Vector blockPoint = toBlockVector(point); + + if (!containsBlock(points, blockPoint)) + points.add(blockPoint); + } + } + } + + return orderPointsAsPath(points); + } + + private int findBestY(int x, int z, int minY, int maxY) { + World world = player.getWorld(); + + int safeMinY = Math.max(world.getMinHeight(), Math.min(minY, maxY)); + int safeMaxY = Math.min(world.getMaxHeight() - 1, Math.max(minY, maxY)); + + for (int y = safeMaxY; y >= safeMinY; y--) { + Material material = world.getBlockAt(x, y, z).getType(); + + if (material.isSolid()) + return y; + } + + return safeMaxY; + } + + private List orderPointsAsPath(List points) { + List remaining = new ArrayList<>(); + List ordered = new ArrayList<>(); + + for (Vector point : points) { + if (!containsBlock(remaining, point)) + remaining.add(point); + } + + if (remaining.isEmpty()) + return ordered; + + Vector current = findStartPoint(remaining); + ordered.add(current); + remaining.remove(current); + + while (!remaining.isEmpty()) { + Vector next = findNearestPoint(current, remaining); + ordered.add(next); + remaining.remove(next); + current = next; + } + + return ordered; + } + + private Vector findStartPoint(List points) { + Vector best = points.get(0); + + for (Vector point : points) { + if (point.getBlockX() < best.getBlockX()) { + best = point; + continue; + } + + if (point.getBlockX() == best.getBlockX() && point.getBlockZ() < best.getBlockZ()) + best = point; + } + + return best; + } + + private Vector findNearestPoint(Vector from, List points) { + Vector best = points.get(0); + double bestDistance = distanceSquared2D(from, best); + + for (Vector point : points) { + double distance = distanceSquared2D(from, point); + + if (distance < bestDistance) { + best = point; + bestDistance = distance; + } + } + + return best; + } + + private double distanceSquared2D(Vector a, Vector b) { + double dx = a.getBlockX() - b.getBlockX(); + double dz = a.getBlockZ() - b.getBlockZ(); + + return dx * dx + dz * dz; + } + + private List removeOnlyConsecutiveDuplicates(List points) { + List result = new ArrayList<>(); + + for (Vector point : points) { + if (result.isEmpty() || !sameBlock(result.get(result.size() - 1), point)) + result.add(point); + } + + return result; + } + + private Vector toBlockVector(Vector vector) { + return new Vector( + Math.round(vector.getX()), + Math.round(vector.getY()), + Math.round(vector.getZ()) + ); + } + + private boolean containsBlock(List points, Vector target) { + for (Vector point : points) { + if (sameBlock(point, target)) + return true; + } + + return false; + } + + private boolean sameBlock(Vector a, Vector b) { + return a.getBlockX() == b.getBlockX() + && a.getBlockY() == b.getBlockY() + && a.getBlockZ() == b.getBlockZ(); + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java index 0c07f34f..61069e1d 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java @@ -22,6 +22,8 @@ import net.buildtheearth.buildteamtools.utils.MenuItems; import net.buildtheearth.buildteamtools.utils.menus.AbstractMenu; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Sound; import org.bukkit.entity.Player; @@ -38,13 +40,9 @@ public class GeneratorMenu extends AbstractMenu { public static final String GENERATOR_INV_NAME = "What do you want to generate?"; public static final int HOUSE_ITEM_SLOT = 9; - public static final int ROAD_ITEM_SLOT = 11; - public static final int RAILWAY_ITEM_SLOT = 13; - public static final int TREE_ITEM_SLOT = 15; - public static final int FIELD_ITEM_SLOT = 17; public GeneratorMenu(Player player, boolean autoLoad) { @@ -105,9 +103,9 @@ protected void setPreviewItems() { "- Convex", "", "§eFeatures:", - "- Straight rail sections", - "- Curves and corners", - "- Automatic rail orientation", + "- Straight sections", + "- Direction changes", + "- Automatic side block orientation", "", "§8Left-click to generate", "§8Right-click for Tutorial" @@ -172,7 +170,7 @@ protected void setPreviewItems() { @Override protected void setMenuItemsAsync() { - // No Async / DB Items + // No async or database items. } @Override @@ -256,7 +254,16 @@ protected void setItemClickEventsAsync() { } private void sendMoreInformation(@NonNull Player clickPlayer, @NonNull GeneratorType generator) { - clickPlayer.sendMessage(Component.text(generator.getWikiPage(), NamedTextColor.RED)); + String wikiPage = generator.getWikiPage(); + + clickPlayer.sendMessage( + Component.text("Open generator documentation: ", NamedTextColor.GRAY) + .append( + Component.text(wikiPage, NamedTextColor.RED) + .clickEvent(ClickEvent.openUrl(wikiPage)) + .hoverEvent(HoverEvent.showText(Component.text("Click to open this page", NamedTextColor.GRAY))) + ) + ); } @Override From 003504c594946e03b5ca3c89ce53430ebbcbf7df Mon Sep 17 00:00:00 2001 From: Jasupa Date: Thu, 30 Apr 2026 21:51:36 +0200 Subject: [PATCH 04/18] feature: - Improved path handling for curves, diagonals, gaps, and direction changes - Added side block builder for railway side/anvil placement - Added orientation resolver for side block facing - Added RailBlockPlacement and RailBlockRole placement model - Added RailType interface and SampleRailType implementation - Moved block data creation into the rail type system - Added WorldEdit/Bukkit placement handler for rail generation - Refactored RailScripts into an orchestration flow --- .../components/rail/RailScripts.java | 310 ++---------------- .../components/rail/path/RailPathBuilder.java | 246 ++++++++++++-- .../rail/placement/RailBlockPlacement.java | 29 ++ .../rail/placement/RailBlockRole.java | 6 + .../rail/placement/RailPlacementBuilder.java | 58 ++++ .../rail/placement/RailWorldEditPlacer.java | 79 +++++ .../rail/side/RailOrientationResolver.java | 95 ++++++ .../components/rail/side/RailSideBlock.java | 34 ++ .../components/rail/side/RailSideBuilder.java | 178 ++++++++++ .../components/rail/types/RailType.java | 11 + .../components/rail/types/SampleRailType.java | 69 ++++ 11 files changed, 807 insertions(+), 308 deletions(-) create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index a0717797..c642da81 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -1,41 +1,32 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.math.BlockVector3; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPathBuilder; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockPlacement; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailPlacementBuilder; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailWorldEditPlacer; import net.buildtheearth.buildteamtools.modules.generator.components.rail.selection.RailSelectionPointReader; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.SampleRailType; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; import net.buildtheearth.buildteamtools.modules.generator.model.History; import net.buildtheearth.buildteamtools.modules.generator.model.Script; import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Directional; import org.bukkit.entity.Player; import org.bukkit.util.Vector; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; public class RailScripts extends Script { - private static final Material[] CENTER_MATERIALS = new Material[]{ - Material.DEAD_FIRE_CORAL_BLOCK, - Material.STONE, - Material.COBBLESTONE - }; + private final RailPathBuilder pathBuilder = new RailPathBuilder(); + private final RailPlacementBuilder placementBuilder = new RailPlacementBuilder(); + private final RailWorldEditPlacer placer = new RailWorldEditPlacer(); - private static final Material SIDE_MATERIAL = Material.ANVIL; + private final RailType railType = new SampleRailType(); public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); @@ -53,14 +44,21 @@ private void generateRail() { return; } - RailPath railPath = new RailPathBuilder().build(controlPoints); + RailPath railPath = pathBuilder.build(controlPoints); if (!railPath.isValid()) { getPlayer().sendMessage("§cRail Generator could not derive a valid path from this selection."); return; } - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeSampleTrack(railPath)); + List placements = placementBuilder.buildPlacements(railPath); + + if (placements.isEmpty()) { + getPlayer().sendMessage("§cRail Generator did not create any block placements."); + return; + } + + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeRail(placements)); } catch (Exception exception) { getPlayer().sendMessage("§cRail Generator failed while generating from the WorldEdit selection."); exception.printStackTrace(); @@ -72,283 +70,27 @@ private List getControlPoints() { return reader.readControlPoints(); } - private void placeSampleTrack(RailPath railPath) { - List centerPath = railPath.getCenterPath(); - - Map blockMap = new LinkedHashMap<>(); - LinkedHashSet centerPositions = new LinkedHashSet<>(); - Map sideBlocks = new LinkedHashMap<>(); - - for (Vector center : centerPath) - centerPositions.add(toBlockVector3(center)); - - createSideBlocksFromCenterEdges(centerPath, centerPositions, sideBlocks); - placeSideBlocks(blockMap, sideBlocks); - placeCenterBlocks(blockMap, centerPositions); - - boolean placedWithWorldEdit = tryPlaceWithWorldEdit(blockMap); + private void placeRail(List placements) { + boolean placedWithWorldEdit = placer.placeWithWorldEdit(this, placements, railType); if (!placedWithWorldEdit) { getPlayer().sendMessage("§eWorldEdit history is unavailable. Falling back to Bukkit placement."); getPlayer().sendMessage("§eUse §6/gen undo§e instead of §6//undo§e for this generation."); - placeWithBukkitFallback(blockMap); - return; - } - - GeneratorModule.getInstance() - .getPlayerHistory(getPlayer()) - .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, 1)); - - getGeneratorComponent().sendSuccessMessage(getPlayer()); - } - - private void createSideBlocksFromCenterEdges( - List centerPath, - LinkedHashSet centerPositions, - Map sideBlocks - ) { - for (int i = 0; i < centerPath.size() - 1; i++) { - Vector from = centerPath.get(i); - Vector to = centerPath.get(i + 1); - Vector direction = getDirection(from, to); - if (direction == null) - continue; + List changes = placer.placeWithBukkitFallback(this, placements, railType); - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); + GeneratorModule.getInstance() + .getPlayerHistory(getPlayer()) + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); - if (dx != 0 && dz != 0) { - placeDiagonalEdgeSideBlocks(sideBlocks, centerPositions, from, dx, dz); - } else { - placeStraightEdgeSideBlocks(sideBlocks, centerPositions, from, to, direction); - } - } - } - - private void placeStraightEdgeSideBlocks( - Map sideBlocks, - LinkedHashSet centerPositions, - Vector from, - Vector to, - Vector direction - ) { - Vector leftOffset = getLeftOffset(direction); - Vector rightOffset = getRightOffset(direction); - - addSideBlock(sideBlocks, centerPositions, offset(from, leftOffset), direction); - addSideBlock(sideBlocks, centerPositions, offset(to, leftOffset), direction); - addSideBlock(sideBlocks, centerPositions, offset(from, rightOffset), direction); - addSideBlock(sideBlocks, centerPositions, offset(to, rightOffset), direction); - } - - private void placeDiagonalEdgeSideBlocks( - Map sideBlocks, - LinkedHashSet centerPositions, - Vector from, - int dx, - int dz - ) { - Vector direction = new Vector(dx, 0, dz); - - BlockVector3 horizontalSide = BlockVector3.at( - from.getBlockX() + dx, - from.getBlockY(), - from.getBlockZ() - ); - - BlockVector3 verticalSide = BlockVector3.at( - from.getBlockX(), - from.getBlockY(), - from.getBlockZ() + dz - ); - - addSideBlock(sideBlocks, centerPositions, horizontalSide, direction); - addSideBlock(sideBlocks, centerPositions, verticalSide, direction); - } - - private BlockVector3 offset(Vector center, Vector offset) { - return BlockVector3.at( - center.getBlockX() + offset.getBlockX(), - center.getBlockY(), - center.getBlockZ() + offset.getBlockZ() - ); - } - - private void addSideBlock( - Map sideBlocks, - LinkedHashSet centerPositions, - BlockVector3 position, - Vector direction - ) { - if (centerPositions.contains(position)) + getGeneratorComponent().sendSuccessMessage(getPlayer()); return; - - SideBlock sideBlock = sideBlocks.computeIfAbsent(position, SideBlock::new); - sideBlock.addDirection(direction); - } - - private Vector getLeftOffset(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - return new Vector(-dz, 0, dx); - } - - private Vector getRightOffset(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - return new Vector(dz, 0, -dx); - } - - private void placeSideBlocks(Map blockMap, Map sideBlocks) { - for (SideBlock sideBlock : sideBlocks.values()) { - Vector direction = getBestAnvilDirection(sideBlock, sideBlocks); - blockMap.put(sideBlock.position, getAnvilBlockData(direction)); - } - } - - private Vector getBestAnvilDirection(SideBlock sideBlock, Map sideBlocks) { - boolean eastWest = sideBlocks.containsKey(sideBlock.position.add(1, 0, 0)) - || sideBlocks.containsKey(sideBlock.position.add(-1, 0, 0)); - - boolean northSouth = sideBlocks.containsKey(sideBlock.position.add(0, 0, 1)) - || sideBlocks.containsKey(sideBlock.position.add(0, 0, -1)); - - if (eastWest && !northSouth) - return new Vector(1, 0, 0); - - if (northSouth && !eastWest) - return new Vector(0, 0, 1); - - return sideBlock.getAverageDirection(); - } - - private Vector getDirection(Vector from, Vector to) { - int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); - int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); - - if (dx == 0 && dz == 0) - return null; - - return new Vector(dx, 0, dz); - } - - private void placeCenterBlocks(Map blockMap, LinkedHashSet centerPositions) { - for (BlockVector3 center : centerPositions) - blockMap.put(center, getCenterBlockData(center)); - } - - private boolean tryPlaceWithWorldEdit(Map blockMap) { - try (EditSession editSession = WorldEdit.getInstance() - .newEditSessionBuilder() - .world(getWeWorld()) - .actor(getActor()) - .build()) { - - for (Map.Entry entry : blockMap.entrySet()) - editSession.setBlock(entry.getKey(), BukkitAdapter.adapt(entry.getValue())); - - editSession.flushQueue(); - getLocalSession().remember(editSession); - return true; - } catch (Throwable throwable) { - throwable.printStackTrace(); - return false; - } - } - - private void placeWithBukkitFallback(Map blockMap) { - List changes = new ArrayList<>(); - - for (Map.Entry entry : blockMap.entrySet()) { - BlockVector3 position = entry.getKey(); - BlockData newData = entry.getValue(); - - org.bukkit.block.Block block = getPlayer().getWorld().getBlockAt(position.x(), position.y(), position.z()); - - changes.add(new History.BlockChange( - getPlayer().getWorld().getName(), - position.x(), - position.y(), - position.z(), - block.getBlockData().getAsString(), - newData.getAsString() - )); - - block.setBlockData(newData, false); } GeneratorModule.getInstance() .getPlayerHistory(getPlayer()) - .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, 1)); getGeneratorComponent().sendSuccessMessage(getPlayer()); } - - private BlockData getCenterBlockData(BlockVector3 position) { - int index = Math.floorMod(position.x() * 31 + position.z() * 17, CENTER_MATERIALS.length); - return CENTER_MATERIALS[index].createBlockData(); - } - - private BlockData getAnvilBlockData(Vector direction) { - BlockData data = SIDE_MATERIAL.createBlockData(); - - if (data instanceof Directional directional) - directional.setFacing(toBlockFace(direction)); - - return data; - } - - private BlockFace toBlockFace(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - if (Math.abs(dx) >= Math.abs(dz)) { - if (dx > 0) - return BlockFace.EAST; - - if (dx < 0) - return BlockFace.WEST; - } - - if (dz > 0) - return BlockFace.SOUTH; - - if (dz < 0) - return BlockFace.NORTH; - - return BlockFace.EAST; - } - - private BlockVector3 toBlockVector3(Vector vector) { - return BlockVector3.at(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); - } - - private static class SideBlock { - - private final BlockVector3 position; - private int directionX; - private int directionZ; - - private SideBlock(BlockVector3 position) { - this.position = position; - } - - private void addDirection(Vector direction) { - directionX += direction.getBlockX(); - directionZ += direction.getBlockZ(); - } - - private Vector getAverageDirection() { - int dx = Integer.compare(directionX, 0); - int dz = Integer.compare(directionZ, 0); - - if (dx == 0 && dz == 0) - return new Vector(1, 0, 0); - - return new Vector(dx, 0, dz); - } - } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java index 74f42180..34b4caa0 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java @@ -7,33 +7,174 @@ public class RailPathBuilder { + private static final int MAX_CORNER_TRIM = 4; + private static final int MIN_CORNER_DISTANCE = 4; + public RailPath build(List controlPoints) { if (controlPoints == null || controlPoints.size() < 2) return new RailPath(new ArrayList<>()); - List centerPath = new ArrayList<>(); + List normalizedControlPoints = removeOnlyConsecutiveDuplicates(controlPoints); - for (int i = 0; i < controlPoints.size() - 1; i++) { - Vector from = controlPoints.get(i); - Vector to = controlPoints.get(i + 1); + if (normalizedControlPoints.size() < 2) + return new RailPath(new ArrayList<>()); - appendEightDirectionalLine(centerPath, from, to); - } + List centerPath = createCurvedCenterPath(normalizedControlPoints); centerPath = repairGaps(centerPath); + centerPath = removeImmediateBacktracking(centerPath); centerPath = removeOnlyConsecutiveDuplicates(centerPath); return new RailPath(centerPath); } + private List createCurvedCenterPath(List controlPoints) { + List path = new ArrayList<>(); + + addPointIfNew(path, toBlockVector(controlPoints.get(0))); + + if (controlPoints.size() == 2) { + appendEightDirectionalLine(path, controlPoints.get(0), controlPoints.get(1)); + return path; + } + + for (int i = 1; i < controlPoints.size() - 1; i++) { + Vector previous = toBlockVector(controlPoints.get(i - 1)); + Vector current = toBlockVector(controlPoints.get(i)); + Vector next = toBlockVector(controlPoints.get(i + 1)); + + if (!shouldCurveCorner(previous, current, next)) { + appendEightDirectionalLine(path, path.get(path.size() - 1), current); + continue; + } + + int trim = getCornerTrim(previous, current, next); + + if (trim <= 0) { + appendEightDirectionalLine(path, path.get(path.size() - 1), current); + continue; + } + + Vector beforeCorner = getPointBeforeCorner(previous, current, trim); + Vector afterCorner = getPointAfterCorner(current, next, trim); + + if (sameBlock(beforeCorner, current) || sameBlock(afterCorner, current)) { + appendEightDirectionalLine(path, path.get(path.size() - 1), current); + continue; + } + + appendEightDirectionalLine(path, path.get(path.size() - 1), beforeCorner); + appendQuadraticCurve(path, beforeCorner, current, afterCorner); + } + + appendEightDirectionalLine(path, path.get(path.size() - 1), controlPoints.get(controlPoints.size() - 1)); + + return path; + } + + private boolean shouldCurveCorner(Vector previous, Vector current, Vector next) { + Vector incoming = getDirection(previous, current); + Vector outgoing = getDirection(current, next); + + if (incoming == null || outgoing == null) + return false; + + if (sameDirection(incoming, outgoing)) + return false; + + if (oppositeDirection(incoming, outgoing)) + return false; + + int incomingLength = getChebyshevDistance(previous, current); + int outgoingLength = getChebyshevDistance(current, next); + + return incomingLength >= MIN_CORNER_DISTANCE && outgoingLength >= MIN_CORNER_DISTANCE; + } + + private int getCornerTrim(Vector previous, Vector current, Vector next) { + int incomingLength = getChebyshevDistance(previous, current); + int outgoingLength = getChebyshevDistance(current, next); + + int shortest = Math.min(incomingLength, outgoingLength); + + if (shortest < MIN_CORNER_DISTANCE) + return 0; + + return Math.max(1, Math.min(MAX_CORNER_TRIM, shortest / 3)); + } + + private Vector getPointBeforeCorner(Vector previous, Vector current, int trim) { + List incomingLine = createEightDirectionalLine(previous, current); + + int index = Math.max(0, incomingLine.size() - 1 - trim); + + return incomingLine.get(index); + } + + private Vector getPointAfterCorner(Vector current, Vector next, int trim) { + List outgoingLine = createEightDirectionalLine(current, next); + + int index = Math.min(outgoingLine.size() - 1, trim); + + return outgoingLine.get(index); + } + + private void appendQuadraticCurve(List path, Vector start, Vector control, Vector end) { + int firstDistance = getChebyshevDistance(start, control); + int secondDistance = getChebyshevDistance(control, end); + int samples = Math.max(6, (firstDistance + secondDistance) * 3); + + for (int sample = 1; sample <= samples; sample++) { + double t = sample / (double) samples; + + double inverse = 1.0 - t; + + double x = inverse * inverse * start.getX() + + 2.0 * inverse * t * control.getX() + + t * t * end.getX(); + + double y = inverse * inverse * start.getY() + + 2.0 * inverse * t * control.getY() + + t * t * end.getY(); + + double z = inverse * inverse * start.getZ() + + 2.0 * inverse * t * control.getZ() + + t * t * end.getZ(); + + Vector point = toBlockVector(new Vector(x, y, z)); + + if (path.isEmpty()) { + addPointIfNew(path, point); + continue; + } + + Vector last = path.get(path.size() - 1); + + if (getChebyshevDistance(last, point) <= 1) { + addPointIfNew(path, point); + } else { + appendEightDirectionalLine(path, last, point); + } + } + } + + private List createEightDirectionalLine(Vector from, Vector to) { + List line = new ArrayList<>(); + appendEightDirectionalLine(line, from, to); + return line; + } + private void appendEightDirectionalLine(List path, Vector from, Vector to) { - int startX = from.getBlockX(); - int startY = from.getBlockY(); - int startZ = from.getBlockZ(); + Vector start = toBlockVector(from); + Vector end = toBlockVector(to); - int endX = to.getBlockX(); - int endY = to.getBlockY(); - int endZ = to.getBlockZ(); + int startX = start.getBlockX(); + int startY = start.getBlockY(); + int startZ = start.getBlockZ(); + + int endX = end.getBlockX(); + int endY = end.getBlockY(); + int endZ = end.getBlockZ(); int dx = endX - startX; int dy = endY - startY; @@ -79,32 +220,89 @@ private List repairGaps(List path) { return repaired; } - private int getChebyshevDistance(Vector a, Vector b) { - int dx = Math.abs(a.getBlockX() - b.getBlockX()); - int dz = Math.abs(a.getBlockZ() - b.getBlockZ()); + private List removeImmediateBacktracking(List path) { + if (path.size() < 3) + return path; - return Math.max(dx, dz); + List cleaned = new ArrayList<>(); + + for (Vector point : path) { + addPointIfNew(cleaned, point); + + while (cleaned.size() >= 3) { + Vector a = cleaned.get(cleaned.size() - 3); + Vector b = cleaned.get(cleaned.size() - 2); + Vector c = cleaned.get(cleaned.size() - 1); + + if (sameBlock(a, c)) { + cleaned.remove(cleaned.size() - 1); + cleaned.remove(cleaned.size() - 1); + continue; + } + + break; + } + } + + return cleaned; + } + + private List removeOnlyConsecutiveDuplicates(List path) { + List result = new ArrayList<>(); + + for (Vector point : path) + addPointIfNew(result, toBlockVector(point)); + + return result; } private void addPointIfNew(List path, Vector point) { + Vector blockPoint = toBlockVector(point); + if (path.isEmpty()) { - path.add(point); + path.add(blockPoint); return; } Vector last = path.get(path.size() - 1); - if (!sameBlock(last, point)) - path.add(point); + if (!sameBlock(last, blockPoint)) + path.add(blockPoint); } - private List removeOnlyConsecutiveDuplicates(List path) { - List result = new ArrayList<>(); + private Vector getDirection(Vector from, Vector to) { + int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); + int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); - for (Vector point : path) - addPointIfNew(result, point); + if (dx == 0 && dz == 0) + return null; - return result; + return new Vector(dx, 0, dz); + } + + private boolean sameDirection(Vector a, Vector b) { + return a.getBlockX() == b.getBlockX() + && a.getBlockZ() == b.getBlockZ(); + } + + private boolean oppositeDirection(Vector a, Vector b) { + return a.getBlockX() == -b.getBlockX() + && a.getBlockZ() == -b.getBlockZ(); + } + + private int getChebyshevDistance(Vector a, Vector b) { + int dx = Math.abs(a.getBlockX() - b.getBlockX()); + int dz = Math.abs(a.getBlockZ() - b.getBlockZ()); + + return Math.max(dx, dz); + } + + private Vector toBlockVector(Vector vector) { + return new Vector( + Math.round(vector.getX()), + Math.round(vector.getY()), + Math.round(vector.getZ()) + ); } private boolean sameBlock(Vector a, Vector b) { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java new file mode 100644 index 00000000..e33015c2 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java @@ -0,0 +1,29 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; + +import com.sk89q.worldedit.math.BlockVector3; +import org.bukkit.util.Vector; + +public class RailBlockPlacement { + + private final BlockVector3 position; + private final RailBlockRole role; + private final Vector direction; + + public RailBlockPlacement(BlockVector3 position, RailBlockRole role, Vector direction) { + this.position = position; + this.role = role; + this.direction = direction == null ? new Vector(1, 0, 0) : direction; + } + + public BlockVector3 getPosition() { + return position; + } + + public RailBlockRole getRole() { + return role; + } + + public Vector getDirection() { + return direction; + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java new file mode 100644 index 00000000..97f161a7 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java @@ -0,0 +1,6 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; + +public enum RailBlockRole { + CENTER, + SIDE +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java new file mode 100644 index 00000000..588e1bc7 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java @@ -0,0 +1,58 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; + +import com.sk89q.worldedit.math.BlockVector3; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.side.RailOrientationResolver; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.side.RailSideBlock; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.side.RailSideBuilder; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +public class RailPlacementBuilder { + + private final RailSideBuilder sideBuilder; + private final RailOrientationResolver orientationResolver; + + public RailPlacementBuilder() { + this.sideBuilder = new RailSideBuilder(); + this.orientationResolver = new RailOrientationResolver(); + } + + public List buildPlacements(RailPath railPath) { + List placements = new ArrayList<>(); + + LinkedHashSet centerPositions = sideBuilder.getCenterPositions(railPath); + Map sideBlocks = sideBuilder.buildSideBlocks(railPath); + + addSidePlacements(placements, sideBlocks); + addCenterPlacements(placements, centerPositions); + + return placements; + } + + private void addSidePlacements(List placements, Map sideBlocks) { + for (RailSideBlock sideBlock : sideBlocks.values()) { + Vector direction = orientationResolver.resolveDirection(sideBlock, sideBlocks); + + placements.add(new RailBlockPlacement( + sideBlock.getPosition(), + RailBlockRole.SIDE, + direction + )); + } + } + + private void addCenterPlacements(List placements, LinkedHashSet centerPositions) { + for (BlockVector3 centerPosition : centerPositions) { + placements.add(new RailBlockPlacement( + centerPosition, + RailBlockRole.CENTER, + new Vector(1, 0, 0) + )); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java new file mode 100644 index 00000000..5019dbca --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java @@ -0,0 +1,79 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; +import net.buildtheearth.buildteamtools.modules.generator.model.History; +import net.buildtheearth.buildteamtools.modules.generator.model.Script; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class RailWorldEditPlacer { + + public boolean placeWithWorldEdit(Script script, List placements, RailType railType) { + Map blockMap = createBlockMap(placements, railType); + + try (EditSession editSession = WorldEdit.getInstance() + .newEditSessionBuilder() + .world(script.getWeWorld()) + .actor(script.getActor()) + .build()) { + + for (Map.Entry entry : blockMap.entrySet()) + editSession.setBlock(entry.getKey(), BukkitAdapter.adapt(entry.getValue())); + + editSession.flushQueue(); + script.getLocalSession().remember(editSession); + + return true; + } catch (Throwable throwable) { + throwable.printStackTrace(); + return false; + } + } + + public List placeWithBukkitFallback( + Script script, + List placements, + RailType railType + ) { + Map blockMap = createBlockMap(placements, railType); + List changes = new ArrayList<>(); + + for (Map.Entry entry : blockMap.entrySet()) { + BlockVector3 position = entry.getKey(); + BlockData newData = entry.getValue(); + + Block block = script.getPlayer().getWorld().getBlockAt(position.x(), position.y(), position.z()); + + changes.add(new History.BlockChange( + script.getPlayer().getWorld().getName(), + position.x(), + position.y(), + position.z(), + block.getBlockData().getAsString(), + newData.getAsString() + )); + + block.setBlockData(newData, false); + } + + return changes; + } + + private Map createBlockMap(List placements, RailType railType) { + Map blockMap = new LinkedHashMap<>(); + + for (RailBlockPlacement placement : placements) + blockMap.put(placement.getPosition(), railType.createBlockData(placement)); + + return blockMap; + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java new file mode 100644 index 00000000..7f68c291 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java @@ -0,0 +1,95 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.side; + +import com.sk89q.worldedit.math.BlockVector3; +import org.bukkit.util.Vector; + +import java.util.Map; + +public class RailOrientationResolver { + + public Vector resolveDirection(RailSideBlock sideBlock, Map sideBlocks) { + BlockVector3 position = sideBlock.getPosition(); + + boolean east = sideBlocks.containsKey(position.add(1, 0, 0)); + boolean west = sideBlocks.containsKey(position.add(-1, 0, 0)); + boolean south = sideBlocks.containsKey(position.add(0, 0, 1)); + boolean north = sideBlocks.containsKey(position.add(0, 0, -1)); + + boolean eastWest = east || west; + boolean northSouth = north || south; + + if (eastWest && !northSouth) + return getHorizontalDirection(east, west); + + if (northSouth && !eastWest) + return getVerticalDirection(south, north); + + if (eastWest && northSouth) + return resolveCornerDirection(sideBlock, east, west, south, north); + + return sideBlock.getAverageDirection(); + } + + private Vector getHorizontalDirection(boolean east, boolean west) { + if (east && !west) + return new Vector(1, 0, 0); + + if (west && !east) + return new Vector(-1, 0, 0); + + return new Vector(1, 0, 0); + } + + private Vector getVerticalDirection(boolean south, boolean north) { + if (south && !north) + return new Vector(0, 0, 1); + + if (north && !south) + return new Vector(0, 0, -1); + + return new Vector(0, 0, 1); + } + + private Vector resolveCornerDirection( + RailSideBlock sideBlock, + boolean east, + boolean west, + boolean south, + boolean north + ) { + Vector average = sideBlock.getAverageDirection(); + + int avgX = average.getBlockX(); + int avgZ = average.getBlockZ(); + + if (Math.abs(avgX) > Math.abs(avgZ)) { + if (avgX > 0 && east) + return new Vector(1, 0, 0); + + if (avgX < 0 && west) + return new Vector(-1, 0, 0); + } + + if (Math.abs(avgZ) > Math.abs(avgX)) { + if (avgZ > 0 && south) + return new Vector(0, 0, 1); + + if (avgZ < 0 && north) + return new Vector(0, 0, -1); + } + + if (east) + return new Vector(1, 0, 0); + + if (west) + return new Vector(-1, 0, 0); + + if (south) + return new Vector(0, 0, 1); + + if (north) + return new Vector(0, 0, -1); + + return average; + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java new file mode 100644 index 00000000..b07b892e --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java @@ -0,0 +1,34 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.side; + +import com.sk89q.worldedit.math.BlockVector3; +import org.bukkit.util.Vector; + +public class RailSideBlock { + + private final BlockVector3 position; + private int directionX; + private int directionZ; + + public RailSideBlock(BlockVector3 position) { + this.position = position; + } + + public BlockVector3 getPosition() { + return position; + } + + public void addDirection(Vector direction) { + directionX += direction.getBlockX(); + directionZ += direction.getBlockZ(); + } + + public Vector getAverageDirection() { + int dx = Integer.compare(directionX, 0); + int dz = Integer.compare(directionZ, 0); + + if (dx == 0 && dz == 0) + return new Vector(1, 0, 0); + + return new Vector(dx, 0, dz); + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java new file mode 100644 index 00000000..7169ec98 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java @@ -0,0 +1,178 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.side; + +import com.sk89q.worldedit.math.BlockVector3; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; +import org.bukkit.util.Vector; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +public class RailSideBuilder { + + public Map buildSideBlocks(RailPath railPath) { + Map sideBlocks = new LinkedHashMap<>(); + LinkedHashSet centerPositions = getCenterPositions(railPath); + + List centerPath = railPath.getCenterPath(); + + for (int i = 0; i < centerPath.size() - 1; i++) { + Vector from = centerPath.get(i); + Vector to = centerPath.get(i + 1); + Vector direction = getDirection(from, to); + + if (direction == null) + continue; + + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + if (dx != 0 && dz != 0) { + placeDiagonalEdgeSideBlocks(sideBlocks, centerPositions, from, dx, dz); + } else { + placeStraightEdgeSideBlocks(sideBlocks, centerPositions, from, to, direction); + } + } + + removeFloatingSideBlocks(sideBlocks); + + return sideBlocks; + } + + public LinkedHashSet getCenterPositions(RailPath railPath) { + LinkedHashSet centerPositions = new LinkedHashSet<>(); + + for (Vector center : railPath.getCenterPath()) + centerPositions.add(toBlockVector3(center)); + + return centerPositions; + } + + private void placeStraightEdgeSideBlocks( + Map sideBlocks, + LinkedHashSet centerPositions, + Vector from, + Vector to, + Vector direction + ) { + Vector leftOffset = getLeftOffset(direction); + Vector rightOffset = getRightOffset(direction); + + addSideBlock(sideBlocks, centerPositions, offset(from, leftOffset), direction); + addSideBlock(sideBlocks, centerPositions, offset(to, leftOffset), direction); + addSideBlock(sideBlocks, centerPositions, offset(from, rightOffset), direction); + addSideBlock(sideBlocks, centerPositions, offset(to, rightOffset), direction); + } + + private void placeDiagonalEdgeSideBlocks( + Map sideBlocks, + LinkedHashSet centerPositions, + Vector from, + int dx, + int dz + ) { + Vector direction = new Vector(dx, 0, dz); + + BlockVector3 horizontalSide = BlockVector3.at( + from.getBlockX() + dx, + from.getBlockY(), + from.getBlockZ() + ); + + BlockVector3 verticalSide = BlockVector3.at( + from.getBlockX(), + from.getBlockY(), + from.getBlockZ() + dz + ); + + addSideBlock(sideBlocks, centerPositions, horizontalSide, direction); + addSideBlock(sideBlocks, centerPositions, verticalSide, direction); + } + + private void addSideBlock( + Map sideBlocks, + LinkedHashSet centerPositions, + BlockVector3 position, + Vector direction + ) { + if (centerPositions.contains(position)) + return; + + RailSideBlock sideBlock = sideBlocks.computeIfAbsent(position, RailSideBlock::new); + sideBlock.addDirection(direction); + } + + private void removeFloatingSideBlocks(Map sideBlocks) { + if (sideBlocks.size() <= 2) + return; + + sideBlocks.entrySet().removeIf(entry -> countSideNeighbours(entry.getKey(), sideBlocks) == 0); + } + + private int countSideNeighbours(BlockVector3 position, Map sideBlocks) { + int count = 0; + + if (sideBlocks.containsKey(position.add(1, 0, 0))) + count++; + + if (sideBlocks.containsKey(position.add(-1, 0, 0))) + count++; + + if (sideBlocks.containsKey(position.add(0, 0, 1))) + count++; + + if (sideBlocks.containsKey(position.add(0, 0, -1))) + count++; + + if (sideBlocks.containsKey(position.add(1, 0, 1))) + count++; + + if (sideBlocks.containsKey(position.add(1, 0, -1))) + count++; + + if (sideBlocks.containsKey(position.add(-1, 0, 1))) + count++; + + if (sideBlocks.containsKey(position.add(-1, 0, -1))) + count++; + + return count; + } + + private Vector getLeftOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + return new Vector(-dz, 0, dx); + } + + private Vector getRightOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + return new Vector(dz, 0, -dx); + } + + private BlockVector3 offset(Vector center, Vector offset) { + return BlockVector3.at( + center.getBlockX() + offset.getBlockX(), + center.getBlockY(), + center.getBlockZ() + offset.getBlockZ() + ); + } + + private Vector getDirection(Vector from, Vector to) { + int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); + int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); + + if (dx == 0 && dz == 0) + return null; + + return new Vector(dx, 0, dz); + } + + private BlockVector3 toBlockVector3(Vector vector) { + return BlockVector3.at(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); + } +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java new file mode 100644 index 00000000..e7c9b358 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java @@ -0,0 +1,11 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.types; + +import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockPlacement; +import org.bukkit.block.data.BlockData; + +public interface RailType { + + String getName(); + + BlockData createBlockData(RailBlockPlacement placement); +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java new file mode 100644 index 00000000..6114369e --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java @@ -0,0 +1,69 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.types; + +import com.sk89q.worldedit.math.BlockVector3; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockPlacement; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockRole; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.util.Vector; + +public class SampleRailType implements RailType { + + private static final Material[] CENTER_MATERIALS = new Material[]{ + Material.DEAD_FIRE_CORAL_BLOCK, + Material.STONE, + Material.COBBLESTONE + }; + + private static final Material SIDE_MATERIAL = Material.ANVIL; + + @Override + public String getName() { + return "Sample Railway"; + } + + @Override + public BlockData createBlockData(RailBlockPlacement placement) { + if (placement.getRole() == RailBlockRole.CENTER) + return createCenterBlockData(placement.getPosition()); + + return createSideBlockData(placement.getDirection()); + } + + private BlockData createCenterBlockData(BlockVector3 position) { + int index = Math.floorMod(position.x() * 31 + position.z() * 17, CENTER_MATERIALS.length); + return CENTER_MATERIALS[index].createBlockData(); + } + + private BlockData createSideBlockData(Vector direction) { + BlockData data = SIDE_MATERIAL.createBlockData(); + + if (data instanceof Directional directional) + directional.setFacing(toBlockFace(direction)); + + return data; + } + + private BlockFace toBlockFace(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + if (Math.abs(dx) >= Math.abs(dz)) { + if (dx > 0) + return BlockFace.EAST; + + if (dx < 0) + return BlockFace.WEST; + } + + if (dz > 0) + return BlockFace.SOUTH; + + if (dz < 0) + return BlockFace.NORTH; + + return BlockFace.EAST; + } +} \ No newline at end of file From 6bd86153d0c23488f2f99ce2d81a1b1004b34416 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Fri, 1 May 2026 14:35:18 +0200 Subject: [PATCH 05/18] refactor: - Normalised vectors before comparing block positions - Used Vector#getBlockX/Y/Z instead of manual rounding - Moved rail validation logic into hasValidRailSelection - Kept checkForPlayer aligned with the generator component contract - Improved readability of rail path and selection validation code --- .../generator/components/rail/Rail.java | 4 +++ .../components/rail/path/RailPathBuilder.java | 26 ++++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index 4d244bd4..3a2363a3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -19,6 +19,10 @@ public Rail() { @Override public boolean checkForPlayer(Player player) { + return hasValidRailSelection(player); + } + + private boolean hasValidRailSelection(Player player) { if (GeneratorUtils.checkForNoWorldEditSelection(player)) { player.sendMessage("§cRail Generator requires an active WorldEdit selection."); return false; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java index 34b4caa0..a7663baf 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java @@ -126,7 +126,6 @@ private void appendQuadraticCurve(List path, Vector start, Vector contro for (int sample = 1; sample <= samples; sample++) { double t = sample / (double) samples; - double inverse = 1.0 - t; double x = inverse * inverse * start.getX() @@ -231,7 +230,6 @@ private List removeImmediateBacktracking(List path) { while (cleaned.size() >= 3) { Vector a = cleaned.get(cleaned.size() - 3); - Vector b = cleaned.get(cleaned.size() - 2); Vector c = cleaned.get(cleaned.size() - 1); if (sameBlock(a, c)) { @@ -271,8 +269,11 @@ private void addPointIfNew(List path, Vector point) { } private Vector getDirection(Vector from, Vector to) { - int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); - int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); + Vector blockFrom = toBlockVector(from); + Vector blockTo = toBlockVector(to); + + int dx = Integer.compare(blockTo.getBlockX() - blockFrom.getBlockX(), 0); + int dz = Integer.compare(blockTo.getBlockZ() - blockFrom.getBlockZ(), 0); if (dx == 0 && dz == 0) return null; @@ -291,23 +292,24 @@ private boolean oppositeDirection(Vector a, Vector b) { } private int getChebyshevDistance(Vector a, Vector b) { - int dx = Math.abs(a.getBlockX() - b.getBlockX()); - int dz = Math.abs(a.getBlockZ() - b.getBlockZ()); + Vector blockA = toBlockVector(a); + Vector blockB = toBlockVector(b); + + int dx = Math.abs(blockA.getBlockX() - blockB.getBlockX()); + int dz = Math.abs(blockA.getBlockZ() - blockB.getBlockZ()); return Math.max(dx, dz); } private Vector toBlockVector(Vector vector) { return new Vector( - Math.round(vector.getX()), - Math.round(vector.getY()), - Math.round(vector.getZ()) + vector.getBlockX(), + vector.getBlockY(), + vector.getBlockZ() ); } private boolean sameBlock(Vector a, Vector b) { - return a.getBlockX() == b.getBlockX() - && a.getBlockY() == b.getBlockY() - && a.getBlockZ() == b.getBlockZ(); + return toBlockVector(a).equals(toBlockVector(b)); } } \ No newline at end of file From f3315c422354fd039a4cd567a3fa1bcd028fa66e Mon Sep 17 00:00:00 2001 From: Jasupa Date: Tue, 5 May 2026 20:04:49 +0200 Subject: [PATCH 06/18] refactor: - Fixed the Qodana issues --- .../generator/components/rail/RailScripts.java | 8 +++++++- .../rail/placement/RailBlockPlacement.java | 14 ++------------ .../rail/placement/RailWorldEditPlacer.java | 17 ++++++++++++++--- .../rail/side/RailOrientationResolver.java | 4 ++-- .../components/rail/side/RailSideBlock.java | 6 ++---- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index c642da81..6cab068b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -19,6 +19,7 @@ import org.bukkit.util.Vector; import java.util.List; +import java.util.logging.Level; public class RailScripts extends Script { @@ -61,7 +62,12 @@ private void generateRail() { Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeRail(placements)); } catch (Exception exception) { getPlayer().sendMessage("§cRail Generator failed while generating from the WorldEdit selection."); - exception.printStackTrace(); + + BuildTeamTools.getInstance().getLogger().log( + Level.SEVERE, + "Rail Generator failed while generating from the WorldEdit selection.", + exception + ); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java index e33015c2..54080feb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java @@ -1,8 +1,10 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; import com.sk89q.worldedit.math.BlockVector3; +import lombok.Getter; import org.bukkit.util.Vector; +@Getter public class RailBlockPlacement { private final BlockVector3 position; @@ -14,16 +16,4 @@ public RailBlockPlacement(BlockVector3 position, RailBlockRole role, Vector dire this.role = role; this.direction = direction == null ? new Vector(1, 0, 0) : direction; } - - public BlockVector3 getPosition() { - return position; - } - - public RailBlockRole getRole() { - return role; - } - - public Vector getDirection() { - return direction; - } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java index 5019dbca..029f1525 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java @@ -4,6 +4,8 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockState; +import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; import net.buildtheearth.buildteamtools.modules.generator.model.History; import net.buildtheearth.buildteamtools.modules.generator.model.Script; @@ -14,6 +16,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; public class RailWorldEditPlacer { @@ -26,15 +29,23 @@ public boolean placeWithWorldEdit(Script script, List placem .actor(script.getActor()) .build()) { - for (Map.Entry entry : blockMap.entrySet()) - editSession.setBlock(entry.getKey(), BukkitAdapter.adapt(entry.getValue())); + for (Map.Entry entry : blockMap.entrySet()) { + BlockVector3 position = entry.getKey(); + BlockState blockState = BukkitAdapter.adapt(entry.getValue()); + + editSession.setBlock(position.x(), position.y(), position.z(), blockState); + } editSession.flushQueue(); script.getLocalSession().remember(editSession); return true; } catch (Throwable throwable) { - throwable.printStackTrace(); + BuildTeamTools.getInstance().getLogger().log( + Level.SEVERE, + "Failed to place railway with WorldEdit.", + throwable + ); return false; } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java index 7f68c291..072f2b90 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java @@ -21,10 +21,10 @@ public Vector resolveDirection(RailSideBlock sideBlock, Map Date: Thu, 7 May 2026 18:33:13 +0200 Subject: [PATCH 07/18] fix: - made sure only /gen rail is valid as a command and not /gen railway --- .../modules/generator/commands/GeneratorCommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java index c14bba39..c13f0e8b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java @@ -40,7 +40,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return true; case "rail": - case "railway": GeneratorModule.getInstance().getRail().analyzeCommand(p, args); return true; From b4099a7db89c86c37a3236bd4de8b9a81a8059b2 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Sun, 17 May 2026 12:11:46 +0200 Subject: [PATCH 08/18] fix: - Added path, side block and placement builders - Added validation for unsupported, too small and too large selections - Used Bukkit scheduler for safer async generation flow - Fixed rail help command so it does not start generation - Fixed copied rail command to use /gen rail - Removed lane support from v1 scope - Fixed rail undo history for repeated generation on the same selection - Skipped unchanged blocks to avoid unnecessary undo entries --- .../modules/generator/GeneratorModule.java | 2 +- .../generator/components/house/House.java | 4 +- .../generator/components/rail/RailFlag.java | 22 ++--- .../components/rail/RailScripts.java | 81 ++++++++++++------- .../components/rail/RailSettings.java | 7 +- .../rail/placement/RailWorldEditPlacer.java | 44 ++-------- ...mpleRailType.java => DefaultRailType.java} | 4 +- .../generator/model/GeneratorComponent.java | 69 +++++++++------- 8 files changed, 113 insertions(+), 120 deletions(-) rename src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/{SampleRailType.java => DefaultRailType.java} (96%) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/GeneratorModule.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/GeneratorModule.java index c0033b94..392744a0 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/GeneratorModule.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/GeneratorModule.java @@ -93,7 +93,7 @@ public void registerCommands() { @Override public void registerListeners() { super.registerListeners( - new GeneratorListener() + new GeneratorListener() ); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/House.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/House.java index e0da3777..c804522e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/House.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/house/House.java @@ -17,7 +17,7 @@ public boolean checkForPlayer(Player p) { if (GeneratorUtils.checkForNoWorldEditSelection(p)) return false; - if (getPlayerSettings().get(p.getUniqueId()).getBlocks() == null) // Needed because block checks are made afterwards + if (getPlayerSettings().get(p.getUniqueId()).getBlocks() == null) getPlayerSettings().get(p.getUniqueId()).setBlocks(GeneratorUtils.analyzeRegion(p, p.getWorld())); Block[][][] blocks = getPlayerSettings().get(p.getUniqueId()).getBlocks(); @@ -35,4 +35,4 @@ public void generate(Player p) { new HouseScripts(p, this); } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java index a78994b0..0b12dcd1 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java @@ -4,31 +4,19 @@ import net.buildtheearth.buildteamtools.modules.generator.model.FlagType; public enum RailFlag implements Flag { - LANE_COUNT("c", FlagType.INTEGER); - - private final String flag; - private final FlagType flagType; - - RailFlag(String flag, FlagType flagType){ - this.flag = flag; - this.flagType = flagType; - } + ; @Override public String getFlag() { - return flag; + return null; } @Override public FlagType getFlagType() { - return flagType; + return null; } - public static RailFlag byString(String flag){ - for(RailFlag railFlag : RailFlag.values()) - if(railFlag.getFlag().equalsIgnoreCase(flag)) - return railFlag; - + public static RailFlag byString(String flag) { return null; } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 6cab068b..fc197ef3 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -8,8 +8,8 @@ import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailPlacementBuilder; import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailWorldEditPlacer; import net.buildtheearth.buildteamtools.modules.generator.components.rail.selection.RailSelectionPointReader; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.DefaultRailType; import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.SampleRailType; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; import net.buildtheearth.buildteamtools.modules.generator.model.History; @@ -23,20 +23,22 @@ public class RailScripts extends Script { + private static final int MAX_PATH_POINTS = 20_000; + private static final int MAX_BLOCK_PLACEMENTS = 100_000; + private final RailPathBuilder pathBuilder = new RailPathBuilder(); private final RailPlacementBuilder placementBuilder = new RailPlacementBuilder(); private final RailWorldEditPlacer placer = new RailWorldEditPlacer(); - private final RailType railType = new SampleRailType(); + private final RailType railType = new DefaultRailType(); public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); - Thread thread = new Thread(this::generateRail); - thread.start(); + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), this::readSelectionOnMainThread); } - private void generateRail() { + private void readSelectionOnMainThread() { try { List controlPoints = getControlPoints(); @@ -45,29 +47,44 @@ private void generateRail() { return; } + Bukkit.getScheduler().runTaskAsynchronously( + BuildTeamTools.getInstance(), + () -> buildRailAsync(controlPoints) + ); + } catch (Exception exception) { + fail("Rail Generator failed while reading the WorldEdit selection.", exception); + } + } + + private void buildRailAsync(List controlPoints) { + try { RailPath railPath = pathBuilder.build(controlPoints); if (!railPath.isValid()) { - getPlayer().sendMessage("§cRail Generator could not derive a valid path from this selection."); + sendPlayerMessage("§cRail Generator could not derive a valid path from this selection."); + return; + } + + if (railPath.size() > MAX_PATH_POINTS) { + sendPlayerMessage("§cRail Generator selection is too large. Please use a smaller selection."); return; } List placements = placementBuilder.buildPlacements(railPath); if (placements.isEmpty()) { - getPlayer().sendMessage("§cRail Generator did not create any block placements."); + sendPlayerMessage("§cRail Generator did not create any block placements."); + return; + } + + if (placements.size() > MAX_BLOCK_PLACEMENTS) { + sendPlayerMessage("§cRail Generator would place too many blocks. Please use a smaller selection."); return; } Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeRail(placements)); } catch (Exception exception) { - getPlayer().sendMessage("§cRail Generator failed while generating from the WorldEdit selection."); - - BuildTeamTools.getInstance().getLogger().log( - Level.SEVERE, - "Rail Generator failed while generating from the WorldEdit selection.", - exception - ); + fail("Rail Generator failed while generating from the WorldEdit selection.", exception); } } @@ -77,26 +94,36 @@ private List getControlPoints() { } private void placeRail(List placements) { - boolean placedWithWorldEdit = placer.placeWithWorldEdit(this, placements, railType); - - if (!placedWithWorldEdit) { - getPlayer().sendMessage("§eWorldEdit history is unavailable. Falling back to Bukkit placement."); - getPlayer().sendMessage("§eUse §6/gen undo§e instead of §6//undo§e for this generation."); - - List changes = placer.placeWithBukkitFallback(this, placements, railType); + List changes = placer.placeWithBukkitHistory(this, placements, railType); - GeneratorModule.getInstance() - .getPlayerHistory(getPlayer()) - .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); - - getGeneratorComponent().sendSuccessMessage(getPlayer()); + if (changes.isEmpty()) { + getPlayer().sendMessage("§eRail Generator did not change any blocks. No undo entry was added."); return; } GeneratorModule.getInstance() .getPlayerHistory(getPlayer()) - .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, 1)); + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); getGeneratorComponent().sendSuccessMessage(getPlayer()); } + + private void fail(String message, Exception exception) { + sendPlayerMessage("§c" + message); + + BuildTeamTools.getInstance().getLogger().log( + Level.SEVERE, + message, + exception + ); + } + + private void sendPlayerMessage(String message) { + if (Bukkit.isPrimaryThread()) { + getPlayer().sendMessage(message); + return; + } + + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> getPlayer().sendMessage(message)); + } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java index 0d7ef243..507881fb 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java @@ -9,9 +9,8 @@ public RailSettings(Player player) { super(player); } + @Override public void setDefaultValues() { - - // Lane Count (Default: Fixed Value) - setValue(RailFlag.LANE_COUNT, 1); + // Rail Generator v1 does not use configurable settings yet. } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java index 029f1525..d96e0640 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java @@ -1,11 +1,6 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.world.block.BlockState; -import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; import net.buildtheearth.buildteamtools.modules.generator.model.History; import net.buildtheearth.buildteamtools.modules.generator.model.Script; @@ -16,41 +11,10 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; public class RailWorldEditPlacer { - public boolean placeWithWorldEdit(Script script, List placements, RailType railType) { - Map blockMap = createBlockMap(placements, railType); - - try (EditSession editSession = WorldEdit.getInstance() - .newEditSessionBuilder() - .world(script.getWeWorld()) - .actor(script.getActor()) - .build()) { - - for (Map.Entry entry : blockMap.entrySet()) { - BlockVector3 position = entry.getKey(); - BlockState blockState = BukkitAdapter.adapt(entry.getValue()); - - editSession.setBlock(position.x(), position.y(), position.z(), blockState); - } - - editSession.flushQueue(); - script.getLocalSession().remember(editSession); - - return true; - } catch (Throwable throwable) { - BuildTeamTools.getInstance().getLogger().log( - Level.SEVERE, - "Failed to place railway with WorldEdit.", - throwable - ); - return false; - } - } - - public List placeWithBukkitFallback( + public List placeWithBukkitHistory( Script script, List placements, RailType railType @@ -63,13 +27,17 @@ public List placeWithBukkitFallback( BlockData newData = entry.getValue(); Block block = script.getPlayer().getWorld().getBlockAt(position.x(), position.y(), position.z()); + BlockData oldData = block.getBlockData(); + + if (oldData.matches(newData)) + continue; changes.add(new History.BlockChange( script.getPlayer().getWorld().getName(), position.x(), position.y(), position.z(), - block.getBlockData().getAsString(), + oldData.getAsString(), newData.getAsString() )); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java similarity index 96% rename from src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java rename to src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java index 6114369e..fe0d6983 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/SampleRailType.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java @@ -9,7 +9,7 @@ import org.bukkit.block.data.Directional; import org.bukkit.util.Vector; -public class SampleRailType implements RailType { +public class DefaultRailType implements RailType { private static final Material[] CENTER_MATERIALS = new Material[]{ Material.DEAD_FIRE_CORAL_BLOCK, @@ -21,7 +21,7 @@ public class SampleRailType implements RailType { @Override public String getName() { - return "Sample Railway"; + return "Default Railway"; } @Override diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java index cde217d7..d77152d2 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java @@ -37,23 +37,33 @@ protected GeneratorComponent(@NonNull GeneratorType type) { } public abstract boolean checkForPlayer(Player p); - public abstract void generate(Player p); + public abstract void generate(Player p); + public void analyzeCommand(Player p, String[] args) { + if (isHelpCommand(args)) { + sendHelp(p); + return; + } - public void analyzeCommand(Player p, String[] args){ - sendHelp(p, args); addPlayerSetting(p); convertArgsToSettings(p, args); generate(p); } - public void addPlayerSetting(UUID uuid, Settings settings){ + private boolean isHelpCommand(String[] args) { + return args.length == 2 + && (args[1].equalsIgnoreCase("info") + || args[1].equalsIgnoreCase("help") + || args[1].equalsIgnoreCase("?")); + } + + public void addPlayerSetting(UUID uuid, Settings settings) { playerSettings.put(uuid, settings); } - public void addPlayerSetting(Player p){ - switch (generatorType){ + public void addPlayerSetting(Player p) { + switch (generatorType) { case HOUSE: addPlayerSetting(p.getUniqueId(), new HouseSettings(p)); break; @@ -73,8 +83,8 @@ public void addPlayerSetting(Player p){ } public void sendHelp(Player p, String @NonNull [] args) { - if (args.length == 2 && (args[1].equals("info") || args[1].equals("help") || args[1].equals("?"))) - sendHelp(p); + if (isHelpCommand(args)) + sendHelp(p); } public void sendHelp(@NonNull Player p) { @@ -88,7 +98,7 @@ public void sendMoreInfo(Player p) { } public void sendError(Player p) { - p.sendMessage("§cThere was an error while generating the house. Please contact the admins"); + p.sendMessage("§cThere was an error while generating the " + generatorType.getName().toLowerCase() + ". Please contact the admins."); } public String getCommand(@NonNull Player p) { @@ -97,7 +107,7 @@ public String getCommand(@NonNull Player p) { String type = switch (generatorType) { case HOUSE -> "house"; case ROAD -> "road"; - case RAILWAY -> "railway"; + case RAILWAY -> "rail"; case TREE -> "tree"; case FIELD -> "field"; }; @@ -110,7 +120,7 @@ public String getCommand(@NonNull Player p) { return command.toString(); } - public void sendSuccessMessage(Player p){ + public void sendSuccessMessage(Player p) { TextComponent copyCommand = Component.text("[COPY]", NamedTextColor.YELLOW, TextDecoration.BOLD) .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, getCommand(p))) .hoverEvent(HoverEvent.showText(Component.text("Click to copy command", NamedTextColor.GRAY))); @@ -119,7 +129,11 @@ public void sendSuccessMessage(Player p){ .clickEvent(ClickEvent.runCommand("/gen undo")) .hoverEvent(HoverEvent.showText(Component.text("Click to undo last generation", NamedTextColor.GRAY))); - TextComponent message = getMessage().append(Component.text(" ")).append(copyCommand).append(Component.text(" ")).append(undo); + TextComponent message = getMessage() + .append(Component.text(" ")) + .append(copyCommand) + .append(Component.text(" ")) + .append(undo); p.sendMessage(message); p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); @@ -129,40 +143,37 @@ public void sendSuccessMessage(Player p){ String type = switch (generatorType) { case HOUSE -> "House"; case ROAD -> "Road"; - case RAILWAY -> "Railway"; + case RAILWAY -> "Rail"; case TREE -> "Tree"; case FIELD -> "Field"; }; - return LegacyComponentSerializer.legacyAmpersand().deserialize(BuildTeamTools.PREFIX + type + "§a successfully §7generated."); + return LegacyComponentSerializer.legacyAmpersand() + .deserialize(BuildTeamTools.PREFIX + type + "§a successfully §7generated."); } - /** Conversion: - * Command: /gen house -w 123:12 -r 456:78 - * args: ["-w", "123:12", "-r", "456:78"] - * HouseSettings: - * WALL_COLOR: 123:12 - * ROOF_TYPE: 456:78 - */ - protected void convertArgsToSettings(Player p, String[] args){ - for(String flag : GeneratorUtils.convertArgsToFlags(args)){ + protected void convertArgsToSettings(Player p, String[] args) { + for (String flag : GeneratorUtils.convertArgsToFlags(args)) { String[] flagAndValue = GeneratorUtils.convertToFlagAndValue(flag, p); - if(flagAndValue == null) continue; + if (flagAndValue == null) + continue; String flagName = flagAndValue[0]; - if(flagName == null) continue; + if (flagName == null) + continue; Flag finalFlag = Flag.byString(generatorType, flagName); - if(finalFlag == null) continue; + if (finalFlag == null) + continue; Object flagValue = FlagType.convertToFlagType(finalFlag, flagAndValue[1]); String errorMessage = FlagType.validateFlagType(finalFlag, flagValue); - if(errorMessage != null){ + if (errorMessage != null) { p.sendMessage(errorMessage); continue; } @@ -170,7 +181,7 @@ protected void convertArgsToSettings(Player p, String[] args){ getPlayerSettings().get(p.getUniqueId()).setValue(finalFlag, flagValue); } - if(getPlayerSettings().get(p.getUniqueId()).getValues().isEmpty() && args.length > 1) + if (getPlayerSettings().get(p.getUniqueId()).getValues().isEmpty() && args.length > 1) sendHelp(p); } @@ -178,4 +189,4 @@ protected void convertArgsToSettings(Player p, String[] args){ public String getWikiPage() { return generatorType.getWikiPage(); } -} +} \ No newline at end of file From aaab94c70556427e24ef6eac42e9e4d946a954f8 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Tue, 19 May 2026 19:25:18 +0200 Subject: [PATCH 09/18] refractor: - Made sure the existing Generator Library is used directly for the rail generator instead of making my own one. feature: - The rail generator now generators slower 'async' to make sure it doesn't overload servers. --- .../generator/components/rail/Rail.java | 139 +++- .../components/rail/RailScripts.java | 666 ++++++++++++++++-- .../components/rail/RailSettings.java | 44 +- .../components/rail/path/RailPath.java | 28 - .../components/rail/path/RailPathBuilder.java | 315 --------- .../rail/placement/RailBlockPlacement.java | 19 - .../rail/placement/RailBlockRole.java | 6 - .../rail/placement/RailPlacementBuilder.java | 58 -- .../rail/placement/RailWorldEditPlacer.java | 58 -- .../selection/RailSelectionPointReader.java | 230 ------ .../rail/side/RailOrientationResolver.java | 95 --- .../components/rail/side/RailSideBlock.java | 32 - .../components/rail/side/RailSideBuilder.java | 178 ----- .../rail/types/DefaultRailType.java | 69 -- .../components/rail/types/RailType.java | 11 - .../listeners/GeneratorListener.java | 22 +- .../modules/generator/model/Command.java | 89 ++- .../generator/model/GeneratorComponent.java | 8 + .../modules/generator/model/Script.java | 25 +- 19 files changed, 853 insertions(+), 1239 deletions(-) delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index 3a2363a3..2f921956 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -1,49 +1,71 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; -import com.sk89q.worldedit.regions.Region; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.selection.RailSelectionPointReader; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import java.util.ArrayList; import java.util.List; public class Rail extends GeneratorComponent { + private static final int TARGET_BLOCK_RANGE = 200; + public Rail() { super(GeneratorType.RAILWAY); } @Override - public boolean checkForPlayer(Player player) { - return hasValidRailSelection(player); - } + public void analyzeCommand(Player player, String[] args) { + addPlayerSetting(player); - private boolean hasValidRailSelection(Player player) { - if (GeneratorUtils.checkForNoWorldEditSelection(player)) { - player.sendMessage("§cRail Generator requires an active WorldEdit selection."); - return false; - } + if (args.length >= 2) { + String subCommand = args[1].toLowerCase(); - Region region = GeneratorUtils.getWorldEditSelection(player); - RailSelectionPointReader reader = new RailSelectionPointReader(player, region); + switch (subCommand) { + case "help", "info", "?" -> { + sendHelp(player); + return; + } - if (!reader.isSupportedSelection()) { - player.sendMessage("§cRail Generator only supports cuboid, polygonal and convex WorldEdit selections."); - return false; - } + case "add", "point" -> { + addPoint(player); + return; + } + + case "clear", "reset" -> { + clearPoints(player); + return; + } - List controlPoints = reader.readControlPoints(); + case "points", "list" -> { + listPoints(player); + return; + } - if (controlPoints.size() < 2) { - player.sendMessage("§cRail Generator could not read enough points from this selection."); - return false; + default -> { + player.sendMessage("§cUnknown rail command: §7" + args[1]); + sendHelp(player); + return; + } + } } - return true; + generate(player); + } + + @Override + public boolean checkForPlayer(Player player) { + RailSettings settings = getRailSettings(player); + + if (settings != null && settings.hasEnoughCustomControlPoints()) + return true; + + return !GeneratorUtils.checkForNoWorldEditSelection(player); } @Override @@ -51,6 +73,81 @@ public void generate(Player player) { if (!GeneratorModule.getInstance().getRail().checkForPlayer(player)) return; + RailSettings settings = getRailSettings(player); + + if (settings != null && settings.hasEnoughCustomControlPoints()) { + new RailScripts(player, this, new ArrayList<>(settings.getCustomControlPoints())); + return; + } + new RailScripts(player, this); } + + private void addPoint(Player player) { + RailSettings settings = getRailSettings(player); + + if (settings == null) { + player.sendMessage("§cRail settings could not be loaded."); + return; + } + + Block targetBlock = player.getTargetBlockExact(TARGET_BLOCK_RANGE); + + if (targetBlock == null) { + player.sendMessage("§cLook at a block first to add a rail point."); + return; + } + + Vector point = new Vector( + targetBlock.getX(), + targetBlock.getY() + 1, + targetBlock.getZ() + ); + + settings.addCustomControlPoint(point); + + player.sendMessage("§aAdded rail point §7#" + settings.getCustomControlPoints().size() + + " §8(" + point.getBlockX() + ", " + point.getBlockY() + ", " + point.getBlockZ() + ")"); + } + + private void clearPoints(Player player) { + RailSettings settings = getRailSettings(player); + + if (settings == null) { + player.sendMessage("§cRail settings could not be loaded."); + return; + } + + settings.clearCustomControlPoints(); + player.sendMessage("§aCleared all custom rail points."); + } + + private void listPoints(Player player) { + RailSettings settings = getRailSettings(player); + + if (settings == null) { + player.sendMessage("§cRail settings could not be loaded."); + return; + } + + List points = settings.getCustomControlPoints(); + + if (points.isEmpty()) { + player.sendMessage("§eNo custom rail points saved."); + return; + } + + player.sendMessage("§aSaved rail points:"); + + for (int index = 0; index < points.size(); index++) { + Vector point = points.get(index); + + player.sendMessage("§7#" + (index + 1) + + " §8(" + point.getBlockX() + ", " + point.getBlockY() + ", " + point.getBlockZ() + ")"); + } + } + + private RailSettings getRailSettings(Player player) { + return (RailSettings) getPlayerSettings().get(player.getUniqueId()); + } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index fc197ef3..d9d65efe 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -1,129 +1,649 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; -import net.buildtheearth.buildteamtools.BuildTeamTools; -import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPathBuilder; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockPlacement; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailPlacementBuilder; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailWorldEditPlacer; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.selection.RailSelectionPointReader; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.DefaultRailType; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; +import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.cryptomorin.xseries.XMaterial; +import com.fastasyncworldedit.core.registry.state.PropertyKey; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; -import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; -import net.buildtheearth.buildteamtools.modules.generator.model.History; import net.buildtheearth.buildteamtools.modules.generator.model.Script; -import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; -import java.util.logging.Level; +import java.util.Map; public class RailScripts extends Script { + private static final int MAX_CONTROL_POINTS = 250; private static final int MAX_PATH_POINTS = 20_000; private static final int MAX_BLOCK_PLACEMENTS = 100_000; - private final RailPathBuilder pathBuilder = new RailPathBuilder(); - private final RailPlacementBuilder placementBuilder = new RailPlacementBuilder(); - private final RailWorldEditPlacer placer = new RailWorldEditPlacer(); + private static final XMaterial[] CENTER_MATERIALS = new XMaterial[]{ + XMaterial.DEAD_FIRE_CORAL_BLOCK, + XMaterial.STONE, + XMaterial.COBBLESTONE + }; - private final RailType railType = new DefaultRailType(); + private final List customControlPoints; + + private List controlPoints; + private List centerPath; public RailScripts(Player player, GeneratorComponent generatorComponent) { + this(player, generatorComponent, null); + } + + public RailScripts(Player player, GeneratorComponent generatorComponent, List customControlPoints) { super(player, generatorComponent); + this.customControlPoints = customControlPoints; - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), this::readSelectionOnMainThread); + Thread thread = new Thread(this::railScript_v_1_0); + thread.start(); } - private void readSelectionOnMainThread() { - try { - List controlPoints = getControlPoints(); + private void railScript_v_1_0() { + controlPoints = getControlPoints(); - if (controlPoints.size() < 2) { - getPlayer().sendMessage("§cRail Generator needs at least two usable points in the selection."); - return; - } + if (controlPoints.size() < 2) { + getPlayer().sendMessage("§cRail Generator needs at least two points."); + return; + } + + if (controlPoints.size() > MAX_CONTROL_POINTS) { + getPlayer().sendMessage("§cRail Generator has too many points. Please use fewer points."); + return; + } + + centerPath = createEightDirectionalPath(controlPoints); + + if (centerPath.size() < 2) { + getPlayer().sendMessage("§cRail Generator could not create a valid rail path."); + return; + } + + if (centerPath.size() > MAX_PATH_POINTS) { + getPlayer().sendMessage("§cRail Generator path is too large. Please use a smaller selection."); + return; + } + + Map sideBlocks = buildSideBlocks(centerPath); + Map blocksToPlace = buildBlockMap(centerPath, sideBlocks); + + if (blocksToPlace.size() > MAX_BLOCK_PLACEMENTS) { + getPlayer().sendMessage("§cRail Generator would place too many blocks. Please use a smaller selection."); + return; + } + + for (Map.Entry entry : blocksToPlace.entrySet()) { + PositionKey key = entry.getKey(); + BlockState blockState = entry.getValue(); + + if (blockState == null) + continue; + + Vector position = key.toVector(); + + createCuboidSelection(position, position); + replaceBlocks((BlockState[]) null, blockState); + } - Bukkit.getScheduler().runTaskAsynchronously( - BuildTeamTools.getInstance(), - () -> buildRailAsync(controlPoints) + finish(null, getCuboidRestoreSelection()); + } + + private List getControlPoints() { + if (customControlPoints != null && customControlPoints.size() >= 2) + return copyPoints(customControlPoints); + + if (getRegion() instanceof CuboidRegion cuboidRegion) + return getCuboidControlPoints(cuboidRegion); + + if (getRegion() instanceof ConvexPolyhedralRegion convexRegion) + return getConvexControlPoints(convexRegion); + + if (getRegion() instanceof Polygonal2DRegion polygonalRegion) + return getPolygonalControlPoints(polygonalRegion); + + return new ArrayList<>(GeneratorUtils.getSelectionPointsFromRegion(getRegion())); + } + + private List copyPoints(List points) { + List copiedPoints = new ArrayList<>(); + + for (Vector point : points) + copiedPoints.add(toBlockVector(point)); + + return copiedPoints; + } + + private List getCuboidControlPoints(CuboidRegion cuboidRegion) { + List points = new ArrayList<>(); + + BlockVector3 pos1 = cuboidRegion.getPos1(); + BlockVector3 pos2 = cuboidRegion.getPos2(); + + points.add(new Vector(pos1.x(), pos1.y(), pos1.z())); + points.add(new Vector(pos2.x(), pos2.y(), pos2.z())); + + return points; + } + + private List getConvexControlPoints(ConvexPolyhedralRegion convexRegion) { + List points = new ArrayList<>(); + + for (BlockVector3 point : convexRegion.getVertices()) { + points.add(new Vector( + point.x(), + point.y(), + point.z() + )); + } + + return points; + } + + private List getPolygonalControlPoints(Polygonal2DRegion polygonalRegion) { + List points = new ArrayList<>(); + + for (BlockVector2 point : polygonalRegion.getPoints()) { + int y = getRailYFromWorld( + point.x(), + point.z(), + polygonalRegion.getMinimumY(), + polygonalRegion.getMaximumY() ); - } catch (Exception exception) { - fail("Rail Generator failed while reading the WorldEdit selection.", exception); + + points.add(new Vector( + point.x(), + y, + point.z() + )); } + + return points; } - private void buildRailAsync(List controlPoints) { - try { - RailPath railPath = pathBuilder.build(controlPoints); + private int getRailYFromWorld(int x, int z, int minimumY, int maximumY) { + for (int y = maximumY; y >= minimumY; y--) { + Block block = getPlayer().getWorld().getBlockAt(x, y, z); + Material material = block.getType(); - if (!railPath.isValid()) { - sendPlayerMessage("§cRail Generator could not derive a valid path from this selection."); - return; + if (!material.isAir() + && material != Material.WATER + && material != Material.LAVA) { + return y + 1; } + } - if (railPath.size() > MAX_PATH_POINTS) { - sendPlayerMessage("§cRail Generator selection is too large. Please use a smaller selection."); - return; - } + return minimumY; + } - List placements = placementBuilder.buildPlacements(railPath); + private List createEightDirectionalPath(List points) { + List path = new ArrayList<>(); - if (placements.isEmpty()) { - sendPlayerMessage("§cRail Generator did not create any block placements."); - return; - } + if (points == null || points.isEmpty()) + return path; + + addPointIfNew(path, toBlockVector(points.get(0))); + + for (int index = 0; index < points.size() - 1; index++) { + Vector start = toBlockVector(points.get(index)); + Vector end = toBlockVector(points.get(index + 1)); + + appendEightDirectionalLine(path, start, end); + } + + return path; + } + + private void appendEightDirectionalLine(List path, Vector start, Vector end) { + int x = start.getBlockX(); + int z = start.getBlockZ(); + + int startY = start.getBlockY(); + + int endX = end.getBlockX(); + int endY = end.getBlockY(); + int endZ = end.getBlockZ(); + + int totalHorizontalSteps = Math.max( + Math.abs(endX - x), + Math.abs(endZ - z) + ); + + if (totalHorizontalSteps == 0) { + addPointIfNew(path, new Vector(endX, endY, endZ)); + return; + } + + int currentStep = 0; + + while (x != endX || z != endZ) { + if (x < endX) + x++; + else if (x > endX) + x--; + + if (z < endZ) + z++; + else if (z > endZ) + z--; + + currentStep++; + + double progress = currentStep / (double) totalHorizontalSteps; + int y = (int) Math.round(startY + (endY - startY) * progress); - if (placements.size() > MAX_BLOCK_PLACEMENTS) { - sendPlayerMessage("§cRail Generator would place too many blocks. Please use a smaller selection."); + addPointIfNew(path, new Vector(x, y, z)); + } + } + + private Map buildSideBlocks(List centerPath) { + Map sideBlocks = new LinkedHashMap<>(); + Map centerBlocks = createCenterBlockMap(centerPath); + + for (int index = 0; index < centerPath.size(); index++) { + Vector center = centerPath.get(index); + List directions = getDirectionsForCenterPoint(centerPath, index); + + for (Vector direction : directions) + addSideBlocksForDirection(sideBlocks, centerBlocks, center, direction); + } + + return sideBlocks; + } + + private Map createCenterBlockMap(List centerPath) { + Map centerBlocks = new LinkedHashMap<>(); + + for (Vector center : centerPath) + centerBlocks.put(PositionKey.from(center), center); + + return centerBlocks; + } + + private List getDirectionsForCenterPoint(List path, int index) { + List directions = new ArrayList<>(); + + if (index > 0) + addDirectionIfNew(directions, getHorizontalDirection(path.get(index - 1), path.get(index))); + + if (index < path.size() - 1) + addDirectionIfNew(directions, getHorizontalDirection(path.get(index), path.get(index + 1))); + + return directions; + } + + private void addDirectionIfNew(List directions, Vector direction) { + if (direction == null) + return; + + for (Vector existingDirection : directions) { + if (existingDirection.getBlockX() == direction.getBlockX() + && existingDirection.getBlockZ() == direction.getBlockZ()) return; - } + } + + directions.add(direction); + } - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeRail(placements)); - } catch (Exception exception) { - fail("Rail Generator failed while generating from the WorldEdit selection.", exception); + private void addSideBlocksForDirection( + Map sideBlocks, + Map centerBlocks, + Vector center, + Vector direction + ) { + if (isDiagonal(direction)) { + addDiagonalSideBlocks(sideBlocks, centerBlocks, center, direction); + return; } + + addStraightSideBlocks(sideBlocks, centerBlocks, center, direction); } - private List getControlPoints() { - RailSelectionPointReader reader = new RailSelectionPointReader(getPlayer(), getRegion()); - return reader.readControlPoints(); + private void addStraightSideBlocks( + Map sideBlocks, + Map centerBlocks, + Vector center, + Vector direction + ) { + Vector left = center.clone().add(getLeftOffset(direction)); + Vector right = center.clone().add(getRightOffset(direction)); + + addSideBlock(sideBlocks, centerBlocks, left, direction); + addSideBlock(sideBlocks, centerBlocks, right, direction); } - private void placeRail(List placements) { - List changes = placer.placeWithBukkitHistory(this, placements, railType); + private void addDiagonalSideBlocks( + Map sideBlocks, + Map centerBlocks, + Vector center, + Vector direction + ) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + Vector firstSide = new Vector( + center.getBlockX() + dx, + center.getBlockY(), + center.getBlockZ() + ); - if (changes.isEmpty()) { - getPlayer().sendMessage("§eRail Generator did not change any blocks. No undo entry was added."); + Vector secondSide = new Vector( + center.getBlockX(), + center.getBlockY(), + center.getBlockZ() + dz + ); + + addSideBlock(sideBlocks, centerBlocks, firstSide, direction); + addSideBlock(sideBlocks, centerBlocks, secondSide, direction); + } + + private void addSideBlock( + Map sideBlocks, + Map centerBlocks, + Vector position, + Vector direction + ) { + PositionKey key = PositionKey.from(position); + + if (centerBlocks.containsKey(key)) return; + + RailSideBlock sideBlock = sideBlocks.computeIfAbsent( + key, + ignored -> new RailSideBlock(position) + ); + + sideBlock.addDirection(direction); + } + + private Map buildBlockMap( + List centerPath, + Map sideBlocks + ) { + Map blockMap = new LinkedHashMap<>(); + + for (RailSideBlock sideBlock : sideBlocks.values()) { + Vector resolvedDirection = resolveSideBlockDirection(sideBlock, sideBlocks); + BlockState anvil = createAnvilBlockState(resolvedDirection); + + if (anvil != null) + blockMap.put(PositionKey.from(sideBlock.position()), anvil); + } + + for (Vector center : centerPath) + blockMap.put(PositionKey.from(center), createCenterBlockState(center)); + + return blockMap; + } + + private Vector resolveSideBlockDirection( + RailSideBlock sideBlock, + Map sideBlocks + ) { + Vector position = sideBlock.position(); + + boolean east = sideBlocks.containsKey(PositionKey.of( + position.getBlockX() + 1, + position.getBlockY(), + position.getBlockZ() + )); + + boolean west = sideBlocks.containsKey(PositionKey.of( + position.getBlockX() - 1, + position.getBlockY(), + position.getBlockZ() + )); + + boolean south = sideBlocks.containsKey(PositionKey.of( + position.getBlockX(), + position.getBlockY(), + position.getBlockZ() + 1 + )); + + boolean north = sideBlocks.containsKey(PositionKey.of( + position.getBlockX(), + position.getBlockY(), + position.getBlockZ() - 1 + )); + + boolean eastWest = east || west; + boolean northSouth = north || south; + + if (eastWest && !northSouth) + return getHorizontalDirection(east, west); + + if (!eastWest && northSouth) + return getVerticalDirection(south, north); + + if (eastWest || northSouth) + return resolveCornerDirection(sideBlock, east, west, south, north); + + return sideBlock.getAverageDirection(); + } + + private Vector getHorizontalDirection(boolean east, boolean west) { + if (east && !west) + return new Vector(1, 0, 0); + + if (west && !east) + return new Vector(-1, 0, 0); + + return new Vector(1, 0, 0); + } + + private Vector getVerticalDirection(boolean south, boolean north) { + if (south && !north) + return new Vector(0, 0, 1); + + if (north && !south) + return new Vector(0, 0, -1); + + return new Vector(0, 0, 1); + } + + private Vector resolveCornerDirection( + RailSideBlock sideBlock, + boolean east, + boolean west, + boolean south, + boolean north + ) { + Vector average = sideBlock.getAverageDirection(); + + int averageX = average.getBlockX(); + int averageZ = average.getBlockZ(); + + if (Math.abs(averageX) > Math.abs(averageZ)) { + if (averageX > 0 && east) + return new Vector(1, 0, 0); + + if (averageX < 0 && west) + return new Vector(-1, 0, 0); + } + + if (Math.abs(averageZ) > Math.abs(averageX)) { + if (averageZ > 0 && south) + return new Vector(0, 0, 1); + + if (averageZ < 0 && north) + return new Vector(0, 0, -1); + } + + if (east) + return new Vector(1, 0, 0); + + if (west) + return new Vector(-1, 0, 0); + + if (south) + return new Vector(0, 0, 1); + + if (north) + return new Vector(0, 0, -1); + + return average; + } + + private BlockState createCenterBlockState(Vector position) { + int index = Math.floorMod( + position.getBlockX() * 31 + position.getBlockZ() * 17, + CENTER_MATERIALS.length + ); + + return GeneratorUtils.getBlockState(CENTER_MATERIALS[index]); + } + + private BlockState createAnvilBlockState(Vector direction) { + if (BlockTypes.ANVIL == null) + return null; + + return BlockTypes.ANVIL + .getDefaultState() + .with(PropertyKey.FACING, getWorldEditDirection(direction)); + } + + private Direction getWorldEditDirection(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + if (Math.abs(dx) >= Math.abs(dz)) { + if (dx > 0) + return Direction.EAST; + + if (dx < 0) + return Direction.WEST; } - GeneratorModule.getInstance() - .getPlayerHistory(getPlayer()) - .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); + if (dz > 0) + return Direction.SOUTH; + + if (dz < 0) + return Direction.NORTH; + + return Direction.EAST; + } + + private Vector getHorizontalDirection(Vector from, Vector to) { + int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); + int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); - getGeneratorComponent().sendSuccessMessage(getPlayer()); + if (dx == 0 && dz == 0) + return null; + + return new Vector(dx, 0, dz); } - private void fail(String message, Exception exception) { - sendPlayerMessage("§c" + message); + private boolean isDiagonal(Vector direction) { + return direction.getBlockX() != 0 && direction.getBlockZ() != 0; + } + + private Vector getLeftOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + return new Vector(-dz, 0, dx); + } - BuildTeamTools.getInstance().getLogger().log( - Level.SEVERE, - message, - exception + private Vector getRightOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); + + return new Vector(dz, 0, -dx); + } + + private Vector toBlockVector(Vector vector) { + return new Vector( + vector.getBlockX(), + vector.getBlockY(), + vector.getBlockZ() ); } - private void sendPlayerMessage(String message) { - if (Bukkit.isPrimaryThread()) { - getPlayer().sendMessage(message); + private void addPointIfNew(List points, Vector point) { + Vector blockPoint = toBlockVector(point); + + if (points.isEmpty()) { + points.add(blockPoint); return; } - Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> getPlayer().sendMessage(message)); + if (!isSameBlock(points.get(points.size() - 1), blockPoint)) + points.add(blockPoint); + } + + private List getCuboidRestoreSelection() { + List restoreSelection = new ArrayList<>(); + + restoreSelection.add(controlPoints.get(0)); + restoreSelection.add(controlPoints.get(controlPoints.size() - 1)); + + return restoreSelection; + } + + private boolean isSameBlock(Vector first, Vector second) { + return first.getBlockX() == second.getBlockX() + && first.getBlockY() == second.getBlockY() + && first.getBlockZ() == second.getBlockZ(); + } + + private record PositionKey(int x, int y, int z) { + + private static PositionKey from(Vector vector) { + return new PositionKey( + vector.getBlockX(), + vector.getBlockY(), + vector.getBlockZ() + ); + } + + private static PositionKey of(int x, int y, int z) { + return new PositionKey(x, y, z); + } + + private Vector toVector() { + return new Vector(x, y, z); + } + } + + private static class RailSideBlock { + + private final Vector position; + private int directionX; + private int directionZ; + + private RailSideBlock(Vector position) { + this.position = position; + } + + private Vector position() { + return position; + } + + private void addDirection(Vector direction) { + directionX += direction.getBlockX(); + directionZ += direction.getBlockZ(); + } + + private Vector getAverageDirection() { + int dx = Integer.compare(directionX, 0); + int dz = Integer.compare(directionZ, 0); + + if (dx == 0 && dz == 0) + return new Vector(1, 0, 0); + + return new Vector(dx, 0, dz); + } } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java index 507881fb..eeb4ef0b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java @@ -2,15 +2,57 @@ import net.buildtheearth.buildteamtools.modules.generator.model.Settings; import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; public class RailSettings extends Settings { + private List customControlPoints; + public RailSettings(Player player) { super(player); } @Override public void setDefaultValues() { - // Rail Generator v1 does not use configurable settings yet. + if (customControlPoints == null) { + customControlPoints = new ArrayList<>(); + return; + } + + customControlPoints.clear(); + } + + public void addCustomControlPoint(Vector point) { + ensureCustomControlPoints(); + + customControlPoints.add(new Vector( + point.getBlockX(), + point.getBlockY(), + point.getBlockZ() + )); + } + + public void clearCustomControlPoints() { + ensureCustomControlPoints(); + customControlPoints.clear(); + } + + public List getCustomControlPoints() { + ensureCustomControlPoints(); + return Collections.unmodifiableList(customControlPoints); + } + + public boolean hasEnoughCustomControlPoints() { + ensureCustomControlPoints(); + return customControlPoints.size() >= 2; + } + + private void ensureCustomControlPoints() { + if (customControlPoints == null) + customControlPoints = new ArrayList<>(); } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java deleted file mode 100644 index 5ffec594..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPath.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.path; - -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class RailPath { - - private final List centerPath; - - public RailPath(List centerPath) { - this.centerPath = new ArrayList<>(centerPath); - } - - public List getCenterPath() { - return Collections.unmodifiableList(centerPath); - } - - public int size() { - return centerPath.size(); - } - - public boolean isValid() { - return centerPath.size() >= 2; - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java deleted file mode 100644 index a7663baf..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java +++ /dev/null @@ -1,315 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.path; - -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.List; - -public class RailPathBuilder { - - private static final int MAX_CORNER_TRIM = 4; - private static final int MIN_CORNER_DISTANCE = 4; - - public RailPath build(List controlPoints) { - if (controlPoints == null || controlPoints.size() < 2) - return new RailPath(new ArrayList<>()); - - List normalizedControlPoints = removeOnlyConsecutiveDuplicates(controlPoints); - - if (normalizedControlPoints.size() < 2) - return new RailPath(new ArrayList<>()); - - List centerPath = createCurvedCenterPath(normalizedControlPoints); - - centerPath = repairGaps(centerPath); - centerPath = removeImmediateBacktracking(centerPath); - centerPath = removeOnlyConsecutiveDuplicates(centerPath); - - return new RailPath(centerPath); - } - - private List createCurvedCenterPath(List controlPoints) { - List path = new ArrayList<>(); - - addPointIfNew(path, toBlockVector(controlPoints.get(0))); - - if (controlPoints.size() == 2) { - appendEightDirectionalLine(path, controlPoints.get(0), controlPoints.get(1)); - return path; - } - - for (int i = 1; i < controlPoints.size() - 1; i++) { - Vector previous = toBlockVector(controlPoints.get(i - 1)); - Vector current = toBlockVector(controlPoints.get(i)); - Vector next = toBlockVector(controlPoints.get(i + 1)); - - if (!shouldCurveCorner(previous, current, next)) { - appendEightDirectionalLine(path, path.get(path.size() - 1), current); - continue; - } - - int trim = getCornerTrim(previous, current, next); - - if (trim <= 0) { - appendEightDirectionalLine(path, path.get(path.size() - 1), current); - continue; - } - - Vector beforeCorner = getPointBeforeCorner(previous, current, trim); - Vector afterCorner = getPointAfterCorner(current, next, trim); - - if (sameBlock(beforeCorner, current) || sameBlock(afterCorner, current)) { - appendEightDirectionalLine(path, path.get(path.size() - 1), current); - continue; - } - - appendEightDirectionalLine(path, path.get(path.size() - 1), beforeCorner); - appendQuadraticCurve(path, beforeCorner, current, afterCorner); - } - - appendEightDirectionalLine(path, path.get(path.size() - 1), controlPoints.get(controlPoints.size() - 1)); - - return path; - } - - private boolean shouldCurveCorner(Vector previous, Vector current, Vector next) { - Vector incoming = getDirection(previous, current); - Vector outgoing = getDirection(current, next); - - if (incoming == null || outgoing == null) - return false; - - if (sameDirection(incoming, outgoing)) - return false; - - if (oppositeDirection(incoming, outgoing)) - return false; - - int incomingLength = getChebyshevDistance(previous, current); - int outgoingLength = getChebyshevDistance(current, next); - - return incomingLength >= MIN_CORNER_DISTANCE && outgoingLength >= MIN_CORNER_DISTANCE; - } - - private int getCornerTrim(Vector previous, Vector current, Vector next) { - int incomingLength = getChebyshevDistance(previous, current); - int outgoingLength = getChebyshevDistance(current, next); - - int shortest = Math.min(incomingLength, outgoingLength); - - if (shortest < MIN_CORNER_DISTANCE) - return 0; - - return Math.max(1, Math.min(MAX_CORNER_TRIM, shortest / 3)); - } - - private Vector getPointBeforeCorner(Vector previous, Vector current, int trim) { - List incomingLine = createEightDirectionalLine(previous, current); - - int index = Math.max(0, incomingLine.size() - 1 - trim); - - return incomingLine.get(index); - } - - private Vector getPointAfterCorner(Vector current, Vector next, int trim) { - List outgoingLine = createEightDirectionalLine(current, next); - - int index = Math.min(outgoingLine.size() - 1, trim); - - return outgoingLine.get(index); - } - - private void appendQuadraticCurve(List path, Vector start, Vector control, Vector end) { - int firstDistance = getChebyshevDistance(start, control); - int secondDistance = getChebyshevDistance(control, end); - int samples = Math.max(6, (firstDistance + secondDistance) * 3); - - for (int sample = 1; sample <= samples; sample++) { - double t = sample / (double) samples; - double inverse = 1.0 - t; - - double x = inverse * inverse * start.getX() - + 2.0 * inverse * t * control.getX() - + t * t * end.getX(); - - double y = inverse * inverse * start.getY() - + 2.0 * inverse * t * control.getY() - + t * t * end.getY(); - - double z = inverse * inverse * start.getZ() - + 2.0 * inverse * t * control.getZ() - + t * t * end.getZ(); - - Vector point = toBlockVector(new Vector(x, y, z)); - - if (path.isEmpty()) { - addPointIfNew(path, point); - continue; - } - - Vector last = path.get(path.size() - 1); - - if (getChebyshevDistance(last, point) <= 1) { - addPointIfNew(path, point); - } else { - appendEightDirectionalLine(path, last, point); - } - } - } - - private List createEightDirectionalLine(Vector from, Vector to) { - List line = new ArrayList<>(); - appendEightDirectionalLine(line, from, to); - return line; - } - - private void appendEightDirectionalLine(List path, Vector from, Vector to) { - Vector start = toBlockVector(from); - Vector end = toBlockVector(to); - - int startX = start.getBlockX(); - int startY = start.getBlockY(); - int startZ = start.getBlockZ(); - - int endX = end.getBlockX(); - int endY = end.getBlockY(); - int endZ = end.getBlockZ(); - - int dx = endX - startX; - int dy = endY - startY; - int dz = endZ - startZ; - - int steps = Math.max(Math.abs(dx), Math.abs(dz)); - - if (steps == 0) { - addPointIfNew(path, new Vector(startX, startY, startZ)); - return; - } - - for (int step = 0; step <= steps; step++) { - double t = step / (double) steps; - - int x = (int) Math.round(startX + dx * t); - int y = (int) Math.round(startY + dy * t); - int z = (int) Math.round(startZ + dz * t); - - addPointIfNew(path, new Vector(x, y, z)); - } - } - - private List repairGaps(List path) { - if (path.size() < 2) - return path; - - List repaired = new ArrayList<>(); - repaired.add(path.get(0)); - - for (int i = 1; i < path.size(); i++) { - Vector previous = repaired.get(repaired.size() - 1); - Vector current = path.get(i); - - if (getChebyshevDistance(previous, current) <= 1) { - addPointIfNew(repaired, current); - continue; - } - - appendEightDirectionalLine(repaired, previous, current); - } - - return repaired; - } - - private List removeImmediateBacktracking(List path) { - if (path.size() < 3) - return path; - - List cleaned = new ArrayList<>(); - - for (Vector point : path) { - addPointIfNew(cleaned, point); - - while (cleaned.size() >= 3) { - Vector a = cleaned.get(cleaned.size() - 3); - Vector c = cleaned.get(cleaned.size() - 1); - - if (sameBlock(a, c)) { - cleaned.remove(cleaned.size() - 1); - cleaned.remove(cleaned.size() - 1); - continue; - } - - break; - } - } - - return cleaned; - } - - private List removeOnlyConsecutiveDuplicates(List path) { - List result = new ArrayList<>(); - - for (Vector point : path) - addPointIfNew(result, toBlockVector(point)); - - return result; - } - - private void addPointIfNew(List path, Vector point) { - Vector blockPoint = toBlockVector(point); - - if (path.isEmpty()) { - path.add(blockPoint); - return; - } - - Vector last = path.get(path.size() - 1); - - if (!sameBlock(last, blockPoint)) - path.add(blockPoint); - } - - private Vector getDirection(Vector from, Vector to) { - Vector blockFrom = toBlockVector(from); - Vector blockTo = toBlockVector(to); - - int dx = Integer.compare(blockTo.getBlockX() - blockFrom.getBlockX(), 0); - int dz = Integer.compare(blockTo.getBlockZ() - blockFrom.getBlockZ(), 0); - - if (dx == 0 && dz == 0) - return null; - - return new Vector(dx, 0, dz); - } - - private boolean sameDirection(Vector a, Vector b) { - return a.getBlockX() == b.getBlockX() - && a.getBlockZ() == b.getBlockZ(); - } - - private boolean oppositeDirection(Vector a, Vector b) { - return a.getBlockX() == -b.getBlockX() - && a.getBlockZ() == -b.getBlockZ(); - } - - private int getChebyshevDistance(Vector a, Vector b) { - Vector blockA = toBlockVector(a); - Vector blockB = toBlockVector(b); - - int dx = Math.abs(blockA.getBlockX() - blockB.getBlockX()); - int dz = Math.abs(blockA.getBlockZ() - blockB.getBlockZ()); - - return Math.max(dx, dz); - } - - private Vector toBlockVector(Vector vector) { - return new Vector( - vector.getBlockX(), - vector.getBlockY(), - vector.getBlockZ() - ); - } - - private boolean sameBlock(Vector a, Vector b) { - return toBlockVector(a).equals(toBlockVector(b)); - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java deleted file mode 100644 index 54080feb..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; - -import com.sk89q.worldedit.math.BlockVector3; -import lombok.Getter; -import org.bukkit.util.Vector; - -@Getter -public class RailBlockPlacement { - - private final BlockVector3 position; - private final RailBlockRole role; - private final Vector direction; - - public RailBlockPlacement(BlockVector3 position, RailBlockRole role, Vector direction) { - this.position = position; - this.role = role; - this.direction = direction == null ? new Vector(1, 0, 0) : direction; - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java deleted file mode 100644 index 97f161a7..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockRole.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; - -public enum RailBlockRole { - CENTER, - SIDE -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java deleted file mode 100644 index 588e1bc7..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailPlacementBuilder.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; - -import com.sk89q.worldedit.math.BlockVector3; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.side.RailOrientationResolver; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.side.RailSideBlock; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.side.RailSideBuilder; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -public class RailPlacementBuilder { - - private final RailSideBuilder sideBuilder; - private final RailOrientationResolver orientationResolver; - - public RailPlacementBuilder() { - this.sideBuilder = new RailSideBuilder(); - this.orientationResolver = new RailOrientationResolver(); - } - - public List buildPlacements(RailPath railPath) { - List placements = new ArrayList<>(); - - LinkedHashSet centerPositions = sideBuilder.getCenterPositions(railPath); - Map sideBlocks = sideBuilder.buildSideBlocks(railPath); - - addSidePlacements(placements, sideBlocks); - addCenterPlacements(placements, centerPositions); - - return placements; - } - - private void addSidePlacements(List placements, Map sideBlocks) { - for (RailSideBlock sideBlock : sideBlocks.values()) { - Vector direction = orientationResolver.resolveDirection(sideBlock, sideBlocks); - - placements.add(new RailBlockPlacement( - sideBlock.getPosition(), - RailBlockRole.SIDE, - direction - )); - } - } - - private void addCenterPlacements(List placements, LinkedHashSet centerPositions) { - for (BlockVector3 centerPosition : centerPositions) { - placements.add(new RailBlockPlacement( - centerPosition, - RailBlockRole.CENTER, - new Vector(1, 0, 0) - )); - } - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java deleted file mode 100644 index d96e0640..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.placement; - -import com.sk89q.worldedit.math.BlockVector3; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.types.RailType; -import net.buildtheearth.buildteamtools.modules.generator.model.History; -import net.buildtheearth.buildteamtools.modules.generator.model.Script; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class RailWorldEditPlacer { - - public List placeWithBukkitHistory( - Script script, - List placements, - RailType railType - ) { - Map blockMap = createBlockMap(placements, railType); - List changes = new ArrayList<>(); - - for (Map.Entry entry : blockMap.entrySet()) { - BlockVector3 position = entry.getKey(); - BlockData newData = entry.getValue(); - - Block block = script.getPlayer().getWorld().getBlockAt(position.x(), position.y(), position.z()); - BlockData oldData = block.getBlockData(); - - if (oldData.matches(newData)) - continue; - - changes.add(new History.BlockChange( - script.getPlayer().getWorld().getName(), - position.x(), - position.y(), - position.z(), - oldData.getAsString(), - newData.getAsString() - )); - - block.setBlockData(newData, false); - } - - return changes; - } - - private Map createBlockMap(List placements, RailType railType) { - Map blockMap = new LinkedHashMap<>(); - - for (RailBlockPlacement placement : placements) - blockMap.put(placement.getPosition(), railType.createBlockData(placement)); - - return blockMap; - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java deleted file mode 100644 index 3559228f..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/selection/RailSelectionPointReader.java +++ /dev/null @@ -1,230 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.selection; - -import com.alpsbte.alpslib.utils.GeneratorUtils; -import com.sk89q.worldedit.math.BlockVector2; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; -import com.sk89q.worldedit.regions.CuboidRegion; -import com.sk89q.worldedit.regions.Polygonal2DRegion; -import com.sk89q.worldedit.regions.Region; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.List; - -public class RailSelectionPointReader { - - private final Player player; - private final Region region; - - public RailSelectionPointReader(Player player, Region region) { - this.player = player; - this.region = region; - } - - public boolean isSupportedSelection() { - return region instanceof CuboidRegion - || region instanceof Polygonal2DRegion - || region instanceof ConvexPolyhedralRegion; - } - - public List readControlPoints() { - if (region instanceof CuboidRegion cuboidRegion) - return readCuboidPoints(cuboidRegion); - - if (region instanceof Polygonal2DRegion polygonalRegion) - return readPolygonalPoints(polygonalRegion); - - if (region instanceof ConvexPolyhedralRegion convexRegion) - return readConvexPoints(convexRegion); - - return new ArrayList<>(); - } - - private List readCuboidPoints(CuboidRegion cuboidRegion) { - List points = new ArrayList<>(); - - BlockVector3 pos1 = cuboidRegion.getPos1(); - BlockVector3 pos2 = cuboidRegion.getPos2(); - - Vector start = new Vector(pos1.x(), pos1.y(), pos1.z()); - Vector end = new Vector(pos2.x(), pos2.y(), pos2.z()); - - if (sameBlock(start, end)) { - start = new Vector( - cuboidRegion.getMinimumPoint().x(), - cuboidRegion.getMinimumPoint().y(), - cuboidRegion.getMinimumPoint().z() - ); - - end = new Vector( - cuboidRegion.getMaximumPoint().x(), - cuboidRegion.getMinimumPoint().y(), - cuboidRegion.getMaximumPoint().z() - ); - } - - points.add(start); - points.add(end); - - return points; - } - - private List readPolygonalPoints(Polygonal2DRegion polygonalRegion) { - List points = new ArrayList<>(); - - int minY = polygonalRegion.getMinimumY(); - int maxY = polygonalRegion.getMaximumY(); - - for (BlockVector2 point : polygonalRegion.getPoints()) { - int y = findBestY(point.x(), point.z(), minY, maxY); - points.add(new Vector(point.x(), y, point.z())); - } - - return removeOnlyConsecutiveDuplicates(points); - } - - private List readConvexPoints(ConvexPolyhedralRegion convexRegion) { - List points = new ArrayList<>(); - - for (BlockVector3 point : convexRegion.getVertices()) { - Vector vector = new Vector(point.x(), point.y(), point.z()); - - if (!containsBlock(points, vector)) - points.add(vector); - } - - if (points.size() < 2) { - List fallbackPoints = GeneratorUtils.getSelectionPointsFromRegion(region); - - if (fallbackPoints != null) { - for (Vector point : fallbackPoints) { - Vector blockPoint = toBlockVector(point); - - if (!containsBlock(points, blockPoint)) - points.add(blockPoint); - } - } - } - - return orderPointsAsPath(points); - } - - private int findBestY(int x, int z, int minY, int maxY) { - World world = player.getWorld(); - - int safeMinY = Math.max(world.getMinHeight(), Math.min(minY, maxY)); - int safeMaxY = Math.min(world.getMaxHeight() - 1, Math.max(minY, maxY)); - - for (int y = safeMaxY; y >= safeMinY; y--) { - Material material = world.getBlockAt(x, y, z).getType(); - - if (material.isSolid()) - return y; - } - - return safeMaxY; - } - - private List orderPointsAsPath(List points) { - List remaining = new ArrayList<>(); - List ordered = new ArrayList<>(); - - for (Vector point : points) { - if (!containsBlock(remaining, point)) - remaining.add(point); - } - - if (remaining.isEmpty()) - return ordered; - - Vector current = findStartPoint(remaining); - ordered.add(current); - remaining.remove(current); - - while (!remaining.isEmpty()) { - Vector next = findNearestPoint(current, remaining); - ordered.add(next); - remaining.remove(next); - current = next; - } - - return ordered; - } - - private Vector findStartPoint(List points) { - Vector best = points.get(0); - - for (Vector point : points) { - if (point.getBlockX() < best.getBlockX()) { - best = point; - continue; - } - - if (point.getBlockX() == best.getBlockX() && point.getBlockZ() < best.getBlockZ()) - best = point; - } - - return best; - } - - private Vector findNearestPoint(Vector from, List points) { - Vector best = points.get(0); - double bestDistance = distanceSquared2D(from, best); - - for (Vector point : points) { - double distance = distanceSquared2D(from, point); - - if (distance < bestDistance) { - best = point; - bestDistance = distance; - } - } - - return best; - } - - private double distanceSquared2D(Vector a, Vector b) { - double dx = a.getBlockX() - b.getBlockX(); - double dz = a.getBlockZ() - b.getBlockZ(); - - return dx * dx + dz * dz; - } - - private List removeOnlyConsecutiveDuplicates(List points) { - List result = new ArrayList<>(); - - for (Vector point : points) { - if (result.isEmpty() || !sameBlock(result.get(result.size() - 1), point)) - result.add(point); - } - - return result; - } - - private Vector toBlockVector(Vector vector) { - return new Vector( - Math.round(vector.getX()), - Math.round(vector.getY()), - Math.round(vector.getZ()) - ); - } - - private boolean containsBlock(List points, Vector target) { - for (Vector point : points) { - if (sameBlock(point, target)) - return true; - } - - return false; - } - - private boolean sameBlock(Vector a, Vector b) { - return a.getBlockX() == b.getBlockX() - && a.getBlockY() == b.getBlockY() - && a.getBlockZ() == b.getBlockZ(); - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java deleted file mode 100644 index 072f2b90..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailOrientationResolver.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.side; - -import com.sk89q.worldedit.math.BlockVector3; -import org.bukkit.util.Vector; - -import java.util.Map; - -public class RailOrientationResolver { - - public Vector resolveDirection(RailSideBlock sideBlock, Map sideBlocks) { - BlockVector3 position = sideBlock.getPosition(); - - boolean east = sideBlocks.containsKey(position.add(1, 0, 0)); - boolean west = sideBlocks.containsKey(position.add(-1, 0, 0)); - boolean south = sideBlocks.containsKey(position.add(0, 0, 1)); - boolean north = sideBlocks.containsKey(position.add(0, 0, -1)); - - boolean eastWest = east || west; - boolean northSouth = north || south; - - if (eastWest && !northSouth) - return getHorizontalDirection(east, west); - - if (!eastWest && northSouth) - return getVerticalDirection(south, north); - - if (eastWest) - return resolveCornerDirection(sideBlock, east, west, south, north); - - return sideBlock.getAverageDirection(); - } - - private Vector getHorizontalDirection(boolean east, boolean west) { - if (east && !west) - return new Vector(1, 0, 0); - - if (west && !east) - return new Vector(-1, 0, 0); - - return new Vector(1, 0, 0); - } - - private Vector getVerticalDirection(boolean south, boolean north) { - if (south && !north) - return new Vector(0, 0, 1); - - if (north && !south) - return new Vector(0, 0, -1); - - return new Vector(0, 0, 1); - } - - private Vector resolveCornerDirection( - RailSideBlock sideBlock, - boolean east, - boolean west, - boolean south, - boolean north - ) { - Vector average = sideBlock.getAverageDirection(); - - int avgX = average.getBlockX(); - int avgZ = average.getBlockZ(); - - if (Math.abs(avgX) > Math.abs(avgZ)) { - if (avgX > 0 && east) - return new Vector(1, 0, 0); - - if (avgX < 0 && west) - return new Vector(-1, 0, 0); - } - - if (Math.abs(avgZ) > Math.abs(avgX)) { - if (avgZ > 0 && south) - return new Vector(0, 0, 1); - - if (avgZ < 0 && north) - return new Vector(0, 0, -1); - } - - if (east) - return new Vector(1, 0, 0); - - if (west) - return new Vector(-1, 0, 0); - - if (south) - return new Vector(0, 0, 1); - - if (north) - return new Vector(0, 0, -1); - - return average; - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java deleted file mode 100644 index 343e46e7..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.side; - -import com.sk89q.worldedit.math.BlockVector3; -import lombok.Getter; -import org.bukkit.util.Vector; - -@Getter -public class RailSideBlock { - - private final BlockVector3 position; - private int directionX; - private int directionZ; - - public RailSideBlock(BlockVector3 position) { - this.position = position; - } - - public void addDirection(Vector direction) { - directionX += direction.getBlockX(); - directionZ += direction.getBlockZ(); - } - - public Vector getAverageDirection() { - int dx = Integer.compare(directionX, 0); - int dz = Integer.compare(directionZ, 0); - - if (dx == 0 && dz == 0) - return new Vector(1, 0, 0); - - return new Vector(dx, 0, dz); - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java deleted file mode 100644 index 7169ec98..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBuilder.java +++ /dev/null @@ -1,178 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.side; - -import com.sk89q.worldedit.math.BlockVector3; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.path.RailPath; -import org.bukkit.util.Vector; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -public class RailSideBuilder { - - public Map buildSideBlocks(RailPath railPath) { - Map sideBlocks = new LinkedHashMap<>(); - LinkedHashSet centerPositions = getCenterPositions(railPath); - - List centerPath = railPath.getCenterPath(); - - for (int i = 0; i < centerPath.size() - 1; i++) { - Vector from = centerPath.get(i); - Vector to = centerPath.get(i + 1); - Vector direction = getDirection(from, to); - - if (direction == null) - continue; - - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - if (dx != 0 && dz != 0) { - placeDiagonalEdgeSideBlocks(sideBlocks, centerPositions, from, dx, dz); - } else { - placeStraightEdgeSideBlocks(sideBlocks, centerPositions, from, to, direction); - } - } - - removeFloatingSideBlocks(sideBlocks); - - return sideBlocks; - } - - public LinkedHashSet getCenterPositions(RailPath railPath) { - LinkedHashSet centerPositions = new LinkedHashSet<>(); - - for (Vector center : railPath.getCenterPath()) - centerPositions.add(toBlockVector3(center)); - - return centerPositions; - } - - private void placeStraightEdgeSideBlocks( - Map sideBlocks, - LinkedHashSet centerPositions, - Vector from, - Vector to, - Vector direction - ) { - Vector leftOffset = getLeftOffset(direction); - Vector rightOffset = getRightOffset(direction); - - addSideBlock(sideBlocks, centerPositions, offset(from, leftOffset), direction); - addSideBlock(sideBlocks, centerPositions, offset(to, leftOffset), direction); - addSideBlock(sideBlocks, centerPositions, offset(from, rightOffset), direction); - addSideBlock(sideBlocks, centerPositions, offset(to, rightOffset), direction); - } - - private void placeDiagonalEdgeSideBlocks( - Map sideBlocks, - LinkedHashSet centerPositions, - Vector from, - int dx, - int dz - ) { - Vector direction = new Vector(dx, 0, dz); - - BlockVector3 horizontalSide = BlockVector3.at( - from.getBlockX() + dx, - from.getBlockY(), - from.getBlockZ() - ); - - BlockVector3 verticalSide = BlockVector3.at( - from.getBlockX(), - from.getBlockY(), - from.getBlockZ() + dz - ); - - addSideBlock(sideBlocks, centerPositions, horizontalSide, direction); - addSideBlock(sideBlocks, centerPositions, verticalSide, direction); - } - - private void addSideBlock( - Map sideBlocks, - LinkedHashSet centerPositions, - BlockVector3 position, - Vector direction - ) { - if (centerPositions.contains(position)) - return; - - RailSideBlock sideBlock = sideBlocks.computeIfAbsent(position, RailSideBlock::new); - sideBlock.addDirection(direction); - } - - private void removeFloatingSideBlocks(Map sideBlocks) { - if (sideBlocks.size() <= 2) - return; - - sideBlocks.entrySet().removeIf(entry -> countSideNeighbours(entry.getKey(), sideBlocks) == 0); - } - - private int countSideNeighbours(BlockVector3 position, Map sideBlocks) { - int count = 0; - - if (sideBlocks.containsKey(position.add(1, 0, 0))) - count++; - - if (sideBlocks.containsKey(position.add(-1, 0, 0))) - count++; - - if (sideBlocks.containsKey(position.add(0, 0, 1))) - count++; - - if (sideBlocks.containsKey(position.add(0, 0, -1))) - count++; - - if (sideBlocks.containsKey(position.add(1, 0, 1))) - count++; - - if (sideBlocks.containsKey(position.add(1, 0, -1))) - count++; - - if (sideBlocks.containsKey(position.add(-1, 0, 1))) - count++; - - if (sideBlocks.containsKey(position.add(-1, 0, -1))) - count++; - - return count; - } - - private Vector getLeftOffset(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - return new Vector(-dz, 0, dx); - } - - private Vector getRightOffset(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - return new Vector(dz, 0, -dx); - } - - private BlockVector3 offset(Vector center, Vector offset) { - return BlockVector3.at( - center.getBlockX() + offset.getBlockX(), - center.getBlockY(), - center.getBlockZ() + offset.getBlockZ() - ); - } - - private Vector getDirection(Vector from, Vector to) { - int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); - int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); - - if (dx == 0 && dz == 0) - return null; - - return new Vector(dx, 0, dz); - } - - private BlockVector3 toBlockVector3(Vector vector) { - return BlockVector3.at(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java deleted file mode 100644 index fe0d6983..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/DefaultRailType.java +++ /dev/null @@ -1,69 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.types; - -import com.sk89q.worldedit.math.BlockVector3; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockPlacement; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockRole; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Directional; -import org.bukkit.util.Vector; - -public class DefaultRailType implements RailType { - - private static final Material[] CENTER_MATERIALS = new Material[]{ - Material.DEAD_FIRE_CORAL_BLOCK, - Material.STONE, - Material.COBBLESTONE - }; - - private static final Material SIDE_MATERIAL = Material.ANVIL; - - @Override - public String getName() { - return "Default Railway"; - } - - @Override - public BlockData createBlockData(RailBlockPlacement placement) { - if (placement.getRole() == RailBlockRole.CENTER) - return createCenterBlockData(placement.getPosition()); - - return createSideBlockData(placement.getDirection()); - } - - private BlockData createCenterBlockData(BlockVector3 position) { - int index = Math.floorMod(position.x() * 31 + position.z() * 17, CENTER_MATERIALS.length); - return CENTER_MATERIALS[index].createBlockData(); - } - - private BlockData createSideBlockData(Vector direction) { - BlockData data = SIDE_MATERIAL.createBlockData(); - - if (data instanceof Directional directional) - directional.setFacing(toBlockFace(direction)); - - return data; - } - - private BlockFace toBlockFace(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); - - if (Math.abs(dx) >= Math.abs(dz)) { - if (dx > 0) - return BlockFace.EAST; - - if (dx < 0) - return BlockFace.WEST; - } - - if (dz > 0) - return BlockFace.SOUTH; - - if (dz < 0) - return BlockFace.NORTH; - - return BlockFace.EAST; - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java deleted file mode 100644 index e7c9b358..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/types/RailType.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail.types; - -import net.buildtheearth.buildteamtools.modules.generator.components.rail.placement.RailBlockPlacement; -import org.bukkit.block.data.BlockData; - -public interface RailType { - - String getName(); - - BlockData createBlockData(RailBlockPlacement placement); -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java index 1d4523ae..79494019 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java @@ -13,13 +13,19 @@ public class GeneratorListener implements Listener { + public static final String INTERNAL_GENERATOR_COMMAND_METADATA = "btt-internal-generator-command"; + @EventHandler public void onCommand(PlayerCommandPreprocessEvent e) { Player p = e.getPlayer(); - if(!GeneratorModule.getInstance().isGenerating(p)) + if (!GeneratorModule.getInstance().isGenerating(p)) + return; + + if (!e.getMessage().startsWith("//")) return; - if(!e.getMessage().startsWith("//")) + + if (p.hasMetadata(INTERNAL_GENERATOR_COMMAND_METADATA)) return; e.setCancelled(true); @@ -28,18 +34,20 @@ public void onCommand(PlayerCommandPreprocessEvent e) { } @EventHandler(priority = EventPriority.LOWEST) - public void onInteract(PlayerInteractEvent e){ + public void onInteract(PlayerInteractEvent e) { Player p = e.getPlayer(); - if(!GeneratorModule.getInstance().isGenerating(p)) + if (!GeneratorModule.getInstance().isGenerating(p)) return; - if(e.getItem() == null) + + if (e.getItem() == null) return; - if(e.getItem().getType() != Material.WOODEN_AXE) + + if (e.getItem().getType() != Material.WOODEN_AXE) return; e.setCancelled(true); p.playSound(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F); ChatHelper.sendErrorMessage(p, "You can't use WorldEdit while generating a structure."); } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index 4ac228fd..e2fa937e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -11,13 +11,16 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import lombok.Getter; +import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.common.CommonModule; +import net.buildtheearth.buildteamtools.modules.generator.listeners.GeneratorListener; import net.buildtheearth.buildteamtools.utils.MenuItems; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.util.Vector; import java.util.Arrays; @@ -84,48 +87,46 @@ public Command(Script script, Block[][][] blocks) { } /** Processes the commands from the command queue to prevent the server from freezing. */ - public void tick(){ - if(operations.isEmpty()) { - if(!isFinished) + public void tick() { + if (operations.isEmpty()) { + if (!isFinished) finish(); return; } percentage = (int) Math.round((double) (totalCommands - operations.size()) / (double) totalCommands * 100); - if(!breakPointActive &&! threadActive) + if (!breakPointActive && !threadActive) player.sendActionBar("§a§lGenerator Progress: §7" + percentage + "%"); else player.sendActionBar("§e§lGenerator Progress: §7" + percentage + "%"); - if(threadActive) + if (threadActive) return; - - // Process commands in batches of MAX_COMMANDS_PER_SERVER_TICK - for(int i = 0; i < MAX_COMMANDS_PER_SERVER_TICK;){ - if(operations.isEmpty()){ - if(!isFinished) + for (int i = 0; i < MAX_COMMANDS_PER_SERVER_TICK;) { + if (operations.isEmpty()) { + if (!isFinished) finish(); break; } - Operation command = operations.get(0); processOperation(command); - if(breakPointActive || threadActive) + if (breakPointActive || threadActive) break; // Skip WorldEdit commands that take no time to execute - if(command.getOperationType() == Operation.OperationType.COMMAND){ + if (command.getOperationType() == Operation.OperationType.COMMAND) { String commandString = (String) command.getValues().get(0); - if(commandString.startsWith("//gmask") - || commandString.startsWith("//mask") - || commandString.startsWith("//pos") - || commandString.startsWith("//sel") - || commandString.startsWith("//expand")) + + if (commandString.startsWith("//gmask") + || commandString.startsWith("//mask") + || commandString.startsWith("//pos") + || commandString.startsWith("//sel") + || commandString.startsWith("//expand")) continue; } @@ -134,7 +135,7 @@ public void tick(){ } /** Processes a single command. */ - public void processOperation(Operation operation){ + public void processOperation(Operation operation) { CompletableFuture future = null; try { @@ -145,7 +146,7 @@ public void processOperation(Operation operation){ if (command.contains("%%XYZ/")) command = convertXYZ(command); - player.chat(command); + runInternalGeneratorCommand(command); break; case BREAKPOINT: @@ -171,7 +172,7 @@ public void processOperation(Operation operation){ oldBlockData = block.getBlockData(); BlockType blockType = BlockTypes.BARRIER; - if(blockType == null) + if (blockType == null) break; GeneratorUtils.createCuboidSelection(getPlayer(), point, point); @@ -225,33 +226,53 @@ public void processOperation(Operation operation){ GeneratorUtils.expandSelection(localSession, (Vector) operation.get(0)); break; } - }catch (Exception e){ - if(operation != null) + } catch (Exception e) { + if (operation != null) ChatHelper.logError("Error while processing command: " + operation.getOperationType() + " - " + operation.getValuesAsString()); else ChatHelper.logError("Error while processing command."); + e.printStackTrace(); } - if(future != null){ + if (future != null) { threadActive = true; + // Ensure we clear threadActive and remove the operation regardless of success or exception future.whenComplete((v, ex) -> { threadActive = false; + if (ex != null) { ChatHelper.logError("Async operation failed: " + operation.getOperationType() + " - " + operation.getValuesAsString()); ex.printStackTrace(); } + // Remove the processed operation from the queue operations.remove(0); }); - - }else if(!breakPointActive) + } else if (!breakPointActive) { operations.remove(0); + } + } + + private void runInternalGeneratorCommand(String command) { + player.setMetadata( + GeneratorListener.INTERNAL_GENERATOR_COMMAND_METADATA, + new FixedMetadataValue(BuildTeamTools.getInstance(), true) + ); + + try { + player.chat(command); + } finally { + player.removeMetadata( + GeneratorListener.INTERNAL_GENERATOR_COMMAND_METADATA, + BuildTeamTools.getInstance() + ); + } } /** Converts the XYZ coordinates in a command to the highest block at that location while skipping certain blocks. */ - public String convertXYZ(String command){ + public String convertXYZ(String command) { String xyz = command.split("%%XYZ/")[1].split("/%%")[0]; String[] xyzSplit = xyz.split(","); @@ -261,24 +282,24 @@ public String convertXYZ(String command){ int maxHeight = y; - if(blocks != null) + if (blocks != null) maxHeight = GeneratorUtils.getMaxHeight(blocks, x, z, MenuItems.getIgnoredMaterials()); - if(maxHeight == 0) + + if (maxHeight == 0) maxHeight = y; String commandSuffix = ""; - if(command.split("/%%").length > 1) + + if (command.split("/%%").length > 1) commandSuffix = command.split("/%%")[1]; return command.split("%%XYZ/")[0] + x + "," + maxHeight + "," + z + commandSuffix; } - - /** Called when the command queue is finished. */ - public void finish(){ + public void finish() { player.sendActionBar("§a§lGenerator Progress: §7100%"); isFinished = true; generatorComponent.sendSuccessMessage(player); } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java index d77152d2..b4b429f1 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java @@ -152,6 +152,14 @@ public void sendSuccessMessage(Player p) { .deserialize(BuildTeamTools.PREFIX + type + "§a successfully §7generated."); } + /** + * Conversion: + * Command: /gen house -w 123:12 -r 456:78 + * args: ["-w", "123:12", "-r", "456:78"] + * HouseSettings: + * WALL_COLOR: 123:12 + * ROOF_TYPE: 456:78 + */ protected void convertArgsToSettings(Player p, String[] args) { for (String flag : GeneratorUtils.convertArgsToFlags(args)) { String[] flagAndValue = GeneratorUtils.convertToFlagAndValue(flag, p); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java index 0a5dc7a0..d3f2e5b1 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java @@ -106,10 +106,23 @@ protected BlockState getSlab(BlockType blockType, String type){ * * @param command The command to add */ - public void createCommand(String command){ + public void createCommand(String command) { operations.add(new Operation(command)); } + /** + * Adds a command to the operation list and counts it as one undoable change. + * + * Use this for generator commands that actually modify blocks, such as //line, + * //set or //replace. Do not use this for pure selection commands. + * + * @param command The command to add + */ + public void createUndoableCommand(String command) { + operations.add(new Operation(command)); + changes++; + } + /** * This method is used to create a break point in the script. * When this command is reached, the script will pause and wait for the Operation to finish. @@ -326,7 +339,7 @@ public void replaceBlocksWithMask(String mask, BlockState from, BlockState to) { */ public void drawCurveWithMask(List masks, List points, BlockState[] blocks, boolean matchElevation) { operations.add(new Operation(Operation.OperationType.DRAW_CURVE_WITH_MASKS, masks.toArray(new String[0]), points.toArray(new Vector[0]), blocks, matchElevation)); - changes += masks.size(); + changes += Math.max(1, masks.size()); } public void drawCurveWithMask(List masks, List points, XMaterial[] blocks, boolean matchElevation) { @@ -363,7 +376,7 @@ public void drawCurve(List points, XMaterial block, boolean matchElevati */ public void drawPolyLineWithMask(List masks, List points, BlockState[] blocks, boolean matchElevation, boolean connectLineEnds) { operations.add(new Operation(Operation.OperationType.DRAW_POLY_LINE_WITH_MASKS, masks.toArray(new String[0]), points.toArray(new Vector[0]), blocks, matchElevation, connectLineEnds)); - changes += masks.size(); + changes += Math.max(1, masks.size()); } public void drawPolyLineWithMask(List masks, List points, XMaterial[] blocks, boolean matchElevation, boolean connectLineEnds) { @@ -402,7 +415,7 @@ public void drawPolyLine(List points, XMaterial block, boolean matchElev */ public void drawLineWithMask(List masks, Vector point1, Vector point2, BlockState[] blocks, boolean matchElevation) { operations.add(new Operation(Operation.OperationType.DRAW_LINE_WITH_MASKS, masks.toArray(new String[0]), point1, point2, blocks, matchElevation)); - changes += masks.size(); + changes += Math.max(1, masks.size()); } public void drawLineWithMask(List masks, Vector point1, Vector point2, XMaterial[] blocks, boolean matchElevation) { @@ -426,4 +439,8 @@ public void drawLine(Vector point1, Vector point2, XMaterial[] blocks, boolean m public void drawLine(Vector point1, Vector point2, XMaterial block, boolean matchElevation) { drawLineWithMask(new ArrayList<>(), point1, point2, new XMaterial[]{block}, matchElevation); } + + public void drawLine(Vector point1, Vector point2, BlockState[] blocks, boolean matchElevation) { + drawLineWithMask(new ArrayList<>(), point1, point2, blocks, matchElevation); + } } From 423e31ecbb5c2ae59e8177eb9c34a769587e461f Mon Sep 17 00:00:00 2001 From: Jasupa Date: Sun, 24 May 2026 18:46:35 +0200 Subject: [PATCH 10/18] refractor: - Made sure the existing Generator Library from alplibs is used directly for the rail generator where possible feature: - The rail generator now uses real worldedit commands and is thus much smoother and faster. - The path curves are way smoother instead of blocky. --- .../generator/components/rail/Rail.java | 74 +- .../components/rail/RailScripts.java | 1272 ++++++++++++----- .../modules/generator/model/Command.java | 41 +- .../modules/generator/model/Operation.java | 1 + .../modules/generator/model/Script.java | 12 + 5 files changed, 1050 insertions(+), 350 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index 2f921956..c891a060 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -14,6 +14,8 @@ public class Rail extends GeneratorComponent { private static final int TARGET_BLOCK_RANGE = 200; + private static final int CUSTOM_SELECTION_PADDING = 4; + private static final int CUSTOM_SELECTION_VERTICAL_PADDING = 12; public Rail() { super(GeneratorType.RAILWAY); @@ -21,7 +23,8 @@ public Rail() { @Override public void analyzeCommand(Player player, String[] args) { - addPlayerSetting(player); + if (getRailSettings(player) == null) + addPlayerSetting(player); if (args.length >= 2) { String subCommand = args[1].toLowerCase(); @@ -76,13 +79,78 @@ public void generate(Player player) { RailSettings settings = getRailSettings(player); if (settings != null && settings.hasEnoughCustomControlPoints()) { - new RailScripts(player, this, new ArrayList<>(settings.getCustomControlPoints())); + List customControlPoints = new ArrayList<>(settings.getCustomControlPoints()); + createCustomPointSelection(player, customControlPoints); + new RailScripts(player, this, customControlPoints); return; } new RailScripts(player, this); } + private void createCustomPointSelection(Player player, List customControlPoints) { + List selectionLine = new ArrayList<>(customControlPoints); + + if (selectionLine.size() >= 2) + selectionLine = GeneratorUtils.extendPolyLine(selectionLine); + + List selectionPoints = GeneratorUtils.shiftPoints(selectionLine, CUSTOM_SELECTION_PADDING, true); + + if (selectionPoints == null || selectionPoints.size() < 3) { + Vector[] bounds = getCustomPointBounds(customControlPoints); + GeneratorUtils.createCuboidSelection(player, bounds[0], bounds[1]); + return; + } + + GeneratorUtils.createPolySelection( + player, + selectionPoints, + getCustomSelectionMinY(player, customControlPoints), + getCustomSelectionMaxY(player, customControlPoints) + ); + } + + private Vector[] getCustomPointBounds(List customControlPoints) { + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int minZ = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + int maxZ = Integer.MIN_VALUE; + + for (Vector point : customControlPoints) { + minX = Math.min(minX, point.getBlockX() - CUSTOM_SELECTION_PADDING); + minY = Math.min(minY, point.getBlockY() - CUSTOM_SELECTION_VERTICAL_PADDING); + minZ = Math.min(minZ, point.getBlockZ() - CUSTOM_SELECTION_PADDING); + maxX = Math.max(maxX, point.getBlockX() + CUSTOM_SELECTION_PADDING); + maxY = Math.max(maxY, point.getBlockY() + CUSTOM_SELECTION_VERTICAL_PADDING); + maxZ = Math.max(maxZ, point.getBlockZ() + CUSTOM_SELECTION_PADDING); + } + + return new Vector[]{ + new Vector(minX, minY, minZ), + new Vector(maxX, maxY, maxZ) + }; + } + + private int getCustomSelectionMinY(Player player, List customControlPoints) { + int minY = Integer.MAX_VALUE; + + for (Vector point : customControlPoints) + minY = Math.min(minY, point.getBlockY()); + + return Math.max(player.getWorld().getMinHeight(), minY - CUSTOM_SELECTION_VERTICAL_PADDING); + } + + private int getCustomSelectionMaxY(Player player, List customControlPoints) { + int maxY = Integer.MIN_VALUE; + + for (Vector point : customControlPoints) + maxY = Math.max(maxY, point.getBlockY()); + + return Math.min(player.getWorld().getMaxHeight() - 1, maxY + CUSTOM_SELECTION_VERTICAL_PADDING); + } + private void addPoint(Player player) { RailSettings settings = getRailSettings(player); @@ -150,4 +218,4 @@ private void listPoints(Player player) { private RailSettings getRailSettings(Player player) { return (RailSettings) getPlayerSettings().get(player.getUniqueId()); } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index d9d65efe..88b6fe5c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -3,31 +3,34 @@ import com.alpsbte.alpslib.utils.GeneratorUtils; import com.cryptomorin.xseries.XMaterial; import com.fastasyncworldedit.core.registry.state.PropertyKey; -import com.sk89q.worldedit.math.BlockVector2; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; -import com.sk89q.worldedit.regions.CuboidRegion; -import com.sk89q.worldedit.regions.Polygonal2DRegion; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypes; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.Script; -import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; public class RailScripts extends Script { private static final int MAX_CONTROL_POINTS = 250; private static final int MAX_PATH_POINTS = 20_000; private static final int MAX_BLOCK_PLACEMENTS = 100_000; + private static final int SELECTION_PADDING = 4; + private static final int SELECTION_VERTICAL_PADDING = 12; + private static final int PREPARE_SELECTION_EXPANSION = 8; + private static final int INVALID_SIDE_PAIR_PENALTY = 1_000_000; + private static final int CENTER_COLLISION_PENALTY = 100_000; + private static final int FALLBACK_SIDE_PAIR_PENALTY = 1_000; private static final XMaterial[] CENTER_MATERIALS = new XMaterial[]{ XMaterial.DEAD_FIRE_CORAL_BLOCK, @@ -37,8 +40,10 @@ public class RailScripts extends Script { private final List customControlPoints; - private List controlPoints; - private List centerPath; + private Block[][][] blocks; + private List controlPoints = new ArrayList<>(); + private List centerPath = new ArrayList<>(); + private List railSelectionPoints = new ArrayList<>(); public RailScripts(Player player, GeneratorComponent generatorComponent) { this(player, generatorComponent, null); @@ -48,24 +53,45 @@ public RailScripts(Player player, GeneratorComponent generatorComponent, List { + prepareSession(); + railScript_v_2_0(); + }); thread.start(); } - private void railScript_v_1_0() { - controlPoints = getControlPoints(); + private void prepareSession() { + controlPoints = sanitizePoints(getControlPoints()); - if (controlPoints.size() < 2) { - getPlayer().sendMessage("§cRail Generator needs at least two points."); + if (!hasValidControlPoints()) return; - } - if (controlPoints.size() > MAX_CONTROL_POINTS) { - getPlayer().sendMessage("§cRail Generator has too many points. Please use fewer points."); - return; - } + railSelectionPoints = createRailSelectionPoints(controlPoints); + GeneratorUtils.createPolySelection( + getPlayer(), + railSelectionPoints, + getSelectionMinY(controlPoints), + getSelectionMaxY(controlPoints) + ); - centerPath = createEightDirectionalPath(controlPoints); + blocks = GeneratorUtils.prepareScriptSession( + localSession, + actor, + getPlayer(), + weWorld, + PREPARE_SELECTION_EXPANSION, + true, + false, + false + ); + + snapMissingControlPointHeightsToTerrain(controlPoints); + centerPath = createShortestPath(controlPoints); + } + + private void railScript_v_2_0() { + if (!hasValidControlPoints()) + return; if (centerPath.size() < 2) { getPlayer().sendMessage("§cRail Generator could not create a valid rail path."); @@ -77,490 +103,1001 @@ private void railScript_v_1_0() { return; } - Map sideBlocks = buildSideBlocks(centerPath); - Map blocksToPlace = buildBlockMap(centerPath, sideBlocks); + Map railBlocks = buildRailBlocks(centerPath); - if (blocksToPlace.size() > MAX_BLOCK_PLACEMENTS) { + if (railBlocks.size() > MAX_BLOCK_PLACEMENTS) { getPlayer().sendMessage("§cRail Generator would place too many blocks. Please use a smaller selection."); return; } - for (Map.Entry entry : blocksToPlace.entrySet()) { - PositionKey key = entry.getKey(); - BlockState blockState = entry.getValue(); + setBlockStatesAtPositions( + railBlocks.keySet().stream().map(PositionKey::toVector).toList(), + new ArrayList<>(railBlocks.values()) + ); - if (blockState == null) - continue; + finish(blocks, getRestoreSelectionPoints()); + } - Vector position = key.toVector(); + private boolean hasValidControlPoints() { + if (controlPoints.size() < 2) { + getPlayer().sendMessage("§cRail Generator needs at least two points."); + return false; + } - createCuboidSelection(position, position); - replaceBlocks((BlockState[]) null, blockState); + if (controlPoints.size() > MAX_CONTROL_POINTS) { + getPlayer().sendMessage("§cRail Generator has too many points. Please use fewer points."); + return false; } - finish(null, getCuboidRestoreSelection()); + return true; } private List getControlPoints() { if (customControlPoints != null && customControlPoints.size() >= 2) return copyPoints(customControlPoints); - if (getRegion() instanceof CuboidRegion cuboidRegion) - return getCuboidControlPoints(cuboidRegion); + List selectionPoints = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - if (getRegion() instanceof ConvexPolyhedralRegion convexRegion) - return getConvexControlPoints(convexRegion); + if (selectionPoints == null) + return Collections.emptyList(); - if (getRegion() instanceof Polygonal2DRegion polygonalRegion) - return getPolygonalControlPoints(polygonalRegion); - - return new ArrayList<>(GeneratorUtils.getSelectionPointsFromRegion(getRegion())); + return copyPoints(selectionPoints); } - private List copyPoints(List points) { - List copiedPoints = new ArrayList<>(); + private List createRailSelectionPoints(List points) { + List selectionLine = new ArrayList<>(points); - for (Vector point : points) - copiedPoints.add(toBlockVector(point)); + if (selectionLine.size() >= 2) + selectionLine = GeneratorUtils.extendPolyLine(selectionLine); - return copiedPoints; + List shiftedPoints = GeneratorUtils.shiftPoints(selectionLine, SELECTION_PADDING, true); + + if (shiftedPoints == null || shiftedPoints.size() < 3) + return createBoundsSelectionPoints(points); + + return shiftedPoints; } - private List getCuboidControlPoints(CuboidRegion cuboidRegion) { - List points = new ArrayList<>(); + private List createShortestPath(List points) { + List path = new ArrayList<>(); + + if (points.isEmpty()) + return path; - BlockVector3 pos1 = cuboidRegion.getPos1(); - BlockVector3 pos2 = cuboidRegion.getPos2(); + addPointIfNew(path, points.get(0)); - points.add(new Vector(pos1.x(), pos1.y(), pos1.z())); - points.add(new Vector(pos2.x(), pos2.y(), pos2.z())); + for (int index = 0; index < points.size() - 1; index++) + appendShortestLine(path, points.get(index), points.get(index + 1)); - return points; + return path; } - private List getConvexControlPoints(ConvexPolyhedralRegion convexRegion) { - List points = new ArrayList<>(); + private void appendShortestLine(List path, Vector start, Vector end) { + int startX = start.getBlockX(); + int startY = start.getBlockY(); + int startZ = start.getBlockZ(); + int deltaX = end.getBlockX() - startX; + int deltaY = end.getBlockY() - startY; + int deltaZ = end.getBlockZ() - startZ; + + int horizontalSteps = Math.max(Math.abs(deltaX), Math.abs(deltaZ)); + + if (horizontalSteps == 0) { + replaceLastPoint(path, end); + return; + } - for (BlockVector3 point : convexRegion.getVertices()) { - points.add(new Vector( - point.x(), - point.y(), - point.z() + for (int step = 1; step <= horizontalSteps; step++) { + double progress = step / (double) horizontalSteps; + addPointIfNew(path, new Vector( + startX + (int) Math.round(deltaX * progress), + startY + (int) Math.round(deltaY * progress), + startZ + (int) Math.round(deltaZ * progress) )); } + } + + private Map buildRailBlocks(List path) { + Map blockMap = new LinkedHashMap<>(); + Set centerPositions = getCenterPositions(path); + Set centerColumns = getCenterColumns(path); + Map sideBlocks = buildSideBlocks(path, centerPositions, centerColumns); + + for (RailSideBlock sideBlock : sideBlocks.values()) { + if (centerColumns.contains(ColumnKey.from(sideBlock.key()))) + continue; + + blockMap.put(sideBlock.key(), createAnvilBlockState(resolveSideBlockFacing(sideBlock, sideBlocks))); + } - return points; + for (Vector center : path) + blockMap.put(PositionKey.from(center), createCenterBlockState(center)); + + return blockMap; } - private List getPolygonalControlPoints(Polygonal2DRegion polygonalRegion) { - List points = new ArrayList<>(); + private List buildSidePairs(List path, Set centerPositions) { + List sidePairs = new ArrayList<>(); + RailSidePair previousPair = null; - for (BlockVector2 point : polygonalRegion.getPoints()) { - int y = getRailYFromWorld( - point.x(), - point.z(), - polygonalRegion.getMinimumY(), - polygonalRegion.getMaximumY() - ); + for (int index = 0; index < path.size(); index++) { + List candidates = getSidePairCandidates(path, centerPositions, index); + RailSidePair selectedPair = selectSidePair(previousPair, candidates, centerPositions); - points.add(new Vector( - point.x(), - y, - point.z() - )); + sidePairs.add(selectedPair); + previousPair = selectedPair; } - return points; + return sidePairs; } - private int getRailYFromWorld(int x, int z, int minimumY, int maximumY) { - for (int y = maximumY; y >= minimumY; y--) { - Block block = getPlayer().getWorld().getBlockAt(x, y, z); - Material material = block.getType(); + private List getSidePairCandidates( + List path, + Set centerPositions, + int index + ) { + List candidates = new ArrayList<>(); + Vector center = path.get(index); + RailDirection previousDirection = getHorizontalDirection(path, index - 1, index); + RailDirection nextDirection = getHorizontalDirection(path, index, index + 1); + + addSidePairIfNew(candidates, createSidePair(center, getRailDirection(path, index))); + + if (previousDirection != null) + addSidePairIfNew(candidates, createSidePair(center, previousDirection)); + + if (nextDirection != null) + addSidePairIfNew(candidates, createSidePair(center, nextDirection)); + + if (previousDirection != null && nextDirection != null) { + addBlendedSidePair(candidates, center, previousDirection, nextDirection); + addCornerSidePairs(candidates, center, centerPositions, previousDirection, nextDirection); + } + + addFallbackSidePairs(candidates, center, getRailDirection(path, index), centerPositions); + + return candidates; + } + + private RailSidePair selectSidePair( + RailSidePair previousPair, + List candidates, + Set centerPositions + ) { + RailSidePair selectedPair = candidates.get(0); + int selectedScore = Integer.MAX_VALUE; - if (!material.isAir() - && material != Material.WATER - && material != Material.LAVA) { - return y + 1; + for (RailSidePair candidate : candidates) { + RailSidePair[] orientations = new RailSidePair[]{candidate, candidate.reversed()}; + + for (RailSidePair orientedCandidate : orientations) { + int score = getSidePairScore(previousPair, orientedCandidate, centerPositions); + + if (score < selectedScore) { + selectedScore = score; + selectedPair = orientedCandidate; + } } } - return minimumY; + return selectedPair; } - private List createEightDirectionalPath(List points) { - List path = new ArrayList<>(); + private int getSidePairScore( + RailSidePair previousPair, + RailSidePair candidate, + Set centerPositions + ) { + int score = getCenterCollisionPenalty(candidate, centerPositions); - if (points == null || points.isEmpty()) - return path; + if (previousPair != null) + score += getSidePairTransitionDistance(previousPair, candidate); - addPointIfNew(path, toBlockVector(points.get(0))); + score += candidate.fallbackPenalty(); - for (int index = 0; index < points.size() - 1; index++) { - Vector start = toBlockVector(points.get(index)); - Vector end = toBlockVector(points.get(index + 1)); + return score; + } - appendEightDirectionalLine(path, start, end); - } + private int getCenterCollisionPenalty(RailSidePair candidate, Set centerPositions) { + if (candidate.hasDuplicatePosition()) + return INVALID_SIDE_PAIR_PENALTY; - return path; + int penalty = 0; + + if (isCenterCollision(candidate.first(), centerPositions)) + penalty += CENTER_COLLISION_PENALTY; + + if (isCenterCollision(candidate.second(), centerPositions)) + penalty += CENTER_COLLISION_PENALTY; + + return penalty; } - private void appendEightDirectionalLine(List path, Vector start, Vector end) { - int x = start.getBlockX(); - int z = start.getBlockZ(); + private boolean isCenterCollision(RailSidePlacement sidePlacement, Set centerPositions) { + return centerPositions.contains(PositionKey.from(sidePlacement.position())); + } - int startY = start.getBlockY(); + private boolean isCenterColumnCollision( + PositionKey key, + Set centerPositions, + Set centerColumns + ) { + return centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key)); + } - int endX = end.getBlockX(); - int endY = end.getBlockY(); - int endZ = end.getBlockZ(); + private int getSidePairTransitionDistance(RailSidePair previousPair, RailSidePair candidate) { + return getChebyshevDistance(previousPair.first().position(), candidate.first().position()) + + getChebyshevDistance(previousPair.second().position(), candidate.second().position()); + } - int totalHorizontalSteps = Math.max( - Math.abs(endX - x), - Math.abs(endZ - z) + private int getChebyshevDistance(Vector first, Vector second) { + return Math.max( + Math.max( + Math.abs(second.getBlockX() - first.getBlockX()), + Math.abs(second.getBlockY() - first.getBlockY()) + ), + Math.abs(second.getBlockZ() - first.getBlockZ()) ); + } - if (totalHorizontalSteps == 0) { - addPointIfNew(path, new Vector(endX, endY, endZ)); - return; + private Map buildSideBlocks( + List path, + Set centerPositions, + Set centerColumns + ) { + Map exactSideBlocks = new LinkedHashMap<>(); + + for (List sideLane : buildSideLanes(path)) { + for (RailSidePlacement sidePlacement : sideLane) + addExactSideBlock(exactSideBlocks, centerPositions, centerColumns, sidePlacement); } - int currentStep = 0; + List sidePairs = buildSidePairs(path, centerPositions); - while (x != endX || z != endZ) { - if (x < endX) - x++; - else if (x > endX) - x--; + for (RailSidePair sidePair : sidePairs) { + addExactSideBlock(exactSideBlocks, centerPositions, centerColumns, sidePair.first()); + addExactSideBlock(exactSideBlocks, centerPositions, centerColumns, sidePair.second()); + } - if (z < endZ) - z++; - else if (z > endZ) - z--; + Map sideBlocks = selectBestSideBlockPerColumn(exactSideBlocks, centerPositions); + repairMissingSideBlocks(path, sidePairs, sideBlocks, centerPositions, centerColumns); - currentStep++; + return sideBlocks; + } - double progress = currentStep / (double) totalHorizontalSteps; - int y = (int) Math.round(startY + (endY - startY) * progress); + private List> buildSideLanes(List path) { + List firstLane = new ArrayList<>(); + List secondLane = new ArrayList<>(); - addPointIfNew(path, new Vector(x, y, z)); - } - } + for (int index = 0; index < path.size() - 1; index++) { + RailDirection direction = getHorizontalDirection(path, index, index + 1); - private Map buildSideBlocks(List centerPath) { - Map sideBlocks = new LinkedHashMap<>(); - Map centerBlocks = createCenterBlockMap(centerPath); + if (direction == null) + continue; + + RailSidePair startPair = createSidePair(path.get(index), direction); + RailSidePair endPair = createSidePair(path.get(index + 1), direction); - for (int index = 0; index < centerPath.size(); index++) { - Vector center = centerPath.get(index); - List directions = getDirectionsForCenterPoint(centerPath, index); + boolean reverseSegment = !firstLane.isEmpty() && shouldReverseSideSegment(firstLane, secondLane, startPair); - for (Vector direction : directions) - addSideBlocksForDirection(sideBlocks, centerBlocks, center, direction); + if (reverseSegment) { + startPair = startPair.reversed(); + endPair = endPair.reversed(); + } + + appendSideLaneSegment(firstLane, startPair.first(), endPair.first()); + appendSideLaneSegment(secondLane, startPair.second(), endPair.second()); } - return sideBlocks; + return List.of(firstLane, secondLane); } - private Map createCenterBlockMap(List centerPath) { - Map centerBlocks = new LinkedHashMap<>(); + private boolean shouldReverseSideSegment( + List firstLane, + List secondLane, + RailSidePair startPair + ) { + RailSidePlacement firstEnd = firstLane.get(firstLane.size() - 1); + RailSidePlacement secondEnd = secondLane.get(secondLane.size() - 1); + int normalDistance = getChebyshevDistance(firstEnd.position(), startPair.first().position()) + + getChebyshevDistance(secondEnd.position(), startPair.second().position()); + int reversedDistance = getChebyshevDistance(firstEnd.position(), startPair.second().position()) + + getChebyshevDistance(secondEnd.position(), startPair.first().position()); + + return reversedDistance < normalDistance; + } - for (Vector center : centerPath) - centerBlocks.put(PositionKey.from(center), center); + private void appendSideLaneSegment( + List lane, + RailSidePlacement start, + RailSidePlacement end + ) { + if (lane.isEmpty()) { + lane.add(start); + } else { + appendSideLine(lane, lane.get(lane.size() - 1), start); + } - return centerBlocks; + appendSideLine(lane, lane.get(lane.size() - 1), end); } - private List getDirectionsForCenterPoint(List path, int index) { - List directions = new ArrayList<>(); + private void appendSideLine( + List lane, + RailSidePlacement start, + RailSidePlacement end + ) { + Vector startPosition = start.position(); + Vector endPosition = end.position(); + int deltaX = endPosition.getBlockX() - startPosition.getBlockX(); + int deltaY = endPosition.getBlockY() - startPosition.getBlockY(); + int deltaZ = endPosition.getBlockZ() - startPosition.getBlockZ(); + int horizontalSteps = Math.max(Math.abs(deltaX), Math.abs(deltaZ)); + Direction facing = getLineFacing(deltaX, deltaZ, end.facing()); + + if (horizontalSteps == 0) { + replaceLastSidePlacement(lane, new RailSidePlacement(endPosition, facing)); + return; + } - if (index > 0) - addDirectionIfNew(directions, getHorizontalDirection(path.get(index - 1), path.get(index))); + for (int step = 1; step <= horizontalSteps; step++) { + double progress = step / (double) horizontalSteps; + addSidePlacementIfNew(lane, new RailSidePlacement( + new Vector( + startPosition.getBlockX() + (int) Math.round(deltaX * progress), + startPosition.getBlockY() + (int) Math.round(deltaY * progress), + startPosition.getBlockZ() + (int) Math.round(deltaZ * progress) + ), + facing + )); + } + } - if (index < path.size() - 1) - addDirectionIfNew(directions, getHorizontalDirection(path.get(index), path.get(index + 1))); + private Direction getLineFacing(int deltaX, int deltaZ, Direction fallbackFacing) { + if (Math.abs(deltaX) > Math.abs(deltaZ)) + return getXFacing(deltaX); - return directions; + if (Math.abs(deltaZ) > Math.abs(deltaX)) + return getZFacing(deltaZ); + + return fallbackFacing; } - private void addDirectionIfNew(List directions, Vector direction) { - if (direction == null) - return; + private void addSidePlacementIfNew(List lane, RailSidePlacement sidePlacement) { + if (lane.isEmpty() || !isSameBlock(lane.get(lane.size() - 1).position(), sidePlacement.position())) + lane.add(sidePlacement); + } - for (Vector existingDirection : directions) { - if (existingDirection.getBlockX() == direction.getBlockX() - && existingDirection.getBlockZ() == direction.getBlockZ()) - return; + private void replaceLastSidePlacement(List lane, RailSidePlacement sidePlacement) { + if (lane.isEmpty()) { + lane.add(sidePlacement); + return; } - directions.add(direction); + lane.set(lane.size() - 1, sidePlacement); } - private void addSideBlocksForDirection( + private void addExactSideBlock( Map sideBlocks, - Map centerBlocks, - Vector center, - Vector direction + Set centerPositions, + Set centerColumns, + RailSidePlacement sidePlacement ) { - if (isDiagonal(direction)) { - addDiagonalSideBlocks(sideBlocks, centerBlocks, center, direction); + PositionKey key = PositionKey.from(sidePlacement.position()); + + if (isCenterColumnCollision(key, centerPositions, centerColumns)) return; - } - addStraightSideBlocks(sideBlocks, centerBlocks, center, direction); + sideBlocks + .computeIfAbsent(key, ignored -> new RailSideBlock(key)) + .addFacing(sidePlacement.facing()); } - private void addStraightSideBlocks( - Map sideBlocks, - Map centerBlocks, - Vector center, - Vector direction + private Map selectBestSideBlockPerColumn( + Map exactSideBlocks, + Set centerPositions ) { - Vector left = center.clone().add(getLeftOffset(direction)); - Vector right = center.clone().add(getRightOffset(direction)); + Map selectedColumns = new LinkedHashMap<>(); - addSideBlock(sideBlocks, centerBlocks, left, direction); - addSideBlock(sideBlocks, centerBlocks, right, direction); + for (RailSideBlock sideBlock : exactSideBlocks.values()) { + ColumnKey columnKey = ColumnKey.from(sideBlock.key()); + RailSideBlock selectedBlock = selectedColumns.get(columnKey); + + if (selectedBlock == null + || getSideBlockCoverageScore(sideBlock.key(), centerPositions) > getSideBlockCoverageScore(selectedBlock.key(), centerPositions) + || getSideBlockCoverageScore(sideBlock.key(), centerPositions) == getSideBlockCoverageScore(selectedBlock.key(), centerPositions) + && sideBlock.getSupportScore() > selectedBlock.getSupportScore()) + selectedColumns.put(columnKey, sideBlock); + } + + Map sideBlocks = new LinkedHashMap<>(); + + for (RailSideBlock sideBlock : selectedColumns.values()) + sideBlocks.put(sideBlock.key(), sideBlock); + + return sideBlocks; } - private void addDiagonalSideBlocks( + private int getSideBlockCoverageScore(PositionKey sideBlockKey, Set centerPositions) { + int score = 0; + + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) + continue; + + if (centerPositions.contains(PositionKey.of( + sideBlockKey.x() + dx, + sideBlockKey.y(), + sideBlockKey.z() + dz + ))) + score++; + } + } + + return score; + } + + private void repairMissingSideBlocks( + List path, + List sidePairs, Map sideBlocks, - Map centerBlocks, - Vector center, - Vector direction + Set centerPositions, + Set centerColumns ) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); + Map sideColumns = createSideColumnMap(sideBlocks); - Vector firstSide = new Vector( - center.getBlockX() + dx, - center.getBlockY(), - center.getBlockZ() - ); + for (int index = 0; index < path.size(); index++) { + Vector center = path.get(index); + int sideBlockCount = getAdjacentSideBlockCount(center, sideBlocks); - Vector secondSide = new Vector( - center.getBlockX(), - center.getBlockY(), - center.getBlockZ() + dz - ); + if (sideBlockCount >= 2) + continue; + + sideBlockCount = repairSidePlacement( + sidePairs.get(index).first(), + sideBlocks, + sideColumns, + centerPositions, + centerColumns, + sideBlockCount + ); - addSideBlock(sideBlocks, centerBlocks, firstSide, direction); - addSideBlock(sideBlocks, centerBlocks, secondSide, direction); + if (sideBlockCount >= 2) + continue; + + sideBlockCount = repairSidePlacement( + sidePairs.get(index).second(), + sideBlocks, + sideColumns, + centerPositions, + centerColumns, + sideBlockCount + ); + + if (sideBlockCount >= 2) + continue; + + for (RailSidePlacement sidePlacement : getFallbackSidePlacements( + center, + getRailDirection(path, index), + centerPositions, + centerColumns + )) { + sideBlockCount = repairSidePlacement( + sidePlacement, + sideBlocks, + sideColumns, + centerPositions, + centerColumns, + sideBlockCount + ); + + if (sideBlockCount >= 2) + break; + } + } } - private void addSideBlock( + private int repairSidePlacement( + RailSidePlacement sidePlacement, Map sideBlocks, - Map centerBlocks, - Vector position, - Vector direction + Map sideColumns, + Set centerPositions, + Set centerColumns, + int sideBlockCount ) { - PositionKey key = PositionKey.from(position); + PositionKey key = PositionKey.from(sidePlacement.position()); - if (centerBlocks.containsKey(key)) - return; + if (isCenterColumnCollision(key, centerPositions, centerColumns)) + return sideBlockCount; - RailSideBlock sideBlock = sideBlocks.computeIfAbsent( - key, - ignored -> new RailSideBlock(position) - ); + if (sideBlocks.containsKey(key)) + return sideBlockCount; - sideBlock.addDirection(direction); + ColumnKey columnKey = ColumnKey.from(key); + + RailSideBlock existingColumnBlock = sideColumns.get(columnKey); + + if (existingColumnBlock != null) { + if (!canReplaceSideBlock(existingColumnBlock.key(), centerPositions, sideBlocks)) + return sideBlockCount; + + sideBlocks.remove(existingColumnBlock.key()); + } + + RailSideBlock sideBlock = new RailSideBlock(key); + sideBlock.addFacing(sidePlacement.facing()); + sideBlocks.put(key, sideBlock); + sideColumns.put(columnKey, sideBlock); + return sideBlockCount + 1; } - private Map buildBlockMap( - List centerPath, + private boolean canReplaceSideBlock( + PositionKey sideBlockKey, + Set centerPositions, Map sideBlocks ) { - Map blockMap = new LinkedHashMap<>(); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) + continue; + + PositionKey centerKey = PositionKey.of( + sideBlockKey.x() + dx, + sideBlockKey.y(), + sideBlockKey.z() + dz + ); + + if (!centerPositions.contains(centerKey)) + continue; + + if (getAdjacentSideBlockCount(centerKey.toVector(), sideBlocks) <= 2) + return false; + } + } - for (RailSideBlock sideBlock : sideBlocks.values()) { - Vector resolvedDirection = resolveSideBlockDirection(sideBlock, sideBlocks); - BlockState anvil = createAnvilBlockState(resolvedDirection); + return true; + } - if (anvil != null) - blockMap.put(PositionKey.from(sideBlock.position()), anvil); + private int getAdjacentSideBlockCount(Vector center, Map sideBlocks) { + int count = 0; + + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) + continue; + + if (sideBlocks.containsKey(PositionKey.of( + center.getBlockX() + dx, + center.getBlockY(), + center.getBlockZ() + dz + ))) + count++; + } } - for (Vector center : centerPath) - blockMap.put(PositionKey.from(center), createCenterBlockState(center)); + return count; + } - return blockMap; + private boolean isAdjacentOnSameHeight(Vector center, PositionKey key) { + if (key.y() != center.getBlockY()) + return false; + + int dx = Math.abs(key.x() - center.getBlockX()); + int dz = Math.abs(key.z() - center.getBlockZ()); + + return dx <= 1 && dz <= 1 && (dx != 0 || dz != 0); } - private Vector resolveSideBlockDirection( + private Map createSideColumnMap(Map sideBlocks) { + Map sideColumns = new LinkedHashMap<>(); + + for (RailSideBlock sideBlock : sideBlocks.values()) + sideColumns.put(ColumnKey.from(sideBlock.key()), sideBlock); + + return sideColumns; + } + + private Direction resolveSideBlockFacing( RailSideBlock sideBlock, Map sideBlocks ) { - Vector position = sideBlock.position(); + PositionKey key = sideBlock.key(); + boolean east = sideBlocks.containsKey(PositionKey.of(key.x() + 1, key.y(), key.z())); + boolean west = sideBlocks.containsKey(PositionKey.of(key.x() - 1, key.y(), key.z())); + boolean south = sideBlocks.containsKey(PositionKey.of(key.x(), key.y(), key.z() + 1)); + boolean north = sideBlocks.containsKey(PositionKey.of(key.x(), key.y(), key.z() - 1)); + int xConnections = (east ? 1 : 0) + (west ? 1 : 0); + int zConnections = (south ? 1 : 0) + (north ? 1 : 0); + Direction preferredFacing = sideBlock.getPreferredFacing(); + + if (xConnections > zConnections) + return resolveXFacing(preferredFacing, east, west); + + if (zConnections > xConnections) + return resolveZFacing(preferredFacing, south, north); + + return preferredFacing; + } - boolean east = sideBlocks.containsKey(PositionKey.of( - position.getBlockX() + 1, - position.getBlockY(), - position.getBlockZ() - )); + private Direction resolveXFacing(Direction preferredFacing, boolean east, boolean west) { + if (preferredFacing == Direction.EAST && east) + return Direction.EAST; - boolean west = sideBlocks.containsKey(PositionKey.of( - position.getBlockX() - 1, - position.getBlockY(), - position.getBlockZ() - )); + if (preferredFacing == Direction.WEST && west) + return Direction.WEST; - boolean south = sideBlocks.containsKey(PositionKey.of( - position.getBlockX(), - position.getBlockY(), - position.getBlockZ() + 1 - )); + if (east && !west) + return Direction.EAST; - boolean north = sideBlocks.containsKey(PositionKey.of( - position.getBlockX(), - position.getBlockY(), - position.getBlockZ() - 1 - )); + if (west && !east) + return Direction.WEST; - boolean eastWest = east || west; - boolean northSouth = north || south; + return preferredFacing == Direction.WEST ? Direction.WEST : Direction.EAST; + } - if (eastWest && !northSouth) - return getHorizontalDirection(east, west); + private Direction resolveZFacing(Direction preferredFacing, boolean south, boolean north) { + if (preferredFacing == Direction.SOUTH && south) + return Direction.SOUTH; - if (!eastWest && northSouth) - return getVerticalDirection(south, north); + if (preferredFacing == Direction.NORTH && north) + return Direction.NORTH; - if (eastWest || northSouth) - return resolveCornerDirection(sideBlock, east, west, south, north); + if (south && !north) + return Direction.SOUTH; - return sideBlock.getAverageDirection(); + if (north && !south) + return Direction.NORTH; + + return preferredFacing == Direction.NORTH ? Direction.NORTH : Direction.SOUTH; } - private Vector getHorizontalDirection(boolean east, boolean west) { - if (east && !west) - return new Vector(1, 0, 0); + private Set getCenterPositions(List path) { + Set centerPositions = new HashSet<>(); - if (west && !east) - return new Vector(-1, 0, 0); + for (Vector center : path) + centerPositions.add(PositionKey.from(center)); - return new Vector(1, 0, 0); + return centerPositions; } - private Vector getVerticalDirection(boolean south, boolean north) { - if (south && !north) - return new Vector(0, 0, 1); + private Set getCenterColumns(List path) { + Set centerColumns = new HashSet<>(); - if (north && !south) - return new Vector(0, 0, -1); + for (Vector center : path) + centerColumns.add(ColumnKey.from(PositionKey.from(center))); - return new Vector(0, 0, 1); + return centerColumns; } - private Vector resolveCornerDirection( - RailSideBlock sideBlock, - boolean east, - boolean west, - boolean south, - boolean north + private void addBlendedSidePair( + List candidates, + Vector center, + RailDirection previousDirection, + RailDirection nextDirection ) { - Vector average = sideBlock.getAverageDirection(); + int blendedX = Integer.compare(previousDirection.dx() + nextDirection.dx(), 0); + int blendedZ = Integer.compare(previousDirection.dz() + nextDirection.dz(), 0); - int averageX = average.getBlockX(); - int averageZ = average.getBlockZ(); + if (blendedX == 0 && blendedZ == 0) + return; - if (Math.abs(averageX) > Math.abs(averageZ)) { - if (averageX > 0 && east) - return new Vector(1, 0, 0); + addSidePairIfNew(candidates, createSidePair(center, new RailDirection(blendedX, blendedZ))); + } - if (averageX < 0 && west) - return new Vector(-1, 0, 0); + private void addCornerSidePairs( + List candidates, + Vector center, + Set centerPositions, + RailDirection previousDirection, + RailDirection nextDirection + ) { + RailSidePair previousPair = createSidePair(center, previousDirection); + RailSidePair nextPair = createSidePair(center, nextDirection); + RailSidePlacement[] previousPlacements = new RailSidePlacement[]{ + previousPair.first(), + previousPair.second() + }; + RailSidePlacement[] nextPlacements = new RailSidePlacement[]{ + nextPair.first(), + nextPair.second() + }; + + for (RailSidePlacement previousPlacement : previousPlacements) { + for (RailSidePlacement nextPlacement : nextPlacements) { + if (isSameBlock(previousPlacement.position(), nextPlacement.position())) + continue; + + RailSidePair cornerPair = new RailSidePair(previousPlacement, nextPlacement, 0); + + if (getCenterCollisionPenalty(cornerPair, centerPositions) == 0) + addSidePairIfNew(candidates, cornerPair); + } } + } + + private void addFallbackSidePairs( + List candidates, + Vector center, + RailDirection direction, + Set centerPositions + ) { + List placements = getFallbackSidePlacements(center, direction, centerPositions); + + for (int firstIndex = 0; firstIndex < placements.size(); firstIndex++) { + for (int secondIndex = firstIndex + 1; secondIndex < placements.size(); secondIndex++) { + RailSidePlacement first = placements.get(firstIndex); + RailSidePlacement second = placements.get(secondIndex); + int separation = getChebyshevDistance(first.position(), second.position()); - if (Math.abs(averageZ) > Math.abs(averageX)) { - if (averageZ > 0 && south) - return new Vector(0, 0, 1); + if (separation < 2) + continue; - if (averageZ < 0 && north) - return new Vector(0, 0, -1); + addSidePairIfNew(candidates, new RailSidePair(first, second, FALLBACK_SIDE_PAIR_PENALTY)); + } } + } - if (east) - return new Vector(1, 0, 0); + private List getFallbackSidePlacements( + Vector center, + RailDirection direction, + Set centerPositions + ) { + return getFallbackSidePlacements(center, direction, centerPositions, Collections.emptySet()); + } + + private List getFallbackSidePlacements( + Vector center, + RailDirection direction, + Set centerPositions, + Set centerColumns + ) { + List placements = new ArrayList<>(); + + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) + continue; + + Vector position = new Vector( + center.getBlockX() + dx, + center.getBlockY(), + center.getBlockZ() + dz + ); - if (west) - return new Vector(-1, 0, 0); + PositionKey key = PositionKey.from(position); - if (south) - return new Vector(0, 0, 1); + if (isCenterColumnCollision(key, centerPositions, centerColumns)) + continue; - if (north) - return new Vector(0, 0, -1); + placements.add(new RailSidePlacement(position, getFallbackFacing(dx, dz, direction))); + } + } - return average; + return placements; } - private BlockState createCenterBlockState(Vector position) { - int index = Math.floorMod( - position.getBlockX() * 31 + position.getBlockZ() * 17, - CENTER_MATERIALS.length - ); + private Direction getFallbackFacing(int offsetX, int offsetZ, RailDirection direction) { + if (Math.abs(direction.dx()) >= Math.abs(direction.dz()) && direction.dx() != 0) + return getXFacing(direction.dx()); - return GeneratorUtils.getBlockState(CENTER_MATERIALS[index]); + if (direction.dz() != 0) + return getZFacing(direction.dz()); + + if (Math.abs(offsetX) >= Math.abs(offsetZ) && offsetX != 0) + return getXFacing(offsetX); + + return getZFacing(offsetZ); } - private BlockState createAnvilBlockState(Vector direction) { - if (BlockTypes.ANVIL == null) - return null; + private RailSidePair createSidePair(Vector center, RailDirection direction) { + int x = center.getBlockX(); + int y = center.getBlockY(); + int z = center.getBlockZ(); + int dx = direction.dx(); + int dz = direction.dz(); + + if (dx != 0 && dz != 0) + return new RailSidePair( + new RailSidePlacement(new Vector(x + dx, y, z), getZFacing(dz)), + new RailSidePlacement(new Vector(x, y, z + dz), getXFacing(dx)), + 0 + ); - return BlockTypes.ANVIL - .getDefaultState() - .with(PropertyKey.FACING, getWorldEditDirection(direction)); + if (dx != 0) + return new RailSidePair( + new RailSidePlacement(new Vector(x, y, z + 1), getXFacing(dx)), + new RailSidePlacement(new Vector(x, y, z - 1), getXFacing(dx)), + 0 + ); + + return new RailSidePair( + new RailSidePlacement(new Vector(x + 1, y, z), getZFacing(dz)), + new RailSidePlacement(new Vector(x - 1, y, z), getZFacing(dz)), + 0 + ); + } + + private void addSidePairIfNew(List candidates, RailSidePair sidePair) { + for (RailSidePair candidate : candidates) { + if (candidate.hasSamePositions(sidePair)) + return; + } + + candidates.add(sidePair); } - private Direction getWorldEditDirection(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); + private RailDirection getRailDirection(List path, int index) { + RailDirection nextDirection = getHorizontalDirection(path, index, index + 1); + RailDirection previousDirection = getHorizontalDirection(path, index - 1, index); - if (Math.abs(dx) >= Math.abs(dz)) { - if (dx > 0) - return Direction.EAST; + if (nextDirection != null && previousDirection != null) { + if (nextDirection.equals(previousDirection)) + return nextDirection; - if (dx < 0) - return Direction.WEST; + int blendedX = Integer.compare(nextDirection.dx() + previousDirection.dx(), 0); + int blendedZ = Integer.compare(nextDirection.dz() + previousDirection.dz(), 0); + + if (blendedX != 0 || blendedZ != 0) + return new RailDirection(blendedX, blendedZ); + + return nextDirection; } - if (dz > 0) - return Direction.SOUTH; + if (nextDirection != null) + return nextDirection; - if (dz < 0) - return Direction.NORTH; + if (previousDirection != null) + return previousDirection; + + for (int nextIndex = index + 1; nextIndex < path.size(); nextIndex++) { + RailDirection direction = getHorizontalDirection(path, index, nextIndex); + + if (direction != null) + return direction; + } + + for (int previousIndex = index - 1; previousIndex >= 0; previousIndex--) { + RailDirection direction = getHorizontalDirection(path, previousIndex, index); + + if (direction != null) + return direction; + } - return Direction.EAST; + return new RailDirection(1, 0); } - private Vector getHorizontalDirection(Vector from, Vector to) { + private RailDirection getHorizontalDirection(List path, int fromIndex, int toIndex) { + if (fromIndex < 0 || toIndex < 0 || fromIndex >= path.size() || toIndex >= path.size()) + return null; + + Vector from = path.get(fromIndex); + Vector to = path.get(toIndex); int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); if (dx == 0 && dz == 0) return null; - return new Vector(dx, 0, dz); + return new RailDirection(dx, dz); + } + + private void snapMissingControlPointHeightsToTerrain(List points) { + if (blocks == null || !hasMissingControlPointHeights(points)) + return; + + GeneratorUtils.adjustHeight(points, blocks); + } + + private boolean hasMissingControlPointHeights(List points) { + if (customControlPoints != null) + return false; + + for (Vector point : points) + if (point.getBlockY() != 0) + return false; + + return true; } - private boolean isDiagonal(Vector direction) { - return direction.getBlockX() != 0 && direction.getBlockZ() != 0; + private List getRestoreSelectionPoints() { + if (customControlPoints != null && railSelectionPoints.size() >= 3) + return railSelectionPoints; + + return controlPoints; } - private Vector getLeftOffset(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); + private int getSelectionMinY(List points) { + int minY = getRegion() != null ? getRegion().getMinimumY() : getMinPointY(points); + return Math.max(getPlayer().getWorld().getMinHeight(), minY - SELECTION_VERTICAL_PADDING); + } - return new Vector(-dz, 0, dx); + private int getSelectionMaxY(List points) { + int maxY = getRegion() != null ? getRegion().getMaximumY() : getMaxPointY(points); + return Math.min(getPlayer().getWorld().getMaxHeight() - 1, maxY + SELECTION_VERTICAL_PADDING); } - private Vector getRightOffset(Vector direction) { - int dx = direction.getBlockX(); - int dz = direction.getBlockZ(); + private List createBoundsSelectionPoints(List points) { + int minX = Integer.MAX_VALUE; + int minZ = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxZ = Integer.MIN_VALUE; + + for (Vector point : points) { + minX = Math.min(minX, point.getBlockX() - SELECTION_PADDING); + minZ = Math.min(minZ, point.getBlockZ() - SELECTION_PADDING); + maxX = Math.max(maxX, point.getBlockX() + SELECTION_PADDING); + maxZ = Math.max(maxZ, point.getBlockZ() + SELECTION_PADDING); + } - return new Vector(dz, 0, -dx); + return List.of( + new Vector(minX, 0, minZ), + new Vector(maxX, 0, minZ), + new Vector(maxX, 0, maxZ), + new Vector(minX, 0, maxZ) + ); + } + + private int getMinPointY(List points) { + int minY = Integer.MAX_VALUE; + + for (Vector point : points) + minY = Math.min(minY, point.getBlockY()); + + return minY == Integer.MAX_VALUE ? getPlayer().getWorld().getMinHeight() : minY; + } + + private int getMaxPointY(List points) { + int maxY = Integer.MIN_VALUE; + + for (Vector point : points) + maxY = Math.max(maxY, point.getBlockY()); + + return maxY == Integer.MIN_VALUE ? getPlayer().getWorld().getMaxHeight() - 1 : maxY; + } + + private List sanitizePoints(List points) { + List sanitizedPoints = new ArrayList<>(); + + for (Vector point : points) + addPointIfNew(sanitizedPoints, toBlockVector(point)); + + return sanitizedPoints; + } + + private List copyPoints(List points) { + List copiedPoints = new ArrayList<>(); + + for (Vector point : points) + copiedPoints.add(toBlockVector(point)); + + return copiedPoints; + } + + private BlockState createCenterBlockState(Vector position) { + int index = Math.floorMod( + position.getBlockX() * 31 + position.getBlockY() * 23 + position.getBlockZ() * 17, + CENTER_MATERIALS.length + ); + + return GeneratorUtils.getBlockState(CENTER_MATERIALS[index]); + } + + private BlockState createAnvilBlockState(Direction direction) { + if (BlockTypes.ANVIL == null) + return null; + + return BlockTypes.ANVIL + .getDefaultState() + .with(PropertyKey.FACING, direction); + } + + private Direction getXFacing(int dx) { + return dx >= 0 ? Direction.EAST : Direction.WEST; + } + + private Direction getZFacing(int dz) { + return dz >= 0 ? Direction.SOUTH : Direction.NORTH; } private Vector toBlockVector(Vector vector) { @@ -574,22 +1111,19 @@ private Vector toBlockVector(Vector vector) { private void addPointIfNew(List points, Vector point) { Vector blockPoint = toBlockVector(point); - if (points.isEmpty()) { - points.add(blockPoint); - return; - } - - if (!isSameBlock(points.get(points.size() - 1), blockPoint)) + if (points.isEmpty() || !isSameBlock(points.get(points.size() - 1), blockPoint)) points.add(blockPoint); } - private List getCuboidRestoreSelection() { - List restoreSelection = new ArrayList<>(); + private void replaceLastPoint(List points, Vector point) { + Vector blockPoint = toBlockVector(point); - restoreSelection.add(controlPoints.get(0)); - restoreSelection.add(controlPoints.get(controlPoints.size() - 1)); + if (points.isEmpty()) { + points.add(blockPoint); + return; + } - return restoreSelection; + points.set(points.size() - 1, blockPoint); } private boolean isSameBlock(Vector first, Vector second) { @@ -598,6 +1132,34 @@ private boolean isSameBlock(Vector first, Vector second) { && first.getBlockZ() == second.getBlockZ(); } + private record RailDirection(int dx, int dz) { + } + + private record RailSidePlacement(Vector position, Direction facing) { + } + + private record RailSidePair(RailSidePlacement first, RailSidePlacement second, int fallbackPenalty) { + + private RailSidePair reversed() { + return new RailSidePair(second, first, fallbackPenalty); + } + + private boolean hasDuplicatePosition() { + return isSamePosition(first, second); + } + + private boolean hasSamePositions(RailSidePair other) { + return (isSamePosition(first, other.first) && isSamePosition(second, other.second)) + || (isSamePosition(first, other.second) && isSamePosition(second, other.first)); + } + + private static boolean isSamePosition(RailSidePlacement first, RailSidePlacement second) { + return first.position().getBlockX() == second.position().getBlockX() + && first.position().getBlockY() == second.position().getBlockY() + && first.position().getBlockZ() == second.position().getBlockZ(); + } + } + private record PositionKey(int x, int y, int z) { private static PositionKey from(Vector vector) { @@ -617,33 +1179,51 @@ private Vector toVector() { } } + private record ColumnKey(int x, int z) { + + private static ColumnKey from(PositionKey key) { + return new ColumnKey(key.x(), key.z()); + } + } + private static class RailSideBlock { - private final Vector position; - private int directionX; - private int directionZ; + private final PositionKey key; + private final Map facingScores = new LinkedHashMap<>(); - private RailSideBlock(Vector position) { - this.position = position; + private RailSideBlock(PositionKey key) { + this.key = key; } - private Vector position() { - return position; + private PositionKey key() { + return key; } - private void addDirection(Vector direction) { - directionX += direction.getBlockX(); - directionZ += direction.getBlockZ(); + private void addFacing(Direction facing) { + facingScores.merge(facing, 1, Integer::sum); } - private Vector getAverageDirection() { - int dx = Integer.compare(directionX, 0); - int dz = Integer.compare(directionZ, 0); + private int getSupportScore() { + int score = 0; - if (dx == 0 && dz == 0) - return new Vector(1, 0, 0); + for (int facingScore : facingScores.values()) + score += facingScore; + + return score; + } + + private Direction getPreferredFacing() { + Direction preferredFacing = Direction.EAST; + int preferredScore = -1; + + for (Map.Entry entry : facingScores.entrySet()) { + if (entry.getValue() > preferredScore) { + preferredFacing = entry.getKey(); + preferredScore = entry.getValue(); + } + } - return new Vector(dx, 0, dz); + return preferredFacing; } } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index e2fa937e..34d34153 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -2,8 +2,12 @@ import com.alpsbte.alpslib.utils.ChatHelper; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.world.World; @@ -15,6 +19,7 @@ import net.buildtheearth.buildteamtools.modules.common.CommonModule; import net.buildtheearth.buildteamtools.modules.generator.listeners.GeneratorListener; import net.buildtheearth.buildteamtools.utils.MenuItems; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -190,6 +195,10 @@ public void processOperation(Operation operation) { future = GeneratorUtils.replaceBlocks(localSession, actor, weWorld, (BlockState[]) operation.get(0), (BlockState[]) operation.get(1)); break; + case SET_BLOCKSTATES_AT_POSITIONS: + future = setBlockStatesAtPositions(Arrays.asList((Vector[]) operation.get(0)), Arrays.asList((BlockState[]) operation.get(1))); + break; + case DRAW_CURVE_WITH_MASKS: future = GeneratorUtils.drawCurveWithMasks(localSession, actor, weWorld, blocks, Arrays.asList((String[]) operation.get(0)), Arrays.asList((Vector[]) operation.get(1)), (BlockState[]) operation.get(2), (Boolean) operation.get(3)); break; @@ -271,6 +280,36 @@ private void runInternalGeneratorCommand(String command) { } } + private CompletableFuture setBlockStatesAtPositions(List positions, List blockStates) { + CompletableFuture future = new CompletableFuture<>(); + + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { + try (EditSession editSession = WorldEdit.getInstance().newEditSession(weWorld)) { + for (int index = 0; index < positions.size(); index++) { + BlockState blockState = blockStates.get(index); + + if (blockState == null) + continue; + + Vector position = positions.get(index); + editSession.setBlock( + BlockVector3.at(position.getBlockX(), position.getBlockY(), position.getBlockZ()), + blockState + ); + } + + GeneratorUtils.saveEditSession(editSession, localSession, actor); + future.complete(null); + } catch (MaxChangedBlocksException e) { + future.completeExceptionally(e); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } + /** Converts the XYZ coordinates in a command to the highest block at that location while skipping certain blocks. */ public String convertXYZ(String command) { String xyz = command.split("%%XYZ/")[1].split("/%%")[0]; @@ -302,4 +341,4 @@ public void finish() { isFinished = true; generatorComponent.sendSuccessMessage(player); } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java index 0d230139..01e465c5 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java @@ -19,6 +19,7 @@ public enum OperationType { CUBOID_SELECTION(Vector.class, Vector.class), POLYGONAL_SELECTION(Vector[].class), REPLACE_BLOCKSTATES(BlockState[].class, BlockState[].class), + SET_BLOCKSTATES_AT_POSITIONS(Vector[].class, BlockState[].class), REPLACE_BLOCKSTATES_WITH_MASKS(String[].class, BlockState.class, BlockState[].class, Integer.class), DRAW_CURVE_WITH_MASKS(String[].class, Vector[].class, BlockState[].class, Boolean.class), DRAW_POLY_LINE_WITH_MASKS(String[].class, Vector[].class, BlockState[].class, Boolean.class, Boolean.class), diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java index d3f2e5b1..3d039b6e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java @@ -245,6 +245,18 @@ public void replaceBlocks(BlockState from, BlockState to){ replaceBlocks(new BlockState[]{from}, new BlockState[]{to}); } + public void setBlockStatesAtPositions(List positions, List blockStates) { + if (positions.size() != blockStates.size()) + throw new IllegalArgumentException("Position and BlockState lists must have the same size"); + + operations.add(new Operation( + Operation.OperationType.SET_BLOCKSTATES_AT_POSITIONS, + positions.toArray(new Vector[0]), + blockStates.toArray(new BlockState[0]) + )); + changes++; + } + /** * Set blocks with a mask. * It creates a new Operation with type SET_BLOCKS_WITH_EXPRESSION_MASK and adds it to the list of operations to execute. From 85ce7e894dc630db140e8de07f2c6f2b1239d0df Mon Sep 17 00:00:00 2001 From: Jasupa Date: Mon, 25 May 2026 17:49:39 +0200 Subject: [PATCH 11/18] refractor: - Rail generator side block placement --- .../components/rail/RailScripts.java | 833 +++++------------- 1 file changed, 208 insertions(+), 625 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 88b6fe5c..19e0f16d 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -2,7 +2,6 @@ import com.alpsbte.alpslib.utils.GeneratorUtils; import com.cryptomorin.xseries.XMaterial; -import com.fastasyncworldedit.core.registry.state.PropertyKey; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypes; @@ -28,9 +27,7 @@ public class RailScripts extends Script { private static final int SELECTION_PADDING = 4; private static final int SELECTION_VERTICAL_PADDING = 12; private static final int PREPARE_SELECTION_EXPANSION = 8; - private static final int INVALID_SIDE_PAIR_PENALTY = 1_000_000; - private static final int CENTER_COLLISION_PENALTY = 100_000; - private static final int FALLBACK_SIDE_PAIR_PENALTY = 1_000; + private static final Direction DEFAULT_FACING = Direction.EAST; private static final XMaterial[] CENTER_MATERIALS = new XMaterial[]{ XMaterial.DEAD_FIRE_CORAL_BLOCK, @@ -86,7 +83,7 @@ private void prepareSession() { ); snapMissingControlPointHeightsToTerrain(controlPoints); - centerPath = createShortestPath(controlPoints); + centerPath = createCenterPath(controlPoints); } private void railScript_v_2_0() { @@ -158,6 +155,10 @@ private List createRailSelectionPoints(List points) { return shiftedPoints; } + private List createCenterPath(List points) { + return removeOrthogonalCorners(createShortestPath(points)); + } + private List createShortestPath(List points) { List path = new ArrayList<>(); @@ -173,13 +174,9 @@ private List createShortestPath(List points) { } private void appendShortestLine(List path, Vector start, Vector end) { - int startX = start.getBlockX(); - int startY = start.getBlockY(); - int startZ = start.getBlockZ(); - int deltaX = end.getBlockX() - startX; - int deltaY = end.getBlockY() - startY; - int deltaZ = end.getBlockZ() - startZ; - + int deltaX = end.getBlockX() - start.getBlockX(); + int deltaY = end.getBlockY() - start.getBlockY(); + int deltaZ = end.getBlockZ() - start.getBlockZ(); int horizontalSteps = Math.max(Math.abs(deltaX), Math.abs(deltaZ)); if (horizontalSteps == 0) { @@ -190,154 +187,53 @@ private void appendShortestLine(List path, Vector start, Vector end) { for (int step = 1; step <= horizontalSteps; step++) { double progress = step / (double) horizontalSteps; addPointIfNew(path, new Vector( - startX + (int) Math.round(deltaX * progress), - startY + (int) Math.round(deltaY * progress), - startZ + (int) Math.round(deltaZ * progress) + start.getBlockX() + (int) Math.round(deltaX * progress), + start.getBlockY() + (int) Math.round(deltaY * progress), + start.getBlockZ() + (int) Math.round(deltaZ * progress) )); } } - private Map buildRailBlocks(List path) { - Map blockMap = new LinkedHashMap<>(); - Set centerPositions = getCenterPositions(path); - Set centerColumns = getCenterColumns(path); - Map sideBlocks = buildSideBlocks(path, centerPositions, centerColumns); + private List removeOrthogonalCorners(List path) { + List result = new ArrayList<>(); - for (RailSideBlock sideBlock : sideBlocks.values()) { - if (centerColumns.contains(ColumnKey.from(sideBlock.key()))) + for (int index = 0; index < path.size(); index++) { + if (index > 0 && index < path.size() - 1 && isOrthogonalCorner(path.get(index - 1), path.get(index), path.get(index + 1))) continue; - blockMap.put(sideBlock.key(), createAnvilBlockState(resolveSideBlockFacing(sideBlock, sideBlocks))); + result.add(path.get(index)); } - for (Vector center : path) - blockMap.put(PositionKey.from(center), createCenterBlockState(center)); - - return blockMap; + return result; } - private List buildSidePairs(List path, Set centerPositions) { - List sidePairs = new ArrayList<>(); - RailSidePair previousPair = null; - - for (int index = 0; index < path.size(); index++) { - List candidates = getSidePairCandidates(path, centerPositions, index); - RailSidePair selectedPair = selectSidePair(previousPair, candidates, centerPositions); - - sidePairs.add(selectedPair); - previousPair = selectedPair; - } + private boolean isOrthogonalCorner(Vector previous, Vector current, Vector next) { + RailStep previousStep = getStep(previous, current); + RailStep nextStep = getStep(current, next); - return sidePairs; + return previousStep != null + && nextStep != null + && previousStep.dx() * nextStep.dx() + previousStep.dz() * nextStep.dz() == 0 + && previousStep.dx() != nextStep.dx() + && previousStep.dz() != nextStep.dz(); } - private List getSidePairCandidates( - List path, - Set centerPositions, - int index - ) { - List candidates = new ArrayList<>(); - Vector center = path.get(index); - RailDirection previousDirection = getHorizontalDirection(path, index - 1, index); - RailDirection nextDirection = getHorizontalDirection(path, index, index + 1); - - addSidePairIfNew(candidates, createSidePair(center, getRailDirection(path, index))); - - if (previousDirection != null) - addSidePairIfNew(candidates, createSidePair(center, previousDirection)); - - if (nextDirection != null) - addSidePairIfNew(candidates, createSidePair(center, nextDirection)); - - if (previousDirection != null && nextDirection != null) { - addBlendedSidePair(candidates, center, previousDirection, nextDirection); - addCornerSidePairs(candidates, center, centerPositions, previousDirection, nextDirection); - } - - addFallbackSidePairs(candidates, center, getRailDirection(path, index), centerPositions); - - return candidates; - } - - private RailSidePair selectSidePair( - RailSidePair previousPair, - List candidates, - Set centerPositions - ) { - RailSidePair selectedPair = candidates.get(0); - int selectedScore = Integer.MAX_VALUE; - - for (RailSidePair candidate : candidates) { - RailSidePair[] orientations = new RailSidePair[]{candidate, candidate.reversed()}; - - for (RailSidePair orientedCandidate : orientations) { - int score = getSidePairScore(previousPair, orientedCandidate, centerPositions); + private Map buildRailBlocks(List path) { + Map railBlocks = new LinkedHashMap<>(); + Set centerPositions = getCenterPositions(path); + Set centerColumns = getCenterColumns(path); + Map sideBlocks = buildSideBlocks(path, centerPositions, centerColumns); + Map sideColumns = createSideColumnMap(sideBlocks); - if (score < selectedScore) { - selectedScore = score; - selectedPair = orientedCandidate; - } - } + for (RailSideBlock sideBlock : sideBlocks.values()) { + if (!centerColumns.contains(ColumnKey.from(sideBlock.key()))) + railBlocks.put(sideBlock.key(), createAnvilBlockState(resolveSideBlockFacing(sideBlock, sideColumns))); } - return selectedPair; - } - - private int getSidePairScore( - RailSidePair previousPair, - RailSidePair candidate, - Set centerPositions - ) { - int score = getCenterCollisionPenalty(candidate, centerPositions); - - if (previousPair != null) - score += getSidePairTransitionDistance(previousPair, candidate); - - score += candidate.fallbackPenalty(); - - return score; - } - - private int getCenterCollisionPenalty(RailSidePair candidate, Set centerPositions) { - if (candidate.hasDuplicatePosition()) - return INVALID_SIDE_PAIR_PENALTY; - - int penalty = 0; - - if (isCenterCollision(candidate.first(), centerPositions)) - penalty += CENTER_COLLISION_PENALTY; - - if (isCenterCollision(candidate.second(), centerPositions)) - penalty += CENTER_COLLISION_PENALTY; - - return penalty; - } - - private boolean isCenterCollision(RailSidePlacement sidePlacement, Set centerPositions) { - return centerPositions.contains(PositionKey.from(sidePlacement.position())); - } - - private boolean isCenterColumnCollision( - PositionKey key, - Set centerPositions, - Set centerColumns - ) { - return centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key)); - } - - private int getSidePairTransitionDistance(RailSidePair previousPair, RailSidePair candidate) { - return getChebyshevDistance(previousPair.first().position(), candidate.first().position()) - + getChebyshevDistance(previousPair.second().position(), candidate.second().position()); - } + for (Vector center : path) + railBlocks.put(PositionKey.from(center), createCenterBlockState(center)); - private int getChebyshevDistance(Vector first, Vector second) { - return Math.max( - Math.max( - Math.abs(second.getBlockX() - first.getBlockX()), - Math.abs(second.getBlockY() - first.getBlockY()) - ), - Math.abs(second.getBlockZ() - first.getBlockZ()) - ); + return railBlocks; } private Map buildSideBlocks( @@ -347,144 +243,81 @@ private Map buildSideBlocks( ) { Map exactSideBlocks = new LinkedHashMap<>(); - for (List sideLane : buildSideLanes(path)) { - for (RailSidePlacement sidePlacement : sideLane) - addExactSideBlock(exactSideBlocks, centerPositions, centerColumns, sidePlacement); - } - - List sidePairs = buildSidePairs(path, centerPositions); - - for (RailSidePair sidePair : sidePairs) { - addExactSideBlock(exactSideBlocks, centerPositions, centerColumns, sidePair.first()); - addExactSideBlock(exactSideBlocks, centerPositions, centerColumns, sidePair.second()); + for (int index = 0; index < path.size(); index++) { + for (RailSidePlacement sidePlacement : getSidePlacements(path, index)) + addSideBlock(exactSideBlocks, sidePlacement, centerPositions, centerColumns); } Map sideBlocks = selectBestSideBlockPerColumn(exactSideBlocks, centerPositions); - repairMissingSideBlocks(path, sidePairs, sideBlocks, centerPositions, centerColumns); + repairMissingSideBlocks(path, sideBlocks, centerPositions, centerColumns); return sideBlocks; } - private List> buildSideLanes(List path) { - List firstLane = new ArrayList<>(); - List secondLane = new ArrayList<>(); + private List getSidePlacements(List path, int index) { + List placements = new ArrayList<>(); + Vector center = path.get(index); + RailStep previousStep = index > 0 ? getStep(path.get(index - 1), center) : null; + RailStep nextStep = index < path.size() - 1 ? getStep(center, path.get(index + 1)) : null; - for (int index = 0; index < path.size() - 1; index++) { - RailDirection direction = getHorizontalDirection(path, index, index + 1); + addSidePlacements(placements, center, getRailStep(path, index, new RailStep(1, 0))); - if (direction == null) - continue; + if (previousStep != null) + addSidePlacements(placements, center, previousStep); - RailSidePair startPair = createSidePair(path.get(index), direction); - RailSidePair endPair = createSidePair(path.get(index + 1), direction); + if (nextStep != null) + addSidePlacements(placements, center, nextStep); - boolean reverseSegment = !firstLane.isEmpty() && shouldReverseSideSegment(firstLane, secondLane, startPair); + if (previousStep != null && nextStep != null) { + int dx = Integer.compare(previousStep.dx() + nextStep.dx(), 0); + int dz = Integer.compare(previousStep.dz() + nextStep.dz(), 0); - if (reverseSegment) { - startPair = startPair.reversed(); - endPair = endPair.reversed(); - } - - appendSideLaneSegment(firstLane, startPair.first(), endPair.first()); - appendSideLaneSegment(secondLane, startPair.second(), endPair.second()); + if (dx != 0 || dz != 0) + addSidePlacements(placements, center, new RailStep(dx, dz)); } - return List.of(firstLane, secondLane); + return placements; } - private boolean shouldReverseSideSegment( - List firstLane, - List secondLane, - RailSidePair startPair - ) { - RailSidePlacement firstEnd = firstLane.get(firstLane.size() - 1); - RailSidePlacement secondEnd = secondLane.get(secondLane.size() - 1); - int normalDistance = getChebyshevDistance(firstEnd.position(), startPair.first().position()) - + getChebyshevDistance(secondEnd.position(), startPair.second().position()); - int reversedDistance = getChebyshevDistance(firstEnd.position(), startPair.second().position()) - + getChebyshevDistance(secondEnd.position(), startPair.first().position()); - - return reversedDistance < normalDistance; - } + private void addSidePlacements(List placements, Vector center, RailStep step) { + int x = center.getBlockX(); + int y = center.getBlockY(); + int z = center.getBlockZ(); - private void appendSideLaneSegment( - List lane, - RailSidePlacement start, - RailSidePlacement end - ) { - if (lane.isEmpty()) { - lane.add(start); + if (step.dx() != 0 && step.dz() != 0) { + addSidePlacement(placements, new Vector(x + step.dx(), y, z), GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING)); + addSidePlacement(placements, new Vector(x, y, z + step.dz()), GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING)); + } else if (step.dx() != 0) { + Direction facing = GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); + addSidePlacement(placements, new Vector(x, y, z + 1), facing); + addSidePlacement(placements, new Vector(x, y, z - 1), facing); } else { - appendSideLine(lane, lane.get(lane.size() - 1), start); - } - - appendSideLine(lane, lane.get(lane.size() - 1), end); - } - - private void appendSideLine( - List lane, - RailSidePlacement start, - RailSidePlacement end - ) { - Vector startPosition = start.position(); - Vector endPosition = end.position(); - int deltaX = endPosition.getBlockX() - startPosition.getBlockX(); - int deltaY = endPosition.getBlockY() - startPosition.getBlockY(); - int deltaZ = endPosition.getBlockZ() - startPosition.getBlockZ(); - int horizontalSteps = Math.max(Math.abs(deltaX), Math.abs(deltaZ)); - Direction facing = getLineFacing(deltaX, deltaZ, end.facing()); - - if (horizontalSteps == 0) { - replaceLastSidePlacement(lane, new RailSidePlacement(endPosition, facing)); - return; - } - - for (int step = 1; step <= horizontalSteps; step++) { - double progress = step / (double) horizontalSteps; - addSidePlacementIfNew(lane, new RailSidePlacement( - new Vector( - startPosition.getBlockX() + (int) Math.round(deltaX * progress), - startPosition.getBlockY() + (int) Math.round(deltaY * progress), - startPosition.getBlockZ() + (int) Math.round(deltaZ * progress) - ), - facing - )); + Direction facing = GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); + addSidePlacement(placements, new Vector(x + 1, y, z), facing); + addSidePlacement(placements, new Vector(x - 1, y, z), facing); } } - private Direction getLineFacing(int deltaX, int deltaZ, Direction fallbackFacing) { - if (Math.abs(deltaX) > Math.abs(deltaZ)) - return getXFacing(deltaX); + private void addSidePlacement(List placements, Vector position, Direction facing) { + PositionKey key = PositionKey.from(position); - if (Math.abs(deltaZ) > Math.abs(deltaX)) - return getZFacing(deltaZ); - - return fallbackFacing; - } - - private void addSidePlacementIfNew(List lane, RailSidePlacement sidePlacement) { - if (lane.isEmpty() || !isSameBlock(lane.get(lane.size() - 1).position(), sidePlacement.position())) - lane.add(sidePlacement); - } - - private void replaceLastSidePlacement(List lane, RailSidePlacement sidePlacement) { - if (lane.isEmpty()) { - lane.add(sidePlacement); - return; + for (RailSidePlacement placement : placements) { + if (PositionKey.from(placement.position()).equals(key)) + return; } - lane.set(lane.size() - 1, sidePlacement); + placements.add(new RailSidePlacement(position, facing)); } - private void addExactSideBlock( + private void addSideBlock( Map sideBlocks, + RailSidePlacement sidePlacement, Set centerPositions, - Set centerColumns, - RailSidePlacement sidePlacement + Set centerColumns ) { PositionKey key = PositionKey.from(sidePlacement.position()); - if (isCenterColumnCollision(key, centerPositions, centerColumns)) + if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key))) return; sideBlocks @@ -502,10 +335,7 @@ private Map selectBestSideBlockPerColumn( ColumnKey columnKey = ColumnKey.from(sideBlock.key()); RailSideBlock selectedBlock = selectedColumns.get(columnKey); - if (selectedBlock == null - || getSideBlockCoverageScore(sideBlock.key(), centerPositions) > getSideBlockCoverageScore(selectedBlock.key(), centerPositions) - || getSideBlockCoverageScore(sideBlock.key(), centerPositions) == getSideBlockCoverageScore(selectedBlock.key(), centerPositions) - && sideBlock.getSupportScore() > selectedBlock.getSupportScore()) + if (selectedBlock == null || isBetterSideBlock(sideBlock, selectedBlock, centerPositions)) selectedColumns.put(columnKey, sideBlock); } @@ -517,29 +347,30 @@ private Map selectBestSideBlockPerColumn( return sideBlocks; } + private boolean isBetterSideBlock( + RailSideBlock candidate, + RailSideBlock selectedBlock, + Set centerPositions + ) { + int candidateCoverage = getSideBlockCoverageScore(candidate.key(), centerPositions); + int selectedCoverage = getSideBlockCoverageScore(selectedBlock.key(), centerPositions); + + return candidateCoverage > selectedCoverage + || candidateCoverage == selectedCoverage && candidate.getSupportScore() > selectedBlock.getSupportScore(); + } + private int getSideBlockCoverageScore(PositionKey sideBlockKey, Set centerPositions) { int score = 0; - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) - continue; - - if (centerPositions.contains(PositionKey.of( - sideBlockKey.x() + dx, - sideBlockKey.y(), - sideBlockKey.z() + dz - ))) - score++; - } - } + for (PositionKey centerPosition : centerPositions) + if (isAdjacent(sideBlockKey, centerPosition)) + score++; return score; } private void repairMissingSideBlocks( List path, - List sidePairs, Map sideBlocks, Set centerPositions, Set centerColumns @@ -553,38 +384,13 @@ private void repairMissingSideBlocks( if (sideBlockCount >= 2) continue; - sideBlockCount = repairSidePlacement( - sidePairs.get(index).first(), - sideBlocks, - sideColumns, - centerPositions, - centerColumns, - sideBlockCount - ); - - if (sideBlockCount >= 2) - continue; - - sideBlockCount = repairSidePlacement( - sidePairs.get(index).second(), - sideBlocks, - sideColumns, - centerPositions, - centerColumns, - sideBlockCount - ); - - if (sideBlockCount >= 2) - continue; + List repairPlacements = getSidePlacements(path, index); + repairPlacements.addAll(getFallbackSidePlacements(center, getRailStep(path, index, new RailStep(1, 0)))); - for (RailSidePlacement sidePlacement : getFallbackSidePlacements( - center, - getRailDirection(path, index), - centerPositions, - centerColumns - )) { + for (RailSidePlacement sidePlacement : repairPlacements) { sideBlockCount = repairSidePlacement( sidePlacement, + PositionKey.from(center), sideBlocks, sideColumns, centerPositions, @@ -598,8 +404,41 @@ private void repairMissingSideBlocks( } } + private List getFallbackSidePlacements(Vector center, RailStep step) { + List placements = new ArrayList<>(); + + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) + continue; + + addSidePlacement( + placements, + new Vector(center.getBlockX() + dx, center.getBlockY(), center.getBlockZ() + dz), + getFallbackFacing(dx, dz, step) + ); + } + } + + return placements; + } + + private Direction getFallbackFacing(int offsetX, int offsetZ, RailStep step) { + if (Math.abs(step.dx()) >= Math.abs(step.dz()) && step.dx() != 0) + return GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); + + if (step.dz() != 0) + return GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); + + if (Math.abs(offsetX) >= Math.abs(offsetZ) && offsetX != 0) + return GeneratorUtils.getFacing(offsetX, 0, DEFAULT_FACING); + + return GeneratorUtils.getFacing(0, offsetZ, DEFAULT_FACING); + } + private int repairSidePlacement( RailSidePlacement sidePlacement, + PositionKey centerKey, Map sideBlocks, Map sideColumns, Set centerPositions, @@ -608,15 +447,12 @@ private int repairSidePlacement( ) { PositionKey key = PositionKey.from(sidePlacement.position()); - if (isCenterColumnCollision(key, centerPositions, centerColumns)) - return sideBlockCount; - - if (sideBlocks.containsKey(key)) + if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key)) || sideBlocks.containsKey(key)) return sideBlockCount; ColumnKey columnKey = ColumnKey.from(key); - RailSideBlock existingColumnBlock = sideColumns.get(columnKey); + boolean replacingAdjacentBlock = existingColumnBlock != null && isAdjacent(existingColumnBlock.key(), centerKey); if (existingColumnBlock != null) { if (!canReplaceSideBlock(existingColumnBlock.key(), centerPositions, sideBlocks)) @@ -629,7 +465,7 @@ private int repairSidePlacement( sideBlock.addFacing(sidePlacement.facing()); sideBlocks.put(key, sideBlock); sideColumns.put(columnKey, sideBlock); - return sideBlockCount + 1; + return sideBlockCount + (replacingAdjacentBlock ? 0 : 1); } private boolean canReplaceSideBlock( @@ -637,23 +473,9 @@ private boolean canReplaceSideBlock( Set centerPositions, Map sideBlocks ) { - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) - continue; - - PositionKey centerKey = PositionKey.of( - sideBlockKey.x() + dx, - sideBlockKey.y(), - sideBlockKey.z() + dz - ); - - if (!centerPositions.contains(centerKey)) - continue; - - if (getAdjacentSideBlockCount(centerKey.toVector(), sideBlocks) <= 2) - return false; - } + for (PositionKey centerPosition : centerPositions) { + if (isAdjacent(sideBlockKey, centerPosition) && getAdjacentSideBlockCount(centerPosition.toVector(), sideBlocks) <= 2) + return false; } return true; @@ -662,103 +484,20 @@ private boolean canReplaceSideBlock( private int getAdjacentSideBlockCount(Vector center, Map sideBlocks) { int count = 0; - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) - continue; - - if (sideBlocks.containsKey(PositionKey.of( - center.getBlockX() + dx, - center.getBlockY(), - center.getBlockZ() + dz - ))) - count++; - } + for (PositionKey sideBlockKey : sideBlocks.keySet()) { + if (isAdjacent(sideBlockKey, PositionKey.from(center))) + count++; } return count; } - private boolean isAdjacentOnSameHeight(Vector center, PositionKey key) { - if (key.y() != center.getBlockY()) - return false; - - int dx = Math.abs(key.x() - center.getBlockX()); - int dz = Math.abs(key.z() - center.getBlockZ()); - - return dx <= 1 && dz <= 1 && (dx != 0 || dz != 0); - } - - private Map createSideColumnMap(Map sideBlocks) { - Map sideColumns = new LinkedHashMap<>(); - - for (RailSideBlock sideBlock : sideBlocks.values()) - sideColumns.put(ColumnKey.from(sideBlock.key()), sideBlock); - - return sideColumns; - } - - private Direction resolveSideBlockFacing( - RailSideBlock sideBlock, - Map sideBlocks - ) { - PositionKey key = sideBlock.key(); - boolean east = sideBlocks.containsKey(PositionKey.of(key.x() + 1, key.y(), key.z())); - boolean west = sideBlocks.containsKey(PositionKey.of(key.x() - 1, key.y(), key.z())); - boolean south = sideBlocks.containsKey(PositionKey.of(key.x(), key.y(), key.z() + 1)); - boolean north = sideBlocks.containsKey(PositionKey.of(key.x(), key.y(), key.z() - 1)); - int xConnections = (east ? 1 : 0) + (west ? 1 : 0); - int zConnections = (south ? 1 : 0) + (north ? 1 : 0); - Direction preferredFacing = sideBlock.getPreferredFacing(); - - if (xConnections > zConnections) - return resolveXFacing(preferredFacing, east, west); - - if (zConnections > xConnections) - return resolveZFacing(preferredFacing, south, north); - - return preferredFacing; - } - - private Direction resolveXFacing(Direction preferredFacing, boolean east, boolean west) { - if (preferredFacing == Direction.EAST && east) - return Direction.EAST; + private boolean isAdjacent(PositionKey first, PositionKey second) { + int dx = Math.abs(first.x() - second.x()); + int dy = Math.abs(first.y() - second.y()); + int dz = Math.abs(first.z() - second.z()); - if (preferredFacing == Direction.WEST && west) - return Direction.WEST; - - if (east && !west) - return Direction.EAST; - - if (west && !east) - return Direction.WEST; - - return preferredFacing == Direction.WEST ? Direction.WEST : Direction.EAST; - } - - private Direction resolveZFacing(Direction preferredFacing, boolean south, boolean north) { - if (preferredFacing == Direction.SOUTH && south) - return Direction.SOUTH; - - if (preferredFacing == Direction.NORTH && north) - return Direction.NORTH; - - if (south && !north) - return Direction.SOUTH; - - if (north && !south) - return Direction.NORTH; - - return preferredFacing == Direction.NORTH ? Direction.NORTH : Direction.SOUTH; - } - - private Set getCenterPositions(List path) { - Set centerPositions = new HashSet<>(); - - for (Vector center : path) - centerPositions.add(PositionKey.from(center)); - - return centerPositions; + return dx <= 1 && dy <= 1 && dz <= 1 && (dx != 0 || dz != 0); } private Set getCenterColumns(List path) { @@ -770,216 +509,91 @@ private Set getCenterColumns(List path) { return centerColumns; } - private void addBlendedSidePair( - List candidates, - Vector center, - RailDirection previousDirection, - RailDirection nextDirection - ) { - int blendedX = Integer.compare(previousDirection.dx() + nextDirection.dx(), 0); - int blendedZ = Integer.compare(previousDirection.dz() + nextDirection.dz(), 0); + private Set getCenterPositions(List path) { + Set centerPositions = new HashSet<>(); - if (blendedX == 0 && blendedZ == 0) - return; + for (Vector center : path) + centerPositions.add(PositionKey.from(center)); - addSidePairIfNew(candidates, createSidePair(center, new RailDirection(blendedX, blendedZ))); + return centerPositions; } - private void addCornerSidePairs( - List candidates, - Vector center, - Set centerPositions, - RailDirection previousDirection, - RailDirection nextDirection - ) { - RailSidePair previousPair = createSidePair(center, previousDirection); - RailSidePair nextPair = createSidePair(center, nextDirection); - RailSidePlacement[] previousPlacements = new RailSidePlacement[]{ - previousPair.first(), - previousPair.second() - }; - RailSidePlacement[] nextPlacements = new RailSidePlacement[]{ - nextPair.first(), - nextPair.second() - }; - - for (RailSidePlacement previousPlacement : previousPlacements) { - for (RailSidePlacement nextPlacement : nextPlacements) { - if (isSameBlock(previousPlacement.position(), nextPlacement.position())) - continue; + private Map createSideColumnMap(Map sideBlocks) { + Map sideColumns = new LinkedHashMap<>(); - RailSidePair cornerPair = new RailSidePair(previousPlacement, nextPlacement, 0); + for (RailSideBlock sideBlock : sideBlocks.values()) + sideColumns.put(ColumnKey.from(sideBlock.key()), sideBlock); - if (getCenterCollisionPenalty(cornerPair, centerPositions) == 0) - addSidePairIfNew(candidates, cornerPair); - } - } + return sideColumns; } - private void addFallbackSidePairs( - List candidates, - Vector center, - RailDirection direction, - Set centerPositions - ) { - List placements = getFallbackSidePlacements(center, direction, centerPositions); - - for (int firstIndex = 0; firstIndex < placements.size(); firstIndex++) { - for (int secondIndex = firstIndex + 1; secondIndex < placements.size(); secondIndex++) { - RailSidePlacement first = placements.get(firstIndex); - RailSidePlacement second = placements.get(secondIndex); - int separation = getChebyshevDistance(first.position(), second.position()); + private Direction resolveSideBlockFacing(RailSideBlock sideBlock, Map sideColumns) { + PositionKey key = sideBlock.key(); + boolean east = sideColumns.containsKey(ColumnKey.of(key.x() + 1, key.z())); + boolean west = sideColumns.containsKey(ColumnKey.of(key.x() - 1, key.z())); + boolean south = sideColumns.containsKey(ColumnKey.of(key.x(), key.z() + 1)); + boolean north = sideColumns.containsKey(ColumnKey.of(key.x(), key.z() - 1)); + int xConnections = (east ? 1 : 0) + (west ? 1 : 0); + int zConnections = (south ? 1 : 0) + (north ? 1 : 0); + Direction preferredFacing = sideBlock.getPreferredFacing(); - if (separation < 2) - continue; + if (xConnections > zConnections) + return resolveAxisFacing(preferredFacing, Direction.EAST, Direction.WEST, east, west); - addSidePairIfNew(candidates, new RailSidePair(first, second, FALLBACK_SIDE_PAIR_PENALTY)); - } - } - } + if (zConnections > xConnections) + return resolveAxisFacing(preferredFacing, Direction.SOUTH, Direction.NORTH, south, north); - private List getFallbackSidePlacements( - Vector center, - RailDirection direction, - Set centerPositions - ) { - return getFallbackSidePlacements(center, direction, centerPositions, Collections.emptySet()); + return preferredFacing; } - private List getFallbackSidePlacements( - Vector center, - RailDirection direction, - Set centerPositions, - Set centerColumns + private Direction resolveAxisFacing( + Direction preferredFacing, + Direction positiveFacing, + Direction negativeFacing, + boolean hasPositiveNeighbor, + boolean hasNegativeNeighbor ) { - List placements = new ArrayList<>(); - - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) - continue; - - Vector position = new Vector( - center.getBlockX() + dx, - center.getBlockY(), - center.getBlockZ() + dz - ); - - PositionKey key = PositionKey.from(position); - - if (isCenterColumnCollision(key, centerPositions, centerColumns)) - continue; - - placements.add(new RailSidePlacement(position, getFallbackFacing(dx, dz, direction))); - } - } - - return placements; - } - - private Direction getFallbackFacing(int offsetX, int offsetZ, RailDirection direction) { - if (Math.abs(direction.dx()) >= Math.abs(direction.dz()) && direction.dx() != 0) - return getXFacing(direction.dx()); - - if (direction.dz() != 0) - return getZFacing(direction.dz()); - - if (Math.abs(offsetX) >= Math.abs(offsetZ) && offsetX != 0) - return getXFacing(offsetX); - - return getZFacing(offsetZ); - } - - private RailSidePair createSidePair(Vector center, RailDirection direction) { - int x = center.getBlockX(); - int y = center.getBlockY(); - int z = center.getBlockZ(); - int dx = direction.dx(); - int dz = direction.dz(); - - if (dx != 0 && dz != 0) - return new RailSidePair( - new RailSidePlacement(new Vector(x + dx, y, z), getZFacing(dz)), - new RailSidePlacement(new Vector(x, y, z + dz), getXFacing(dx)), - 0 - ); - - if (dx != 0) - return new RailSidePair( - new RailSidePlacement(new Vector(x, y, z + 1), getXFacing(dx)), - new RailSidePlacement(new Vector(x, y, z - 1), getXFacing(dx)), - 0 - ); + if (preferredFacing == positiveFacing && hasPositiveNeighbor || preferredFacing == negativeFacing && hasNegativeNeighbor) + return preferredFacing; - return new RailSidePair( - new RailSidePlacement(new Vector(x + 1, y, z), getZFacing(dz)), - new RailSidePlacement(new Vector(x - 1, y, z), getZFacing(dz)), - 0 - ); - } + if (hasPositiveNeighbor && !hasNegativeNeighbor) + return positiveFacing; - private void addSidePairIfNew(List candidates, RailSidePair sidePair) { - for (RailSidePair candidate : candidates) { - if (candidate.hasSamePositions(sidePair)) - return; - } + if (hasNegativeNeighbor && !hasPositiveNeighbor) + return negativeFacing; - candidates.add(sidePair); + return preferredFacing == negativeFacing ? negativeFacing : positiveFacing; } - private RailDirection getRailDirection(List path, int index) { - RailDirection nextDirection = getHorizontalDirection(path, index, index + 1); - RailDirection previousDirection = getHorizontalDirection(path, index - 1, index); + private RailStep getRailStep(List path, int index, RailStep fallbackStep) { + RailStep previousStep = index > 0 ? getStep(path.get(index - 1), path.get(index)) : null; + RailStep nextStep = index < path.size() - 1 ? getStep(path.get(index), path.get(index + 1)) : null; - if (nextDirection != null && previousDirection != null) { - if (nextDirection.equals(previousDirection)) - return nextDirection; + if (previousStep != null && nextStep != null) { + int dx = Integer.compare(previousStep.dx() + nextStep.dx(), 0); + int dz = Integer.compare(previousStep.dz() + nextStep.dz(), 0); - int blendedX = Integer.compare(nextDirection.dx() + previousDirection.dx(), 0); - int blendedZ = Integer.compare(nextDirection.dz() + previousDirection.dz(), 0); - - if (blendedX != 0 || blendedZ != 0) - return new RailDirection(blendedX, blendedZ); - - return nextDirection; + if (dx != 0 || dz != 0) + return new RailStep(dx, dz); } - if (nextDirection != null) - return nextDirection; - - if (previousDirection != null) - return previousDirection; + if (nextStep != null) + return nextStep; - for (int nextIndex = index + 1; nextIndex < path.size(); nextIndex++) { - RailDirection direction = getHorizontalDirection(path, index, nextIndex); - - if (direction != null) - return direction; - } + if (previousStep != null) + return previousStep; - for (int previousIndex = index - 1; previousIndex >= 0; previousIndex--) { - RailDirection direction = getHorizontalDirection(path, previousIndex, index); - - if (direction != null) - return direction; - } - - return new RailDirection(1, 0); + return fallbackStep; } - private RailDirection getHorizontalDirection(List path, int fromIndex, int toIndex) { - if (fromIndex < 0 || toIndex < 0 || fromIndex >= path.size() || toIndex >= path.size()) - return null; - - Vector from = path.get(fromIndex); - Vector to = path.get(toIndex); + private RailStep getStep(Vector from, Vector to) { int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); if (dx == 0 && dz == 0) return null; - return new RailDirection(dx, dz); + return new RailStep(dx, dz); } private void snapMissingControlPointHeightsToTerrain(List points) { @@ -1084,20 +698,7 @@ private BlockState createCenterBlockState(Vector position) { } private BlockState createAnvilBlockState(Direction direction) { - if (BlockTypes.ANVIL == null) - return null; - - return BlockTypes.ANVIL - .getDefaultState() - .with(PropertyKey.FACING, direction); - } - - private Direction getXFacing(int dx) { - return dx >= 0 ? Direction.EAST : Direction.WEST; - } - - private Direction getZFacing(int dz) { - return dz >= 0 ? Direction.SOUTH : Direction.NORTH; + return GeneratorUtils.getBlockStateWithFacing(BlockTypes.ANVIL, direction); } private Vector toBlockVector(Vector vector) { @@ -1132,34 +733,12 @@ private boolean isSameBlock(Vector first, Vector second) { && first.getBlockZ() == second.getBlockZ(); } - private record RailDirection(int dx, int dz) { + private record RailStep(int dx, int dz) { } private record RailSidePlacement(Vector position, Direction facing) { } - private record RailSidePair(RailSidePlacement first, RailSidePlacement second, int fallbackPenalty) { - - private RailSidePair reversed() { - return new RailSidePair(second, first, fallbackPenalty); - } - - private boolean hasDuplicatePosition() { - return isSamePosition(first, second); - } - - private boolean hasSamePositions(RailSidePair other) { - return (isSamePosition(first, other.first) && isSamePosition(second, other.second)) - || (isSamePosition(first, other.second) && isSamePosition(second, other.first)); - } - - private static boolean isSamePosition(RailSidePlacement first, RailSidePlacement second) { - return first.position().getBlockX() == second.position().getBlockX() - && first.position().getBlockY() == second.position().getBlockY() - && first.position().getBlockZ() == second.position().getBlockZ(); - } - } - private record PositionKey(int x, int y, int z) { private static PositionKey from(Vector vector) { @@ -1184,6 +763,10 @@ private record ColumnKey(int x, int z) { private static ColumnKey from(PositionKey key) { return new ColumnKey(key.x(), key.z()); } + + private static ColumnKey of(int x, int z) { + return new ColumnKey(x, z); + } } private static class RailSideBlock { @@ -1213,7 +796,7 @@ private int getSupportScore() { } private Direction getPreferredFacing() { - Direction preferredFacing = Direction.EAST; + Direction preferredFacing = DEFAULT_FACING; int preferredScore = -1; for (Map.Entry entry : facingScores.entrySet()) { From fd244c74b784f7cd2501c75f7a0f154883e2be67 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Tue, 26 May 2026 18:13:48 +0200 Subject: [PATCH 12/18] style(generator): restore single-line guard statements for railscripts --- .../components/rail/RailScripts.java | 93 +++++++------------ 1 file changed, 31 insertions(+), 62 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 19e0f16d..784bce28 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -60,8 +60,7 @@ public RailScripts(Player player, GeneratorComponent generatorComponent, List getControlPoints() { - if (customControlPoints != null && customControlPoints.size() >= 2) - return copyPoints(customControlPoints); + if (customControlPoints != null && customControlPoints.size() >= 2) return copyPoints(customControlPoints); List selectionPoints = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - if (selectionPoints == null) - return Collections.emptyList(); + if (selectionPoints == null) return Collections.emptyList(); return copyPoints(selectionPoints); } @@ -149,8 +145,7 @@ private List createRailSelectionPoints(List points) { List shiftedPoints = GeneratorUtils.shiftPoints(selectionLine, SELECTION_PADDING, true); - if (shiftedPoints == null || shiftedPoints.size() < 3) - return createBoundsSelectionPoints(points); + if (shiftedPoints == null || shiftedPoints.size() < 3) return createBoundsSelectionPoints(points); return shiftedPoints; } @@ -162,8 +157,7 @@ private List createCenterPath(List points) { private List createShortestPath(List points) { List path = new ArrayList<>(); - if (points.isEmpty()) - return path; + if (points.isEmpty()) return path; addPointIfNew(path, points.get(0)); @@ -198,8 +192,7 @@ private List removeOrthogonalCorners(List path) { List result = new ArrayList<>(); for (int index = 0; index < path.size(); index++) { - if (index > 0 && index < path.size() - 1 && isOrthogonalCorner(path.get(index - 1), path.get(index), path.get(index + 1))) - continue; + if (index > 0 && index < path.size() - 1 && isOrthogonalCorner(path.get(index - 1), path.get(index), path.get(index + 1))) continue; result.add(path.get(index)); } @@ -302,8 +295,7 @@ private void addSidePlacement(List placements, Vector positio PositionKey key = PositionKey.from(position); for (RailSidePlacement placement : placements) { - if (PositionKey.from(placement.position()).equals(key)) - return; + if (PositionKey.from(placement.position()).equals(key)) return; } placements.add(new RailSidePlacement(position, facing)); @@ -317,8 +309,7 @@ private void addSideBlock( ) { PositionKey key = PositionKey.from(sidePlacement.position()); - if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key))) - return; + if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key))) return; sideBlocks .computeIfAbsent(key, ignored -> new RailSideBlock(key)) @@ -381,8 +372,7 @@ private void repairMissingSideBlocks( Vector center = path.get(index); int sideBlockCount = getAdjacentSideBlockCount(center, sideBlocks); - if (sideBlockCount >= 2) - continue; + if (sideBlockCount >= 2) continue; List repairPlacements = getSidePlacements(path, index); repairPlacements.addAll(getFallbackSidePlacements(center, getRailStep(path, index, new RailStep(1, 0)))); @@ -398,8 +388,7 @@ private void repairMissingSideBlocks( sideBlockCount ); - if (sideBlockCount >= 2) - break; + if (sideBlockCount >= 2) break; } } } @@ -409,8 +398,7 @@ private List getFallbackSidePlacements(Vector center, RailSte for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) - continue; + if (dx == 0 && dz == 0) continue; addSidePlacement( placements, @@ -424,14 +412,11 @@ private List getFallbackSidePlacements(Vector center, RailSte } private Direction getFallbackFacing(int offsetX, int offsetZ, RailStep step) { - if (Math.abs(step.dx()) >= Math.abs(step.dz()) && step.dx() != 0) - return GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); + if (Math.abs(step.dx()) >= Math.abs(step.dz()) && step.dx() != 0) return GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); - if (step.dz() != 0) - return GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); + if (step.dz() != 0) return GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); - if (Math.abs(offsetX) >= Math.abs(offsetZ) && offsetX != 0) - return GeneratorUtils.getFacing(offsetX, 0, DEFAULT_FACING); + if (Math.abs(offsetX) >= Math.abs(offsetZ) && offsetX != 0) return GeneratorUtils.getFacing(offsetX, 0, DEFAULT_FACING); return GeneratorUtils.getFacing(0, offsetZ, DEFAULT_FACING); } @@ -447,16 +432,14 @@ private int repairSidePlacement( ) { PositionKey key = PositionKey.from(sidePlacement.position()); - if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key)) || sideBlocks.containsKey(key)) - return sideBlockCount; + if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key)) || sideBlocks.containsKey(key)) return sideBlockCount; ColumnKey columnKey = ColumnKey.from(key); RailSideBlock existingColumnBlock = sideColumns.get(columnKey); boolean replacingAdjacentBlock = existingColumnBlock != null && isAdjacent(existingColumnBlock.key(), centerKey); if (existingColumnBlock != null) { - if (!canReplaceSideBlock(existingColumnBlock.key(), centerPositions, sideBlocks)) - return sideBlockCount; + if (!canReplaceSideBlock(existingColumnBlock.key(), centerPositions, sideBlocks)) return sideBlockCount; sideBlocks.remove(existingColumnBlock.key()); } @@ -474,8 +457,7 @@ private boolean canReplaceSideBlock( Map sideBlocks ) { for (PositionKey centerPosition : centerPositions) { - if (isAdjacent(sideBlockKey, centerPosition) && getAdjacentSideBlockCount(centerPosition.toVector(), sideBlocks) <= 2) - return false; + if (isAdjacent(sideBlockKey, centerPosition) && getAdjacentSideBlockCount(centerPosition.toVector(), sideBlocks) <= 2) return false; } return true; @@ -537,11 +519,9 @@ private Direction resolveSideBlockFacing(RailSideBlock sideBlock, Map zConnections) - return resolveAxisFacing(preferredFacing, Direction.EAST, Direction.WEST, east, west); + if (xConnections > zConnections) return resolveAxisFacing(preferredFacing, Direction.EAST, Direction.WEST, east, west); - if (zConnections > xConnections) - return resolveAxisFacing(preferredFacing, Direction.SOUTH, Direction.NORTH, south, north); + if (zConnections > xConnections) return resolveAxisFacing(preferredFacing, Direction.SOUTH, Direction.NORTH, south, north); return preferredFacing; } @@ -553,14 +533,11 @@ private Direction resolveAxisFacing( boolean hasPositiveNeighbor, boolean hasNegativeNeighbor ) { - if (preferredFacing == positiveFacing && hasPositiveNeighbor || preferredFacing == negativeFacing && hasNegativeNeighbor) - return preferredFacing; + if (preferredFacing == positiveFacing && hasPositiveNeighbor || preferredFacing == negativeFacing && hasNegativeNeighbor) return preferredFacing; - if (hasPositiveNeighbor && !hasNegativeNeighbor) - return positiveFacing; + if (hasPositiveNeighbor && !hasNegativeNeighbor) return positiveFacing; - if (hasNegativeNeighbor && !hasPositiveNeighbor) - return negativeFacing; + if (hasNegativeNeighbor && !hasPositiveNeighbor) return negativeFacing; return preferredFacing == negativeFacing ? negativeFacing : positiveFacing; } @@ -573,15 +550,12 @@ private RailStep getRailStep(List path, int index, RailStep fallbackStep int dx = Integer.compare(previousStep.dx() + nextStep.dx(), 0); int dz = Integer.compare(previousStep.dz() + nextStep.dz(), 0); - if (dx != 0 || dz != 0) - return new RailStep(dx, dz); + if (dx != 0 || dz != 0) return new RailStep(dx, dz); } - if (nextStep != null) - return nextStep; + if (nextStep != null) return nextStep; - if (previousStep != null) - return previousStep; + if (previousStep != null) return previousStep; return fallbackStep; } @@ -590,33 +564,28 @@ private RailStep getStep(Vector from, Vector to) { int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); - if (dx == 0 && dz == 0) - return null; + if (dx == 0 && dz == 0) return null; return new RailStep(dx, dz); } private void snapMissingControlPointHeightsToTerrain(List points) { - if (blocks == null || !hasMissingControlPointHeights(points)) - return; + if (blocks == null || !hasMissingControlPointHeights(points)) return; GeneratorUtils.adjustHeight(points, blocks); } private boolean hasMissingControlPointHeights(List points) { - if (customControlPoints != null) - return false; + if (customControlPoints != null) return false; for (Vector point : points) - if (point.getBlockY() != 0) - return false; + if (point.getBlockY() != 0) return false; return true; } private List getRestoreSelectionPoints() { - if (customControlPoints != null && railSelectionPoints.size() >= 3) - return railSelectionPoints; + if (customControlPoints != null && railSelectionPoints.size() >= 3) return railSelectionPoints; return controlPoints; } From 4fa4de9728a12ab49e8676f1684d43902f21e71e Mon Sep 17 00:00:00 2001 From: Jasupa Date: Tue, 26 May 2026 19:13:27 +0200 Subject: [PATCH 13/18] refactor(rail): remove custom rail point commands --- .../generator/components/rail/Rail.java | 171 +----------------- .../components/rail/RailScripts.java | 16 +- .../components/rail/RailSettings.java | 45 +---- 3 files changed, 3 insertions(+), 229 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index c891a060..deaeb054 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -4,26 +4,17 @@ import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; -import org.bukkit.block.Block; import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.List; public class Rail extends GeneratorComponent { - private static final int TARGET_BLOCK_RANGE = 200; - private static final int CUSTOM_SELECTION_PADDING = 4; - private static final int CUSTOM_SELECTION_VERTICAL_PADDING = 12; - public Rail() { super(GeneratorType.RAILWAY); } @Override public void analyzeCommand(Player player, String[] args) { - if (getRailSettings(player) == null) + if (!getPlayerSettings().containsKey(player.getUniqueId())) addPlayerSetting(player); if (args.length >= 2) { @@ -35,21 +26,6 @@ public void analyzeCommand(Player player, String[] args) { return; } - case "add", "point" -> { - addPoint(player); - return; - } - - case "clear", "reset" -> { - clearPoints(player); - return; - } - - case "points", "list" -> { - listPoints(player); - return; - } - default -> { player.sendMessage("§cUnknown rail command: §7" + args[1]); sendHelp(player); @@ -63,11 +39,6 @@ public void analyzeCommand(Player player, String[] args) { @Override public boolean checkForPlayer(Player player) { - RailSettings settings = getRailSettings(player); - - if (settings != null && settings.hasEnoughCustomControlPoints()) - return true; - return !GeneratorUtils.checkForNoWorldEditSelection(player); } @@ -76,146 +47,6 @@ public void generate(Player player) { if (!GeneratorModule.getInstance().getRail().checkForPlayer(player)) return; - RailSettings settings = getRailSettings(player); - - if (settings != null && settings.hasEnoughCustomControlPoints()) { - List customControlPoints = new ArrayList<>(settings.getCustomControlPoints()); - createCustomPointSelection(player, customControlPoints); - new RailScripts(player, this, customControlPoints); - return; - } - new RailScripts(player, this); } - - private void createCustomPointSelection(Player player, List customControlPoints) { - List selectionLine = new ArrayList<>(customControlPoints); - - if (selectionLine.size() >= 2) - selectionLine = GeneratorUtils.extendPolyLine(selectionLine); - - List selectionPoints = GeneratorUtils.shiftPoints(selectionLine, CUSTOM_SELECTION_PADDING, true); - - if (selectionPoints == null || selectionPoints.size() < 3) { - Vector[] bounds = getCustomPointBounds(customControlPoints); - GeneratorUtils.createCuboidSelection(player, bounds[0], bounds[1]); - return; - } - - GeneratorUtils.createPolySelection( - player, - selectionPoints, - getCustomSelectionMinY(player, customControlPoints), - getCustomSelectionMaxY(player, customControlPoints) - ); - } - - private Vector[] getCustomPointBounds(List customControlPoints) { - int minX = Integer.MAX_VALUE; - int minY = Integer.MAX_VALUE; - int minZ = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - int maxY = Integer.MIN_VALUE; - int maxZ = Integer.MIN_VALUE; - - for (Vector point : customControlPoints) { - minX = Math.min(minX, point.getBlockX() - CUSTOM_SELECTION_PADDING); - minY = Math.min(minY, point.getBlockY() - CUSTOM_SELECTION_VERTICAL_PADDING); - minZ = Math.min(minZ, point.getBlockZ() - CUSTOM_SELECTION_PADDING); - maxX = Math.max(maxX, point.getBlockX() + CUSTOM_SELECTION_PADDING); - maxY = Math.max(maxY, point.getBlockY() + CUSTOM_SELECTION_VERTICAL_PADDING); - maxZ = Math.max(maxZ, point.getBlockZ() + CUSTOM_SELECTION_PADDING); - } - - return new Vector[]{ - new Vector(minX, minY, minZ), - new Vector(maxX, maxY, maxZ) - }; - } - - private int getCustomSelectionMinY(Player player, List customControlPoints) { - int minY = Integer.MAX_VALUE; - - for (Vector point : customControlPoints) - minY = Math.min(minY, point.getBlockY()); - - return Math.max(player.getWorld().getMinHeight(), minY - CUSTOM_SELECTION_VERTICAL_PADDING); - } - - private int getCustomSelectionMaxY(Player player, List customControlPoints) { - int maxY = Integer.MIN_VALUE; - - for (Vector point : customControlPoints) - maxY = Math.max(maxY, point.getBlockY()); - - return Math.min(player.getWorld().getMaxHeight() - 1, maxY + CUSTOM_SELECTION_VERTICAL_PADDING); - } - - private void addPoint(Player player) { - RailSettings settings = getRailSettings(player); - - if (settings == null) { - player.sendMessage("§cRail settings could not be loaded."); - return; - } - - Block targetBlock = player.getTargetBlockExact(TARGET_BLOCK_RANGE); - - if (targetBlock == null) { - player.sendMessage("§cLook at a block first to add a rail point."); - return; - } - - Vector point = new Vector( - targetBlock.getX(), - targetBlock.getY() + 1, - targetBlock.getZ() - ); - - settings.addCustomControlPoint(point); - - player.sendMessage("§aAdded rail point §7#" + settings.getCustomControlPoints().size() - + " §8(" + point.getBlockX() + ", " + point.getBlockY() + ", " + point.getBlockZ() + ")"); - } - - private void clearPoints(Player player) { - RailSettings settings = getRailSettings(player); - - if (settings == null) { - player.sendMessage("§cRail settings could not be loaded."); - return; - } - - settings.clearCustomControlPoints(); - player.sendMessage("§aCleared all custom rail points."); - } - - private void listPoints(Player player) { - RailSettings settings = getRailSettings(player); - - if (settings == null) { - player.sendMessage("§cRail settings could not be loaded."); - return; - } - - List points = settings.getCustomControlPoints(); - - if (points.isEmpty()) { - player.sendMessage("§eNo custom rail points saved."); - return; - } - - player.sendMessage("§aSaved rail points:"); - - for (int index = 0; index < points.size(); index++) { - Vector point = points.get(index); - - player.sendMessage("§7#" + (index + 1) - + " §8(" + point.getBlockX() + ", " + point.getBlockY() + ", " + point.getBlockZ() + ")"); - } - } - - private RailSettings getRailSettings(Player player) { - return (RailSettings) getPlayerSettings().get(player.getUniqueId()); - } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 784bce28..8d4d59db 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -35,20 +35,12 @@ public class RailScripts extends Script { XMaterial.COBBLESTONE }; - private final List customControlPoints; - private Block[][][] blocks; private List controlPoints = new ArrayList<>(); private List centerPath = new ArrayList<>(); - private List railSelectionPoints = new ArrayList<>(); public RailScripts(Player player, GeneratorComponent generatorComponent) { - this(player, generatorComponent, null); - } - - public RailScripts(Player player, GeneratorComponent generatorComponent, List customControlPoints) { super(player, generatorComponent); - this.customControlPoints = customControlPoints; Thread thread = new Thread(() -> { prepareSession(); @@ -62,7 +54,7 @@ private void prepareSession() { if (!hasValidControlPoints()) return; - railSelectionPoints = createRailSelectionPoints(controlPoints); + List railSelectionPoints = createRailSelectionPoints(controlPoints); GeneratorUtils.createPolySelection( getPlayer(), railSelectionPoints, @@ -128,8 +120,6 @@ private boolean hasValidControlPoints() { } private List getControlPoints() { - if (customControlPoints != null && customControlPoints.size() >= 2) return copyPoints(customControlPoints); - List selectionPoints = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); if (selectionPoints == null) return Collections.emptyList(); @@ -576,8 +566,6 @@ private void snapMissingControlPointHeightsToTerrain(List points) { } private boolean hasMissingControlPointHeights(List points) { - if (customControlPoints != null) return false; - for (Vector point : points) if (point.getBlockY() != 0) return false; @@ -585,8 +573,6 @@ private boolean hasMissingControlPointHeights(List points) { } private List getRestoreSelectionPoints() { - if (customControlPoints != null && railSelectionPoints.size() >= 3) return railSelectionPoints; - return controlPoints; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java index eeb4ef0b..414ad632 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java @@ -2,57 +2,14 @@ import net.buildtheearth.buildteamtools.modules.generator.model.Settings; import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; public class RailSettings extends Settings { - private List customControlPoints; - public RailSettings(Player player) { super(player); } @Override public void setDefaultValues() { - if (customControlPoints == null) { - customControlPoints = new ArrayList<>(); - return; - } - - customControlPoints.clear(); - } - - public void addCustomControlPoint(Vector point) { - ensureCustomControlPoints(); - - customControlPoints.add(new Vector( - point.getBlockX(), - point.getBlockY(), - point.getBlockZ() - )); - } - - public void clearCustomControlPoints() { - ensureCustomControlPoints(); - customControlPoints.clear(); - } - - public List getCustomControlPoints() { - ensureCustomControlPoints(); - return Collections.unmodifiableList(customControlPoints); - } - - public boolean hasEnoughCustomControlPoints() { - ensureCustomControlPoints(); - return customControlPoints.size() >= 2; - } - - private void ensureCustomControlPoints() { - if (customControlPoints == null) - customControlPoints = new ArrayList<>(); } -} \ No newline at end of file +} From bf13bfcef7265ae10200a647b3f08fa874f6df4e Mon Sep 17 00:00:00 2001 From: Jasupa Date: Wed, 27 May 2026 20:59:16 +0200 Subject: [PATCH 14/18] fix(generator): address review feedback Count position block changes by placement count, use persistent data for internal generator commands, remove unused rail flag handling and the unused help overload, restore the field menu flow, and simplify rail path point handling. --- .../generator/components/rail/RailFlag.java | 22 --------------- .../components/rail/RailScripts.java | 28 ++----------------- .../listeners/GeneratorListener.java | 10 +++++-- .../modules/generator/menu/GeneratorMenu.java | 15 ++++++++-- .../modules/generator/model/Command.java | 14 ++++------ .../modules/generator/model/Flag.java | 3 +- .../generator/model/GeneratorComponent.java | 7 +---- .../modules/generator/model/Script.java | 2 +- 8 files changed, 32 insertions(+), 69 deletions(-) delete mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java deleted file mode 100644 index 0b12dcd1..00000000 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.buildtheearth.buildteamtools.modules.generator.components.rail; - -import net.buildtheearth.buildteamtools.modules.generator.model.Flag; -import net.buildtheearth.buildteamtools.modules.generator.model.FlagType; - -public enum RailFlag implements Flag { - ; - - @Override - public String getFlag() { - return null; - } - - @Override - public FlagType getFlagType() { - return null; - } - - public static RailFlag byString(String flag) { - return null; - } -} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 8d4d59db..59a5f6af 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -50,7 +50,7 @@ public RailScripts(Player player, GeneratorComponent generatorComponent) { } private void prepareSession() { - controlPoints = sanitizePoints(getControlPoints()); + controlPoints = getControlPoints(); if (!hasValidControlPoints()) return; @@ -149,7 +149,7 @@ private List createShortestPath(List points) { if (points.isEmpty()) return path; - addPointIfNew(path, points.get(0)); + path.add(toBlockVector(points.get(0))); for (int index = 0; index < points.size() - 1; index++) appendShortestLine(path, points.get(index), points.get(index + 1)); @@ -170,7 +170,7 @@ private void appendShortestLine(List path, Vector start, Vector end) { for (int step = 1; step <= horizontalSteps; step++) { double progress = step / (double) horizontalSteps; - addPointIfNew(path, new Vector( + path.add(new Vector( start.getBlockX() + (int) Math.round(deltaX * progress), start.getBlockY() + (int) Math.round(deltaY * progress), start.getBlockZ() + (int) Math.round(deltaZ * progress) @@ -625,15 +625,6 @@ private int getMaxPointY(List points) { return maxY == Integer.MIN_VALUE ? getPlayer().getWorld().getMaxHeight() - 1 : maxY; } - private List sanitizePoints(List points) { - List sanitizedPoints = new ArrayList<>(); - - for (Vector point : points) - addPointIfNew(sanitizedPoints, toBlockVector(point)); - - return sanitizedPoints; - } - private List copyPoints(List points) { List copiedPoints = new ArrayList<>(); @@ -664,13 +655,6 @@ private Vector toBlockVector(Vector vector) { ); } - private void addPointIfNew(List points, Vector point) { - Vector blockPoint = toBlockVector(point); - - if (points.isEmpty() || !isSameBlock(points.get(points.size() - 1), blockPoint)) - points.add(blockPoint); - } - private void replaceLastPoint(List points, Vector point) { Vector blockPoint = toBlockVector(point); @@ -682,12 +666,6 @@ private void replaceLastPoint(List points, Vector point) { points.set(points.size() - 1, blockPoint); } - private boolean isSameBlock(Vector first, Vector second) { - return first.getBlockX() == second.getBlockX() - && first.getBlockY() == second.getBlockY() - && first.getBlockZ() == second.getBlockZ(); - } - private record RailStep(int dx, int dz) { } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java index 79494019..0dffe9a4 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java @@ -1,8 +1,10 @@ package net.buildtheearth.buildteamtools.modules.generator.listeners; import com.alpsbte.alpslib.utils.ChatHelper; +import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -10,10 +12,12 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.persistence.PersistentDataType; public class GeneratorListener implements Listener { - public static final String INTERNAL_GENERATOR_COMMAND_METADATA = "btt-internal-generator-command"; + public static final NamespacedKey INTERNAL_GENERATOR_COMMAND_KEY = + new NamespacedKey(BuildTeamTools.getInstance(), "internal_generator_command"); @EventHandler public void onCommand(PlayerCommandPreprocessEvent e) { @@ -25,7 +29,7 @@ public void onCommand(PlayerCommandPreprocessEvent e) { if (!e.getMessage().startsWith("//")) return; - if (p.hasMetadata(INTERNAL_GENERATOR_COMMAND_METADATA)) + if (p.getPersistentDataContainer().has(INTERNAL_GENERATOR_COMMAND_KEY, PersistentDataType.BYTE)) return; e.setCancelled(true); @@ -50,4 +54,4 @@ public void onInteract(PlayerInteractEvent e) { p.playSound(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F); ChatHelper.sendErrorMessage(p, "You can't use WorldEdit while generating a structure."); } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java index 61069e1d..d7674658 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java @@ -4,6 +4,9 @@ import com.cryptomorin.xseries.XMaterial; import net.buildtheearth.buildteamtools.modules.common.CommonModule; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.generator.components.field.Field; +import net.buildtheearth.buildteamtools.modules.generator.components.field.FieldSettings; +import net.buildtheearth.buildteamtools.modules.generator.components.field.menu.CropTypeMenu; import net.buildtheearth.buildteamtools.modules.generator.components.house.House; import net.buildtheearth.buildteamtools.modules.generator.components.house.HouseSettings; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; @@ -249,7 +252,15 @@ protected void setItemClickEventsAsync() { return; } - sendMoreInformation(clickPlayer, GeneratorType.FIELD); + Field field = GeneratorModule.getInstance().getField(); + field.getPlayerSettings().put(clickPlayer.getUniqueId(), new FieldSettings(clickPlayer)); + + if (!field.checkForPlayer(clickPlayer)) + return; + + clickPlayer.closeInventory(); + clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); + new CropTypeMenu(clickPlayer, true); })); } @@ -275,4 +286,4 @@ protected Mask getMask() { .pattern("111111111") .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index 34d34153..b266e33a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -25,7 +25,7 @@ import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; -import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.util.Vector; import java.util.Arrays; @@ -265,18 +265,16 @@ public void processOperation(Operation operation) { } private void runInternalGeneratorCommand(String command) { - player.setMetadata( - GeneratorListener.INTERNAL_GENERATOR_COMMAND_METADATA, - new FixedMetadataValue(BuildTeamTools.getInstance(), true) + player.getPersistentDataContainer().set( + GeneratorListener.INTERNAL_GENERATOR_COMMAND_KEY, + PersistentDataType.BYTE, + (byte) 1 ); try { player.chat(command); } finally { - player.removeMetadata( - GeneratorListener.INTERNAL_GENERATOR_COMMAND_METADATA, - BuildTeamTools.getInstance() - ); + player.getPersistentDataContainer().remove(GeneratorListener.INTERNAL_GENERATOR_COMMAND_KEY); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java index c95f7177..9fecd3d2 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java @@ -2,7 +2,6 @@ import net.buildtheearth.buildteamtools.modules.generator.components.field.FieldFlag; import net.buildtheearth.buildteamtools.modules.generator.components.house.HouseFlag; -import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailFlag; import net.buildtheearth.buildteamtools.modules.generator.components.road.RoadFlag; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeFlag; import org.jspecify.annotations.NonNull; @@ -19,7 +18,7 @@ static Flag byString(@NonNull GeneratorType generatorType, String flag) { case HOUSE -> HouseFlag.byString(flag); case ROAD -> RoadFlag.byString(flag); case TREE -> TreeFlag.byString(flag); - case RAILWAY -> RailFlag.byString(flag); + case RAILWAY -> null; case FIELD -> FieldFlag.byString(flag); }; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java index b4b429f1..43d9113b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java @@ -82,11 +82,6 @@ public void addPlayerSetting(Player p) { } } - public void sendHelp(Player p, String @NonNull [] args) { - if (isHelpCommand(args)) - sendHelp(p); - } - public void sendHelp(@NonNull Player p) { p.sendMessage(Component.text(getWikiPage(), NamedTextColor.YELLOW)); } @@ -197,4 +192,4 @@ protected void convertArgsToSettings(Player p, String[] args) { public String getWikiPage() { return generatorType.getWikiPage(); } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java index 3d039b6e..70fe8205 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java @@ -254,7 +254,7 @@ public void setBlockStatesAtPositions(List positions, List b positions.toArray(new Vector[0]), blockStates.toArray(new BlockState[0]) )); - changes++; + changes += positions.size(); } /** From ed56d63aa9367be0bf5845d958ec2a7fab7bed12 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Wed, 27 May 2026 22:18:08 +0200 Subject: [PATCH 15/18] fix(generator): address rail review feedback Apply documentation click events to the full message, use shared command argument handling for rail, rename the rail generator enum, use generator error messages on failures, and extract history entry and block change models. --- .../generator/commands/GeneratorCommand.java | 6 +- .../generator/components/rail/Rail.java | 27 +---- .../components/rail/RailScripts.java | 11 +- .../modules/generator/menu/GeneratorMenu.java | 17 ++- .../modules/generator/model/Command.java | 11 ++ .../modules/generator/model/Flag.java | 2 +- .../generator/model/GeneratorComponent.java | 21 +++- .../generator/model/GeneratorType.java | 2 +- .../modules/generator/model/History.java | 113 +----------------- .../modules/generator/model/Script.java | 2 +- 10 files changed, 50 insertions(+), 162 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java index c13f0e8b..803a6099 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java @@ -3,7 +3,7 @@ import com.alpsbte.alpslib.utils.ChatHelper; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.menu.GeneratorMenu; -import net.buildtheearth.buildteamtools.modules.generator.model.History; +import net.buildtheearth.buildteamtools.modules.generator.model.HistoryEntry; import net.buildtheearth.buildteamtools.modules.network.model.Permissions; import net.buildtheearth.buildteamtools.utils.Utils; import org.bukkit.command.Command; @@ -58,7 +58,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N } ChatHelper.sendMessageBox(sender, "Generator History for " + p.getName(), () -> { - for (History.HistoryEntry history : GeneratorModule.getInstance().getPlayerHistory(p).getHistoryEntries()) { + for (HistoryEntry history : GeneratorModule.getInstance().getPlayerHistory(p).getHistoryEntries()) { long timeDifference = System.currentTimeMillis() - history.getTimeCreated(); p.sendMessage("§e- " + history.getGeneratorType().name() + " §7-§e " + Utils.toDate(timeDifference) + " ago §7-§e " + history.getWorldEditCommandCount() + " Commands executed"); } @@ -92,4 +92,4 @@ public static void sendHelp(CommandSender sender) { sender.sendMessage("§eRedo last command:§7 /gen redo"); }); } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index deaeb054..65f0726a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -9,32 +9,7 @@ public class Rail extends GeneratorComponent { public Rail() { - super(GeneratorType.RAILWAY); - } - - @Override - public void analyzeCommand(Player player, String[] args) { - if (!getPlayerSettings().containsKey(player.getUniqueId())) - addPlayerSetting(player); - - if (args.length >= 2) { - String subCommand = args[1].toLowerCase(); - - switch (subCommand) { - case "help", "info", "?" -> { - sendHelp(player); - return; - } - - default -> { - player.sendMessage("§cUnknown rail command: §7" + args[1]); - sendHelp(player); - return; - } - } - } - - generate(player); + super(GeneratorType.RAIL); } @Override diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 59a5f6af..6cce1df6 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -5,8 +5,10 @@ import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypes; +import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.Script; +import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -43,8 +45,13 @@ public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); Thread thread = new Thread(() -> { - prepareSession(); - railScript_v_2_0(); + try { + prepareSession(); + railScript_v_2_0(); + } catch (Exception exception) { + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> getGeneratorComponent().sendError(getPlayer())); + exception.printStackTrace(); + } }); thread.start(); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java index d7674658..c6cb38a1 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java @@ -44,7 +44,7 @@ public class GeneratorMenu extends AbstractMenu { public static final int HOUSE_ITEM_SLOT = 9; public static final int ROAD_ITEM_SLOT = 11; - public static final int RAILWAY_ITEM_SLOT = 13; + public static final int RAIL_ITEM_SLOT = 13; public static final int TREE_ITEM_SLOT = 15; public static final int FIELD_ITEM_SLOT = 17; @@ -115,7 +115,7 @@ protected void setPreviewItems() { ); ItemStack railwayItem = Item.create(XMaterial.RAIL.get(), "§9Generate Railway", railwayLore); - getMenu().getSlot(RAILWAY_ITEM_SLOT).setItem(railwayItem); + getMenu().getSlot(RAIL_ITEM_SLOT).setItem(railwayItem); if (!CommonModule.getInstance().getDependencyComponent().isSchematicBrushEnabled()) { ArrayList treeLore = ListUtil.createList( @@ -212,9 +212,9 @@ protected void setItemClickEventsAsync() { new RoadColorMenu(clickPlayer, true); })); - getMenu().getSlot(RAILWAY_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { + getMenu().getSlot(RAIL_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { - sendMoreInformation(clickPlayer, GeneratorType.RAILWAY); + sendMoreInformation(clickPlayer, GeneratorType.RAIL); return; } @@ -268,12 +268,9 @@ private void sendMoreInformation(@NonNull Player clickPlayer, @NonNull Generator String wikiPage = generator.getWikiPage(); clickPlayer.sendMessage( - Component.text("Open generator documentation: ", NamedTextColor.GRAY) - .append( - Component.text(wikiPage, NamedTextColor.RED) - .clickEvent(ClickEvent.openUrl(wikiPage)) - .hoverEvent(HoverEvent.showText(Component.text("Click to open this page", NamedTextColor.GRAY))) - ) + Component.text("Open generator documentation: " + wikiPage, NamedTextColor.GRAY) + .clickEvent(ClickEvent.openUrl(wikiPage)) + .hoverEvent(HoverEvent.showText(Component.text("Click to open this page", NamedTextColor.GRAY))) ); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index b266e33a..fa4db60c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -242,6 +242,7 @@ public void processOperation(Operation operation) { ChatHelper.logError("Error while processing command."); e.printStackTrace(); + sendGeneratorError(); } if (future != null) { @@ -254,6 +255,7 @@ public void processOperation(Operation operation) { if (ex != null) { ChatHelper.logError("Async operation failed: " + operation.getOperationType() + " - " + operation.getValuesAsString()); ex.printStackTrace(); + sendGeneratorError(); } // Remove the processed operation from the queue @@ -264,6 +266,15 @@ public void processOperation(Operation operation) { } } + private void sendGeneratorError() { + if (Bukkit.isPrimaryThread()) { + generatorComponent.sendError(player); + return; + } + + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> generatorComponent.sendError(player)); + } + private void runInternalGeneratorCommand(String command) { player.getPersistentDataContainer().set( GeneratorListener.INTERNAL_GENERATOR_COMMAND_KEY, diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java index 9fecd3d2..2d2676c9 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java @@ -18,7 +18,7 @@ static Flag byString(@NonNull GeneratorType generatorType, String flag) { case HOUSE -> HouseFlag.byString(flag); case ROAD -> RoadFlag.byString(flag); case TREE -> TreeFlag.byString(flag); - case RAILWAY -> null; + case RAIL -> null; case FIELD -> FieldFlag.byString(flag); }; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java index 43d9113b..d72b0cb4 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java @@ -47,7 +47,9 @@ public void analyzeCommand(Player p, String[] args) { } addPlayerSetting(p); - convertArgsToSettings(p, args); + if (!convertArgsToSettings(p, args)) + return; + generate(p); } @@ -70,7 +72,7 @@ public void addPlayerSetting(Player p) { case ROAD: addPlayerSetting(p.getUniqueId(), new RoadSettings(p)); break; - case RAILWAY: + case RAIL: addPlayerSetting(p.getUniqueId(), new RailSettings(p)); break; case TREE: @@ -102,7 +104,7 @@ public String getCommand(@NonNull Player p) { String type = switch (generatorType) { case HOUSE -> "house"; case ROAD -> "road"; - case RAILWAY -> "rail"; + case RAIL -> "rail"; case TREE -> "tree"; case FIELD -> "field"; }; @@ -138,7 +140,7 @@ public void sendSuccessMessage(Player p) { String type = switch (generatorType) { case HOUSE -> "House"; case ROAD -> "Road"; - case RAILWAY -> "Rail"; + case RAIL -> "Rail"; case TREE -> "Tree"; case FIELD -> "Field"; }; @@ -155,7 +157,9 @@ public void sendSuccessMessage(Player p) { * WALL_COLOR: 123:12 * ROOF_TYPE: 456:78 */ - protected void convertArgsToSettings(Player p, String[] args) { + protected boolean convertArgsToSettings(Player p, String[] args) { + boolean convertedFlag = false; + for (String flag : GeneratorUtils.convertArgsToFlags(args)) { String[] flagAndValue = GeneratorUtils.convertToFlagAndValue(flag, p); @@ -172,6 +176,7 @@ protected void convertArgsToSettings(Player p, String[] args) { if (finalFlag == null) continue; + convertedFlag = true; Object flagValue = FlagType.convertToFlagType(finalFlag, flagAndValue[1]); String errorMessage = FlagType.validateFlagType(finalFlag, flagValue); @@ -184,8 +189,12 @@ protected void convertArgsToSettings(Player p, String[] args) { getPlayerSettings().get(p.getUniqueId()).setValue(finalFlag, flagValue); } - if (getPlayerSettings().get(p.getUniqueId()).getValues().isEmpty() && args.length > 1) + if (!convertedFlag && args.length > 1) { sendHelp(p); + return false; + } + + return true; } @Override diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java index 53b53551..ec3f2726 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java @@ -7,7 +7,7 @@ public enum GeneratorType { HOUSE("House", WikiLinks.Gen.HOUSE), ROAD("Road", WikiLinks.Gen.ROAD), - RAILWAY("Railway", WikiLinks.Gen.RAIL), + RAIL("Rail", WikiLinks.Gen.RAIL), TREE("Tree", WikiLinks.Gen.TREE), FIELD("Field", WikiLinks.Gen.FIELD); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java index 207dfab7..cc55862c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java @@ -5,7 +5,6 @@ import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.extension.platform.Actor; import lombok.Getter; -import lombok.Setter; import net.buildtheearth.buildteamtools.BuildTeamTools; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -13,13 +12,9 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import java.util.ArrayList; -import java.util.List; public class History { @@ -108,110 +103,4 @@ public void redoCommand(Player p) { p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); }, 20L); } - - public static class HistoryEntry { - - @Getter - private final GeneratorType generatorType; - - @Getter - private final long timeCreated; - - @Getter - private final Script script; - - @Getter - private final int worldEditCommandCount; - - @Getter - private final List blockChanges; - - public HistoryEntry(GeneratorType generatorType, Script script) { - this.generatorType = generatorType; - this.timeCreated = System.currentTimeMillis(); - this.worldEditCommandCount = script.getChanges(); - this.script = script; - this.blockChanges = new ArrayList<>(); - } - - public HistoryEntry(GeneratorType generatorType, Script script, int worldEditCommandCount) { - this.generatorType = generatorType; - this.timeCreated = System.currentTimeMillis(); - this.worldEditCommandCount = worldEditCommandCount; - this.script = script; - this.blockChanges = new ArrayList<>(); - } - - public HistoryEntry(GeneratorType generatorType, Script script, List blockChanges) { - this.generatorType = generatorType; - this.timeCreated = System.currentTimeMillis(); - this.worldEditCommandCount = 0; - this.script = script; - this.blockChanges = blockChanges == null ? new ArrayList<>() : blockChanges; - } - - public boolean hasBlockChanges() { - return blockChanges != null && !blockChanges.isEmpty(); - } - - public void applyUndo() { - for (int i = blockChanges.size() - 1; i >= 0; i--) - blockChanges.get(i).applyOld(); - } - - public void applyRedo() { - for (BlockChange blockChange : blockChanges) - blockChange.applyNew(); - } - } - - public static class BlockChange { - - @Getter - private final String worldName; - - @Getter - private final int x; - - @Getter - private final int y; - - @Getter - private final int z; - - @Getter - private final String oldBlockData; - - @Getter - @Setter - private String newBlockData; - - public BlockChange(String worldName, int x, int y, int z, String oldBlockData, String newBlockData) { - this.worldName = worldName; - this.x = x; - this.y = y; - this.z = z; - this.oldBlockData = oldBlockData; - this.newBlockData = newBlockData; - } - - public void applyOld() { - apply(oldBlockData); - } - - public void applyNew() { - apply(newBlockData); - } - - private void apply(String blockDataString) { - World world = Bukkit.getWorld(worldName); - - if (world == null) - return; - - Block block = world.getBlockAt(x, y, z); - BlockData blockData = Bukkit.createBlockData(blockDataString); - block.setBlockData(blockData, false); - } - } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java index 70fe8205..aaa81927 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java @@ -59,7 +59,7 @@ protected void finish(Block[][][] blocks, List points){ //setGmask(null); GeneratorModule.getInstance().getGeneratorCommands().add(new Command(this, blocks)); - GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new History.HistoryEntry(getGeneratorComponent().getGeneratorType(), this)); + GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new HistoryEntry(getGeneratorComponent().getGeneratorType(), this)); } From e21c5ab359059e02c4d09f7c2ae5a90f8f76d635 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Thu, 28 May 2026 12:11:21 +0200 Subject: [PATCH 16/18] fix(generator): address command execution review feedback Replace player metadata with an internal command queue, use the non-deprecated WorldEdit setBlock overload, combine duplicate catch handling, run rail generation through the Bukkit scheduler, and keep history models extracted. --- .../components/rail/RailScripts.java | 3 +- .../listeners/GeneratorListener.java | 51 +++++++++++++-- .../modules/generator/model/BlockChange.java | 58 +++++++++++++++++ .../modules/generator/model/Command.java | 15 ++--- .../modules/generator/model/HistoryEntry.java | 62 +++++++++++++++++++ 5 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/BlockChange.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 6cce1df6..8c653eca 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -44,7 +44,7 @@ public class RailScripts extends Script { public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); - Thread thread = new Thread(() -> { + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { try { prepareSession(); railScript_v_2_0(); @@ -53,7 +53,6 @@ public RailScripts(Player player, GeneratorComponent generatorComponent) { exception.printStackTrace(); } }); - thread.start(); } private void prepareSession() { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java index 0dffe9a4..6fb10e7c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java @@ -1,10 +1,8 @@ package net.buildtheearth.buildteamtools.modules.generator.listeners; import com.alpsbte.alpslib.utils.ChatHelper; -import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import org.bukkit.Material; -import org.bukkit.NamespacedKey; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -12,12 +10,53 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.persistence.PersistentDataType; + +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; public class GeneratorListener implements Listener { - public static final NamespacedKey INTERNAL_GENERATOR_COMMAND_KEY = - new NamespacedKey(BuildTeamTools.getInstance(), "internal_generator_command"); + private static final Map> INTERNAL_GENERATOR_COMMANDS = new ConcurrentHashMap<>(); + + public static void queueInternalGeneratorCommand(Player player, String command) { + INTERNAL_GENERATOR_COMMANDS + .computeIfAbsent(player.getUniqueId(), ignored -> new ConcurrentLinkedQueue<>()) + .add(command); + } + + public static void removeInternalGeneratorCommand(Player player, String command) { + Queue commands = INTERNAL_GENERATOR_COMMANDS.get(player.getUniqueId()); + + if (commands == null) + return; + + commands.remove(command); + + if (commands.isEmpty()) + INTERNAL_GENERATOR_COMMANDS.remove(player.getUniqueId(), commands); + } + + private static boolean consumeInternalGeneratorCommand(Player player, String command) { + Queue commands = INTERNAL_GENERATOR_COMMANDS.get(player.getUniqueId()); + + if (commands == null) + return false; + + String queuedCommand = commands.peek(); + + if (!command.equals(queuedCommand)) + return false; + + commands.poll(); + + if (commands.isEmpty()) + INTERNAL_GENERATOR_COMMANDS.remove(player.getUniqueId(), commands); + + return true; + } @EventHandler public void onCommand(PlayerCommandPreprocessEvent e) { @@ -29,7 +68,7 @@ public void onCommand(PlayerCommandPreprocessEvent e) { if (!e.getMessage().startsWith("//")) return; - if (p.getPersistentDataContainer().has(INTERNAL_GENERATOR_COMMAND_KEY, PersistentDataType.BYTE)) + if (consumeInternalGeneratorCommand(p, e.getMessage())) return; e.setCancelled(true); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/BlockChange.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/BlockChange.java new file mode 100644 index 00000000..2fe94289 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/BlockChange.java @@ -0,0 +1,58 @@ +package net.buildtheearth.buildteamtools.modules.generator.model; + +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; + +public class BlockChange { + + @Getter + private final String worldName; + + @Getter + private final int x; + + @Getter + private final int y; + + @Getter + private final int z; + + @Getter + private final String oldBlockData; + + @Getter + @Setter + private String newBlockData; + + public BlockChange(String worldName, int x, int y, int z, String oldBlockData, String newBlockData) { + this.worldName = worldName; + this.x = x; + this.y = y; + this.z = z; + this.oldBlockData = oldBlockData; + this.newBlockData = newBlockData; + } + + public void applyOld() { + apply(oldBlockData); + } + + public void applyNew() { + apply(newBlockData); + } + + private void apply(String blockDataString) { + World world = Bukkit.getWorld(worldName); + + if (world == null) + return; + + Block block = world.getBlockAt(x, y, z); + BlockData blockData = Bukkit.createBlockData(blockDataString); + block.setBlockData(blockData, false); + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index fa4db60c..15f2bdc9 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -4,9 +4,9 @@ import com.alpsbte.alpslib.utils.GeneratorUtils; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; @@ -25,7 +25,6 @@ import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; -import org.bukkit.persistence.PersistentDataType; import org.bukkit.util.Vector; import java.util.Arrays; @@ -276,16 +275,12 @@ private void sendGeneratorError() { } private void runInternalGeneratorCommand(String command) { - player.getPersistentDataContainer().set( - GeneratorListener.INTERNAL_GENERATOR_COMMAND_KEY, - PersistentDataType.BYTE, - (byte) 1 - ); + GeneratorListener.queueInternalGeneratorCommand(player, command); try { player.chat(command); } finally { - player.getPersistentDataContainer().remove(GeneratorListener.INTERNAL_GENERATOR_COMMAND_KEY); + GeneratorListener.removeInternalGeneratorCommand(player, command); } } @@ -303,14 +298,12 @@ private CompletableFuture setBlockStatesAtPositions(List positions Vector position = positions.get(index); editSession.setBlock( BlockVector3.at(position.getBlockX(), position.getBlockY(), position.getBlockZ()), - blockState + (Pattern) blockState ); } GeneratorUtils.saveEditSession(editSession, localSession, actor); future.complete(null); - } catch (MaxChangedBlocksException e) { - future.completeExceptionally(e); } catch (Exception e) { future.completeExceptionally(e); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java new file mode 100644 index 00000000..d80fc1b9 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java @@ -0,0 +1,62 @@ +package net.buildtheearth.buildteamtools.modules.generator.model; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class HistoryEntry { + + @Getter + private final GeneratorType generatorType; + + @Getter + private final long timeCreated; + + @Getter + private final Script script; + + @Getter + private final int worldEditCommandCount; + + @Getter + private final List blockChanges; + + public HistoryEntry(GeneratorType generatorType, Script script) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = script.getChanges(); + this.script = script; + this.blockChanges = new ArrayList<>(); + } + + public HistoryEntry(GeneratorType generatorType, Script script, int worldEditCommandCount) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = worldEditCommandCount; + this.script = script; + this.blockChanges = new ArrayList<>(); + } + + public HistoryEntry(GeneratorType generatorType, Script script, List blockChanges) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = 0; + this.script = script; + this.blockChanges = blockChanges == null ? new ArrayList<>() : blockChanges; + } + + public boolean hasBlockChanges() { + return blockChanges != null && !blockChanges.isEmpty(); + } + + public void applyUndo() { + for (int i = blockChanges.size() - 1; i >= 0; i--) + blockChanges.get(i).applyOld(); + } + + public void applyRedo() { + for (BlockChange blockChange : blockChanges) + blockChange.applyNew(); + } +} From a8395da6f59b9f1abc21f2637343d85e4b4e4f09 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Thu, 28 May 2026 12:38:42 +0200 Subject: [PATCH 17/18] fix(rail): address rail review feedback Use a clearer WorldEdit selection check, send rail errors with Adventure components, document missing control point heights, and note that the old RailPathBuilder block comparison code no longer exists. --- .../generator/components/rail/Rail.java | 4 ++++ .../components/rail/RailScripts.java | 21 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index 65f0726a..0762e099 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -14,6 +14,10 @@ public Rail() { @Override public boolean checkForPlayer(Player player) { + return hasWorldEditSelection(player); + } + + private boolean hasWorldEditSelection(Player player) { return !GeneratorUtils.checkForNoWorldEditSelection(player); } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 8c653eca..2ae0bd78 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -8,6 +8,8 @@ import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.Script; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -87,19 +89,19 @@ private void railScript_v_2_0() { if (!hasValidControlPoints()) return; if (centerPath.size() < 2) { - getPlayer().sendMessage("§cRail Generator could not create a valid rail path."); + sendRailError("Rail Generator could not create a valid rail path."); return; } if (centerPath.size() > MAX_PATH_POINTS) { - getPlayer().sendMessage("§cRail Generator path is too large. Please use a smaller selection."); + sendRailError("Rail Generator path is too large. Please use a smaller selection."); return; } Map railBlocks = buildRailBlocks(centerPath); if (railBlocks.size() > MAX_BLOCK_PLACEMENTS) { - getPlayer().sendMessage("§cRail Generator would place too many blocks. Please use a smaller selection."); + sendRailError("Rail Generator would place too many blocks. Please use a smaller selection."); return; } @@ -113,18 +115,25 @@ private void railScript_v_2_0() { private boolean hasValidControlPoints() { if (controlPoints.size() < 2) { - getPlayer().sendMessage("§cRail Generator needs at least two points."); + sendRailError("Rail Generator needs at least two points."); return false; } if (controlPoints.size() > MAX_CONTROL_POINTS) { - getPlayer().sendMessage("§cRail Generator has too many points. Please use fewer points."); + sendRailError("Rail Generator has too many points. Please use fewer points."); return false; } return true; } + private void sendRailError(String message) { + Bukkit.getScheduler().runTask( + BuildTeamTools.getInstance(), + () -> getPlayer().sendMessage(Component.text(message, NamedTextColor.RED)) + ); + } + private List getControlPoints() { List selectionPoints = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); @@ -572,6 +581,8 @@ private void snapMissingControlPointHeightsToTerrain(List points) { } private boolean hasMissingControlPointHeights(List points) { + // WorldEdit polygon and convex selection points can arrive with Y=0, + // which means the rail should snap those points to the prepared terrain. for (Vector point : points) if (point.getBlockY() != 0) return false; From 0d4a165d92fcca341e6a5baf857bde887ddcb4cc Mon Sep 17 00:00:00 2001 From: Jasupa Date: Thu, 28 May 2026 21:01:21 +0200 Subject: [PATCH 18/18] fix(generator): improve rail placement and shared block updates - validate rail selections before generation - add rail type settings and menu flow - place rail blocks on terrain surface per column - fix missing side anvils and anvil facing in rail corners - use Alps-Lib block placement helper --- .../generator/components/rail/Rail.java | 26 +- .../generator/components/rail/RailFlag.java | 31 ++ .../components/rail/RailScripts.java | 521 +++++------------- .../components/rail/RailSettings.java | 1 + .../generator/components/rail/RailType.java | 32 ++ .../components/rail/menu/RailTypeMenu.java | 56 ++ .../modules/generator/menu/GeneratorMenu.java | 4 +- .../modules/generator/model/Command.java | 34 +- .../modules/generator/model/Flag.java | 3 +- .../modules/generator/model/FlagType.java | 4 + .../modules/generator/model/Settings.java | 4 + 11 files changed, 286 insertions(+), 430 deletions(-) create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java create mode 100644 src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index 0762e099..b7a3c2ae 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -1,9 +1,16 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Sound; import org.bukkit.entity.Player; public class Rail extends GeneratorComponent { @@ -14,11 +21,24 @@ public Rail() { @Override public boolean checkForPlayer(Player player) { - return hasWorldEditSelection(player); + if (GeneratorUtils.checkForNoWorldEditSelection(player)) + return false; + + Region region = GeneratorUtils.getWorldEditSelection(player); + + if (isSupportedRailSelection(region)) + return true; + + player.sendMessage(Component.text("Rail Generator supports cuboid, polygonal and convex WorldEdit selections.", NamedTextColor.RED)); + player.closeInventory(); + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F); + return false; } - private boolean hasWorldEditSelection(Player player) { - return !GeneratorUtils.checkForNoWorldEditSelection(player); + private boolean isSupportedRailSelection(Region region) { + return region instanceof CuboidRegion + || region instanceof Polygonal2DRegion + || region instanceof ConvexPolyhedralRegion; } @Override diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java new file mode 100644 index 00000000..a60a1ccf --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java @@ -0,0 +1,31 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail; + +import lombok.Getter; +import net.buildtheearth.buildteamtools.modules.generator.model.Flag; +import net.buildtheearth.buildteamtools.modules.generator.model.FlagType; +import org.jspecify.annotations.Nullable; + +public enum RailFlag implements Flag { + + RAIL_TYPE("t", FlagType.RAIL_TYPE); + + @Getter + private final String flag; + + @Getter + private final FlagType flagType; + + RailFlag(String flag, FlagType flagType) { + this.flag = flag; + this.flagType = flagType; + } + + public static @Nullable RailFlag byString(String flag) { + for (RailFlag railFlag : RailFlag.values()) { + if (railFlag.getFlag().equalsIgnoreCase(flag)) + return railFlag; + } + + return null; + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index 2ae0bd78..1970c12a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -8,6 +8,8 @@ import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.Script; +import net.buildtheearth.buildteamtools.modules.generator.model.Settings; +import net.buildtheearth.buildteamtools.utils.MenuItems; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; @@ -31,6 +33,7 @@ public class RailScripts extends Script { private static final int SELECTION_PADDING = 4; private static final int SELECTION_VERTICAL_PADDING = 12; private static final int PREPARE_SELECTION_EXPANSION = 8; + private static final int SURFACE_Y_OFFSET = 1; private static final Direction DEFAULT_FACING = Direction.EAST; private static final XMaterial[] CENTER_MATERIALS = new XMaterial[]{ @@ -42,13 +45,17 @@ public class RailScripts extends Script { private Block[][][] blocks; private List controlPoints = new ArrayList<>(); private List centerPath = new ArrayList<>(); + private RailType railType = RailType.STANDARD; public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); + sendRailInfo("Rail Generator is preparing your selection..."); + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { try { - prepareSession(); + if (!prepareSession()) return; + railScript_v_2_0(); } catch (Exception exception) { Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> getGeneratorComponent().sendError(getPlayer())); @@ -57,10 +64,10 @@ public RailScripts(Player player, GeneratorComponent generatorComponent) { }); } - private void prepareSession() { + private boolean prepareSession() { controlPoints = getControlPoints(); - if (!hasValidControlPoints()) return; + if (!hasValidControlPoints()) return false; List railSelectionPoints = createRailSelectionPoints(controlPoints); GeneratorUtils.createPolySelection( @@ -83,11 +90,13 @@ private void prepareSession() { snapMissingControlPointHeightsToTerrain(controlPoints); centerPath = createCenterPath(controlPoints); + adjustCenterPathToTerrain(); + railType = getRailType(); + + return true; } private void railScript_v_2_0() { - if (!hasValidControlPoints()) return; - if (centerPath.size() < 2) { sendRailError("Rail Generator could not create a valid rail path."); return; @@ -127,6 +136,13 @@ private boolean hasValidControlPoints() { return true; } + private void sendRailInfo(String message) { + Bukkit.getScheduler().runTask( + BuildTeamTools.getInstance(), + () -> getPlayer().sendMessage(Component.text(message, NamedTextColor.YELLOW)) + ); + } + private void sendRailError(String message) { Bukkit.getScheduler().runTask( BuildTeamTools.getInstance(), @@ -139,7 +155,7 @@ private List getControlPoints() { if (selectionPoints == null) return Collections.emptyList(); - return copyPoints(selectionPoints); + return GeneratorUtils.copyToBlockVectors(selectionPoints); } private List createRailSelectionPoints(List points) { @@ -150,341 +166,145 @@ private List createRailSelectionPoints(List points) { List shiftedPoints = GeneratorUtils.shiftPoints(selectionLine, SELECTION_PADDING, true); - if (shiftedPoints == null || shiftedPoints.size() < 3) return createBoundsSelectionPoints(points); + if (shiftedPoints == null || shiftedPoints.size() < 3) + return GeneratorUtils.createBoundsSelectionPoints(points, SELECTION_PADDING); return shiftedPoints; } private List createCenterPath(List points) { - return removeOrthogonalCorners(createShortestPath(points)); - } - - private List createShortestPath(List points) { - List path = new ArrayList<>(); - - if (points.isEmpty()) return path; - - path.add(toBlockVector(points.get(0))); - - for (int index = 0; index < points.size() - 1; index++) - appendShortestLine(path, points.get(index), points.get(index + 1)); - - return path; - } - - private void appendShortestLine(List path, Vector start, Vector end) { - int deltaX = end.getBlockX() - start.getBlockX(); - int deltaY = end.getBlockY() - start.getBlockY(); - int deltaZ = end.getBlockZ() - start.getBlockZ(); - int horizontalSteps = Math.max(Math.abs(deltaX), Math.abs(deltaZ)); - - if (horizontalSteps == 0) { - replaceLastPoint(path, end); - return; - } - - for (int step = 1; step <= horizontalSteps; step++) { - double progress = step / (double) horizontalSteps; - path.add(new Vector( - start.getBlockX() + (int) Math.round(deltaX * progress), - start.getBlockY() + (int) Math.round(deltaY * progress), - start.getBlockZ() + (int) Math.round(deltaZ * progress) - )); - } - } - - private List removeOrthogonalCorners(List path) { - List result = new ArrayList<>(); - - for (int index = 0; index < path.size(); index++) { - if (index > 0 && index < path.size() - 1 && isOrthogonalCorner(path.get(index - 1), path.get(index), path.get(index + 1))) continue; - - result.add(path.get(index)); - } - - return result; - } - - private boolean isOrthogonalCorner(Vector previous, Vector current, Vector next) { - RailStep previousStep = getStep(previous, current); - RailStep nextStep = getStep(current, next); - - return previousStep != null - && nextStep != null - && previousStep.dx() * nextStep.dx() + previousStep.dz() * nextStep.dz() == 0 - && previousStep.dx() != nextStep.dx() - && previousStep.dz() != nextStep.dz(); + return GeneratorUtils.removeOrthogonalCorners(GeneratorUtils.createShortestBlockPath(points)); } private Map buildRailBlocks(List path) { Map railBlocks = new LinkedHashMap<>(); Set centerPositions = getCenterPositions(path); Set centerColumns = getCenterColumns(path); - Map sideBlocks = buildSideBlocks(path, centerPositions, centerColumns); - Map sideColumns = createSideColumnMap(sideBlocks); + Map sideBlocks = new LinkedHashMap<>(); - for (RailSideBlock sideBlock : sideBlocks.values()) { - if (!centerColumns.contains(ColumnKey.from(sideBlock.key()))) - railBlocks.put(sideBlock.key(), createAnvilBlockState(resolveSideBlockFacing(sideBlock, sideColumns))); + for (int index = 0; index < path.size(); index++) { + Vector center = path.get(index); + + for (RailSidePlacement sidePlacement : getSidePlacements(path, index)) + addSideBlock(sideBlocks, center, sidePlacement, centerPositions, centerColumns); } + for (RailSideBlock sideBlock : sideBlocks.values()) + railBlocks.put(sideBlock.key(), createAnvilBlockState(resolveSideBlockFacing(sideBlock, sideBlocks))); + for (Vector center : path) railBlocks.put(PositionKey.from(center), createCenterBlockState(center)); return railBlocks; } - private Map buildSideBlocks( - List path, - Set centerPositions, - Set centerColumns - ) { - Map exactSideBlocks = new LinkedHashMap<>(); - - for (int index = 0; index < path.size(); index++) { - for (RailSidePlacement sidePlacement : getSidePlacements(path, index)) - addSideBlock(exactSideBlocks, sidePlacement, centerPositions, centerColumns); - } - - Map sideBlocks = selectBestSideBlockPerColumn(exactSideBlocks, centerPositions); - repairMissingSideBlocks(path, sideBlocks, centerPositions, centerColumns); - - return sideBlocks; - } - private List getSidePlacements(List path, int index) { List placements = new ArrayList<>(); Vector center = path.get(index); RailStep previousStep = index > 0 ? getStep(path.get(index - 1), center) : null; RailStep nextStep = index < path.size() - 1 ? getStep(center, path.get(index + 1)) : null; - addSidePlacements(placements, center, getRailStep(path, index, new RailStep(1, 0))); + addSidePlacements(placements, getRailStep(path, index, new RailStep(1, 0))); if (previousStep != null) - addSidePlacements(placements, center, previousStep); + addSidePlacements(placements, previousStep); if (nextStep != null) - addSidePlacements(placements, center, nextStep); - - if (previousStep != null && nextStep != null) { - int dx = Integer.compare(previousStep.dx() + nextStep.dx(), 0); - int dz = Integer.compare(previousStep.dz() + nextStep.dz(), 0); - - if (dx != 0 || dz != 0) - addSidePlacements(placements, center, new RailStep(dx, dz)); - } + addSidePlacements(placements, nextStep); return placements; } - private void addSidePlacements(List placements, Vector center, RailStep step) { - int x = center.getBlockX(); - int y = center.getBlockY(); - int z = center.getBlockZ(); - + private void addSidePlacements(List placements, RailStep step) { if (step.dx() != 0 && step.dz() != 0) { - addSidePlacement(placements, new Vector(x + step.dx(), y, z), GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING)); - addSidePlacement(placements, new Vector(x, y, z + step.dz()), GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING)); - } else if (step.dx() != 0) { + addSidePlacement(placements, new RailStep(step.dx(), 0), GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING)); + addSidePlacement(placements, new RailStep(0, step.dz()), GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING)); + return; + } + + if (step.dx() != 0) { Direction facing = GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); - addSidePlacement(placements, new Vector(x, y, z + 1), facing); - addSidePlacement(placements, new Vector(x, y, z - 1), facing); - } else { - Direction facing = GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); - addSidePlacement(placements, new Vector(x + 1, y, z), facing); - addSidePlacement(placements, new Vector(x - 1, y, z), facing); + addSidePlacement(placements, new RailStep(0, 1), facing); + addSidePlacement(placements, new RailStep(0, -1), facing); + return; } - } - private void addSidePlacement(List placements, Vector position, Direction facing) { - PositionKey key = PositionKey.from(position); + Direction facing = GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); + addSidePlacement(placements, new RailStep(1, 0), facing); + addSidePlacement(placements, new RailStep(-1, 0), facing); + } + private void addSidePlacement(List placements, RailStep offset, Direction facing) { for (RailSidePlacement placement : placements) { - if (PositionKey.from(placement.position()).equals(key)) return; + if (placement.offset().equals(offset)) return; } - placements.add(new RailSidePlacement(position, facing)); + placements.add(new RailSidePlacement(offset, facing)); } private void addSideBlock( - Map sideBlocks, + Map sideBlocks, + Vector center, RailSidePlacement sidePlacement, Set centerPositions, Set centerColumns ) { - PositionKey key = PositionKey.from(sidePlacement.position()); - - if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key))) return; - - sideBlocks - .computeIfAbsent(key, ignored -> new RailSideBlock(key)) - .addFacing(sidePlacement.facing()); - } - - private Map selectBestSideBlockPerColumn( - Map exactSideBlocks, - Set centerPositions - ) { - Map selectedColumns = new LinkedHashMap<>(); - - for (RailSideBlock sideBlock : exactSideBlocks.values()) { - ColumnKey columnKey = ColumnKey.from(sideBlock.key()); - RailSideBlock selectedBlock = selectedColumns.get(columnKey); - - if (selectedBlock == null || isBetterSideBlock(sideBlock, selectedBlock, centerPositions)) - selectedColumns.put(columnKey, sideBlock); - } - - Map sideBlocks = new LinkedHashMap<>(); - - for (RailSideBlock sideBlock : selectedColumns.values()) - sideBlocks.put(sideBlock.key(), sideBlock); - - return sideBlocks; - } - - private boolean isBetterSideBlock( - RailSideBlock candidate, - RailSideBlock selectedBlock, - Set centerPositions - ) { - int candidateCoverage = getSideBlockCoverageScore(candidate.key(), centerPositions); - int selectedCoverage = getSideBlockCoverageScore(selectedBlock.key(), centerPositions); - - return candidateCoverage > selectedCoverage - || candidateCoverage == selectedCoverage && candidate.getSupportScore() > selectedBlock.getSupportScore(); - } - - private int getSideBlockCoverageScore(PositionKey sideBlockKey, Set centerPositions) { - int score = 0; - - for (PositionKey centerPosition : centerPositions) - if (isAdjacent(sideBlockKey, centerPosition)) - score++; - - return score; - } - - private void repairMissingSideBlocks( - List path, - Map sideBlocks, - Set centerPositions, - Set centerColumns - ) { - Map sideColumns = createSideColumnMap(sideBlocks); - - for (int index = 0; index < path.size(); index++) { - Vector center = path.get(index); - int sideBlockCount = getAdjacentSideBlockCount(center, sideBlocks); - - if (sideBlockCount >= 2) continue; - - List repairPlacements = getSidePlacements(path, index); - repairPlacements.addAll(getFallbackSidePlacements(center, getRailStep(path, index, new RailStep(1, 0)))); - - for (RailSidePlacement sidePlacement : repairPlacements) { - sideBlockCount = repairSidePlacement( - sidePlacement, - PositionKey.from(center), - sideBlocks, - sideColumns, - centerPositions, - centerColumns, - sideBlockCount - ); + RailStep sideOffset = sidePlacement.offset(); - if (sideBlockCount >= 2) break; - } - } - } - - private List getFallbackSidePlacements(Vector center, RailStep step) { - List placements = new ArrayList<>(); - - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) continue; - - addSidePlacement( - placements, - new Vector(center.getBlockX() + dx, center.getBlockY(), center.getBlockZ() + dz), - getFallbackFacing(dx, dz, step) - ); - } - } - - return placements; - } + if (sideOffset.dx() == 0 && sideOffset.dz() == 0) + return; - private Direction getFallbackFacing(int offsetX, int offsetZ, RailStep step) { - if (Math.abs(step.dx()) >= Math.abs(step.dz()) && step.dx() != 0) return GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); + int x = center.getBlockX() + sideOffset.dx(); + int z = center.getBlockZ() + sideOffset.dz(); + int y = getRailSurfaceY(x, z, center.getBlockY()); - if (step.dz() != 0) return GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); + PositionKey key = PositionKey.of(x, y, z); + ColumnKey columnKey = ColumnKey.from(key); - if (Math.abs(offsetX) >= Math.abs(offsetZ) && offsetX != 0) return GeneratorUtils.getFacing(offsetX, 0, DEFAULT_FACING); + if (centerPositions.contains(key) || centerColumns.contains(columnKey)) + return; - return GeneratorUtils.getFacing(0, offsetZ, DEFAULT_FACING); + sideBlocks + .computeIfAbsent(columnKey, ignored -> new RailSideBlock(key)) + .addFacing(sidePlacement.facing()); } - private int repairSidePlacement( - RailSidePlacement sidePlacement, - PositionKey centerKey, - Map sideBlocks, - Map sideColumns, - Set centerPositions, - Set centerColumns, - int sideBlockCount - ) { - PositionKey key = PositionKey.from(sidePlacement.position()); - - if (centerPositions.contains(key) || centerColumns.contains(ColumnKey.from(key)) || sideBlocks.containsKey(key)) return sideBlockCount; - - ColumnKey columnKey = ColumnKey.from(key); - RailSideBlock existingColumnBlock = sideColumns.get(columnKey); - boolean replacingAdjacentBlock = existingColumnBlock != null && isAdjacent(existingColumnBlock.key(), centerKey); + private Direction resolveSideBlockFacing(RailSideBlock sideBlock, Map sideBlocks) { + PositionKey key = sideBlock.key(); + boolean east = sideBlocks.containsKey(ColumnKey.of(key.x() + 1, key.z())); + boolean west = sideBlocks.containsKey(ColumnKey.of(key.x() - 1, key.z())); + boolean south = sideBlocks.containsKey(ColumnKey.of(key.x(), key.z() + 1)); + boolean north = sideBlocks.containsKey(ColumnKey.of(key.x(), key.z() - 1)); + int xConnections = (east ? 1 : 0) + (west ? 1 : 0); + int zConnections = (south ? 1 : 0) + (north ? 1 : 0); + Direction preferredFacing = sideBlock.getPreferredFacing(); - if (existingColumnBlock != null) { - if (!canReplaceSideBlock(existingColumnBlock.key(), centerPositions, sideBlocks)) return sideBlockCount; + if (xConnections > zConnections) + return resolveAxisFacing(preferredFacing, Direction.EAST, Direction.WEST, east, west); - sideBlocks.remove(existingColumnBlock.key()); - } + if (zConnections > xConnections) + return resolveAxisFacing(preferredFacing, Direction.SOUTH, Direction.NORTH, south, north); - RailSideBlock sideBlock = new RailSideBlock(key); - sideBlock.addFacing(sidePlacement.facing()); - sideBlocks.put(key, sideBlock); - sideColumns.put(columnKey, sideBlock); - return sideBlockCount + (replacingAdjacentBlock ? 0 : 1); + return preferredFacing; } - private boolean canReplaceSideBlock( - PositionKey sideBlockKey, - Set centerPositions, - Map sideBlocks + private Direction resolveAxisFacing( + Direction preferredFacing, + Direction positiveFacing, + Direction negativeFacing, + boolean hasPositiveNeighbor, + boolean hasNegativeNeighbor ) { - for (PositionKey centerPosition : centerPositions) { - if (isAdjacent(sideBlockKey, centerPosition) && getAdjacentSideBlockCount(centerPosition.toVector(), sideBlocks) <= 2) return false; - } - - return true; - } - - private int getAdjacentSideBlockCount(Vector center, Map sideBlocks) { - int count = 0; - - for (PositionKey sideBlockKey : sideBlocks.keySet()) { - if (isAdjacent(sideBlockKey, PositionKey.from(center))) - count++; - } + if (preferredFacing == positiveFacing && hasPositiveNeighbor || preferredFacing == negativeFacing && hasNegativeNeighbor) + return preferredFacing; - return count; - } + if (hasPositiveNeighbor && !hasNegativeNeighbor) + return positiveFacing; - private boolean isAdjacent(PositionKey first, PositionKey second) { - int dx = Math.abs(first.x() - second.x()); - int dy = Math.abs(first.y() - second.y()); - int dz = Math.abs(first.z() - second.z()); + if (hasNegativeNeighbor && !hasPositiveNeighbor) + return negativeFacing; - return dx <= 1 && dy <= 1 && dz <= 1 && (dx != 0 || dz != 0); + return preferredFacing == negativeFacing ? negativeFacing : positiveFacing; } private Set getCenterColumns(List path) { @@ -505,48 +325,6 @@ private Set getCenterPositions(List path) { return centerPositions; } - private Map createSideColumnMap(Map sideBlocks) { - Map sideColumns = new LinkedHashMap<>(); - - for (RailSideBlock sideBlock : sideBlocks.values()) - sideColumns.put(ColumnKey.from(sideBlock.key()), sideBlock); - - return sideColumns; - } - - private Direction resolveSideBlockFacing(RailSideBlock sideBlock, Map sideColumns) { - PositionKey key = sideBlock.key(); - boolean east = sideColumns.containsKey(ColumnKey.of(key.x() + 1, key.z())); - boolean west = sideColumns.containsKey(ColumnKey.of(key.x() - 1, key.z())); - boolean south = sideColumns.containsKey(ColumnKey.of(key.x(), key.z() + 1)); - boolean north = sideColumns.containsKey(ColumnKey.of(key.x(), key.z() - 1)); - int xConnections = (east ? 1 : 0) + (west ? 1 : 0); - int zConnections = (south ? 1 : 0) + (north ? 1 : 0); - Direction preferredFacing = sideBlock.getPreferredFacing(); - - if (xConnections > zConnections) return resolveAxisFacing(preferredFacing, Direction.EAST, Direction.WEST, east, west); - - if (zConnections > xConnections) return resolveAxisFacing(preferredFacing, Direction.SOUTH, Direction.NORTH, south, north); - - return preferredFacing; - } - - private Direction resolveAxisFacing( - Direction preferredFacing, - Direction positiveFacing, - Direction negativeFacing, - boolean hasPositiveNeighbor, - boolean hasNegativeNeighbor - ) { - if (preferredFacing == positiveFacing && hasPositiveNeighbor || preferredFacing == negativeFacing && hasNegativeNeighbor) return preferredFacing; - - if (hasPositiveNeighbor && !hasNegativeNeighbor) return positiveFacing; - - if (hasNegativeNeighbor && !hasPositiveNeighbor) return negativeFacing; - - return preferredFacing == negativeFacing ? negativeFacing : positiveFacing; - } - private RailStep getRailStep(List path, int index, RailStep fallbackStep) { RailStep previousStep = index > 0 ? getStep(path.get(index - 1), path.get(index)) : null; RailStep nextStep = index < path.size() - 1 ? getStep(path.get(index), path.get(index + 1)) : null; @@ -580,6 +358,25 @@ private void snapMissingControlPointHeightsToTerrain(List points) { GeneratorUtils.adjustHeight(points, blocks); } + private void adjustCenterPathToTerrain() { + if (blocks == null || centerPath.isEmpty()) return; + + for (Vector point : centerPath) + point.setY(getRailSurfaceY(point.getBlockX(), point.getBlockZ(), point.getBlockY())); + } + + private int getRailSurfaceY(int x, int z, int fallbackY) { + if (blocks == null) + return fallbackY; + + int surfaceY = GeneratorUtils.getMaxHeight(blocks, x, z, MenuItems.getIgnoredMaterials()); + + if (surfaceY == 0) + return fallbackY; + + return surfaceY + SURFACE_Y_OFFSET; + } + private boolean hasMissingControlPointHeights(List points) { // WorldEdit polygon and convex selection points can arrive with Y=0, // which means the rail should snap those points to the prepared terrain. @@ -594,64 +391,22 @@ private List getRestoreSelectionPoints() { } private int getSelectionMinY(List points) { - int minY = getRegion() != null ? getRegion().getMinimumY() : getMinPointY(points); + int minY = getRegion() != null ? getRegion().getMinimumY() : GeneratorUtils.getMinHeight(points); return Math.max(getPlayer().getWorld().getMinHeight(), minY - SELECTION_VERTICAL_PADDING); } private int getSelectionMaxY(List points) { - int maxY = getRegion() != null ? getRegion().getMaximumY() : getMaxPointY(points); + int maxY = getRegion() != null ? getRegion().getMaximumY() : GeneratorUtils.getMaxHeight(points); return Math.min(getPlayer().getWorld().getMaxHeight() - 1, maxY + SELECTION_VERTICAL_PADDING); } - private List createBoundsSelectionPoints(List points) { - int minX = Integer.MAX_VALUE; - int minZ = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - int maxZ = Integer.MIN_VALUE; - - for (Vector point : points) { - minX = Math.min(minX, point.getBlockX() - SELECTION_PADDING); - minZ = Math.min(minZ, point.getBlockZ() - SELECTION_PADDING); - maxX = Math.max(maxX, point.getBlockX() + SELECTION_PADDING); - maxZ = Math.max(maxZ, point.getBlockZ() + SELECTION_PADDING); - } - - return List.of( - new Vector(minX, 0, minZ), - new Vector(maxX, 0, minZ), - new Vector(maxX, 0, maxZ), - new Vector(minX, 0, maxZ) - ); - } - - private int getMinPointY(List points) { - int minY = Integer.MAX_VALUE; - - for (Vector point : points) - minY = Math.min(minY, point.getBlockY()); - - return minY == Integer.MAX_VALUE ? getPlayer().getWorld().getMinHeight() : minY; - } - - private int getMaxPointY(List points) { - int maxY = Integer.MIN_VALUE; - - for (Vector point : points) - maxY = Math.max(maxY, point.getBlockY()); - - return maxY == Integer.MIN_VALUE ? getPlayer().getWorld().getMaxHeight() - 1 : maxY; - } - - private List copyPoints(List points) { - List copiedPoints = new ArrayList<>(); - - for (Vector point : points) - copiedPoints.add(toBlockVector(point)); - - return copiedPoints; + private BlockState createCenterBlockState(Vector position) { + return switch (railType) { + case STANDARD -> createStandardCenterBlockState(position); + }; } - private BlockState createCenterBlockState(Vector position) { + private BlockState createStandardCenterBlockState(Vector position) { int index = Math.floorMod( position.getBlockX() * 31 + position.getBlockY() * 23 + position.getBlockZ() * 17, CENTER_MATERIALS.length @@ -664,29 +419,20 @@ private BlockState createAnvilBlockState(Direction direction) { return GeneratorUtils.getBlockStateWithFacing(BlockTypes.ANVIL, direction); } - private Vector toBlockVector(Vector vector) { - return new Vector( - vector.getBlockX(), - vector.getBlockY(), - vector.getBlockZ() - ); - } - - private void replaceLastPoint(List points, Vector point) { - Vector blockPoint = toBlockVector(point); + private RailType getRailType() { + Settings settings = getGeneratorComponent().getPlayerSettings().get(getPlayer().getUniqueId()); - if (points.isEmpty()) { - points.add(blockPoint); - return; - } + if (!(settings instanceof RailSettings railSettings)) + return RailType.STANDARD; - points.set(points.size() - 1, blockPoint); + Object value = railSettings.getValues().get(RailFlag.RAIL_TYPE); + return value instanceof RailType selectedRailType ? selectedRailType : RailType.STANDARD; } private record RailStep(int dx, int dz) { } - private record RailSidePlacement(Vector position, Direction facing) { + private record RailSidePlacement(RailStep offset, Direction facing) { } private record PositionKey(int x, int y, int z) { @@ -736,15 +482,6 @@ private void addFacing(Direction facing) { facingScores.merge(facing, 1, Integer::sum); } - private int getSupportScore() { - int score = 0; - - for (int facingScore : facingScores.values()) - score += facingScore; - - return score; - } - private Direction getPreferredFacing() { Direction preferredFacing = DEFAULT_FACING; int preferredScore = -1; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java index 414ad632..ae244c3d 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java @@ -11,5 +11,6 @@ public RailSettings(Player player) { @Override public void setDefaultValues() { + setValue(RailFlag.RAIL_TYPE, RailType.STANDARD); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java new file mode 100644 index 00000000..6c641532 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java @@ -0,0 +1,32 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail; + +import com.cryptomorin.xseries.XMaterial; +import lombok.Getter; +import org.jspecify.annotations.Nullable; + +public enum RailType { + + STANDARD("standard", "Standard", XMaterial.RAIL); + + @Getter + private final String identifier; + @Getter + private final String displayName; + @Getter + private final XMaterial icon; + + RailType(String identifier, String displayName, XMaterial icon) { + this.identifier = identifier; + this.displayName = displayName; + this.icon = icon; + } + + public static @Nullable RailType byString(String value) { + for (RailType railType : RailType.values()) { + if (railType.getIdentifier().equalsIgnoreCase(value) || railType.name().equalsIgnoreCase(value)) + return railType; + } + + return null; + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java new file mode 100644 index 00000000..af2e400e --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java @@ -0,0 +1,56 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.menu; + +import com.alpsbte.alpslib.utils.item.Item; +import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailFlag; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailSettings; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailType; +import net.buildtheearth.buildteamtools.modules.generator.menu.GeneratorMenu; +import net.buildtheearth.buildteamtools.modules.generator.model.Settings; +import net.buildtheearth.buildteamtools.utils.menus.NameListMenu; +import org.apache.commons.lang3.tuple.MutablePair; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + +public class RailTypeMenu extends NameListMenu { + + public static final String RAIL_TYPE_INV_NAME = "Choose a Rail Type"; + + public RailTypeMenu(Player player, boolean autoLoad) { + super(player, RAIL_TYPE_INV_NAME, getRailTypes(), new GeneratorMenu(player, false), autoLoad); + } + + private static @NonNull List> getRailTypes() { + List> railTypes = new ArrayList<>(); + + for (RailType railType : RailType.values()) + railTypes.add(new MutablePair<>(Item.create(railType.getIcon().get(), railType.getDisplayName()), railType.getIdentifier())); + + return railTypes; + } + + @Override + protected void setItemClickEventsAsync() { + super.setItemClickEventsAsync(); + + if (canProceed()) + getMenu().getSlot(NEXT_ITEM_SLOT).setClickHandler((clickPlayer, clickInformation) -> { + Settings settings = GeneratorModule.getInstance().getRail().getPlayerSettings().get(clickPlayer.getUniqueId()); + + if (!(settings instanceof RailSettings railSettings)) + return; + + railSettings.setValue(RailFlag.RAIL_TYPE, selectedNames.getFirst()); + + clickPlayer.closeInventory(); + clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); + + GeneratorModule.getInstance().getRail().generate(clickPlayer); + }); + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java index c6cb38a1..d7ba446e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java @@ -13,6 +13,7 @@ import net.buildtheearth.buildteamtools.modules.generator.components.house.menu.WallColorMenu; import net.buildtheearth.buildteamtools.modules.generator.components.rail.Rail; import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailSettings; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.menu.RailTypeMenu; import net.buildtheearth.buildteamtools.modules.generator.components.road.Road; import net.buildtheearth.buildteamtools.modules.generator.components.road.RoadSettings; import net.buildtheearth.buildteamtools.modules.generator.components.road.menu.RoadColorMenu; @@ -106,6 +107,7 @@ protected void setPreviewItems() { "- Convex", "", "§eFeatures:", + "- Rail Type selection", "- Straight sections", "- Direction changes", "- Automatic side block orientation", @@ -226,7 +228,7 @@ protected void setItemClickEventsAsync() { clickPlayer.closeInventory(); clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); - rail.generate(clickPlayer); + new RailTypeMenu(clickPlayer, true); })); getMenu().getSlot(TREE_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index 15f2bdc9..977ad684 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -2,12 +2,8 @@ import com.alpsbte.alpslib.utils.ChatHelper; import com.alpsbte.alpslib.utils.GeneratorUtils; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Actor; -import com.sk89q.worldedit.function.pattern.Pattern; -import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.world.World; @@ -195,7 +191,7 @@ public void processOperation(Operation operation) { break; case SET_BLOCKSTATES_AT_POSITIONS: - future = setBlockStatesAtPositions(Arrays.asList((Vector[]) operation.get(0)), Arrays.asList((BlockState[]) operation.get(1))); + future = GeneratorUtils.setBlockStatesAtPositions(localSession, actor, weWorld, Arrays.asList((Vector[]) operation.get(0)), Arrays.asList((BlockState[]) operation.get(1))); break; case DRAW_CURVE_WITH_MASKS: @@ -284,34 +280,6 @@ private void runInternalGeneratorCommand(String command) { } } - private CompletableFuture setBlockStatesAtPositions(List positions, List blockStates) { - CompletableFuture future = new CompletableFuture<>(); - - Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { - try (EditSession editSession = WorldEdit.getInstance().newEditSession(weWorld)) { - for (int index = 0; index < positions.size(); index++) { - BlockState blockState = blockStates.get(index); - - if (blockState == null) - continue; - - Vector position = positions.get(index); - editSession.setBlock( - BlockVector3.at(position.getBlockX(), position.getBlockY(), position.getBlockZ()), - (Pattern) blockState - ); - } - - GeneratorUtils.saveEditSession(editSession, localSession, actor); - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(e); - } - }); - - return future; - } - /** Converts the XYZ coordinates in a command to the highest block at that location while skipping certain blocks. */ public String convertXYZ(String command) { String xyz = command.split("%%XYZ/")[1].split("/%%")[0]; diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java index 2d2676c9..0f00eec6 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java @@ -2,6 +2,7 @@ import net.buildtheearth.buildteamtools.modules.generator.components.field.FieldFlag; import net.buildtheearth.buildteamtools.modules.generator.components.house.HouseFlag; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailFlag; import net.buildtheearth.buildteamtools.modules.generator.components.road.RoadFlag; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeFlag; import org.jspecify.annotations.NonNull; @@ -18,7 +19,7 @@ static Flag byString(@NonNull GeneratorType generatorType, String flag) { case HOUSE -> HouseFlag.byString(flag); case ROAD -> RoadFlag.byString(flag); case TREE -> TreeFlag.byString(flag); - case RAIL -> null; + case RAIL -> RailFlag.byString(flag); case FIELD -> FieldFlag.byString(flag); }; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java index ed9dc8dd..76366680 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java @@ -7,6 +7,7 @@ import net.buildtheearth.buildteamtools.modules.generator.components.field.CropStage; import net.buildtheearth.buildteamtools.modules.generator.components.field.CropType; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailType; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeType; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeWidth; @@ -22,6 +23,7 @@ public enum FlagType { ROOF_TYPE(RoofType.class), CROP_TYPE(CropType.class), CROP_STAGE(CropStage.class), + RAIL_TYPE(RailType.class), TREE_TYPE(TreeType.class), TREE_WIDTH(TreeWidth.class); @@ -85,6 +87,8 @@ public static Object convertToFlagType(Flag flag, String value){ return CropType.getByIdentifier(value); case CROP_STAGE: return CropStage.getByIdentifier(value); + case RAIL_TYPE: + return RailType.byString(value); case TREE_TYPE: return TreeType.byString(value); case TREE_WIDTH: diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java index 7bed4c57..f617c70b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java @@ -7,6 +7,7 @@ import net.buildtheearth.buildteamtools.modules.generator.components.field.CropStage; import net.buildtheearth.buildteamtools.modules.generator.components.field.CropType; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailType; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeWidth; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -85,6 +86,9 @@ public HashMap getValuesAsString(){ case CROP_STAGE: value = new StringBuilder(((CropStage) valueObject).getIdentifier()); break; + case RAIL_TYPE: + value = new StringBuilder(((RailType) valueObject).getIdentifier()); + break; case TREE_WIDTH: value = new StringBuilder(((TreeWidth) valueObject).getName()); break;