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

CyclopsMC / IntegratedDynamics / 22094089464

17 Feb 2026 10:03AM UTC coverage: 53.26% (+0.1%) from 53.129%
22094089464

push

github

rubensworks
Merge remote-tracking branch 'origin/master-1.21-lts' into master-1.21

2905 of 8816 branches covered (32.95%)

Branch coverage included in aggregate %.

17597 of 29678 relevant lines covered (59.29%)

3.08 hits per line

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

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

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

56
import java.util.Map;
57
import java.util.Objects;
58
import java.util.Optional;
59

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

66
    @Getter
4✔
67
    @NBTPersist private EnumFacingMap<Boolean> connected = EnumFacingMap.newMap();
2✔
68
    @NBTPersist private EnumFacingMap<Boolean> forceDisconnected = EnumFacingMap.newMap();
3✔
69
    @Getter
4✔
70
    @NBTPersist private EnumFacingMap<Integer> redstoneLevels = EnumFacingMap.newMap();
2✔
71
    @Getter
4✔
72
    @NBTPersist private EnumFacingMap<Boolean> redstoneInputs = EnumFacingMap.newMap();
2✔
73
    @Getter
4✔
74
    @NBTPersist private EnumFacingMap<Boolean> redstoneStrong = EnumFacingMap.newMap();
2✔
75
    @Getter
4✔
76
    @NBTPersist private EnumFacingMap<Integer> lastRedstonePulses = EnumFacingMap.newMap();
2✔
77
    @Getter
4✔
78
    @NBTPersist private EnumFacingMap<Integer> scheduledPulseRemaining = EnumFacingMap.newMap();
2✔
79
    @Getter
1✔
80
    @NBTPersist private EnumFacingMap<Integer> lightLevels = EnumFacingMap.newMap();
2✔
81
    private EnumFacingMap<Integer> previousLightLevels;
82
    @Getter
6✔
83
    @Setter
4✔
84
    @NBTPersist private CompoundTag facadeBlockTag = null;
85

86
    @Getter
3✔
87
    private final PartContainerTileMultipartTicking partContainer;
88
    @Getter
3✔
89
    private final CableTileMultipartTicking cable;
90
    @Getter
3✔
91
    private final INetworkCarrier networkCarrier;
92
    @Getter
3✔
93
    private final ICableFakeable cableFakeable;
94
    @NBTPersist
95
    @Setter
4✔
96
    private boolean forceLightCheckAtClient;
97

98
    private ModelData cachedState = null;
3✔
99

100
    public BlockEntityMultipartTicking(BlockPos blockPos, BlockState blockState) {
101
        super(RegistryEntries.BLOCK_ENTITY_MULTIPART_TICKING.get(), blockPos, blockState);
7✔
102
        partContainer = new PartContainerTileMultipartTicking(this);
6✔
103
        cable = new CableTileMultipartTicking(this);
6✔
104
        networkCarrier = new NetworkCarrierDefault();
5✔
105
        cableFakeable = new CableFakeableMultipartTicking(this);
6✔
106
    }
1✔
107

108
    public static void registerMultipartTickingCapabilities(RegisterCapabilitiesEvent event, BlockEntityType<? extends BlockEntityMultipartTicking> blockEntityType) {
109
        event.registerBlockEntity(
5✔
110
                Capabilities.PartContainer.BLOCK,
111
                blockEntityType,
112
                (blockEntity, context) -> blockEntity.getPartContainer()
3✔
113
        );
114
        event.registerBlockEntity(
5✔
115
                Capabilities.NetworkElementProvider.BLOCK,
116
                blockEntityType,
117
                (blockEntity, context) -> new NetworkElementProviderPartContainer(blockEntity.getPartContainer())
6✔
118
        );
119
        event.registerBlockEntity(
5✔
120
                Capabilities.Facadeable.BLOCK,
121
                blockEntityType,
122
                (blockEntity, context) -> new FacadeableTileMultipartTicking(blockEntity)
5✔
123
        );
124
        event.registerBlockEntity(
5✔
125
                Capabilities.Cable.BLOCK,
126
                blockEntityType,
127
                (blockEntity, context) -> blockEntity.getCable()
3✔
128
        );
129
        event.registerBlockEntity(
5✔
130
                Capabilities.NetworkCarrier.BLOCK,
131
                blockEntityType,
132
                (blockEntity, context) -> blockEntity.getNetworkCarrier()
3✔
133
        );
134
        event.registerBlockEntity(
5✔
135
                Capabilities.CableFakeable.BLOCK,
136
                blockEntityType,
137
                (blockEntity, context) -> blockEntity.getCableFakeable()
3✔
138
        );
139
        event.registerBlockEntity(
5✔
140
                Capabilities.PathElement.BLOCK,
141
                blockEntityType,
142
                (blockEntity, context) -> new PathElementTileMultipartTicking(blockEntity, blockEntity.getCable())
7✔
143
        );
144
        registerPartCapabilityAsBlockCapability(event, blockEntityType, Capabilities.ValueInterface.BLOCK, Capabilities.ValueInterface.PART);
5✔
145
        registerPartCapabilityAsBlockCapability(event, blockEntityType, Capabilities.VariableContainer.BLOCK, Capabilities.VariableContainer.PART);
5✔
146
        ModLoader.postEventWrapContainerInModOrder(new RegisterPartCapabilitiesEvent(event, blockEntityType));
6✔
147

148
        event.registerBlockEntity(
5✔
149
                Capabilities.DynamicLight.BLOCK,
150
                blockEntityType,
151
                DynamicLightTileMultipartTicking::new
152
        );
153
        event.registerBlockEntity(
5✔
154
                Capabilities.DynamicRedstone.BLOCK,
155
                blockEntityType,
156
                DynamicRedstoneTileMultipartTicking::new
157
        );
158
    }
1✔
159

160
    public static <T> void registerPartCapabilityAsBlockCapability(RegisterCapabilitiesEvent event, BlockEntityType<? extends BlockEntityMultipartTicking> blockEntityType, BlockCapability<T, Direction> blockCapability, PartCapability<T> partCapability) {
161
        event.registerBlockEntity(
6✔
162
                blockCapability,
163
                blockEntityType,
164
                (blockEntity, context) -> {
165
                    INetwork network = blockEntity.getNetwork();
3✔
166
                    if (network != null) {
2!
167
                        IPartNetwork partNetwork = NetworkHelpers.getPartNetworkChecked(network);
3✔
168
                        return blockEntity.getPartContainer()
7✔
169
                                .getCapability(partCapability, network, partNetwork, PartTarget.fromCenter(PartPos.of(blockEntity.getLevel(), blockEntity.getBlockPos(), context)))
8✔
170
                                .orElse(null);
1✔
171
                    }
172
                    return null;
×
173
                }
174
        );
175
    }
1✔
176

177
    @Override
178
    public void saveAdditional(ValueOutput output) {
179
        super.saveAdditional(output);
3✔
180
        forceLightCheckAtClient = false;
3✔
181
        partContainer.toValueOutput(output.child("partContainer"));
6✔
182
        output.putBoolean("realCable", cableFakeable.isRealCable());
6✔
183
    }
1✔
184

185
    @Override
186
    public void read(ValueInput input) {
187
        EnumFacingMap<Boolean> lastConnected = EnumFacingMap.newMap(connected);
4✔
188
        Tag lastFacadeBlock = facadeBlockTag;
3✔
189
        boolean lastRealCable = cableFakeable.isRealCable();
4✔
190
        partContainer.fromValueInput(input.child("partContainer").orElseThrow());
8✔
191
        boolean wasLightTransparent = getLevel() != null && CableHelpers.isLightTransparent(getLevel(), getBlockPos(), null, getBlockState());
5!
192

193
        super.read(input);
3✔
194
        cableFakeable.setRealCable(input.getBooleanOr("realCable", false));
7✔
195
        boolean isLightTransparent = getLevel() != null && CableHelpers.isLightTransparent(getLevel(), getBlockPos(), null, getBlockState());
5!
196
        if (getLevel() != null && (lastConnected == null || connected == null || !lastConnected.equals(connected)
3!
197
                || !Objects.equals(lastFacadeBlock, facadeBlockTag)
×
198
                || lastRealCable != cableFakeable.isRealCable() || wasLightTransparent != isLightTransparent)) {
×
199
            IModHelpers.get().getBlockHelpers().markForUpdate(getLevel(), getBlockPos());
×
200
        }
201
    }
1✔
202

203
    @Override
204
    public void onDataPacket(Connection net, ValueInput valueInput) {
205
        super.onDataPacket(net, valueInput);
×
206
        onUpdateReceived();
×
207
    }
×
208

209
    protected void onUpdateReceived() {
210
        if(!lightLevels.equals(previousLightLevels)) {
×
211
            previousLightLevels = lightLevels;
×
212
        }
213
        cachedState = null;
×
214
        IModHelpers.get().getBlockHelpers().markForUpdate(getLevel(), getBlockPos());
×
215
        if (forceLightCheckAtClient) {
×
216
            getLevel().getLightEngine().checkBlock(getBlockPos());
×
217
        }
218
    }
×
219

220
    public ModelData getConnectionState() {
221
        if (cachedState != null) {
×
222
            return cachedState;
×
223
        }
224
        ModelData.Builder builder = ModelData.builder();
×
225
        if (partContainer.getPartData() != null) { // Can be null in rare cases where rendering happens before data sync
×
226
            builder.with(BlockCable.REALCABLE, cableFakeable.isRealCable());
×
227
            if (connected.isEmpty()) {
×
228
                getCable().updateConnections(false);
×
229
            }
230
            for (Direction side : Direction.values()) {
×
231
                builder.with(BlockCable.CONNECTED[side.ordinal()],
×
232
                        !cable.isForceDisconnected(side) && connected.get(side));
×
233
                builder.with(BlockCable.PART_RENDERPOSITIONS[side.ordinal()],
×
234
                        partContainer.hasPart(side) ? partContainer.getPart(side).getPartRenderPosition() : PartRenderPosition.NONE);
×
235
            }
236
            IFacadeable facadeable = Optional.ofNullable(level.getCapability(Capabilities.Facadeable.BLOCK, getBlockPos(), getBlockState(), this, null))
×
237
                    .orElseGet(FacadeableDefault::new);
×
238
            builder.with(BlockCable.FACADE, facadeable.hasFacade() ? Optional.of(facadeable.getFacade()) : Optional.empty());
×
239
            builder.with(BlockCable.PARTCONTAINER, partContainer);
×
240
            builder.with(BlockCable.RENDERSTATE, new CableRenderState(
×
241
                    this.cableFakeable.isRealCable(),
×
242
                    EnumFacingMap.newMap(this.connected),
×
243
                    EnumFacingMap.newMap(this.partContainer.getPartData()),
×
244
                    facadeBlockTag
245
                    ));
246
        }
247
        return cachedState = builder.build();
×
248
    }
249

250
    public void updateRedstoneInfo(Direction side, boolean strongPower) {
251
        this.setChanged();
2✔
252
        if (getLevel().isLoaded(getBlockPos().relative(side))) {
8✔
253
            Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, side.getOpposite(), null);
7✔
254
            getLevel().neighborChanged(getBlockPos().relative(side), getBlockState().getBlock(), orientation);
11✔
255
            if (strongPower) {
2✔
256
                // When we are emitting a strong power, also update all neighbours of the target
257
                getLevel().updateNeighborsAt(getBlockPos().relative(side), getBlockState().getBlock());
10✔
258
            }
259
        }
260
    }
1✔
261

262
    public void updateLightInfo() {
263
        sendUpdate();
×
264
    }
×
265

266
    public void updateScheduledPulses() {
267
        if (!getLevel().isClientSide()) {
4!
268
            EnumFacingMap<Integer> scheduledPulses = getScheduledPulseRemaining();
3✔
269
            if (!scheduledPulses.isEmpty()) {
3✔
270
                java.util.List<Direction> toRemove = new java.util.ArrayList<>();
4✔
271
                for (Map.Entry<Direction, Integer> entry : scheduledPulses.entrySet()) {
11✔
272
                    Direction side = entry.getKey();
4✔
273
                    int remaining = entry.getValue();
5✔
274
                    if (remaining > 0) {
2!
275
                        remaining--;
1✔
276
                        if (remaining == 0) {
2✔
277
                            // Mark for removal and reset the redstone level to 0
278
                            toRemove.add(side);
4✔
279
                            EnumFacingMap<Integer> redstoneLevels = getRedstoneLevels();
3✔
280
                            EnumFacingMap<Boolean> redstoneStrongs = getRedstoneStrong();
3✔
281
                            boolean strongPower = redstoneStrongs.getOrDefault(side, false);
8✔
282
                            redstoneLevels.put(side, 0);
6✔
283
                            updateRedstoneInfo(side, strongPower);
4✔
284
                        } else {
1✔
285
                            scheduledPulses.put(side, remaining);
6✔
286
                        }
287
                    }
288
                }
1✔
289
                // Remove completed pulses
290
                for (Direction side : toRemove) {
10✔
291
                    scheduledPulses.remove(side);
4✔
292
                }
1✔
293
            }
294
        }
295
    }
1✔
296

297
    public INetwork getNetwork() {
298
        return networkCarrier.getNetwork();
4✔
299
    }
300

301
    @Override
302
    public void onSet(PartHelpers.PartStateHolder<?, ?> partStateHolder) {
303

304
    }
×
305

306
    /**
307
     * @return The raw force disconnection data.
308
     */
309
    public EnumFacingMap<Boolean> getForceDisconnected() {
310
        return this.forceDisconnected;
3✔
311
    }
312

313
    public void setForceDisconnected(EnumFacingMap<Boolean> forceDisconnected) {
314
        this.forceDisconnected.clear();
×
315
        this.forceDisconnected.putAll(forceDisconnected);
×
316
    }
×
317

318
    @Override
319
    public void onChunkUnloaded() {
320
        super.onChunkUnloaded();
2✔
321
        invalidateParts();
2✔
322
    }
1✔
323

324
    protected void invalidateParts() {
325
        if (getLevel() != null && !getLevel().isClientSide()) {
7!
326
            INetwork network = getNetwork();
3✔
327
            if (network != null) {
2✔
328
                for (Map.Entry<Direction, PartHelpers.PartStateHolder<?, ?>> entry : partContainer.getPartData().entrySet()) {
13✔
329
                    INetworkElement element = entry.getValue().getPart().createNetworkElement(getPartContainer(), DimPos.of(getLevel(), getBlockPos()), entry.getKey());
16✔
330
                    element.invalidate(network);
3✔
331
                }
1✔
332
            }
333
        }
334
    }
1✔
335

336
    @Override
337
    protected Direction transformFacingForRotation(Direction facing) {
338
        // Ignore rotations on this tile
339
        return facing;
×
340
    }
341

342
    @Override
343
    public void preRemoveSideEffects(BlockPos pos, BlockState state) {
344
        super.preRemoveSideEffects(pos, state);
4✔
345

346
        if (!CableHelpers.isRemovingCable()) {
2✔
347
            CableHelpers.onCableRemoving(level, pos, false, false, state, this);
9✔
348
        }
349
    }
1✔
350

351
    @Override
352
    public void setRemoved() {
353
        // Only required for cases where cables are moved by commands or cleared after game tests.
354
        if (getNetworkCarrier().getNetwork() != null) {
4✔
355
            CableHelpers.onCableRemovingNetwork(getBlockState(), this, getNetworkCarrier(), new PathElementTileMultipartTicking(this, this.getCable()));
13✔
356
        }
357

358
        super.setRemoved();
2✔
359
    }
1✔
360

361
    public static class Ticker<T extends BlockEntityMultipartTicking> extends BlockEntityTickerDelayed<T> {
3✔
362
        @Override
363
        protected void update(Level level, BlockPos pos, BlockState blockState, T blockEntity) {
364
            super.update(level, pos, blockState, blockEntity);
6✔
365

366
            if (blockEntity.getConnected().isEmpty()) {
4✔
367
                blockEntity.getCable().updateConnections();
3✔
368
            }
369

370
            // Handle scheduled pulse resets before aspect evaluation
371
            blockEntity.updateScheduledPulses();
2✔
372

373
            blockEntity.getPartContainer().update();
3✔
374

375
            // Revalidate network if that hasn't happened yet
376
            if (blockEntity.getNetwork() == null) {
3✔
377
                NetworkHelpers.revalidateNetworkElements(level, pos);
4✔
378
            }
379
        }
1✔
380
    }
381
}
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