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

CyclopsMC / IntegratedDynamics / 22038914515

15 Feb 2026 04:15PM UTC coverage: 45.252% (+0.1%) from 45.155%
22038914515

push

github

web-flow
Add configurable pulse length property to Redstone Writer, Closes #724 (#1602)

2652 of 8642 branches covered (30.69%)

Branch coverage included in aggregate %.

11996 of 23728 relevant lines covered (50.56%)

2.4 hits per line

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

64.1
/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.core.HolderLookup;
8
import net.minecraft.nbt.CompoundTag;
9
import net.minecraft.nbt.Tag;
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.neoforged.fml.ModLoader;
14
import net.neoforged.neoforge.capabilities.BlockCapability;
15
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
16
import net.neoforged.neoforge.client.model.data.ModelData;
17
import org.cyclops.cyclopscore.blockentity.BlockEntityTickerDelayed;
18
import org.cyclops.cyclopscore.blockentity.CyclopsBlockEntity;
19
import org.cyclops.cyclopscore.datastructure.DimPos;
20
import org.cyclops.cyclopscore.datastructure.EnumFacingMap;
21
import org.cyclops.cyclopscore.helper.BlockHelpers;
22
import org.cyclops.cyclopscore.persist.nbt.NBTPersist;
23
import org.cyclops.integrateddynamics.Capabilities;
24
import org.cyclops.integrateddynamics.RegistryEntries;
25
import org.cyclops.integrateddynamics.api.block.IFacadeable;
26
import org.cyclops.integrateddynamics.api.block.cable.ICableFakeable;
27
import org.cyclops.integrateddynamics.api.network.INetwork;
28
import org.cyclops.integrateddynamics.api.network.INetworkCarrier;
29
import org.cyclops.integrateddynamics.api.network.INetworkElement;
30
import org.cyclops.integrateddynamics.api.network.IPartNetwork;
31
import org.cyclops.integrateddynamics.api.part.PartCapability;
32
import org.cyclops.integrateddynamics.api.part.PartPos;
33
import org.cyclops.integrateddynamics.api.part.PartRenderPosition;
34
import org.cyclops.integrateddynamics.api.part.PartTarget;
35
import org.cyclops.integrateddynamics.block.BlockCable;
36
import org.cyclops.integrateddynamics.capability.cable.CableFakeableMultipartTicking;
37
import org.cyclops.integrateddynamics.capability.cable.CableTileMultipartTicking;
38
import org.cyclops.integrateddynamics.capability.dynamiclight.DynamicLightTileMultipartTicking;
39
import org.cyclops.integrateddynamics.capability.dynamicredstone.DynamicRedstoneTileMultipartTicking;
40
import org.cyclops.integrateddynamics.capability.facadeable.FacadeableDefault;
41
import org.cyclops.integrateddynamics.capability.facadeable.FacadeableTileMultipartTicking;
42
import org.cyclops.integrateddynamics.capability.network.NetworkCarrierDefault;
43
import org.cyclops.integrateddynamics.capability.networkelementprovider.NetworkElementProviderPartContainer;
44
import org.cyclops.integrateddynamics.capability.partcontainer.PartContainerTileMultipartTicking;
45
import org.cyclops.integrateddynamics.capability.path.PathElementTileMultipartTicking;
46
import org.cyclops.integrateddynamics.client.model.CableRenderState;
47
import org.cyclops.integrateddynamics.core.helper.CableHelpers;
48
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
49
import org.cyclops.integrateddynamics.core.helper.PartHelpers;
50
import org.cyclops.integrateddynamics.core.part.event.RegisterPartCapabilitiesEvent;
51

52
import java.util.Map;
53
import java.util.Objects;
54
import java.util.Optional;
55

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

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

82
    @Getter
3✔
83
    private final PartContainerTileMultipartTicking partContainer;
84
    @Getter
3✔
85
    private final CableTileMultipartTicking cable;
86
    @Getter
3✔
87
    private final INetworkCarrier networkCarrier;
88
    @Getter
3✔
89
    private final ICableFakeable cableFakeable;
90
    @NBTPersist
91
    @Setter
4✔
92
    private boolean forceLightCheckAtClient;
93

94
    private ModelData cachedState = null;
3✔
95

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

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

144
        event.registerBlockEntity(
5✔
145
                Capabilities.DynamicLight.BLOCK,
146
                blockEntityType,
147
                DynamicLightTileMultipartTicking::new
148
        );
149
        event.registerBlockEntity(
5✔
150
                Capabilities.DynamicRedstone.BLOCK,
151
                blockEntityType,
152
                DynamicRedstoneTileMultipartTicking::new
153
        );
154
    }
1✔
155

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

173
    @Override
174
    public void saveAdditional(CompoundTag tag, HolderLookup.Provider provider) {
175
        super.saveAdditional(tag, provider);
4✔
176
        forceLightCheckAtClient = false;
3✔
177
        tag.put("partContainer", partContainer.serializeNBT(provider));
8✔
178
        tag.putBoolean("realCable", cableFakeable.isRealCable());
6✔
179
    }
1✔
180

181
    @Override
182
    public void read(CompoundTag tag, HolderLookup.Provider provider) {
183
        EnumFacingMap<Boolean> lastConnected = EnumFacingMap.newMap(connected);
4✔
184
        Tag lastFacadeBlock = facadeBlockTag;
3✔
185
        boolean lastRealCable = cableFakeable.isRealCable();
4✔
186
        partContainer.deserializeNBT(provider, tag.getCompound("partContainer"));
7✔
187
        boolean wasLightTransparent = getLevel() != null && CableHelpers.isLightTransparent(getLevel(), getBlockPos(), null);
5!
188

189
        super.read(tag, provider);
4✔
190
        cableFakeable.setRealCable(tag.getBoolean("realCable"));
6✔
191
        boolean isLightTransparent = getLevel() != null && CableHelpers.isLightTransparent(getLevel(), getBlockPos(), null);
5!
192
        if (getLevel() != null && (lastConnected == null || connected == null || !lastConnected.equals(connected)
3!
193
                || !Objects.equals(lastFacadeBlock, facadeBlockTag)
×
194
                || lastRealCable != cableFakeable.isRealCable() || wasLightTransparent != isLightTransparent)) {
×
195
            BlockHelpers.markForUpdate(getLevel(), getBlockPos());
×
196
        }
197
    }
1✔
198

199
    @Override
200
    public void onUpdateReceived() {
201
        if(!lightLevels.equals(previousLightLevels)) {
×
202
            previousLightLevels = lightLevels;
×
203
        }
204
        cachedState = null;
×
205
        BlockHelpers.markForUpdate(getLevel(), getBlockPos());
×
206
        if (forceLightCheckAtClient) {
×
207
            getLevel().getLightEngine().checkBlock(getBlockPos());
×
208
        }
209
    }
×
210

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

241
    public void updateRedstoneInfo(Direction side, boolean strongPower) {
242
        this.setChanged();
2✔
243
        if (getLevel().isLoaded(getBlockPos().relative(side))) {
8!
244
            getLevel().neighborChanged(getBlockPos().relative(side), getBlockState().getBlock(), getBlockPos());
12✔
245
            if (strongPower) {
2✔
246
                // When we are emitting a strong power, also update all neighbours of the target
247
                getLevel().updateNeighborsAt(getBlockPos().relative(side), getBlockState().getBlock());
10✔
248
            }
249
        }
250
    }
1✔
251

252
    public void updateLightInfo() {
253
        sendUpdate();
×
254
    }
×
255

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

287
    public INetwork getNetwork() {
288
        return networkCarrier.getNetwork();
4✔
289
    }
290

291
    @Override
292
    public void onSet(PartHelpers.PartStateHolder<?, ?> partStateHolder) {
293

294
    }
×
295

296
    /**
297
     * @return The raw force disconnection data.
298
     */
299
    public EnumFacingMap<Boolean> getForceDisconnected() {
300
        return this.forceDisconnected;
3✔
301
    }
302

303
    public void setForceDisconnected(EnumFacingMap<Boolean> forceDisconnected) {
304
        this.forceDisconnected.clear();
×
305
        this.forceDisconnected.putAll(forceDisconnected);
×
306
    }
×
307

308
    @Override
309
    public void onChunkUnloaded() {
310
        super.onChunkUnloaded();
2✔
311
        invalidateParts();
2✔
312
    }
1✔
313

314
    protected void invalidateParts() {
315
        if (getLevel() != null && !getLevel().isClientSide) {
7!
316
            INetwork network = getNetwork();
3✔
317
            if (network != null) {
2✔
318
                for (Map.Entry<Direction, PartHelpers.PartStateHolder<?, ?>> entry : partContainer.getPartData().entrySet()) {
13✔
319
                    INetworkElement element = entry.getValue().getPart().createNetworkElement(getPartContainer(), DimPos.of(getLevel(), getBlockPos()), entry.getKey());
16✔
320
                    element.invalidate(network);
3✔
321
                }
1✔
322
            }
323
        }
324
    }
1✔
325

326
    @Override
327
    protected Direction transformFacingForRotation(Direction facing) {
328
        // Ignore rotations on this tile
329
        return facing;
×
330
    }
331

332
    public static class Ticker<T extends BlockEntityMultipartTicking> extends BlockEntityTickerDelayed<T> {
3✔
333
        @Override
334
        protected void update(Level level, BlockPos pos, BlockState blockState, T blockEntity) {
335
            super.update(level, pos, blockState, blockEntity);
6✔
336

337
            if (blockEntity.getConnected().isEmpty()) {
4✔
338
                blockEntity.getCable().updateConnections();
3✔
339
            }
340

341
            // Handle scheduled pulse resets before aspect evaluation
342
            blockEntity.updateScheduledPulses();
2✔
343

344
            blockEntity.getPartContainer().update();
3✔
345

346
            // Revalidate network if that hasn't happened yet
347
            if (blockEntity.getNetwork() == null) {
3✔
348
                NetworkHelpers.revalidateNetworkElements(level, pos);
4✔
349
            }
350
        }
1✔
351
    }
352
}
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