From 1b967f197ffd42df5d38f9728ce4c53455f0fd7c Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Sat, 26 Sep 2026 09:12:37 +0000 Subject: [PATCH 1/2] feat: recover market demand every four hours Make the demand recovery interval configurable so the live market can wait four hours between ticks instead of one, and cover the schedule with unit tests. Co-authored-by: Cursor --- pom.xml | 11 ++++ .../net/tfminecraft/marketblock/Cache.java | 2 + .../tfminecraft/marketblock/MarketBlock.java | 1 + .../marketblock/loader/ConfigLoader.java | 5 +- .../marketblock/manager/TradeManager.java | 32 ++++++++--- .../marketblock/util/DemandSchedule.java | 24 ++++++++ src/main/resources/config.yml | 2 + .../marketblock/loader/ConfigLoaderTest.java | 55 +++++++++++++++++++ .../marketblock/trade/TradeDemandTest.java | 47 ++++++++++++++++ .../marketblock/util/DemandScheduleTest.java | 34 ++++++++++++ 10 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 src/main/java/net/tfminecraft/marketblock/util/DemandSchedule.java create mode 100644 src/test/java/net/tfminecraft/marketblock/loader/ConfigLoaderTest.java create mode 100644 src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java create mode 100644 src/test/java/net/tfminecraft/marketblock/util/DemandScheduleTest.java diff --git a/pom.xml b/pom.xml index cfab5a0..74e0166 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,12 @@ + + org.junit.jupiter + junit-jupiter + 5.14.0 + test + @@ -93,6 +99,11 @@ maven-compiler-plugin 3.14.1 + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.4 + diff --git a/src/main/java/net/tfminecraft/marketblock/Cache.java b/src/main/java/net/tfminecraft/marketblock/Cache.java index 076c752..8b93dbf 100644 --- a/src/main/java/net/tfminecraft/marketblock/Cache.java +++ b/src/main/java/net/tfminecraft/marketblock/Cache.java @@ -7,11 +7,13 @@ import org.bukkit.block.Block; +import net.tfminecraft.marketblock.util.DemandSchedule; import net.tfminecraft.tlibs.TLibs; public class Cache { public static List slots = new ArrayList<>(); public static String marketBlock; + public static double demandRecoveryHours = DemandSchedule.DEFAULT_HOURS; public static final Map freshnessPrice = new LinkedHashMap<>(); public static boolean blockIsMarketBlock(Block b) { diff --git a/src/main/java/net/tfminecraft/marketblock/MarketBlock.java b/src/main/java/net/tfminecraft/marketblock/MarketBlock.java index bc2cbb5..4da38fb 100644 --- a/src/main/java/net/tfminecraft/marketblock/MarketBlock.java +++ b/src/main/java/net/tfminecraft/marketblock/MarketBlock.java @@ -86,5 +86,6 @@ public void reload(Player p) { public void reload() { loadConfigs(); + tradeManager.rescheduleDemandCycle(); } } diff --git a/src/main/java/net/tfminecraft/marketblock/loader/ConfigLoader.java b/src/main/java/net/tfminecraft/marketblock/loader/ConfigLoader.java index f849f01..04b41a7 100644 --- a/src/main/java/net/tfminecraft/marketblock/loader/ConfigLoader.java +++ b/src/main/java/net/tfminecraft/marketblock/loader/ConfigLoader.java @@ -7,8 +7,9 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import net.tfminecraft.tlibs.interfaces.LoaderInterface; import net.tfminecraft.marketblock.Cache; +import net.tfminecraft.marketblock.util.DemandSchedule; +import net.tfminecraft.tlibs.interfaces.LoaderInterface; public class ConfigLoader implements LoaderInterface{ @@ -22,6 +23,8 @@ public void load(File configFile) { } Cache.marketBlock = config.getString("market-block"); + Cache.demandRecoveryHours = DemandSchedule.normalizeHours( + config.getDouble("demand-recovery-hours", DemandSchedule.DEFAULT_HOURS)); Cache.slots = config.getIntegerList("slots"); diff --git a/src/main/java/net/tfminecraft/marketblock/manager/TradeManager.java b/src/main/java/net/tfminecraft/marketblock/manager/TradeManager.java index 63ddf3b..d9e90c9 100644 --- a/src/main/java/net/tfminecraft/marketblock/manager/TradeManager.java +++ b/src/main/java/net/tfminecraft/marketblock/manager/TradeManager.java @@ -14,6 +14,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import net.tfminecraft.marketblock.Cache; import net.tfminecraft.marketblock.MarketBlock; @@ -25,12 +26,14 @@ import net.tfminecraft.marketblock.loader.TradeLoader; import net.tfminecraft.marketblock.trade.Category; import net.tfminecraft.marketblock.trade.Trade; +import net.tfminecraft.marketblock.util.DemandSchedule; import net.tfminecraft.marketblock.util.InventoryUtils; import net.tfminecraft.marketblock.util.PriceCalculator; import net.tfminecraft.marketblock.util.SaleTake; public class TradeManager implements Listener { InventoryManager inv = new InventoryManager(); + private BukkitTask demandTask; public void update() { for(Player p : Bukkit.getOnlinePlayers()) { @@ -61,19 +64,32 @@ private String getItemId(MBGUI type, ItemStack i) { } public void start() { - demandCycle(); + scheduleDemand(0L); } - public void demandCycle() { - new BukkitRunnable() { + public void rescheduleDemandCycle() { + scheduleDemand(DemandSchedule.periodTicks(Cache.demandRecoveryHours)); + } + + private void scheduleDemand(long delayTicks) { + if (demandTask != null) { + demandTask.cancel(); + demandTask = null; + } + long period = DemandSchedule.periodTicks(Cache.demandRecoveryHours); + demandTask = new BukkitRunnable() { @Override public void run() { - for(Trade t : TradeLoader.getTrades().values()) { - t.demand(); - } - update(); + recoverDemand(); } - }.runTaskTimer(MarketBlock.plugin, 0, 60*60*20L); + }.runTaskTimer(MarketBlock.plugin, delayTicks, period); + } + + public void recoverDemand() { + for (Trade t : TradeLoader.getTrades().values()) { + t.demand(); + } + update(); } @EventHandler diff --git a/src/main/java/net/tfminecraft/marketblock/util/DemandSchedule.java b/src/main/java/net/tfminecraft/marketblock/util/DemandSchedule.java new file mode 100644 index 0000000..bdbe06e --- /dev/null +++ b/src/main/java/net/tfminecraft/marketblock/util/DemandSchedule.java @@ -0,0 +1,24 @@ +package net.tfminecraft.marketblock.util; + +public final class DemandSchedule { + public static final double DEFAULT_HOURS = 4.0; + public static final long TICKS_PER_HOUR = 60L * 60L * 20L; + + private DemandSchedule() { + } + + public static double normalizeHours(double configuredHours) { + if (!Double.isFinite(configuredHours) || configuredHours <= 0.0) { + return DEFAULT_HOURS; + } + return configuredHours; + } + + public static long periodTicks(double configuredHours) { + double ticks = normalizeHours(configuredHours) * TICKS_PER_HOUR; + if (!Double.isFinite(ticks) || ticks >= Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + return Math.max(1L, Math.round(ticks)); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b0679f9..c505265 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,7 @@ slots: [10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43] market-block: iaf(tfmc:market_block) +# Hours between demand recovery ticks. Each tick raises every item's demand. +demand-recovery-hours: 4 freshness-price: fresh: 1.0 stale: 0.5 diff --git a/src/test/java/net/tfminecraft/marketblock/loader/ConfigLoaderTest.java b/src/test/java/net/tfminecraft/marketblock/loader/ConfigLoaderTest.java new file mode 100644 index 0000000..c07f48d --- /dev/null +++ b/src/test/java/net/tfminecraft/marketblock/loader/ConfigLoaderTest.java @@ -0,0 +1,55 @@ +package net.tfminecraft.marketblock.loader; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.nio.file.Files; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.tfminecraft.marketblock.Cache; +import net.tfminecraft.marketblock.util.DemandSchedule; + +class ConfigLoaderTest { + private final ConfigLoader loader = new ConfigLoader(); + + @AfterEach + void restoreDefault() { + Cache.demandRecoveryHours = DemandSchedule.DEFAULT_HOURS; + } + + @Test + void loadsDemandRecoveryHours(@TempDir File dir) throws Exception { + File config = write(dir, "demand-recovery-hours: 4\n"); + + loader.load(config); + + assertEquals(4.0, Cache.demandRecoveryHours); + } + + @Test + void missingKeyDefaultsToFourHours(@TempDir File dir) throws Exception { + File config = write(dir, "market-block: iaf(tfmc:market_block)\n"); + + loader.load(config); + + assertEquals(DemandSchedule.DEFAULT_HOURS, Cache.demandRecoveryHours); + } + + @Test + void nonPositiveHoursFallBackToFour(@TempDir File dir) throws Exception { + loader.load(write(dir, "demand-recovery-hours: 0\n")); + assertEquals(4.0, Cache.demandRecoveryHours); + + loader.load(write(dir, "demand-recovery-hours: -1\n")); + assertEquals(4.0, Cache.demandRecoveryHours); + } + + private static File write(File dir, String yaml) throws Exception { + File config = new File(dir, "config.yml"); + Files.writeString(config.toPath(), yaml); + return config; + } +} diff --git a/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java b/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java new file mode 100644 index 0000000..13efecd --- /dev/null +++ b/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java @@ -0,0 +1,47 @@ +package net.tfminecraft.marketblock.trade; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class TradeDemandTest { + + @Test + void eachRecoveryTickRaisesDemandByOneUpToJustUnderSeven() { + Trade trade = trade(1_000); + trade.setDemand(10); + + for (int i = 0; i < 20; i++) { + double before = trade.getDemand(); + trade.demand(); + double increase = trade.getDemand() - before; + assertTrue(increase >= 1.0, "increase was " + increase); + assertTrue(increase < 7.0, "increase was " + increase); + } + } + + @Test + void recoveryStopsAtTheDemandLimit() { + Trade trade = trade(20); + trade.setDemand(19.5); + + trade.demand(); + + assertEquals(20.0, trade.getDemand()); + } + + @Test + void sellingCannotDropDemandBelowOne() { + Trade trade = trade(20); + trade.setDemand(1); + + trade.sell(); + + assertEquals(1.0, trade.getDemand()); + } + + private static Trade trade(double limit) { + return new Trade("oak", new Category(), limit / 2, limit, 1, "v.oak_log", 2, 64, 0); + } +} diff --git a/src/test/java/net/tfminecraft/marketblock/util/DemandScheduleTest.java b/src/test/java/net/tfminecraft/marketblock/util/DemandScheduleTest.java new file mode 100644 index 0000000..99cb122 --- /dev/null +++ b/src/test/java/net/tfminecraft/marketblock/util/DemandScheduleTest.java @@ -0,0 +1,34 @@ +package net.tfminecraft.marketblock.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class DemandScheduleTest { + + @Test + void fourHoursIsTheDefaultPeriod() { + assertEquals(4.0 * DemandSchedule.TICKS_PER_HOUR, DemandSchedule.periodTicks(4)); + assertEquals(288_000L, DemandSchedule.periodTicks(DemandSchedule.DEFAULT_HOURS)); + } + + @Test + void oneHourMatchesThePreviousHardcodedInterval() { + assertEquals(72_000L, DemandSchedule.periodTicks(1)); + } + + @Test + void fractionalHoursRoundToTheNearestTick() { + assertEquals(36_000L, DemandSchedule.periodTicks(0.5)); + assertEquals(1L, DemandSchedule.periodTicks(1.0 / DemandSchedule.TICKS_PER_HOUR / 10)); + } + + @Test + void missingOrInvalidHoursFallBackToFour() { + assertEquals(DemandSchedule.DEFAULT_HOURS, DemandSchedule.normalizeHours(0)); + assertEquals(DemandSchedule.DEFAULT_HOURS, DemandSchedule.normalizeHours(-2)); + assertEquals(DemandSchedule.DEFAULT_HOURS, DemandSchedule.normalizeHours(Double.NaN)); + assertEquals(DemandSchedule.DEFAULT_HOURS, DemandSchedule.normalizeHours(Double.POSITIVE_INFINITY)); + assertEquals(288_000L, DemandSchedule.periodTicks(0)); + } +} From 9658aa04810a2bd8b4161e880fb89ffe4f3c0d6d Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Sat, 26 Sep 2026 09:18:17 +0000 Subject: [PATCH 2/2] test: allow a rounding ulp in demand recovery bounds Co-authored-by: Cursor --- .../java/net/tfminecraft/marketblock/trade/TradeDemandTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java b/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java index 13efecd..712342d 100644 --- a/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java +++ b/src/test/java/net/tfminecraft/marketblock/trade/TradeDemandTest.java @@ -16,7 +16,7 @@ void eachRecoveryTickRaisesDemandByOneUpToJustUnderSeven() { double before = trade.getDemand(); trade.demand(); double increase = trade.getDemand() - before; - assertTrue(increase >= 1.0, "increase was " + increase); + assertTrue(increase + 1e-9 >= 1.0, "increase was " + increase); assertTrue(increase < 7.0, "increase was " + increase); } }