diff --git a/src/main/java/com/simibubi/create/content/contraptions/base/HorizontalAxisKineticBlock.java b/src/main/java/com/simibubi/create/content/contraptions/base/HorizontalAxisKineticBlock.java index a9c674ac9..77627e94d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/base/HorizontalAxisKineticBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/base/HorizontalAxisKineticBlock.java @@ -1,5 +1,7 @@ package com.simibubi.create.content.contraptions.base; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; import com.simibubi.create.foundation.utility.Iterate; import net.minecraft.core.BlockPos; @@ -61,6 +63,11 @@ public abstract class HorizontalAxisKineticBlock extends KineticBlock { return state.getValue(HORIZONTAL_AXIS); } + @Override + public KineticConnections getInitialConnections(BlockState state) { + return AllConnections.FULL_SHAFT.apply(state.getValue(HORIZONTAL_AXIS)); + } + @Override public boolean hasShaftTowards(LevelReader world, BlockPos pos, BlockState state, Direction face) { return face.getAxis() == state.getValue(HORIZONTAL_AXIS); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java index 681b117d9..a77e3d3b6 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerBlock.java @@ -10,6 +10,8 @@ import com.simibubi.create.AllTileEntities; import com.simibubi.create.content.contraptions.base.HorizontalAxisKineticBlock; import com.simibubi.create.content.contraptions.relays.elementary.CogWheelBlock; import com.simibubi.create.content.contraptions.relays.elementary.ICogWheel; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; import com.simibubi.create.foundation.block.ITE; import com.simibubi.create.foundation.utility.placement.IPlacementHelper; import com.simibubi.create.foundation.utility.placement.PlacementHelpers; @@ -43,6 +45,11 @@ public class SpeedControllerBlock extends HorizontalAxisKineticBlock implements super(properties); } + @Override + public KineticConnections getInitialConnections(BlockState state) { + return AllConnections.SPEED_CONTROLLER.apply(state.getValue(HORIZONTAL_AXIS)); + } + @Override public BlockState getStateForPlacement(BlockPlaceContext context) { BlockState above = context.getLevel() @@ -111,7 +118,7 @@ public class SpeedControllerBlock extends HorizontalAxisKineticBlock implements public Class getTileEntityClass() { return SpeedControllerTileEntity.class; } - + @Override public BlockEntityType getTileEntityType() { return AllTileEntities.ROTATION_SPEED_CONTROLLER.get(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java index 9f213cafc..5fecca80f 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/advanced/SpeedControllerTileEntity.java @@ -49,11 +49,14 @@ public class SpeedControllerTileEntity extends KineticTileEntity { targetSpeed.value = DEFAULT_SPEED; targetSpeed.moveText(new Vec3(9, 0, 10)); targetSpeed.withUnit(i -> Lang.translate("generic.unit.rpm")); - targetSpeed.withCallback(i -> this.updateTargetRotation()); targetSpeed.withStepFunction(CreativeMotorTileEntity::step); behaviours.add(targetSpeed); } + public float getTargetSpeed() { + return targetSpeed.getValue(); + } + private void updateTargetRotation() { if (hasNetwork()) getOrCreateNetwork().remove(this); @@ -123,7 +126,7 @@ public class SpeedControllerTileEntity extends KineticTileEntity { return true; } - private class ControllerValueBoxTransform extends ValueBoxTransform.Sided { + private static class ControllerValueBoxTransform extends ValueBoxTransform.Sided { @Override protected Vec3 getSouthLocation() { diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java index 7bb5e2c2d..5d6575675 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java @@ -2,7 +2,11 @@ package com.simibubi.create.content.contraptions.relays.elementary; import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity; + import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; @@ -23,4 +27,13 @@ public class SimpleKineticTileEntity extends KineticTileEntity { return false; } + @Override + public float getGeneratedSpeed() { + Block block = getBlockState().getBlock(); + BlockEntity below = level.getBlockEntity(getBlockPos().below()); + if (block instanceof ICogWheel cog && cog.isLargeCog() + && below instanceof SpeedControllerTileEntity controller && controller.getSpeed() != 0) + return controller.getTargetSpeed(); + return 0; + } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java b/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java index 0c61ca27e..a3e0833eb 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java @@ -11,6 +11,7 @@ import net.minecraft.core.Vec3i; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import static net.minecraft.world.level.block.state.properties.BlockStateProperties.AXIS; @@ -30,7 +31,8 @@ public class AllConnections { public static final LazyMap TYPE_SHAFT = ValueType.map(), TYPE_LARGE_COG = ValueType.map(), - TYPE_SMALL_COG = ValueType.map(); + TYPE_SMALL_COG = ValueType.map(), + TYPE_SPEED_CONTROLLER_TOP = ValueType.map(); private static Direction pos(Axis axis) { @@ -48,6 +50,14 @@ public class AllConnections { return new Entry(diff, TYPE_LARGE_COG.apply(from), TYPE_LARGE_COG.apply(to), ratio); } + private static Optional oppAxis(Axis axis) { + return switch (axis) { + case X -> Optional.of(Axis.Z); + case Z -> Optional.of(Axis.X); + default -> Optional.empty(); + }; + } + public static final KineticConnections EMPTY = new KineticConnections(); @@ -61,6 +71,7 @@ public class AllConnections { LARGE_COG = new LazyMap<>(axis -> { Type large = TYPE_LARGE_COG.apply(axis); Type small = TYPE_SMALL_COG.apply(axis); + List out = new LinkedList<>(); Direction cur = DirectionHelper.getPositivePerpendicular(axis); for (int i = 0; i < 4; i++) { @@ -70,12 +81,18 @@ public class AllConnections { out.add(new Entry(cur.getNormal().relative(next), large, small, -2)); cur = next; } + + oppAxis(axis).ifPresent(opp -> { + Type sc = TYPE_SPEED_CONTROLLER_TOP.apply(opp); + out.add(new Entry(Direction.DOWN.getNormal(), large, sc).stressOnly()); + }); return new KineticConnections(out); }), SMALL_COG = new LazyMap<>(axis -> { Type large = TYPE_LARGE_COG.apply(axis); Type small = TYPE_SMALL_COG.apply(axis); + List out = new LinkedList<>(); Direction cur = DirectionHelper.getPositivePerpendicular(axis); for (int i = 0; i < 4; i++) { @@ -84,11 +101,19 @@ public class AllConnections { out.add(new Entry(cur.getNormal().relative(next), small, large, -0.5f)); cur = next; } + return new KineticConnections(out); }), LARGE_COG_FULL_SHAFT = new LazyMap<>(axis -> LARGE_COG.apply(axis).merge(FULL_SHAFT.apply(axis))), - SMALL_COG_FULL_SHAFT = new LazyMap<>(axis -> SMALL_COG.apply(axis).merge(FULL_SHAFT.apply(axis))); + SMALL_COG_FULL_SHAFT = new LazyMap<>(axis -> SMALL_COG.apply(axis).merge(FULL_SHAFT.apply(axis))), + + SPEED_CONTROLLER = new LazyMap<>(axis -> { + Type sc = TYPE_SPEED_CONTROLLER_TOP.apply(axis); + Type large = TYPE_LARGE_COG.apply(oppAxis(axis).get()); + Vec3i up = Direction.UP.getNormal(); + return new KineticConnections(new Entry(up, sc, large).stressOnly()).merge(FULL_SHAFT.apply(axis)); + }); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java index 66b39c0c6..2174094a5 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java @@ -32,9 +32,16 @@ public class KineticConnections { public Entry(Vec3i offset, Type type) { this(offset, type, type, 1); } + public Entry stressOnly() { + return new Entry(offset, new Value(value.from, value.to, 0)); + } } - private static record Value(Type from, Type to, float ratio) { } + private static record Value(Type from, Type to, float ratio) { + public boolean isStressOnly() { + return ratio == 0; + } + } private final Map connections; @@ -65,12 +72,26 @@ public class KineticConnections { Value toValue = to.connections.get(offset.multiply(-1)); if (toValue == null) return Optional.empty(); + if (fromValue.isStressOnly() || toValue.isStressOnly()) return Optional.empty(); + if (fromValue.from.compatible(toValue.to) && fromValue.to.compatible(toValue.from) && (Mth.equal(fromValue.ratio, 1/toValue.ratio) || (Mth.equal(toValue.ratio, 1/fromValue.ratio)))) return Optional.of(fromValue.ratio); return Optional.empty(); } + public boolean checkStressOnlyConnection(KineticConnections to, Vec3i offset) { + Value fromValue = connections.get(offset); + if (fromValue == null) return false; + + Value toValue = to.connections.get(offset.multiply(-1)); + if (toValue == null) return false; + + if (!fromValue.isStressOnly() || !toValue.isStressOnly()) return false; + + return fromValue.from.compatible(toValue.to) && fromValue.to.compatible(toValue.from); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -91,5 +112,9 @@ public class KineticConnections { return new KineticConnections(out); } + public boolean hasStressOnlyConnections() { + return connections.values().stream().anyMatch(Value::isStressOnly); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java index 42577fe17..a4c16398e 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java @@ -2,6 +2,8 @@ package com.simibubi.create.content.contraptions.solver; import com.simibubi.create.foundation.utility.Pair; +import com.simibubi.create.foundation.utility.ResetableLazy; + import javax.annotation.Nullable; import java.util.HashSet; @@ -15,46 +17,71 @@ public class KineticNetwork { private final Set members = new HashSet<>(); private final Set generators = new HashSet<>(); private final Set> conflictingCycles = new HashSet<>(); + private float rootTheoreticalSpeed; - private @Nullable KineticNode mainGenerator; private boolean rootSpeedDirty; + private @Nullable KineticNode mainGenerator; + private boolean overstressed; - private float rootSpeedCur; - private float rootSpeedPrev; + private boolean rootSpeedChanged; private final Set potentialNewBranches = new HashSet<>(); + private final ResetableLazy totalStressImpact = ResetableLazy.of(() -> + (float) members.stream().mapToDouble(n -> n.getTotalStressImpact(rootTheoreticalSpeed)).sum()); + private final ResetableLazy totalStressCapacity = ResetableLazy.of(() -> + (float) members.stream().mapToDouble(KineticNode::getStressCapacity).sum()); + + private boolean ticked; + private final Set stressConnectors = new HashSet<>(); + public KineticNetwork(KineticNode root) { addMember(root); } public void addMember(KineticNode node) { members.add(node); + potentialNewBranches.add(node); + if (node.getConnections().hasStressOnlyConnections()) stressConnectors.add(node); + if (node.isGenerator() && !generators.contains(node)) { generators.add(node); rootSpeedDirty = true; + rootSpeedChanged = true; } - - potentialNewBranches.add(node); + if (node.hasStressImpact()) onMemberStressImpactUpdated(); + if (node.hasStressCapacity()) onMemberStressCapacityUpdated(); } - public void updateMember(KineticNode node) { - if (!members.contains(node)) throw new IllegalArgumentException(); - + public void onMemberGeneratedSpeedUpdated(KineticNode node) { if (node.isGenerator()) { generators.add(node); } else { generators.remove(node); } rootSpeedDirty = true; + rootSpeedChanged = true; + } + + public void onMemberStressImpactUpdated() { + totalStressImpact.reset(); + } + + public void onMemberStressCapacityUpdated() { + totalStressCapacity.reset(); } public void removeMember(KineticNode node) { - members.remove(node); if (node.isGenerator() && generators.contains(node)) { generators.remove(node); rootSpeedDirty = true; + rootSpeedChanged = true; } + if (node.hasStressImpact()) onMemberStressImpactUpdated(); + if (node.hasStressCapacity()) onMemberStressCapacityUpdated(); + + members.remove(node); + stressConnectors.remove(node); conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node); } @@ -105,73 +132,88 @@ public class KineticNetwork { } rootTheoreticalSpeed = newSpeed * sign; - if (!overstressed) { - rootSpeedCur = rootTheoreticalSpeed; - } - mainGenerator = newGenerator; rootSpeedDirty = false; return result; } - /** - * @return a List of new networks created during this function call - */ - public List tick() { - List newNetworks = updateMemberSpeeds(); + public float getTotalStressImpact() { + return totalStressImpact.get(); + } - if (generators.isEmpty()) { - overstressed = false; - members.forEach(KineticNode::stop); - members.forEach(KineticNode::flushChangedSpeed); - return newNetworks; + public float getTotalStressCapacity() { + return totalStressCapacity.get(); + } + + private float getRootSpeed() { + return isStopped() ? 0 : rootTheoreticalSpeed; + } + + public void untick() { + ticked = false; + } + + public void tick(List newNetworks) { + if (ticked) return; + + Set stressConnected = stressConnectors.stream() + .flatMap(KineticNode::getActiveStressOnlyConnections) + .collect(Collectors.toSet()); + stressConnected.add(this); + + float stressImpact = 0; + float stressCapacity = 0; + + for (KineticNetwork cur : stressConnected) { + cur.ticked = true; + cur.updateMemberSpeeds(newNetworks); + stressImpact += cur.getTotalStressImpact(); + stressCapacity += cur.getTotalStressCapacity(); } - float stressImpact = (float) members.stream().mapToDouble(n -> n.getTotalStressImpact(rootTheoreticalSpeed)).sum(); - float stressCapacity = (float) members.stream().mapToDouble(KineticNode::getStressCapacity).sum(); + boolean nowOverstressed = stressImpact > stressCapacity; - if (stressImpact > stressCapacity) { - if (!overstressed) { - overstressed = true; - rootSpeedCur = 0; - members.forEach(KineticNode::stop); - } - } else { - if (overstressed) { - overstressed = false; - rootSpeedCur = rootTheoreticalSpeed; - newNetworks.addAll(bulldozeContradictingMembers()); - newNetworks.addAll(updateMemberSpeeds()); + for (KineticNetwork cur : stressConnected) { + if (cur.generators.isEmpty()) { + cur.overstressed = false; + } else if (nowOverstressed) { + if (!cur.overstressed) { + cur.overstressed = true; + rootSpeedChanged = true; + cur.members.forEach(KineticNode::stop); + } + } else { + if (cur.overstressed) { + cur.overstressed = false; + rootSpeedChanged = true; + cur.bulldozeContradictingMembers(newNetworks); + cur.updateMemberSpeeds(newNetworks); + } } + + cur.members.forEach(KineticNode::flushChangedSpeed); } - - members.forEach(KineticNode::flushChangedSpeed); - return newNetworks; } /** * Update the speed of every member, starting from the main generator and popping off speeding nodes along the way - * @return a List of new networks created during this function call + * @param newNetworks a List that any new networks created during this call will be added to */ - private List updateMemberSpeeds() { - boolean rootSpeedChanged = rootSpeedPrev != rootSpeedCur; - rootSpeedPrev = rootSpeedCur; - + private void updateMemberSpeeds(List newNetworks) { // if we're stopped, then all members' speeds will be 0, so no need to check for speeding nodes if (isStopped()) { members.forEach(KineticNode::stop); - return new LinkedList<>(); + return; } SolveResult recalculateSpeedResult = tryRecalculateSpeed(); // generators should not be turning against each other or have conflicting cycles by now assert(recalculateSpeedResult.isOk()); - List newNetworks = new LinkedList<>(); - if (rootSpeedChanged) { // root speed changed, update all nodes starting from the main generator + rootSpeedChanged = false; bfs(mainGenerator, newNetworks, false); } else if (!potentialNewBranches.isEmpty()) { // new nodes added, update only the new network branches @@ -180,8 +222,6 @@ public class KineticNetwork { .forEach(n -> bfs(n, newNetworks, true)); potentialNewBranches.clear(); } - - return newNetworks; } private void bfs(KineticNode root, List newNetworks, boolean followSource) { @@ -195,7 +235,7 @@ public class KineticNetwork { if (!members.contains(cur) || visited.contains(cur)) continue; visited.add(cur); - if (cur.tryUpdateSpeed(rootSpeedCur).isOk()) { + if (cur.tryUpdateSpeed(getRootSpeed()).isOk()) { cur.getActiveConnections() .map(Pair::getFirst) .filter(n -> !followSource || n.getSource() == cur) @@ -208,7 +248,7 @@ public class KineticNetwork { } } - private List bulldozeContradictingMembers() { + private void bulldozeContradictingMembers(List newNetworks) { /* This method is necessary to handle the edge case where contradicting nodes have been added to the network while it was overstressed and now that it's moving again we need to pop them. Here we can't just stop following a @@ -216,10 +256,8 @@ public class KineticNetwork { just pop all potentially contradicting nodes off and hope no one cares */ - List newNetworks = new LinkedList<>(); - // generators running against network - float sign = Math.signum(rootSpeedCur); + float sign = Math.signum(rootTheoreticalSpeed); List runningAgainst = generators.stream() .filter(n -> Math.signum(n.getGeneratedSpeedAtRoot()) != sign) .collect(Collectors.toList()); @@ -230,8 +268,6 @@ public class KineticNetwork { .map(Pair::getFirst) .collect(Collectors.toList()); cycles.forEach(n -> { n.popBlock(); newNetworks.add(n.getNetwork()); }); - - return newNetworks; } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java index 0803dd3e2..64da7c544 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java @@ -39,8 +39,8 @@ public class KineticNode { this.connections = entity.getConnections(); this.generatedSpeed = entity.getGeneratedSpeed(); - this.stressCapacity = entity.getStressCapacity(); this.stressImpact = entity.getStressImpact(); + this.stressCapacity = entity.getStressCapacity(); this.network = new KineticNetwork(this); } @@ -70,6 +70,14 @@ public class KineticNode { return getActiveConnections().collect(Collectors.toList()); } + public Stream getActiveStressOnlyConnections() { + return connections.getDirections().stream() + .map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d)) + .filter(n -> connections.checkStressOnlyConnection(n.connections, d))) + .flatMap(Optional::stream) + .map(KineticNode::getNetwork); + } + public float getGeneratedSpeedAtRoot() { return generatedSpeed / speedRatio; } @@ -79,17 +87,34 @@ public class KineticNode { } public void onUpdated() { - float newSpeed = entity.getGeneratedSpeed(); - if (generatedSpeed != newSpeed) { - generatedSpeed = newSpeed; - network.updateMember(this); + float generatedSpeedNew = entity.getGeneratedSpeed(); + if (this.generatedSpeed != generatedSpeedNew) { + this.generatedSpeed = generatedSpeedNew; + network.onMemberGeneratedSpeedUpdated(this); if (network.tryRecalculateSpeed().isContradiction()) { popBlock(); } } - stressImpact = entity.getStressImpact(); - stressCapacity = entity.getStressCapacity(); + float stressImpactNew = entity.getStressImpact(); + if (this.stressImpact != stressImpactNew) { + this.stressImpact = stressImpactNew; + network.onMemberStressImpactUpdated(); + } + + float stressCapacityNew = entity.getStressCapacity(); + if (this.stressCapacity != stressCapacityNew) { + this.stressCapacity = stressCapacityNew; + network.onMemberStressCapacityUpdated(); + } + } + + public boolean hasStressCapacity() { + return stressCapacity != 0; + } + + public boolean hasStressImpact() { + return stressImpact != 0; } public float getTheoreticalSpeed(float speedAtRoot) { @@ -111,7 +136,7 @@ public class KineticNode { return network.tryRecalculateSpeed(); } - public KineticNode getSource() { + public @Nullable KineticNode getSource() { return source; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java index d44e30c13..814285be7 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java @@ -55,17 +55,16 @@ public class KineticSolver { } public void tick() { - Set visited = new HashSet<>(); + Set networks = nodes.values().stream().map(KineticNode::getNetwork).collect(Collectors.toSet()); + networks.forEach(KineticNetwork::untick); + List frontier = new LinkedList<>(); - Set networks = nodes.values().stream().map(KineticNode::getNetwork).collect(Collectors.toSet()); for (KineticNetwork network : networks) { frontier.add(network); while (!frontier.isEmpty()) { KineticNetwork cur = frontier.remove(0); - if (visited.contains(cur)) continue; - visited.add(cur); - frontier.addAll(cur.tick()); + cur.tick(frontier); } } }