diff --git a/AdvancedCore/pom.xml b/AdvancedCore/pom.xml
index 383ffe5bbe..d4da9904fc 100644
--- a/AdvancedCore/pom.xml
+++ b/AdvancedCore/pom.xml
@@ -208,7 +208,7 @@
com.bencodezsimpleapi
- 1.0.2-20260905.234759-10
+ 1.0.2-SNAPSHOTcompile
@@ -513,4 +513,4 @@
-
+
\ No newline at end of file
diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardFileData.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardFileData.java
index 6844b1ea2d..3a521532ff 100644
--- a/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardFileData.java
+++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardFileData.java
@@ -14,6 +14,8 @@
import com.bencodez.advancedcore.AdvancedCorePlugin;
import com.bencodez.advancedcore.api.misc.files.FilesManager;
+import com.bencodez.advancedcore.bukkit.rewards.BukkitRewardConfigReader;
+import com.bencodez.advancedcore.core.rewards.RewardConfigReader;
import com.bencodez.simpleapi.file.CaseInsensitiveSection;
import lombok.Getter;
@@ -26,6 +28,10 @@ public class RewardFileData {
private ConfigurationSection configData;
+ // Resolve through the public getter on each read, including after reloads
+ // and for subclasses that supply their own configuration section.
+ private final RewardConfigReader rewardConfig = new BukkitRewardConfigReader(this::getConfigData);
+
/** The data file. */
private File dataFile;
@@ -79,14 +85,11 @@ public void createSection(String path) {
* @return the chance
*/
public double getChance() {
- return getConfigData().getDouble("Chance");
+ return rewardConfig.getChance();
}
public Set getChoices() {
- if (getConfigData().isConfigurationSection("Choices")) {
- return getConfigData().getConfigurationSection("Choices").getKeys(false);
- }
- return new HashSet<>();
+ return rewardConfig.getChoices();
}
public ConfigurationSection getChoicesItem(String choice) {
@@ -94,7 +97,7 @@ public ConfigurationSection getChoicesItem(String choice) {
}
public String getChoicesRewardsPath(String choice) {
- return "Choices." + choice + ".Rewards";
+ return rewardConfig.getChoicesRewardsPath(choice);
}
/**
@@ -104,11 +107,7 @@ public String getChoicesRewardsPath(String choice) {
*/
@SuppressWarnings("unchecked")
public ArrayList getCommandsConsole() {
- if (getConfigData().isList("Commands")) {
- return (ArrayList) getConfigData().getList("Commands", new ArrayList<>());
- }
- return (ArrayList) getConfigData().getList("Commands.Console", new ArrayList<>());
-
+ return (ArrayList) rewardConfig.getCommandsConsole();
}
/**
@@ -118,7 +117,7 @@ public ArrayList getCommandsConsole() {
*/
@SuppressWarnings("unchecked")
public ArrayList getCommandsPlayer() {
- return (ArrayList) getConfigData().getList("Commands.Player", new ArrayList<>());
+ return (ArrayList) rewardConfig.getCommandsPlayer();
}
public ConfigurationSection getConfigData() {
@@ -138,7 +137,7 @@ public File getDataFile() {
* @return the delayed enabled
*/
public boolean getDelayedEnabled() {
- return getConfigData().getBoolean("Delayed.Enabled");
+ return rewardConfig.getDelayedEnabled();
}
/**
@@ -147,11 +146,11 @@ public boolean getDelayedEnabled() {
* @return the delayed hours
*/
public int getDelayedHours() {
- return getConfigData().getInt("Delayed.Hours");
+ return rewardConfig.getDelayedHours();
}
public int getDelayedMilliSeconds() {
- return getConfigData().getInt("Delayed.MilliSeconds");
+ return rewardConfig.getDelayedMilliSeconds();
}
/**
@@ -160,11 +159,11 @@ public int getDelayedMilliSeconds() {
* @return the delayed minutes
*/
public int getDelayedMinutes() {
- return getConfigData().getInt("Delayed.Minutes");
+ return rewardConfig.getDelayedMinutes();
}
public int getDelayedSeconds() {
- return getConfigData().getInt("Delayed.Seconds");
+ return rewardConfig.getDelayedSeconds();
}
/**
@@ -186,7 +185,7 @@ public ConfigurationSection getDisplayItem() {
* @return the choice rewards enabled
*/
public boolean getEnableChoices() {
- return getConfigData().getBoolean("EnableChoices");
+ return rewardConfig.getEnableChoices();
}
public FileConfiguration getFileData() {
@@ -194,7 +193,7 @@ public FileConfiguration getFileData() {
}
public boolean getForceOffline() {
- return getConfigData().getBoolean("ForceOffline");
+ return rewardConfig.getForceOffline();
}
/**
@@ -352,7 +351,7 @@ public String getPermission() {
@SuppressWarnings("unchecked")
public ArrayList getPriority() {
- return (ArrayList) getConfigData().getList("Priority", new ArrayList<>());
+ return (ArrayList) rewardConfig.getPriority();
}
/**
@@ -361,7 +360,7 @@ public ArrayList getPriority() {
* @return the require permission
*/
public boolean getRequirePermission() {
- return getConfigData().getBoolean("RequirePermission");
+ return rewardConfig.getRequirePermission();
}
/**
@@ -370,22 +369,11 @@ public boolean getRequirePermission() {
* @return the reward type
*/
public String getRewardType() {
- String str = getConfigData().getString("RewardType", "BOTH");
- if (str != null) {
- if (str.equalsIgnoreCase("online")) {
- return "ONLINE";
- }
- if (str.equalsIgnoreCase("offline")) {
- return "OFFLINE";
- } else {
- return "BOTH";
- }
- }
- return "BOTH";
+ return rewardConfig.getRewardType();
}
public String getServer() {
- return getConfigData().getString("Server", "");
+ return rewardConfig.getServer();
}
/**
@@ -394,7 +382,7 @@ public String getServer() {
* @return the timed enabled
*/
public boolean getTimedEnabled() {
- return getConfigData().getBoolean("Timed.Enabled");
+ return rewardConfig.getTimedEnabled();
}
/**
@@ -403,7 +391,7 @@ public boolean getTimedEnabled() {
* @return the timed hour
*/
public int getTimedHour() {
- return getConfigData().getInt("Timed.Hour");
+ return rewardConfig.getTimedHour();
}
/**
@@ -412,7 +400,7 @@ public int getTimedHour() {
* @return the timed minute
*/
public int getTimedMinute() {
- return getConfigData().getInt("Timed.Minute");
+ return rewardConfig.getTimedMinute();
}
/**
@@ -422,9 +410,7 @@ public int getTimedMinute() {
*/
@SuppressWarnings("unchecked")
public ArrayList getWorlds() {
-
- return (ArrayList) getConfigData().getList("Worlds", new ArrayList<>());
-
+ return (ArrayList) rewardConfig.getWorlds();
}
public boolean hasRewardFile() {
@@ -432,7 +418,7 @@ public boolean hasRewardFile() {
}
public boolean isDirectlyDefinedReward() {
- return getConfigData().getBoolean("DirectlyDefinedReward");
+ return rewardConfig.isDirectlyDefinedReward();
}
public boolean isRewardFile() {
diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/bukkit/rewards/BukkitRewardConfigReader.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/bukkit/rewards/BukkitRewardConfigReader.java
new file mode 100644
index 0000000000..5ca35dcc4e
--- /dev/null
+++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/bukkit/rewards/BukkitRewardConfigReader.java
@@ -0,0 +1,58 @@
+package com.bencodez.advancedcore.bukkit.rewards;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.bukkit.configuration.ConfigurationSection;
+
+import com.bencodez.advancedcore.core.rewards.RewardConfigReader;
+import com.bencodez.simpleapi.bukkit.config.BukkitStructuredConfigView;
+
+/**
+ * Keeps native Bukkit getter/default and raw-list behavior while the reward
+ * paths and interpretation live in the shared reader. It deliberately does not
+ * add a case-insensitive wrapper: the supplied section already owns that policy.
+ */
+public final class BukkitRewardConfigReader extends RewardConfigReader {
+ private final Supplier extends ConfigurationSection> source;
+
+ public BukkitRewardConfigReader(Supplier extends ConfigurationSection> source) {
+ super(() -> new BukkitStructuredConfigView(source.get()));
+ this.source = Objects.requireNonNull(source, "source");
+ }
+
+ private ConfigurationSection section() {
+ return Objects.requireNonNull(source.get(), "configuration");
+ }
+
+ // Explicit fallback overloads do not have the same default-tree behavior
+ // as these legacy no-fallback Bukkit getters.
+ @Override
+ protected boolean readBoolean(String path) {
+ return section().getBoolean(path);
+ }
+
+ @Override
+ protected int readInt(String path) {
+ return section().getInt(path);
+ }
+
+ @Override
+ protected double readDouble(String path) {
+ return section().getDouble(path);
+ }
+
+ @Override
+ protected boolean isList(String path) {
+ return section().isList(path);
+ }
+
+ @Override
+ protected List> readList(String path) {
+ // No copying, filtering, coercion or change to the legacy ArrayList
+ // cast in RewardFileData. The empty fallback is fresh on every read.
+ return section().getList(path, new ArrayList<>());
+ }
+}
diff --git a/AdvancedCore/src/main/java/com/bencodez/advancedcore/core/rewards/RewardConfigReader.java b/AdvancedCore/src/main/java/com/bencodez/advancedcore/core/rewards/RewardConfigReader.java
new file mode 100644
index 0000000000..a5ff9899e2
--- /dev/null
+++ b/AdvancedCore/src/main/java/com/bencodez/advancedcore/core/rewards/RewardConfigReader.java
@@ -0,0 +1,185 @@
+package com.bencodez.advancedcore.core.rewards;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import com.bencodez.simpleapi.core.config.StructuredConfigView;
+import com.bencodez.simpleapi.core.config.StructuredConfigView.Kind;
+
+/**
+ * Platform-neutral interpretation of reward settings. This class neither loads
+ * files nor executes rewards. The supplied view owns casing, paths and typed
+ * conversion rules; wrap it in SimpleAPI's case-insensitive view when required.
+ *
+ *
The supplier is evaluated on reads, not construction, so replacing a
+ * configuration does not leave this reader bound to a stale section. A live
+ * view is not made thread-safe by this reader.
+ */
+public class RewardConfigReader {
+ private final Supplier extends StructuredConfigView> source;
+
+ public RewardConfigReader(Supplier extends StructuredConfigView> source) {
+ this.source = Objects.requireNonNull(source, "source");
+ }
+
+ protected final StructuredConfigView config() {
+ return Objects.requireNonNull(source.get(), "configuration");
+ }
+
+ /** Adapter seam for native getters with implicit default-tree semantics. */
+ protected boolean readBoolean(String path) {
+ return config().getBoolean(path, false);
+ }
+
+ protected int readInt(String path) {
+ return config().getInt(path, 0);
+ }
+
+ protected double readDouble(String path) {
+ return config().getDouble(path, 0);
+ }
+
+ protected boolean isList(String path) {
+ return config().kind(path) == Kind.LIST;
+ }
+
+ /**
+ * Plain-data lists retain order and value types, without string coercion or
+ * filtering. The default implementation returns a detached, unmodifiable
+ * list. A native compatibility adapter may preserve its existing live list.
+ */
+ protected List> readList(String path) {
+ StructuredConfigView view = config();
+ if (view.kind(path) != Kind.LIST) {
+ return Collections.emptyList();
+ }
+ return (List>) view.value(path);
+ }
+
+ public double getChance() {
+ return readDouble("Chance");
+ }
+
+ public Set getChoices() {
+ if (config().isConfigurationSection("Choices")) {
+ return config().getConfigurationSection("Choices").getKeys(false);
+ }
+ return new HashSet<>();
+ }
+
+ public String getChoicesRewardsPath(String choice) {
+ return "Choices." + choice + ".Rewards";
+ }
+
+ public List> getCommandsConsole() {
+ if (isList("Commands")) {
+ return readList("Commands");
+ }
+ return readList("Commands.Console");
+ }
+
+ public List> getCommandsPlayer() {
+ return readList("Commands.Player");
+ }
+
+ public boolean getDelayedEnabled() {
+ return readBoolean("Delayed.Enabled");
+ }
+
+ public int getDelayedHours() {
+ return readInt("Delayed.Hours");
+ }
+
+ public int getDelayedMilliSeconds() {
+ return readInt("Delayed.MilliSeconds");
+ }
+
+ public int getDelayedMinutes() {
+ return readInt("Delayed.Minutes");
+ }
+
+ public int getDelayedSeconds() {
+ return readInt("Delayed.Seconds");
+ }
+
+ public boolean getEnableChoices() {
+ return readBoolean("EnableChoices");
+ }
+
+ public boolean getForceOffline() {
+ return readBoolean("ForceOffline");
+ }
+
+ public String getPermission(String rewardName) {
+ return config().getString("Permission", "AdvancedCore.Reward." + rewardName);
+ }
+
+ public List> getPriority() {
+ return readList("Priority");
+ }
+
+ public boolean getRequirePermission() {
+ return readBoolean("RequirePermission");
+ }
+
+ public String getRewardType() {
+ String type = config().getString("RewardType", "BOTH");
+ if ("online".equalsIgnoreCase(type)) {
+ return "ONLINE";
+ }
+ if ("offline".equalsIgnoreCase(type)) {
+ return "OFFLINE";
+ }
+ return "BOTH";
+ }
+
+ public String getServer() {
+ return config().getString("Server", "");
+ }
+
+ public boolean getTimedEnabled() {
+ return readBoolean("Timed.Enabled");
+ }
+
+ public int getTimedHour() {
+ return readInt("Timed.Hour");
+ }
+
+ public int getTimedMinute() {
+ return readInt("Timed.Minute");
+ }
+
+ public List> getWorlds() {
+ return readList("Worlds");
+ }
+
+ public boolean isDirectlyDefinedReward() {
+ return readBoolean("DirectlyDefinedReward");
+ }
+
+ /** Native objects remain in the adapter; these methods return read views. */
+ public StructuredConfigView getChoicesItem(String choice) {
+ return config().getConfigurationSection("Choices." + choice + ".DisplayItem");
+ }
+
+ public StructuredConfigView getChoiceRewards(String choice) {
+ return config().getConfigurationSection(getChoicesRewardsPath(choice));
+ }
+
+ public StructuredConfigView getDisplayItem() {
+ return config().getConfigurationSection("DisplayItem");
+ }
+
+ public StructuredConfigView getItemSection(String item) {
+ return config().getConfigurationSection("Items." + item);
+ }
+
+ /** Literal keys allow definition names containing the path separator. */
+ public StructuredConfigView definitionAt(String... keys) {
+ return config().structuredAt(keys);
+ }
+}
diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java
index c2fc3069f1..25ca5401a6 100644
--- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java
+++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java
@@ -24,15 +24,4 @@ void javadocPublisherUsesImmutableRevisionAndExplicitPermissions() throws IOExce
assertTrue(workflow.contains("maven-javadoc-plugin:3.12.0:javadoc"));
assertFalse(workflow.contains("Javadoc-publisher.yml@main"));
}
-
- @Test
- void simpleApiDependencyUsesImmutableSnapshotBuild() throws IOException {
- String pom = Files.readString(Path.of("pom.xml"));
- int dependency = pom.indexOf("simpleapi");
-
- assertTrue(dependency >= 0);
- String declaration = pom.substring(dependency, Math.min(pom.length(), dependency + 200));
- assertFalse(declaration.contains("SNAPSHOT"));
- assertTrue(declaration.contains("1.0.2-20260905.234759-10"));
- }
}
diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/rewards/CoreRewardConfigFixture.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/rewards/CoreRewardConfigFixture.java
new file mode 100644
index 0000000000..938c46e538
--- /dev/null
+++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/rewards/CoreRewardConfigFixture.java
@@ -0,0 +1,178 @@
+package com.bencodez.advancedcore.tests.rewards;
+
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.bencodez.advancedcore.core.rewards.RewardConfigReader;
+import com.bencodez.simpleapi.core.config.StructuredConfigView;
+import com.bencodez.simpleapi.core.config.StructuredConfigView.Kind;
+
+/** JDK-only behavioral fixture; no Bukkit, Configurate, Mockito or JUnit linkage. */
+public final class CoreRewardConfigFixture {
+ private CoreRewardConfigFixture() { }
+
+ public static void main(String[] args) {
+ run();
+ System.out.println("CoreRewardConfigFixture: 10 scenarios passed");
+ }
+
+ public static void run() {
+ Map values = new LinkedHashMap<>();
+ AtomicReference source = new AtomicReference<>(view(values));
+ RewardConfigReader reader = new RewardConfigReader(source::get);
+
+ equal(0.0, reader.getChance());
+ equal("BOTH", reader.getRewardType());
+ equal("", reader.getServer());
+ equal("AdvancedCore.Reward.Daily", reader.getPermission("Daily"));
+ equal(List.of(), reader.getCommandsConsole());
+ equal(false, reader.getDelayedEnabled());
+ equal(0, reader.getTimedHour());
+
+ List