Basic loaders implemented

This commit is contained in:
gamer147
2025-11-24 15:23:38 -05:00
parent 4455668e90
commit 7cc3f0e184
6 changed files with 205 additions and 12 deletions

View File

@@ -3,5 +3,10 @@
"id": 1,
"name": "Consumables",
"description": "Limited usage consumable items."
},
{
"id": 2,
"name": "Services",
"description": "Non-item services and boosts."
}
]

View File

@@ -4,7 +4,33 @@
"categoryId": 1,
"name": "Sleep Powder",
"description": "Grants 1 hour of sleep bonus.",
"ironPrice": 50000,
"image": "https://www.wurmpedia.com/images/5/5e/Sleep_powder.png"
"image": "https://www.wurmpedia.com/images/5/5e/Sleep_powder.png",
"currency": {
"type": "WurmBankCurrency",
"ironAmount": 50000
},
"handler": {
"type": "ShopWurmItemPurchaseEffect",
"itemTemplateId": 740,
"ql": 99.0,
"rarity": 0
}
},
{
"id": 2,
"categoryId": 1,
"name": "Res Stone",
"description": "Gives you one resurrection charge.",
"image": "https://www.wurmpedia.com/images/a/aa/Resurrection_stone_icon.png",
"currency": {
"type": "WurmBankCurrency",
"ironAmount": 100000
},
"handler": {
"type": "ShopWurmItemPurchaseEffect",
"itemTemplateId": 302,
"ql": 99.0,
"rarity": 0
}
}
]

View File

@@ -1,18 +1,25 @@
package mod.treestar.shopmod.categoryprovider;
import com.wurmonline.server.support.JSONArray;
import com.wurmonline.server.support.JSONObject;
import com.wurmonline.server.support.JSONTokener;
import mod.treestar.shopmod.datamodels.ShopCategory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JsonShopCategoryProvider implements ShopCategoryProvider {
private static final Logger logger = Logger.getLogger(JsonShopCategoryProvider.class.getName());
private final String categoryJsonPath;
private long lastModified = -1L;
private List<ShopCategory> cachedCategories = Collections.emptyList();
public JsonShopCategoryProvider(String categoryJsonPath) {
this.categoryJsonPath = categoryJsonPath;
@@ -20,14 +27,46 @@ public class JsonShopCategoryProvider implements ShopCategoryProvider {
@Override
public List<ShopCategory> getCategories() {
return Collections.emptyList();
try {
maybeReload();
} catch (IOException e) {
logger.log(Level.WARNING, "Failed loading categories from " + categoryJsonPath, e);
}
return cachedCategories;
}
private void loadCategories() throws IOException {
private void maybeReload() throws IOException {
File file = new File(categoryJsonPath);
if (!file.exists()) {
logger.log(Level.WARNING, "Category file not found at " + categoryJsonPath);
cachedCategories = Collections.emptyList();
return;
}
if (file.lastModified() == lastModified) {
return; // up to date
}
try (FileInputStream f = new FileInputStream(file)) {
JSONTokener tokenizer = new JSONTokener(f);
JSONArray typeArray = new JSONArray(tokenizer);
cachedCategories = parseCategories(typeArray);
lastModified = file.lastModified();
}
}
private List<ShopCategory> parseCategories(JSONArray typeArray) {
List<ShopCategory> categories = new ArrayList<>();
for (int i = 0; i < typeArray.length(); i++) {
JSONObject obj = typeArray.getJSONObject(i);
if (!obj.has("id") || !obj.has("name")) {
logger.log(Level.WARNING, "Skipping category missing required fields at index " + i);
continue;
}
ShopCategory category = new ShopCategory();
category.setId(obj.getInt("id"));
category.setName(obj.getString("name"));
category.setDescription(obj.optString("description", ""));
categories.add(category);
}
return categories;
}
}

View File

@@ -72,4 +72,15 @@ public class ShopItem {
public void setCurrency(ShopCurrency currency) {
this.currency = currency;
}
/**
* Human-readable price for UI/BML display.
*/
public String getPriceDisplay() {
return currency != null ? currency.getDisplay() : "Unavailable";
}
public boolean hasCurrency() {
return currency != null;
}
}

View File

@@ -1,20 +1,35 @@
package mod.treestar.shopmod.itemprovider;
import com.wurmonline.server.support.JSONArray;
import com.wurmonline.server.support.JSONObject;
import com.wurmonline.server.support.JSONTokener;
import mod.treestar.shopmod.currencies.ShopCurrency;
import mod.treestar.shopmod.currencies.WurmBankCurrency;
import mod.treestar.shopmod.datamodels.ShopItem;
import mod.treestar.shopmod.purchasehandlers.ShopItemPurchaseEffect;
import mod.treestar.shopmod.purchasehandlers.ShopWurmItemPurchaseEffect;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Loads shop items from a JSON file. Parsing will be completed in a later step.
* Loads shop items from a JSON file. Expects an array of objects with:
* id (int), name (string), description (string, optional), image (string, optional),
* categoryId (int), currency (object with type and amount), handler (object describing purchase effect).
*/
public class JsonShopItemProvider implements ShopItemProvider {
private static final Logger logger = Logger.getLogger(JsonShopItemProvider.class.getName());
private final String itemJsonPath;
private long lastModified = -1L;
private List<ShopItem> cachedItems = Collections.emptyList();
public JsonShopItemProvider(String itemJsonPath) {
this.itemJsonPath = itemJsonPath;
@@ -22,14 +37,97 @@ public class JsonShopItemProvider implements ShopItemProvider {
@Override
public List<ShopItem> getItems() {
return Collections.emptyList();
try {
maybeReload();
} catch (IOException e) {
logger.log(Level.WARNING, "Failed loading items from " + itemJsonPath, e);
}
return cachedItems;
}
private void loadItems() throws IOException {
private void maybeReload() throws IOException {
File file = new File(itemJsonPath);
if (!file.exists()) {
logger.log(Level.WARNING, "Item file not found at " + itemJsonPath);
cachedItems = Collections.emptyList();
return;
}
if (file.lastModified() == lastModified) {
return; // up to date
}
try (FileInputStream f = new FileInputStream(file)) {
JSONTokener tokenizer = new JSONTokener(f);
JSONArray typeArray = new JSONArray(tokenizer);
cachedItems = parseItems(typeArray);
lastModified = file.lastModified();
}
}
private List<ShopItem> parseItems(JSONArray typeArray) {
List<ShopItem> items = new ArrayList<>();
for (int i = 0; i < typeArray.length(); i++) {
JSONObject obj = typeArray.getJSONObject(i);
if (!obj.has("id") || !obj.has("name") || !obj.has("categoryId")) {
logger.log(Level.WARNING, "Skipping item missing required fields at index " + i);
continue;
}
ShopItem item = new ShopItem();
item.setId(obj.getInt("id"));
item.setName(obj.getString("name"));
item.setDescription(obj.optString("description", ""));
item.setImage(obj.optString("image", ""));
item.setCategoryId(obj.getInt("categoryId"));
ShopCurrency currency = parseCurrency(obj.optJSONObject("currency"), i);
item.setCurrency(currency);
ShopItemPurchaseEffect handler = parsePurchaseHandler(obj.optJSONObject("handler"), i);
item.setPurchaseHandler(handler);
if (currency == null || handler == null) {
logger.log(Level.WARNING, "Skipping item at index " + i + " due to missing currency or handler");
continue;
}
items.add(item);
}
return items;
}
private ShopCurrency parseCurrency(JSONObject currencyObj, int index) {
if (currencyObj == null) {
logger.log(Level.WARNING, "Item at index " + index + " missing currency; item will be unusable");
return null;
}
String type = currencyObj.optString("type", "");
switch (type) {
case "WurmBankCurrency":
long ironAmount = currencyObj.optLong("ironAmount", currencyObj.optLong("priceIron", 0L));
return new WurmBankCurrency(ironAmount);
default:
logger.log(Level.WARNING, "Unknown currency type '" + type + "' for item at index " + index);
return null;
}
}
private ShopItemPurchaseEffect parsePurchaseHandler(JSONObject handlerObj, int index) {
if (handlerObj == null) {
logger.log(Level.WARNING, "Item at index " + index + " missing handler; item will be unusable");
return null;
}
String type = handlerObj.optString("type", "");
switch (type) {
case "ShopWurmItemPurchaseEffect":
int templateId = handlerObj.optInt("itemTemplateId", -1);
if (templateId <= 0) {
logger.log(Level.WARNING, "Handler for item at index " + index + " missing valid itemTemplateId");
return null;
}
ShopWurmItemPurchaseEffect effect = new ShopWurmItemPurchaseEffect();
effect.setItemTemplateId(templateId);
effect.setQl((float) handlerObj.optDouble("ql", 50));
effect.setRandomQl(handlerObj.optBoolean("randomQl", false));
effect.setRarity((byte) handlerObj.optInt("rarity", 0));
return effect;
default:
logger.log(Level.WARNING, "Unknown handler type '" + type + "' for item at index " + index);
return null;
}
}
}

View File

@@ -5,8 +5,6 @@ import com.wurmonline.server.Server;
import com.wurmonline.server.items.*;
import com.wurmonline.server.players.Player;
import java.util.Random;
/**
* Simple item purchase effect that gives a player a specific item on a successful purchase.
* @see ShopItemPurchaseEffect
@@ -17,6 +15,22 @@ public class ShopWurmItemPurchaseEffect implements ShopItemPurchaseEffect {
private boolean randomQl;
private byte rarity;
public void setItemTemplateId(int itemTemplateId) {
this.itemTemplateId = itemTemplateId;
}
public void setQl(float ql) {
this.ql = ql;
}
public void setRandomQl(boolean randomQl) {
this.randomQl = randomQl;
}
public void setRarity(byte rarity) {
this.rarity = rarity;
}
@Override
public void onPurchase(Player player) {
try {