diff --git a/src/main/java/com/simibubi/create/content/contraptions/base/IRotate.java b/src/main/java/com/simibubi/create/content/contraptions/base/IRotate.java index 8f405b615..1c0c5b8c1 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/base/IRotate.java +++ b/src/main/java/com/simibubi/create/content/contraptions/base/IRotate.java @@ -1,6 +1,8 @@ package com.simibubi.create.content.contraptions.base; import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.item.ItemDescription; @@ -101,7 +103,7 @@ public interface IRotate extends IWrenchable { else if (stressPercent > .5d) return StressImpact.MEDIUM; else return StressImpact.LOW; } - + public static boolean isEnabled() { return !AllConfigs.SERVER.kinetics.disableStress.get(); } @@ -119,19 +121,23 @@ public interface IRotate extends IWrenchable { } } - public boolean hasShaftTowards(LevelReader world, BlockPos pos, BlockState state, Direction face); + boolean hasShaftTowards(LevelReader world, BlockPos pos, BlockState state, Direction face); - public Axis getRotationAxis(BlockState state); + Axis getRotationAxis(BlockState state); - public default SpeedLevel getMinimumRequiredSpeedLevel() { + default KineticConnections getInitialConnections(BlockState state) { + return AllConnections.EMPTY; + } + + default SpeedLevel getMinimumRequiredSpeedLevel() { return SpeedLevel.NONE; } - public default boolean hideStressImpact() { + default boolean hideStressImpact() { return false; } - public default boolean showCapacityWithAnnotation() { + default boolean showCapacityWithAnnotation() { return false; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java index 252b6fd04..0fe7c74a0 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java @@ -11,15 +11,14 @@ import com.jozufozu.flywheel.api.FlywheelRendered; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.simibubi.create.Create; import com.simibubi.create.content.contraptions.KineticNetwork; -import com.simibubi.create.content.contraptions.RotationPropagator; import com.simibubi.create.content.contraptions.base.IRotate.SpeedLevel; import com.simibubi.create.content.contraptions.base.IRotate.StressImpact; import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation; import com.simibubi.create.content.contraptions.goggles.IHaveHoveringInformation; import com.simibubi.create.content.contraptions.relays.elementary.ICogWheel; import com.simibubi.create.content.contraptions.relays.gearbox.GearboxBlock; +import com.simibubi.create.content.contraptions.solver.AllConnections; import com.simibubi.create.content.contraptions.solver.KineticConnections; -import com.simibubi.create.content.contraptions.solver.KineticNodeState; import com.simibubi.create.content.contraptions.solver.KineticSolver; import com.simibubi.create.foundation.block.BlockStressValues; import com.simibubi.create.foundation.config.AllConfigs; @@ -37,7 +36,6 @@ import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; import net.minecraft.core.Direction.AxisDirection; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextComponent; import net.minecraft.util.Mth; @@ -70,18 +68,17 @@ public class KineticTileEntity extends SmartTileEntity protected float lastStressApplied; protected float lastCapacityProvided; - private KineticNodeState kineticNodeState; + private KineticConnections connections = AllConnections.EMPTY; - public KineticNodeState getKineticNodeState() { - return kineticNodeState; - } - - public KineticNodeState getInitialKineticNodeState() { - return new KineticNodeState(new KineticConnections(), 0); + public KineticTileEntity(BlockEntityType typeIn, BlockPos pos, BlockState state) { + super(typeIn, pos, state); + effects = new KineticEffectHandler(this); + if (state.getBlock() instanceof IRotate rotate) { + connections = rotate.getInitialConnections(state); + } } private void addToSolver() { - kineticNodeState = getInitialKineticNodeState(); KineticSolver.getSolver(level).addNode(this); } @@ -89,12 +86,31 @@ public class KineticTileEntity extends SmartTileEntity KineticSolver.getSolver(level).removeNode(this); } - - public KineticTileEntity(BlockEntityType typeIn, BlockPos pos, BlockState state) { - super(typeIn, pos, state); - effects = new KineticEffectHandler(this); + public KineticConnections getConnections() { + return connections; } + public float getGeneratedSpeed() { + return 0; + } + + public float getStressImpact() { + return getDefaultStressImpact(); + } + + public float getStressCapacity() { + return getDefaultStressCapacity(); + } + + public float getDefaultStressImpact() { + return (float) BlockStressValues.getImpact(getStressConfigKey()); + } + + public float getDefaultStressCapacity() { + return (float) BlockStressValues.getCapacity(getStressConfigKey()); + } + + @Override public void initialize() { if (!level.isClientSide) { @@ -276,10 +292,6 @@ public class KineticTileEntity extends SmartTileEntity DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> InstancedRenderDispatcher.enqueueUpdate(this)); } - public float getGeneratedSpeed() { - return 0; - } - public boolean isSource() { return getGeneratedSpeed() != 0; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java index 3bc5df474..1f9e3fc0d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java @@ -3,6 +3,8 @@ package com.simibubi.create.content.contraptions.components.motor; import com.simibubi.create.AllShapes; import com.simibubi.create.AllTileEntities; import com.simibubi.create.content.contraptions.base.DirectionalKineticBlock; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; import com.simibubi.create.content.contraptions.solver.KineticSolver; import com.simibubi.create.foundation.block.ITE; @@ -52,6 +54,11 @@ public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE Lang.translate("generic.unit.rpm")); - generatedSpeed.withCallback(i -> this.getKineticNodeState().setGeneratedSpeed(getGeneratedSpeed())); generatedSpeed.withStepFunction(CreativeMotorTileEntity::step); behaviours.add(generatedSpeed); } - @Override - public KineticNodeState getInitialKineticNodeState() { - return new KineticNodeState( - AllConnections.HALF_SHAFT.apply(getBlockState().getValue(CreativeMotorBlock.FACING)), - getGeneratedSpeed() - ); - } - @Override public void initialize() { super.initialize(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/turntable/TurntableBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/turntable/TurntableBlock.java index f4908cb45..5ddf1c8b9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/turntable/TurntableBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/turntable/TurntableBlock.java @@ -4,6 +4,8 @@ import com.simibubi.create.AllShapes; import com.simibubi.create.AllTileEntities; import com.simibubi.create.content.contraptions.base.KineticBlock; import com.simibubi.create.content.contraptions.base.KineticTileEntity; +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.VecHelper; @@ -98,16 +100,21 @@ public class TurntableBlock extends KineticBlock implements ITE getTileEntityClass() { return TurntableTileEntity.class; } - + @Override public BlockEntityType getTileEntityType() { return AllTileEntities.TURNTABLE.get(); } - + @Override public boolean isPathfindable(BlockState state, BlockGetter reader, BlockPos pos, PathComputationType type) { return false; diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java index 85345a7d8..6f02be7c7 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java @@ -35,7 +35,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel, ISimpleConnectable { +public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel { boolean isLarge; @@ -63,7 +63,7 @@ public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel, ISim } @Override - public KineticConnections getConnections(BlockState state) { + public KineticConnections getInitialConnections(BlockState state) { return (isLargeCog() ? AllConnections.LARGE_COG_FULL_SHAFT : AllConnections.SMALL_COG_FULL_SHAFT) .apply(state.getValue(AXIS)); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ISimpleConnectable.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ISimpleConnectable.java deleted file mode 100644 index f927ac670..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ISimpleConnectable.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.simibubi.create.content.contraptions.relays.elementary; - -import com.simibubi.create.content.contraptions.solver.KineticConnections; - -import net.minecraft.world.level.block.state.BlockState; - -public interface ISimpleConnectable { - KineticConnections getConnections(BlockState state); -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java index 706dc0789..6e1246bcf 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java @@ -28,7 +28,7 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class ShaftBlock extends AbstractShaftBlock implements ISimpleConnectable { +public class ShaftBlock extends AbstractShaftBlock { private static final int placementHelperId = PlacementHelpers.register(new PlacementHelper()); @@ -41,7 +41,7 @@ public class ShaftBlock extends AbstractShaftBlock implements ISimpleConnectable } @Override - public KineticConnections getConnections(BlockState state) { + public KineticConnections getInitialConnections(BlockState state) { return AllConnections.FULL_SHAFT.apply(state.getValue(AXIS)); } 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 414886ef9..7bb5e2c2d 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,11 +2,6 @@ package com.simibubi.create.content.contraptions.relays.elementary; import com.simibubi.create.content.contraptions.base.KineticTileEntity; -import com.simibubi.create.content.contraptions.base.RotatedPillarKineticBlock; -import com.simibubi.create.content.contraptions.solver.AllConnections; -import com.simibubi.create.content.contraptions.solver.KineticConnections; -import com.simibubi.create.content.contraptions.solver.KineticNodeState; - import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -28,14 +23,4 @@ public class SimpleKineticTileEntity extends KineticTileEntity { return false; } - @Override - public KineticNodeState getInitialKineticNodeState() { - KineticConnections connections = AllConnections.EMPTY; - BlockState state = getBlockState(); - if (state.getBlock() instanceof ISimpleConnectable connectable) { - connections = connectable.getConnections(state); - } - return new KineticNodeState(connections, 0); - } - } 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 d5dacf453..eacc9d523 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 @@ -8,6 +8,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; public class KineticNetwork { @@ -17,6 +18,7 @@ public class KineticNetwork { private float rootSpeed; private @Nullable KineticNode mainGenerator; private boolean speedDirty; + private boolean overstressed; public KineticNetwork(KineticNode root) { addMember(root); @@ -55,19 +57,19 @@ public class KineticNetwork { conflictingCycles.add(Pair.of(from, to)); } - public float getRootSpeed() { - return rootSpeed; - } - - public boolean isStopped() { return generators.isEmpty(); } + public boolean isStopped() { return generators.isEmpty() || overstressed; } /** * Recalculates the speed at the root node of this network. - * @return CONTRADICTION if the network has generators turning against each other, and OK otherwise + * @return CONTRADICTION if the network has cycles with conflicting speed ratios or generators turning against + * each other, and OK otherwise */ - public SolveResult recalculateSpeed() { + public SolveResult tryRecalculateSpeed() { + if (!conflictingCycles.isEmpty() && !isStopped()) return SolveResult.CONTRADICTION; if (!speedDirty) return SolveResult.OK; + SolveResult result = SolveResult.OK; + float newSpeed = 0; KineticNode newGenerator = null; float sign = 0; @@ -82,7 +84,8 @@ public class KineticNetwork { if (Math.signum(speedAtRoot) != sign) { // generators are turning against each other - return SolveResult.CONTRADICTION; + result = SolveResult.CONTRADICTION; + continue; } if (newSpeed < speedAtRoot * sign) { @@ -93,7 +96,9 @@ public class KineticNetwork { rootSpeed = newSpeed * sign; mainGenerator = newGenerator; speedDirty = false; - return SolveResult.OK; + + if (overstressed) return SolveResult.OK; + return result; } /** @@ -101,23 +106,42 @@ public class KineticNetwork { */ public List tick() { List newNetworks = updateMemberSpeeds(); + + if (generators.isEmpty()) { + overstressed = false; + return newNetworks; + } + + float stressImpact = (float) members.stream().mapToDouble(n -> n.getTotalStressImpact(rootSpeed)).sum(); + float stressCapacity = (float) members.stream().mapToDouble(KineticNode::getStressCapacity).sum(); + + if (stressImpact > stressCapacity) { + if (!overstressed) { + overstressed = true; + members.forEach(KineticNode::stop); + } + } else { + if (overstressed) { + overstressed = false; + newNetworks.addAll(bulldozeContradictingMembers()); + newNetworks.addAll(updateMemberSpeeds()); + } + } + members.forEach(KineticNode::flushChangedSpeed); return newNetworks; } private List updateMemberSpeeds() { - SolveResult recalculateSpeedResult = recalculateSpeed(); - // generators should not be turning against each other by now - assert(recalculateSpeedResult.isOk()); - - // if we're stopped then all members' speeds will be 0, so no need to check for speeding nodes + // if we're stopped, then all members' speeds will be 0, so no need to check for speeding nodes if (isStopped()) { - members.forEach(KineticNode::tryUpdateSpeed); - return List.of(); + members.forEach(KineticNode::stop); + return new LinkedList<>(); } - // there should be no cycles with conflicting speed ratios by now - assert(conflictingCycles.isEmpty()); + SolveResult recalculateSpeedResult = tryRecalculateSpeed(); + // generators should not be turning against each other or have conflicting cycles by now + assert(recalculateSpeedResult.isOk()); // update node speeds in a breadth-first order, checking for speeding nodes along the way List newNetworks = new LinkedList<>(); @@ -128,7 +152,7 @@ public class KineticNetwork { while (!frontier.isEmpty()) { KineticNode cur = frontier.remove(0); visited.add(cur); - if (cur.tryUpdateSpeed().isOk()) { + if (cur.tryUpdateSpeed(rootSpeed).isOk()) { cur.getActiveConnections() .map(Pair::getFirst) .filter(n -> !visited.contains(n)) @@ -143,4 +167,23 @@ public class KineticNetwork { return newNetworks; } + private List bulldozeContradictingMembers() { + List newNetworks = new LinkedList<>(); + + // generators running against network + float sign = Math.signum(rootSpeed); + List runningAgainst = generators.stream() + .filter(n -> Math.signum(n.getGeneratedSpeedAtRoot()) != sign) + .collect(Collectors.toList()); + runningAgainst.forEach(n -> { n.onPopBlock(); newNetworks.add(n.getNetwork()); }); + + // conflicting cycles + List cycles = conflictingCycles.stream() + .map(Pair::getFirst) + .collect(Collectors.toList()); + cycles.forEach(n -> { n.onPopBlock(); 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 0471b38f1..780a31fad 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 @@ -27,6 +27,8 @@ public class KineticNode { private final KineticConnections connections; private float generatedSpeed; + private float stressCapacity; + private float stressImpact; private float speedCur; private float speedNext; @@ -35,9 +37,10 @@ public class KineticNode { this.nodeAccessor = nodeAccessor; this.entity = entity; - KineticNodeState state = entity.getKineticNodeState(); - this.connections = state.getConnections(); - this.generatedSpeed = state.getGeneratedSpeed(); + this.connections = entity.getConnections(); + this.generatedSpeed = entity.getGeneratedSpeed(); + this.stressCapacity = entity.getStressCapacity(); + this.stressImpact = entity.getStressImpact(); this.network = new KineticNetwork(this); } @@ -67,10 +70,6 @@ public class KineticNode { return getActiveConnections().collect(Collectors.toList()); } - public float getGeneratedSpeed() { - return generatedSpeed; - } - public float getGeneratedSpeedAtRoot() { return generatedSpeed / speedRatio; } @@ -79,20 +78,37 @@ public class KineticNode { return generatedSpeed != 0; } - public void setGeneratedSpeed(float newSpeed) { - if (generatedSpeed == newSpeed) return; - generatedSpeed = newSpeed; - network.updateMember(this); - if (network.recalculateSpeed().isContradiction()) { - onPopBlock(); + public void onUpdated() { + float newSpeed = entity.getGeneratedSpeed(); + if (generatedSpeed != newSpeed) { + generatedSpeed = newSpeed; + network.updateMember(this); + if (network.tryRecalculateSpeed().isContradiction()) { + onPopBlock(); + } } + + stressImpact = entity.getStressImpact(); + stressCapacity = entity.getStressCapacity(); + } + + public float getTheoreticalSpeed(float speedAtRoot) { + return speedAtRoot * speedRatio; + } + + public float getStressCapacity() { + return Math.abs(stressCapacity * generatedSpeed); + } + + public float getTotalStressImpact(float speedAtRoot) { + return Math.abs(stressImpact * getTheoreticalSpeed(speedAtRoot)); } private SolveResult setNetwork(KineticNetwork network) { this.network.removeMember(this); this.network = network; network.addMember(this); - return network.recalculateSpeed(); + return network.tryRecalculateSpeed(); } private SolveResult setSource(KineticNode from, float ratio) { @@ -145,7 +161,7 @@ public class KineticNode { if (next.setSource(cur, ratio).isOk()) { frontier.add(next); } else { - // this node will run against the network + // this node will run against the network or activate a conflicting cycle onPopBlock(); return; } @@ -164,21 +180,27 @@ public class KineticNode { private void rerootHere() { source = null; speedRatio = 1; - setNetwork(new KineticNetwork(this)); + SolveResult recalculateSpeedResult = setNetwork(new KineticNetwork(this)); + assert(recalculateSpeedResult.isOk()); propagateSource(); } /** * Updates the speed of this node based on its network's root speed and its own speed ratio. - * @return CONTRADICTION if the node's new speed exceeds the maximum value, and OK otherwise + * @param speedAtRoot Current speed at the root of this node's network + * @return CONTRADICTION if the node's new speed exceeds the maximum value, and OK otherwise */ - protected SolveResult tryUpdateSpeed() { - speedNext = network.getRootSpeed() * speedRatio; + protected SolveResult tryUpdateSpeed(float speedAtRoot) { + speedNext = getTheoreticalSpeed(speedAtRoot); if (Math.abs(speedNext) > AllConfigs.SERVER.kinetics.maxRotationSpeed.get()) return SolveResult.CONTRADICTION; return SolveResult.OK; } + protected void stop() { + speedNext = 0; + } + public void flushChangedSpeed() { if (speedCur != speedNext) { speedCur = speedNext; diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNodeState.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNodeState.java deleted file mode 100644 index 2aa195b1d..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNodeState.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -public class KineticNodeState { - private KineticConnections connections; - private float generatedSpeed; - - public KineticNodeState(KineticConnections connections, float generatedSpeed) { - this.connections = connections; - this.generatedSpeed = generatedSpeed; - } - - public KineticConnections getConnections() { - return connections; - } - - public float getGeneratedSpeed() { - return generatedSpeed; - } - - public void setConnections(KineticConnections connections) { - this.connections = connections; - } - - public void setGeneratedSpeed(float generatedSpeed) { - this.generatedSpeed = generatedSpeed; - } -} 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 bd9f0ed8e..d44e30c13 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 @@ -34,15 +34,14 @@ public class KineticSolver { public void updateNode(KineticTileEntity entity) { KineticNode node = nodes.get(entity.getBlockPos()); - KineticNodeState state = entity.getKineticNodeState(); - if (!node.getConnections().equals(state.getConnections())) { + if (!node.getConnections().equals(entity.getConnections())) { // connections changed, so things could've been disconnected removeNode(entity); addNode(entity); } else { - // connections are the same, so just set speed in case it changed - node.setGeneratedSpeed(state.getGeneratedSpeed()); + // connections are the same, so just update in case other properties changed + node.onUpdated(); } }