Skip to content

Commit c4551e5

Browse files
committed
Improve //hollow command
- Add -p flag to iterate from placement position instead of the selection boundaries - Add -g flag to consider geometry in visibility calculations - By default, //hollow will no longer open up faces that are touching the selection bounding box. The old behaviour can be access with the -o flag.
1 parent 11e9068 commit c4551e5

3 files changed

Lines changed: 214 additions & 35 deletions

File tree

verification/src/changes/accepted-core-public-api-changes.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
"changes": [
2323
"METHOD_REMOVED"
2424
]
25+
},
26+
{
27+
"type": "com.sk89q.worldedit.EditSession",
28+
"member": "Method com.sk89q.worldedit.EditSession.makeCuboidFaces(com.sk89q.worldedit.regions.Region,int,com.sk89q.worldedit.function.pattern.Pattern)",
29+
"changes": [
30+
"METHOD_NOW_FINAL"
31+
]
2532
}
2633
],
2734
"Why on earth is this even an issue?": [

worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java

Lines changed: 180 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.common.collect.ImmutableList;
2323
import com.google.errorprone.annotations.InlineMe;
24+
import com.sk89q.worldedit.blocks.ShapeType;
2425
import com.sk89q.worldedit.entity.BaseEntity;
2526
import com.sk89q.worldedit.entity.Entity;
2627
import com.sk89q.worldedit.event.extent.EditSessionEvent;
@@ -129,6 +130,7 @@
129130
import com.sk89q.worldedit.world.block.BlockType;
130131
import com.sk89q.worldedit.world.block.BlockTypes;
131132
import com.sk89q.worldedit.world.generation.TreeType;
133+
import com.sk89q.worldedit.world.registry.BlockMaterial;
132134
import com.sk89q.worldedit.world.registry.LegacyMapper;
133135
import org.apache.logging.log4j.Logger;
134136

@@ -2658,6 +2660,43 @@ public int deformRegion(final Region region, final Transform targetTransform, fi
26582660
return affected;
26592661
}
26602662

2663+
private Direction[] getBlockedDirections(BlockVector3 position) {
2664+
BlockState blockState = getBlock(position);
2665+
BlockType blockType = blockState.getBlockType();
2666+
BlockMaterial material = blockState.getMaterial();
2667+
2668+
if (material.isAir()) {
2669+
return NO_DIRECTIONS;
2670+
}
2671+
if (material.isLiquid()) {
2672+
return NO_DIRECTIONS;
2673+
}
2674+
Direction[] blockedDirections = BLOCKED_DIRECTIONS_OVERRIDE.get(blockType);
2675+
if (blockedDirections != null) {
2676+
return blockedDirections;
2677+
}
2678+
if (material.isFullCube(ShapeType.VISUAL_SHAPE)) {
2679+
return CARDINAL_UPRIGHT_DIRECTIONS;
2680+
}
2681+
2682+
Direction[] result = new Direction[CARDINAL_UPRIGHT_DIRECTIONS.length];
2683+
int count = 0;
2684+
2685+
for (Direction direction : CARDINAL_UPRIGHT_DIRECTIONS) {
2686+
if (material.isFullFace(ShapeType.VISUAL_SHAPE, direction)) {
2687+
result[count++] = direction;
2688+
}
2689+
}
2690+
2691+
// Short-cut for blocks that are blocked in all directions but for some reason not isFullCube
2692+
// This enables == comparison later
2693+
if (count == 6) {
2694+
return CARDINAL_UPRIGHT_DIRECTIONS;
2695+
}
2696+
2697+
return Arrays.copyOf(result, count);
2698+
}
2699+
26612700
/**
26622701
* Hollows out the region (Semi-well-defined for non-cuboid selections).
26632702
*
@@ -2667,46 +2706,70 @@ public int deformRegion(final Region region, final Transform targetTransform, fi
26672706
*
26682707
* @return number of blocks affected
26692708
* @throws MaxChangedBlocksException thrown if too many blocks are changed
2709+
* @deprecated Use {@link EditSession#hollowOutRegion(Region, int, Pattern, boolean, Collection, boolean)} instead.
26702710
*/
2671-
public int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
2672-
// Positions to consider as 'outside'
2673-
Collection<BlockVector3> startingPositions = new ArrayList<>();
2711+
@InlineMe(replacement = "this.hollowOutRegion(region, thickness, pattern, true, null, false)")
2712+
@Deprecated
2713+
public final int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
2714+
return hollowOutRegion(region, thickness, pattern, true, null, false);
2715+
}
26742716

2675-
// Initialize BFS with selection bounding box
2676-
final BlockVector3 min = region.getMinimumPoint();
2677-
final BlockVector3 max = region.getMaximumPoint();
2717+
/**
2718+
* Hollows out the region (bounding-box mode is semi-well-defined for non-cuboid selections).
2719+
* The startingPositions parameter takes precedence over usePlacementPosition.
2720+
*
2721+
* @param region the region to hollow out.
2722+
* @param thickness the thickness of the shell to leave (manhattan distance)
2723+
* @param pattern The block pattern to use
2724+
* @param openSides Open up faces touching the bounding box. This matches the legacy behaviour.
2725+
* @param startingPositions Positions to consider as 'outside'. If null, use the selection bounding box
2726+
* @param useBlockGeometry Consider block geometry for visibility calculation
2727+
* @return number of blocks affected
2728+
* @throws MaxChangedBlocksException thrown if too many blocks are changed
2729+
*/
2730+
public int hollowOutRegion(Region region, int thickness, Pattern pattern, boolean openSides, Collection<BlockVector3> startingPositions, boolean useBlockGeometry) throws MaxChangedBlocksException {
2731+
if (startingPositions == null) {
2732+
// If no startingPositions are specified, use the selection bounding box
2733+
startingPositions = new ArrayList<>();
2734+
2735+
// Initialize BFS with selection bounding box
2736+
final BlockVector3 min = region.getMinimumPoint();
2737+
final BlockVector3 max = region.getMaximumPoint();
2738+
2739+
final int minX = min.x();
2740+
final int minY = min.y();
2741+
final int minZ = min.z();
2742+
final int maxX = max.x();
2743+
final int maxY = max.y();
2744+
final int maxZ = max.z();
26782745

2679-
final int minX = min.x();
2680-
final int minY = min.y();
2681-
final int minZ = min.z();
2682-
final int maxX = max.x();
2683-
final int maxY = max.y();
2684-
final int maxZ = max.z();
2746+
for (int x = minX; x <= maxX; ++x) {
2747+
for (int y = minY; y <= maxY; ++y) {
2748+
startingPositions.add(BlockVector3.at(x, y, minZ));
2749+
startingPositions.add(BlockVector3.at(x, y, maxZ));
2750+
}
2751+
}
26852752

2686-
for (int x = minX; x <= maxX; ++x) {
26872753
for (int y = minY; y <= maxY; ++y) {
2688-
startingPositions.add(BlockVector3.at(x, y, minZ));
2689-
startingPositions.add(BlockVector3.at(x, y, maxZ));
2754+
for (int z = minZ; z <= maxZ; ++z) {
2755+
startingPositions.add(BlockVector3.at(minX, y, z));
2756+
startingPositions.add(BlockVector3.at(maxX, y, z));
2757+
}
26902758
}
2691-
}
26922759

2693-
for (int y = minY; y <= maxY; ++y) {
26942760
for (int z = minZ; z <= maxZ; ++z) {
2695-
startingPositions.add(BlockVector3.at(minX, y, z));
2696-
startingPositions.add(BlockVector3.at(maxX, y, z));
2761+
for (int x = minX; x <= maxX; ++x) {
2762+
startingPositions.add(BlockVector3.at(x, minY, z));
2763+
startingPositions.add(BlockVector3.at(x, maxY, z));
2764+
}
26972765
}
2698-
}
26992766

2700-
for (int z = minZ; z <= maxZ; ++z) {
2701-
for (int x = minX; x <= maxX; ++x) {
2702-
startingPositions.add(BlockVector3.at(x, minY, z));
2703-
startingPositions.add(BlockVector3.at(x, maxY, z));
2767+
if (openSides) {
2768+
// Remove movement blockers from visible list
2769+
startingPositions.removeIf(blockVector3 -> getBlock(blockVector3).getBlockType().getMaterial().isMovementBlocker());
27042770
}
27052771
}
27062772

2707-
// Remove movement blockers from visible list
2708-
startingPositions.removeIf(blockVector3 -> getBlock(blockVector3).getBlockType().getMaterial().isMovementBlocker());
2709-
27102773
// The set of blocks that were seen
27112774
final Set<BlockVector3> visible = new HashSet<>(startingPositions);
27122775

@@ -2715,13 +2778,36 @@ public int hollowOutRegion(Region region, int thickness, Pattern pattern) throws
27152778
while (!queue.isEmpty()) {
27162779
final BlockVector3 current = queue.poll();
27172780

2718-
final BlockState block = getBlock(current);
2719-
if (block.getBlockType().getMaterial().isMovementBlocker()) {
2720-
continue;
2781+
Direction[] blockedDirections;
2782+
if (useBlockGeometry) {
2783+
// Get blocked directions for this block
2784+
blockedDirections = getBlockedDirections(current);
2785+
2786+
// Short-cut if blocked in all directions
2787+
if (blockedDirections == CARDINAL_UPRIGHT_DIRECTIONS) {
2788+
continue;
2789+
}
2790+
} else {
2791+
final BlockState block = getBlock(current);
2792+
if (block.getBlockType().getMaterial().isMovementBlocker()) {
2793+
// In non-geometry mode, all movement blockers are assumed to be blocked in all directions, so short-cut here
2794+
continue;
2795+
}
2796+
2797+
// All other blocks are assumed to have no blocked directions
2798+
blockedDirections = NO_DIRECTIONS;
27212799
}
27222800

2723-
for (BlockVector3 recurseDirection : CARDINAL_UPRIGHT_OFFSETS) {
2724-
final BlockVector3 neighbor = current.add(recurseDirection);
2801+
outer:
2802+
for (Direction direction : CARDINAL_UPRIGHT_DIRECTIONS) {
2803+
// Check if this direction is blocked
2804+
for (Direction blockedDirection : blockedDirections) {
2805+
if (blockedDirection == direction) {
2806+
continue outer; // skip this direction
2807+
}
2808+
}
2809+
2810+
final BlockVector3 neighbor = current.add(direction.toBlockVector());
27252811

27262812
// Abort if we're outside the region
27272813
if (!region.contains(neighbor)) {
@@ -3180,6 +3266,10 @@ public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int
31803266
return changed;
31813267
}
31823268

3269+
/**
3270+
* Contains no directions.
3271+
*/
3272+
private static final Direction[] NO_DIRECTIONS = {};
31833273
/**
31843274
* Contains all cardinal and upright directions.
31853275
*/
@@ -3193,6 +3283,64 @@ public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int
31933283
.map(Direction::toBlockVector)
31943284
.toArray(BlockVector3[]::new);
31953285

3286+
/**
3287+
* Some blocks need a special case for one reason or another.
3288+
*/
3289+
private static final Map<BlockType, Direction[]> BLOCKED_DIRECTIONS_OVERRIDE = new HashMap<>();
3290+
3291+
static {
3292+
Arrays.asList(
3293+
// fullcubes that you can see through on all 6 sides
3294+
BlockTypes.SPAWNER,
3295+
BlockTypes.BEACON,
3296+
BlockTypes.MANGROVE_ROOTS,
3297+
BlockTypes.ICE,
3298+
3299+
// You can see through some of the doors/trapdoors, which is not reflected in their visual shape
3300+
// Commented lines are opaque and kept for reference
3301+
BlockTypes.OAK_DOOR,
3302+
BlockTypes.OAK_TRAPDOOR,
3303+
// BlockTypes.SPRUCE_DOOR,
3304+
// BlockTypes.SPRUCE_TRAPDOOR,
3305+
// BlockTypes.BIRCH_DOOR,
3306+
// BlockTypes.BIRCH_TRAPDOOR,
3307+
BlockTypes.JUNGLE_DOOR,
3308+
BlockTypes.JUNGLE_TRAPDOOR,
3309+
BlockTypes.ACACIA_DOOR,
3310+
BlockTypes.ACACIA_TRAPDOOR,
3311+
// BlockTypes.DARK_OAK_DOOR,
3312+
// BlockTypes.DARK_OAK_TRAPDOOR,
3313+
// BlockTypes.MANGROVE_DOOR,
3314+
BlockTypes.MANGROVE_TRAPDOOR,
3315+
BlockTypes.CHERRY_DOOR,
3316+
BlockTypes.CHERRY_TRAPDOOR,
3317+
// BlockTypes.PALE_OAK_DOOR,
3318+
// BlockTypes.PALE_OAK_TRAPDOOR,
3319+
BlockTypes.BAMBOO_DOOR,
3320+
BlockTypes.BAMBOO_TRAPDOOR,
3321+
BlockTypes.CRIMSON_DOOR,
3322+
BlockTypes.CRIMSON_TRAPDOOR,
3323+
BlockTypes.WARPED_DOOR,
3324+
BlockTypes.WARPED_TRAPDOOR,
3325+
BlockTypes.IRON_DOOR,
3326+
BlockTypes.IRON_TRAPDOOR
3327+
).forEach(blockType -> {
3328+
BLOCKED_DIRECTIONS_OVERRIDE.put(blockType, NO_DIRECTIONS);
3329+
});
3330+
3331+
// The copper doors/trapdoors are too many to list individually
3332+
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^.*:(?:.*_)?(?:copper_door|copper_trapdoor)(?:_.*)?$");
3333+
for (BlockType blockType : BlockType.REGISTRY) {
3334+
if (pattern.matcher(blockType.id()).matches()) {
3335+
BLOCKED_DIRECTIONS_OVERRIDE.put(blockType, NO_DIRECTIONS);
3336+
}
3337+
}
3338+
3339+
// These are visual fullcubes, but they're mostly open:
3340+
BLOCKED_DIRECTIONS_OVERRIDE.put(BlockTypes.TRIAL_SPAWNER, new Direction[] { Direction.UP });
3341+
BLOCKED_DIRECTIONS_OVERRIDE.put(BlockTypes.VAULT, new Direction[] { Direction.UP, Direction.DOWN });
3342+
}
3343+
31963344
private static double lengthSq(double x, double y, double z) {
31973345
return (x * x) + (y * y) + (z * z);
31983346
}

worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,11 @@
7979
import org.enginehub.piston.annotation.param.Arg;
8080
import org.enginehub.piston.annotation.param.ArgFlag;
8181
import org.enginehub.piston.annotation.param.Switch;
82+
import org.enginehub.piston.exception.CommandException;
8283

8384
import java.util.ArrayList;
85+
import java.util.Collection;
86+
import java.util.Collections;
8487
import java.util.List;
8588

8689
import static com.sk89q.worldedit.command.util.Logging.LogMode.ALL;
@@ -548,15 +551,36 @@ public int deform(Actor actor, LocalSession session, EditSession editSession,
548551
)
549552
@CommandPermissions("worldedit.region.hollow")
550553
@Logging(REGION)
551-
public int hollow(Actor actor, EditSession editSession,
554+
public int hollow(Actor actor, EditSession editSession, LocalSession session,
552555
@Selection Region region,
553556
@Arg(desc = "Thickness of the shell to leave", def = "1")
554557
int thickness,
555558
@Arg(desc = "The pattern of blocks to replace the hollowed area with", def = "air")
556-
Pattern pattern) throws WorldEditException {
559+
Pattern pattern,
560+
@Switch(name = 'o', desc = "Open up faces touching the bounding box. This matches the legacy behaviour.")
561+
boolean openSides,
562+
@Switch(name = 'p', desc = "Consider placement position as 'outside' instead of the selection bounding box. Overrides -o.")
563+
boolean usePlacementPosition,
564+
@Switch(name = 'g', desc = "Consider block geometry for visibility calculation")
565+
boolean useBlockGeometry) throws WorldEditException {
557566
checkCommandArgument(thickness >= 1, "Thickness must be >= 1");
558567

559-
int affected = editSession.hollowOutRegion(region, thickness, pattern);
568+
final Collection<BlockVector3> startingPositions;
569+
if (usePlacementPosition) {
570+
BlockVector3 placementPosition = session.getPlacementPosition(actor);
571+
if (!region.contains(placementPosition)) {
572+
throw new CommandException(
573+
TextComponent.of("Placement position must be inside selection (")
574+
.append(session.getPlacement().getInfo())
575+
.append(TextComponent.of(")")),
576+
ImmutableList.of()
577+
);
578+
}
579+
startingPositions = Collections.singletonList(placementPosition);
580+
} else {
581+
startingPositions = null;
582+
}
583+
int affected = editSession.hollowOutRegion(region, thickness, pattern, openSides, startingPositions, useBlockGeometry);
560584
actor.printInfo(TranslatableComponent.of("worldedit.hollow.changed", TextComponent.of(affected)));
561585
return affected;
562586
}

0 commit comments

Comments
 (0)