completely rework coolant logic

This commit is contained in:
Curle 2023-07-01 02:14:39 +01:00
parent 8089051c00
commit f942596b7f
4 changed files with 312 additions and 168 deletions

View File

@ -71,7 +71,5 @@ public class Engage {
bus.addListener(this::commonSetup); bus.addListener(this::commonSetup);
} }
public void commonSetup(FMLCommonSetupEvent e) { public void commonSetup(FMLCommonSetupEvent e) { }
e.enqueueWork(() -> CoolantBaseBlock.VALID_COOLANT_BLOCKS = Set.of(Blocks.COMPRESSOR_BLOCK.block().get(), Blocks.COPPER_TUBE_BLOCK.block().get(), Blocks.COOLANT_METERING_BLOCK.block().get(), Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()));
}
} }

View File

@ -1,5 +1,6 @@
package uk.gemwire.engage.block.coolant; package uk.gemwire.engage.block.coolant;
import com.google.common.graph.Network;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
@ -7,6 +8,7 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import uk.gemwire.engage.registries.Blocks; import uk.gemwire.engage.registries.Blocks;
import java.util.*; import java.util.*;
@ -17,8 +19,6 @@ import java.util.*;
public class CoolantBaseBlock extends Block { public class CoolantBaseBlock extends Block {
static Logger LOGGER = LogManager.getLogger(); static Logger LOGGER = LogManager.getLogger();
public static Set<Block> VALID_COOLANT_BLOCKS;
public CoolantBaseBlock(Properties p_49795_) { public CoolantBaseBlock(Properties p_49795_) {
super(p_49795_); super(p_49795_);
} }
@ -27,168 +27,5 @@ public class CoolantBaseBlock extends Block {
public void onPlace(BlockState prior, Level level, BlockPos pos, BlockState newState, boolean p_60570_) { public void onPlace(BlockState prior, Level level, BlockPos pos, BlockState newState, boolean p_60570_) {
if (level.isClientSide) return; if (level.isClientSide) return;
long startTime = System.currentTimeMillis();
//searchForValidNetwork(new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ()), level);
long endTime = System.currentTimeMillis();
LOGGER.info("Discovery took " + (endTime - startTime) + "ms");
}
private Queue<Transition> getValidTransitionsFrom(NetworkState state, Level level) {
Queue<Transition> dirs = new ArrayDeque<>();
for (Direction dir : Direction.values()) {
if (state.lastTransition != null && dir == state.lastTransition) continue;
BlockPos newPos = state.currentPos.relative(dir);
if (state.visited.contains(newPos)) continue;
if (VALID_COOLANT_BLOCKS.contains(level.getBlockState(newPos).getBlock())) dirs.add(new Transition(dir, newPos, level.getBlockState(newPos)));
}
return dirs;
}
// TODO: Revert the search to a known good state
private boolean searchForValidNetwork(BlockPos.MutableBlockPos pos, Level level) {
NetworkState state = new NetworkState(false, false, false, new HashSet<>(), false, null, pos);
Queue<StateTransitions> queue = new ArrayDeque<>();
LOGGER.info("Beginning network discovery");
// Get the valid directions we can go.
var dirs = getValidTransitionsFrom(state, level);
LOGGER.info("First step: " + dirs.size() + " valid moves.");
// If we aren't connected to at least 2 other valid blocks, it is impossible to have a completed network.
if (dirs.size() < 2) {
return false;
}
state.visited.add(pos);
queue.add(new StateTransitions(state.copy(), dirs));
// Check the block we're at, first.
if (this == Blocks.COMPRESSOR_BLOCK.block().get()) state.compressor = true;
else if (this == Blocks.COOLANT_METERING_BLOCK.block().get()) { state.metering = true; state.premature = true; }
else if (this == Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()) state.spreader = true;
while(!queue.isEmpty()) {
// The search loop
search(level, queue);
// Try to finalise stuff - we gotta have everything in our network.
// First, check that the last remaining direction takes us into a block we've already visited (the closed loop)
if (state.visited.contains(dirs.iterator().next().pos())) {
// Check for premature mode
if (state.premature) {
if (!(state.compressor && state.spreader)) {
LOGGER.info("Two heat spreaders not found in valid configuration!");
queue.poll();
}
} else {
if (!(state.compressor && state.metering && state.spreader)) {
LOGGER.info("Not encountered all components of the coolant system!");
queue.poll();
} }
} }
}
queue.poll();
}
LOGGER.info("Valid coolant loop!");
return true;
}
void search(Level level, Queue<StateTransitions> queue) {
StateTransitions currentState = queue.peek();
while (currentState.transitions.size() > 1) {
// Traverse the new block
Transition transition = currentState.transitions.iterator().next();
currentState.state.lastTransition = transition.dir();
currentState.state.currentPos = transition.pos();
Block reachedBlock = transition.state().getBlock();
LOGGER.info("Traversing through " + currentState.state.lastTransition + " to " + currentState.state.currentPos + ", which is a " + getBlockName(reachedBlock));
currentState.state.visited.add(currentState.state.currentPos);
// If we reached a metering block for the first time
if (reachedBlock == Blocks.COOLANT_METERING_BLOCK.block().get()) {
currentState.state.metering();
// The compressor has its own things to check..
} else if (reachedBlock == Blocks.COMPRESSOR_BLOCK.block().get()) {
currentState.state.compressor();
// Heat spreaders may have many, so we just flip the flag to be true if we're just getting to one now.
} else if (reachedBlock == Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()) {
if (!currentState.state.spreader) currentState.state.spreader = true;
}
Queue<Transition> newTransitions = getValidTransitionsFrom(currentState.state, level);
queue.add(new StateTransitions(currentState.state.copy(), newTransitions));
currentState = queue.peek();
}
}
private String getBlockName(Block block) {
return block.getName().getContents().toString();
}
record Transition(Direction dir, BlockPos pos, BlockState state) {}
static class NetworkState {
boolean metering, compressor, spreader, premature;
Set<BlockPos> visited;
Direction lastTransition;
BlockPos currentPos;
public NetworkState(boolean metering, boolean compressor, boolean spreader, HashSet<BlockPos> visited, boolean premature, Direction lastTransition, BlockPos pos) {
this.metering = metering;
this.compressor = compressor;
this.spreader = spreader;
this.premature = premature;
this.visited = visited;
this.lastTransition = lastTransition;
this.currentPos = pos;
}
void metering() {
if (metering)
LOGGER.info("Two metering devices in the same network! Not valid.");
// And we have already reached a heat spreader:
if (spreader) {
// Tell the system to check for another
spreader = false;
// Set the metering flag true
} else {
// We're searching prematurely!
// We'll search for it at the compressor instead.
premature = true;
}
metering = true;
}
void compressor() {
if (compressor)
LOGGER.info("Two compressors in the same network! Not valid.");
// Premature metering means we should check that a heat spreader is between the metering device and the compressor.
if (premature) {
if (!spreader) {
LOGGER.info("Metering Device and Compressor are too close together! Not valid.");
} else {
compressor = true;
}
} else {
compressor = true;
}
}
NetworkState copy() {
return new NetworkState(metering, compressor, spreader, new HashSet<>(visited), premature, lastTransition, currentPos);
}
}
record StateTransitions(NetworkState state, Queue<Transition> transitions) {}
}

View File

@ -0,0 +1,215 @@
package uk.gemwire.engage.systems.coolant;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import uk.gemwire.engage.block.coolant.CoolantBaseBlock;
import uk.gemwire.engage.registries.Blocks;
import java.util.List;
import java.util.Set;
public class CoolantNetworkGraph {
public enum ValidBlocks {
COPPER_PIPE("pipe", Blocks.COPPER_TUBE_BLOCK.block().get()),
COMPRESSOR("compressor", Blocks.COMPRESSOR_BLOCK.block().get()),
METERING("metering", Blocks.COMPRESSOR_BLOCK.block().get()),
SPREADER("spreader", Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get());
public final String name;
public final CoolantBaseBlock type;
ValidBlocks(String name, Block block) {
this.name = name;
this.type = (CoolantBaseBlock) block;
}
public static String getNameFor(Block b) {
for (ValidBlocks v : values()) {
if (v.type == b) return v.name;
}
return "invalid";
}
public static Block getBlockFor(String name) {
for (ValidBlocks v : values()) {
if (v.name.equals(name)) return v.type;
}
return null;
}
}
// A Transition leads to another valid Network block.
public record Transition(BlockPos start, Direction dir, BlockPos dest, BlockState state) {}
// A Node in the network - a set of BlockPositions representing a Coolant Network Block.
public record NetworkBlockNode(CoolantBaseBlock blockType, Set<BlockPos> positions, Set<Transition> transitions) {
boolean hasTransitionTo(CoolantBaseBlock type) {
for (Transition t : transitions) {
if (t.state.getBlock() == type) return true;
}
return false;
}
}
/**
* A network consists of at least one Network Block Node (@see CoolantBaseBlock#VALID_COOLANT_BLOCKS)
*/
public static class Graph {
List<NetworkBlockNode> nodes;
public Graph(List<NetworkBlockNode> nodes) {
this.nodes = nodes;
}
Direction distance(BlockPos bp1, BlockPos bp2) {
for (Direction dir : Direction.values()) {
if (bp1.relative(dir).equals(bp2)) return dir;
}
return null;
}
@Nullable
NetworkBlockNode getConnectedNodeOfType(NetworkBlockNode start, CoolantBaseBlock type) {
if (!start.hasTransitionTo(type)) return null;
BlockPos transitionedBlock = null;
for (Transition t : start.transitions) {
if (t.state.getBlock() == type) {
transitionedBlock = t.dest;
}
}
if (transitionedBlock == null) return null;
for (NetworkBlockNode node : nodes) {
if (node.positions.contains(transitionedBlock)) return node;
}
return null;
}
@Nullable
NetworkBlockNode getConnectedNodeOfTypeExcludingNode(NetworkBlockNode start, CoolantBaseBlock type, NetworkBlockNode blacklist) {
if (!start.hasTransitionTo(type)) return null;
BlockPos transitionedBlock = null;
for (Transition t : start.transitions) {
if (t.state.getBlock() == type && !blacklist.positions.contains(t.dest)) {
transitionedBlock = t.dest;
}
}
if (transitionedBlock == null) return null;
for (NetworkBlockNode node : nodes) {
if (node.positions.contains(transitionedBlock)) return node;
}
return null;
}
boolean addBlock(BlockPos position, Level level) {
boolean foundNode = false;
// Get Block Type
if (level.getBlockState(position).getBlock() instanceof CoolantBaseBlock coolant) {
// Find node to add it to
for (NetworkBlockNode node : nodes) {
// Find the neighbors
for (BlockPos pos : node.positions) {
// Cache the direction
Direction dir = distance(pos, position);
if (dir != null) {
// Add the position to the node if of the same type
if (node.blockType == coolant) {
node.positions.add(position);
foundNode = true;
}
// Add the transition to the node, even if it will lead to a new Node.
node.transitions.add(new Transition(pos, dir, position, level.getBlockState(position)));
}
}
}
if (foundNode) return true;
// Couldn't add to any existing Node, so create a new one
NetworkBlockNode node = new NetworkBlockNode(coolant, Set.of(position), Set.of());
nodes.add(node);
// Find transitions into neighboring nodes
for (Direction dir : Direction.values()) {
BlockState directionBlock = level.getBlockState(position.relative(dir));
if (directionBlock.getBlock() instanceof CoolantBaseBlock) {
node.transitions.add(new Transition(position, dir, position.relative(dir), level.getBlockState(position)));
}
}
return true;
}
return false;
}
boolean tryVerifyNetwork() {
// Find one of the two cables connected to the Compressor.
NetworkBlockNode cableNode = null;
for (NetworkBlockNode node : nodes) {
if (node.blockType == Blocks.COPPER_TUBE_BLOCK.block().get() && node.hasTransitionTo((CoolantBaseBlock) Blocks.COMPRESSOR_BLOCK.block().get())) {
cableNode = node;
break;
}
}
if (cableNode == null) return false;
if (cableNode.hasTransitionTo((CoolantBaseBlock) Blocks.COOLANT_METERING_BLOCK.block().get())) return false;
// Start at Copper pipe.
// Move to Compressor.
NetworkBlockNode compressorNode = getConnectedNodeOfType(cableNode, (CoolantBaseBlock) Blocks.COMPRESSOR_BLOCK.block().get());
if (compressorNode == null) return false;
// Move to Copper pipe.
NetworkBlockNode cable2Node = getConnectedNodeOfTypeExcludingNode(compressorNode, (CoolantBaseBlock) Blocks.COPPER_TUBE_BLOCK.block().get(), cableNode);
if (cable2Node == null) return false;
// Move to Heat Spreader 1 (evaporator, "before" the metering device)
NetworkBlockNode evaporatorNode = getConnectedNodeOfType(cable2Node, (CoolantBaseBlock) Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get());
if (evaporatorNode == null) return false;
// Move to Copper pipe.
cable2Node = getConnectedNodeOfTypeExcludingNode(evaporatorNode, (CoolantBaseBlock) Blocks.COPPER_TUBE_BLOCK.block().get(), cable2Node);
if (cable2Node == null) return false;
// Move to metering device.
NetworkBlockNode meteringNode = getConnectedNodeOfType(cable2Node, (CoolantBaseBlock) Blocks.COOLANT_METERING_BLOCK.block().get());
if (meteringNode == null) return false;
// Move to Copper pipe.
cable2Node = getConnectedNodeOfTypeExcludingNode(meteringNode, (CoolantBaseBlock) Blocks.COPPER_TUBE_BLOCK.block().get(), cable2Node);
if (cable2Node == null) return false;
// Move to Heat Spreader 2 (condenser, "after" the metering device)
NetworkBlockNode condenserNode = getConnectedNodeOfType(cable2Node, (CoolantBaseBlock) Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get());
if (condenserNode == null) return false;
// Move to Copper pipe.
cable2Node = getConnectedNodeOfTypeExcludingNode(meteringNode, (CoolantBaseBlock) Blocks.COPPER_TUBE_BLOCK.block().get(), cable2Node);
if (cable2Node == null) return false;
return cableNode == cable2Node;
}
boolean containsBlock(BlockPos position) {
for (NetworkBlockNode node : nodes) {
if (node.positions.contains(position)) return true;
}
return false;
}
}
}

View File

@ -0,0 +1,94 @@
package uk.gemwire.engage.systems.coolant;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.jetbrains.annotations.NotNull;
import uk.gemwire.engage.block.coolant.CoolantBaseBlock;
import java.util.*;
public class CoolantNetworksSavedData extends SavedData {
// The list of all Coolant Networks currently loaded
public List<CoolantNetworkGraph.Graph> networks;
public CoolantNetworksSavedData(List<CoolantNetworkGraph.Graph> networks) { this.networks = networks; }
public CoolantNetworksSavedData() { networks = new ArrayList<>(); }
@Override
public @NotNull CompoundTag save(final @NotNull CompoundTag pCompoundTag) {
ListTag net = new ListTag();
for (var acc : networks) {
ListTag nodes = new ListTag();
for (var node : acc.nodes) {
CompoundTag n = new CompoundTag();
n.putString("type", CoolantNetworkGraph.ValidBlocks.getNameFor(node.blockType()));
ListTag pos = new ListTag();
for (var p : node.positions()) {
pos.add(NbtUtils.writeBlockPos(p));
}
n.put("positions", pos);
ListTag transitions = new ListTag();
for (var t : node.transitions()) {
CompoundTag transition = new CompoundTag();
transition.put("start", NbtUtils.writeBlockPos(t.start()));
transition.put("dest", NbtUtils.writeBlockPos(t.start()));
transition.put("dir", IntTag.valueOf(t.dir().get3DDataValue()));
transition.put("state", NbtUtils.writeBlockState(t.state()));
transitions.add(transition);
}
n.put("transitions", transitions);
nodes.add(n);
}
net.add(nodes);
}
pCompoundTag.put("networks", net);
return pCompoundTag;
}
public static CoolantNetworksSavedData load(CompoundTag tag) {
ListTag nets = tag.getList("networks", ListTag.TAG_COMPOUND);
List<CoolantNetworkGraph.Graph> graphs = new ArrayList<>();
nets.iterator().forEachRemaining(network -> {
List<CoolantNetworkGraph.NetworkBlockNode> nodes = new ArrayList<>();
((ListTag) network).iterator().forEachRemaining(node -> {
Block type = CoolantNetworkGraph.ValidBlocks.getBlockFor(((CompoundTag) node).getString("type"));
Set<BlockPos> positions = new HashSet<>();
ListTag poses = ((CompoundTag) node).getList("positions", ListTag.TAG_COMPOUND);
poses.iterator().forEachRemaining(pos -> positions.add(NbtUtils.readBlockPos((CompoundTag) pos)));
Set<CoolantNetworkGraph.Transition> transitions = new HashSet<>();
ListTag transes = ((CompoundTag) node).getList("transitions", ListTag.TAG_COMPOUND);
transes.iterator().forEachRemaining(trans ->
transitions.add(
new CoolantNetworkGraph.Transition(
NbtUtils.readBlockPos(((CompoundTag) trans).getCompound("start")),
Direction.from3DDataValue(((CompoundTag) trans).getInt("dir")),
NbtUtils.readBlockPos(((CompoundTag) trans).getCompound("dest")),
NbtUtils.readBlockState(ServerLifecycleHooks.getCurrentServer().overworld().holderLookup(Registries.BLOCK), ((CompoundTag) trans).getCompound("state"))
)
)
);
nodes.add(new CoolantNetworkGraph.NetworkBlockNode((CoolantBaseBlock) type, positions, transitions));
});
graphs.add(new CoolantNetworkGraph.Graph(nodes));
});
return new CoolantNetworksSavedData(graphs);
}
public static CoolantNetworksSavedData getOrCreate(ServerLevel level) {
return level.getDataStorage().computeIfAbsent(CoolantNetworksSavedData::load, CoolantNetworksSavedData::new, "graphs");
}
}