From 535d06d6941ed6bda675dac845c2c26cf99a8969 Mon Sep 17 00:00:00 2001 From: Jasupa Date: Sun, 26 Apr 2026 17:42:04 +0200 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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); + } }