From 85a4954af6f40979fc243560cd4c1afaea2a5043 Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:25:18 +0000 Subject: [PATCH] Export supply hub links for the web map. The map export now lists each guild's connected hub pairs, with the two installations, the mode, the distance and the shares carried, so the web map can draw them. Each installation row also carries its hub slots and how many hubs it hosts. The export is queued when a hub is built or removed or a permit changes. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../guild/hub/SupplyHubCommands.java | 7 + .../simplefactions/map/export/Markers.java | 155 +++++++++++++++++- .../export/MarkersInstallationKindTest.java | 37 +++++ 3 files changed, 198 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java b/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java index 1920438c..f139564a 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java @@ -373,6 +373,13 @@ private static double reachableTrade(Guild guild, Installation installation) { /** Hub changes move trade power, so the map and incomes are brought up to date at once. */ private static void recalculateTrade() { + if (FactionManager.getMap() != null && FactionManager.factions != null) { + for (Faction faction : FactionManager.factions) { + if (faction != null && faction.getRGB() != null) { + FactionManager.getMap().enqueue("nation", faction.getRGB()); + } + } + } SimpleFactions plugin = SimpleFactions.getInstance(); if (plugin == null || plugin.getProvinceManager() == null) { return; diff --git a/src/main/java/net/tfminecraft/simplefactions/map/export/Markers.java b/src/main/java/net/tfminecraft/simplefactions/map/export/Markers.java index 50d6d7db..735f182e 100644 --- a/src/main/java/net/tfminecraft/simplefactions/map/export/Markers.java +++ b/src/main/java/net/tfminecraft/simplefactions/map/export/Markers.java @@ -5,12 +5,20 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.List; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import net.md_5.bungee.api.ChatColor; import net.tfminecraft.simplefactions.Cache; +import net.tfminecraft.simplefactions.guild.Guild; +import net.tfminecraft.simplefactions.guild.hub.HubNetwork; +import net.tfminecraft.simplefactions.guild.hub.HubTransport.Link; +import net.tfminecraft.simplefactions.guild.hub.SupplyHub; +import net.tfminecraft.simplefactions.guild.hub.SupplyHubService; +import net.tfminecraft.simplefactions.loaders.InstallationConfigLoader; import net.tfminecraft.simplefactions.managers.FactionManager; import net.tfminecraft.simplefactions.managers.WarManager; import net.tfminecraft.simplefactions.objects.Faction; @@ -67,11 +75,21 @@ public static void export(File out) throws IOException { JsonArray installations = new JsonArray(); for (Faction faction : FactionManager.factions) { for (Installation installation : faction.getInstallationHandler().getAll()) { - installations.add(installationRow(installation, faction.getId())); + installations.add(installationRow( + installation, + faction.getId(), + InstallationConfigLoader.getHubSlots(installation.getKind(), installation.getLevel()), + SupplyHubService.countAt(faction.getId(), installation.getId(), SupplyHubService.allGuilds()))); } } root.add("installations", installations); + JsonArray hubLinks = new JsonArray(); + for (Guild guild : SupplyHubService.allGuilds()) { + hubLinks.addAll(hubLinkRows(guild, HubNetwork.linksFor(guild))); + } + root.add("hub_links", hubLinks); + JsonArray forts = new JsonArray(); for (Faction faction : FactionManager.factions) { for (Installation installation : faction.getInstallationHandler().getAll()) { @@ -115,6 +133,14 @@ public static void export(File out) throws IOException { } static JsonObject installationRow(Installation installation, String factionId) { + return installationRow( + installation, + factionId, + SupplyHubService.defaultHubSlots(installation.getKind(), installation.getLevel()), + 0); + } + + static JsonObject installationRow(Installation installation, String factionId, int hubSlots, int hubs) { JsonObject row = new JsonObject(); row.addProperty("id", installation.getId()); row.addProperty("name", installation.getName()); @@ -124,9 +150,136 @@ static JsonObject installationRow(Installation installation, String factionId) { row.addProperty("center_x", installation.getCenterX()); row.addProperty("center_z", installation.getCenterZ()); row.addProperty("level", installation.getLevel()); + row.addProperty("hub_slots", hubSlots); + row.addProperty("hubs", hubs); + return row; + } + + static JsonArray hubLinkRows(Guild guild, List links) { + JsonArray rows = new JsonArray(); + if (guild == null || guild.getFaction() == null || guild.getFaction().getId() == null || links == null) { + return rows; + } + for (Link link : links) { + if (link == null || link.fromProvince() >= link.toProvince()) { + continue; + } + Installation from = activeHubAt(guild, link.fromProvince(), link.mode().getKind()); + Installation to = activeHubAt(guild, link.toProvince(), link.mode().getKind()); + if (from == null || to == null) { + continue; + } + Faction fromFaction = hubOwner(guild, from); + Faction toFaction = hubOwner(guild, to); + if (fromFaction == null || toFaction == null) { + continue; + } + rows.add(hubLinkRow( + guild.getId(), + stripColor(guild.getName()), + guild.getFaction().getId(), + link, + hubEndpoint(from, fromFaction), + hubEndpoint(to, toFaction))); + } + return rows; + } + + static JsonObject hubLinkRow( + String guildId, + String guildName, + String factionId, + Link link, + HubEndpoint from, + HubEndpoint to) { + JsonObject row = new JsonObject(); + row.addProperty("guild_id", guildId); + row.addProperty("guild_name", stripColor(guildName)); + row.addProperty("faction_id", factionId); + row.addProperty("mode", link.mode().getKey()); + row.addProperty("distance", link.distance()); + row.addProperty("trade_share", roundShare(link.tradeFactor())); + row.addProperty("production_share", roundShare(link.productionFactor())); + row.add("from", hubEndpointRow(from)); + row.add("to", hubEndpointRow(to)); + return row; + } + + static JsonObject hubEndpointRow(HubEndpoint endpoint) { + JsonObject row = new JsonObject(); + row.addProperty("installation_id", endpoint.installationId()); + row.addProperty("faction_id", endpoint.factionId()); + row.addProperty("name", stripColor(endpoint.name())); + row.addProperty("province_id", endpoint.provinceId()); + row.addProperty("center_x", endpoint.centerX()); + row.addProperty("center_z", endpoint.centerZ()); return row; } + private static Installation activeHubAt(Guild guild, int provinceId, InstallationKind kind) { + for (SupplyHub hub : guild.getSupplyHubs()) { + Installation installation = SupplyHubService.findInstallation(hub.ownerFactionId(), hub.installationId()); + if (installation == null || installation.getProvince() != provinceId || installation.getKind() != kind) { + continue; + } + Faction owner = FactionManager.getByString(hub.ownerFactionId()); + boolean permit = owner != null && owner.hasHubPermit(guild.getId()); + String guildFactionId = guild.getFaction() == null ? null : guild.getFaction().getId(); + SupplyHubService.HubStanding standing = SupplyHubService.standing( + hub, + true, + SupplyHubService.ownerAllows(guildFactionId, hub.ownerFactionId(), permit), + InstallationConfigLoader.getHubSlots(installation.getKind(), installation.getLevel()), + SupplyHubService.atInstallation( + hub.ownerFactionId(), hub.installationId(), SupplyHubService.allGuilds())); + if (standing.active()) { + return installation; + } + } + return null; + } + + private static Faction hubOwner(Guild guild, Installation installation) { + for (SupplyHub hub : guild.getSupplyHubs()) { + if (hub.installationId() == null || hub.ownerFactionId() == null) { + continue; + } + Installation hubInstallation = SupplyHubService.findInstallation( + hub.ownerFactionId(), hub.installationId()); + if (hubInstallation == installation) { + return FactionManager.getByString(hub.ownerFactionId()); + } + } + return null; + } + + private static HubEndpoint hubEndpoint(Installation installation, Faction owner) { + return new HubEndpoint( + installation.getId(), + owner.getId(), + installation.getName(), + installation.getProvince(), + installation.getCenterX(), + installation.getCenterZ()); + } + + private static String stripColor(String value) { + return ChatColor.stripColor(value == null ? "" : value); + } + + private static double roundShare(double share) { + return Math.round(share * 10000.0) / 10000.0; + } + + record HubEndpoint( + String installationId, + String factionId, + String name, + int provinceId, + int centerX, + int centerZ) { + } + static String installationKind(Installation installation) { return installation.getKind().getCommandName(); } diff --git a/src/test/java/net/tfminecraft/simplefactions/map/export/MarkersInstallationKindTest.java b/src/test/java/net/tfminecraft/simplefactions/map/export/MarkersInstallationKindTest.java index 7ae99450..8db20c84 100644 --- a/src/test/java/net/tfminecraft/simplefactions/map/export/MarkersInstallationKindTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/map/export/MarkersInstallationKindTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; +import net.tfminecraft.simplefactions.guild.hub.HubTransport; import net.tfminecraft.simplefactions.installation.Installation; import net.tfminecraft.simplefactions.installation.InstallationKind; @@ -27,4 +28,40 @@ void installationMapRowIncludesLevel() { assertTrue(row.get("level").isJsonPrimitive()); assertEquals(2, row.get("level").getAsInt()); } + + @Test + void installationMapRowIncludesHubCapacityAndUsage() { + Installation station = new Installation( + "central_station", "Central Station", InstallationKind.TRAIN_STATION, 1, 0, 0, 1L, 2); + + var row = Markers.installationRow(station, "faction-a", 2, 1); + + assertEquals(2, row.get("hub_slots").getAsInt()); + assertEquals(1, row.get("hubs").getAsInt()); + } + + @Test + void hubLinkRowUsesMapShapeAndRoundsShares() { + HubTransport.Link link = new HubTransport.Link(12, 28, HubTransport.Mode.RAIL, 1234.5, 0.629876, 0.224444); + + var row = Markers.hubLinkRow( + "The_Chisels", + "§bThe Chisels", + "Thalendor", + link, + new Markers.HubEndpoint("Abrar_Station", "Khazabrar", "§aAbrar Station", 12, 740, 1344), + new Markers.HubEndpoint("Dunir_Station", "Khazabrar", "Dunir Station", 28, 572, 1340)); + + assertEquals("The_Chisels", row.get("guild_id").getAsString()); + assertEquals("The Chisels", row.get("guild_name").getAsString()); + assertEquals("Thalendor", row.get("faction_id").getAsString()); + assertEquals("rail", row.get("mode").getAsString()); + assertEquals(1234.5, row.get("distance").getAsDouble()); + assertEquals(0.6299, row.get("trade_share").getAsDouble()); + assertEquals(0.2244, row.get("production_share").getAsDouble()); + assertEquals("Abrar_Station", row.getAsJsonObject("from").get("installation_id").getAsString()); + assertEquals("Khazabrar", row.getAsJsonObject("from").get("faction_id").getAsString()); + assertEquals("Abrar Station", row.getAsJsonObject("from").get("name").getAsString()); + assertEquals(28, row.getAsJsonObject("to").get("province_id").getAsInt()); + } }