diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..bf02bb5
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..43e0905
--- /dev/null
+++ b/.project
@@ -0,0 +1,15 @@
+
+
+ DivinityEconomy
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..e0f15db
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "automatic"
+}
\ No newline at end of file
diff --git a/DivinityEconomy.eml b/DivinityEconomy.eml
new file mode 100644
index 0000000..54cded0
--- /dev/null
+++ b/DivinityEconomy.eml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DivinityEconomy.userlibraries b/DivinityEconomy.userlibraries
new file mode 100644
index 0000000..7ac4270
--- /dev/null
+++ b/DivinityEconomy.userlibraries
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index c1fb736..3517698 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.divinitycraft
DivinityEconomy
- 3.5.7
+ 3.6.Kodak
DivinityEconomy - The Global Market
https://divinitycraft.org
@@ -37,11 +37,6 @@
-
- com.tchristofferson
- ConfigUpdater
- 2.1-SNAPSHOT
-
org.mariuszgromada.math
MathParser.org-mXparser
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/DEPlugin.java b/src/main/java/org/divinitycraft/divinityeconomy/DEPlugin.java
index fa8a028..0aaeffd 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/DEPlugin.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/DEPlugin.java
@@ -174,6 +174,7 @@ public void onEnable() {
new ESetValue(this);
new ESetValueTC(this);
new Reload(this);
+ new Modded(this);
new ReloadTC(this);
new Save(this);
new SaveTC(this);
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/commands/admin/Modded.java b/src/main/java/org/divinitycraft/divinityeconomy/commands/admin/Modded.java
new file mode 100644
index 0000000..1dc3484
--- /dev/null
+++ b/src/main/java/org/divinitycraft/divinityeconomy/commands/admin/Modded.java
@@ -0,0 +1,36 @@
+package org.divinitycraft.divinityeconomy.commands.admin;
+
+import org.divinitycraft.divinityeconomy.DEPlugin;
+import org.divinitycraft.divinityeconomy.commands.DivinityCommand;
+import org.divinitycraft.divinityeconomy.config.Setting;
+import org.divinitycraft.divinityeconomy.lang.LangEntry;
+import org.bukkit.entity.Player;
+
+public class Modded extends DivinityCommand {
+
+ public Modded(DEPlugin main) {
+ super(main, "modded", true, Setting.COMMAND_RELOAD_ENABLE_BOOLEAN);
+ }
+
+ @Override
+ public boolean onPlayerCommand(Player sender, String[] args) {
+ int reloaded = getMain().getMatMan().reloadModdedItems();
+ if (reloaded > 0) {
+ getMain().getConsole().info(sender, "Reloaded %d modded materials", reloaded);
+ } else {
+ getMain().getConsole().info(sender, "No modded materials were reloaded");
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onConsoleCommand(String[] args) {
+ int reloaded = getMain().getMatMan().reloadModdedItems();
+ if (reloaded > 0) {
+ getMain().getConsole().info("Reloaded %d modded materials", reloaded);
+ } else {
+ getMain().getConsole().info("No modded materials were reloaded");
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/config/ConfigManager.java b/src/main/java/org/divinitycraft/divinityeconomy/config/ConfigManager.java
index a95fe56..dfa59ca 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/config/ConfigManager.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/config/ConfigManager.java
@@ -1,13 +1,12 @@
package org.divinitycraft.divinityeconomy.config;
-import com.tchristofferson.configupdater.ConfigUpdater;
import org.divinitycraft.divinityeconomy.DEPlugin;
import org.divinitycraft.divinityeconomy.DivinityModule;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
-import java.io.IOException;
+
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
@@ -44,8 +43,13 @@ public void init() {
this.saveFile(config, configFile);
try {
- ConfigUpdater.update(getMain(), configFile, this.getFile(configFile), Collections.emptyList());
- } catch (IOException e) {
+ try {
+ Class> cfgUpd = Class.forName("com.tchristofferson.configupdater.ConfigUpdater");
+ java.lang.reflect.Method m = cfgUpd.getMethod("update", org.bukkit.plugin.Plugin.class, String.class, java.io.File.class, java.util.List.class);
+ m.invoke(null, getMain(), configFile, this.getFile(configFile), Collections.emptyList());
+ } catch (ClassNotFoundException ignored) {
+ }
+ } catch (Exception e) {
e.printStackTrace();
}
getMain().reloadConfig();
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/market/TokenManager.java b/src/main/java/org/divinitycraft/divinityeconomy/market/TokenManager.java
index f1d1faa..d179702 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/market/TokenManager.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/market/TokenManager.java
@@ -1,6 +1,5 @@
package org.divinitycraft.divinityeconomy.market;
-import com.tchristofferson.configupdater.ConfigUpdater;
import org.divinitycraft.divinityeconomy.Constants;
import org.divinitycraft.divinityeconomy.DEPlugin;
import org.divinitycraft.divinityeconomy.DivinityModule;
@@ -13,6 +12,8 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
@@ -484,54 +485,83 @@ public void loadAliases() {
aliasFile.createNewFile();
}
- // Run Update
- ConfigUpdater.update(getMain(), this.aliasFile, aliasFile, Collections.emptyList());
+ // Run Update via reflection if ConfigUpdater is available; otherwise skip
+ try {
+ Class> cfgUpd = Class.forName("com.tchristofferson.configupdater.ConfigUpdater");
+ java.lang.reflect.Method m = cfgUpd.getMethod("update", org.bukkit.plugin.Plugin.class, String.class, java.io.File.class, java.util.List.class);
+ m.invoke(null, getMain(), this.aliasFile, aliasFile, Collections.emptyList());
+ } catch (ClassNotFoundException ignored) {
+ } catch (Exception ignored) {
+ }
} catch (IOException e) {
e.printStackTrace();
}
- // Load config
- FileConfiguration config = this.getConfMan().loadFile(this.aliasFile);
+ // Load config with error handling for corrupted YAML
+ FileConfiguration config = null;
+ try {
+ config = this.getConfMan().loadFile(this.aliasFile);
+ } catch (Exception e) {
+ this.getConsole().warn("Failed to load %s (YAML may be corrupted): %s", this.aliasFile, e.getMessage());
+ // Create backup and start fresh
+ try {
+ File backup = new File(this.getConfMan().getFile(this.aliasFile).getAbsolutePath() + ".backup");
+ Files.copy(
+ this.getConfMan().getFile(this.aliasFile).toPath(),
+ backup.toPath(),
+ StandardCopyOption.REPLACE_EXISTING
+ );
+ this.getConsole().warn("Created backup at %s", backup.getAbsolutePath());
+ } catch (Exception ignored) {
+ }
+ config = this.getConfMan().readResource(this.aliasFile);
+ }
// Store the alias -> ItemID pairs
Map values = new ConcurrentHashMap<>();
// Store ItemID -> arraylist to migrate to ItemID -> String[] pairs
Map> revBuildAliasValues = new ConcurrentHashMap<>();
Map> revResultAliasValues = new ConcurrentHashMap<>();
- // Loop through keys in config
- for (String key : config.getKeys(false)) {
- // Get string item name
- // Format key/value
- key = key.toLowerCase().replace(" ", "");
- String value = config.getString(key);
-
- // If value is null, skip
- if (value == null) {
- if (!this.getConfMan().getBoolean(Setting.IGNORE_ALIAS_ERRORS_BOOLEAN))
- this.getConsole().warn("Bad config value in %s: '%s' - Corresponding value is null", this.aliasFile, key);
- continue;
- }
-
- value = value.toLowerCase().replace(" ", "");
-
- // If the value is not stored in the items map, skip
- if (this.getItem(value) == null) {
- if (!this.getConfMan().getBoolean(Setting.IGNORE_ALIAS_ERRORS_BOOLEAN))
- this.getConsole().warn("Bad config value in %s: '%s' - Corresponding value '%s' does not exist.", this.aliasFile, key, value);
- continue;
- }
-
- // Store the value under the key
- values.put(key, value);
- if (!values.containsKey(key)) values.put(key, key);
-
- // Store the value under the key (Value = materialID, key = alias)
- if (!revBuildAliasValues.containsKey(value)) {
- revBuildAliasValues.put(value, new HashSet<>());
- revBuildAliasValues.get(value).add(value);
+
+ // Loop through keys in config safely
+ try {
+ for (String key : config.getKeys(false)) {
+ // Get string item name
+ // Format key/value
+ key = key.toLowerCase().replace(" ", "");
+ String value = config.getString(key);
+
+ // If value is null, skip
+ if (value == null) {
+ if (!this.getConfMan().getBoolean(Setting.IGNORE_ALIAS_ERRORS_BOOLEAN))
+ this.getConsole().warn("Bad config value in %s: '%s' - Corresponding value is null", this.aliasFile, key);
+ continue;
+ }
+
+ value = value.toLowerCase().replace(" ", "");
+
+ // If the value is not stored in the items map, skip
+ if (this.getItem(value) == null) {
+ if (!this.getConfMan().getBoolean(Setting.IGNORE_ALIAS_ERRORS_BOOLEAN))
+ this.getConsole().warn("Bad config value in %s: '%s' - Corresponding value '%s' does not exist.", this.aliasFile, key, value);
+ continue;
+ }
+
+ // Store the value under the key
+ values.put(key, value);
+ if (!values.containsKey(key)) values.put(key, key);
+
+ // Store the value under the key (Value = materialID, key = alias)
+ if (!revBuildAliasValues.containsKey(value)) {
+ revBuildAliasValues.put(value, new HashSet<>());
+ revBuildAliasValues.get(value).add(value);
+ }
+ revBuildAliasValues.get(value).add(key);
}
- revBuildAliasValues.get(value).add(key);
+ } catch (Exception e) {
+ this.getConsole().warn("Error loading aliases from %s: %s", this.aliasFile, e.getMessage());
}
+
this.aliasMap = values;
// Migrate all keys-arraylist pairs to key-array pairs
@@ -541,6 +571,67 @@ public void loadAliases() {
this.getConsole().info(LangEntry.MARKET_ItemAliasesLoaded.get(getMain()), values.size(), this.aliasFile);
}
+ /**
+ * Adds an alias for an item to the runtime alias maps.
+ * The alias will be persisted to file at the next saveAliases() call.
+ * @param alias - The alias name (will be lowercased)
+ * @param itemId - The item ID to point to
+ */
+ public void addAlias(String alias, String itemId) {
+ if (alias == null || itemId == null) return;
+
+ try {
+ alias = alias.toLowerCase().replace(" ", "");
+ itemId = itemId.toLowerCase().replace(" ", "");
+
+ // Check if item exists
+ if (this.getItem(itemId) == null) {
+ if (this.getConsole() != null) {
+ this.getConsole().warn("Cannot add alias '%s': item '%s' does not exist", alias, itemId);
+ }
+ return;
+ }
+
+ // Add to aliasMap
+ this.aliasMap.put(alias, itemId);
+
+ // Add to revAliasMap
+ if (!this.revAliasMap.containsKey(itemId)) {
+ this.revAliasMap.put(itemId, new HashSet<>());
+ this.revAliasMap.get(itemId).add(itemId);
+ }
+ this.revAliasMap.get(itemId).add(alias);
+ } catch (Exception e) {
+ if (this.getConsole() != null) {
+ this.getConsole().warn("Failed to add alias '%s': %s", alias, e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Saves all aliases to the alias config file asynchronously.
+ * This prevents blocking the main server thread.
+ */
+ public void saveAliases() {
+ // Run save in a separate thread to prevent blocking server
+ new Thread(() -> {
+ try {
+ FileConfiguration config = this.getConfMan().loadFile(this.aliasFile);
+
+ // Write all aliases from the current aliasMap
+ for (String alias : this.aliasMap.keySet()) {
+ config.set(alias, this.aliasMap.get(alias));
+ }
+
+ this.getConfMan().saveFile(config, this.aliasFile);
+ } catch (Exception e) {
+ if (this.getConsole() != null) {
+ this.getConsole().warn("Failed to save aliases: %s", e.getMessage());
+ }
+ }
+ }).start();
+ }
+
/**
* Loads the items from the items file into the items variable
*/
@@ -553,8 +644,14 @@ public void loadItems() {
itemFile.createNewFile();
}
- // Run Update
- ConfigUpdater.update(getMain(), this.itemFile, this.getConfMan().getFile(this.itemFile), Collections.emptyList());
+ // Run Update via reflection if ConfigUpdater is available; otherwise skip
+ try {
+ Class> cfgUpd = Class.forName("com.tchristofferson.configupdater.ConfigUpdater");
+ java.lang.reflect.Method m = cfgUpd.getMethod("update", org.bukkit.plugin.Plugin.class, String.class, java.io.File.class, java.util.List.class);
+ m.invoke(null, getMain(), this.itemFile, this.getConfMan().getFile(this.itemFile), Collections.emptyList());
+ } catch (ClassNotFoundException ignored) {
+ } catch (Exception ignored) {
+ }
} catch (IOException e) {
e.printStackTrace();
}
@@ -570,6 +667,7 @@ public void loadItems() {
Map values = new ConcurrentHashMap<>();
// Loop through keys and get data
// Add data to a MaterialData and put in HashMap under key
+ // First load vanilla items from defaultConf
for (String key : defaultConf.getKeys(false)) {
ConfigurationSection data = this.config.getConfigurationSection(key);
@@ -595,6 +693,33 @@ public void loadItems() {
this.totalItems += itemData.getQuantity();
values.put(key, itemData);
}
+
+ // Also load modded items that exist in config but not in defaultConf
+ // These are items that were dynamically imported and saved to materials.yml
+ for (String key : this.config.getKeys(false)) {
+ if (defaultConf.contains(key)) continue; // Skip vanilla items already loaded
+
+ ConfigurationSection data = this.config.getConfigurationSection(key);
+ if (data == null) {
+ if (!this.getConfMan().getBoolean(Setting.IGNORE_ITEM_ERRORS_BOOLEAN))
+ this.getConsole().warn("Bad config value in %s: '%s' - Data is null, skipping modded item.", this.itemFile, key);
+ continue;
+ }
+
+ // For modded items, use the item config as default since they don't have a vanilla default
+ MarketableToken itemData = this.loadItem(key, data, data);
+ if (!itemData.check()) {
+ if (!this.getConfMan().getBoolean(Setting.IGNORE_ITEM_ERRORS_BOOLEAN))
+ this.getConsole().warn("Bad config value in %s for '%s': %s (modded item)", this.itemFile, key, itemData.getError());
+ continue;
+ }
+
+ String formattedKey = key.toLowerCase().replace(" ", "");
+ this.defaultTotalItems += itemData.getDefaultQuantity();
+ this.totalItems += itemData.getQuantity();
+ values.put(formattedKey, itemData);
+ }
+
// Copy values into items
this.itemMap = values;
this.getConsole().info(LangEntry.MARKET_ItemsLoaded.get(getMain()), values.size(), this.totalItems, this.defaultTotalItems, this.itemFile);
@@ -650,12 +775,25 @@ public void saveItems() {
private void saveFile() {
// load back all info
FileConfiguration config = this.getConfMan().loadFile(this.itemFile);
+
+ // Save all existing keys that are in the itemMap
for (String key : config.getKeys(false)) {
String internalKey = key.toLowerCase().replace(" ", "");
if (!this.itemMap.containsKey(internalKey)) continue;
config.set(key, this.config.get(key));
}
+
+ // Also save any new items that exist in the itemMap but weren't in the original config
+ for (String mapKey : this.itemMap.keySet()) {
+ MarketableToken token = this.itemMap.get(mapKey);
+ String originalKey = token.getID(); // Use original ID as the key in the file
+
+ // Only add if not already processed above
+ if (!config.contains(originalKey)) {
+ config.set(originalKey, this.config.getConfigurationSection(mapKey));
+ }
+ }
this.getConfMan().saveFile(config, this.itemFile);
}
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MarketableMaterial.java b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MarketableMaterial.java
index 09dd468..8b8d609 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MarketableMaterial.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MarketableMaterial.java
@@ -6,6 +6,7 @@
import org.divinitycraft.divinityeconomy.market.items.MarketableItem;
import org.divinitycraft.divinityeconomy.player.PlayerManager;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -18,15 +19,69 @@ public abstract class MarketableMaterial extends MarketableItem {
public MarketableMaterial(DEPlugin main, MaterialManager itemManager, String ID, ConfigurationSection config, ConfigurationSection defaultConfig) {
super(main, itemManager, ID, config, defaultConfig);
- Material material;
- try {
- material = Material.valueOf(
- config.getString(MapKeys.MATERIAL_ID.key, ID)
- );
- } catch (IllegalArgumentException | NullPointerException exception) {
- material = null;
+ String materialId = config.getString(MapKeys.MATERIAL_ID.key, ID);
+ Material mat = null;
+ if (materialId != null) {
+ try {
+ mat = Material.valueOf(materialId);
+ } catch (IllegalArgumentException ignored) {
+ }
+
+ if (mat == null) {
+ try {
+ mat = Material.matchMaterial(materialId);
+ } catch (Throwable ignored) {
+ }
+ }
+
+ if (mat == null) {
+ try {
+ NamespacedKey key = NamespacedKey.fromString(materialId);
+ if (key != null) {
+ try {
+ mat = Material.matchMaterial(key.toString());
+ } catch (Throwable ignored) {
+ }
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+
+ // Final fallback: iterate registered Materials and match by NamespacedKey or key name (for modded materials)
+ if (mat == null && materialId != null) {
+ for (Material m : Material.values()) {
+ try {
+ NamespacedKey mk = m.getKey();
+ if (mk != null) {
+ if (mk.toString().equalsIgnoreCase(materialId) || mk.getKey().equalsIgnoreCase(materialId)) {
+ mat = m;
+ break;
+ }
+ }
+ if (m.name().equalsIgnoreCase(materialId)) {
+ mat = m;
+ break;
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+ }
+ }
+
+ this.material = mat;
+
+ // If this material couldn't be resolved to a vanilla Bukkit Material
+ // treat it as a modded/non-vanilla item by defaulting quantity to 0
+ // and disallowing it unless the server admin explicitly sets values
+ // in the materials.yml config.
+ if (this.material == null) {
+ if (!this.itemConfig.contains(MapKeys.QUANTITY.key)) {
+ this.itemConfig.set(MapKeys.QUANTITY.key, 0);
+ }
+ if (!this.itemConfig.contains(MapKeys.ALLOWED.key)) {
+ this.itemConfig.set(MapKeys.ALLOWED.key, false);
+ }
}
- this.material = material;
}
@@ -75,6 +130,9 @@ public Material getMaterial() {
* @return
*/
public ItemStack[] getItemStacks(int amount) {
+ // If material couldn't be resolved, return empty array
+ if (this.getMaterial() == null) return new ItemStack[0];
+
// Get the max stack size and the number of stacks
int maxStackSize = this.getMaterial().getMaxStackSize();
int stacks = (int) Math.ceil((double) amount / maxStackSize);
@@ -99,6 +157,7 @@ public ItemStack[] getItemStacks(int amount) {
* @return ItemStack[] - An array of the ItemStack's in the player of material
*/
public ItemStack[] getMaterialSlots(Player player) {
+ if (this.material == null) return new ItemStack[0];
Map inventory = player.getInventory().all(this.material);
ArrayList itemStacks = new ArrayList<>();
for (ItemStack itemStack : inventory.values()) {
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MaterialManager.java b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MaterialManager.java
index 97d155b..421f0c6 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MaterialManager.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/MaterialManager.java
@@ -3,6 +3,7 @@
import org.divinitycraft.divinityeconomy.DEPlugin;
import org.divinitycraft.divinityeconomy.config.Setting;
import org.divinitycraft.divinityeconomy.lang.LangEntry;
+import org.divinitycraft.divinityeconomy.market.MapKeys;
import org.divinitycraft.divinityeconomy.market.MarketableToken;
import org.divinitycraft.divinityeconomy.market.items.ItemManager;
import org.divinitycraft.divinityeconomy.utils.Converter;
@@ -13,6 +14,9 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Material;
public abstract class MaterialManager extends ItemManager {
@@ -56,6 +60,16 @@ public void run() {
this.saveTimer.runTaskTimerAsynchronously(getMain(), timer, timer);
this.loadItems();
this.loadAliases();
+ // Schedule a delayed attempt to resolve modded (non-vanilla) materials
+ // Some hybrid servers (NeoForge/Arclight) may register modded materials
+ // after plugin enable; this will re-attempt resolution shortly after.
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ int reloaded = reloadModdedItems();
+ if (reloaded > 0) getConsole().info("Reloaded %d modded materials", reloaded);
+ }
+ }.runTaskLater(getMain(), 200L); // ~10 seconds
// this.checkLoadedItems(); - This is for internal debugging only. Hi! :)
this.getMarkMan().addManager(this);
}
@@ -188,4 +202,147 @@ public void checkLoadedItems() {
public Set getLocalKeys() {
return new HashSet<>();
}
+
+ /**
+ * Re-attempt to resolve modded / non-vanilla materials which previously
+ * failed to resolve at load time. This creates fresh MarketableMaterial
+ * instances via {@link #loadItem} and replaces entries that now resolve.
+ *
+ * @return number of items successfully reloaded with a resolved Material
+ */
+ public int reloadModdedItems() {
+ int reloaded = 0;
+ try {
+ // First, attempt to resolve any existing unresolved entries (previous behaviour)
+ // Collect candidates whose Material is currently unresolved
+ Set candidates = new HashSet<>();
+ for (String key : this.itemMap.keySet()) {
+ MarketableToken token = this.itemMap.get(key);
+ if (!(token instanceof MarketableMaterial)) continue;
+ MarketableMaterial mat = (MarketableMaterial) token;
+ if (mat.getMaterial() == null) candidates.add(key);
+ }
+
+ if (candidates.isEmpty()) {
+ this.getConsole().debug("No unresolved modded materials found to reload.");
+ // Continue - even if there were no unresolved entries, we still want to scan for new modded materials
+ } else {
+ this.getConsole().info("Found %d unresolved modded material entries to attempt reload.", candidates.size());
+
+ for (String key : candidates) {
+ MarketableMaterial token = (MarketableMaterial) this.itemMap.get(key);
+ String materialId = token.getItemConfig().getString(MapKeys.MATERIAL_ID.key, token.getID());
+ this.getConsole().info("Attempting to reload '%s' (configured MATERIAL_ID='%s')", key, materialId);
+
+ MarketableMaterial newMat = (MarketableMaterial) this.loadItem(token.getID(), token.getItemConfig(), token.getDefaultItemConfig());
+ if (newMat != null && newMat.check() && newMat.getMaterial() != null) {
+ // adjust totals
+ this.defaultTotalItems -= token.getDefaultQuantity();
+ this.totalItems -= token.getQuantity();
+ this.defaultTotalItems += newMat.getDefaultQuantity();
+ this.totalItems += newMat.getQuantity();
+
+ ((Map) this.itemMap).put(key, newMat);
+ reloaded++;
+ this.getConsole().info("Successfully reloaded '%s' -> resolved as %s", key, newMat.getMaterial().name());
+ } else {
+ this.getConsole().warn("Failed to resolve material for '%s' (MATERIAL_ID='%s')", key, materialId);
+ }
+ }
+ }
+
+ // Second, scan the current server Material registry for non-vanilla (modded) materials
+ // and import any that are not yet present in the market config.
+ int imported = 0;
+ Set discovered = new HashSet<>();
+ for (Material m : Material.values()) {
+ try {
+ NamespacedKey k = m.getKey();
+ if (k == null) continue;
+ String namespace = k.getNamespace();
+ if (namespace == null) continue;
+ if ("minecraft".equals(namespace)) continue; // skip vanilla
+
+ String nsKey = k.toString(); // modid:item
+ // avoid duplicates
+ if (discovered.contains(nsKey)) continue;
+ discovered.add(nsKey);
+
+ // Check if already represented in itemMap (by MATERIAL_ID or ID)
+ // OR if it already exists in the config file (user may have edited it manually)
+ boolean exists = false;
+
+ // Check itemMap first
+ for (MarketableToken token : this.itemMap.values()) {
+ String mid = token.getItemConfig().getString(MapKeys.MATERIAL_ID.key, token.getID());
+ if (mid == null) continue;
+ if (mid.equalsIgnoreCase(nsKey) || mid.equalsIgnoreCase(m.name()) || mid.equalsIgnoreCase(k.getKey())) {
+ exists = true;
+ break;
+ }
+ }
+
+ // Also check the config file itself to avoid overwriting user edits
+ if (!exists && this.config.contains(nsKey)) {
+ exists = true;
+ }
+
+ if (exists) continue;
+
+ // Create config section for this modded material with sensible defaults
+ this.getConsole().info("Importing modded material: %s", nsKey);
+ this.config.set(nsKey + "." + MapKeys.MATERIAL_ID.key, nsKey);
+ this.config.set(nsKey + "." + MapKeys.QUANTITY.key, 0);
+ this.config.set(nsKey + "." + MapKeys.ALLOWED.key, true);
+
+ // Create a minimal default section for loadItem
+ YamlConfiguration defaultSec = new YamlConfiguration();
+ defaultSec.set(MapKeys.QUANTITY.key, 0);
+ defaultSec.set(MapKeys.ALLOWED.key, true);
+
+ MarketableMaterial newMat = (MarketableMaterial) this.loadItem(nsKey, this.config.getConfigurationSection(nsKey), defaultSec);
+ if (newMat != null && newMat.check() && newMat.getMaterial() != null) {
+ // add into map and adjust totals
+ this.defaultTotalItems += newMat.getDefaultQuantity();
+ this.totalItems += newMat.getQuantity();
+ ((Map) this.itemMap).put(nsKey.toLowerCase().replace(" ", ""), newMat);
+ imported++;
+ this.getConsole().info("Imported modded material '%s' -> %s", nsKey, newMat.getMaterial().name());
+
+ // Automatically create an alias using just the key part (after the colon)
+ // e.g., "cobblemon:mago_berry" -> alias "mago_berry"
+ // Alias will be saved later in batch
+ try {
+ int colonIdx = nsKey.indexOf(':');
+ if (colonIdx > 0 && colonIdx < nsKey.length() - 1) {
+ String keyPart = nsKey.substring(colonIdx + 1);
+ this.addAlias(keyPart, nsKey);
+ }
+ } catch (Exception e) {
+ this.getConsole().warn("Failed to create alias for '%s': %s", nsKey, e.getMessage());
+ }
+ } else {
+ // Rollback config if load failed
+ this.config.set(nsKey, null);
+ this.getConsole().warn("Failed to import modded material '%s'", nsKey);
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+
+ if (imported > 0) {
+ // Persist new entries
+ this.saveItems();
+ this.getConsole().info("Imported %d new modded materials into %s", imported, this.itemFile);
+ reloaded += imported;
+
+ // Save all created aliases at once
+ this.saveAliases();
+ this.getConsole().info("Saved aliases for imported modded materials");
+ }
+ } catch (Exception e) {
+ this.getConsole().warn("Failed to reload modded materials: %s", e.getMessage());
+ }
+ return reloaded;
+ }
}
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/BlockManager.java b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/BlockManager.java
index 5984ad8..b035143 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/BlockManager.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/BlockManager.java
@@ -9,7 +9,6 @@
import org.divinitycraft.divinityeconomy.market.items.materials.MaterialValueResponse;
import net.milkbowl.vault.economy.EconomyResponse.ResponseType;
import org.bukkit.Material;
-import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
@@ -90,11 +89,9 @@ public void init() {
@Override
public Set getLocalKeys() {
return Arrays.stream(Material.values())
- .map(Material::getKey)
- .map(NamespacedKey::getKey)
- .map(Object::toString)
- .map(String::toUpperCase)
- .collect(Collectors.toSet());
+ .map(Material::name)
+ .map(String::toUpperCase)
+ .collect(Collectors.toSet());
}
diff --git a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/MarketableBlock.java b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/MarketableBlock.java
index 3cd4353..2b9bad7 100644
--- a/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/MarketableBlock.java
+++ b/src/main/java/org/divinitycraft/divinityeconomy/market/items/materials/block/MarketableBlock.java
@@ -32,12 +32,20 @@ public MarketableBlock(DEPlugin main, MaterialManager itemManager, String ID, Co
*/
@Override
public boolean check() {
- return this.material != null;
+ // Allow market entries for modded/non-vanilla items (material may be null)
+ return true;
}
@Override
public ItemStack getItemStack(int amount) {
- return new ItemStack(this.getMaterial(), amount);
+ if (this.getMaterial() != null) {
+ return new ItemStack(this.getMaterial(), amount);
+ }
+
+ // Material couldn't be resolved (modded item). Return a placeholder stack.
+ // Admins can override this in materials.yml to point to a valid material
+ // if they want buy/sell functionality.
+ return new ItemStack(org.bukkit.Material.STONE, amount);
}
/**
@@ -67,6 +75,7 @@ public boolean equals(ItemStack itemStack) {
if (itemMeta instanceof PotionMeta) {
return false;
} else {
+ if (this.getMaterial() == null) return false;
return itemStack.getType().equals(this.getMaterial());
}
}
diff --git a/src/resources/config.yml b/src/resources/config.yml
index 30d3fcf..a16ccd8 100644
--- a/src/resources/config.yml
+++ b/src/resources/config.yml
@@ -98,7 +98,7 @@
# Main Settings
Main:
# This is the plugin config version
- Version: 3.5.7
+ Version: 3.6.Kodak
# Should placeholder api support be enabled? ( use "true" / "false" )
Enable PAPI: true
# What language file should be used? ( use any string ) | default = en_GB.yml
diff --git a/src/resources/plugin.yml b/src/resources/plugin.yml
index 20e9ceb..b753398 100644
--- a/src/resources/plugin.yml
+++ b/src/resources/plugin.yml
@@ -1,7 +1,7 @@
name: DivinityEconomy
prefix: DivinityEconomy
main: org.divinitycraft.divinityeconomy.DEPlugin
-version: 3.5.7
+version: 3.6.Kodak
author: EDGRRRR
description: Divinity Economy - The dynamic global economy plugin.
api-version: 1.20
@@ -208,6 +208,13 @@ commands:
permission-message: "You do not have the permission to use this command"
usage: "/reload materials, /reload enchants, /reload potions, /reload entities, /reload experience, /reload config"
+ modded:
+ description: "Attempts to (re)load modded materials registered after startup."
+ aliases: []
+ permission: de.admin.modded
+ permission-message: "You do not have the permission to use this command"
+ usage: "/modded"
+
save:
description: "Saves the respective market file."
aliases: []