• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyclopsMC / IntegratedDynamics / 30758491470

02 Aug 2026 05:15PM UTC coverage: 45.534% (-0.1%) from 45.649%
30758491470

push

github

rubensworks
Add base compatibility with Create Aeronautics

This allows ray trace handlers for cables to be registered, which will
be done in the compat mod for Create Aeronautics.
Furthermore, this commits ensures that no items are dropped when the
isMoving flag is false within Block#onRemove.

Closes #1674

2803 of 8926 branches covered (31.4%)

Branch coverage included in aggregate %.

12311 of 24267 relevant lines covered (50.73%)

2.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.34
/src/main/java/org/cyclops/integrateddynamics/core/block/VoxelShapeComponents.java
1
package org.cyclops.integrateddynamics.core.block;
2

3
import com.google.common.collect.Lists;
4
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
5
import it.unimi.dsi.fastutil.doubles.DoubleList;
6
import net.minecraft.client.resources.model.BakedModel;
7
import net.minecraft.core.AxisCycle;
8
import net.minecraft.core.BlockPos;
9
import net.minecraft.core.Direction;
10
import net.minecraft.world.InteractionHand;
11
import net.minecraft.world.InteractionResult;
12
import net.minecraft.world.entity.Entity;
13
import net.minecraft.world.entity.LivingEntity;
14
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
15
import net.minecraft.world.entity.ai.attributes.Attributes;
16
import net.minecraft.world.entity.player.Player;
17
import net.minecraft.world.item.ItemStack;
18
import net.minecraft.world.level.BlockGetter;
19
import net.minecraft.world.level.Level;
20
import net.minecraft.world.level.block.state.BlockState;
21
import net.minecraft.world.phys.AABB;
22
import net.minecraft.world.phys.BlockHitResult;
23
import net.minecraft.world.phys.Vec3;
24
import net.minecraft.world.phys.shapes.CollisionContext;
25
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
26
import net.minecraft.world.phys.shapes.Shapes;
27
import net.minecraft.world.phys.shapes.VoxelShape;
28
import net.neoforged.api.distmarker.Dist;
29
import net.neoforged.api.distmarker.OnlyIn;
30
import org.apache.commons.lang3.tuple.Pair;
31
import org.cyclops.integrateddynamics.api.block.cable.ICableRayTraceHandler;
32
import org.cyclops.integrateddynamics.block.shapes.CollisionContextBlockSupport;
33
import org.cyclops.integrateddynamics.core.block.cable.CableRayTraceHandlers;
34

35
import javax.annotation.Nullable;
36
import java.util.Collection;
37
import java.util.Iterator;
38
import java.util.List;
39
import java.util.stream.Collectors;
40

41
/**
42
 * A {@link VoxelShape} that contains one or more {@link VoxelShapeComponents.IComponent}.
43
 *
44
 * These components are used to handle ray tracing on seperate components.
45
 * Consequently, ray trace results on this object may be safely cast to {@link BlockRayTraceResultComponent}
46
 * so that the targeted component may be retrieved.
47
 *
48
 * @author rubensworks
49
 */
50
public class VoxelShapeComponents extends VoxelShape implements Iterable<VoxelShape> {
51

52
    private final Collection<Pair<VoxelShape, IComponent>> entries;
53
    private final String stateId;
54

55
    protected VoxelShapeComponents(Collection<Pair<VoxelShape, IComponent>> entries, String stateId) {
56
        super(createInnerPart(entries));
4✔
57
        this.entries = entries;
3✔
58
        this.stateId = stateId;
3✔
59
    }
1✔
60

61
    protected static DiscreteVoxelShape createInnerPart(Collection<Pair<VoxelShape, IComponent>> entries) {
62
        return new Part(entries.stream()
6✔
63
                .map(pair -> pair.getLeft().shape)
6✔
64
                .collect(Collectors.toList()));
4✔
65
    }
66

67
    public static VoxelShapeComponents create(BlockState blockState, BlockGetter world, BlockPos blockPos,
68
                                              CollisionContext selectionContext, List<IComponent> components) {
69
        List<Pair<VoxelShape, IComponent>> entries = Lists.newArrayList();
2✔
70
        for (VoxelShapeComponents.IComponent component : components) {
10✔
71
            VoxelShape shape = component.getShape(blockState, world, blockPos, selectionContext);
7✔
72
            entries.add(Pair.of(shape, component));
6✔
73
        }
1✔
74

75
        StringBuilder stateIdBuilder = new StringBuilder();
4✔
76
        for (IComponent component : components) {
10✔
77
            stateIdBuilder.append(component.getStateId(blockState, world, blockPos));
8✔
78
            stateIdBuilder.append(";");
4✔
79
        }
1✔
80
        stateIdBuilder.append(selectionContext instanceof CollisionContextBlockSupport);
5✔
81

82
        return new VoxelShapeComponents(entries, stateIdBuilder.toString());
7✔
83
    }
84

85
    public String getStateId() {
86
        return this.stateId;
3✔
87
    }
88

89
    @Override
90
    public Iterator<VoxelShape> iterator() {
91
        return entries.stream().map(Pair::getLeft).iterator();
7✔
92
    }
93

94
    @Override
95
    public double min(Direction.Axis axis) {
96
        boolean first = true;
×
97
        double startMin = 0;
×
98
        for (VoxelShape shape : this) {
×
99
            double start = shape.min(axis);
×
100
            if (first || start < startMin) {
×
101
                startMin = start;
×
102
                first = false;
×
103
            }
104
        }
×
105
        return startMin;
×
106
    }
107

108
    @Override
109
    public double max(Direction.Axis axis) {
110
        boolean first = true;
×
111
        double endMax = 0;
×
112
        for (VoxelShape shape : this) {
×
113
            double end = shape.max(axis);
×
114
            if (first || end > endMax) {
×
115
                endMax = end;
×
116
                first = false;
×
117
            }
118
        }
×
119
        return endMax;
×
120
    }
121

122
    @Override
123
    public DoubleList getCoords(Direction.Axis axis) {
124
        DoubleArrayList values = new DoubleArrayList();
×
125
        for (VoxelShape shape : this) {
×
126
            values.addAll(shape.getCoords(axis));
×
127
        }
×
128
        return values;
×
129
    }
130

131
    @Override
132
    public boolean isEmpty() {
133
        for (VoxelShape shape : this) {
×
134
            if (!shape.isEmpty()) {
×
135
                return false;
×
136
            }
137
        }
×
138
        return true;
×
139
    }
140

141
    @Override
142
    public VoxelShape move(double x, double y, double z) {
143
        List<Pair<VoxelShape, IComponent>> entries = Lists.newArrayList();
×
144
        for (Pair<VoxelShape, IComponent> entry : this.entries) {
×
145
            entries.add(Pair.of(entry.getLeft().move(x, y, z), entry.getRight()));
×
146
        }
×
147
        return new VoxelShapeComponents(entries, this.stateId);
×
148
    }
149

150
    @Override
151
    public void forAllEdges(Shapes.DoubleLineConsumer consumer) {
152
        for (VoxelShape shape : this) {
×
153
            shape.forAllEdges(consumer);
×
154
        }
×
155
    }
×
156

157
    @Override
158
    public void forAllBoxes(Shapes.DoubleLineConsumer consumer) {
159
        for (VoxelShape shape : this) {
×
160
            shape.forAllBoxes(consumer);
×
161
        }
×
162
    }
×
163

164
    @Override
165
    public double max(Direction.Axis axis, double a, double b) {
166
        boolean first = true;
×
167
        double valueMax = 0D;
×
168
        for (VoxelShape shape : this) {
×
169
            double value = shape.max(axis, a, b);
×
170
            if (first || value > valueMax) {
×
171
                valueMax = value;
×
172
                first = false;
×
173
            }
174
        }
×
175
        return valueMax;
×
176
    }
177

178
    @Nullable
179
    @Override
180
    public BlockRayTraceResultComponent clip(Vec3 startVec, Vec3 endVec, BlockPos pos) {
181
        // Find component with shape that is closest to the startVec
182
        double distanceMin = Double.POSITIVE_INFINITY;
2✔
183
        VoxelShapeComponents.IComponent componentMin = null;
2✔
184
        BlockHitResult resultMin = null;
2✔
185

186
        for (Pair<VoxelShape, IComponent> entry : entries) {
11✔
187
            VoxelShape shape = entry.getLeft();
4✔
188
            BlockHitResult result = shape.clip(startVec, endVec, pos);
6✔
189
            if (result != null) {
2✔
190
                double distance = result.getLocation().distanceToSqr(startVec);
5✔
191
                if (distance < distanceMin) {
4✔
192
                    // If the previous match was a part and the current one is a facade,
193
                    // check if the direction of that part matches with the matched face of the facade,
194
                    // and if so, don't match the facade,
195
                    // because we want the user to be able to select parts within facades.
196
                    if (resultMin == null || !(entry.getRight().isRaytraceLastForFace() && componentMin.getRaytraceDirection() == result.getDirection())) {
7!
197
                        distanceMin = distance;
2✔
198
                        componentMin = entry.getRight();
4✔
199
                        resultMin = result;
2✔
200
                    }
201
                }
202
            }
203
        }
1✔
204

205
        // Store the component in the ray trace result when we found one.
206
        if (resultMin != null) {
2!
207
            return new BlockRayTraceResultComponent(resultMin, componentMin);
6✔
208
        }
209

210
        return null;
×
211
    }
212

213
    // A modified version of #clip, but with a Vec3 param
214
    @Nullable
215
    public BlockRayTraceResultComponent clipVec(Vec3 startVec, Vec3 endVec, BlockPos pos, Vec3 posVec) {
216
        // Find component with shape that is closest to the startVec
217
        double distanceMin = Double.POSITIVE_INFINITY;
×
218
        VoxelShapeComponents.IComponent componentMin = null;
×
219
        BlockHitResult resultMin = null;
×
220

221
        for (Pair<VoxelShape, IComponent> entry : entries) {
×
222
            VoxelShape shape = entry.getLeft();
×
223
            BlockHitResult result = shapeClipVec(shape, startVec, endVec, pos, posVec);
×
224
            if (result != null) {
×
225
                double distance = result.getLocation().distanceToSqr(startVec);
×
226
                if (distance < distanceMin) {
×
227
                    // If the previous match was a part and the current one is a facade,
228
                    // check if the direction of that part matches with the matched face of the facade,
229
                    // and if so, don't match the facade,
230
                    // because we want the user to be able to select parts within facades.
231
                    if (resultMin == null || !(entry.getRight().isRaytraceLastForFace() && componentMin.getRaytraceDirection() == result.getDirection())) {
×
232
                        distanceMin = distance;
×
233
                        componentMin = entry.getRight();
×
234
                        resultMin = result;
×
235
                    }
236
                }
237
            }
238
        }
×
239

240
        // Store the component in the ray trace result when we found one.
241
        if (resultMin != null) {
×
242
            return new BlockRayTraceResultComponent(resultMin, componentMin);
×
243
        }
244

245
        return null;
×
246
    }
247

248
    @Nullable // A modified version of VoxelShape#clip, but with a Vec3 param
249
    public BlockHitResult shapeClipVec(VoxelShape shape, Vec3 startVec, Vec3 endVec, BlockPos pos, Vec3 posVec) {
250
        if (shape.isEmpty()) {
×
251
            return null;
×
252
        } else {
253
            Vec3 vec3 = endVec.subtract(startVec);
×
254
            if (vec3.lengthSqr() < 1.0E-7) {
×
255
                return null;
×
256
            } else {
257
                Vec3 vec31 = startVec.add(vec3.scale(0.001));
×
258
                return shape.shape
×
259
                        .isFullWide(
×
260
                                shape.findIndex(Direction.Axis.X, vec31.x - posVec.x()),
×
261
                                shape.findIndex(Direction.Axis.Y, vec31.y - posVec.y()),
×
262
                                shape.findIndex(Direction.Axis.Z, vec31.z - posVec.z())
×
263
                        )
264
                        ? new BlockHitResult(vec31, Direction.getNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true)
×
265
                        : aabbClip(shape.toAabbs(), startVec, endVec, pos, posVec);
×
266
            }
267
        }
268
    }
269

270
    @Nullable // A modified version of AABB#clip, but with a Vec3 param
271
    public static BlockHitResult aabbClip(Iterable<AABB> boxes, Vec3 start, Vec3 end, BlockPos pos, Vec3 posVec) {
272
        double[] adouble = new double[]{1.0};
×
273
        Direction direction = null;
×
274
        double d0 = end.x - start.x;
×
275
        double d1 = end.y - start.y;
×
276
        double d2 = end.z - start.z;
×
277

278
        for (AABB aabb : boxes) {
×
279
            direction = AABB.getDirection(aabb.move(posVec), start, adouble, direction, d0, d1, d2);
×
280
        }
×
281

282
        if (direction == null) {
×
283
            return null;
×
284
        } else {
285
            double d3 = adouble[0];
×
286
            return new BlockHitResult(start.add(d3 * d0, d3 * d1, d3 * d2), direction, pos, false);
×
287
        }
288
    }
289

290
    /**
291
     * Do a ray trace for the current look direction of the player.
292
     * @param pos The block position to perform a ray trace for.
293
     * @param entity The entity.
294
     * @return A holder object with information on the ray tracing.
295
     */
296
    @Nullable
297
    public BlockRayTraceResultComponent rayTrace(BlockPos pos, @Nullable Entity entity) {
298
        for (ICableRayTraceHandler handler : CableRayTraceHandlers.REGISTRY.getHandlers()) {
7!
299
            if (handler.canHandle(pos, entity)) {
×
300
                return handler.rayTrace(pos, entity, this::rayTraceInnerVec);
×
301
            }
302
        }
×
303
        return rayTraceInner(pos, entity);
5✔
304
    }
305

306
    /**
307
     * Do a ray trace for the current look direction of the player.
308
     * @param pos The block position to perform a ray trace for.
309
     * @param entity The entity.
310
     * @return A holder object with information on the ray tracing.
311
     */
312
    @Nullable
313
    protected BlockRayTraceResultComponent rayTraceInner(BlockPos pos, @Nullable Entity entity) {
314
        if(entity == null) {
2✔
315
            return null;
2✔
316
        }
317
        AttributeInstance reachDistanceAttribute = entity instanceof LivingEntity ? ((LivingEntity) entity).getAttribute(Attributes.BLOCK_INTERACTION_RANGE) : null;
9!
318
        double reachDistance = reachDistanceAttribute == null ? 5 : reachDistanceAttribute.getValue();
5!
319

320
        double eyeHeight = entity.getCommandSenderWorld().isClientSide() ? entity.getEyeHeight() : entity.getEyeHeight(); // Client removed :  - player.getDefaultEyeHeight()
8!
321
        Vec3 lookVec = entity.getLookAngle();
3✔
322
        Vec3 origin = new Vec3(entity.getX(), entity.getY() + eyeHeight, entity.getZ());
12✔
323
        Vec3 direction = origin.add(lookVec.x * reachDistance, lookVec.y * reachDistance, lookVec.z * reachDistance);
15✔
324

325
        return clip(origin, direction, pos);
6✔
326
    }
327

328
    /**
329
     * Do a ray trace for the current look direction of the player.
330
     * @param pos The block position to perform a ray trace for.
331
     * @param entity The entity.
332
     * @return A holder object with information on the ray tracing.
333
     */
334
    @Nullable
335
    protected BlockRayTraceResultComponent rayTraceInnerVec(BlockPos pos, @Nullable Entity entity, Vec3 posVec) {
336
        if(entity == null) {
×
337
            return null;
×
338
        }
339
        AttributeInstance reachDistanceAttribute = entity instanceof LivingEntity ? ((LivingEntity) entity).getAttribute(Attributes.BLOCK_INTERACTION_RANGE) : null;
×
340
        double reachDistance = reachDistanceAttribute == null ? 5 : reachDistanceAttribute.getValue();
×
341

342
        double eyeHeight = entity.getCommandSenderWorld().isClientSide() ? entity.getEyeHeight() : entity.getEyeHeight(); // Client removed :  - player.getDefaultEyeHeight()
×
343
        Vec3 lookVec = entity.getLookAngle();
×
344
        Vec3 origin = new Vec3(entity.getX(), entity.getY() + eyeHeight, entity.getZ());
×
345
        Vec3 direction = origin.add(lookVec.x * reachDistance, lookVec.y * reachDistance, lookVec.z * reachDistance);
×
346

347
        return clipVec(origin, direction, pos, posVec);
×
348
    }
349

350
    @Override
351
    public double collideX(AxisCycle rotation, AABB axisAlignedBB, double range) {
352
        boolean first = true;
×
353
        double valueBest = 0D;
×
354
        for (VoxelShape shape : this) {
×
355
            double value = shape.collideX(rotation, axisAlignedBB, range);
×
356
            if (range > 0) {
×
357
                if (first || value < valueBest) {
×
358
                    valueBest = value;
×
359
                    first = false;
×
360
                }
361
            } else {
362
                if (first || value > valueBest) {
×
363
                    valueBest = value;
×
364
                    first = false;
×
365
                }
366
            }
367
        }
×
368
        return valueBest;
×
369
    }
370

371
    public static class Part extends DiscreteVoxelShape implements Iterable<DiscreteVoxelShape> {
372

373
        private final Collection<DiscreteVoxelShape> entries;
374

375
        public Part(Collection<DiscreteVoxelShape> entries) {
376
            super(0, 0, 0);
5✔
377
            this.entries = entries;
3✔
378
        }
1✔
379

380
        @Override
381
        public Iterator<DiscreteVoxelShape> iterator() {
382
            return entries.iterator();
×
383
        }
384

385
        @Override
386
        public boolean isFullWide(int x, int y, int z) {
387
            for (DiscreteVoxelShape part : this) {
×
388
                if (part.isFullWide(x, y, z)) {
×
389
                    return true;
×
390
                }
391
            }
×
392
            return false;
×
393
        }
394

395
        @Override
396
        public boolean isFull(int x, int y, int z) {
397
            for (DiscreteVoxelShape part : this) {
×
398
                if (part.isFull(x, y, z)) {
×
399
                    return true;
×
400
                }
401
            }
×
402
            return false;
×
403
        }
404

405
        @Override
406
        public void fill(int x, int y, int z) {
407
            for (DiscreteVoxelShape part : this) {
×
408
                part.fill(x, y, z);
×
409
            }
×
410
        }
×
411

412
        @Override
413
        public int firstFull(Direction.Axis axis) {
414
            boolean first = true;
×
415
            int startMin = 0;
×
416
            for (DiscreteVoxelShape part : this) {
×
417
                int start = part.firstFull(axis);
×
418
                if (first || start < startMin) {
×
419
                    startMin = start;
×
420
                    first = false;
×
421
                }
422
            }
×
423
            return startMin;
×
424
        }
425

426
        @Override
427
        public int lastFull(Direction.Axis axis) {
428
            boolean first = true;
×
429
            int endMax = 0;
×
430
            for (DiscreteVoxelShape part : this) {
×
431
                int end = part.lastFull(axis);
×
432
                if (first || end > endMax) {
×
433
                    endMax = end;
×
434
                    first = false;
×
435
                }
436
            }
×
437
            return endMax;
×
438
        }
439

440
        @Override
441
        public int getSize(Direction.Axis axis) {
442
            boolean first = true;
×
443
            int sizeMax = 0;
×
444
            for (DiscreteVoxelShape part : this) {
×
445
                int size = part.getSize(axis);
×
446
                if (first || size > sizeMax) {
×
447
                    sizeMax = size;
×
448
                    first = false;
×
449
                }
450
            }
×
451
            return sizeMax;
×
452
        }
453

454
        @Override
455
        public void forAllBoxes(IntLineConsumer consumer, boolean p_197831_2_) {
456
            for (DiscreteVoxelShape part : this) {
×
457
                part.forAllBoxes(consumer, p_197831_2_);
×
458
            }
×
459
        }
×
460
    }
461

462
    public static interface IComponent {
463

464
        /**
465
         * @param blockState The block state.
466
         * @param world The world.
467
         * @param blockPos The position.
468
         * @return Unique identifier for the component's state.
469
         */
470
        public String getStateId(BlockState blockState, BlockGetter world, BlockPos blockPos);
471

472
        /**
473
         * Get the shape of this component.
474
         * @param blockState The block state.
475
         * @param world The world.
476
         * @param blockPos The position.
477
         * @param selectionContext The selection context.
478
         * @return The shape.
479
         */
480
        public VoxelShape getShape(BlockState blockState, BlockGetter world, BlockPos blockPos, CollisionContext selectionContext);
481

482
        /**
483
         * Get the pick block item.
484
         * @param world The world
485
         * @param pos The position
486
         * @return The item.
487
         */
488
        public ItemStack getCloneItemStack(Level world, BlockPos pos);
489

490
        /**
491
         * Destroy this component
492
         * @param world The world
493
         * @param pos The position
494
         * @param player The player destroying the component.
495
         * @param saveState If the component state should be saved in the dropped item.
496
         * @return If the complete block was destroyed
497
         */
498
        public boolean destroy(Level world, BlockPos pos, Player player, boolean saveState);
499

500
        /**
501
         * @param world The world
502
         * @param pos The position
503
         * @return The model that will be used to render the breaking overlay.
504
         */
505
        @OnlyIn(Dist.CLIENT)
506
        @Nullable
507
        public BakedModel getBreakingBaseModel(Level world, BlockPos pos);
508

509
        /**
510
         * When this component has been activated.
511
         * @param state The block state.
512
         * @param world The world.
513
         * @param blockPos The position.
514
         * @param player The player.
515
         * @param hand The hand.
516
         * @param hit The ray trace result.
517
         * @return Action result.
518
         */
519
        public InteractionResult onBlockActivated(BlockState state, Level world, BlockPos blockPos, Player player,
520
                                                 InteractionHand hand, BlockRayTraceResultComponent hit);
521

522
        /**
523
         * @return The direction this component points at.
524
         */
525
        @Nullable
526
        public Direction getRaytraceDirection();
527

528
        /**
529
         * @return If this component should only be raytraced if no other components matched for this face.
530
         */
531
        public boolean isRaytraceLastForFace();
532

533
    }
534

535
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc