diff --git a/src/games/stendhal/server/actions/CommandCenter.java b/src/games/stendhal/server/actions/CommandCenter.java index e5f9fe6fe04..d3fc0aa05eb 100644 --- a/src/games/stendhal/server/actions/CommandCenter.java +++ b/src/games/stendhal/server/actions/CommandCenter.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2010 - Stendhal * + * (C) Copyright 2003-2024 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -133,6 +133,7 @@ private static void registerActions() { WhoAction.register(); register("info", new InfoAction()); register("markscroll", new MarkScrollAction()); + register("shop_inventory", new ShopInventoryAction()); } /** diff --git a/src/games/stendhal/server/actions/ShopInventoryAction.java b/src/games/stendhal/server/actions/ShopInventoryAction.java new file mode 100644 index 00000000000..e7e74c32764 --- /dev/null +++ b/src/games/stendhal/server/actions/ShopInventoryAction.java @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright © 2024 - Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +package games.stendhal.server.actions; + +import org.apache.log4j.Logger; + +import games.stendhal.server.entity.npc.MerchantNPC; +import games.stendhal.server.entity.npc.NPCList; +import games.stendhal.server.entity.npc.SpeakerNPC; +import games.stendhal.server.entity.npc.shop.ShopType; +import games.stendhal.server.entity.player.Player; +import games.stendhal.server.events.ShopInventoryEvent; +import marauroa.common.game.RPAction; + + +/** + * Action for requesting an NPC's shop inventory. + */ +public class ShopInventoryAction implements ActionListener { + + private static Logger logger = Logger.getLogger(ShopInventoryAction.class); + + + @Override + public void onAction(final Player player, final RPAction action) { + if (!action.has("npc")) { + logger.error("NPC name must be specified"); + return; + } + if (!action.has("type")) { + logger.error("Shop type must be specified"); + return; + } + + final String typeName = action.get("type"); + final ShopType type = ShopType.fromString(typeName); + if (type == null) { + logger.error("Unrecognized shop type \"" + typeName + "\""); + return; + } + + final String npcName = action.get("npc"); + final SpeakerNPC npc = NPCList.get().get(npcName); + if (npc == null || !(npc instanceof MerchantNPC)) { + logger.error("Unrecognized merchant NPC \"" + npcName + "\""); + return; + } + + player.addEvent(new ShopInventoryEvent((MerchantNPC) npc, type)); + player.notifyWorldAboutChanges(); + } +} diff --git a/src/games/stendhal/server/core/engine/RPClassGenerator.java b/src/games/stendhal/server/core/engine/RPClassGenerator.java index 0796b32db31..28d503d8653 100644 --- a/src/games/stendhal/server/core/engine/RPClassGenerator.java +++ b/src/games/stendhal/server/core/engine/RPClassGenerator.java @@ -83,6 +83,7 @@ import games.stendhal.server.events.PrivateTextEvent; import games.stendhal.server.events.ProgressStatusEvent; import games.stendhal.server.events.ReachedAchievementEvent; +import games.stendhal.server.events.ShopInventoryEvent; import games.stendhal.server.events.ShowItemListEvent; import games.stendhal.server.events.ShowOutfitListEvent; import games.stendhal.server.events.SoundEvent; @@ -349,6 +350,10 @@ public void createRPClassesWithoutBaking() { BestiaryEvent.generateRPClass(); } + // shops + if (!RPClass.hasRPClass("shop_inventory")) { + ShopInventoryEvent.generateRPClass(); + } if (!RPClass.hasRPClass(Events.OUTFIT_LIST)) { ShowOutfitListEvent.generateRPClass(); } diff --git a/src/games/stendhal/server/entity/npc/MerchantNPC.java b/src/games/stendhal/server/entity/npc/MerchantNPC.java new file mode 100644 index 00000000000..480eca82298 --- /dev/null +++ b/src/games/stendhal/server/entity/npc/MerchantNPC.java @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright © 2024 - Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +package games.stendhal.server.entity.npc; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import games.stendhal.server.entity.npc.shop.OutfitShopsList; +import games.stendhal.server.entity.npc.shop.ShopInventory; +import games.stendhal.server.entity.npc.shop.ShopType; +import games.stendhal.server.entity.npc.shop.ShopsList; + + +/** + * An NPC that manages one or more shops. + */ +public class MerchantNPC extends SpeakerNPC { + + /** Shops that this NPC manages. */ + private final Map shops; + + + /** + * Creates a new shopkeeper. + * + * @param name + * NPC's name. + */ + public MerchantNPC(final String name) { + super(name); + shops = new HashMap<>(); + } + + /** + * Adds a shop to this NPC. + * + * @param type + * Shop type (buy, sell, outfit). + * @param name + * Shop name identifier used to retrieve inventory from shops list. + */ + public void addShop(final ShopType type, final String name) { + shops.put(type, name); + } + + /** + * Retrieves NPC's shop inventory list. + * + * @param type + * Shop type (buy, sell, outfit). + * @return + * Shop inventory or `null` if NPC does not support shop type. + */ + public ShopInventory getInventory(final ShopType type) { + final String name = shops.get(type); + if (name == null) { + return null; + } + if (ShopType.OUTFIT.equals(type)) { + return OutfitShopsList.get().get(name); + } + return ShopsList.get().get(name, type); + } + + /** + * Retrieves shop types supported by this NPC. + * + * @return + * List of available shop types. + */ + public Set getShopTypes() { + return shops.keySet(); + } +} diff --git a/src/games/stendhal/server/entity/npc/shop/ItemShopInventory.java b/src/games/stendhal/server/entity/npc/shop/ItemShopInventory.java index adca1befde4..d547f840b77 100644 --- a/src/games/stendhal/server/entity/npc/shop/ItemShopInventory.java +++ b/src/games/stendhal/server/entity/npc/shop/ItemShopInventory.java @@ -1,5 +1,6 @@ /*************************************************************************** - * (C) Copyright 2023 - Stendhal * + * Copyright © 2023-2024 - Faiumoni e. V. * + *************************************************************************** *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -44,4 +45,16 @@ public void addTradeFor(final String name, final String required, final int coun put(name, 0); } } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for (final String name: keySet()) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(name + ":" + get(name)); + } + return getShopType().toString() + "(" + sb.toString() + ")"; + } } diff --git a/src/games/stendhal/server/entity/npc/shop/OutfitShopInventory.java b/src/games/stendhal/server/entity/npc/shop/OutfitShopInventory.java index d00ae6cfd69..edd693b57f6 100644 --- a/src/games/stendhal/server/entity/npc/shop/OutfitShopInventory.java +++ b/src/games/stendhal/server/entity/npc/shop/OutfitShopInventory.java @@ -1,5 +1,6 @@ /*************************************************************************** - * (C) Copyright 2023 - Stendhal * + * Copyright © 2023-2024 - Faiumoni e. V. * + *************************************************************************** *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -117,4 +118,17 @@ public void addTradeFor(final String name, final String required, final int coun } super.addTradeFor(name, required, count); } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for (final String name: keySet()) { + if (sb.length() > 0) { + sb.append(","); + } + final Pair p = get(name); + sb.append("name=" + name + "," + p.first() + ":" + p.second()); + } + return getShopType().toString() + "(" + sb.toString() + ")"; + } } diff --git a/src/games/stendhal/server/entity/npc/shop/OutfitShopsList.java b/src/games/stendhal/server/entity/npc/shop/OutfitShopsList.java index 547790bbb80..4ca5d0537b0 100644 --- a/src/games/stendhal/server/entity/npc/shop/OutfitShopsList.java +++ b/src/games/stendhal/server/entity/npc/shop/OutfitShopsList.java @@ -19,6 +19,7 @@ import org.apache.log4j.Logger; +import games.stendhal.server.entity.npc.MerchantNPC; import games.stendhal.server.entity.npc.NPCList; import games.stendhal.server.entity.npc.SpeakerNPC; import games.stendhal.server.entity.npc.behaviour.adder.OutfitChangerAdder; @@ -164,6 +165,11 @@ public void putOnOutfit(final Player player, final String oname) { } new OutfitChangerAdder().addOutfitChanger(npc, behaviour, action, !fl.containsKey("noOffer"), fl.containsKey("returnable")); + + if (npc instanceof MerchantNPC) { + // register with merchant NPC for retrieving shop inventory for interactive dialogs + ((MerchantNPC) npc).addShop(ShopType.OUTFIT, name); + } } /** diff --git a/src/games/stendhal/server/entity/npc/shop/ShopInventory.java b/src/games/stendhal/server/entity/npc/shop/ShopInventory.java index 77cb7478a8d..fdca1783d75 100644 --- a/src/games/stendhal/server/entity/npc/shop/ShopInventory.java +++ b/src/games/stendhal/server/entity/npc/shop/ShopInventory.java @@ -1,5 +1,6 @@ /*************************************************************************** - * (C) Copyright 2023 - Stendhal * + * Copyright © 2023-2024 - Faiumoni e. V. * + *************************************************************************** *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -84,4 +85,7 @@ public void addTradeFor(final String name, final String required, final int coun temp.add(new Pair<>(required, count)); tradeFor.put(name, temp); } + + @Override + public abstract String toString(); } diff --git a/src/games/stendhal/server/entity/npc/shop/ShopsList.java b/src/games/stendhal/server/entity/npc/shop/ShopsList.java index 60797984866..0b623d31d44 100644 --- a/src/games/stendhal/server/entity/npc/shop/ShopsList.java +++ b/src/games/stendhal/server/entity/npc/shop/ShopsList.java @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2024 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -17,6 +17,7 @@ import org.apache.log4j.Logger; import games.stendhal.server.core.engine.SingletonRepository; +import games.stendhal.server.entity.npc.MerchantNPC; import games.stendhal.server.entity.npc.SpeakerNPC; import games.stendhal.server.entity.npc.behaviour.adder.BuyerAdder; import games.stendhal.server.entity.npc.behaviour.adder.SellerAdder; @@ -205,6 +206,11 @@ public void configureNPC(final SpeakerNPC npc, final String shopname, final Shop } else { new BuyerAdder().addBuyer(npc, new BuyerBehaviour(inventory, priceFactor), offer); } + + if (npc instanceof MerchantNPC) { + // register with merchant NPC for retrieving shop inventory for interactive dialogs + ((MerchantNPC) npc).addShop(stype, shopname); + } } /** diff --git a/src/games/stendhal/server/events/ShopInventoryEvent.java b/src/games/stendhal/server/events/ShopInventoryEvent.java new file mode 100644 index 00000000000..1769c65e56f --- /dev/null +++ b/src/games/stendhal/server/events/ShopInventoryEvent.java @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright © 2024 - Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +package games.stendhal.server.events; + +import org.apache.log4j.Logger; + +import games.stendhal.server.entity.npc.MerchantNPC; +import games.stendhal.server.entity.npc.shop.ShopInventory; +import games.stendhal.server.entity.npc.shop.ShopType; +import marauroa.common.game.Definition.Type; +import marauroa.common.game.RPClass; +import marauroa.common.game.RPEvent; + + +/** + * Event that retrieves an NPC's shop inventory. + */ +public class ShopInventoryEvent extends RPEvent { + + private static Logger logger = Logger.getLogger(ShopInventoryEvent.class); + + + public static void generateRPClass() { + final RPClass rpclass = new RPClass("shop_inventory"); + rpclass.addAttribute("type", Type.STRING); + rpclass.addAttribute("contents", Type.STRING); + } + + public ShopInventoryEvent(final MerchantNPC npc, final ShopType type) { + final ShopInventory inv = npc.getInventory(type); + if (inv == null) { + logger.warn("Shop type " + type.toString() + " does not exist for NPC " + npc.getName()); + return; + } + put("type", type.toString()); + put("contents", buildInventoryList(inv)); + } + + private String buildInventoryList(final ShopInventory inv) { + return inv.toString().split("(")[1].split(")")[0]; + } +}