Initial setup

This commit is contained in:
gamer147
2025-11-24 15:13:15 -05:00
parent 081658af45
commit 4455668e90
22 changed files with 353 additions and 183 deletions

View File

@@ -10,16 +10,8 @@ repositories {
maven {
url = uri("https://gotti.no-ip.org/maven/repository")
}
maven {
url = uri("https://jitpack.io")
}
}
dependencies {
implementation("org.gotti.wurmunlimited:server-modlauncher:0.46")
implementation("com.github.Sindusk:sindusklibrary:v2.3")
}
tasks.test {
useJUnitPlatform()
}

View File

@@ -0,0 +1,7 @@
[
{
"id": 1,
"name": "Consumables",
"description": "Limited usage consumable items."
}
]

View File

@@ -1,18 +1,10 @@
{
"categories": [
{
"id": 1,
"name": "Items",
"description": "Available in-game items for purchase.",
"items": [
{
"id": 1,
"name": "Sleep Powder",
"description": "Grants 1 hour of sleep bonus.",
"ironPrice": 50000,
"image": "https://www.wurmpedia.com/images/5/5e/Sleep_powder.png"
}
]
}
]
}
[
{
"id": 1,
"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"
}
]

View File

@@ -0,0 +1,36 @@
package com.wurmonline.server.questions;
import com.wurmonline.server.NoSuchPlayerException;
import com.wurmonline.server.Players;
import mod.treestar.shopmod.ShopService;
import java.util.Properties;
public class ShopQuestion extends Question {
private static final int SHOP_QUESTION_ID = 90001;
private ShopService shopService;
public ShopQuestion(long responderId, String shopName, ShopService shopService) throws NoSuchPlayerException {
super(Players.getInstance().getPlayer(responderId), shopName, null, SHOP_QUESTION_ID, responderId);
this.shopService = shopService;
}
@Override
public void answer(Properties properties) {
super.answer(properties);
}
@Override
public void sendQuestion() {
super.sendQuestion();
}
public ShopService getShopService() {
return shopService;
}
public void setShopService(ShopService shopService) {
this.shopService = shopService;
}
}

View File

@@ -0,0 +1,63 @@
package mod.treestar.shopmod;
import mod.treestar.shopmod.categoryprovider.JsonShopCategoryProvider;
import mod.treestar.shopmod.itemprovider.JsonShopItemProvider;
import org.gotti.wurmunlimited.modloader.interfaces.Configurable;
import org.gotti.wurmunlimited.modloader.interfaces.Initable;
import org.gotti.wurmunlimited.modloader.interfaces.WurmServerMod;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ShopMod implements WurmServerMod, Initable, Configurable {
private static final Logger logger = Logger.getLogger(ShopMod.class.getName());
private String shopName = "Server Shop";
private boolean enableTokenAccess = true;
private boolean enableMailboxAccess = false;
private String categoryJsonPath = "mods/shop/categories.json";
private String itemJsonPath = "mods/shop/items.json";
private Level logLevel = Level.INFO;
@Override
public void configure(Properties properties) {
shopName = properties.getProperty("shopName", shopName);
enableTokenAccess = getBoolean(properties, "enableTokenAccess", enableTokenAccess);
enableMailboxAccess = getBoolean(properties, "enableMailboxAccess", enableMailboxAccess);
categoryJsonPath = properties.getProperty("categoryJsonPath", categoryJsonPath);
itemJsonPath = properties.getProperty("itemJsonPath", itemJsonPath);
logLevel = getLogLevel(properties.getProperty("logLevel", logLevel.getName()));
logger.setLevel(logLevel);
}
@Override
public void init() {
ShopService shopService = ShopService.getInstance();
shopService.registerCategoryProvider(new JsonShopCategoryProvider(categoryJsonPath));
shopService.registerItemProvider(new JsonShopItemProvider(itemJsonPath));
logger.log(Level.INFO, String.format("Initialized shop '%s' (token access: %s, mailbox access: %s)", shopName, enableTokenAccess, enableMailboxAccess));
}
/**
* Factory for other mods to build a new shop instance separate from the default singleton.
*/
public ShopService createShopService() {
return ShopService.create();
}
private boolean getBoolean(Properties properties, String key, boolean defaultValue) {
return Boolean.parseBoolean(properties.getProperty(key, Boolean.toString(defaultValue)));
}
private Level getLogLevel(String value) {
try {
return Level.parse(value);
} catch (IllegalArgumentException e) {
logger.log(Level.WARNING, String.format("Invalid log level '%s', defaulting to %s", value, logLevel.getName()), e);
return logLevel;
}
}
}

View File

@@ -0,0 +1,37 @@
package mod.treestar.shopmod;
import mod.treestar.shopmod.categoryprovider.ShopCategoryProvider;
import mod.treestar.shopmod.itemprovider.ShopItemProvider;
import java.util.ArrayList;
import java.util.List;
public class ShopService {
private List<ShopCategoryProvider> categoryProviders = new ArrayList<>();
private List<ShopItemProvider> itemProviders = new ArrayList<>();
private static ShopService instance;
public void registerCategoryProvider(ShopCategoryProvider provider) {
categoryProviders.add(provider);
}
public void registerItemProvider(ShopItemProvider provider) {
itemProviders.add(provider);
}
public static ShopService getInstance() {
if(instance == null) {
instance = new ShopService();
}
return instance;
}
/**
* Creates a new, unconfigured shop service for consumers that want a separate shop instance.
*/
public static ShopService create() {
return new ShopService();
}
}

View File

@@ -1,15 +0,0 @@
package mod.treestar.shopmod;
import mod.treestar.shopmod.itemprovider.SilverShopItemProvider;
import org.gotti.wurmunlimited.modloader.interfaces.WurmServerMod;
import java.util.ArrayList;
import java.util.List;
public class SilverShop implements WurmServerMod {
@Override
public void init() {
}
}

View File

@@ -1,74 +0,0 @@
package mod.treestar.shopmod;
import com.wurmonline.server.players.Player;
import mod.treestar.shopmod.categoryprovider.SilverShopCategoryProvider;
import mod.treestar.shopmod.datamodels.SilverShopCategory;
import mod.treestar.shopmod.datamodels.SilverShopItem;
import mod.treestar.shopmod.datamodels.SilverShopPurchaseOrder;
import mod.treestar.shopmod.itemprovider.SilverShopItemProvider;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
public class SilverShopService {
private HashMap<Integer, SilverShopCategory> categories;
private HashMap<Integer, SilverShopItem> items;
private List<SilverShopCategoryProvider> categoryProviders;
private List<SilverShopItemProvider> itemProviders;
private static SilverShopService instance;
public void init() {
for (SilverShopCategoryProvider categoryProvider : categoryProviders) {
List<SilverShopCategory> providerCategories = categoryProvider.getCategories();
for (SilverShopCategory category : providerCategories) {
if (categories.containsKey(category.getId())) {
// Ignore it. The newer category simply won't update any details.
continue;
}
categories.put(category.getId(), category);
}
}
for (SilverShopItemProvider itemProvider : itemProviders) {
List<SilverShopItem> providerItems = itemProvider.getItems();
}
}
public boolean canPurchase(SilverShopPurchaseOrder purchaseOrder, Player shopper) {
long totalCost = purchaseOrder.getTotalCost();
if(shopper.getMoney() < totalCost) {
return false;
}
return true;
}
public boolean attemptPurchase(SilverShopPurchaseOrder purchaseOrder, Player shopper) {
if(!canPurchase(purchaseOrder, shopper)) {
return false;
}
long totalCost = purchaseOrder.getTotalCost();
try {
if (shopper.chargeMoney(totalCost)) {
return true;
}
} catch (IOException e) {
return false;
}
return false;
}
public static SilverShopService getInstance() {
if(instance == null) {
instance = new SilverShopService();
}
return instance;
}
}

View File

@@ -0,0 +1,33 @@
package mod.treestar.shopmod.categoryprovider;
import com.wurmonline.server.support.JSONArray;
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.Collections;
import java.util.List;
public class JsonShopCategoryProvider implements ShopCategoryProvider {
private final String categoryJsonPath;
public JsonShopCategoryProvider(String categoryJsonPath) {
this.categoryJsonPath = categoryJsonPath;
}
@Override
public List<ShopCategory> getCategories() {
return Collections.emptyList();
}
private void loadCategories() throws IOException {
File file = new File(categoryJsonPath);
try (FileInputStream f = new FileInputStream(file)) {
JSONTokener tokenizer = new JSONTokener(f);
JSONArray typeArray = new JSONArray(tokenizer);
}
}
}

View File

@@ -0,0 +1,9 @@
package mod.treestar.shopmod.categoryprovider;
import mod.treestar.shopmod.datamodels.ShopCategory;
import java.util.List;
public interface ShopCategoryProvider {
List<ShopCategory> getCategories();
}

View File

@@ -1,10 +0,0 @@
package mod.treestar.shopmod.categoryprovider;
import mod.treestar.shopmod.datamodels.SilverShopCategory;
import java.util.List;
public interface SilverShopCategoryProvider {
void init();
List<SilverShopCategory> getCategories();
}

View File

@@ -0,0 +1,9 @@
package mod.treestar.shopmod.currencies;
import com.wurmonline.server.players.Player;
public interface ShopCurrency {
String getDisplay();
boolean canPlayerAfford(Player player);
boolean chargePlayer(Player player);
}

View File

@@ -0,0 +1,49 @@
package mod.treestar.shopmod.currencies;
import com.wurmonline.server.economy.Change;
import com.wurmonline.server.economy.Economy;
import com.wurmonline.server.players.Player;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Currency backed by a player's Wurm bank balance.
*/
public class WurmBankCurrency implements ShopCurrency {
private static final Logger logger = Logger.getLogger(WurmBankCurrency.class.getName());
private long ironAmount;
public WurmBankCurrency() {
}
public WurmBankCurrency(long ironAmount) {
this.ironAmount = ironAmount;
}
public void setIronAmount(long ironAmount) {
this.ironAmount = ironAmount;
}
@Override
public String getDisplay() {
Change bankAmount = Economy.getEconomy().getChangeFor(ironAmount);
return bankAmount.getChangeShortString();
}
@Override
public boolean canPlayerAfford(Player player) {
return player.getMoney() >= ironAmount;
}
@Override
public boolean chargePlayer(Player player) {
try {
return player.chargeMoney(ironAmount);
} catch (IOException e) {
logger.log(Level.WARNING, "An error occurred when charging money for player with wurmId " + player.getWurmId(), e);
return false;
}
}
}

View File

@@ -5,12 +5,12 @@ import java.util.List;
/**
* A category containing some number of items for the silver shop.
*/
public class SilverShopCategory {
public class ShopCategory {
private int id;
private String name;
private String description;
private List<SilverShopItem> items;
private List<ShopItem> items;
public int getId() {
return id;
@@ -36,11 +36,11 @@ public class SilverShopCategory {
this.description = description;
}
public List<SilverShopItem> getItems() {
public List<ShopItem> getItems() {
return items;
}
public void setItems(List<SilverShopItem> items) {
public void setItems(List<ShopItem> items) {
this.items = items;
}
}

View File

@@ -1,19 +1,21 @@
package mod.treestar.shopmod.datamodels;
import mod.treestar.shopmod.purchasehandlers.SilverShopItemPurchaseEffect;
import mod.treestar.shopmod.purchasehandlers.ShopItemPurchaseEffect;
import mod.treestar.shopmod.currencies.ShopCurrency;
/**
* Represents a single item for purchase in the silver shop. This may be an in-game item, service, etc.
*/
public class SilverShopItem {
public class ShopItem {
private int id;
private String name;
private String description;
private String image;
private long ironPrice;
private int categoryId;
private SilverShopItemPurchaseEffect purchaseHandler;
private ShopCurrency currency;
private ShopItemPurchaseEffect purchaseHandler;
public int getId() {
return id;
@@ -47,19 +49,11 @@ public class SilverShopItem {
this.image = image;
}
public long getIronPrice() {
return ironPrice;
}
public void setIronPrice(long ironPrice) {
this.ironPrice = ironPrice;
}
public SilverShopItemPurchaseEffect getPurchaseHandler() {
public ShopItemPurchaseEffect getPurchaseHandler() {
return purchaseHandler;
}
public void setPurchaseHandler(SilverShopItemPurchaseEffect purchaseHandler) {
public void setPurchaseHandler(ShopItemPurchaseEffect purchaseHandler) {
this.purchaseHandler = purchaseHandler;
}
@@ -70,4 +64,12 @@ public class SilverShopItem {
public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
}
public ShopCurrency getCurrency() {
return currency;
}
public void setCurrency(ShopCurrency currency) {
this.currency = currency;
}
}

View File

@@ -0,0 +1,23 @@
package mod.treestar.shopmod.datamodels;
import java.util.List;
/**
* A single purchase in the shop.
*/
public class ShopPurchaseOrder {
private List<ShopItem> shoppingCart;
public List<ShopItem> getShoppingCart() {
return shoppingCart;
}
public void setShoppingCart(List<ShopItem> shoppingCart) {
this.shoppingCart = shoppingCart;
}
public long getTotalCost() {
// Cost calculation will depend on per-item currency; to be implemented alongside currency-aware purchase flow.
return 0L;
}
}

View File

@@ -1,22 +0,0 @@
package mod.treestar.shopmod.datamodels;
import java.util.List;
/**
* A single purchase in the shop.
*/
public class SilverShopPurchaseOrder {
private List<SilverShopItem> shoppingCart;
public List<SilverShopItem> getShoppingCart() {
return shoppingCart;
}
public void setShoppingCart(List<SilverShopItem> shoppingCart) {
this.shoppingCart = shoppingCart;
}
public long getTotalCost() {
return getShoppingCart().stream().mapToLong(SilverShopItem::getIronPrice).sum();
}
}

View File

@@ -0,0 +1,35 @@
package mod.treestar.shopmod.itemprovider;
import com.wurmonline.server.support.JSONArray;
import com.wurmonline.server.support.JSONTokener;
import mod.treestar.shopmod.datamodels.ShopItem;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* Loads shop items from a JSON file. Parsing will be completed in a later step.
*/
public class JsonShopItemProvider implements ShopItemProvider {
private final String itemJsonPath;
public JsonShopItemProvider(String itemJsonPath) {
this.itemJsonPath = itemJsonPath;
}
@Override
public List<ShopItem> getItems() {
return Collections.emptyList();
}
private void loadItems() throws IOException {
File file = new File(itemJsonPath);
try (FileInputStream f = new FileInputStream(file)) {
JSONTokener tokenizer = new JSONTokener(f);
JSONArray typeArray = new JSONArray(tokenizer);
}
}
}

View File

@@ -0,0 +1,12 @@
package mod.treestar.shopmod.itemprovider;
import mod.treestar.shopmod.datamodels.ShopItem;
import java.util.List;
/**
* An interface representing a provider of silver shop items.
*/
public interface ShopItemProvider {
List<ShopItem> getItems();
}

View File

@@ -1,14 +0,0 @@
package mod.treestar.shopmod.itemprovider;
import mod.treestar.shopmod.datamodels.SilverShopCategory;
import mod.treestar.shopmod.datamodels.SilverShopItem;
import java.util.List;
/**
* An interface representing a provider of silver shop items.
*/
public interface SilverShopItemProvider {
void init();
List<SilverShopItem> getItems();
}

View File

@@ -2,7 +2,7 @@ package mod.treestar.shopmod.purchasehandlers;
import com.wurmonline.server.players.Player;
public interface SilverShopItemPurchaseEffect {
public interface ShopItemPurchaseEffect {
/**
* What happens after a player successfully purchases the item. This assumes the transaction was completed successfully.
* @param player the player that performed the purchase

View File

@@ -1,14 +1,17 @@
package mod.treestar.shopmod.purchasehandlers;
import com.wurmonline.server.FailedException;
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 SilverShopItemPurchaseEffect
* @see ShopItemPurchaseEffect
*/
public class SilverShopWurmItemPurchaseEffect implements SilverShopItemPurchaseEffect {
public class ShopWurmItemPurchaseEffect implements ShopItemPurchaseEffect {
private int itemTemplateId;
private float ql;
private boolean randomQl;
@@ -17,7 +20,10 @@ public class SilverShopWurmItemPurchaseEffect implements SilverShopItemPurchaseE
@Override
public void onPurchase(Player player) {
try {
Item item = ItemFactory.createItem(itemTemplateId, ql, (byte) 0, (byte) 0, null);
if(randomQl) {
ql = Server.rand.nextInt(99) + 1;
}
Item item = ItemFactory.createItem(itemTemplateId, ql, (byte) 0, (byte) rarity, null);
player.getInventory().insertItem(item);
ItemTemplate template = ItemTemplateFactory.getInstance().getTemplate(itemTemplateId);
player.sendSystemMessage(String.format("You receive a %s.", template.getName()));