Too stressed out

This commit is contained in:
reidbhuntley 2021-12-29 23:42:09 -05:00
parent 5c5e535551
commit cc0d7f402d
13 changed files with 169 additions and 135 deletions

View file

@ -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;
@ -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;
}

View file

@ -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;
}

View file

@ -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<C
.getAxis();
}
@Override
public KineticConnections getInitialConnections(BlockState state) {
return AllConnections.HALF_SHAFT.apply(state.getValue(FACING));
}
@Override
public boolean hideStressImpact() {
return true;

View file

@ -4,8 +4,6 @@ import java.util.List;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.base.GeneratingKineticTileEntity;
import com.simibubi.create.content.contraptions.solver.AllConnections;
import com.simibubi.create.content.contraptions.solver.KineticNodeState;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.CenteredSideValueBoxTransform;
@ -39,19 +37,10 @@ public class CreativeMotorTileEntity extends GeneratingKineticTileEntity {
generatedSpeed.value = DEFAULT_SPEED;
generatedSpeed.scrollableValue = DEFAULT_SPEED;
generatedSpeed.withUnit(i -> 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();

View file

@ -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,6 +100,11 @@ public class TurntableBlock extends KineticBlock implements ITE<TurntableTileEnt
return Axis.Y;
}
@Override
public KineticConnections getInitialConnections(BlockState state) {
return AllConnections.HALF_SHAFT.apply(Direction.DOWN);
}
@Override
public Class<TurntableTileEntity> getTileEntityClass() {
return TurntableTileEntity.class;

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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<KineticNetwork> tick() {
List<KineticNetwork> 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<KineticNetwork> 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<KineticNetwork> 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<KineticNetwork> bulldozeContradictingMembers() {
List<KineticNetwork> newNetworks = new LinkedList<>();
// generators running against network
float sign = Math.signum(rootSpeed);
List<KineticNode> 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<KineticNode> cycles = conflictingCycles.stream()
.map(Pair::getFirst)
.collect(Collectors.toList());
cycles.forEach(n -> { n.onPopBlock(); newNetworks.add(n.getNetwork()); });
return newNetworks;
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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();
}
}