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..803a6099 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/commands/GeneratorCommand.java @@ -3,11 +3,9 @@ import com.alpsbte.alpslib.utils.ChatHelper; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.menu.GeneratorMenu; -import net.buildtheearth.buildteamtools.modules.generator.model.History; +import net.buildtheearth.buildteamtools.modules.generator.model.HistoryEntry; import net.buildtheearth.buildteamtools.modules.network.model.Permissions; import net.buildtheearth.buildteamtools.utils.Utils; -import 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."); @@ -71,7 +58,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N } ChatHelper.sendMessageBox(sender, "Generator History for " + p.getName(), () -> { - for (History.HistoryEntry history : GeneratorModule.getInstance().getPlayerHistory(p).getHistoryEntries()) { + for (HistoryEntry history : GeneratorModule.getInstance().getPlayerHistory(p).getHistoryEntries()) { long timeDifference = System.currentTimeMillis() - history.getTimeCreated(); p.sendMessage("§e- " + history.getGeneratorType().name() + " §7-§e " + Utils.toDate(timeDifference) + " ago §7-§e " + history.getWorldEditCommandCount() + " Commands executed"); } @@ -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"); - }); } } 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..b7a3c2ae 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/Rail.java @@ -1,27 +1,51 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorType; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Sound; import org.bukkit.entity.Player; public class Rail extends GeneratorComponent { public Rail() { - super(GeneratorType.RAILWAY); + super(GeneratorType.RAIL); } @Override - public boolean checkForPlayer(Player p) { - return !GeneratorUtils.checkForNoWorldEditSelection(p); + public boolean checkForPlayer(Player player) { + if (GeneratorUtils.checkForNoWorldEditSelection(player)) + return false; + + Region region = GeneratorUtils.getWorldEditSelection(player); + + if (isSupportedRailSelection(region)) + return true; + + player.sendMessage(Component.text("Rail Generator supports cuboid, polygonal and convex WorldEdit selections.", NamedTextColor.RED)); + player.closeInventory(); + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0F, 1.0F); + return false; + } + + private boolean isSupportedRailSelection(Region region) { + return region instanceof CuboidRegion + || region instanceof Polygonal2DRegion + || region instanceof ConvexPolyhedralRegion; } @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/RailFlag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailFlag.java index a78994b0..a60a1ccf 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 @@ -1,33 +1,30 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; +import lombok.Getter; import net.buildtheearth.buildteamtools.modules.generator.model.Flag; import net.buildtheearth.buildteamtools.modules.generator.model.FlagType; +import org.jspecify.annotations.Nullable; public enum RailFlag implements Flag { - LANE_COUNT("c", FlagType.INTEGER); + RAIL_TYPE("t", FlagType.RAIL_TYPE); + + @Getter private final String flag; + + @Getter private final FlagType flagType; - RailFlag(String flag, FlagType flagType){ + RailFlag(String flag, FlagType flagType) { this.flag = flag; this.flagType = flagType; } - @Override - public String getFlag() { - return flag; - } - - @Override - public FlagType getFlagType() { - return flagType; - } - - public static RailFlag byString(String flag){ - for(RailFlag railFlag : RailFlag.values()) - if(railFlag.getFlag().equalsIgnoreCase(flag)) + public static @Nullable RailFlag byString(String flag) { + for (RailFlag railFlag : RailFlag.values()) { + if (railFlag.getFlag().equalsIgnoreCase(flag)) return railFlag; + } return null; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java index ab1156d2..1970c12a 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailScripts.java @@ -1,127 +1,499 @@ package net.buildtheearth.buildteamtools.modules.generator.components.rail; import com.alpsbte.alpslib.utils.GeneratorUtils; +import com.cryptomorin.xseries.XMaterial; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.buildtheearth.buildteamtools.BuildTeamTools; import net.buildtheearth.buildteamtools.modules.generator.model.GeneratorComponent; import net.buildtheearth.buildteamtools.modules.generator.model.Script; +import net.buildtheearth.buildteamtools.modules.generator.model.Settings; +import net.buildtheearth.buildteamtools.utils.MenuItems; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; public class RailScripts extends Script { + private static final int MAX_CONTROL_POINTS = 250; + private static final int MAX_PATH_POINTS = 20_000; + private static final int MAX_BLOCK_PLACEMENTS = 100_000; + private static final int SELECTION_PADDING = 4; + private static final int SELECTION_VERTICAL_PADDING = 12; + private static final int PREPARE_SELECTION_EXPANSION = 8; + private static final int SURFACE_Y_OFFSET = 1; + private static final Direction DEFAULT_FACING = Direction.EAST; + + private static final XMaterial[] CENTER_MATERIALS = new XMaterial[]{ + XMaterial.DEAD_FIRE_CORAL_BLOCK, + XMaterial.STONE, + XMaterial.COBBLESTONE + }; + + private Block[][][] blocks; + private List controlPoints = new ArrayList<>(); + private List centerPath = new ArrayList<>(); + private RailType railType = RailType.STANDARD; + public RailScripts(Player player, GeneratorComponent generatorComponent) { super(player, generatorComponent); - throw new UnsupportedOperationException("RailScripts is currently broken."); - //getPlayer().chat("/clearhistory"); - //Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), this::railScript_v_1_3); + sendRailInfo("Rail Generator is preparing your selection..."); + + Bukkit.getScheduler().runTaskAsynchronously(BuildTeamTools.getInstance(), () -> { + try { + if (!prepareSession()) return; + + railScript_v_2_0(); + } catch (Exception exception) { + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> getGeneratorComponent().sendError(getPlayer())); + exception.printStackTrace(); + } + }); + } + + private boolean prepareSession() { + controlPoints = getControlPoints(); + + if (!hasValidControlPoints()) return false; + + List railSelectionPoints = createRailSelectionPoints(controlPoints); + GeneratorUtils.createPolySelection( + getPlayer(), + railSelectionPoints, + getSelectionMinY(controlPoints), + getSelectionMaxY(controlPoints) + ); + + blocks = GeneratorUtils.prepareScriptSession( + localSession, + actor, + getPlayer(), + weWorld, + PREPARE_SELECTION_EXPANSION, + true, + false, + false + ); + + snapMissingControlPointHeightsToTerrain(controlPoints); + centerPath = createCenterPath(controlPoints); + adjustCenterPathToTerrain(); + railType = getRailType(); + + return true; + } + + private void railScript_v_2_0() { + if (centerPath.size() < 2) { + sendRailError("Rail Generator could not create a valid rail path."); + return; + } + + if (centerPath.size() > MAX_PATH_POINTS) { + sendRailError("Rail Generator path is too large. Please use a smaller selection."); + return; + } + + Map railBlocks = buildRailBlocks(centerPath); + + if (railBlocks.size() > MAX_BLOCK_PLACEMENTS) { + sendRailError("Rail Generator would place too many blocks. Please use a smaller selection."); + return; + } + + setBlockStatesAtPositions( + railBlocks.keySet().stream().map(PositionKey::toVector).toList(), + new ArrayList<>(railBlocks.values()) + ); + + finish(blocks, getRestoreSelectionPoints()); + } + + private boolean hasValidControlPoints() { + if (controlPoints.size() < 2) { + sendRailError("Rail Generator needs at least two points."); + return false; + } + + if (controlPoints.size() > MAX_CONTROL_POINTS) { + sendRailError("Rail Generator has too many points. Please use fewer points."); + return false; + } + + return true; + } + + private void sendRailInfo(String message) { + Bukkit.getScheduler().runTask( + BuildTeamTools.getInstance(), + () -> getPlayer().sendMessage(Component.text(message, NamedTextColor.YELLOW)) + ); + } + + private void sendRailError(String message) { + Bukkit.getScheduler().runTask( + BuildTeamTools.getInstance(), + () -> getPlayer().sendMessage(Component.text(message, NamedTextColor.RED)) + ); + } + + private List getControlPoints() { + List selectionPoints = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); + + if (selectionPoints == null) return Collections.emptyList(); + + return GeneratorUtils.copyToBlockVectors(selectionPoints); + } + + private List createRailSelectionPoints(List points) { + List selectionLine = new ArrayList<>(points); + + if (selectionLine.size() >= 2) + selectionLine = GeneratorUtils.extendPolyLine(selectionLine); + + List shiftedPoints = GeneratorUtils.shiftPoints(selectionLine, SELECTION_PADDING, true); + + if (shiftedPoints == null || shiftedPoints.size() < 3) + return GeneratorUtils.createBoundsSelectionPoints(points, SELECTION_PADDING); + + return shiftedPoints; + } + + private List createCenterPath(List points) { + return GeneratorUtils.removeOrthogonalCorners(GeneratorUtils.createShortestBlockPath(points)); + } + + private Map buildRailBlocks(List path) { + Map railBlocks = new LinkedHashMap<>(); + Set centerPositions = getCenterPositions(path); + Set centerColumns = getCenterColumns(path); + Map sideBlocks = new LinkedHashMap<>(); + + for (int index = 0; index < path.size(); index++) { + Vector center = path.get(index); + + for (RailSidePlacement sidePlacement : getSidePlacements(path, index)) + addSideBlock(sideBlocks, center, sidePlacement, centerPositions, centerColumns); + } + + for (RailSideBlock sideBlock : sideBlocks.values()) + railBlocks.put(sideBlock.key(), createAnvilBlockState(resolveSideBlockFacing(sideBlock, sideBlocks))); + + for (Vector center : path) + railBlocks.put(PositionKey.from(center), createCenterBlockState(center)); + + return railBlocks; } - public void railScript_v_1_3() { - List commands = new ArrayList<>(); - //HashMap flags = rail.getPlayerSettings().get(p.getUniqueId()).getValues(); + private List getSidePlacements(List path, int index) { + List placements = new ArrayList<>(); + Vector center = path.get(index); + RailStep previousStep = index > 0 ? getStep(path.get(index - 1), center) : null; + RailStep nextStep = index < path.size() - 1 ? getStep(center, path.get(index + 1)) : null; + + addSidePlacements(placements, getRailStep(path, index, new RailStep(1, 0))); + + if (previousStep != null) + addSidePlacements(placements, previousStep); + + if (nextStep != null) + addSidePlacements(placements, nextStep); - int xPos = getPlayer().getLocation().getBlockX(); - int zPos = getPlayer().getLocation().getBlockZ(); + return placements; + } - int operations = 0; + private void addSidePlacements(List placements, RailStep step) { + if (step.dx() != 0 && step.dz() != 0) { + addSidePlacement(placements, new RailStep(step.dx(), 0), GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING)); + addSidePlacement(placements, new RailStep(0, step.dz()), GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING)); + return; + } - int railWidth = 5; + if (step.dx() != 0) { + Direction facing = GeneratorUtils.getFacing(step.dx(), 0, DEFAULT_FACING); + addSidePlacement(placements, new RailStep(0, 1), facing); + addSidePlacement(placements, new RailStep(0, -1), facing); + return; + } + Direction facing = GeneratorUtils.getFacing(0, step.dz(), DEFAULT_FACING); + addSidePlacement(placements, new RailStep(1, 0), facing); + addSidePlacement(placements, new RailStep(-1, 0), facing); + } - // TODO START TEMP + private void addSidePlacement(List placements, RailStep offset, Direction facing) { + for (RailSidePlacement placement : placements) { + if (placement.offset().equals(offset)) return; + } - 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"); + placements.add(new RailSidePlacement(offset, facing)); + } - finish(regionBlocks, points); + private void addSideBlock( + Map sideBlocks, + Vector center, + RailSidePlacement sidePlacement, + Set centerPositions, + Set centerColumns + ) { + RailStep sideOffset = sidePlacement.offset(); + if (sideOffset.dx() == 0 && sideOffset.dz() == 0) + return; - // TODO END TEMP + int x = center.getBlockX() + sideOffset.dx(); + int z = center.getBlockZ() + sideOffset.dz(); + int y = getRailSurfaceY(x, z, center.getBlockY()); - /* - // Get the points of the region - List points = GeneratorUtils.getSelectionPointsFromRegion(getRegion()); - points = GeneratorUtils.populatePoints(points, 5); + PositionKey key = PositionKey.of(x, y, z); + ColumnKey columnKey = ColumnKey.from(key); - // ----------- PREPARATION 01 ---------- - // Replace all unnecessary blocks with air + if (centerPositions.contains(key) || centerColumns.contains(columnKey)) + return; - List polyRegionLine = new ArrayList<>(points); - polyRegionLine = GeneratorUtils.extendPolyLine(polyRegionLine); - List polyRegionPoints = GeneratorUtils.shiftPoints(polyRegionLine, railWidth + 2, true); + sideBlocks + .computeIfAbsent(columnKey, ignored -> new RailSideBlock(key)) + .addFacing(sidePlacement.facing()); + } - // Create a region from the points - GeneratorUtils.createPolySelection(getPlayer(), polyRegionPoints, null); + private Direction resolveSideBlockFacing(RailSideBlock sideBlock, Map sideBlocks) { + PositionKey key = sideBlock.key(); + boolean east = sideBlocks.containsKey(ColumnKey.of(key.x() + 1, key.z())); + boolean west = sideBlocks.containsKey(ColumnKey.of(key.x() - 1, key.z())); + boolean south = sideBlocks.containsKey(ColumnKey.of(key.x(), key.z() + 1)); + boolean north = sideBlocks.containsKey(ColumnKey.of(key.x(), key.z() - 1)); + int xConnections = (east ? 1 : 0) + (west ? 1 : 0); + int zConnections = (south ? 1 : 0) + (north ? 1 : 0); + Direction preferredFacing = sideBlock.getPreferredFacing(); - getPlayer().chat("//expand 30 up"); - getPlayer().chat("//expand 10 down"); + if (xConnections > zConnections) + return resolveAxisFacing(preferredFacing, Direction.EAST, Direction.WEST, east, west); - // Remove non-solid blocks - getPlayer().chat("//gmask !#solid"); - getPlayer().chat("//replace 0"); - operations++; + if (zConnections > xConnections) + return resolveAxisFacing(preferredFacing, Direction.SOUTH, Direction.NORTH, south, north); - // Remove all trees and pumpkins - getPlayer().chat("//gmask"); - getPlayer().chat("//replace leaves,log,pumpkin 0"); - operations++; + return preferredFacing; + } - getPlayer().chat("//gmask"); + private Direction resolveAxisFacing( + Direction preferredFacing, + Direction positiveFacing, + Direction negativeFacing, + boolean hasPositiveNeighbor, + boolean hasNegativeNeighbor + ) { + if (preferredFacing == positiveFacing && hasPositiveNeighbor || preferredFacing == negativeFacing && hasNegativeNeighbor) + return preferredFacing; + if (hasPositiveNeighbor && !hasNegativeNeighbor) + return positiveFacing; - Block[][][] regionBlocks = GeneratorUtils.analyzeRegion(getPlayer(), getPlayer().getWorld()); - GeneratorUtils.adjustHeight(points, regionBlocks); + if (hasNegativeNeighbor && !hasPositiveNeighbor) + return negativeFacing; + return preferredFacing == negativeFacing ? negativeFacing : positiveFacing; + } - // ----------- RAILWAY ---------- + private Set getCenterColumns(List path) { + Set centerColumns = new HashSet<>(); - // Draw the railway curve + for (Vector center : path) + centerColumns.add(ColumnKey.from(PositionKey.from(center))); - GeneratorUtils.createConvexSelection(commands, points); - commands.add("//gmask !solid"); - commands.add("//curve 42"); - operations++; - commands.add("//gmask"); + return centerColumns; + } + private Set getCenterPositions(List path) { + Set centerPositions = new HashSet<>(); - // Create the railway - GeneratorUtils.createPolySelection(commands, polyRegionPoints); + for (Vector center : path) + centerPositions.add(PositionKey.from(center)); - 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++; + return centerPositions; + } - 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 RailStep getRailStep(List path, int index, RailStep fallbackStep) { + RailStep previousStep = index > 0 ? getStep(path.get(index - 1), path.get(index)) : null; + RailStep nextStep = index < path.size() - 1 ? getStep(path.get(index), path.get(index + 1)) : null; - commands.add("//gmask"); + if (previousStep != null && nextStep != null) { + int dx = Integer.compare(previousStep.dx() + nextStep.dx(), 0); + int dz = Integer.compare(previousStep.dz() + nextStep.dz(), 0); - // Depending on the selection type, the selection needs to be restored correctly - if(getRegion() instanceof Polygonal2DRegion || getRegion() instanceof ConvexPolyhedralRegion) - GeneratorUtils.createConvexSelection(commands, points); - else if(getRegion() instanceof CuboidRegion){ - CuboidRegion cuboidRegion = (CuboidRegion) getRegion(); - Vector pos1 = new Vector(cuboidRegion.getPos1().getX(), cuboidRegion.getPos1().getY(), cuboidRegion.getPos1().getZ()); - Vector pos2 = new Vector(cuboidRegion.getPos2().getX(), cuboidRegion.getPos2().getY(), cuboidRegion.getPos2().getZ()); - GeneratorUtils.createCuboidSelection(commands, pos1, pos2); + if (dx != 0 || dz != 0) return new RailStep(dx, dz); } - GeneratorModule.getInstance().getGeneratorCommands().add(new Command(getPlayer(), getGeneratorComponent(), commands, operations, regionBlocks)); - GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new History.HistoryEntry(GeneratorType.RAILWAY, operations));*/ + if (nextStep != null) return nextStep; + + if (previousStep != null) return previousStep; + + return fallbackStep; + } + + private RailStep getStep(Vector from, Vector to) { + int dx = Integer.compare(to.getBlockX() - from.getBlockX(), 0); + int dz = Integer.compare(to.getBlockZ() - from.getBlockZ(), 0); + + if (dx == 0 && dz == 0) return null; + + return new RailStep(dx, dz); + } + + private void snapMissingControlPointHeightsToTerrain(List points) { + if (blocks == null || !hasMissingControlPointHeights(points)) return; + + GeneratorUtils.adjustHeight(points, blocks); + } + + private void adjustCenterPathToTerrain() { + if (blocks == null || centerPath.isEmpty()) return; + + for (Vector point : centerPath) + point.setY(getRailSurfaceY(point.getBlockX(), point.getBlockZ(), point.getBlockY())); + } + + private int getRailSurfaceY(int x, int z, int fallbackY) { + if (blocks == null) + return fallbackY; + + int surfaceY = GeneratorUtils.getMaxHeight(blocks, x, z, MenuItems.getIgnoredMaterials()); + + if (surfaceY == 0) + return fallbackY; + + return surfaceY + SURFACE_Y_OFFSET; + } + + private boolean hasMissingControlPointHeights(List points) { + // WorldEdit polygon and convex selection points can arrive with Y=0, + // which means the rail should snap those points to the prepared terrain. + for (Vector point : points) + if (point.getBlockY() != 0) return false; + + return true; + } + + private List getRestoreSelectionPoints() { + return controlPoints; + } + + private int getSelectionMinY(List points) { + int minY = getRegion() != null ? getRegion().getMinimumY() : GeneratorUtils.getMinHeight(points); + return Math.max(getPlayer().getWorld().getMinHeight(), minY - SELECTION_VERTICAL_PADDING); + } + + private int getSelectionMaxY(List points) { + int maxY = getRegion() != null ? getRegion().getMaximumY() : GeneratorUtils.getMaxHeight(points); + return Math.min(getPlayer().getWorld().getMaxHeight() - 1, maxY + SELECTION_VERTICAL_PADDING); + } + + private BlockState createCenterBlockState(Vector position) { + return switch (railType) { + case STANDARD -> createStandardCenterBlockState(position); + }; + } + + private BlockState createStandardCenterBlockState(Vector position) { + int index = Math.floorMod( + position.getBlockX() * 31 + position.getBlockY() * 23 + position.getBlockZ() * 17, + CENTER_MATERIALS.length + ); + + return GeneratorUtils.getBlockState(CENTER_MATERIALS[index]); + } + + private BlockState createAnvilBlockState(Direction direction) { + return GeneratorUtils.getBlockStateWithFacing(BlockTypes.ANVIL, direction); + } + + private RailType getRailType() { + Settings settings = getGeneratorComponent().getPlayerSettings().get(getPlayer().getUniqueId()); + + if (!(settings instanceof RailSettings railSettings)) + return RailType.STANDARD; + + Object value = railSettings.getValues().get(RailFlag.RAIL_TYPE); + return value instanceof RailType selectedRailType ? selectedRailType : RailType.STANDARD; + } + + private record RailStep(int dx, int dz) { + } + + private record RailSidePlacement(RailStep offset, Direction facing) { + } + + private record PositionKey(int x, int y, int z) { + + private static PositionKey from(Vector vector) { + return new PositionKey( + vector.getBlockX(), + vector.getBlockY(), + vector.getBlockZ() + ); + } + + private static PositionKey of(int x, int y, int z) { + return new PositionKey(x, y, z); + } + + private Vector toVector() { + return new Vector(x, y, z); + } + } + + private record ColumnKey(int x, int z) { + + private static ColumnKey from(PositionKey key) { + return new ColumnKey(key.x(), key.z()); + } + + private static ColumnKey of(int x, int z) { + return new ColumnKey(x, z); + } + } + + private static class RailSideBlock { + + private final PositionKey key; + private final Map facingScores = new LinkedHashMap<>(); + + private RailSideBlock(PositionKey key) { + this.key = key; + } + + private PositionKey key() { + return key; + } + + private void addFacing(Direction facing) { + facingScores.merge(facing, 1, Integer::sum); + } + + private Direction getPreferredFacing() { + Direction preferredFacing = DEFAULT_FACING; + int preferredScore = -1; + + for (Map.Entry entry : facingScores.entrySet()) { + if (entry.getValue() > preferredScore) { + preferredFacing = entry.getKey(); + preferredScore = entry.getValue(); + } + } + + return preferredFacing; + } } -} \ 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..ae244c3d 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailSettings.java @@ -9,9 +9,8 @@ public RailSettings(Player player) { super(player); } + @Override public void setDefaultValues() { - - // Lane Count (Default: Fixed Value) - setValue(RailFlag.LANE_COUNT, 1); + setValue(RailFlag.RAIL_TYPE, RailType.STANDARD); } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java new file mode 100644 index 00000000..6c641532 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/RailType.java @@ -0,0 +1,32 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail; + +import com.cryptomorin.xseries.XMaterial; +import lombok.Getter; +import org.jspecify.annotations.Nullable; + +public enum RailType { + + STANDARD("standard", "Standard", XMaterial.RAIL); + + @Getter + private final String identifier; + @Getter + private final String displayName; + @Getter + private final XMaterial icon; + + RailType(String identifier, String displayName, XMaterial icon) { + this.identifier = identifier; + this.displayName = displayName; + this.icon = icon; + } + + public static @Nullable RailType byString(String value) { + for (RailType railType : RailType.values()) { + if (railType.getIdentifier().equalsIgnoreCase(value) || railType.name().equalsIgnoreCase(value)) + return railType; + } + + return null; + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java new file mode 100644 index 00000000..af2e400e --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/components/rail/menu/RailTypeMenu.java @@ -0,0 +1,56 @@ +package net.buildtheearth.buildteamtools.modules.generator.components.rail.menu; + +import com.alpsbte.alpslib.utils.item.Item; +import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailFlag; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailSettings; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailType; +import net.buildtheearth.buildteamtools.modules.generator.menu.GeneratorMenu; +import net.buildtheearth.buildteamtools.modules.generator.model.Settings; +import net.buildtheearth.buildteamtools.utils.menus.NameListMenu; +import org.apache.commons.lang3.tuple.MutablePair; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + +public class RailTypeMenu extends NameListMenu { + + public static final String RAIL_TYPE_INV_NAME = "Choose a Rail Type"; + + public RailTypeMenu(Player player, boolean autoLoad) { + super(player, RAIL_TYPE_INV_NAME, getRailTypes(), new GeneratorMenu(player, false), autoLoad); + } + + private static @NonNull List> getRailTypes() { + List> railTypes = new ArrayList<>(); + + for (RailType railType : RailType.values()) + railTypes.add(new MutablePair<>(Item.create(railType.getIcon().get(), railType.getDisplayName()), railType.getIdentifier())); + + return railTypes; + } + + @Override + protected void setItemClickEventsAsync() { + super.setItemClickEventsAsync(); + + if (canProceed()) + getMenu().getSlot(NEXT_ITEM_SLOT).setClickHandler((clickPlayer, clickInformation) -> { + Settings settings = GeneratorModule.getInstance().getRail().getPlayerSettings().get(clickPlayer.getUniqueId()); + + if (!(settings instanceof RailSettings railSettings)) + return; + + railSettings.setValue(RailFlag.RAIL_TYPE, selectedNames.getFirst()); + + clickPlayer.closeInventory(); + clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); + + GeneratorModule.getInstance().getRail().generate(clickPlayer); + }); + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java index 1d4523ae..6fb10e7c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/listeners/GeneratorListener.java @@ -11,15 +11,64 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerInteractEvent; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + public class GeneratorListener implements Listener { + private static final Map> INTERNAL_GENERATOR_COMMANDS = new ConcurrentHashMap<>(); + + public static void queueInternalGeneratorCommand(Player player, String command) { + INTERNAL_GENERATOR_COMMANDS + .computeIfAbsent(player.getUniqueId(), ignored -> new ConcurrentLinkedQueue<>()) + .add(command); + } + + public static void removeInternalGeneratorCommand(Player player, String command) { + Queue commands = INTERNAL_GENERATOR_COMMANDS.get(player.getUniqueId()); + + if (commands == null) + return; + + commands.remove(command); + + if (commands.isEmpty()) + INTERNAL_GENERATOR_COMMANDS.remove(player.getUniqueId(), commands); + } + + private static boolean consumeInternalGeneratorCommand(Player player, String command) { + Queue commands = INTERNAL_GENERATOR_COMMANDS.get(player.getUniqueId()); + + if (commands == null) + return false; + + String queuedCommand = commands.peek(); + + if (!command.equals(queuedCommand)) + return false; + + commands.poll(); + + if (commands.isEmpty()) + INTERNAL_GENERATOR_COMMANDS.remove(player.getUniqueId(), commands); + + return true; + } + @EventHandler public void onCommand(PlayerCommandPreprocessEvent e) { Player p = e.getPlayer(); - if(!GeneratorModule.getInstance().isGenerating(p)) + if (!GeneratorModule.getInstance().isGenerating(p)) return; - if(!e.getMessage().startsWith("//")) + + if (!e.getMessage().startsWith("//")) + return; + + if (consumeInternalGeneratorCommand(p, e.getMessage())) return; e.setCancelled(true); @@ -28,14 +77,16 @@ 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); 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..d7ba446e 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/menu/GeneratorMenu.java @@ -4,10 +4,16 @@ import com.cryptomorin.xseries.XMaterial; import net.buildtheearth.buildteamtools.modules.common.CommonModule; import net.buildtheearth.buildteamtools.modules.generator.GeneratorModule; +import net.buildtheearth.buildteamtools.modules.generator.components.field.Field; +import net.buildtheearth.buildteamtools.modules.generator.components.field.FieldSettings; +import net.buildtheearth.buildteamtools.modules.generator.components.field.menu.CropTypeMenu; import net.buildtheearth.buildteamtools.modules.generator.components.house.House; import net.buildtheearth.buildteamtools.modules.generator.components.house.HouseSettings; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; import net.buildtheearth.buildteamtools.modules.generator.components.house.menu.WallColorMenu; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.Rail; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailSettings; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.menu.RailTypeMenu; import net.buildtheearth.buildteamtools.modules.generator.components.road.Road; import net.buildtheearth.buildteamtools.modules.generator.components.road.RoadSettings; import net.buildtheearth.buildteamtools.modules.generator.components.road.menu.RoadColorMenu; @@ -20,6 +26,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 +44,142 @@ 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 RAIL_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 - getMenu().getSlot(RAILWAY_ITEM_SLOT).setItem(railwayItem); - + ArrayList railwayLore = ListUtil.createList( + "", + "§eDescription:", + "Generate a predefined railway", + "from your active WorldEdit selection", + "", + "§eSupported selections:", + "- Cuboid", + "- Polygonal", + "- Convex", + "", + "§eFeatures:", + "- Rail Type selection", + "- 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(RAIL_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 +189,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 +206,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) -> { + getMenu().getSlot(RAIL_ITEM_SLOT).setClickHandler(((clickPlayer, clickInformation) -> { if (clickInformation.getClickType().equals(ClickType.RIGHT)) { - sendMoreInformation(clickPlayer, GeneratorType.RAILWAY); + sendMoreInformation(clickPlayer, GeneratorType.RAIL); 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);*/ + new RailTypeMenu(clickPlayer, true); })); - // 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 +240,49 @@ 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 field = GeneratorModule.getInstance().getField(); field.getPlayerSettings().put(clickPlayer.getUniqueId(), new FieldSettings(clickPlayer)); - if(!field.checkForPlayer(clickPlayer)) + if (!field.checkForPlayer(clickPlayer)) return; clickPlayer.closeInventory(); clickPlayer.playSound(clickPlayer.getLocation(), Sound.UI_BUTTON_CLICK, 1.0F, 1.0F); - new CropTypeMenu(clickPlayer, true);*/ + new CropTypeMenu(clickPlayer, true); })); } 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: " + wikiPage, NamedTextColor.GRAY) + .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/BlockChange.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/BlockChange.java new file mode 100644 index 00000000..2fe94289 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/BlockChange.java @@ -0,0 +1,58 @@ +package net.buildtheearth.buildteamtools.modules.generator.model; + +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; + +public class BlockChange { + + @Getter + private final String worldName; + + @Getter + private final int x; + + @Getter + private final int y; + + @Getter + private final int z; + + @Getter + private final String oldBlockData; + + @Getter + @Setter + private String newBlockData; + + public BlockChange(String worldName, int x, int y, int z, String oldBlockData, String newBlockData) { + this.worldName = worldName; + this.x = x; + this.y = y; + this.z = z; + this.oldBlockData = oldBlockData; + this.newBlockData = newBlockData; + } + + public void applyOld() { + apply(oldBlockData); + } + + public void applyNew() { + apply(newBlockData); + } + + private void apply(String blockDataString) { + World world = Bukkit.getWorld(worldName); + + if (world == null) + return; + + Block block = world.getBlockAt(x, y, z); + BlockData blockData = Bukkit.createBlockData(blockDataString); + block.setBlockData(blockData, false); + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java index 4ac228fd..977ad684 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Command.java @@ -11,8 +11,11 @@ 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.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -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); @@ -189,6 +190,10 @@ public void processOperation(Operation operation){ future = GeneratorUtils.replaceBlocks(localSession, actor, weWorld, (BlockState[]) operation.get(0), (BlockState[]) operation.get(1)); break; + case SET_BLOCKSTATES_AT_POSITIONS: + future = GeneratorUtils.setBlockStatesAtPositions(localSession, actor, weWorld, Arrays.asList((Vector[]) operation.get(0)), Arrays.asList((BlockState[]) operation.get(1))); + break; + case DRAW_CURVE_WITH_MASKS: future = GeneratorUtils.drawCurveWithMasks(localSession, actor, weWorld, blocks, Arrays.asList((String[]) operation.get(0)), Arrays.asList((Vector[]) operation.get(1)), (BlockState[]) operation.get(2), (Boolean) operation.get(3)); break; @@ -225,33 +230,58 @@ 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(); + sendGeneratorError(); } - 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(); + sendGeneratorError(); } + // Remove the processed operation from the queue operations.remove(0); }); - - }else if(!breakPointActive) + } else if (!breakPointActive) { operations.remove(0); + } + } + + private void sendGeneratorError() { + if (Bukkit.isPrimaryThread()) { + generatorComponent.sendError(player); + return; + } + + Bukkit.getScheduler().runTask(BuildTeamTools.getInstance(), () -> generatorComponent.sendError(player)); + } + + private void runInternalGeneratorCommand(String command) { + GeneratorListener.queueInternalGeneratorCommand(player, command); + + try { + player.chat(command); + } finally { + GeneratorListener.removeInternalGeneratorCommand(player, command); + } } /** 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,22 +291,22 @@ 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); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java index c95f7177..0f00eec6 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Flag.java @@ -19,7 +19,7 @@ static Flag byString(@NonNull GeneratorType generatorType, String flag) { case HOUSE -> HouseFlag.byString(flag); case ROAD -> RoadFlag.byString(flag); case TREE -> TreeFlag.byString(flag); - case RAILWAY -> RailFlag.byString(flag); + case RAIL -> RailFlag.byString(flag); case FIELD -> FieldFlag.byString(flag); }; } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java index ed9dc8dd..76366680 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/FlagType.java @@ -7,6 +7,7 @@ import net.buildtheearth.buildteamtools.modules.generator.components.field.CropStage; import net.buildtheearth.buildteamtools.modules.generator.components.field.CropType; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailType; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeType; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeWidth; @@ -22,6 +23,7 @@ public enum FlagType { ROOF_TYPE(RoofType.class), CROP_TYPE(CropType.class), CROP_STAGE(CropStage.class), + RAIL_TYPE(RailType.class), TREE_TYPE(TreeType.class), TREE_WIDTH(TreeWidth.class); @@ -85,6 +87,8 @@ public static Object convertToFlagType(Flag flag, String value){ return CropType.getByIdentifier(value); case CROP_STAGE: return CropStage.getByIdentifier(value); + case RAIL_TYPE: + return RailType.byString(value); case TREE_TYPE: return TreeType.byString(value); case TREE_WIDTH: diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java index cde217d7..d72b0cb4 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorComponent.java @@ -37,30 +37,42 @@ 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); + if (!convertArgsToSettings(p, args)) + return; + 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; case ROAD: addPlayerSetting(p.getUniqueId(), new RoadSettings(p)); break; - case RAILWAY: + case RAIL: addPlayerSetting(p.getUniqueId(), new RailSettings(p)); break; case TREE: @@ -72,11 +84,6 @@ 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); - } - public void sendHelp(@NonNull Player p) { p.sendMessage(Component.text(getWikiPage(), NamedTextColor.YELLOW)); } @@ -88,7 +95,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 +104,7 @@ public String getCommand(@NonNull Player p) { String type = switch (generatorType) { case HOUSE -> "house"; case ROAD -> "road"; - case RAILWAY -> "railway"; + case RAIL -> "rail"; case TREE -> "tree"; case FIELD -> "field"; }; @@ -110,7 +117,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 +126,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 +140,48 @@ public void sendSuccessMessage(Player p){ String type = switch (generatorType) { case HOUSE -> "House"; case ROAD -> "Road"; - case RAILWAY -> "Railway"; + case RAIL -> "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 boolean convertArgsToSettings(Player p, String[] args) { + boolean convertedFlag = false; + + 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; + convertedFlag = true; Object flagValue = FlagType.convertToFlagType(finalFlag, flagAndValue[1]); String errorMessage = FlagType.validateFlagType(finalFlag, flagValue); - if(errorMessage != null){ + if (errorMessage != null) { p.sendMessage(errorMessage); continue; } @@ -170,8 +189,12 @@ protected void convertArgsToSettings(Player p, String[] args){ getPlayerSettings().get(p.getUniqueId()).setValue(finalFlag, flagValue); } - if(getPlayerSettings().get(p.getUniqueId()).getValues().isEmpty() && args.length > 1) + if (!convertedFlag && args.length > 1) { sendHelp(p); + return false; + } + + return true; } @Override diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java index 53b53551..ec3f2726 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/GeneratorType.java @@ -7,7 +7,7 @@ public enum GeneratorType { HOUSE("House", WikiLinks.Gen.HOUSE), ROAD("Road", WikiLinks.Gen.ROAD), - RAILWAY("Railway", WikiLinks.Gen.RAIL), + RAIL("Rail", WikiLinks.Gen.RAIL), TREE("Tree", WikiLinks.Gen.TREE), FIELD("Field", WikiLinks.Gen.FIELD); diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java index 02e8473f..cc55862c 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/History.java @@ -27,36 +27,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,52 +71,36 @@ 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); - GeneratorUtils.redo(session, p, actor, worldEditCommandCount); + if (entry.hasBlockChanges()) { + entry.applyRedo(); + } else { + LocalSession session = entry.getScript().getLocalSession(); + Actor actor = entry.getScript().getActor(); + int worldEditCommandCount = entry.getWorldEditCommandCount(); - getHistoryEntries().add(getUndoHistoryEntries().get(0)); - getUndoHistoryEntries().clear(); + GeneratorUtils.redo(session, p, actor, worldEditCommandCount); + } - p.playSound(p.getLocation(), Sound.ENTITY_ZOMBIE_DESTROY_EGG, 1.0F, 1.0F); + 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))) ); p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); }, 20L); } - - public static class HistoryEntry { - - @Getter - private final GeneratorType generatorType; - @Getter - private final long timeCreated; - @Getter - private final Script script; - @Getter - private final int worldEditCommandCount; - - public HistoryEntry(GeneratorType generatorType, Script script) { - this.generatorType = generatorType; - this.timeCreated = System.currentTimeMillis(); - this.worldEditCommandCount = script.getChanges(); - this.script = script; - } - } } - - diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java new file mode 100644 index 00000000..d80fc1b9 --- /dev/null +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/HistoryEntry.java @@ -0,0 +1,62 @@ +package net.buildtheearth.buildteamtools.modules.generator.model; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class HistoryEntry { + + @Getter + private final GeneratorType generatorType; + + @Getter + private final long timeCreated; + + @Getter + private final Script script; + + @Getter + private final int worldEditCommandCount; + + @Getter + private final List blockChanges; + + public HistoryEntry(GeneratorType generatorType, Script script) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = script.getChanges(); + this.script = script; + this.blockChanges = new ArrayList<>(); + } + + public HistoryEntry(GeneratorType generatorType, Script script, int worldEditCommandCount) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = worldEditCommandCount; + this.script = script; + this.blockChanges = new ArrayList<>(); + } + + public HistoryEntry(GeneratorType generatorType, Script script, List blockChanges) { + this.generatorType = generatorType; + this.timeCreated = System.currentTimeMillis(); + this.worldEditCommandCount = 0; + this.script = script; + this.blockChanges = blockChanges == null ? new ArrayList<>() : blockChanges; + } + + public boolean hasBlockChanges() { + return blockChanges != null && !blockChanges.isEmpty(); + } + + public void applyUndo() { + for (int i = blockChanges.size() - 1; i >= 0; i--) + blockChanges.get(i).applyOld(); + } + + public void applyRedo() { + for (BlockChange blockChange : blockChanges) + blockChange.applyNew(); + } +} diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java index 0d230139..01e465c5 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Operation.java @@ -19,6 +19,7 @@ public enum OperationType { CUBOID_SELECTION(Vector.class, Vector.class), POLYGONAL_SELECTION(Vector[].class), REPLACE_BLOCKSTATES(BlockState[].class, BlockState[].class), + SET_BLOCKSTATES_AT_POSITIONS(Vector[].class, BlockState[].class), REPLACE_BLOCKSTATES_WITH_MASKS(String[].class, BlockState.class, BlockState[].class, Integer.class), DRAW_CURVE_WITH_MASKS(String[].class, Vector[].class, BlockState[].class, Boolean.class), DRAW_POLY_LINE_WITH_MASKS(String[].class, Vector[].class, BlockState[].class, Boolean.class, Boolean.class), diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java index 0a5dc7a0..aaa81927 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Script.java @@ -59,7 +59,7 @@ protected void finish(Block[][][] blocks, List points){ //setGmask(null); GeneratorModule.getInstance().getGeneratorCommands().add(new Command(this, blocks)); - GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new History.HistoryEntry(getGeneratorComponent().getGeneratorType(), this)); + GeneratorModule.getInstance().getPlayerHistory(getPlayer()).addHistoryEntry(new HistoryEntry(getGeneratorComponent().getGeneratorType(), this)); } @@ -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. @@ -232,6 +245,18 @@ public void replaceBlocks(BlockState from, BlockState to){ replaceBlocks(new BlockState[]{from}, new BlockState[]{to}); } + public void setBlockStatesAtPositions(List positions, List blockStates) { + if (positions.size() != blockStates.size()) + throw new IllegalArgumentException("Position and BlockState lists must have the same size"); + + operations.add(new Operation( + Operation.OperationType.SET_BLOCKSTATES_AT_POSITIONS, + positions.toArray(new Vector[0]), + blockStates.toArray(new BlockState[0]) + )); + changes += positions.size(); + } + /** * Set blocks with a mask. * It creates a new Operation with type SET_BLOCKS_WITH_EXPRESSION_MASK and adds it to the list of operations to execute. @@ -326,7 +351,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 +388,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 +427,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 +451,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); + } } diff --git a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java index 7bed4c57..f617c70b 100644 --- a/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java +++ b/src/main/java/net/buildtheearth/buildteamtools/modules/generator/model/Settings.java @@ -7,6 +7,7 @@ import net.buildtheearth.buildteamtools.modules.generator.components.field.CropStage; import net.buildtheearth.buildteamtools.modules.generator.components.field.CropType; import net.buildtheearth.buildteamtools.modules.generator.components.house.RoofType; +import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailType; import net.buildtheearth.buildteamtools.modules.generator.components.tree.TreeWidth; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -85,6 +86,9 @@ public HashMap getValuesAsString(){ case CROP_STAGE: value = new StringBuilder(((CropStage) valueObject).getIdentifier()); break; + case RAIL_TYPE: + value = new StringBuilder(((RailType) valueObject).getIdentifier()); + break; case TREE_WIDTH: value = new StringBuilder(((TreeWidth) valueObject).getName()); break;