|
| 1 | +package dev.compactmods.machines.tunnel.graph; |
| 2 | + |
| 3 | +import java.util.HashMap; |
| 4 | +import java.util.Map; |
| 5 | +import java.util.Optional; |
| 6 | +import java.util.stream.Collectors; |
| 7 | +import com.google.common.graph.MutableValueGraph; |
| 8 | +import com.google.common.graph.ValueGraphBuilder; |
| 9 | +import dev.compactmods.machines.CompactMachines; |
| 10 | +import dev.compactmods.machines.api.tunnels.TunnelDefinition; |
| 11 | +import net.minecraft.core.BlockPos; |
| 12 | +import net.minecraft.core.Direction; |
| 13 | +import net.minecraft.resources.ResourceLocation; |
| 14 | +import org.jetbrains.annotations.NotNull; |
| 15 | + |
| 16 | +/** |
| 17 | + * Represents a room's tunnel connections in a graph-style format. |
| 18 | + * This should be accessed through the saved data for specific machine room chunks. |
| 19 | + */ |
| 20 | +public class TunnelConnectionGraph { |
| 21 | + |
| 22 | + /** |
| 23 | + * The full data graph. Contains tunnel nodes, machine ids, and tunnel type information. |
| 24 | + */ |
| 25 | + private final MutableValueGraph<ITunnelGraphNode, ITunnelGraphEdge> graph; |
| 26 | + |
| 27 | + /** |
| 28 | + * Quick access to tunnel information for specific locations. |
| 29 | + */ |
| 30 | + private final Map<BlockPos, TunnelNode> tunnels; |
| 31 | + |
| 32 | + /** |
| 33 | + * Quick access to machine information nodes. |
| 34 | + */ |
| 35 | + private final Map<Integer, MachineNode> machines; |
| 36 | + |
| 37 | + /** |
| 38 | + * Quick access to tunnel definition nodes. |
| 39 | + */ |
| 40 | + private final Map<ResourceLocation, TunnelTypeNode> tunnelTypes; |
| 41 | + |
| 42 | + public TunnelConnectionGraph() { |
| 43 | + graph = ValueGraphBuilder |
| 44 | + .directed() |
| 45 | + .build(); |
| 46 | + |
| 47 | + tunnels = new HashMap<>(); |
| 48 | + machines = new HashMap<>(); |
| 49 | + tunnelTypes = new HashMap<>(); |
| 50 | + } |
| 51 | + |
| 52 | + /** |
| 53 | + * Finds which machine a tunnel is connected to. |
| 54 | + * |
| 55 | + * @param tunnel The tunnel to find a connection for. |
| 56 | + * @return The id of the connected machine. |
| 57 | + */ |
| 58 | + public Optional<Integer> connectedMachine(BlockPos tunnel) { |
| 59 | + if (!tunnels.containsKey(tunnel)) |
| 60 | + return Optional.empty(); |
| 61 | + |
| 62 | + var tNode = tunnels.get(tunnel); |
| 63 | + return graph.successors(tNode) |
| 64 | + .stream() |
| 65 | + .findFirst() |
| 66 | + .filter(n -> n instanceof MachineNode) |
| 67 | + .map(mNode -> ((MachineNode) mNode).machId()); |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * Registers a tunnel as being connected to a machine on a particular side. |
| 72 | + * If the tunnel already is registered, this will report a failure. |
| 73 | + * |
| 74 | + * @param tunnelPos The position of the tunnel inside the room. |
| 75 | + * @param type The type of tunnel being registered. |
| 76 | + * @param machineId The machine the tunnel is to be connected to. |
| 77 | + * @param side The side of the machine the tunnel is connecting to. |
| 78 | + * @return True if the connection could be established; false if the tunnel type is already registered for the given side. |
| 79 | + */ |
| 80 | + public boolean registerTunnel(BlockPos tunnelPos, TunnelDefinition type, int machineId, Direction side) { |
| 81 | + // First we need to get the machine the tunnel is trying to connect to |
| 82 | + var machineRegistered = machines.containsKey(machineId); |
| 83 | + MachineNode machineNode; |
| 84 | + if (!machineRegistered) { |
| 85 | + machineNode = new MachineNode(machineId); |
| 86 | + machines.put(machineId, machineNode); |
| 87 | + graph.addNode(machineNode); |
| 88 | + } else { |
| 89 | + machineNode = machines.get(machineId); |
| 90 | + } |
| 91 | + |
| 92 | + TunnelNode tunnelNode = getOrCreateTunnelNode(tunnelPos); |
| 93 | + if(graph.hasEdgeConnecting(tunnelNode, machineNode)) { |
| 94 | + // connection already formed between the tunnel at pos and the machine |
| 95 | + graph.edgeValue(tunnelNode, machineNode).ifPresent(edge -> { |
| 96 | + CompactMachines.LOGGER.info("Tunnel already registered for machine {} at position {}.", |
| 97 | + machineId, |
| 98 | + tunnelPos); |
| 99 | + }); |
| 100 | + |
| 101 | + return false; |
| 102 | + } |
| 103 | + |
| 104 | + // graph direction is (tunnel)-[connected_to]->(machine) |
| 105 | + var tunnelsForSide = graph.predecessors(machineNode) |
| 106 | + .stream() |
| 107 | + .filter(n -> n instanceof TunnelNode) |
| 108 | + .filter(tunnel -> graph.edgeValue(machineNode, tunnel) |
| 109 | + .map(edge -> !((TunnelMachineEdge) edge).side().equals(side)) |
| 110 | + .orElse(false)) |
| 111 | + .map(t -> (TunnelNode) t) |
| 112 | + .collect(Collectors.toSet()); |
| 113 | + |
| 114 | + var tunnelTypeNode = createOrRegisterTunnelType(type); |
| 115 | + |
| 116 | + // If tunnels are registered for the requested side, make sure there isn't a type conflict |
| 117 | + if (!tunnelsForSide.isEmpty()) { |
| 118 | + for (var sidedTunnel : tunnelsForSide) { |
| 119 | + // if we already have a tunnel with the same side and type, log the conflict and early exit |
| 120 | + var existingConn = graph.edgeValue(sidedTunnel, tunnelTypeNode); |
| 121 | + if(existingConn.isPresent()) { |
| 122 | + CompactMachines.LOGGER.info("Tunnel type {} already registered for side {} at position {}.", type.getRegistryName(), |
| 123 | + side.getSerializedName(), |
| 124 | + sidedTunnel.position()); |
| 125 | + |
| 126 | + return false; |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // no tunnels registered for side yet - free to make new tunnel node |
| 132 | + createTunnelAndLink(tunnelNode, side, machineNode, tunnelTypeNode); |
| 133 | + return true; |
| 134 | + } |
| 135 | + |
| 136 | + private void createTunnelAndLink(TunnelNode newTunnel, Direction side, MachineNode machineNode, TunnelTypeNode typeNode) { |
| 137 | + var newEdge = new TunnelMachineEdge(side); |
| 138 | + graph.putEdgeValue(newTunnel, machineNode, newEdge); |
| 139 | + graph.putEdgeValue(newTunnel, typeNode, new TunnelTypeEdge()); |
| 140 | + } |
| 141 | + |
| 142 | + @NotNull |
| 143 | + private TunnelNode getOrCreateTunnelNode(BlockPos tunnelPos) { |
| 144 | + if(tunnels.containsKey(tunnelPos)) |
| 145 | + return tunnels.get(tunnelPos); |
| 146 | + |
| 147 | + var newTunnel = new TunnelNode(tunnelPos); |
| 148 | + graph.addNode(newTunnel); |
| 149 | + tunnels.put(tunnelPos, newTunnel); |
| 150 | + return newTunnel; |
| 151 | + } |
| 152 | + |
| 153 | + public TunnelTypeNode createOrRegisterTunnelType(TunnelDefinition definition) { |
| 154 | + final ResourceLocation id = definition.getRegistryName(); |
| 155 | + |
| 156 | + if (tunnelTypes.containsKey(id)) |
| 157 | + return tunnelTypes.get(id); |
| 158 | + |
| 159 | + TunnelTypeNode newType = new TunnelTypeNode(id); |
| 160 | + graph.addNode(newType); |
| 161 | + tunnelTypes.put(id, newType); |
| 162 | + return newType; |
| 163 | + } |
| 164 | +} |
0 commit comments