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..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 @@ -1,10 +1,15 @@ 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.entity.Player; +import org.bukkit.util.Vector; + +import java.util.List; public class Rail extends GeneratorComponent { @@ -13,15 +18,39 @@ public Rail() { } @Override - public boolean checkForPlayer(Player p) { - return !GeneratorUtils.checkForNoWorldEditSelection(p); + 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; + } + + Region region = GeneratorUtils.getWorldEditSelection(player); + RailSelectionPointReader reader = new RailSelectionPointReader(player, region); + + 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; } @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..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 @@ -1,127 +1,102 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; -import com.alpsbte.alpslib.utils.GeneratorUtils; +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.block.Block; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.util.Vector; -import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; public class RailScripts extends Script { + private final RailPathBuilder pathBuilder = new RailPathBuilder(); + private final RailPlacementBuilder placementBuilder = new RailPlacementBuilder(); + private final RailWorldEditPlacer placer = new RailWorldEditPlacer(); + + private final RailType railType = new SampleRailType(); + 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(); - - int xPos = getPlayer().getLocation().getBlockX(); - int zPos = getPlayer().getLocation().getBlockZ(); - - int operations = 0; - - int railWidth = 5; - - - // TODO START TEMP - - 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); - - - // TODO END TEMP + private void generateRail() { + try { + List controlPoints = getControlPoints(); - /* - // Get the points of the region - List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - points = GeneratorUtils.populatePoints(points, 5); + if (controlPoints.size() < 2) { + getPlayer().sendMessage("§cRail Generator needs at least two usable points in the selection."); + return; + } - // ----------- PREPARATION 01 ---------- - // Replace all unnecessary blocks with air + RailPath railPath = pathBuilder.build(controlPoints); - List polyRegionLine = new ArrayList<>(points); - polyRegionLine = GeneratorUtils.extendPolyLine(polyRegionLine); - List polyRegionPoints = GeneratorUtils.shiftPoints(polyRegionLine, railWidth + 2, true); + if (!railPath.isValid()) { + getPlayer().sendMessage("§cRail Generator could not derive a valid path from this selection."); + return; + } - // Create a region from the points - GeneratorUtils.createPolySelection(getPlayer(), polyRegionPoints, null); + List placements = placementBuilder.buildPlacements(railPath); - getPlayer().chat("//expand 30 up"); - getPlayer().chat("//expand 10 down"); + if (placements.isEmpty()) { + getPlayer().sendMessage("§cRail Generator did not create any block placements."); + return; + } - // Remove non-solid blocks - getPlayer().chat("//gmask !#solid"); - getPlayer().chat("//replace 0"); - operations++; + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> placeRail(placements)); + } catch (Exception exception) { + getPlayer().sendMessage("§cRail Generator failed while generating from the WorldEdit selection."); - // Remove all trees and pumpkins - getPlayer().chat("//gmask"); - getPlayer().chat("//replace leaves,log,pumpkin 0"); - operations++; - - getPlayer().chat("//gmask"); - - - Block[][][] regionBlocks = GeneratorUtils.analyzeRegion(getPlayer(), getPlayer().getWorld()); - GeneratorUtils.adjustHeight(points, regionBlocks); - - - // ----------- RAILWAY ---------- - - // Draw the railway curve - - GeneratorUtils.createConvexSelection(commands, points); - commands.add("//gmask !solid"); - commands.add("//curve 42"); - operations++; - commands.add("//gmask"); + BuildTeamTools.getInstance().getLogger().log( + Level.SEVERE, + "Rail Generator failed while generating from the WorldEdit selection.", + exception + ); + } + } + private List getControlPoints() { + RailSelectionPointReader reader = new RailSelectionPointReader(getPlayer(), getRegion()); + return reader.readControlPoints(); + } - // Create the railway - GeneratorUtils.createPolySelection(commands, polyRegionPoints); + private void placeRail(List placements) { + boolean placedWithWorldEdit = placer.placeWithWorldEdit(this, placements, railType); - 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++; + 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."); - 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++; + List changes = placer.placeWithBukkitFallback(this, placements, railType); - commands.add("//gmask"); + GeneratorModule.getInstance() + .getPlayerHistory(getPlayer()) + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, changes)); - // 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); + getGeneratorComponent().sendSuccessMessage(getPlayer()); + return; } - GeneratorModule.getInstance().getGeneratorCommands().add(new Command(getPlayer(), getGeneratorComponent(), commands, operations, regionBlocks)); - GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, operations));*/ + GeneratorModule.getInstance() + .getPlayerHistory(getPlayer()) + .addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, this, 1)); + + getGeneratorComponent().sendSuccessMessage(getPlayer()); } } \ 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 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..a7663baf --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/path/RailPathBuilder.java @@ -0,0 +1,315 @@ +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 new file mode 100644 index 00000000..54080feb --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailBlockPlacement.java @@ -0,0 +1,19 @@ +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 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..029f1525 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/placement/RailWorldEditPlacer.java @@ -0,0 +1,90 @@ +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; +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; +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( + 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/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/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..072f2b90 --- /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 (!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 new file mode 100644 index 00000000..343e46e7 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/side/RailSideBlock.java @@ -0,0 +1,32 @@ +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 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 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/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