Hi,
I am prototyping a trading mechanic for my game and trying to do it in an ES-friendly way! I want to hear your idea about it and let me know if I am doing it the right way or if I can implement it in a different way.
I am going to have blacksmith, farmer, shopkeeper, miner,… NPCs in the village.
Blacksmith should be able to buy minerals from miners and turn them into weapons and farm tools.
Farmers should be able to buy farm tools from the blacksmith and buy farming seeds from seed sellers. They grow farm plants on the farm lands and sell the farm crops to market.
So NPCs need to be able to trade with each other and also with player.
I am creating an interface for them to be able to trade with each other.
public interface Trader {
public enum OrderState {Success, UnAvailable, UnSupported, InsufficientBudget}
public OrderState buyItem(String itemName, int amount, EntityId buyerId);
public OrderState sellItem(EntityId itemId, int amount, EntityId sellerId);
public OrderState orderItem(String itemName, int amount, EntityId orderedBy);
public List<TradingItem> getItemCatalog();
public boolean isItemAvailable(String itemName, int amount);
}
and
public record TradingItem(String name, Integer buyPrice, Integer sellPrice, int amount) {
public boolean isBuyable() {
return buyPrice != null;
}
public boolean isSalable() {
return sellPrice != null;
}
}
So blacksmiths, farmers, miners, and shopkeepers,… need to implement the Trader interface.
I also created a TraderType
component that specifies which Trader an entity use.
public class TraderType implements EntityComponent, PersistentComponent {
private int type;
protected TraderType() {
// For serialization
}
public TraderType(int type) {
this.type = type;
}
public static TraderType create(String typeName, EntityData ed) {
return new TraderType(ed.getStrings().getStringId(typeName, true));
}
public int getType() {
return type;
}
public String getTypeName(EntityData ed) {
return ed.getStrings().getString(type);
}
@Override
public String toString() {
return toString(null);
}
public String toString(EntityData ed) {
return MoreObjects.toStringHelper(this)
.add("type", ed != null ? getTypeName(ed) : getType())
.toString();
}
}
There is a TradingSystem
that watches for entities that have a TraderType
component and load the appropriate Trader type for an entity
public class TradingSystem extends AbstractGameSystem {
private EntityData ed;
private TraderContainer traders;
private Function<String, Trader> loadFunction;
public void setLoadFunction(Function<String, Trader> loadFunction) {
this.loadFunction = loadFunction;
}
public Function<String, Trader> getLoadFunction() {
return loadFunction;
}
public Trader getTrader(EntityId id) {
if (!isInitialized()) {
return null;
}
TraderWrapper wrapper = traders.getObject(id);
if (wrapper != null) {
return wrapper.getTrader();
}
return null;
}
@Override
protected void initialize() {
ed = getSystem(EntityData.class);
traders = new TraderContainer(ed);
}
@Override
protected void terminate() {
}
@Override
public void start() {
traders.start();
}
@Override
public void stop() {
traders.start();
}
@Override
public void update(SimTime time) {
traders.update();
}
protected Trader loadTrader(String type) {
return loadFunction != null ? loadFunction.apply(type) : null;
}
private static class TraderWrapper {
private Trader trader;
public void setTrader(Trader trader) {
this.trader = trader;
}
public Trader getTrader() {
return trader;
}
}
private class TraderContainer extends EntityContainer<TraderWrapper> {
protected TraderContainer(EntityData ed) {
super(ed, TraderType.class);
}
@Override
protected TraderWrapper addObject(Entity e) {
TraderWrapper wrapper = new TraderWrapper();
updateObject(wrapper, e);
return wrapper;
}
@Override
protected void updateObject(TraderWrapper object, Entity e) {
TraderType traderType = e.get(TraderType.class);
Trader trader = loadTrader(traderType.getTypeName(ed));
object.setTrader(trader);
getSystem(GameObjects.class).ifPresent(e.getId(), o -> o.setVar(Trader.class, trader));
}
@Override
protected void removeObject(TraderWrapper object, Entity e) {
object.setTrader(null);
getSystem(GameObjects.class).ifPresent(e.getId(), o -> o.removeVar(Trader.class));
}
}
}
So when a npc/player wants to trade something with another npc, they can get “Trader” object for that npc from “TradingSystem” and do the trading.
For player, there also will be a TradingHostedService
& TradingClientService
that manages the client-server communication for the player and also a TradingPanelState
that visualizes the trading panel (i.e. display item list, prices,…) on a GUI.
What do you think? Am I doing it correctly in terms of ES? Do you have any suggestions for improving it?
Thanks in advance!