Skip to content

Commit

Permalink
[Npc Shop Enhancement] - Allow an item of the same id to be added mul…
Browse files Browse the repository at this point in the history
…tiple times for the same npc (#207)

Set for use name of the item instead of id in the ShopInfoMap vector
This will allow adding items with the same item ids and customizing the item name
Fixed somes npcs shop bugs
  • Loading branch information
dudantas authored Feb 13, 2022
1 parent 3144b16 commit 9c257da
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 57 deletions.
34 changes: 30 additions & 4 deletions data/npclua/canary.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,36 @@ npcConfig.flags = {

-- Npc shop
npcConfig.shop = {
{clientId = 123, buy = 16000, sell = 16000, count = 1},
{clientId = 130, buy = 100, count = 1},
{clientId = 135, buy = 5000, count = 1},
{clientId = 138, buy = 600, count = 1}
{ itemName = "basket", clientId = 2855, buy = 6 },
{ itemName = "bottle", clientId = 2875, buy = 3 },
{ itemName = "bucket", clientId = 2873, buy = 4 },
{ itemName = "candelabrum", clientId = 2911, buy = 8 },
{ itemName = "candlestick", clientId = 2917, buy = 2 },
{ itemName = "closed trap", clientId = 3481, buy = 280, sell = 75 },
{ itemName = "crowbar", clientId = 3304, buy = 260, sell = 50 },
{ itemName = "cup", clientId = 2881, buy = 2 },
{ itemName = "document", clientId = 2818, buy = 12 },
{ itemName = "fishing rod", clientId = 3483, buy = 150, sell = 40 },
{ itemName = "green backpack", clientId = 2865, buy = 20 },
{ itemName = "green bag", clientId = 2857, buy = 4 },
{ itemName = "hand auger", clientId = 31334, buy = 25 },
{ itemName = "machete", clientId = 3308, buy = 35, sell = 6 },
{ itemName = "net", clientId = 31489, buy = 50 },
{ itemName = "parchment", clientId = 2817, buy = 8 },
{ itemName = "pick", clientId = 3456, buy = 50, sell = 15 },
{ itemName = "plate", clientId = 2905, buy = 6 },
{ itemName = "present", clientId = 2856, buy = 10 },
{ itemName = "rope", clientId = 3003, buy = 50, sell = 15 },
{ itemName = "scroll", clientId = 2815, buy = 5 },
{ itemName = "scythe", clientId = 3453, buy = 50, sell = 10 },
{ itemName = "shovel", clientId = 3457, buy = 50, sell = 8 },
{ itemName = "torch", clientId = 2920, buy = 2 },
{ itemName = "vial", clientId = 2874, sell = 5 },
{ itemName = "vial of oil", clientId = 2874, buy = 20, count = 11 },
{ itemName = "watch", clientId = 2906, buy = 20, sell = 6 },
{ itemName = "waterskin of water", clientId = 2901, buy = 10, count = 1 },
{ itemName = "wooden hammer", clientId = 3459, sell = 15 },
{ itemName = "worm", clientId = 3492, buy = 1 }
}

-- Create keywordHandler and npcHandler
Expand Down
2 changes: 1 addition & 1 deletion src/creatures/creatures_definitions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ struct ShopInfo {

using MarketOfferList = std::list<MarketOffer>;
using HistoryMarketOfferList = std::list<HistoryMarketOffer>;
using ShopInfoMap = std::unordered_map<uint16_t, ShopInfo>;
using ShopInfoMap = std::unordered_map<std::string, ShopInfo>;
using StashItemList = std::map<uint16_t, uint32_t>;

struct Familiar {
Expand Down
8 changes: 4 additions & 4 deletions src/creatures/npcs/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,11 @@ void Npc::onPlayerBuyItem(Player* player, uint16_t serverId,

const ItemType& itemType = Item::items[serverId];

if (getShopItems().find(serverId) == getShopItems().end()) {
if (getShopItems().find(itemType.name) == getShopItems().end()) {
return;
}

ShopInfo shopInfo = getShopItems()[serverId];
ShopInfo shopInfo = getShopItems()[itemType.name];
int64_t totalCost = shopInfo.buyPrice * amount;
if (getCurrency() == ITEM_GOLD_COIN) {
if (!g_game.removeMoney(player, totalCost, 0, true)) {
Expand Down Expand Up @@ -263,11 +263,11 @@ void Npc::onPlayerSellItem(Player* player, uint16_t serverId,

const ItemType& itemType = Item::items[serverId];

if (getShopItems().find(serverId) == getShopItems().end()) {
if (getShopItems().find(itemType.name) == getShopItems().end()) {
return;
}

ShopInfo shopInfo = getShopItems()[serverId];
ShopInfo shopInfo = getShopItems()[itemType.name];

if(!player->removeItemOfType(serverId, amount, -1, false, false)) {
return;
Expand Down
4 changes: 2 additions & 2 deletions src/creatures/npcs/npcs.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ class NpcType
std::string nameDescription;
NpcInfo info;

void addShopItem(uint16_t serverId, ShopInfo &item) {
info.shopItems[serverId] = item;
void addShopItem(const std::string &itemName, const ShopInfo &shopInfo) {
info.shopItems[itemName] = shopInfo;
}

bool loadCallback(LuaScriptInterface* scriptInterface);
Expand Down
9 changes: 5 additions & 4 deletions src/creatures/players/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3787,23 +3787,24 @@ bool Player::updateSaleShopList(const Item* item)
return true;
}

bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) const
bool Player::hasShopItemForSale(uint16_t itemId, uint8_t subType) const
{
if (!shopOwner) {
return false;
}

const ItemType& it = Item::items.getItemIdByClientId(itemId);
ShopInfoMap shopItemMap = shopOwner->getShopItems();
if (shopItemMap.find(itemId) == shopItemMap.end()) {
if (shopItemMap.find(it.name) == shopItemMap.end()) {
return false;
}

const ShopInfo& shopInfo = shopItemMap[itemId];
const ShopInfo& shopInfo = shopItemMap[it.name];
if (shopInfo.buyPrice == 0) {
return false;
}

if (!Item::items[itemId].isFluidContainer()) {
if (!it.isFluidContainer()) {
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/creatures/players/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ class Player final : public Creature, public Cylinder
void openShopWindow(Npc* npc);
bool closeShopWindow(bool sendCloseShopWindow = true);
bool updateSaleShopList(const Item* item);
bool hasShopItemForSale(uint32_t itemId, uint8_t subType) const;
bool hasShopItemForSale(uint16_t itemId, uint8_t subType) const;

void setChaseMode(bool mode);
void setFightMode(FightMode_t mode) {
Expand Down
4 changes: 2 additions & 2 deletions src/lua/functions/creatures/npc/npc_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,13 @@ int NpcFunctions::luaNpcGetShopItem(lua_State* L) {
ShopInfoMap shopItems = npc->getShopItems();
const ItemType &itemType = Item::items.getItemIdByClientId(getNumber<uint16_t>(L, 2));

if (shopItems.find(itemType.id) == shopItems.end()) {
if (shopItems.find(itemType.name) == shopItems.end()) {
reportErrorFunc("No shop item found for clientId");
pushBoolean(L, false);
return 1;
}

ShopInfo shopInfo = shopItems[itemType.id];
ShopInfo shopInfo = shopItems[itemType.name];
setField(L, "clientId", shopInfo.itemClientId);
setField(L, "name", shopInfo.name);
setField(L, "subType", shopInfo.subType);
Expand Down
20 changes: 8 additions & 12 deletions src/lua/functions/creatures/npc/npc_type_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,14 @@ int NpcTypeFunctions::luaNpcTypeAddShopItem(lua_State* L) {
ShopInfo shopItem;

shopItem.itemClientId = static_cast<uint16_t>(getField<uint32_t>(L, table, "clientId"));
shopItem.buyPrice = static_cast<uint16_t>(getField<uint32_t>(L, table, "buy"));
shopItem.sellPrice = static_cast<uint16_t>(getField<uint32_t>(L, table, "sell"));
shopItem.subType = static_cast<uint16_t>(getField<uint32_t>(L, table, "count"));
shopItem.storageKey = static_cast<uint16_t>(getField<uint32_t>(L, table, "storageKey"));
shopItem.storageValue = static_cast<uint16_t>(getField<uint32_t>(L, table, "storageValue"));

const ItemType &it = Item::items.getItemIdByClientId(shopItem.itemClientId);

shopItem.name = it.name;

npcType->addShopItem(it.id, shopItem);

shopItem.buyPrice = getField<uint32_t>(L, table, "buy");
shopItem.sellPrice = getField<uint32_t>(L, table, "sell");
shopItem.subType = static_cast<int32_t>(getField<uint32_t>(L, table, "count"));
shopItem.storageKey = static_cast<int32_t>(getField<uint32_t>(L, table, "storageKey"));
shopItem.storageValue = static_cast<int32_t>(getField<uint32_t>(L, table, "storageValue"));
shopItem.name = getFieldString(L, table, "itemName");

npcType->addShopItem(shopItem.name, shopItem);
return 1;
}

Expand Down
50 changes: 24 additions & 26 deletions src/server/network/protocol/protocolgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3825,16 +3825,13 @@ void ProtocolGame::sendShop(Npc *npc)
msg.add<uint16_t>(itemsToSend);

uint16_t i = 0;
for (auto& shopInfoPair : itemMap)
for (auto& [itemName, shopInfo] : itemMap)
{
const uint16_t itemId = shopInfoPair.first;
const ShopInfo &shopInfo = shopInfoPair.second;

if (++i > itemsToSend) {
break;
}

AddShopItem(msg, shopInfo, itemId);
AddShopItem(msg, shopInfo, itemName);
}

writeToOutputBuffer(msg);
Expand Down Expand Up @@ -3881,7 +3878,7 @@ void ProtocolGame::sendResourceBalance(Resource_t resourceType, uint64_t value)
writeToOutputBuffer(msg);
}

void ProtocolGame::sendSaleItemList(const ShopInfoMap &shop, const std::map<uint32_t, uint32_t> &inventoryMap)
void ProtocolGame::sendSaleItemList(const ShopInfoMap &shopInfoMap, const std::map<uint32_t, uint32_t> &inventoryMap)
{
//Since we already have full inventory map we shouldn't call getMoney here - it is simply wasting cpu power
uint64_t playerMoney = 0;
Expand Down Expand Up @@ -3927,25 +3924,23 @@ void ProtocolGame::sendSaleItemList(const ShopInfoMap &shop, const std::map<uint
auto msgPosition = msg.getBufferPosition();
msg.skipBytes(1);

for (auto& shopInfoPair : shop)
for (auto& [itemName, shopInfo] : shopInfoMap)
{
const uint16_t itemId = shopInfoPair.first;
const ShopInfo &shopInfo = shopInfoPair.second;
if (shopInfo.sellPrice == 0)
{
continue;
}

uint32_t index = static_cast<uint32_t>(itemId);
if (Item::items[itemId].isFluidContainer())
auto index = static_cast<uint32_t>(shopInfo.itemClientId);
if (Item::items[shopInfo.itemClientId].isFluidContainer())
{
index |= (static_cast<uint32_t>(shopInfo.subType) << 16);
}

it = inventoryMap.find(index);
if (it != inventoryMap.end())
{
msg.addItemId(itemId);
msg.addItemId(shopInfo.itemClientId);
msg.addByte(std::min<uint32_t>(it->second, std::numeric_limits<uint8_t>::max()));
if (++itemsToSend >= 0xFF)
{
Expand Down Expand Up @@ -6804,32 +6799,35 @@ void ProtocolGame::AddHiddenShopItem(NetworkMessage &msg)
msg.add<uint32_t>(0);
}

void ProtocolGame::AddShopItem(NetworkMessage &msg, const ShopInfo &item, uint16_t itemId)
void ProtocolGame::AddShopItem(NetworkMessage &msg, const ShopInfo &shopInfo, const std::string &itemName)
{
// Sends the item information empty if the player doesn't have the storage to buy/sell a certain item
int32_t storageValue;
player->getStorageValue(item.storageKey, storageValue);
if (item.storageKey != 0 && storageValue < item.storageValue)
player->getStorageValue(shopInfo.storageKey, storageValue);
if (shopInfo.storageKey != 0 && storageValue < shopInfo.storageValue)
{
AddHiddenShopItem(msg);
return;
}

const ItemType &it = Item::items[itemId];
msg.add<uint16_t>(item.itemClientId);

uint8_t count = std::min(item.subType, 100);
const ItemType &it = Item::items[shopInfo.itemClientId];
msg.add<uint16_t>(shopInfo.itemClientId);

if (it.isSplash() || it.isFluidContainer())
{
count = serverFluidToClient(count);
if (it.isSplash() || it.isFluidContainer()) {
msg.addByte(static_cast<int32_t>(serverFluidToClient(shopInfo.subType)));
} else {
msg.addByte(0x00);
}

msg.addByte(count);
msg.addString(item.name);
// If not send "itemName" variable from the npc shop, will registered the name that is in items.xml
if (itemName.empty()) {
msg.addString(it.name);
} else {
msg.addString(itemName);
}
msg.add<uint32_t>(it.weight);
msg.add<uint32_t>(item.buyPrice == 4294967295 ? 0 : item.buyPrice);
msg.add<uint32_t>(item.sellPrice == 4294967295 ? 0 : item.sellPrice);
msg.add<uint32_t>(shopInfo.buyPrice == 4294967295 ? 0 : shopInfo.buyPrice);
msg.add<uint32_t>(shopInfo.sellPrice == 4294967295 ? 0 : shopInfo.sellPrice);
}

void ProtocolGame::parseExtendedOpcode(NetworkMessage &msg)
Expand Down
2 changes: 1 addition & 1 deletion src/server/network/protocol/protocolgame.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ class ProtocolGame final : public Protocol

//shop
void AddHiddenShopItem(NetworkMessage &msg);
void AddShopItem(NetworkMessage &msg, const ShopInfo &item, uint16_t itemId);
void AddShopItem(NetworkMessage &msg, const ShopInfo &item, const std::string &itemName);

//otclient
void parseExtendedOpcode(NetworkMessage &msg);
Expand Down

0 comments on commit 9c257da

Please sign in to comment.