diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 03b838de6..f60a1cbf2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -2284,6 +2284,90 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { //FAWE end } + /** + * Makes a cone. + * + * @param pos Center of the cone + * @param block The block pattern to use + * @param radiusX The cone's largest north/south extent + * @param radiusZ The cone's largest east/west extent + * @param height The cone's up/down extent. If negative, extend downward. + * @param filled If false, only a shell will be generated. + * @param thickness The cone's wall thickness, if it's hollow. + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeCone( + BlockVector3 pos, + Pattern block, + double radiusX, + double radiusZ, + int height, + boolean filled, + double thickness + ) throws MaxChangedBlocksException { + int affected = 0; + + final int ceilRadiusX = (int) Math.ceil(radiusX); + final int ceilRadiusZ = (int) Math.ceil(radiusZ); + + double rx2 = Math.pow(radiusX, 2); + double ry2 = Math.pow(height, 2); + double rz2 = Math.pow(radiusZ, 2); + + int cx = pos.getX(); + int cy = pos.getY(); + int cz = pos.getZ(); + + for (int y = 0; y < height; ++y) { + double ySquaredMinusHeightOverHeightSquared = Math.pow(y - height, 2) / ry2; + int yy = cy + y; + forX: + for (int x = 0; x <= ceilRadiusX; ++x) { + double xSquaredOverRadiusX = Math.pow(x, 2) / rx2; + int xx = cx + x; + forZ: + for (int z = 0; z <= ceilRadiusZ; ++z) { + int zz = cz + z; + double zSquaredOverRadiusZ = Math.pow(z, 2) / rz2; + double distanceFromOriginMinusHeightSquared = xSquaredOverRadiusX + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared; + + if (distanceFromOriginMinusHeightSquared > 1) { + if (z == 0) { + break forX; + } + break forZ; + } + + if (!filled) { + double xNext = Math.pow(x + thickness, 2) / rx2 + zSquaredOverRadiusZ - ySquaredMinusHeightOverHeightSquared; + double yNext = xSquaredOverRadiusX + zSquaredOverRadiusZ - Math.pow(y + thickness - height, 2) / ry2; + double zNext = xSquaredOverRadiusX + Math.pow(z + thickness, 2) / rz2 - ySquaredMinusHeightOverHeightSquared; + if (xNext <= 0 && zNext <= 0 && (yNext <= 0 && y + thickness != height)) { + continue; + } + } + + if (distanceFromOriginMinusHeightSquared <= 0) { + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + if (setBlock(xx, yy, zz, block)) { + ++affected; + } + } + } + } + } + return affected; + } + /** * Move the blocks in a region a certain direction. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index ca67ad132..dbd698b24 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -188,6 +188,49 @@ public class GenerationCommands { return affected; } + @Command( + name = "/cone", + desc = "Generates a cone." + ) + @CommandPermissions("worldedit.generation.cone") + @Logging(PLACEMENT) + public int cone(Actor actor, LocalSession session, EditSession editSession, + @Arg(desc = "The pattern of blocks to generate") + Pattern pattern, + @Arg(desc = "The radii of the cone. 1st is N/S, 2nd is E/W") + @Radii(2) + List radii, + @Arg(desc = "The height of the cone", def = "1") + int height, + @Switch(name = 'h', desc = "Make a hollow cone") + boolean hollow, + @Arg(desc = "Thickness of the hollow cone", def = "1") + double thickness + ) throws WorldEditException { + double radiusX; + double radiusZ; + switch (radii.size()) { + case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0)); + case 2 -> { + radiusX = Math.max(1, radii.get(0)); + radiusZ = Math.max(1, radii.get(1)); + } + default -> { + actor.printError(Caption.of("worldedit.cone.invalid-radius")); + return 0; + } + } + + worldEdit.checkMaxRadius(radiusX); + worldEdit.checkMaxRadius(radiusZ); + worldEdit.checkMaxRadius(height); + + BlockVector3 pos = session.getPlacementPosition(actor); + int affected = editSession.makeCone(pos, pattern, radiusX, radiusZ, height, !hollow, thickness); + actor.printInfo(Caption.of("worldedit.cone.created", TextComponent.of(affected))); + return affected; + } + @Command( name = "/hsphere", desc = "Generates a hollow sphere." diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 790f7b6fa..5e922cdae 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -489,6 +489,8 @@ "worldedit.jumpto.none": "No block in sight (or too far away)!", "worldedit.up.obstructed": "You would hit something above you.", "worldedit.up.moved": "Woosh!", + "worldedit.cone.invalid-radius": "You must either specify 1 or 2 radius values.", + "worldedit.cone.created": "{0} blocks have been created.", "worldedit.cyl.invalid-radius": "You must either specify 1 or 2 radius values.", "worldedit.cyl.created": "{0} blocks have been created.", "worldedit.hcyl.thickness-too-large": "Thickness cannot be larger than x or z radii.",