Skip to content

Commit 539e702

Browse files
Drefvelinryanbarlow97claude
authored
Leave trade along sea and rail corridors (#122)
After a guild's trade has settled on the graph, each sea and rail edge leaves a share of what is in transit in the provinces it passes through, strongest near the sender. supply-hubs.corridor-share (0.5) sets how much. Air leaves nothing, and deposits never start another hop. Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 1871d8d commit 539e702

9 files changed

Lines changed: 677 additions & 4 deletions

File tree

‎src/main/java/net/tfminecraft/simplefactions/Cache.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ public static boolean requireProvinces(org.bukkit.command.CommandSender sender)
173173
public static boolean supplyHubAutoAccept = false;
174174
/** Share of a hop kept at an end where the guild has no active hub. */
175175
public static double supplyHubNoHubStrength = 0.5;
176+
/** Share of a sea or rail delivery left in the provinces along the way. */
177+
public static double supplyHubCorridorShare = 0.5;
176178

177179
public static double infrastructureFull = 20;
178180
public static double infrastructureTarget = 0.75;

‎src/main/java/net/tfminecraft/simplefactions/guild/hub/Highway.java‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
/**
2424
* Trade along the shared graph. A hop is one edge, in one direction, and it repeats until
2525
* 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.
2627
*/
2728
public final class Highway {
2829
/** A trade node where the guild has an active hub. Ids are compared ignoring case. */
@@ -59,6 +60,15 @@ public static double noHubStrength() {
5960
return Math.max(0, Math.min(1, value));
6061
}
6162

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+
6272
/**
6373
* Seeds trade from each node that can send, highest raw trade first, until a pass moves nothing.
6474
* {@code hubbed} may be empty: both ends then use {@link #noHubStrength()}.
@@ -103,6 +113,81 @@ public static void deliver(
103113
}
104114
}
105115

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+
106191
/**
107192
* Both directions of each pair the guild has hubbed at both ends. The mode is the one with the
108193
* higher trade factor; rail wins a tie. Safe on any thread.

‎src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubService.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public static void loadConfig(FileConfiguration config) {
8585
Cache.supplyHubAutoAccept = config != null && config.getBoolean("supply-hubs.auto-accept", false);
8686
double strength = config == null ? 0.5 : config.getDouble("supply-hubs.no-hub-strength", 0.5);
8787
Cache.supplyHubNoHubStrength = Double.isFinite(strength) ? Math.max(0, Math.min(1, strength)) : 0.5;
88+
double corridor = config == null ? 0.5 : config.getDouble("supply-hubs.corridor-share", 0.5);
89+
Cache.supplyHubCorridorShare = Double.isFinite(corridor) ? Math.max(0, Math.min(1, corridor)) : 0.5;
8890
}
8991

9092
public static int limit(Guild guild) {

‎src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,14 @@ this, guild, graphFor(), hubbedFor(guild),
324324
}
325325
}
326326

327-
/** Sea and rail corridor deposits. Runs only after the highway has settled. */
327+
/** Sea and rail corridor deposits. Runs only after the highway has settled, and does not run it again. */
328328
private void depositCorridors(Guild guild) {
329-
// Filled in when corridor deposits are added. The highway is not run again after this.
330329
if (guild == null) {
331330
return;
332331
}
332+
Highway.deposit(
333+
this, guild, graphFor(), hubbedFor(guild),
334+
GuildModifierOverride.resolve(guild, GuildModifier.HUB_TRADE));
333335
}
334336

335337
private void carryProductionThroughHubs(Guild guild) {

‎src/main/resources/config.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ supply-hubs:
9898
# Trade without your hub at a stop is carried at this strength. Both ends
9999
# without a hub multiply it twice. 1 is full strength.
100100
no-hub-strength: 0.5
101+
# Sea and rail leave this share of the delivery in the provinces along the way.
102+
# Air leaves nothing. 1 leaves the full delivery.
103+
corridor-share: 0.5
101104
# A stop delivers a share of the guild's trade there to the next stop.
102105
# trade / production: share delivered before distance loss (capped at 0.95)
103106
# kept-per-1000-blocks: share that survives each 1000 blocks

0 commit comments

Comments
 (0)