Persistence is key

This commit is contained in:
reidbhuntley 2022-01-01 00:19:10 -05:00
parent f4514b3e5e
commit 44d59fe793
7 changed files with 376 additions and 89 deletions

View file

@ -4,6 +4,7 @@ import static net.minecraft.ChatFormatting.GOLD;
import static net.minecraft.ChatFormatting.GRAY; import static net.minecraft.ChatFormatting.GRAY;
import java.util.List; import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -78,14 +79,6 @@ public class KineticTileEntity extends SmartTileEntity
} }
} }
private void addToSolver() {
KineticSolver.getSolver(level).addNode(this);
}
private void removeFromSolver() {
KineticSolver.getSolver(level).removeNode(this);
}
public KineticConnections getConnections() { public KineticConnections getConnections() {
return connections; return connections;
} }
@ -102,6 +95,10 @@ public class KineticTileEntity extends SmartTileEntity
return getDefaultStressCapacity(); return getDefaultStressCapacity();
} }
public boolean isStressConstant() {
return false;
}
public float getDefaultStressImpact() { public float getDefaultStressImpact() {
return (float) BlockStressValues.getImpact(getStressConfigKey()); return (float) BlockStressValues.getImpact(getStressConfigKey());
} }
@ -110,11 +107,18 @@ public class KineticTileEntity extends SmartTileEntity
return (float) BlockStressValues.getCapacity(getStressConfigKey()); return (float) BlockStressValues.getCapacity(getStressConfigKey());
} }
public Optional<Float> isConnected(BlockPos to) {
return KineticSolver.getSolver(level).isConnected(this.getBlockPos(), to);
}
public boolean isStressOnlyConnected(BlockPos to) {
return KineticSolver.getSolver(level).isStressOnlyConnected(this.getBlockPos(), to);
}
@Override @Override
public void initialize() { public void initialize() {
if (!level.isClientSide) { if (!level.isClientSide) {
addToSolver(); KineticSolver.getSolver(level).addNode(this);
} }
super.initialize(); super.initialize();
@ -125,7 +129,7 @@ public class KineticTileEntity extends SmartTileEntity
super.tick(); super.tick();
effects.tick(); effects.tick();
if (!level.isClientSide && !isRemoved()) { if (!level.isClientSide) {
KineticSolver.getSolver(level).updateNode(this); KineticSolver.getSolver(level).updateNode(this);
} }
@ -144,6 +148,29 @@ public class KineticTileEntity extends SmartTileEntity
flickerTally = getFlickerScore() - 1; flickerTally = getFlickerScore() - 1;
} }
@Override
protected void setRemovedNotDueToChunkUnload() {
if (!level.isClientSide) {
KineticSolver.getSolver(level).removeNode(this);
}
super.setRemovedNotDueToChunkUnload();
}
@Override
public void onChunkUnloaded() {
super.onChunkUnloaded();
if (!level.isClientSide) {
preKineticsUnloaded();
KineticSolver solver = KineticSolver.getSolver(level);
solver.updateNode(this);
solver.unloadNode(this);
}
}
public void preKineticsUnloaded() {}
private void validateKinetics() { private void validateKinetics() {
// if (hasSource()) { // if (hasSource()) {
// if (!hasNetwork()) { // if (!hasNetwork()) {
@ -215,14 +242,6 @@ public class KineticTileEntity extends SmartTileEntity
super.setRemoved(); super.setRemoved();
} }
@Override
protected void setRemovedNotDueToChunkUnload() {
if (!level.isClientSide) {
removeFromSolver();
}
super.setRemovedNotDueToChunkUnload();
}
@Override @Override
protected void write(CompoundTag compound, boolean clientPacket) { protected void write(CompoundTag compound, boolean clientPacket) {
compound.putFloat("Speed", speed); compound.putFloat("Speed", speed);

View file

@ -29,10 +29,10 @@ public class SimpleKineticTileEntity extends KineticTileEntity {
@Override @Override
public float getGeneratedSpeed() { public float getGeneratedSpeed() {
Block block = getBlockState().getBlock(); BlockPos belowPos = getBlockPos().below();
BlockEntity below = level.getBlockEntity(getBlockPos().below()); if (isStressOnlyConnected(belowPos)
if (block instanceof ICogWheel cog && cog.isLargeCog() && level.getBlockEntity(belowPos) instanceof SpeedControllerTileEntity controller
&& below instanceof SpeedControllerTileEntity controller && controller.getSpeed() != 0) && controller.getSpeed() != 0)
return controller.getTargetSpeed(); return controller.getTargetSpeed();
return 0; return 0;
} }

View file

@ -1,40 +1,24 @@
package com.simibubi.create.content.contraptions.solver; package com.simibubi.create.content.contraptions.solver;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.simibubi.create.foundation.utility.DirectionHelper; import com.simibubi.create.foundation.utility.DirectionHelper;
import com.simibubi.create.foundation.utility.LazyMap; import com.simibubi.create.foundation.utility.LazyMap;
import com.simibubi.create.content.contraptions.solver.KineticConnections.Type; import com.simibubi.create.content.contraptions.solver.KineticConnections.Type;
import com.simibubi.create.content.contraptions.solver.KineticConnections.Types;
import com.simibubi.create.content.contraptions.solver.KineticConnections.Entry; import com.simibubi.create.content.contraptions.solver.KineticConnections.Entry;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis; import net.minecraft.core.Direction.Axis;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.util.StringRepresentable;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static net.minecraft.world.level.block.state.properties.BlockStateProperties.AXIS;
public class AllConnections { public class AllConnections {
public static record ValueType(Object value) implements Type {
@Override
public boolean compatible(Type other) {
return this == other;
}
public static <T> LazyMap<T, Type> map() {
return new LazyMap<>(ValueType::new);
}
}
public static final LazyMap<Axis, Type>
TYPE_SHAFT = ValueType.map(),
TYPE_LARGE_COG = ValueType.map(),
TYPE_SMALL_COG = ValueType.map(),
TYPE_SPEED_CONTROLLER_TOP = ValueType.map();
private static Direction pos(Axis axis) { private static Direction pos(Axis axis) {
return Direction.get(Direction.AxisDirection.POSITIVE, axis); return Direction.get(Direction.AxisDirection.POSITIVE, axis);
} }
@ -47,7 +31,7 @@ public class AllConnections {
int fromDiff = from.choose(diff.getX(), diff.getY(), diff.getZ()); int fromDiff = from.choose(diff.getX(), diff.getY(), diff.getZ());
int toDiff = to.choose(diff.getX(), diff.getY(), diff.getZ()); int toDiff = to.choose(diff.getX(), diff.getY(), diff.getZ());
float ratio = fromDiff > 0 ^ toDiff > 0 ? -1 : 1; float ratio = fromDiff > 0 ^ toDiff > 0 ? -1 : 1;
return new Entry(diff, TYPE_LARGE_COG.apply(from), TYPE_LARGE_COG.apply(to), ratio); return new Entry(diff, Type.of(Types.LARGE_COG, from), Type.of(Types.LARGE_COG, to), ratio);
} }
private static Optional<Axis> oppAxis(Axis axis) { private static Optional<Axis> oppAxis(Axis axis) {
@ -63,14 +47,14 @@ public class AllConnections {
public static final LazyMap<Direction, KineticConnections> public static final LazyMap<Direction, KineticConnections>
HALF_SHAFT = new LazyMap<>(dir -> HALF_SHAFT = new LazyMap<>(dir ->
new KineticConnections(new Entry(dir.getNormal(), TYPE_SHAFT.apply(dir.getAxis())))); new KineticConnections(new Entry(dir.getNormal(), Type.of(Types.SHAFT, dir.getAxis()))));
public static final LazyMap<Axis, KineticConnections> public static final LazyMap<Axis, KineticConnections>
FULL_SHAFT = new LazyMap<>(axis -> HALF_SHAFT.apply(pos(axis)).merge(HALF_SHAFT.apply(neg(axis)))), FULL_SHAFT = new LazyMap<>(axis -> HALF_SHAFT.apply(pos(axis)).merge(HALF_SHAFT.apply(neg(axis)))),
LARGE_COG = new LazyMap<>(axis -> { LARGE_COG = new LazyMap<>(axis -> {
Type large = TYPE_LARGE_COG.apply(axis); Type large = Type.of(Types.LARGE_COG, axis);
Type small = TYPE_SMALL_COG.apply(axis); Type small = Type.of(Types.SMALL_COG, axis);
List<Entry> out = new LinkedList<>(); List<Entry> out = new LinkedList<>();
Direction cur = DirectionHelper.getPositivePerpendicular(axis); Direction cur = DirectionHelper.getPositivePerpendicular(axis);
@ -83,15 +67,15 @@ public class AllConnections {
} }
oppAxis(axis).ifPresent(opp -> { oppAxis(axis).ifPresent(opp -> {
Type sc = TYPE_SPEED_CONTROLLER_TOP.apply(opp); Type sc = Type.of(Types.SPEED_CONTROLLER_TOP, opp);
out.add(new Entry(Direction.DOWN.getNormal(), large, sc).stressOnly()); out.add(new Entry(Direction.DOWN.getNormal(), large, sc).stressOnly());
}); });
return new KineticConnections(out); return new KineticConnections(out);
}), }),
SMALL_COG = new LazyMap<>(axis -> { SMALL_COG = new LazyMap<>(axis -> {
Type large = TYPE_LARGE_COG.apply(axis); Type large = Type.of(Types.LARGE_COG, axis);
Type small = TYPE_SMALL_COG.apply(axis); Type small = Type.of(Types.SMALL_COG, axis);
List<Entry> out = new LinkedList<>(); List<Entry> out = new LinkedList<>();
Direction cur = DirectionHelper.getPositivePerpendicular(axis); Direction cur = DirectionHelper.getPositivePerpendicular(axis);
@ -110,8 +94,8 @@ public class AllConnections {
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 -> { SPEED_CONTROLLER = new LazyMap<>(axis -> {
Type sc = TYPE_SPEED_CONTROLLER_TOP.apply(axis); Type sc = Type.of(Types.SPEED_CONTROLLER_TOP, axis);
Type large = TYPE_LARGE_COG.apply(oppAxis(axis).get()); Type large = Type.of(Types.LARGE_COG, oppAxis(axis).get());
Vec3i up = Direction.UP.getNormal(); Vec3i up = Direction.UP.getNormal();
return new KineticConnections(new Entry(up, sc, large).stressOnly()).merge(FULL_SHAFT.apply(axis)); return new KineticConnections(new Entry(up, sc, large).stressOnly()).merge(FULL_SHAFT.apply(axis));
}); });

View file

@ -1,7 +1,15 @@
package com.simibubi.create.content.contraptions.solver; package com.simibubi.create.content.contraptions.solver;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.util.StringRepresentable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -15,8 +23,51 @@ import java.util.stream.Stream;
public class KineticConnections { public class KineticConnections {
public interface Type { public enum Types {
boolean compatible(Type other); SHAFT, LARGE_COG, SMALL_COG, SPEED_CONTROLLER_TOP
}
public static class Type {
private final Types name;
private final String value;
private Type(Types name, String value) {
this.name = name;
this.value = value;
}
private final static Interner<Type> cachedTypes = Interners.newStrongInterner();
public static Type of(Types name, String value) {
return cachedTypes.intern(new Type(name, value));
}
public static Type of(Types name, StringRepresentable value) {
return of(name, value.getSerializedName());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Type type1 = (Type) o;
return name == type1.name && Objects.equals(value, type1.value);
}
@Override
public int hashCode() {
return Objects.hash(name, value);
}
public CompoundTag save(CompoundTag tag) {
NBTHelper.writeEnum(tag, "Name", name);
tag.putString("Value", value);
return tag;
}
public static Type load(CompoundTag tag) {
return Type.of(NBTHelper.readEnum(tag, "Name", Types.class), tag.getString("Value"));
}
} }
public static record Entry(Vec3i offset, Value value) { public static record Entry(Vec3i offset, Value value) {
@ -74,7 +125,7 @@ public class KineticConnections {
if (fromValue.isStressOnly() || toValue.isStressOnly()) return Optional.empty(); if (fromValue.isStressOnly() || toValue.isStressOnly()) return Optional.empty();
if (fromValue.from.compatible(toValue.to) && fromValue.to.compatible(toValue.from) if (fromValue.from.equals(toValue.to) && fromValue.to.equals(toValue.from)
&& (Mth.equal(fromValue.ratio, 1/toValue.ratio) || (Mth.equal(toValue.ratio, 1/fromValue.ratio)))) && (Mth.equal(fromValue.ratio, 1/toValue.ratio) || (Mth.equal(toValue.ratio, 1/fromValue.ratio))))
return Optional.of(fromValue.ratio); return Optional.of(fromValue.ratio);
return Optional.empty(); return Optional.empty();
@ -89,7 +140,7 @@ public class KineticConnections {
if (!fromValue.isStressOnly() || !toValue.isStressOnly()) return false; if (!fromValue.isStressOnly() || !toValue.isStressOnly()) return false;
return fromValue.from.compatible(toValue.to) && fromValue.to.compatible(toValue.from); return fromValue.from.equals(toValue.to) && fromValue.to.equals(toValue.from);
} }
@Override @Override
@ -116,5 +167,32 @@ public class KineticConnections {
return connections.values().stream().anyMatch(Value::isStressOnly); return connections.values().stream().anyMatch(Value::isStressOnly);
} }
public CompoundTag save(CompoundTag tag) {
ListTag connectionsTags = new ListTag();
for (Map.Entry<Vec3i, Value> entry : connections.entrySet()) {
CompoundTag entryTag = new CompoundTag();
entryTag.put("Off", NBTHelper.writeVec3i(entry.getKey()));
entryTag.put("From", entry.getValue().from().save(new CompoundTag()));
entryTag.put("To", entry.getValue().to().save(new CompoundTag()));
entryTag.putFloat("Ratio", entry.getValue().ratio());
connectionsTags.add(entryTag);
}
tag.put("Connections", connectionsTags);
return tag;
}
public static KineticConnections load(CompoundTag tag) {
Map<Vec3i, Value> connections = new HashMap<>();
tag.getList("Connections", Tag.TAG_COMPOUND).forEach(c -> {
CompoundTag comp = (CompoundTag) c;
Vec3i offset = NBTHelper.readVec3i(comp.getList("Off", Tag.TAG_INT));
Type from = Type.load(comp.getCompound("From"));
Type to = Type.load(comp.getCompound("To"));
float ratio = comp.getFloat("Ratio");
connections.put(offset, new Value(from, to, ratio));
});
return new KineticConnections(connections);
}
} }

View file

@ -47,12 +47,15 @@ public class KineticNetwork {
if (node.isGenerator() && !generators.contains(node)) { if (node.isGenerator() && !generators.contains(node)) {
generators.add(node); generators.add(node);
rootSpeedDirty = true; rootSpeedDirty = true;
rootSpeedChanged = true;
} }
if (node.hasStressImpact()) onMemberStressImpactUpdated(); if (node.hasStressImpact()) onMemberStressImpactUpdated();
if (node.hasStressCapacity()) onMemberStressCapacityUpdated(); if (node.hasStressCapacity()) onMemberStressCapacityUpdated();
} }
public void onMemberLoaded(KineticNode node) {
potentialNewBranches.add(node);
}
public void onMemberGeneratedSpeedUpdated(KineticNode node) { public void onMemberGeneratedSpeedUpdated(KineticNode node) {
if (node.isGenerator()) { if (node.isGenerator()) {
generators.add(node); generators.add(node);
@ -60,7 +63,7 @@ public class KineticNetwork {
generators.remove(node); generators.remove(node);
} }
rootSpeedDirty = true; rootSpeedDirty = true;
rootSpeedChanged = true; onMemberStressCapacityUpdated();
} }
public void onMemberStressImpactUpdated() { public void onMemberStressImpactUpdated() {
@ -75,7 +78,6 @@ public class KineticNetwork {
if (node.isGenerator() && generators.contains(node)) { if (node.isGenerator() && generators.contains(node)) {
generators.remove(node); generators.remove(node);
rootSpeedDirty = true; rootSpeedDirty = true;
rootSpeedChanged = true;
} }
if (node.hasStressImpact()) onMemberStressImpactUpdated(); if (node.hasStressImpact()) onMemberStressImpactUpdated();
if (node.hasStressCapacity()) onMemberStressCapacityUpdated(); if (node.hasStressCapacity()) onMemberStressCapacityUpdated();
@ -131,7 +133,11 @@ public class KineticNetwork {
} }
} }
rootTheoreticalSpeed = newSpeed * sign; if (rootTheoreticalSpeed != newSpeed * sign) {
rootTheoreticalSpeed = newSpeed * sign;
onRootSpeedChanged();
}
mainGenerator = newGenerator; mainGenerator = newGenerator;
rootSpeedDirty = false; rootSpeedDirty = false;
@ -150,6 +156,11 @@ public class KineticNetwork {
return isStopped() ? 0 : rootTheoreticalSpeed; return isStopped() ? 0 : rootTheoreticalSpeed;
} }
private void onRootSpeedChanged() {
rootSpeedChanged = true;
onMemberStressImpactUpdated();
}
public void untick() { public void untick() {
ticked = false; ticked = false;
} }
@ -180,13 +191,13 @@ public class KineticNetwork {
} else if (nowOverstressed) { } else if (nowOverstressed) {
if (!cur.overstressed) { if (!cur.overstressed) {
cur.overstressed = true; cur.overstressed = true;
rootSpeedChanged = true; cur.onRootSpeedChanged();
cur.members.forEach(KineticNode::stop); cur.members.forEach(KineticNode::stop);
} }
} else { } else {
if (cur.overstressed) { if (cur.overstressed) {
cur.overstressed = false; cur.overstressed = false;
rootSpeedChanged = true; cur.onRootSpeedChanged();
cur.bulldozeContradictingMembers(newNetworks); cur.bulldozeContradictingMembers(newNetworks);
cur.updateMemberSpeeds(newNetworks); cur.updateMemberSpeeds(newNetworks);
} }
@ -201,16 +212,16 @@ public class KineticNetwork {
* @param newNetworks a List that any new networks created during this call will be added to * @param newNetworks a List that any new networks created during this call will be added to
*/ */
private void updateMemberSpeeds(List<KineticNetwork> newNetworks) { private void updateMemberSpeeds(List<KineticNetwork> newNetworks) {
SolveResult recalculateSpeedResult = tryRecalculateSpeed();
// generators should not be turning against each other or have conflicting cycles 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()) { if (isStopped()) {
members.forEach(KineticNode::stop); members.forEach(KineticNode::stop);
return; return;
} }
SolveResult recalculateSpeedResult = tryRecalculateSpeed();
// generators should not be turning against each other or have conflicting cycles by now
assert(recalculateSpeedResult.isOk());
if (rootSpeedChanged) { if (rootSpeedChanged) {
// root speed changed, update all nodes starting from the main generator // root speed changed, update all nodes starting from the main generator
rootSpeedChanged = false; rootSpeedChanged = false;
@ -220,8 +231,8 @@ public class KineticNetwork {
potentialNewBranches.stream() potentialNewBranches.stream()
.filter(n -> !potentialNewBranches.contains(n.getSource())) .filter(n -> !potentialNewBranches.contains(n.getSource()))
.forEach(n -> bfs(n, newNetworks, true)); .forEach(n -> bfs(n, newNetworks, true));
potentialNewBranches.clear();
} }
potentialNewBranches.clear();
} }
private void bfs(KineticNode root, List<KineticNetwork> newNetworks, boolean followSource) { private void bfs(KineticNode root, List<KineticNetwork> newNetworks, boolean followSource) {

View file

@ -5,6 +5,8 @@ import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -12,39 +14,90 @@ import javax.annotation.Nullable;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class KineticNode { public class KineticNode {
private final Function<BlockPos, Optional<KineticNode>> nodeAccessor; private final KineticSolver solver;
private final KineticTileEntity entity; private @Nullable KineticTileEntity entity;
private @Nullable KineticNode source; private @Nullable KineticNode source;
private KineticNetwork network; private KineticNetwork network;
private float speedRatio = 1; private float speedRatio = 1;
private float speedCur;
private float speedNext;
private final BlockPos pos;
private final KineticConnections connections; private final KineticConnections connections;
private float generatedSpeed; private float generatedSpeed;
private float stressCapacity; private float stressCapacity;
private float stressImpact; private float stressImpact;
private final boolean constantStress;
private float speedCur; public KineticNode(KineticSolver solver, KineticTileEntity entity) {
private float speedNext; this.solver = solver;
public KineticNode(KineticTileEntity entity, Function<BlockPos, Optional<KineticNode>> nodeAccessor) {
this.nodeAccessor = nodeAccessor;
this.entity = entity; this.entity = entity;
this.pos = entity.getBlockPos();
this.connections = entity.getConnections(); this.connections = entity.getConnections();
this.generatedSpeed = entity.getGeneratedSpeed(); this.generatedSpeed = entity.getGeneratedSpeed();
this.stressImpact = entity.getStressImpact(); this.stressImpact = entity.getStressImpact();
this.stressCapacity = entity.getStressCapacity(); this.stressCapacity = entity.getStressCapacity();
this.constantStress = entity.isStressConstant();
this.network = new KineticNetwork(this); this.network = new KineticNetwork(this);
} }
private KineticNode(KineticSolver solver, BlockPos pos, KineticConnections connections, float generatedSpeed,
float stressCapacity, float stressImpact, boolean constantStress) {
this.solver = solver;
this.pos = pos;
this.connections = connections;
this.generatedSpeed = generatedSpeed;
this.stressImpact = stressImpact;
this.stressCapacity = stressCapacity;
this.constantStress = constantStress;
this.network = new KineticNetwork(this);
}
public CompoundTag save(CompoundTag tag) {
tag.put("Pos", NbtUtils.writeBlockPos(pos));
tag.put("Connections", connections.save(new CompoundTag()));
tag.putFloat("Generated", generatedSpeed);
tag.putFloat("Capacity", stressCapacity);
tag.putFloat("Impact", stressImpact);
if (constantStress)
tag.putBoolean("ConstantStress", true);
return tag;
}
public static KineticNode load(KineticSolver solver, CompoundTag tag) {
BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos"));
KineticConnections connections = KineticConnections.load(tag.getCompound("Connections"));
float generatedSpeed = tag.getFloat("Generated");
float stressCapacity = tag.getFloat("Capacity");
float stressImpact = tag.getFloat("Impact");
boolean constantStress = tag.getBoolean("ConstantStress");
return new KineticNode(solver, pos, connections, generatedSpeed, stressCapacity, stressImpact, constantStress);
}
public boolean isLoaded() {
return entity != null;
}
public void onLoaded(KineticTileEntity entity) {
this.entity = entity;
network.onMemberLoaded(this);
if (speedCur != 0) entity.setSpeed(speedCur);
}
public void onUnloaded() {
this.entity = null;
}
public KineticConnections getConnections() { public KineticConnections getConnections() {
return connections; return connections;
} }
@ -53,13 +106,17 @@ public class KineticNode {
return network; return network;
} }
public BlockPos getPos() {
return pos;
}
/** /**
* @return a Stream containing a pair for each compatible connection with this node, where the first value is * @return a Stream containing a pair for each compatible connection with this node, where the first value is
* the connecting node and the second value is the speed ratio of the connection * the connecting node and the second value is the speed ratio of the connection
*/ */
public Stream<Pair<KineticNode, Float>> getActiveConnections() { public Stream<Pair<KineticNode, Float>> getActiveConnections() {
return connections.getDirections().stream() return connections.getDirections().stream()
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d)) .map(d -> solver.getNode(pos.offset(d))
.map(n -> connections.checkConnection(n.connections, d) .map(n -> connections.checkConnection(n.connections, d)
.map(r -> Pair.of(n, r)))) .map(r -> Pair.of(n, r))))
.flatMap(Optional::stream) .flatMap(Optional::stream)
@ -72,7 +129,7 @@ public class KineticNode {
public Stream<KineticNetwork> getActiveStressOnlyConnections() { public Stream<KineticNetwork> getActiveStressOnlyConnections() {
return connections.getDirections().stream() return connections.getDirections().stream()
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d)) .map(d -> solver.getNode(pos.offset(d))
.filter(n -> connections.checkStressOnlyConnection(n.connections, d))) .filter(n -> connections.checkStressOnlyConnection(n.connections, d)))
.flatMap(Optional::stream) .flatMap(Optional::stream)
.map(KineticNode::getNetwork); .map(KineticNode::getNetwork);
@ -86,10 +143,15 @@ public class KineticNode {
return generatedSpeed != 0; return generatedSpeed != 0;
} }
public void onUpdated() { public boolean onUpdated() {
if (entity == null) return false;
boolean changed = false;
float generatedSpeedNew = entity.getGeneratedSpeed(); float generatedSpeedNew = entity.getGeneratedSpeed();
if (this.generatedSpeed != generatedSpeedNew) { if (this.generatedSpeed != generatedSpeedNew) {
this.generatedSpeed = generatedSpeedNew; this.generatedSpeed = generatedSpeedNew;
changed = true;
network.onMemberGeneratedSpeedUpdated(this); network.onMemberGeneratedSpeedUpdated(this);
if (network.tryRecalculateSpeed().isContradiction()) { if (network.tryRecalculateSpeed().isContradiction()) {
popBlock(); popBlock();
@ -99,14 +161,18 @@ public class KineticNode {
float stressImpactNew = entity.getStressImpact(); float stressImpactNew = entity.getStressImpact();
if (this.stressImpact != stressImpactNew) { if (this.stressImpact != stressImpactNew) {
this.stressImpact = stressImpactNew; this.stressImpact = stressImpactNew;
changed = true;
network.onMemberStressImpactUpdated(); network.onMemberStressImpactUpdated();
} }
float stressCapacityNew = entity.getStressCapacity(); float stressCapacityNew = entity.getStressCapacity();
if (this.stressCapacity != stressCapacityNew) { if (this.stressCapacity != stressCapacityNew) {
this.stressCapacity = stressCapacityNew; this.stressCapacity = stressCapacityNew;
changed = true;
network.onMemberStressCapacityUpdated(); network.onMemberStressCapacityUpdated();
} }
return changed;
} }
public boolean hasStressCapacity() { public boolean hasStressCapacity() {
@ -122,11 +188,11 @@ public class KineticNode {
} }
public float getStressCapacity() { public float getStressCapacity() {
return Math.abs(stressCapacity * generatedSpeed); return constantStress ? stressCapacity : stressCapacity * Math.abs(generatedSpeed);
} }
public float getTotalStressImpact(float speedAtRoot) { public float getTotalStressImpact(float speedAtRoot) {
return Math.abs(stressImpact * getTheoreticalSpeed(speedAtRoot)); return constantStress ? stressImpact : stressImpact * Math.abs(getTheoreticalSpeed(speedAtRoot));
} }
private SolveResult setNetwork(KineticNetwork network) { private SolveResult setNetwork(KineticNetwork network) {
@ -233,13 +299,18 @@ public class KineticNode {
public void flushChangedSpeed() { public void flushChangedSpeed() {
if (speedCur != speedNext) { if (speedCur != speedNext) {
speedCur = speedNext; speedCur = speedNext;
entity.setSpeed(speedCur); if (entity != null) {
entity.setSpeed(speedCur);
}
} }
} }
public void popBlock() { public void popBlock() {
// this should cause the node to get removed from the solver and lead to onRemoved() being called if (entity != null) {
entity.getLevel().destroyBlock(entity.getBlockPos(), true); solver.removeAndPopNow(entity);
} else {
solver.removeAndQueuePop(pos);
}
} }
} }

View file

@ -10,30 +10,108 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.simibubi.create.content.contraptions.base.KineticTileEntity; import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.WorldAttached; import com.simibubi.create.foundation.utility.WorldAttached;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.saveddata.SavedData;
public class KineticSolver { import org.checkerframework.checker.units.qual.C;
import org.jetbrains.annotations.NotNull;
private static final WorldAttached<KineticSolver> SOLVERS = new WorldAttached<>($ -> new KineticSolver()); public class KineticSolver extends SavedData {
public static final String DATA_FILE_NAME = "kinetics";
private static final WorldAttached<KineticSolver> SOLVERS = new WorldAttached<>(levelAccessor -> {
if (levelAccessor instanceof ServerLevel level)
return level.getDataStorage().computeIfAbsent(KineticSolver::load, KineticSolver::new, DATA_FILE_NAME);
return new KineticSolver();
});
public static KineticSolver getSolver(Level level) { public static KineticSolver getSolver(Level level) {
return SOLVERS.get(level); return SOLVERS.get(level);
} }
private final Map<BlockPos, KineticNode> nodes = new HashMap<>(); private final Map<BlockPos, KineticNode> nodes = new HashMap<>();
private final Set<BlockPos> popQueue = new HashSet<>();
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag tag) {
ListTag popQueueTag = new ListTag();
for (BlockPos pos : popQueue) {
popQueueTag.add(NbtUtils.writeBlockPos(pos));
}
tag.put("PopQueue", popQueueTag);
ListTag nodesTag = new ListTag();
for (KineticNode node : nodes.values()) {
nodesTag.add(node.save(new CompoundTag()));
}
tag.put("Nodes", nodesTag);
return tag;
}
private static KineticSolver load(CompoundTag tag) {
KineticSolver out = new KineticSolver();
tag.getList("PopQueue", Tag.TAG_COMPOUND).forEach(c ->
out.popQueue.add(NbtUtils.readBlockPos((CompoundTag) c)));
tag.getList("Nodes", Tag.TAG_COMPOUND).forEach(c ->
out.addUnloadedNode(KineticNode.load(out, (CompoundTag) c)));
return out;
}
private void addUnloadedNode(KineticNode node) {
KineticNode nodePrev = nodes.remove(node.getPos());
if (nodePrev != null) nodePrev.onRemoved();
nodes.put(node.getPos(), node);
node.onAdded();
}
public void addNode(KineticTileEntity entity) { public void addNode(KineticTileEntity entity) {
removeNode(entity); BlockPos pos = entity.getBlockPos();
KineticNode node = new KineticNode(entity, this::getNode); if (popQueue.contains(pos)) {
nodes.put(entity.getBlockPos(), node); popQueue.remove(pos);
popBlock(entity.getLevel(), pos);
setDirty();
return;
}
KineticNode nodePrev = nodes.get(pos);
if (nodePrev != null) {
if (nodePrev.isLoaded()) {
nodes.remove(pos);
nodePrev.onRemoved();
} else {
// a node exists here but is unloaded, so just load it with this entity instead of replacing
nodePrev.onLoaded(entity);
return;
}
}
KineticNode node = new KineticNode(this, entity);
nodes.put(pos, node);
node.onAdded(); node.onAdded();
setDirty();
} }
public void updateNode(KineticTileEntity entity) { public void updateNode(KineticTileEntity entity) {
KineticNode node = nodes.get(entity.getBlockPos()); KineticNode node = nodes.get(entity.getBlockPos());
if (node == null) return;
if (!node.getConnections().equals(entity.getConnections())) { if (!node.getConnections().equals(entity.getConnections())) {
// connections changed, so things could've been disconnected // connections changed, so things could've been disconnected
@ -41,17 +119,49 @@ public class KineticSolver {
addNode(entity); addNode(entity);
} else { } else {
// connections are the same, so just update in case other properties changed // connections are the same, so just update in case other properties changed
node.onUpdated(); if (node.onUpdated()) {
setDirty();
}
} }
} }
public void unloadNode(KineticTileEntity entity) {
KineticNode node = nodes.get(entity.getBlockPos());
if (node != null) node.onUnloaded();
}
protected Optional<KineticNode> getNode(BlockPos pos) { protected Optional<KineticNode> getNode(BlockPos pos) {
return Optional.ofNullable(nodes.get(pos)); return Optional.ofNullable(nodes.get(pos));
} }
public void removeNode(KineticTileEntity entity) { public void removeNode(KineticTileEntity entity) {
KineticNode node = nodes.remove(entity.getBlockPos()); KineticNode node = nodes.remove(entity.getBlockPos());
if (node != null) node.onRemoved(); if (node != null) {
node.onRemoved();
setDirty();
}
}
protected void removeAndQueuePop(BlockPos pos) {
KineticNode node = nodes.remove(pos);
if (node != null) {
popQueue.add(pos);
node.onRemoved();
setDirty();
}
}
protected void removeAndPopNow(KineticTileEntity entity) {
KineticNode node = nodes.remove(entity.getBlockPos());
if (node != null) {
popBlock(entity.getLevel(), entity.getBlockPos());
node.onRemoved();
setDirty();
}
}
private void popBlock(Level level, BlockPos pos) {
level.destroyBlock(pos, true);
} }
public void tick() { public void tick() {
@ -69,4 +179,18 @@ public class KineticSolver {
} }
} }
public Optional<Float> isConnected(BlockPos from, BlockPos to) {
return getNode(from).flatMap(fromNode ->
getNode(to).flatMap(toNode ->
fromNode.getConnections()
.checkConnection(toNode.getConnections(), to.subtract(from))));
}
public boolean isStressOnlyConnected(BlockPos from, BlockPos to) {
return getNode(from).flatMap(fromNode ->
getNode(to).map(toNode ->
fromNode.getConnections()
.checkStressOnlyConnection(toNode.getConnections(), to.subtract(from)))
).orElse(false);
}
} }