diff --git a/src/main/java/com/simibubi/create/content/contraptions/KineticNetwork.java b/src/main/java/com/simibubi/create/content/contraptions/KineticNetwork.java index af9076cb6..4701657f2 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/KineticNetwork.java +++ b/src/main/java/com/simibubi/create/content/contraptions/KineticNetwork.java @@ -67,7 +67,7 @@ public class KineticNetwork { sources.put(te, te.calculateAddedStressCapacity()); members.put(te, te.calculateStressApplied()); updateFromNetwork(te); - te.networkDirty = true; + //te.networkDirty = true; } public void updateCapacityFor(KineticTileEntity te, float capacity) { @@ -94,10 +94,10 @@ public class KineticNetwork { return; } - members.keySet() - .stream() - .findFirst() - .map(member -> member.networkDirty = true); +// members.keySet() +// .stream() +// .findFirst() +// .map(member -> member.networkDirty = true); } public void sync() { diff --git a/src/main/java/com/simibubi/create/content/contraptions/base/KineticBlock.java b/src/main/java/com/simibubi/create/content/contraptions/base/KineticBlock.java index b3cf6410e..888071cf8 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/base/KineticBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/base/KineticBlock.java @@ -31,7 +31,7 @@ public abstract class KineticBlock extends Block implements IRotate { BlockEntity tileEntity = worldIn.getBlockEntity(pos); if (tileEntity instanceof KineticTileEntity) { KineticTileEntity kineticTileEntity = (KineticTileEntity) tileEntity; - kineticTileEntity.preventSpeedUpdate = 0; + //kineticTileEntity.preventSpeedUpdate = 0; if (oldState.getBlock() != state.getBlock()) return; @@ -40,7 +40,7 @@ public abstract class KineticBlock extends Block implements IRotate { if (!areStatesKineticallyEquivalent(oldState, state)) return; - kineticTileEntity.preventSpeedUpdate = 2; + //kineticTileEntity.preventSpeedUpdate = 2; } } @@ -65,15 +65,15 @@ public abstract class KineticBlock extends Block implements IRotate { return; KineticTileEntity kte = (KineticTileEntity) tileEntity; - if (kte.preventSpeedUpdate > 0) { - kte.preventSpeedUpdate--; - return; - } +// if (kte.preventSpeedUpdate > 0) { +// kte.preventSpeedUpdate--; +// return; +// } // Remove previous information when block is added kte.warnOfMovement(); kte.clearKineticInformation(); - kte.updateSpeed = true; + // kte.updateSpeed = true; } @Override 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 6b8af4910..9a10d5eba 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 @@ -54,11 +54,8 @@ import net.minecraftforge.fml.DistExecutor; public class KineticTileEntity extends SmartTileEntity implements IHaveGoggleInformation, IHaveHoveringInformation, FlywheelRendered { - public @Nullable Long network; - public @Nullable BlockPos source; - public boolean networkDirty; - public boolean updateSpeed; - public int preventSpeedUpdate; + public @Nullable Long network = null; + public @Nullable BlockPos source = null; protected KineticEffectHandler effects; protected float speed; @@ -96,18 +93,10 @@ public class KineticTileEntity extends SmartTileEntity public KineticTileEntity(BlockEntityType typeIn, BlockPos pos, BlockState state) { super(typeIn, pos, state); effects = new KineticEffectHandler(this); - updateSpeed = true; } @Override public void initialize() { - if (hasNetwork() && !level.isClientSide) { - KineticNetwork network = getOrCreateNetwork(); - if (!network.initialized) - network.initFromTE(capacity, stress, networkSize); - network.addSilently(this, lastCapacityProvided, lastStressApplied); - } - if (!level.isClientSide) { addToSolver(); } @@ -117,9 +106,6 @@ public class KineticTileEntity extends SmartTileEntity @Override public void tick() { - if (!level.isClientSide && needsSpeedUpdate()) - attachKinetics(); - super.tick(); effects.tick(); @@ -140,55 +126,49 @@ public class KineticTileEntity extends SmartTileEntity if (getFlickerScore() > 0) flickerTally = getFlickerScore() - 1; - - if (networkDirty) { - if (hasNetwork()) - getOrCreateNetwork().updateNetwork(); - networkDirty = false; - } } private void validateKinetics() { - if (hasSource()) { - if (!hasNetwork()) { - removeSource(); - return; - } - - if (!level.isLoaded(source)) - return; - - BlockEntity tileEntity = level.getBlockEntity(source); - KineticTileEntity sourceTe = - tileEntity instanceof KineticTileEntity ? (KineticTileEntity) tileEntity : null; - if (sourceTe == null || sourceTe.speed == 0) { - removeSource(); - detachKinetics(); - return; - } - - return; - } - - if (speed != 0) { - if (getGeneratedSpeed() == 0) - speed = 0; - } +// if (hasSource()) { +// if (!hasNetwork()) { +// removeSource(); +// return; +// } +// +// if (!level.isLoaded(source)) +// return; +// +// BlockEntity tileEntity = level.getBlockEntity(source); +// KineticTileEntity sourceTe = +// tileEntity instanceof KineticTileEntity ? (KineticTileEntity) tileEntity : null; +// if (sourceTe == null || sourceTe.speed == 0) { +// removeSource(); +// detachKinetics(); +// return; +// } +// +// return; +// } +// +// if (speed != 0) { +// if (getGeneratedSpeed() == 0) +// speed = 0; +// } } public void updateFromNetwork(float maxStress, float currentStress, int networkSize) { - networkDirty = false; - this.capacity = maxStress; - this.stress = currentStress; - this.networkSize = networkSize; - boolean overStressed = maxStress < currentStress && StressImpact.isEnabled(); - - if (overStressed != this.overStressed) { - float prevSpeed = getSpeed(); - this.overStressed = overStressed; - onSpeedChanged(prevSpeed); - sendData(); - } +// networkDirty = false; +// this.capacity = maxStress; +// this.stress = currentStress; +// this.networkSize = networkSize; +// boolean overStressed = maxStress < currentStress && StressImpact.isEnabled(); +// +// if (overStressed != this.overStressed) { +// float prevSpeed = getSpeed(); +// this.overStressed = overStressed; +// onSpeedChanged(prevSpeed); +// sendData(); +// } } protected Block getStressConfigKey() { @@ -222,9 +202,6 @@ public class KineticTileEntity extends SmartTileEntity @Override protected void setRemovedNotDueToChunkUnload() { if (!level.isClientSide) { - if (hasNetwork()) - getOrCreateNetwork().remove(this); - detachKinetics(); removeFromSolver(); } super.setRemovedNotDueToChunkUnload(); @@ -234,33 +211,33 @@ public class KineticTileEntity extends SmartTileEntity protected void write(CompoundTag compound, boolean clientPacket) { compound.putFloat("Speed", speed); - if (needsSpeedUpdate()) - compound.putBoolean("NeedsSpeedUpdate", true); +// if (needsSpeedUpdate()) +// compound.putBoolean("NeedsSpeedUpdate", true); +// +// if (hasSource()) +// compound.put("Source", NbtUtils.writeBlockPos(source)); - if (hasSource()) - compound.put("Source", NbtUtils.writeBlockPos(source)); - - if (hasNetwork()) { - CompoundTag networkTag = new CompoundTag(); - networkTag.putLong("Id", this.network); - networkTag.putFloat("Stress", stress); - networkTag.putFloat("Capacity", capacity); - networkTag.putInt("Size", networkSize); - - if (lastStressApplied != 0) - networkTag.putFloat("AddedStress", lastStressApplied); - if (lastCapacityProvided != 0) - networkTag.putFloat("AddedCapacity", lastCapacityProvided); - - compound.put("Network", networkTag); - } +// if (hasNetwork()) { +// CompoundTag networkTag = new CompoundTag(); +// //networkTag.putLong("Id", this.network); +// networkTag.putFloat("Stress", stress); +// networkTag.putFloat("Capacity", capacity); +// networkTag.putInt("Size", networkSize); +// +// if (lastStressApplied != 0) +// networkTag.putFloat("AddedStress", lastStressApplied); +// if (lastCapacityProvided != 0) +// networkTag.putFloat("AddedCapacity", lastCapacityProvided); +// +// compound.put("Network", networkTag); +// } super.write(compound, clientPacket); } - public boolean needsSpeedUpdate() { - return updateSpeed; - } +// public boolean needsSpeedUpdate() { +// return updateSpeed; +// } @Override protected void read(CompoundTag compound, boolean clientPacket) { @@ -275,19 +252,19 @@ public class KineticTileEntity extends SmartTileEntity speed = compound.getFloat("Speed"); - if (compound.contains("Source")) - source = NbtUtils.readBlockPos(compound.getCompound("Source")); +// if (compound.contains("Source")) +// source = NbtUtils.readBlockPos(compound.getCompound("Source")); - if (compound.contains("Network")) { - CompoundTag networkTag = compound.getCompound("Network"); - network = networkTag.getLong("Id"); - stress = networkTag.getFloat("Stress"); - capacity = networkTag.getFloat("Capacity"); - networkSize = networkTag.getInt("Size"); - lastStressApplied = networkTag.getFloat("AddedStress"); - lastCapacityProvided = networkTag.getFloat("AddedCapacity"); - overStressed = capacity < stress && StressImpact.isEnabled(); - } +// if (compound.contains("Network")) { +// CompoundTag networkTag = compound.getCompound("Network"); +// network = networkTag.getLong("Id"); +// stress = networkTag.getFloat("Stress"); +// capacity = networkTag.getFloat("Capacity"); +// networkSize = networkTag.getInt("Size"); +// lastStressApplied = networkTag.getFloat("AddedStress"); +// lastCapacityProvided = networkTag.getFloat("AddedCapacity"); +// overStressed = capacity < stress && StressImpact.isEnabled(); +// } super.read(compound, clientPacket); @@ -325,45 +302,45 @@ public class KineticTileEntity extends SmartTileEntity } public void setSource(BlockPos source) { - this.source = source; - if (level == null || level.isClientSide) - return; - - BlockEntity tileEntity = level.getBlockEntity(source); - if (!(tileEntity instanceof KineticTileEntity)) { - removeSource(); - return; - } - - KineticTileEntity sourceTe = (KineticTileEntity) tileEntity; - setNetwork(sourceTe.network); +// this.source = source; +// if (level == null || level.isClientSide) +// return; +// +// BlockEntity tileEntity = level.getBlockEntity(source); +// if (!(tileEntity instanceof KineticTileEntity)) { +// removeSource(); +// return; +// } +// +// KineticTileEntity sourceTe = (KineticTileEntity) tileEntity; +// setNetwork(sourceTe.network); } public void removeSource() { - float prevSpeed = getSpeed(); - - speed = 0; - source = null; - setNetwork(null); - - onSpeedChanged(prevSpeed); +// float prevSpeed = getSpeed(); +// +// speed = 0; +// source = null; +// setNetwork(null); +// +// onSpeedChanged(prevSpeed); } public void setNetwork(@Nullable Long networkIn) { - if (network == networkIn) - return; - if (network != null) - getOrCreateNetwork().remove(this); - - network = networkIn; - - if (networkIn == null) - return; - - network = networkIn; - KineticNetwork network = getOrCreateNetwork(); - network.initialized = true; - network.add(this); +// if (network == networkIn) +// return; +// if (network != null) +// getOrCreateNetwork().remove(this); +// +// network = networkIn; +// +// if (networkIn == null) +// return; +// +// network = networkIn; +// KineticNetwork network = getOrCreateNetwork(); +// network.initialized = true; +// network.add(this); } public KineticNetwork getOrCreateNetwork() { @@ -375,12 +352,12 @@ public class KineticTileEntity extends SmartTileEntity } public void attachKinetics() { - updateSpeed = false; - RotationPropagator.handleAdded(level, worldPosition, this); + //updateSpeed = false; + //RotationPropagator.handleAdded(level, worldPosition, this); } public void detachKinetics() { - RotationPropagator.handleRemoved(level, worldPosition, this); + //RotationPropagator.handleRemoved(level, worldPosition, this); } public boolean isSpeedRequirementFulfilled() { @@ -413,15 +390,15 @@ public class KineticTileEntity extends SmartTileEntity return; } - KineticTileEntity tileEntity = (KineticTileEntity) tileEntityIn; - if (state.getBlock() instanceof KineticBlock - && !((KineticBlock) state.getBlock()).areStatesKineticallyEquivalent(currentState, state)) { - if (tileEntity.hasNetwork()) - tileEntity.getOrCreateNetwork() - .remove(tileEntity); - tileEntity.detachKinetics(); - tileEntity.removeSource(); - } +// KineticTileEntity tileEntity = (KineticTileEntity) tileEntityIn; +// if (state.getBlock() instanceof KineticBlock +// && !((KineticBlock) state.getBlock()).areStatesKineticallyEquivalent(currentState, state)) { +// if (tileEntity.hasNetwork()) +// tileEntity.getOrCreateNetwork() +// .remove(tileEntity); +// tileEntity.detachKinetics(); +// tileEntity.removeSource(); +// } world.setBlock(pos, state, 3); } @@ -492,8 +469,6 @@ public class KineticTileEntity extends SmartTileEntity public void clearKineticInformation() { speed = 0; - source = null; - network = null; overStressed = false; stress = 0; capacity = 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 43ee75207..f0419405d 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,7 +2,13 @@ package com.simibubi.create.content.contraptions.solver; import com.simibubi.create.foundation.utility.Pair; +import net.minecraft.util.Mth; + +import javax.annotation.Nullable; + import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; public class KineticNetwork { @@ -11,19 +17,16 @@ public class KineticNetwork { private final Set generators = new HashSet<>(); private final Set> conflictingCycles = new HashSet<>(); private float rootSpeed; - private boolean speedDirty; public KineticNetwork(KineticNode root) { addMember(root); rootSpeed = root.getGeneratedSpeed(); - speedDirty = false; } public void addMember(KineticNode node) { members.add(node); - if (node.isGenerator() && !generators.contains(node)) { + if (node.isGenerator()) { generators.add(node); - speedDirty = true; } } @@ -34,14 +37,12 @@ public class KineticNetwork { } else { generators.remove(node); } - speedDirty = true; } public void removeMember(KineticNode node) { members.remove(node); - if (node.isGenerator() && generators.contains(node)) { + if (node.isGenerator()) { generators.remove(node); - speedDirty = true; } conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node); } @@ -55,34 +56,70 @@ public class KineticNetwork { return rootSpeed; } - public SolveResult recalculateSpeed() { - if (!conflictingCycles.isEmpty() && !generators.isEmpty()) return SolveResult.CONTRADICTION; - if (!speedDirty) return SolveResult.OK; + /** + * Recalculates the speed at the root of this network, and if it has changed, recalculates the speed of all + * KineticNodes in the network and pops any nodes whose speed has increased above the speed limit. + * @param checkRoot Node to start performing a breadth-first from in order to find and pop speeding nodes. If null, + * speeding nodes are ignored. + * @param forced If true, will check for speeding nodes from checkRoot even if the root speed has not changed. + * @return CONTRADICTION if the network currently has a cycle with conflicting speed ratios or + * has generators turning against each other, and OK otherwise. + */ + public SolveResult recalculateSpeed(@Nullable KineticNode checkRoot, boolean forced) { + if (!conflictingCycles.isEmpty() && !generators.isEmpty()) { + // cycle with conflicting speed ratios is present + return SolveResult.CONTRADICTION; + } + // find the generator that would maximize the root speed float newSpeed = 0; float sign = 0; for (KineticNode generator : generators) { float speedAtRoot = generator.getGeneratedSpeedAtRoot(); if (newSpeed == 0) { sign = Math.signum(speedAtRoot); - newSpeed = sign * speedAtRoot; - } else { - if (Math.signum(speedAtRoot) != sign) - return SolveResult.CONTRADICTION; - newSpeed = Math.max(newSpeed, sign * speedAtRoot); + } else if (Math.signum(speedAtRoot) != sign) { + // generators are turning against each other + return SolveResult.CONTRADICTION; } + newSpeed = Math.max(newSpeed, sign * speedAtRoot); } newSpeed *= sign; - if (rootSpeed != newSpeed) { + if (!Mth.equal(rootSpeed, newSpeed)) { rootSpeed = newSpeed; - for (KineticNode member : members) { - member.onSpeedUpdated(); + + if (checkRoot == null) { + members.forEach(KineticNode::tryUpdateSpeed); + } else { + updateNodeSpeeds(checkRoot, false); } + } else if (forced) { + updateNodeSpeeds(checkRoot, true); } - speedDirty = false; return SolveResult.OK; } + private void updateNodeSpeeds(KineticNode root, boolean followSources) { + Set visited = new HashSet<>(); + List frontier = new LinkedList<>(); + frontier.add(root); + + // update node speeds in a breadth-first order, starting at root + while (!frontier.isEmpty()) { + KineticNode cur = frontier.remove(0); + visited.add(cur); + if (cur.tryUpdateSpeed().isOk()) { + for (KineticNode next : cur.getActiveConnections().keySet()) { + if (!(visited.contains(next) || (followSources && !cur.isSourceOf(next)))) + frontier.add(next); + } + } else { + // stop searching on this branch once a speeding node is found + cur.onPopBlock(); + } + } + } + } 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 de79369fe..b2d2c038f 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 @@ -1,9 +1,11 @@ package com.simibubi.create.content.contraptions.solver; import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.utility.Pair; import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; import javax.annotation.Nullable; @@ -38,13 +40,16 @@ public class KineticNode { this.generatedSpeed = state.getGeneratedSpeed(); this.network = new KineticNetwork(this); - onSpeedUpdated(); } public KineticConnections getConnections() { return connections; } + /** + * @return a map where the keys are every node with a compatible connection to this node, and the values are the + * speed ratios of those connections + */ public Map getActiveConnections() { return connections.getDirections().stream() .map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d)) @@ -68,9 +73,10 @@ public class KineticNode { } public void setGeneratedSpeed(float newSpeed) { + if (Mth.equal(generatedSpeed, newSpeed)) return; generatedSpeed = newSpeed; network.updateMember(this); - if (network.recalculateSpeed().isContradiction()) + if (network.recalculateSpeed(this, false).isContradiction()) onPopBlock(); } @@ -78,7 +84,6 @@ public class KineticNode { this.network.removeMember(this); this.network = network; network.addMember(this); - onSpeedUpdated(); } private void setSource(KineticNode from, float ratio) { @@ -93,21 +98,18 @@ public class KineticNode { .stream() .findAny() .ifPresent(n -> { - if (n.propagateSource().isContradiction()) + if (n.propagateSource(this).isContradiction()) onPopBlock(); }); } - public void onRemoved() { - network.removeMember(this); - for (KineticNode neighbor : getActiveConnections().keySet()) { - if (neighbor.source != this) continue; - neighbor.rerootHere(); - } - network.recalculateSpeed(); - } - - private SolveResult propagateSource() { + /** + * Propagates this node's source and network to any connected nodes that aren't yet part of the same network, then + * repeats this recursively with the connected nodes in a breadth-first order. + * @param checkRoot Node to start searching from when looking for nodes that started speeding because of this call + * @return whether or not this propagation caused a contradiction in the kinetic network + */ + private SolveResult propagateSource(KineticNode checkRoot) { List frontier = new LinkedList<>(); frontier.add(this); @@ -116,30 +118,53 @@ public class KineticNode { for (Map.Entry entry : cur.getActiveConnections().entrySet()) { KineticNode next = entry.getKey(); float ratio = entry.getValue(); + if (next == cur.source) continue; if (next.network == network) { - if (next.speedRatio != cur.speedRatio * ratio) { + if (!Mth.equal(next.speedRatio, cur.speedRatio * ratio)) { + // we found a cycle with conflicting speed ratios network.markConflictingCycle(cur, next); } continue; } + next.setSource(cur, ratio); frontier.add(next); } } - return network.recalculateSpeed(); + return network.recalculateSpeed(checkRoot, true); + } + + public void onRemoved() { + network.removeMember(this); + for (KineticNode neighbor : getActiveConnections().keySet()) { + if (neighbor.source != this) continue; + neighbor.rerootHere(); + } + network.recalculateSpeed(null, false); } private void rerootHere() { source = null; speedRatio = 1; setNetwork(new KineticNetwork(this)); - propagateSource(); + if (tryUpdateSpeed().isOk()) { + propagateSource(this); + } else { + onPopBlock(); + } } - public void onSpeedUpdated() { + /** + * 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 + */ + protected SolveResult tryUpdateSpeed() { speedNext = network.getRootSpeed() * speedRatio; + if (Math.abs(speedNext) > AllConfigs.SERVER.kinetics.maxRotationSpeed.get()) + return SolveResult.CONTRADICTION; + return SolveResult.OK; } public void flushChangedSpeed() { @@ -155,4 +180,8 @@ public class KineticNode { entity.getLevel().destroyBlock(entity.getBlockPos(), true); } + public boolean isSourceOf(KineticNode other) { + return other.source == this; + } + } 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 adf9bebfd..fa16bef31 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 @@ -24,7 +24,11 @@ public class KineticSolver { removeNode(entity); KineticNode node = new KineticNode(entity, this::getNode); nodes.put(entity.getBlockPos(), node); - node.onAdded(); + if (node.tryUpdateSpeed().isOk()) { + node.onAdded(); + } else { + node.onPopBlock(); + } } public void updateNode(KineticTileEntity entity) {