diff --git a/pom.xml b/pom.xml index 261ec9d2..622e1dfd 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ 0.2.4 2.0.1 2.0.0 - 2.7.0 + 2.8.0 2.1.0 diff --git a/src/main/java/net/tfminecraft/simplefactions/Cache.java b/src/main/java/net/tfminecraft/simplefactions/Cache.java index 5d8ae5cd..f25cda73 100644 --- a/src/main/java/net/tfminecraft/simplefactions/Cache.java +++ b/src/main/java/net/tfminecraft/simplefactions/Cache.java @@ -174,6 +174,8 @@ public static boolean requireProvinces(org.bukkit.command.CommandSender sender) public static double infrastructureStation = 10; public static double infrastructurePort = 10; public static double infrastructureAirport = 5; + public static double infrastructureTrack = 10; + public static int infrastructureTrackRefreshSeconds = 300; public static Map baseEffects = new HashMap<>(); diff --git a/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java b/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java index 6a513ac2..6e2e4fd0 100644 --- a/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java +++ b/src/main/java/net/tfminecraft/simplefactions/SimpleFactions.java @@ -9,9 +9,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; import net.tfminecraft.simplefactions.Cache; import net.tfminecraft.simplefactions.map.ProvinceGrid; +import net.tfminecraft.simplefactions.map.infra.TrackProvinceCache; import net.tfminecraft.simplefactions.map.presence.ProvincePresenceListener; import net.tfminecraft.simplefactions.map.presence.ProvincePresenceService; import net.tfminecraft.simplefactions.map.presence.ProvincePresenceTickService; @@ -169,6 +171,7 @@ public class SimpleFactions extends JavaPlugin{ new net.tfminecraft.simplefactions.mercenary.contract.AttendanceService.Hook(); private ProvinceManager provinceSnapshot = new ProvinceManager(); private ProvinceGrid provinceGrid; + private BukkitTask trackInfrastructureRefreshTask; private final PlayerVehicleRegistry vehicleRegistry = new PlayerVehicleRegistry(); private VehicleRegistryPersistence vehicleRegistryPersistence; private final InstallationVehicleOwnerSync installationVehicleOwnerSync = @@ -345,15 +348,20 @@ public void onEnable() { sessionManager.start(); if (Cache.provincesEnabled) { provinceSnapshot = provinceManager.createSnapshotShell(); + refreshTrackProvinces(() -> {}); provinceManager.recalculate(); // Hub tax is assessed at the day change; do it once now so menus are right before then. net.tfminecraft.simplefactions.guild.hub.HubTaxService.refresh(provinceManager); + long refreshTicks = Cache.infrastructureTrackRefreshSeconds * 20L; + trackInfrastructureRefreshTask = getServer().getScheduler().runTaskTimer( + this, () -> { refreshTrackProvinces(); }, refreshTicks, refreshTicks); } inventoryManager.start(); vehicleMaintenanceDecayTask.start(); } @Override public void onDisable() { + if (trackInfrastructureRefreshTask != null) trackInfrastructureRefreshTask.cancel(); net.tfminecraft.simplefactions.integration.rpcharacters.chat.RpCharactersChatIntegration.unregister(); MercenaryStatService.clearAll(); net.tfminecraft.simplefactions.mercenary.company.MercenaryEligibility.reset(); @@ -518,6 +526,31 @@ public ProvinceGrid getProvinceGrid() { return provinceGrid; } + public boolean refreshTrackProvinces() { + return refreshTrackProvinces(provinceManager::recalculate); + } + + private boolean refreshTrackProvinces(Runnable recalculate) { + return TrackProvinceCache.live().refresh(() -> { + if (provinceGrid == null) { + return java.util.Set.of(); + } + if (!getServer().getPluginManager().isPluginEnabled("VehicleFramework")) { + TrackProvinceCache.live().vehicleFrameworkUnavailable( + message -> getLogger().info("[SimpleFactions] " + message)); + return java.util.Set.of(); + } + java.util.Map provinces = + new java.util.HashMap<>(); + for (net.tfminecraft.simplefactions.map.provinces.Province province : provinceManager.getProvinces()) { + provinces.put(province.getId(), province); + } + return net.tfminecraft.simplefactions.guild.hub.VehicleFrameworkTrackProvinces.sample( + Cache.worldName, provinceGrid, provinces); + }, recalculate, + message -> getLogger().warning("[SimpleFactions] " + message)); + } + public SessionManager getSessionManager() { return sessionManager; } diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/hub/VehicleFrameworkTrackProvinces.java b/src/main/java/net/tfminecraft/simplefactions/guild/hub/VehicleFrameworkTrackProvinces.java new file mode 100644 index 00000000..6c43b85f --- /dev/null +++ b/src/main/java/net/tfminecraft/simplefactions/guild/hub/VehicleFrameworkTrackProvinces.java @@ -0,0 +1,26 @@ +package net.tfminecraft.simplefactions.guild.hub; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.tfminecraft.simplefactions.map.ProvinceGrid; +import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup; +import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup.Point; +import net.tfminecraft.simplefactions.map.provinces.Province; +import net.tfminecraft.vehicleframework.VehicleFramework; +import net.tfminecraft.vehicleframework.tracks.TrackRegistry; +import net.tfminecraft.vehicleframework.tracks.TrackSamplePoint; + +/** The only track sampler here that names VehicleFramework's sample point type. */ +public final class VehicleFrameworkTrackProvinces { + private VehicleFrameworkTrackProvinces() {} + + public static Set sample(String world, ProvinceGrid grid, Map provinces) { + TrackRegistry tracks = VehicleFramework.getTrackRegistry(); + if (tracks == null || world == null) return Set.of(); + List samples = tracks.sampleTrack(world, 8); + return TrackProvinceLookup.collect(grid, samples.stream() + .map(point -> new Point(point.x(), point.y(), point.z())).toList(), provinces); + } +} diff --git a/src/main/java/net/tfminecraft/simplefactions/loaders/ConfigLoader.java b/src/main/java/net/tfminecraft/simplefactions/loaders/ConfigLoader.java index 80c45e45..910ba0d9 100644 --- a/src/main/java/net/tfminecraft/simplefactions/loaders/ConfigLoader.java +++ b/src/main/java/net/tfminecraft/simplefactions/loaders/ConfigLoader.java @@ -82,6 +82,9 @@ public void loadConfig(File configFile) { Cache.infrastructureStation = config.getDouble("infrastructure.station", 10); Cache.infrastructurePort = config.getDouble("infrastructure.port", 10); Cache.infrastructureAirport = config.getDouble("infrastructure.airport", 5); + Cache.infrastructureTrack = config.getDouble("infrastructure.track", 10); + Cache.infrastructureTrackRefreshSeconds = Math.max(30, + config.getInt("infrastructure.track-refresh-seconds", 300)); Cache.settlementLargePopulationThreshold = config.getInt("settlement-large-population-threshold", 8); Cache.portSeaProximityBlocks = config.getInt("port-sea-proximity-blocks", 20); diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java index 857d7d19..03661f5d 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/FactionManager.java @@ -503,7 +503,8 @@ public void time() { // and settle the day on trade that reflects them. runDailyStep("supply hub links", () -> { net.tfminecraft.simplefactions.guild.hub.HubNetwork.forgetRoutes(); - SimpleFactions.getInstance().getProvinceManager().recalculate(); + boolean tracksChanged = SimpleFactions.getInstance().refreshTrackProvinces(); + if (!tracksChanged) SimpleFactions.getInstance().getProvinceManager().recalculate(); net.tfminecraft.simplefactions.guild.hub.HubTaxService.refresh( SimpleFactions.getInstance().getProvinceManager()); }); diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java index 5014f703..c0e4b6c4 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java @@ -152,7 +152,8 @@ private void recalculateInfrastructure() { province.getTradeCarry(), !province.isSea(), origin, province.getNeighbours())); } Map sources = InfrastructureSources.collect( - provinces, FactionManager.getAllGuilds(), FactionManager.getCopy()); + provinces, FactionManager.getAllGuilds(), FactionManager.getCopy(), + net.tfminecraft.simplefactions.map.infra.TrackProvinceCache.live().provinces()); Map infrastructure = InfrastructureSpread.spread( graph, sources, Cache.infrastructureWildernessSpread, Cache.infrastructureSpreadFloor); for (Province province : provinces.values()) { diff --git a/src/main/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSources.java b/src/main/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSources.java index 0a14d0bc..d646a38e 100644 --- a/src/main/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSources.java +++ b/src/main/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSources.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import net.tfminecraft.simplefactions.Cache; import net.tfminecraft.simplefactions.enums.GuildModifier; @@ -16,7 +17,8 @@ public final class InfrastructureSources { private InfrastructureSources() {} public static Map collect( - Map provinces, Collection guilds, Collection factions) { + Map provinces, Collection guilds, Collection factions, + Set trackProvinces) { Map sources = new HashMap<>(); for (Guild guild : guilds) { if (guild == null || !guild.isBase() || !guild.hasCapital()) continue; @@ -34,6 +36,9 @@ public static Map collect( add(provinces, sources, installation.getProvince(), amount); } } + for (Integer id : trackProvinces) { + add(provinces, sources, id, Cache.infrastructureTrack); + } return sources; } diff --git a/src/main/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceCache.java b/src/main/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceCache.java new file mode 100644 index 00000000..652018e6 --- /dev/null +++ b/src/main/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceCache.java @@ -0,0 +1,46 @@ +package net.tfminecraft.simplefactions.map.infra; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** Immutable track source snapshot shared by live and preview recalculations. */ +public final class TrackProvinceCache { + private static final TrackProvinceCache LIVE = new TrackProvinceCache(); + + private volatile Set provinces = Set.of(); + private boolean warned; + private boolean vehicleFrameworkUnavailableLogged; + + public static TrackProvinceCache live() { + return LIVE; + } + + public Set provinces() { + return provinces; + } + + public void vehicleFrameworkUnavailable(Consumer info) { + if (!vehicleFrameworkUnavailableLogged) { + vehicleFrameworkUnavailableLogged = true; + info.accept("VehicleFramework is not enabled; railway track gives no infrastructure."); + } + } + + public boolean refresh(Supplier> sampler, Runnable recalculate, Consumer warning) { + Set sampled; + try { + sampled = Set.copyOf(sampler.get()); + } catch (RuntimeException | LinkageError e) { + sampled = Set.of(); + if (!warned) { + warned = true; + warning.accept("Could not read VehicleFramework tracks; track infrastructure is disabled."); + } + } + if (provinces.equals(sampled)) return false; + provinces = sampled; + recalculate.run(); + return true; + } +} diff --git a/src/main/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceLookup.java b/src/main/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceLookup.java new file mode 100644 index 00000000..1dc08123 --- /dev/null +++ b/src/main/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceLookup.java @@ -0,0 +1,24 @@ +package net.tfminecraft.simplefactions.map.infra; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import net.tfminecraft.simplefactions.map.ProvinceGrid; +import net.tfminecraft.simplefactions.map.provinces.Province; + +public final class TrackProvinceLookup { + public record Point(double x, double y, double z) {} + + private TrackProvinceLookup() {} + + public static Set collect(ProvinceGrid grid, Iterable points, Map provinces) { + Set ids = new HashSet<>(); + for (Point point : points) { + int id = grid.getAt((int) Math.floor(point.x()), (int) Math.floor(point.z())); + Province province = provinces.get(id); + if (id != 0 && province != null && !province.isSea()) ids.add(id); + } + return Set.copyOf(ids); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 39ab4746..cda46421 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -193,6 +193,8 @@ infrastructure: station: 10 port: 10 airport: 5 + track: 10 + track-refresh-seconds: 300 terrain-modifiers: - FARMLAND 0.95 diff --git a/src/test/java/net/tfminecraft/simplefactions/loaders/ConfigLoaderInfrastructureTest.java b/src/test/java/net/tfminecraft/simplefactions/loaders/ConfigLoaderInfrastructureTest.java index 14458d71..c9a289bc 100644 --- a/src/test/java/net/tfminecraft/simplefactions/loaders/ConfigLoaderInfrastructureTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/loaders/ConfigLoaderInfrastructureTest.java @@ -26,6 +26,8 @@ void restore() { Cache.infrastructureStation = previous[4]; Cache.infrastructurePort = previous[5]; Cache.infrastructureAirport = previous[6]; + Cache.infrastructureTrack = previous[7]; + Cache.infrastructureTrackRefreshSeconds = (int) previous[8]; } @Test @@ -42,7 +44,7 @@ void missingSectionUsesDefaultsAndResetsPreviousSettings() throws IOException { """); load("enable-map: false\n"); - assertValues(20, 0.75, 0.25, 0.5, 10, 10, 5); + assertValues(20, 0.75, 0.25, 0.5, 10, 10, 5, 10, 300); } @Test @@ -52,7 +54,7 @@ void missingKeysUseDefaultsIndependently() throws IOException { full: 30 """); - assertValues(30, 0.75, 0.25, 0.5, 10, 10, 5); + assertValues(30, 0.75, 0.25, 0.5, 10, 10, 5, 10, 300); } @Test @@ -66,9 +68,11 @@ void configuredKeysAreReadIntoCache() throws IOException { station: 12 port: 8 airport: 4 + track: 17 + track-refresh-seconds: 45 """); - assertValues(30, 0.80, 0.10, 0.25, 12, 8, 4); + assertValues(30, 0.80, 0.10, 0.25, 12, 8, 4, 17, 45); } @Test @@ -101,7 +105,8 @@ private void load(String yaml) throws IOException { private static double[] values() { return new double[] {Cache.infrastructureFull, Cache.infrastructureTarget, Cache.infrastructureWildernessSpread, Cache.infrastructureSpreadFloor, - Cache.infrastructureStation, Cache.infrastructurePort, Cache.infrastructureAirport}; + Cache.infrastructureStation, Cache.infrastructurePort, Cache.infrastructureAirport, + Cache.infrastructureTrack, Cache.infrastructureTrackRefreshSeconds}; } private static void assertValues(double... expected) { diff --git a/src/test/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSourcesTest.java b/src/test/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSourcesTest.java index 7193ea89..5612fc9a 100644 --- a/src/test/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSourcesTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/map/infra/InfrastructureSourcesTest.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -28,6 +29,7 @@ class InfrastructureSourcesTest { private final double station = Cache.infrastructureStation; private final double port = Cache.infrastructurePort; private final double airport = Cache.infrastructureAirport; + private final double track = Cache.infrastructureTrack; @AfterEach void restore() { @@ -35,13 +37,14 @@ void restore() { Cache.infrastructureStation = station; Cache.infrastructurePort = port; Cache.infrastructureAirport = airport; + Cache.infrastructureTrack = track; } @Test void realmCapitalUsesTheResolvedModifierOnce() { Guild guild = guild(true, 1, 12.5); Map sources = InfrastructureSources.collect( - Map.of(1, province(1, "forest")), List.of(guild), List.of()); + Map.of(1, province(1, "forest")), List.of(guild), List.of(), Set.of()); assertEquals(12.5, sources.get(1)); verify(guild, times(1)).getModifier(GuildModifier.INFRASTRUCTURE); @@ -54,7 +57,7 @@ void capitalUsesPreviewOverrideInsteadOfTheLiveModifier() { Guild guild = guild(true, 1, 12.5); GuildModifierOverride.use(guild, Map.of(GuildModifier.INFRASTRUCTURE, 17.0)); Map sources = InfrastructureSources.collect( - Map.of(1, province(1, "forest")), List.of(guild), List.of()); + Map.of(1, province(1, "forest")), List.of(guild), List.of(), Set.of()); assertEquals(17, sources.get(1)); verify(guild, never()).getModifier(GuildModifier.INFRASTRUCTURE); @@ -67,7 +70,7 @@ void nonBaseGuildsAndGuildsWithoutCapitalsAddNothing() { when(noCapital.hasCapital()).thenReturn(false); assertTrue(InfrastructureSources.collect(Map.of(1, province(1, "forest")), - List.of(regular, noCapital), List.of()).isEmpty()); + List.of(regular, noCapital), List.of(), Set.of()).isEmpty()); verify(regular, never()).getModifier(GuildModifier.INFRASTRUCTURE); verify(noCapital, never()).getModifier(GuildModifier.INFRASTRUCTURE); } @@ -84,7 +87,7 @@ void installationsUseConfigAmountsWithoutScalingByLevelAndFortsAddNothing() { Map sources = InfrastructureSources.collect(Map.of( 1, province(1, "forest"), 2, province(2, "forest"), 3, province(3, "forest"), 4, province(4, "forest")), List.of(), - List.of(faction(station, port, airport, fort))); + List.of(faction(station, port, airport, fort)), Set.of()); assertEquals(Map.of(1, 7.0, 2, 11.0, 3, 3.0), sources); verify(station, never()).getLevel(); @@ -98,7 +101,7 @@ void sourcesInOneProvinceAddBeforeTheySpread() { Guild guild = guild(true, 1, 10); Map sources = InfrastructureSources.collect(Map.of( 1, province(1, "forest"), 2, province(2, "plains")), List.of(guild), - List.of(faction(installation(InstallationKind.TRAIN_STATION, 1)))); + List.of(faction(installation(InstallationKind.TRAIN_STATION, 1))), Set.of()); Map result = InfrastructureSpread.spread(Map.of( 1, new InfrastructureSpread.Node(0.6, true, "realm", List.of(2)), 2, new InfrastructureSpread.Node(0.75, true, "realm", List.of(1))), sources, 0.25, 0.5); @@ -114,11 +117,33 @@ void sourcesOutsideTheManagerAndOnSeaOrWaterAreIgnored() { List.of(guild(true, 1, 10), guild(true, 2, 10), guild(true, 3, 10)), List.of(faction(installation(InstallationKind.PORT, 1), installation(InstallationKind.TRAIN_STATION, 2), - installation(InstallationKind.AIRPORT, 3)))); + installation(InstallationKind.AIRPORT, 3))), Set.of()); assertTrue(sources.isEmpty()); } + @Test + void trackProvincesAddOnceAndCombineWithOtherSources() { + Cache.infrastructureTrack = 10; + Map sources = InfrastructureSources.collect(Map.of( + 1, province(1, "forest"), 2, province(2, "sea"), 3, province(3, "plains")), + List.of(), List.of(faction(installation(InstallationKind.TRAIN_STATION, 1))), Set.of(1, 2, 3)); + + assertEquals(Map.of(1, 20.0, 3, 10.0), sources); + } + + @Test + void trackProvinceSourceSpreadsOnlyThroughUnownedLand() { + Map provinces = Map.of(1, province(1, "plains"), 2, province(2, "plains")); + Map sources = InfrastructureSources.collect(provinces, List.of(), List.of(), Set.of(1)); + Map result = InfrastructureSpread.spread(Map.of( + 1, new InfrastructureSpread.Node(0.75, true, null, List.of(2)), + 2, new InfrastructureSpread.Node(0.75, true, "realm", List.of(1))), sources, 0.25, 0.5); + + assertEquals(10, result.get(1).amount()); + assertTrue(!result.containsKey(2)); + } + private static Province province(int id, String terrain) { return new Province(id, terrain, 0); } diff --git a/src/test/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceCacheTest.java b/src/test/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceCacheTest.java new file mode 100644 index 00000000..71ed649d --- /dev/null +++ b/src/test/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceCacheTest.java @@ -0,0 +1,63 @@ +package net.tfminecraft.simplefactions.map.infra; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +class TrackProvinceCacheTest { + @Test + void changedSetRecalculatesOnceAndUnchangedSetDoesNothing() { + TrackProvinceCache cache = new TrackProvinceCache(); + AtomicInteger recalculations = new AtomicInteger(); + + assertTrue(cache.refresh(() -> Set.of(5, 6), recalculations::incrementAndGet, message -> {})); + assertEquals(Set.of(5, 6), cache.provinces()); + assertFalse(cache.refresh(() -> Set.of(6, 5), recalculations::incrementAndGet, message -> {})); + assertEquals(1, recalculations.get()); + } + + @Test + void linkageFailureClearsSetAndWarnsOnlyOnce() { + TrackProvinceCache cache = new TrackProvinceCache(); + AtomicInteger warnings = new AtomicInteger(); + AtomicInteger recalculations = new AtomicInteger(); + cache.refresh(() -> Set.of(5), recalculations::incrementAndGet, message -> {}); + + assertTrue(cache.refresh(() -> { throw new LinkageError("missing sampleTrack"); }, + recalculations::incrementAndGet, message -> warnings.incrementAndGet())); + assertFalse(cache.refresh(() -> { throw new LinkageError("missing sampleTrack"); }, + recalculations::incrementAndGet, message -> warnings.incrementAndGet())); + + assertEquals(Set.of(), cache.provinces()); + assertEquals(1, warnings.get()); + assertEquals(2, recalculations.get()); + } + + @Test + void vehicleFrameworkAbsentLogsOnlyOnce() { + TrackProvinceCache cache = new TrackProvinceCache(); + AtomicInteger infoMessages = new AtomicInteger(); + AtomicInteger recalculations = new AtomicInteger(); + + assertFalse(cache.refresh(() -> { + cache.vehicleFrameworkUnavailable(message -> { + assertEquals("VehicleFramework is not enabled; railway track gives no infrastructure.", message); + infoMessages.incrementAndGet(); + }); + return Set.of(); + }, recalculations::incrementAndGet, message -> {})); + assertFalse(cache.refresh(() -> { + cache.vehicleFrameworkUnavailable(message -> infoMessages.incrementAndGet()); + return Set.of(); + }, recalculations::incrementAndGet, message -> {})); + + assertEquals(1, infoMessages.get()); + assertEquals(0, recalculations.get()); + } +} diff --git a/src/test/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceLookupTest.java b/src/test/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceLookupTest.java new file mode 100644 index 00000000..e5b2865c --- /dev/null +++ b/src/test/java/net/tfminecraft/simplefactions/map/infra/TrackProvinceLookupTest.java @@ -0,0 +1,53 @@ +package net.tfminecraft.simplefactions.map.infra; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPOutputStream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.tfminecraft.simplefactions.map.ProvinceGrid; +import net.tfminecraft.simplefactions.map.provinces.Province; + +class TrackProvinceLookupTest { + @TempDir + Path tempDir; + + @Test + void samplesMapToDistinctLandProvinces() throws IOException { + ProvinceGrid grid = grid(); + Map provinces = Map.of( + 5, new Province(5, "plains", 0), + 6, new Province(6, "forest", 0), + 7, new Province(7, "sea", 0)); + + assertEquals(java.util.Set.of(5, 6), TrackProvinceLookup.collect(grid, List.of( + new TrackProvinceLookup.Point(0.5, 64, 0), + new TrackProvinceLookup.Point(1.5, 64, 0), + new TrackProvinceLookup.Point(2.5, 64, 0), + new TrackProvinceLookup.Point(3.5, 64, 0), + new TrackProvinceLookup.Point(4.5, 64, 0)), provinces)); + } + + private ProvinceGrid grid() throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(bytes)) { + gzip.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putInt(5).putInt(1).array()); + try (DataOutputStream out = new DataOutputStream(gzip)) { + for (int id : new int[] {5, 5, 6, 0, 7}) out.writeShort(Short.reverseBytes((short) id)); + } + } + Path file = tempDir.resolve("grid.gz"); + java.nio.file.Files.write(file, bytes.toByteArray()); + return ProvinceGrid.load(file.toFile()); + } +}