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..712342d
--- /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 + 1e-9 >= 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));
+ }
+}