Skip to content

Commit 7d72986

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 5d19be3 commit 7d72986

3 files changed

Lines changed: 230 additions & 34 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
}

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

Lines changed: 196 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
package com.sk89q.worldedit;
2121

2222
import com.google.common.collect.ImmutableList;
23+
import com.google.common.collect.Maps;
24+
import com.google.common.collect.Sets;
2325
import com.google.errorprone.annotations.InlineMe;
2426
import com.sk89q.worldedit.entity.BaseEntity;
2527
import com.sk89q.worldedit.entity.Entity;
@@ -124,17 +126,20 @@
124126
import com.sk89q.worldedit.world.World;
125127
import com.sk89q.worldedit.world.biome.BiomeType;
126128
import com.sk89q.worldedit.world.block.BaseBlock;
129+
import com.sk89q.worldedit.world.block.BlockCategory;
127130
import com.sk89q.worldedit.world.block.BlockState;
128131
import com.sk89q.worldedit.world.block.BlockStateHolder;
129132
import com.sk89q.worldedit.world.block.BlockType;
130133
import com.sk89q.worldedit.world.block.BlockTypes;
131134
import com.sk89q.worldedit.world.generation.TreeType;
135+
import com.sk89q.worldedit.world.registry.BlockMaterial;
132136
import com.sk89q.worldedit.world.registry.LegacyMapper;
133137
import org.apache.logging.log4j.Logger;
134138

135139
import java.util.ArrayDeque;
136140
import java.util.ArrayList;
137141
import java.util.Arrays;
142+
import java.util.Collection;
138143
import java.util.Collections;
139144
import java.util.HashMap;
140145
import java.util.HashSet;
@@ -2657,6 +2662,43 @@ public int deformRegion(final Region region, final Transform targetTransform, fi
26572662
return affected;
26582663
}
26592664

2665+
private static Direction[] getBlockedDirections(Extent extent, BlockVector3 position) {
2666+
BlockState blockState = extent.getBlock(position);
2667+
BlockType blockType = blockState.getBlockType();
2668+
BlockMaterial material = blockState.getMaterial();
2669+
2670+
if (material.isAir()) {
2671+
return NO_DIRECTIONS;
2672+
}
2673+
if (material.isLiquid()) {
2674+
return NO_DIRECTIONS;
2675+
}
2676+
Direction[] blockedDirections = BLOCKED_DIRECTIONS_OVERRIDE.get(blockType);
2677+
if (blockedDirections != null) {
2678+
return blockedDirections;
2679+
}
2680+
if (material.isFullCube()) {
2681+
return CARDINAL_UPRIGHT_DIRECTIONS;
2682+
}
2683+
2684+
Direction[] result = new Direction[CARDINAL_UPRIGHT_DIRECTIONS.length];
2685+
int count = 0;
2686+
2687+
for (Direction direction : CARDINAL_UPRIGHT_DIRECTIONS) {
2688+
if (material.isFullFace(direction)) {
2689+
result[count++] = direction;
2690+
}
2691+
}
2692+
2693+
// Short-cut for blocks that are blocked in all directions but for some reason not isFullCube
2694+
// This enables == comparison later
2695+
if (count == 6) {
2696+
return CARDINAL_UPRIGHT_DIRECTIONS;
2697+
}
2698+
2699+
return Arrays.copyOf(result, count);
2700+
}
2701+
26602702
/**
26612703
* Hollows out the region (Semi-well-defined for non-cuboid selections).
26622704
*
@@ -2666,59 +2708,107 @@ public int deformRegion(final Region region, final Transform targetTransform, fi
26662708
*
26672709
* @return number of blocks affected
26682710
* @throws MaxChangedBlocksException thrown if too many blocks are changed
2711+
* @deprecated Use {@link EditSession#hollowOutRegion(Region, int, Pattern, boolean, Collection, boolean)} instead.
26692712
*/
2670-
public int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
2713+
@InlineMe(replacement = "this.hollowOutRegion(region, thickness, pattern, true, null, false)")
2714+
@Deprecated
2715+
public final int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
2716+
return hollowOutRegion(region, thickness, pattern, true, null, false);
2717+
}
2718+
2719+
/**
2720+
* Hollows out the region (bounding-box mode is semi-well-defined for non-cuboid selections).
2721+
* The startingPositions parameter takes precedence over usePlacementPosition.
2722+
*
2723+
* @param region the region to hollow out.
2724+
* @param thickness the thickness of the shell to leave (manhattan distance)
2725+
* @param pattern The block pattern to use
2726+
* @param openSides Open up faces touching the bounding box. This matches the legacy behaviour.
2727+
* @param startingPositions Positions to consider as 'outside'. If null, use the selection bounding box
2728+
* @param useBlockGeometry Consider block geometry for visibility calculation
2729+
* @return number of blocks affected
2730+
* @throws MaxChangedBlocksException thrown if too many blocks are changed
2731+
*/
2732+
public int hollowOutRegion(Region region, int thickness, Pattern pattern, boolean openSides, Collection<BlockVector3> startingPositions, boolean useBlockGeometry) throws MaxChangedBlocksException {
26712733
int affected = 0;
26722734

26732735
final Set<BlockVector3> visible = new HashSet<>();
2736+
if (startingPositions == null) {
2737+
// Initialize BFS with selection bounding box
2738+
final BlockVector3 min = region.getMinimumPoint();
2739+
final BlockVector3 max = region.getMaximumPoint();
2740+
2741+
final int minX = min.x();
2742+
final int minY = min.y();
2743+
final int minZ = min.z();
2744+
final int maxX = max.x();
2745+
final int maxY = max.y();
2746+
final int maxZ = max.z();
26742747

2675-
// Initialize BFS with selection bounding box
2676-
final BlockVector3 min = region.getMinimumPoint();
2677-
final BlockVector3 max = region.getMaximumPoint();
2678-
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();
2748+
for (int x = minX; x <= maxX; ++x) {
2749+
for (int y = minY; y <= maxY; ++y) {
2750+
visible.add(BlockVector3.at(x, y, minZ));
2751+
visible.add(BlockVector3.at(x, y, maxZ));
2752+
}
2753+
}
26852754

2686-
for (int x = minX; x <= maxX; ++x) {
26872755
for (int y = minY; y <= maxY; ++y) {
2688-
visible.add(BlockVector3.at(x, y, minZ));
2689-
visible.add(BlockVector3.at(x, y, maxZ));
2756+
for (int z = minZ; z <= maxZ; ++z) {
2757+
visible.add(BlockVector3.at(minX, y, z));
2758+
visible.add(BlockVector3.at(maxX, y, z));
2759+
}
26902760
}
2691-
}
26922761

2693-
for (int y = minY; y <= maxY; ++y) {
26942762
for (int z = minZ; z <= maxZ; ++z) {
2695-
visible.add(BlockVector3.at(minX, y, z));
2696-
visible.add(BlockVector3.at(maxX, y, z));
2763+
for (int x = minX; x <= maxX; ++x) {
2764+
visible.add(BlockVector3.at(x, minY, z));
2765+
visible.add(BlockVector3.at(x, maxY, z));
2766+
}
26972767
}
2698-
}
26992768

2700-
for (int z = minZ; z <= maxZ; ++z) {
2701-
for (int x = minX; x <= maxX; ++x) {
2702-
visible.add(BlockVector3.at(x, minY, z));
2703-
visible.add(BlockVector3.at(x, maxY, z));
2769+
if (openSides) {
2770+
// Remove movement blockers from visible list
2771+
visible.removeIf(blockVector3 -> getBlock(blockVector3).getBlockType().getMaterial().isMovementBlocker());
27042772
}
2773+
} else {
2774+
visible.addAll(startingPositions);
27052775
}
27062776

2707-
// Remove movement blockers from visible list
2708-
visible.removeIf(blockVector3 -> getBlock(blockVector3).getBlockType().getMaterial().isMovementBlocker());
2709-
27102777
// Do BFS to find more visible blocks
27112778
final Queue<BlockVector3> queue = new ArrayDeque<>(visible);
27122779
while (!queue.isEmpty()) {
27132780
final BlockVector3 current = queue.poll();
27142781

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

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

27232813
if (!region.contains(neighbor)) {
27242814
continue;
@@ -3171,6 +3261,10 @@ public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int
31713261
return changed;
31723262
}
31733263

3264+
/**
3265+
* Contains no directions
3266+
*/
3267+
private static final Direction[] NO_DIRECTIONS = {};
31743268
/**
31753269
* Contains all cardinal and upright directions
31763270
*/
@@ -3184,6 +3278,77 @@ public int morph(BlockVector3 position, double brushSize, int minErodeFaces, int
31843278
.map(Direction::toBlockVector)
31853279
.toArray(BlockVector3[]::new);
31863280

3281+
/**
3282+
* Some blocks need a special case for one reason or another
3283+
*/
3284+
private static final Map<BlockType, Direction[]> BLOCKED_DIRECTIONS_OVERRIDE = Maps.asMap(
3285+
Sets.newHashSet(
3286+
// fullcubes that you can see through on all 6 sides
3287+
BlockTypes.SPAWNER,
3288+
BlockTypes.CHEST,
3289+
BlockTypes.BEACON,
3290+
BlockTypes.KELP,
3291+
BlockTypes.KELP_PLANT,
3292+
BlockTypes.MANGROVE_ROOTS,
3293+
BlockTypes.ICE,
3294+
3295+
BlockTypes.OAK_DOOR,
3296+
BlockTypes.OAK_TRAPDOOR,
3297+
// BlockTypes.SPRUCE_DOOR,
3298+
// BlockTypes.SPRUCE_TRAPDOOR,
3299+
// BlockTypes.BIRCH_DOOR,
3300+
// BlockTypes.BIRCH_TRAPDOOR,
3301+
BlockTypes.JUNGLE_DOOR,
3302+
BlockTypes.JUNGLE_TRAPDOOR,
3303+
BlockTypes.ACACIA_DOOR,
3304+
BlockTypes.ACACIA_TRAPDOOR,
3305+
// BlockTypes.DARK_OAK_DOOR,
3306+
// BlockTypes.DARK_OAK_TRAPDOOR,
3307+
// BlockTypes.MANGROVE_DOOR,
3308+
BlockTypes.MANGROVE_TRAPDOOR,
3309+
BlockTypes.CHERRY_DOOR,
3310+
BlockTypes.CHERRY_TRAPDOOR,
3311+
// BlockTypes.PALE_OAK_DOOR,
3312+
// BlockTypes.PALE_OAK_TRAPDOOR,
3313+
BlockTypes.BAMBOO_DOOR,
3314+
BlockTypes.BAMBOO_TRAPDOOR,
3315+
BlockTypes.CRIMSON_DOOR,
3316+
BlockTypes.CRIMSON_TRAPDOOR,
3317+
BlockTypes.WARPED_DOOR,
3318+
BlockTypes.WARPED_TRAPDOOR,
3319+
BlockTypes.IRON_DOOR,
3320+
BlockTypes.IRON_TRAPDOOR
3321+
),
3322+
_ -> NO_DIRECTIONS
3323+
);
3324+
3325+
static {
3326+
BLOCKED_DIRECTIONS_OVERRIDE.put(BlockTypes.TRIAL_SPAWNER, new Direction[] { Direction.UP });
3327+
BLOCKED_DIRECTIONS_OVERRIDE.put(BlockTypes.VAULT, new Direction[] { Direction.UP, Direction.DOWN });
3328+
addSeeThroughBlockCategory("minecraft:fall_damage_resetting");
3329+
addSeeThroughBlockCategory("minecraft:replaceable_by_trees");
3330+
3331+
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^.*:(?:.*_)?(?:glass|grate|copper_door|copper_trapdoor)(?:_.*)?$");
3332+
for (BlockType blockType : BlockType.REGISTRY) {
3333+
if (pattern.matcher(blockType.id()).matches()) {
3334+
BLOCKED_DIRECTIONS_OVERRIDE.put(blockType, NO_DIRECTIONS);
3335+
}
3336+
}
3337+
}
3338+
3339+
private static void addSeeThroughBlockCategory(String blockCategoryKey) {
3340+
BlockCategory blockCategory = BlockCategory.REGISTRY.get(blockCategoryKey);
3341+
if (blockCategory == null) {
3342+
WorldEdit.logger.warn("BlockCategory {} not found during initialization", blockCategoryKey);
3343+
return;
3344+
}
3345+
3346+
for (BlockType blockType : blockCategory.getAll()) {
3347+
BLOCKED_DIRECTIONS_OVERRIDE.put(blockType, NO_DIRECTIONS);
3348+
}
3349+
3350+
}
3351+
31873352
private static double lengthSq(double x, double y, double z) {
31883353
return (x * x) + (y * y) + (z * z);
31893354
}

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)