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

CyclopsMC / IntegratedDynamics / 22186773560

19 Feb 2026 02:52PM UTC coverage: 52.603% (+0.2%) from 52.363%
22186773560

push

github

web-flow
Remove Lombok dependency (#1604)

2911 of 8664 branches covered (33.6%)

Branch coverage included in aggregate %.

17683 of 30486 relevant lines covered (58.0%)

3.01 hits per line

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

65.22
/src/main/java/org/cyclops/integrateddynamics/core/blockentity/BlockEntityMultipartTicking.java
1
package org.cyclops.integrateddynamics.core.blockentity;
2

3
import net.minecraft.core.BlockPos;
4
import net.minecraft.core.Direction;
5
import net.minecraft.nbt.CompoundTag;
6
import net.minecraft.nbt.Tag;
7
import net.minecraft.network.Connection;
8
import net.minecraft.world.level.Level;
9
import net.minecraft.world.level.block.entity.BlockEntityType;
10
import net.minecraft.world.level.block.state.BlockState;
11
import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils;
12
import net.minecraft.world.level.redstone.Orientation;
13
import net.minecraft.world.level.storage.ValueInput;
14
import net.minecraft.world.level.storage.ValueOutput;
15
import net.neoforged.fml.ModLoader;
16
import net.neoforged.neoforge.capabilities.BlockCapability;
17
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
18
import net.neoforged.neoforge.model.data.ModelData;
19
import org.cyclops.cyclopscore.blockentity.BlockEntityTickerDelayed;
20
import org.cyclops.cyclopscore.blockentity.CyclopsBlockEntity;
21
import org.cyclops.cyclopscore.datastructure.DimPos;
22
import org.cyclops.cyclopscore.datastructure.EnumFacingMap;
23
import org.cyclops.cyclopscore.helper.IModHelpers;
24
import org.cyclops.cyclopscore.persist.nbt.NBTPersist;
25
import org.cyclops.integrateddynamics.Capabilities;
26
import org.cyclops.integrateddynamics.RegistryEntries;
27
import org.cyclops.integrateddynamics.api.block.IFacadeable;
28
import org.cyclops.integrateddynamics.api.block.cable.ICableFakeable;
29
import org.cyclops.integrateddynamics.api.network.INetwork;
30
import org.cyclops.integrateddynamics.api.network.INetworkCarrier;
31
import org.cyclops.integrateddynamics.api.network.INetworkElement;
32
import org.cyclops.integrateddynamics.api.network.IPartNetwork;
33
import org.cyclops.integrateddynamics.api.part.PartCapability;
34
import org.cyclops.integrateddynamics.api.part.PartPos;
35
import org.cyclops.integrateddynamics.api.part.PartRenderPosition;
36
import org.cyclops.integrateddynamics.api.part.PartTarget;
37
import org.cyclops.integrateddynamics.block.BlockCable;
38
import org.cyclops.integrateddynamics.capability.cable.CableFakeableMultipartTicking;
39
import org.cyclops.integrateddynamics.capability.cable.CableTileMultipartTicking;
40
import org.cyclops.integrateddynamics.capability.dynamiclight.DynamicLightTileMultipartTicking;
41
import org.cyclops.integrateddynamics.capability.dynamicredstone.DynamicRedstoneTileMultipartTicking;
42
import org.cyclops.integrateddynamics.capability.facadeable.FacadeableDefault;
43
import org.cyclops.integrateddynamics.capability.facadeable.FacadeableTileMultipartTicking;
44
import org.cyclops.integrateddynamics.capability.network.NetworkCarrierDefault;
45
import org.cyclops.integrateddynamics.capability.networkelementprovider.NetworkElementProviderPartContainer;
46
import org.cyclops.integrateddynamics.capability.partcontainer.PartContainerTileMultipartTicking;
47
import org.cyclops.integrateddynamics.capability.path.PathElementTileMultipartTicking;
48
import org.cyclops.integrateddynamics.client.model.CableRenderState;
49
import org.cyclops.integrateddynamics.core.helper.CableHelpers;
50
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
51
import org.cyclops.integrateddynamics.core.helper.PartHelpers;
52
import org.cyclops.integrateddynamics.core.part.event.RegisterPartCapabilitiesEvent;
53

54
import java.util.Map;
55
import java.util.Objects;
56
import java.util.Optional;
57

58
/**
59
 * A ticking part entity which is made up of different parts.
60
 * @author Ruben Taelman
61
 */
62
public class BlockEntityMultipartTicking extends CyclopsBlockEntity implements PartHelpers.IPartStateHolderCallback {
63

64
    @NBTPersist private EnumFacingMap<Boolean> connected = EnumFacingMap.newMap();
3✔
65
    @NBTPersist private EnumFacingMap<Boolean> forceDisconnected = EnumFacingMap.newMap();
3✔
66
    @NBTPersist private EnumFacingMap<Integer> redstoneLevels = EnumFacingMap.newMap();
3✔
67
    @NBTPersist private EnumFacingMap<Boolean> redstoneInputs = EnumFacingMap.newMap();
3✔
68
    @NBTPersist private EnumFacingMap<Boolean> redstoneStrong = EnumFacingMap.newMap();
3✔
69
    @NBTPersist private EnumFacingMap<Integer> lastRedstonePulses = EnumFacingMap.newMap();
3✔
70
    @NBTPersist private EnumFacingMap<Integer> scheduledPulseRemaining = EnumFacingMap.newMap();
3✔
71
    @NBTPersist private EnumFacingMap<Integer> lightLevels = EnumFacingMap.newMap();
3✔
72
    private EnumFacingMap<Integer> previousLightLevels;
73
    @NBTPersist private CompoundTag facadeBlockTag = null;
3✔
74

75
    private final PartContainerTileMultipartTicking partContainer;
76
    private final CableTileMultipartTicking cable;
77
    private final INetworkCarrier networkCarrier;
78
    private final ICableFakeable cableFakeable;
79
    @NBTPersist
80
    private boolean forceLightCheckAtClient;
81

82
    private ModelData cachedState = null;
3✔
83

84
    public EnumFacingMap<Boolean> getConnected() {
85
        return connected;
3✔
86
    }
87

88
    public EnumFacingMap<Integer> getRedstoneLevels() {
89
        return redstoneLevels;
3✔
90
    }
91

92
    public EnumFacingMap<Boolean> getRedstoneInputs() {
93
        return redstoneInputs;
3✔
94
    }
95

96
    public EnumFacingMap<Boolean> getRedstoneStrong() {
97
        return redstoneStrong;
3✔
98
    }
99

100
    public EnumFacingMap<Integer> getLastRedstonePulses() {
101
        return lastRedstonePulses;
3✔
102
    }
103

104
    public EnumFacingMap<Integer> getScheduledPulseRemaining() {
105
        return scheduledPulseRemaining;
3✔
106
    }
107

108
    public EnumFacingMap<Integer> getLightLevels() {
109
        return lightLevels;
×
110
    }
111

112
    public CompoundTag getFacadeBlockTag() {
113
        return facadeBlockTag;
3✔
114
    }
115

116
    public void setFacadeBlockTag(CompoundTag facadeBlockTag) {
117
        this.facadeBlockTag = facadeBlockTag;
3✔
118
    }
1✔
119

120
    public PartContainerTileMultipartTicking getPartContainer() {
121
        return partContainer;
3✔
122
    }
123

124
    public CableTileMultipartTicking getCable() {
125
        return cable;
3✔
126
    }
127

128
    public INetworkCarrier getNetworkCarrier() {
129
        return networkCarrier;
3✔
130
    }
131

132
    public ICableFakeable getCableFakeable() {
133
        return cableFakeable;
3✔
134
    }
135

136
    public void setForceLightCheckAtClient(boolean forceLightCheckAtClient) {
137
        this.forceLightCheckAtClient = forceLightCheckAtClient;
3✔
138
    }
1✔
139

140
    public BlockEntityMultipartTicking(BlockPos blockPos, BlockState blockState) {
141
        super(RegistryEntries.BLOCK_ENTITY_MULTIPART_TICKING.get(), blockPos, blockState);
7✔
142
        partContainer = new PartContainerTileMultipartTicking(this);
6✔
143
        cable = new CableTileMultipartTicking(this);
6✔
144
        networkCarrier = new NetworkCarrierDefault();
5✔
145
        cableFakeable = new CableFakeableMultipartTicking(this);
6✔
146
    }
1✔
147

148
    public static void registerMultipartTickingCapabilities(RegisterCapabilitiesEvent event, BlockEntityType<? extends BlockEntityMultipartTicking> blockEntityType) {
149
        event.registerBlockEntity(
5✔
150
                Capabilities.PartContainer.BLOCK,
151
                blockEntityType,
152
                (blockEntity, context) -> blockEntity.getPartContainer()
3✔
153
        );
154
        event.registerBlockEntity(
5✔
155
                Capabilities.NetworkElementProvider.BLOCK,
156
                blockEntityType,
157
                (blockEntity, context) -> new NetworkElementProviderPartContainer(blockEntity.getPartContainer())
6✔
158
        );
159
        event.registerBlockEntity(
5✔
160
                Capabilities.Facadeable.BLOCK,
161
                blockEntityType,
162
                (blockEntity, context) -> new FacadeableTileMultipartTicking(blockEntity)
5✔
163
        );
164
        event.registerBlockEntity(
5✔
165
                Capabilities.Cable.BLOCK,
166
                blockEntityType,
167
                (blockEntity, context) -> blockEntity.getCable()
3✔
168
        );
169
        event.registerBlockEntity(
5✔
170
                Capabilities.NetworkCarrier.BLOCK,
171
                blockEntityType,
172
                (blockEntity, context) -> blockEntity.getNetworkCarrier()
3✔
173
        );
174
        event.registerBlockEntity(
5✔
175
                Capabilities.CableFakeable.BLOCK,
176
                blockEntityType,
177
                (blockEntity, context) -> blockEntity.getCableFakeable()
3✔
178
        );
179
        event.registerBlockEntity(
5✔
180
                Capabilities.PathElement.BLOCK,
181
                blockEntityType,
182
                (blockEntity, context) -> new PathElementTileMultipartTicking(blockEntity, blockEntity.getCable())
7✔
183
        );
184
        registerPartCapabilityAsBlockCapability(event, blockEntityType, Capabilities.ValueInterface.BLOCK, Capabilities.ValueInterface.PART);
5✔
185
        registerPartCapabilityAsBlockCapability(event, blockEntityType, Capabilities.VariableContainer.BLOCK, Capabilities.VariableContainer.PART);
5✔
186
        ModLoader.postEventWrapContainerInModOrder(new RegisterPartCapabilitiesEvent(event, blockEntityType));
6✔
187

188
        event.registerBlockEntity(
5✔
189
                Capabilities.DynamicLight.BLOCK,
190
                blockEntityType,
191
                DynamicLightTileMultipartTicking::new
192
        );
193
        event.registerBlockEntity(
5✔
194
                Capabilities.DynamicRedstone.BLOCK,
195
                blockEntityType,
196
                DynamicRedstoneTileMultipartTicking::new
197
        );
198
    }
1✔
199

200
    public static <T> void registerPartCapabilityAsBlockCapability(RegisterCapabilitiesEvent event, BlockEntityType<? extends BlockEntityMultipartTicking> blockEntityType, BlockCapability<T, Direction> blockCapability, PartCapability<T> partCapability) {
201
        event.registerBlockEntity(
6✔
202
                blockCapability,
203
                blockEntityType,
204
                (blockEntity, context) -> {
205
                    INetwork network = blockEntity.getNetwork();
3✔
206
                    if (network != null) {
2!
207
                        IPartNetwork partNetwork = NetworkHelpers.getPartNetworkChecked(network);
3✔
208
                        return blockEntity.getPartContainer()
7✔
209
                                .getCapability(partCapability, network, partNetwork, PartTarget.fromCenter(PartPos.of(blockEntity.getLevel(), blockEntity.getBlockPos(), context)))
8✔
210
                                .orElse(null);
1✔
211
                    }
212
                    return null;
×
213
                }
214
        );
215
    }
1✔
216

217
    @Override
218
    public void saveAdditional(ValueOutput output) {
219
        super.saveAdditional(output);
3✔
220
        forceLightCheckAtClient = false;
3✔
221
        partContainer.toValueOutput(output.child("partContainer"));
6✔
222
        output.putBoolean("realCable", cableFakeable.isRealCable());
6✔
223
    }
1✔
224

225
    @Override
226
    public void read(ValueInput input) {
227
        EnumFacingMap<Boolean> lastConnected = EnumFacingMap.newMap(connected);
4✔
228
        Tag lastFacadeBlock = facadeBlockTag;
3✔
229
        boolean lastRealCable = cableFakeable.isRealCable();
4✔
230
        partContainer.fromValueInput(input.child("partContainer").orElseThrow());
8✔
231
        boolean wasLightTransparent = getLevel() != null && CableHelpers.isLightTransparent(getLevel(), getBlockPos(), null, getBlockState());
5!
232

233
        super.read(input);
3✔
234
        cableFakeable.setRealCable(input.getBooleanOr("realCable", false));
7✔
235
        boolean isLightTransparent = getLevel() != null && CableHelpers.isLightTransparent(getLevel(), getBlockPos(), null, getBlockState());
5!
236
        if (getLevel() != null && (lastConnected == null || connected == null || !lastConnected.equals(connected)
3!
237
                || !Objects.equals(lastFacadeBlock, facadeBlockTag)
×
238
                || lastRealCable != cableFakeable.isRealCable() || wasLightTransparent != isLightTransparent)) {
×
239
            IModHelpers.get().getBlockHelpers().markForUpdate(getLevel(), getBlockPos());
×
240
        }
241
    }
1✔
242

243
    @Override
244
    public void onDataPacket(Connection net, ValueInput valueInput) {
245
        super.onDataPacket(net, valueInput);
×
246
        onUpdateReceived();
×
247
    }
×
248

249
    protected void onUpdateReceived() {
250
        if(!lightLevels.equals(previousLightLevels)) {
×
251
            previousLightLevels = lightLevels;
×
252
        }
253
        cachedState = null;
×
254
        IModHelpers.get().getBlockHelpers().markForUpdate(getLevel(), getBlockPos());
×
255
        if (forceLightCheckAtClient) {
×
256
            getLevel().getLightEngine().checkBlock(getBlockPos());
×
257
        }
258
    }
×
259

260
    public ModelData getConnectionState() {
261
        if (cachedState != null) {
×
262
            return cachedState;
×
263
        }
264
        ModelData.Builder builder = ModelData.builder();
×
265
        if (partContainer.getPartData() != null) { // Can be null in rare cases where rendering happens before data sync
×
266
            builder.with(BlockCable.REALCABLE, cableFakeable.isRealCable());
×
267
            if (connected.isEmpty()) {
×
268
                getCable().updateConnections(false);
×
269
            }
270
            for (Direction side : Direction.values()) {
×
271
                builder.with(BlockCable.CONNECTED[side.ordinal()],
×
272
                        !cable.isForceDisconnected(side) && connected.get(side));
×
273
                builder.with(BlockCable.PART_RENDERPOSITIONS[side.ordinal()],
×
274
                        partContainer.hasPart(side) ? partContainer.getPart(side).getPartRenderPosition() : PartRenderPosition.NONE);
×
275
            }
276
            IFacadeable facadeable = Optional.ofNullable(level.getCapability(Capabilities.Facadeable.BLOCK, getBlockPos(), getBlockState(), this, null))
×
277
                    .orElseGet(FacadeableDefault::new);
×
278
            builder.with(BlockCable.FACADE, facadeable.hasFacade() ? Optional.of(facadeable.getFacade()) : Optional.empty());
×
279
            builder.with(BlockCable.PARTCONTAINER, partContainer);
×
280
            builder.with(BlockCable.RENDERSTATE, new CableRenderState(
×
281
                    this.cableFakeable.isRealCable(),
×
282
                    EnumFacingMap.newMap(this.connected),
×
283
                    EnumFacingMap.newMap(this.partContainer.getPartData()),
×
284
                    facadeBlockTag
285
                    ));
286
        }
287
        return cachedState = builder.build();
×
288
    }
289

290
    public void updateRedstoneInfo(Direction side, boolean strongPower) {
291
        this.setChanged();
2✔
292
        if (getLevel().isLoaded(getBlockPos().relative(side))) {
8!
293
            Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, side.getOpposite(), null);
7✔
294
            getLevel().neighborChanged(getBlockPos().relative(side), getBlockState().getBlock(), orientation);
11✔
295
            if (strongPower) {
2✔
296
                // When we are emitting a strong power, also update all neighbours of the target
297
                getLevel().updateNeighborsAt(getBlockPos().relative(side), getBlockState().getBlock());
10✔
298
            }
299
        }
300
    }
1✔
301

302
    public void updateLightInfo() {
303
        sendUpdate();
×
304
    }
×
305

306
    public void updateScheduledPulses() {
307
        if (!getLevel().isClientSide()) {
4!
308
            EnumFacingMap<Integer> scheduledPulses = getScheduledPulseRemaining();
3✔
309
            if (!scheduledPulses.isEmpty()) {
3✔
310
                java.util.List<Direction> toRemove = new java.util.ArrayList<>();
4✔
311
                for (Map.Entry<Direction, Integer> entry : scheduledPulses.entrySet()) {
11✔
312
                    Direction side = entry.getKey();
4✔
313
                    int remaining = entry.getValue();
5✔
314
                    if (remaining > 0) {
2!
315
                        remaining--;
1✔
316
                        if (remaining == 0) {
2✔
317
                            // Mark for removal and reset the redstone level to 0
318
                            toRemove.add(side);
4✔
319
                            EnumFacingMap<Integer> redstoneLevels = getRedstoneLevels();
3✔
320
                            EnumFacingMap<Boolean> redstoneStrongs = getRedstoneStrong();
3✔
321
                            boolean strongPower = redstoneStrongs.getOrDefault(side, false);
8✔
322
                            redstoneLevels.put(side, 0);
6✔
323
                            updateRedstoneInfo(side, strongPower);
4✔
324
                        } else {
1✔
325
                            scheduledPulses.put(side, remaining);
6✔
326
                        }
327
                    }
328
                }
1✔
329
                // Remove completed pulses
330
                for (Direction side : toRemove) {
10✔
331
                    scheduledPulses.remove(side);
4✔
332
                }
1✔
333
            }
334
        }
335
    }
1✔
336

337
    public INetwork getNetwork() {
338
        return networkCarrier.getNetwork();
4✔
339
    }
340

341
    @Override
342
    public void onSet(PartHelpers.PartStateHolder<?, ?> partStateHolder) {
343

344
    }
×
345

346
    /**
347
     * @return The raw force disconnection data.
348
     */
349
    public EnumFacingMap<Boolean> getForceDisconnected() {
350
        return this.forceDisconnected;
3✔
351
    }
352

353
    public void setForceDisconnected(EnumFacingMap<Boolean> forceDisconnected) {
354
        this.forceDisconnected.clear();
×
355
        this.forceDisconnected.putAll(forceDisconnected);
×
356
    }
×
357

358
    @Override
359
    public void onChunkUnloaded() {
360
        super.onChunkUnloaded();
2✔
361
        invalidateParts();
2✔
362
    }
1✔
363

364
    protected void invalidateParts() {
365
        if (getLevel() != null && !getLevel().isClientSide()) {
7!
366
            INetwork network = getNetwork();
3✔
367
            if (network != null) {
2✔
368
                for (Map.Entry<Direction, PartHelpers.PartStateHolder<?, ?>> entry : partContainer.getPartData().entrySet()) {
13✔
369
                    INetworkElement element = entry.getValue().getPart().createNetworkElement(getPartContainer(), DimPos.of(getLevel(), getBlockPos()), entry.getKey());
16✔
370
                    element.invalidate(network);
3✔
371
                }
1✔
372
            }
373
        }
374
    }
1✔
375

376
    @Override
377
    protected Direction transformFacingForRotation(Direction facing) {
378
        // Ignore rotations on this tile
379
        return facing;
×
380
    }
381

382
    @Override
383
    public void preRemoveSideEffects(BlockPos pos, BlockState state) {
384
        super.preRemoveSideEffects(pos, state);
4✔
385

386
        if (!CableHelpers.isRemovingCable()) {
2✔
387
            CableHelpers.onCableRemoving(level, pos, false, false, state, this);
9✔
388
        }
389
    }
1✔
390

391
    @Override
392
    public void setRemoved() {
393
        // Only required for cases where cables are moved by commands or cleared after game tests.
394
        if (getNetworkCarrier().getNetwork() != null) {
4✔
395
            CableHelpers.onCableRemovingNetwork(getBlockState(), this, getNetworkCarrier(), new PathElementTileMultipartTicking(this, this.getCable()));
13✔
396
        }
397

398
        super.setRemoved();
2✔
399
    }
1✔
400

401
    public static class Ticker<T extends BlockEntityMultipartTicking> extends BlockEntityTickerDelayed<T> {
3✔
402
        @Override
403
        protected void update(Level level, BlockPos pos, BlockState blockState, T blockEntity) {
404
            super.update(level, pos, blockState, blockEntity);
6✔
405

406
            if (blockEntity.getConnected().isEmpty()) {
4✔
407
                blockEntity.getCable().updateConnections();
3✔
408
            }
409

410
            // Handle scheduled pulse resets before aspect evaluation
411
            blockEntity.updateScheduledPulses();
2✔
412

413
            blockEntity.getPartContainer().update();
3✔
414

415
            // Revalidate network if that hasn't happened yet
416
            if (blockEntity.getNetwork() == null) {
3✔
417
                NetworkHelpers.revalidateNetworkElements(level, pos);
4✔
418
            }
419
        }
1✔
420
    }
421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc