|
23 | 23 | /** |
24 | 24 | * Trade along the shared graph. A hop is one edge, in one direction, and it repeats until |
25 | 25 | * nothing larger arrives. Production uses {@link #links}, which keeps only pairs hubbed at both ends. |
| 26 | + * Sea and rail then leave a share in the provinces along each edge. That does not start another hop. |
26 | 27 | */ |
27 | 28 | public final class Highway { |
28 | 29 | /** A trade node where the guild has an active hub. Ids are compared ignoring case. */ |
@@ -59,6 +60,15 @@ public static double noHubStrength() { |
59 | 60 | return Math.max(0, Math.min(1, value)); |
60 | 61 | } |
61 | 62 |
|
| 63 | + /** Clamped to 0..1. Missing or non-finite config uses the default half. */ |
| 64 | + public static double corridorShare() { |
| 65 | + double value = Cache.supplyHubCorridorShare; |
| 66 | + if (!Double.isFinite(value)) { |
| 67 | + return 0.5; |
| 68 | + } |
| 69 | + return Math.max(0, Math.min(1, value)); |
| 70 | + } |
| 71 | + |
62 | 72 | /** |
63 | 73 | * Seeds trade from each node that can send, highest raw trade first, until a pass moves nothing. |
64 | 74 | * {@code hubbed} may be empty: both ends then use {@link #noHubStrength()}. |
@@ -103,6 +113,81 @@ public static void deliver( |
103 | 113 | } |
104 | 114 | } |
105 | 115 |
|
| 116 | + /** |
| 117 | + * Seeds sea and rail provinces along each edge, in both directions. Call this only after |
| 118 | + * {@link #deliver} has settled. Each sending node's raw trade is read before any seed, so one |
| 119 | + * deposit's spread cannot raise the next deposit's source. Air leaves nothing. The receiving |
| 120 | + * end's hub does not change the amount. |
| 121 | + */ |
| 122 | + public static void deposit( |
| 123 | + ProvinceManager provinces, Guild guild, TradeGraph graph, Set<HubSite> hubbed, double hubTradeBonus) { |
| 124 | + if (provinces == null || guild == null || guild.getId() == null || graph == null || graph.edges().isEmpty()) { |
| 125 | + return; |
| 126 | + } |
| 127 | + double corridor = corridorShare(); |
| 128 | + if (corridor <= 0) { |
| 129 | + return; |
| 130 | + } |
| 131 | + Set<Node> hubNodes = hubNodes(graph, hubbed); |
| 132 | + double strength = noHubStrength(); |
| 133 | + double bonus = Math.max(0, hubTradeBonus); |
| 134 | + Map<Node, Double> rawAt = new HashMap<>(); |
| 135 | + for (Node node : graph.nodes()) { |
| 136 | + rawAt.put(node, raw(provinces, guild, node)); |
| 137 | + } |
| 138 | + for (Edge edge : graph.edges()) { |
| 139 | + if (edge.mode() == Mode.AIR || edge.provinces().isEmpty()) { |
| 140 | + continue; |
| 141 | + } |
| 142 | + depositDirection(provinces, guild, edge, edge.first(), hubNodes, strength, bonus, corridor, rawAt); |
| 143 | + depositDirection(provinces, guild, edge, edge.second(), hubNodes, strength, bonus, corridor, rawAt); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + private static void depositDirection( |
| 148 | + ProvinceManager provinces, Guild guild, Edge edge, Node from, Set<Node> hubNodes, |
| 149 | + double strength, double bonus, double corridor, Map<Node, Double> rawAt) { |
| 150 | + List<Integer> along = edge.provincesFrom(from); |
| 151 | + int count = along.size(); |
| 152 | + if (count == 0) { |
| 153 | + return; |
| 154 | + } |
| 155 | + double rawFrom = rawAt.getOrDefault(from, 0.0); |
| 156 | + if (rawFrom < 0.5) { |
| 157 | + return; |
| 158 | + } |
| 159 | + boolean fromHub = hubNodes.contains(from); |
| 160 | + double end = fromHub ? 1 : strength; |
| 161 | + Rates rates = HubTransport.rates(edge.mode()); |
| 162 | + double share = tradeShare(rates.trade(), fromHub ? bonus : 0); |
| 163 | + // Kept-per-1000 is at most 0.95, so distance only shrinks the deposit. |
| 164 | + if (rawFrom * end * share * corridor < 0.5) { |
| 165 | + return; |
| 166 | + } |
| 167 | + double length = Math.max(0, edge.length()); |
| 168 | + for (int index = 0; index < count; index++) { |
| 169 | + int provinceId = along.get(index); |
| 170 | + if (!provinces.contains(provinceId)) { |
| 171 | + continue; |
| 172 | + } |
| 173 | + double distance = length * (index + 1.0) / (count + 1.0); |
| 174 | + double amount = rawFrom * end * HubTransport.delivered(share, rates.keptPer1000(), distance) * corridor; |
| 175 | + provinces.get(provinceId).seedTrade(provinces, guild, amount); |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + /** The mode's trade share, capped the same way a hop caps {@code HUB_TRADE}. */ |
| 180 | + private static double tradeShare(double trade, double bonus) { |
| 181 | + if (!Double.isFinite(trade) || trade <= 0) { |
| 182 | + return 0; |
| 183 | + } |
| 184 | + double share = Math.min(HubTransport.MAX_SHARE, trade); |
| 185 | + if (bonus > 0) { |
| 186 | + share = Math.min(HubTransport.MAX_SHARE, share * (1.0 + bonus)); |
| 187 | + } |
| 188 | + return share; |
| 189 | + } |
| 190 | + |
106 | 191 | /** |
107 | 192 | * Both directions of each pair the guild has hubbed at both ends. The mode is the one with the |
108 | 193 | * higher trade factor; rail wins a tie. Safe on any thread. |
|
0 commit comments