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/commands/GeneratorCommand.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java index b3fe253c..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 @@ -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,43 @@ 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); + 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 +72,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 +81,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 +90,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/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/Rail.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java index b0a7b913..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 @@ -4,24 +4,150 @@ import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; +import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; public class Rail extends GeneratorComponent { + private static final int TARGET_BLOCK_RANGE = 200; + public Rail() { super(GeneratorType.RAILWAY); } @Override - public boolean checkForPlayer(Player p) { - return !GeneratorUtils.checkForNoWorldEditSelection(p); + public void analyzeCommand(Player player, String[] args) { + addPlayerSetting(player); + + if (args.length >= 2) { + String subCommand = args[1].toLowerCase(); + + switch (subCommand) { + case "help", "info", "?" -> { + sendHelp(player); + return; + } + + case "add", "point" -> { + addPoint(player); + return; + } + + case "clear", "reset" -> { + clearPoints(player); + return; + } + + case "points", "list" -> { + listPoints(player); + return; + } + + default -> { + player.sendMessage("§cUnknown rail command: §7" + args[1]); + sendHelp(player); + return; + } + } + } + + generate(player); + } + + @Override + public boolean checkForPlayer(Player player) { + RailSettings settings = getRailSettings(player); + + if (settings != null && settings.hasEnoughCustomControlPoints()) + return true; + + return !GeneratorUtils.checkForNoWorldEditSelection(player); } @Override - public void generate(Player p) { - if (!GeneratorModule.getInstance().getRail().checkForPlayer(p)) + 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() + ")"); + } + } - new RailScripts(p, this); + 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/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 ab1156d2..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,127 +1,649 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.cryptomorin.xseries.XMaterial; +import com.fastasyncworldedit.core.registry.state.PropertyKey; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.Script; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +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 static final XMaterial[] CENTER_MATERIALS = new XMaterial[]{ + XMaterial.DEAD_FIRE_CORAL_BLOCK, + XMaterial.STONE, + XMaterial.COBBLESTONE + }; + + 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; + + Thread thread = new Thread(this::railScript_v_1_0); + thread.start(); + } + + private void railScript_v_1_0() { + controlPoints = getControlPoints(); + + 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); + } + + 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() + ); + + points.add(new Vector( + point.x(), + y, + point.z() + )); + } + + return points; + } + + 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 (!material.isAir() + && material != Material.WATER + && material != Material.LAVA) { + return y + 1; + } + } + + return minimumY; + } + + private List createEightDirectionalPath(List points) { + List path = new ArrayList<>(); + + 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); + + 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); + } + + 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 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 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() + ); + + 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); - throw new UnsupportedOperationException("RailScripts is currently broken."); - //getPlayer().chat("/clearhistory"); - //Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), this::railScript_v_1_3); + 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)); } - public void railScript_v_1_3() { - List commands = new ArrayList<>(); - //HashMap flags = rail.getPlayerSettings().get(p.getUniqueId()).getValues(); + private Direction getWorldEditDirection(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); - int xPos = getPlayer().getLocation().getBlockX(); - int zPos = getPlayer().getLocation().getBlockZ(); + if (Math.abs(dx) >= Math.abs(dz)) { + if (dx > 0) + return Direction.EAST; - int operations = 0; + if (dx < 0) + return Direction.WEST; + } - int railWidth = 5; + if (dz > 0) + return Direction.SOUTH; + if (dz < 0) + return Direction.NORTH; - // TODO START TEMP + return Direction.EAST; + } - 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"); + 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); - finish(regionBlocks, points); + if (dx == 0 && dz == 0) + return null; + return new Vector(dx, 0, dz); + } - // TODO END TEMP + private boolean isDiagonal(Vector direction) { + return direction.getBlockX() != 0 && direction.getBlockZ() != 0; + } - /* - // Get the points of the region - List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - points = GeneratorUtils.populatePoints(points, 5); + private Vector getLeftOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); - // ----------- PREPARATION 01 ---------- - // Replace all unnecessary blocks with air + return new Vector(-dz, 0, dx); + } - List polyRegionLine = new ArrayList<>(points); - polyRegionLine = GeneratorUtils.extendPolyLine(polyRegionLine); - List polyRegionPoints = GeneratorUtils.shiftPoints(polyRegionLine, railWidth + 2, true); + private Vector getRightOffset(Vector direction) { + int dx = direction.getBlockX(); + int dz = direction.getBlockZ(); - // Create a region from the points - GeneratorUtils.createPolySelection(getPlayer(), polyRegionPoints, null); + return new Vector(dz, 0, -dx); + } - getPlayer().chat("//expand 30 up"); - getPlayer().chat("//expand 10 down"); + private Vector toBlockVector(Vector vector) { + return new Vector( + vector.getBlockX(), + vector.getBlockY(), + vector.getBlockZ() + ); + } - // Remove non-solid blocks - getPlayer().chat("//gmask !#solid"); - getPlayer().chat("//replace 0"); - operations++; + private void addPointIfNew(List points, Vector point) { + Vector blockPoint = toBlockVector(point); - // Remove all trees and pumpkins - getPlayer().chat("//gmask"); - getPlayer().chat("//replace leaves,log,pumpkin 0"); - operations++; + if (points.isEmpty()) { + points.add(blockPoint); + return; + } - getPlayer().chat("//gmask"); + if (!isSameBlock(points.get(points.size() - 1), blockPoint)) + points.add(blockPoint); + } + private List getCuboidRestoreSelection() { + List restoreSelection = new ArrayList<>(); - Block[][][] regionBlocks = GeneratorUtils.analyzeRegion(getPlayer(), getPlayer().getWorld()); - GeneratorUtils.adjustHeight(points, regionBlocks); + restoreSelection.add(controlPoints.get(0)); + restoreSelection.add(controlPoints.get(controlPoints.size() - 1)); + return restoreSelection; + } - // ----------- RAILWAY ---------- + private boolean isSameBlock(Vector first, Vector second) { + return first.getBlockX() == second.getBlockX() + && first.getBlockY() == second.getBlockY() + && first.getBlockZ() == second.getBlockZ(); + } - // Draw the railway curve + private record PositionKey(int x, int y, int z) { - GeneratorUtils.createConvexSelection(commands, points); - commands.add("//gmask !solid"); - commands.add("//curve 42"); - operations++; - commands.add("//gmask"); + 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); + } + } - // Create the railway - GeneratorUtils.createPolySelection(commands, polyRegionPoints); + private static class RailSideBlock { - 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++; + private final Vector position; + private int directionX; + private int directionZ; - 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++; + private RailSideBlock(Vector position) { + this.position = position; + } - commands.add("//gmask"); + private Vector position() { + return position; + } - // 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); + private void addDirection(Vector direction) { + directionX += direction.getBlockX(); + directionZ += direction.getBlockZ(); } - GeneratorModule.getInstance().getGeneratorCommands().add(new Command(getPlayer(), getGeneratorComponent(), commands, operations, regionBlocks)); - GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, operations));*/ + 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 0d7ef243..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,16 +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() { + 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; + } - // Lane Count (Default: Fixed Value) - setValue(RailFlag.LANE_COUNT, 1); + 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/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/menu/GeneratorMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java index ef738425..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 @@ -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; @@ -20,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; @@ -36,101 +40,141 @@ 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) { 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 sections", + "- Direction changes", + "- Automatic side block 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(); } @Override protected void setMenuItemsAsync() { - // No Async / DB Items + // No async or database items. } @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 +184,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 +201,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,39 +235,44 @@ 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); })); } 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 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/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 cde217d7..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 @@ -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,45 @@ 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: + /** + * 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 + * 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 +189,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 +197,4 @@ protected void convertArgsToSettings(Player p, String[] args){ public String getWikiPage() { return generatorType.getWikiPage(); } -} +} \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/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 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); + } }