Skip to content

Commit 0b31d50

Browse files
committed
Fix the check for the blueprints placed mid air
1 parent 4e7b0d8 commit 0b31d50

File tree

1 file changed

+101
-48
lines changed

1 file changed

+101
-48
lines changed

src/main/java/org/minefortress/blueprints/manager/BaseClientStructureManager.java

Lines changed: 101 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,18 @@ public void tick() {
5454

5555
// For upgrading logic
5656
if (this instanceof ClientBlueprintManager clientBlueprintManager && clientBlueprintManager.isUpgrading()) {
57-
checkIntersectUpgradingBuilding(); // This might set cantBuild = true (via intersectsUpgradingBuilding)
58-
if (!intersectsUpgradingBuilding) {
57+
checkIntersectUpgradingBuilding(); // This sets the 'intersectsUpgradingBuilding' field
58+
if (!intersectsUpgradingBuilding) { // If checkIntersectUpgradingBuilding resulted in false for an upgrade
5959
cantBuild = true;
6060
}
61-
if (cantBuild) return; // Early exit if upgrade intersection fails
61+
if (cantBuild) return; // Early exit if upgrade intersection fails or other reasons
6262
} else {
63-
intersectsUpgradingBuilding = true; // Default to true if not upgrading
63+
// If not upgrading, intersectsUpgradingBuilding is generally true (no failure due to intersection).
64+
// This prevents "doesn't intersect" warnings/logic when not in upgrade mode.
65+
intersectsUpgradingBuilding = true;
6466
}
6567

66-
67-
checkPlacementValidity(); // Combined check for leaves, surface collision, and sub-floor validity
68+
checkPlacementValidity(); // Combined check for leaves, surface collision, sub-floor validity, and air under floor
6869
}
6970
protected BlockPos getStructureBuildPos() {
7071
return structureBuildPos;
@@ -91,96 +92,147 @@ private void checkNotEnoughResources() {
9192
}
9293

9394
private void checkPlacementValidity() {
94-
if (cantBuild) return; // If already marked as can't build (e.g. due to resources), skip further checks
95+
if (cantBuild) return; // If already marked as can't build (e.g., due to resources), skip further checks
96+
if (this.client.world == null || structureBuildPos == null) { // Essential objects check
97+
cantBuild = true;
98+
return;
99+
}
95100

96101
final IStructureBlockData blockData = getBlockData();
97-
final Set<BlockPos> blueprintDataPositions = blockData.getLayer(BlueprintDataLayer.GENERAL)
102+
final BlueprintMetadata selectedStructure = getSelectedStructure();
103+
if (selectedStructure == null) { // Should be caught by isSelecting or resource check, but for safety
104+
cantBuild = true;
105+
return;
106+
}
107+
108+
// Get all non-air block positions from the blueprint in its local coordinate system
109+
final Set<BlockPos> blueprintNonAirLocalPositions = blockData.getLayer(BlueprintDataLayer.GENERAL)
98110
.entrySet()
99111
.stream()
100-
.filter(entry -> !entry.getValue().isAir()) // Only consider non-air blocks from the blueprint
112+
.filter(entry -> !entry.getValue().isAir())
101113
.map(Map.Entry::getKey)
102114
.collect(Collectors.toSet());
103115

104-
final int floorLevel = getSelectedStructure().getFloorLevel();
105-
final Vec3i size = blockData.getSize(); // For leaves check
116+
final int floorLevel = selectedStructure.getFloorLevel();
117+
final Vec3i blueprintSize = blockData.getSize();
106118

107119
// 1. Check for leaves directly under the blueprint's entire footprint
108-
final int groundYInWorld = structureBuildPos.getY() - floorLevel - 1;
109-
for (int dx = 0; dx < size.getX(); dx++) {
110-
for (int dz = 0; dz < size.getZ(); dz++) {
111-
BlockPos currentGroundPos = new BlockPos(
120+
// The footprint ground level is 1 Y-level below the blueprint's origin (Y=0).
121+
// World Y for blueprint's Y=0 is structureBuildPos.getY() - floorLevel.
122+
// So, ground check Y is structureBuildPos.getY() - floorLevel - 1.
123+
final int groundYInWorldForLeavesCheck = structureBuildPos.getY() - floorLevel - 1;
124+
// Iterate over the footprint of the blueprint in world coordinates
125+
for (int dx = 0; dx < blueprintSize.getX(); dx++) {
126+
for (int dz = 0; dz < blueprintSize.getZ(); dz++) {
127+
// structureBuildPos.getX/Z() is minX/Z of structure in world.
128+
BlockPos currentPosUnderFootprint = new BlockPos(
112129
structureBuildPos.getX() + dx,
113-
groundYInWorld,
130+
groundYInWorldForLeavesCheck,
114131
structureBuildPos.getZ() + dz
115132
);
116-
if (this.client.world != null) {
117-
BlockState stateBeneath = this.client.world.getBlockState(currentGroundPos);
118-
if (stateBeneath.isIn(BlockTags.LEAVES)) {
119-
cantBuild = true;
120-
return;
121-
}
133+
// client.world.getBlockState handles out-of-bounds Y by returning air/void_air, which are not leaves.
134+
BlockState stateBeneath = this.client.world.getBlockState(currentPosUnderFootprint);
135+
if (stateBeneath.isIn(BlockTags.LEAVES)) {
136+
cantBuild = true;
137+
return;
122138
}
123139
}
124140
}
125141

126-
// 2. Check blocks AT OR ABOVE the blueprint's floor level
142+
// 2. NEW: Check for air directly under the blueprint's "floor" blocks.
143+
// "Floor" blocks are non-air blueprint blocks at localY == floorLevel.
144+
// Their world position is structureBuildPos.add(localX, 0, localZ)
145+
// because structureBuildPos.getY() is already the world Y-level of the floor.
146+
final boolean airUnderneathFloorBlocks = blueprintNonAirLocalPositions.stream()
147+
.filter(bpLocalPos -> bpLocalPos.getY() == floorLevel) // Filter for blueprint's floor surface blocks
148+
.map(bpFloorLocalPos -> {
149+
// Calculate world position of this blueprint floor block
150+
BlockPos worldFloorBlockPos = structureBuildPos.add(
151+
bpFloorLocalPos.getX(),
152+
0, // Y-offset is 0 because structureBuildPos.getY() is the world Y for floorLevel
153+
bpFloorLocalPos.getZ()
154+
);
155+
return worldFloorBlockPos.down(); // Get the position directly under it in the world
156+
})
157+
.anyMatch(posUnderFloor -> {
158+
// client.world.getBlockState handles Y bounds (returns AIR or VOID_AIR, both .isAir())
159+
return BuildingHelper.canPlaceBlock(this.client.world, posUnderFloor);
160+
});
161+
162+
if (airUnderneathFloorBlocks) {
163+
cantBuild = true;
164+
return;
165+
}
166+
167+
// 3. Check blocks AT OR ABOVE the blueprint's floor level for collisions
127168
final boolean isUpgrading = (this instanceof ClientBlueprintManager cbm) && cbm.isUpgrading();
128169
final BlockBox upgradingBox = isUpgrading ? ((ClientBlueprintManager) this).getUpgradingBuildingBox() : null;
129170

130-
final boolean partAtOrAboveFloorCollides = blueprintDataPositions.stream()
131-
.filter(bpLocalPos -> bpLocalPos.getY() >= floorLevel)
132-
.map(bpLocalPos -> structureBuildPos.add(bpLocalPos.getX(), bpLocalPos.getY() - floorLevel, bpLocalPos.getZ()))
171+
final boolean partAtOrAboveFloorCollides = blueprintNonAirLocalPositions.stream()
172+
.filter(bpLocalPos -> bpLocalPos.getY() >= floorLevel) // Consider blueprint blocks at or above its floor
173+
.map(bpLocalPos -> structureBuildPos.add(bpLocalPos.getX(), bpLocalPos.getY() - floorLevel, bpLocalPos.getZ())) // Map to world pos
133174
.anyMatch(worldPos -> {
134-
boolean currentCanBuild = BuildingHelper.canPlaceBlock(client.world, worldPos);
135-
if (isUpgrading && upgradingBox != null) {
136-
currentCanBuild = currentCanBuild || upgradingBox.contains(worldPos);
175+
boolean currentCanPlace = BuildingHelper.canPlaceBlock(client.world, worldPos);
176+
if (isUpgrading && upgradingBox != null && upgradingBox.contains(worldPos)) {
177+
// If upgrading, and this block is part of the existing structure, it's "placeable" in context.
178+
currentCanPlace = true;
137179
}
138-
return !currentCanBuild; // if !canPlace (and not part of upgrade), then it collides
180+
return !currentCanPlace; // Collision if cannot place (and not an allowed overlap during upgrade)
139181
});
140182

141183
if (partAtOrAboveFloorCollides) {
142184
cantBuild = true;
143185
return;
144186
}
145187

146-
// 3. Check blocks BELOW the blueprint's floor level
147-
final boolean partBelowFloorInvalid = blueprintDataPositions.stream()
148-
.filter(bpLocalPos -> bpLocalPos.getY() < floorLevel)
149-
.map(bpLocalPos -> structureBuildPos.add(bpLocalPos.getX(), bpLocalPos.getY() - floorLevel, bpLocalPos.getZ()))
150-
.anyMatch(worldPos -> BuildingHelper.canPlaceBlock(client.world, worldPos));
188+
// 4. Check blocks BELOW the blueprint's floor level (foundation/sub-structure parts)
189+
// Original logic: These parts must NOT be placeable into air/replaceable blocks,
190+
// i.e., they must be placed into solid, non-replaceable ground.
191+
final boolean partBelowFloorInvalid = blueprintNonAirLocalPositions.stream()
192+
.filter(bpLocalPos -> bpLocalPos.getY() < floorLevel) // Consider blueprint blocks below its floor
193+
.map(bpLocalPos -> structureBuildPos.add(bpLocalPos.getX(), bpLocalPos.getY() - floorLevel, bpLocalPos.getZ())) // Map to world pos
194+
.anyMatch(worldPos -> BuildingHelper.canPlaceBlock(client.world, worldPos)); // Invalid if it *can* be placed (i.e., targets air/replaceable)
151195

152196
if (partBelowFloorInvalid) {
153197
cantBuild = true;
154-
// No return here, as we've already checked other conditions that might set cantBuild
198+
// Original code does not return here, allowing cantBuild to be set and fall through.
199+
// This is fine as canBuild() will reflect the final state of cantBuild.
155200
}
156201
}
157202

158203

159-
private void checkIntersectUpgradingBuilding() { // This method is only relevant if isUpgrading is true
204+
private void checkIntersectUpgradingBuilding() {
160205
if (!(this instanceof ClientBlueprintManager cbm) || !cbm.isUpgrading()) {
161-
intersectsUpgradingBuilding = true; // Not upgrading, so no intersection issue by default
206+
intersectsUpgradingBuilding = true;
162207
return;
163208
}
164209

165-
if (!enoughResources) { // If not enough resources, intersection check is moot for buildability
166-
intersectsUpgradingBuilding = false;
210+
if (!enoughResources) {
211+
intersectsUpgradingBuilding = false; // As per original logic, if not enough resources, it's marked as not intersecting for UI/logic consistency.
167212
return;
168213
}
169214

170215
final IStructureBlockData blockData = getBlockData();
216+
final BlueprintMetadata selectedStructure = getSelectedStructure();
217+
if (selectedStructure == null) { // Safety check
218+
intersectsUpgradingBuilding = false;
219+
return;
220+
}
171221
final Set<BlockPos> blueprintDataPositions = blockData.getLayer(BlueprintDataLayer.GENERAL)
172222
.entrySet()
173223
.stream()
174224
.filter(entry -> !entry.getValue().isAir())
175225
.map(Map.Entry::getKey)
176226
.collect(Collectors.toSet());
177-
final int floorLevel = getSelectedStructure().getFloorLevel();
227+
final int floorLevel = selectedStructure.getFloorLevel();
178228
final BlockBox upgradingBuildingBox = cbm.getUpgradingBuildingBox();
229+
if (upgradingBuildingBox == null) { // Should ideally not happen if cbm.isUpgrading() is true and a valid building is targeted
230+
intersectsUpgradingBuilding = false;
231+
return;
232+
}
179233

180-
// An upgrade is valid if at least one block of the new blueprint (at or above its floor)
181-
// intersects with the existing building's box.
182234
intersectsUpgradingBuilding = blueprintDataPositions.stream()
183-
.filter(bpLocalPos -> bpLocalPos.getY() >= floorLevel) // Consider blocks at or above the blueprint's designated floor for intersection
235+
.filter(bpLocalPos -> bpLocalPos.getY() >= floorLevel)
184236
.map(bpLocalPos -> structureBuildPos.add(bpLocalPos.getX(), bpLocalPos.getY() - floorLevel, bpLocalPos.getZ()))
185237
.anyMatch(upgradingBuildingBox::contains);
186238
}
@@ -198,18 +250,17 @@ private IStructureBlockData getBlockData() {
198250
private BlockPos getSelectedPos() {
199251
if (client.crosshairTarget instanceof final BlockHitResult crosshairTarget && this instanceof ClientBlueprintManager cbm) {
200252
final BlockPos originalPos = crosshairTarget.getBlockPos();
201-
if (client.world == null || originalPos == null) return null; // Ensure world is not null
253+
if (client.world == null || originalPos == null) return null;
202254

203255
final BlockState blockState = client.world.getBlockState(originalPos);
204-
if (blockState.isAir() && !cbm.isUpgrading()) return null; // Can't start placement on air unless upgrading
256+
if (blockState.isAir() && !cbm.isUpgrading()) return null;
205257

206258
var movedPos = originalPos;
207-
final BlockBox upgradingBuildingBox = cbm.getUpgradingBuildingBox(); // Can be null if not upgrading
259+
final BlockBox upgradingBuildingBox = cbm.isUpgrading() ? cbm.getUpgradingBuildingBox() : null;
208260
final boolean intersectingWithTheBuilding = cbm.isUpgrading() && upgradingBuildingBox != null &&
209261
(upgradingBuildingBox.contains(originalPos) || blockState.isOf(FortressBlocks.FORTRESS_BUILDING));
210262

211263
if (intersectingWithTheBuilding) {
212-
// Align Y with the bottom of the existing building box if upgrading
213264
movedPos = new BlockPos(movedPos.getX(), upgradingBuildingBox.getMinY(), movedPos.getZ());
214265
}
215266

@@ -240,6 +291,8 @@ public final Optional<BlockPos> getStructureRenderPos() {
240291
final var selected = getSelectedStructure();
241292
if (selected == null) return Optional.empty();
242293
final var floorLevel = selected.getFloorLevel();
294+
// structureBuildPos.getY() is world Y for blueprint's floorLevel.
295+
// structureBuildPos.down(floorLevel) results in world Y for blueprint's Y=0.
243296
return Optional.ofNullable(structureBuildPos).map(it -> it.down(floorLevel));
244297
}
245298

0 commit comments

Comments
 (0)