From f55cc8f36df0202cf395e7442bf355a2ac354f3e Mon Sep 17 00:00:00 2001 From: Gent Date: Sun, 13 Sep 2020 18:54:47 -0400 Subject: [PATCH 1/5] Load vendor tables --- src/ItemManager.cpp | 2 ++ src/ItemManager.hpp | 9 +++++- src/NPCManager.cpp | 67 +++++++++++++++------------------------------ src/TableData.cpp | 11 ++++++++ 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index fff60df..05e6696 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -7,6 +7,8 @@ #include // for memset() and memcmp() #include +std::map> ItemManager::VendorTables; + void ItemManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler); diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index cd7a302..66c9545 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -3,13 +3,20 @@ #include "CNShardServer.hpp" #include "Player.hpp" +struct VendorListing { + int sort, type, iID, price; +}; + namespace ItemManager { enum class SlotType { EQUIP = 0, INVENTORY = 1, BANK = 3 }; - void init(); + // hopefully this is fine since it's never modified after load + extern std::map> VendorTables; + + void init(); void itemMoveHandler(CNSocket* sock, CNPacketData* data); void itemDeleteHandler(CNSocket* sock, CNPacketData* data); diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index d1e7f76..d1876ab 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -102,56 +102,33 @@ void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE)) return; // malformed packet - //sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE* req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf; + sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE* req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf; + + if (req->iVendorID != req->iNPC_ID || ItemManager::VendorTables.find(req->iNPC_ID) == ItemManager::VendorTables.end()) + return; + + std::vector listings = ItemManager::VendorTables[req->iNPC_ID]; // maybe use iVendorID instead...? + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp); - // TODO: data needs to be read from shopkeeper tabledata - // check req->iVendorID and req->iNPC_ID - // Exaple Items - // shirt - sItemBase base1; - base1.iID = 60; - base1.iOpt = 0; - // expire date - base1.iTimeLimit = 0; - base1.iType = 1; + for (int i = 0; i < 20; i++) { // 20 is the max - sItemVendor item1; - item1.item = base1; - item1.iSortNum = 0; - item1.iVendorID = 1; - // cost amount in float? (doesn't work rn, need to figure out) - item1.fBuyCost = 100.0; + if (i < listings.size()) { // check to make sure listing exists first + sItemBase base; + base.iID = listings[i].iID; + base.iOpt = 0; + base.iTimeLimit = 0; + base.iType = listings[i].type; - // pants - sItemBase base2; - base2.iID = 61; - base2.iOpt = 0; - base2.iTimeLimit = 0; - base2.iType = 2; + sItemVendor vItem; + vItem.item = base; + vItem.iSortNum = listings[i].sort; + vItem.iVendorID = req->iVendorID; + vItem.fBuyCost = listings[i].price; - sItemVendor item2; - item2.item = base2; - item2.iSortNum = 1; - item2.iVendorID = 1; - item2.fBuyCost = 250.0; - - // shoes - sItemBase base3; - base3.iID = 51; - base3.iOpt = 0; - base3.iTimeLimit = 0; - base3.iType = 3; - - sItemVendor item3; - item3.item = base3; - item3.iSortNum = 2; - item3.iVendorID = 1; - item3.fBuyCost = 350.0; - - resp.item[0] = item1; - resp.item[1] = item2; - resp.item[2] = item3; + resp.item[i] = vItem; + } + } sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC)); } diff --git a/src/TableData.cpp b/src/TableData.cpp index df367b1..711a4fa 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -1,6 +1,7 @@ #include "TableData.hpp" #include "NPCManager.hpp" #include "TransportManager.hpp" +#include "ItemManager.hpp" #include "settings.hpp" #include "MissionManager.hpp" @@ -119,6 +120,16 @@ void TableData::init() { } std::cout << "[INFO] Loaded mission-related data" << std::endl; + + // load vendor listings + nlohmann::json listings = xdtData["m_pVendorTable"]["m_pItemData"]; + + for (nlohmann::json::iterator listing = listings.begin(); listing != listings.end(); listing++) { + VendorListing vListing = {listing.value()["m_iSortNumber"], listing.value()["m_iItemType"], listing.value()["m_iitemID"], listing.value()["m_iSellCost"]}; + ItemManager::VendorTables[listing.value()["m_iNpcNumber"]].push_back(vListing); + } + + std::cout << "[INFO] Loaded " << ItemManager::VendorTables.size() << " vendor tables" << std::endl; } catch (const std::exception& err) { std::cerr << "[WARN] Malformed xdt.json file! Reason:" << err.what() << std::endl; From c91022030c9fb33fe931f13047f3e3e7698ee61e Mon Sep 17 00:00:00 2001 From: Gent Date: Mon, 14 Sep 2020 00:25:14 -0400 Subject: [PATCH 2/5] Load item tables + price implementation --- src/ItemManager.cpp | 9 +++++++++ src/ItemManager.hpp | 9 ++++++++- src/NPCManager.cpp | 17 ++++++++++++----- src/TableData.cpp | 16 +++++++++++++++- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index 05e6696..f45bb58 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -7,6 +7,7 @@ #include // for memset() and memcmp() #include +std::map, Item> ItemManager::ItemData; std::map> ItemManager::VendorTables; void ItemManager::init() { @@ -749,3 +750,11 @@ int ItemManager::findFreeSlot(Player *plr) { // not found return -1; } + +bool ItemManager::isItemRegistered(int32_t id, int32_t type) { + return ItemData.find(std::pair(id, type)) != ItemData.end(); +} + +Item ItemManager::getItemData(int32_t id, int32_t type) { + return ItemData[std::pair(id, type)]; +} diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 66c9545..00bd4b5 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -3,8 +3,12 @@ #include "CNShardServer.hpp" #include "Player.hpp" +struct Item { + bool tradeable, sellable; + int buyPrice, sellPrice, stackSize, level; // TODO: implement more as needed +}; struct VendorListing { - int sort, type, iID, price; + int sort, type, iID; }; namespace ItemManager { @@ -14,6 +18,7 @@ namespace ItemManager { BANK = 3 }; // hopefully this is fine since it's never modified after load + extern std::map, Item> ItemData; // -> data extern std::map> VendorTables; void init(); @@ -36,4 +41,6 @@ namespace ItemManager { void chestOpenHandler(CNSocket* sock, CNPacketData* data); int findFreeSlot(Player *plr); + bool isItemRegistered(int32_t id, int32_t type); + Item getItemData(int32_t id, int32_t type); } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index d1876ab..7ddfb6d 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -31,10 +31,17 @@ void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf; Player *plr = PlayerManager::getPlayer(sock); - int itemCost = 100; // TODO: placeholder, look up the price of item + if (!ItemManager::isItemRegistered(req->Item.iID, req->Item.iType)) { + std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found" << std::endl; + // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); + failResp.iErrorCode = 0; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); + return; + } + int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).buyPrice; int slot = ItemManager::findFreeSlot(plr); - if (itemCost > plr->money || slot == -1) { // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); @@ -78,8 +85,8 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { sItemBase* item = &plr->Inven[req->iInvenSlotNum]; INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp); - - int sellValue = 100 * req->iItemCnt; // TODO: lookup item price + + int sellValue = ItemManager::getItemData(item->iID, item->iType).sellPrice * req->iItemCnt; // TODO: lookup item price // increment taros plr->money = plr->money + sellValue; @@ -124,7 +131,7 @@ void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) { vItem.item = base; vItem.iSortNum = listings[i].sort; vItem.iVendorID = req->iVendorID; - vItem.fBuyCost = listings[i].price; + //vItem.fBuyCost = listings[i].price; resp.item[i] = vItem; } diff --git a/src/TableData.cpp b/src/TableData.cpp index 711a4fa..587e2f4 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -121,11 +121,25 @@ void TableData::init() { std::cout << "[INFO] Loaded mission-related data" << std::endl; + // load all item data. i'm sorry. it has to be done + const char* setNames[10] = { "m_pBackItemTable", "m_pFaceItemTable", "m_pGlassItemTable", "m_pHatItemTable", + "m_pHeadItemTable", "m_pPantsItemTable", "m_pShirtsItemTable", "m_pShoesItemTable", "m_pWeaponItemTable", + "m_pVehicleItemTable"}; + nlohmann::json itemSet; + for (int i = 0; i < 10; i++) { + itemSet = xdtData[setNames[i]]["m_pItemData"]; + for (nlohmann::json::iterator item = itemSet.begin(); item != itemSet.end(); item++) + ItemManager::ItemData[std::pair(item.value()["m_iItemNumber"], item.value()["m_iEquipLoc"])] + = { item.value()["m_iTradeAble"] == 1, item.value()["m_iSellAble"] == 1, item.value()["m_iItemPrice"], item.value()["m_iItemSellPrice"], item.value()["m_iStackNumber"], item.value()["m_iMinReqLev"] }; + } + + std::cout << "[INFO] Loaded " << ItemManager::ItemData.size() << " items" << std::endl; + // load vendor listings nlohmann::json listings = xdtData["m_pVendorTable"]["m_pItemData"]; for (nlohmann::json::iterator listing = listings.begin(); listing != listings.end(); listing++) { - VendorListing vListing = {listing.value()["m_iSortNumber"], listing.value()["m_iItemType"], listing.value()["m_iitemID"], listing.value()["m_iSellCost"]}; + VendorListing vListing = { listing.value()["m_iSortNumber"], listing.value()["m_iItemType"], listing.value()["m_iitemID"] }; ItemManager::VendorTables[listing.value()["m_iNpcNumber"]].push_back(vListing); } From da8c833587d91b8b758f04801af7a4e8a108c29e Mon Sep 17 00:00:00 2001 From: Gent Date: Mon, 14 Sep 2020 01:27:20 -0400 Subject: [PATCH 3/5] Implement buyback Sellability tweak Add additional item categories --- src/NPCManager.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++--- src/NPCManager.hpp | 1 + src/TableData.cpp | 10 ++++---- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 7ddfb6d..9d421e2 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -22,6 +22,7 @@ void NPCManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, npcVendorTable); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, npcVendorBuy); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, npcVendorSell); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorRestore); } void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { @@ -32,7 +33,7 @@ void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); if (!ItemManager::isItemRegistered(req->Item.iID, req->Item.iType)) { - std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found" << std::endl; + std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl; // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); failResp.iErrorCode = 0; @@ -84,9 +85,20 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { sItemBase* item = &plr->Inven[req->iInvenSlotNum]; + if (!ItemManager::isItemRegistered(item->iID, item->iType) || !ItemManager::getItemData(item->iID, item->iType).sellable) { // sanity + sellable check + std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl; + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp); + failResp.iErrorCode = 0; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL)); + return; + } + + sItemBase original; + memcpy(&original, item, sizeof(sItemBase)); + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp); - int sellValue = ItemManager::getItemData(item->iID, item->iType).sellPrice * req->iItemCnt; // TODO: lookup item price + int sellValue = ItemManager::getItemData(item->iID, item->iType).sellPrice * req->iItemCnt; // increment taros plr->money = plr->money + sellValue; @@ -100,11 +112,55 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { // response parameters resp.iInvenSlotNum = req->iInvenSlotNum; resp.iCandy = plr->money; - resp.Item = *item; + resp.Item = original; // the item that gets sent to buyback + resp.ItemStay = *item; // the void item that gets put in the slot sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC)); } +void NPCManager::npcVendorRestore(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY)) + return; // malformed packet + + sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + + if (!ItemManager::isItemRegistered(req->Item.iID, req->Item.iType)) { + std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (rebuy)" << std::endl; + // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp); + failResp.iErrorCode = 0; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL)); + return; + } + + int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).sellPrice; // sell price is used on rebuy + int slot = ItemManager::findFreeSlot(plr); + if (itemCost > plr->money || slot == -1) { + // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp); + failResp.iErrorCode = 0; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL)); + return; + } + + if (slot != req->iInvenSlotNum) { + // possible item stacking? + std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl; + } + + plr->money = plr->money - itemCost; + plr->Inven[slot] = req->Item; + + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp); + // response parameters + resp.iCandy = plr->money; + resp.iInvenSlotNum = slot; + resp.Item = req->Item; + + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC)); +} + void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE)) return; // malformed packet diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index b65874d..c77d92b 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -28,6 +28,7 @@ namespace NPCManager { void npcVendorTable(CNSocket* sock, CNPacketData* data); void npcVendorBuy(CNSocket* sock, CNPacketData* data); void npcVendorSell(CNSocket* sock, CNPacketData* data); + void npcVendorRestore(CNSocket* sock, CNPacketData* data); void updatePlayerNPCS(CNSocket* sock, PlayerView& plr); } diff --git a/src/TableData.cpp b/src/TableData.cpp index 587e2f4..f3748c9 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -122,15 +122,15 @@ void TableData::init() { std::cout << "[INFO] Loaded mission-related data" << std::endl; // load all item data. i'm sorry. it has to be done - const char* setNames[10] = { "m_pBackItemTable", "m_pFaceItemTable", "m_pGlassItemTable", "m_pHatItemTable", + const char* setNames[12] = { "m_pBackItemTable", "m_pFaceItemTable", "m_pGlassItemTable", "m_pHatItemTable", "m_pHeadItemTable", "m_pPantsItemTable", "m_pShirtsItemTable", "m_pShoesItemTable", "m_pWeaponItemTable", - "m_pVehicleItemTable"}; + "m_pVehicleItemTable", "m_pGeneralItemTable", "m_pChestItemTable" }; nlohmann::json itemSet; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 12; i++) { itemSet = xdtData[setNames[i]]["m_pItemData"]; for (nlohmann::json::iterator item = itemSet.begin(); item != itemSet.end(); item++) - ItemManager::ItemData[std::pair(item.value()["m_iItemNumber"], item.value()["m_iEquipLoc"])] - = { item.value()["m_iTradeAble"] == 1, item.value()["m_iSellAble"] == 1, item.value()["m_iItemPrice"], item.value()["m_iItemSellPrice"], item.value()["m_iStackNumber"], item.value()["m_iMinReqLev"] }; + ItemManager::ItemData[std::pair(item.value()["m_iItemNumber"], i == 11 ? 9 : (i == 10 ? 7 : item.value()["m_iEquipLoc"]))] + = { item.value()["m_iTradeAble"] == 1, item.value()["m_iSellAble"] == 1, item.value()["m_iItemPrice"], item.value()["m_iItemSellPrice"], item.value()["m_iStackNumber"], i > 9 ? 0 : item.value()["m_iMinReqLev"] }; } std::cout << "[INFO] Loaded " << ItemManager::ItemData.size() << " items" << std::endl; From a976fef2b4fae53ff419b5b0bb4e06e3b400366c Mon Sep 17 00:00:00 2001 From: Gent Date: Mon, 14 Sep 2020 01:52:15 -0400 Subject: [PATCH 4/5] Implement vendor stack logic --- src/NPCManager.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 9d421e2..3ca649a 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -41,7 +41,7 @@ void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { return; } - int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).buyPrice; + int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).buyPrice * req->Item.iOpt; int slot = ItemManager::findFreeSlot(plr); if (itemCost > plr->money || slot == -1) { // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. @@ -103,11 +103,17 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { // increment taros plr->money = plr->money + sellValue; - // empty the slot; probably needs to be changed for stacks - item->iID = 0; - item->iOpt = 0; - item->iType = 0; - item->iTimeLimit = 0; + // modify item + if (plr->Inven[req->iInvenSlotNum].iOpt - req->iItemCnt > 0) { // selling part of a stack + item->iOpt -= req->iItemCnt; + original.iOpt = req->iItemCnt; + } + else { // selling entire slot + item->iID = 0; + item->iOpt = 0; + item->iType = 0; + item->iTimeLimit = 0; + } // response parameters resp.iInvenSlotNum = req->iInvenSlotNum; @@ -134,7 +140,7 @@ void NPCManager::npcVendorRestore(CNSocket* sock, CNPacketData* data) { return; } - int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).sellPrice; // sell price is used on rebuy + int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).sellPrice * req->Item.iOpt; // sell price is used on rebuy int slot = ItemManager::findFreeSlot(plr); if (itemCost > plr->money || slot == -1) { // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. From 148d90f4f175c1ba76ba22695defa3cfa47c791d Mon Sep 17 00:00:00 2001 From: Gent Date: Mon, 14 Sep 2020 10:27:52 -0400 Subject: [PATCH 5/5] "Boosts and potions!" Fixed crate opening such that the item has an iOpt of 1. --- src/ItemManager.cpp | 11 +++--- src/ItemManager.hpp | 3 +- src/NPCManager.cpp | 81 ++++++++++++++++++++++++++++++++------------- src/NPCManager.hpp | 3 +- src/Player.hpp | 2 ++ src/TableData.cpp | 4 +-- 6 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index f45bb58..d27810f 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -721,6 +721,7 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { // item reward item->sItem.iType = 0; item->sItem.iID = 96; + item->sItem.iOpt = 1; item->iSlotNum = pkt->iSlotNum; item->eIL = pkt->eIL; @@ -751,10 +752,8 @@ int ItemManager::findFreeSlot(Player *plr) { return -1; } -bool ItemManager::isItemRegistered(int32_t id, int32_t type) { - return ItemData.find(std::pair(id, type)) != ItemData.end(); -} - -Item ItemManager::getItemData(int32_t id, int32_t type) { - return ItemData[std::pair(id, type)]; +Item* ItemManager::getItemData(int32_t id, int32_t type) { + if(ItemData.find(std::pair(id, type)) != ItemData.end()) + return &ItemData[std::pair(id, type)]; + return nullptr; } diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 00bd4b5..218f09c 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -41,6 +41,5 @@ namespace ItemManager { void chestOpenHandler(CNSocket* sock, CNPacketData* data); int findFreeSlot(Player *plr); - bool isItemRegistered(int32_t id, int32_t type); - Item getItemData(int32_t id, int32_t type); + Item* getItemData(int32_t id, int32_t type); } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 3ca649a..9e8d617 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -22,7 +22,8 @@ void NPCManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, npcVendorTable); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, npcVendorBuy); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, npcVendorSell); - REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorRestore); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorBuyback); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery); } void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { @@ -31,8 +32,9 @@ void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType); - if (!ItemManager::isItemRegistered(req->Item.iID, req->Item.iType)) { + if (item == nullptr) { std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl; // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); @@ -41,7 +43,7 @@ void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { return; } - int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).buyPrice * req->Item.iOpt; + int itemCost = item->sellPrice * (item->stackSize > 1 ? req->Item.iOpt : 1); int slot = ItemManager::findFreeSlot(plr); if (itemCost > plr->money || slot == -1) { // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. @@ -84,8 +86,9 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { } sItemBase* item = &plr->Inven[req->iInvenSlotNum]; + Item* itemData = ItemManager::getItemData(item->iID, item->iType); - if (!ItemManager::isItemRegistered(item->iID, item->iType) || !ItemManager::getItemData(item->iID, item->iType).sellable) { // sanity + sellable check + if (itemData == nullptr || !itemData->sellable) { // sanity + sellable check std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl; INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp); failResp.iErrorCode = 0; @@ -98,7 +101,7 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp); - int sellValue = ItemManager::getItemData(item->iID, item->iType).sellPrice * req->iItemCnt; + int sellValue = itemData->sellPrice * req->iItemCnt; // increment taros plr->money = plr->money + sellValue; @@ -124,14 +127,15 @@ void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC)); } -void NPCManager::npcVendorRestore(CNSocket* sock, CNPacketData* data) { +void NPCManager::npcVendorBuyback(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY)) return; // malformed packet sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType); - if (!ItemManager::isItemRegistered(req->Item.iID, req->Item.iType)) { + if (item == nullptr) { std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (rebuy)" << std::endl; // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp); @@ -140,7 +144,8 @@ void NPCManager::npcVendorRestore(CNSocket* sock, CNPacketData* data) { return; } - int itemCost = ItemManager::getItemData(req->Item.iID, req->Item.iType).sellPrice * req->Item.iOpt; // sell price is used on rebuy + // sell price is used on rebuy. ternary identifies stacked items + int itemCost = item->sellPrice * (item->stackSize > 1 ? req->Item.iOpt : 1); int slot = ItemManager::findFreeSlot(plr); if (itemCost > plr->money || slot == -1) { // NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side. @@ -180,23 +185,20 @@ void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp); - for (int i = 0; i < 20; i++) { // 20 is the max + for (int i = 0; i < listings.size() && i < 20; i++) { // 20 is the max + sItemBase base; + base.iID = listings[i].iID; + base.iOpt = 0; + base.iTimeLimit = 0; + base.iType = listings[i].type; - if (i < listings.size()) { // check to make sure listing exists first - sItemBase base; - base.iID = listings[i].iID; - base.iOpt = 0; - base.iTimeLimit = 0; - base.iType = listings[i].type; + sItemVendor vItem; + vItem.item = base; + vItem.iSortNum = listings[i].sort; + vItem.iVendorID = req->iVendorID; + //vItem.fBuyCost = listings[i].price; this value is not actually the one that is used - sItemVendor vItem; - vItem.item = base; - vItem.iSortNum = listings[i].sort; - vItem.iVendorID = req->iVendorID; - //vItem.fBuyCost = listings[i].price; - - resp.item[i] = vItem; - } + resp.item[i] = vItem; } sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC)); @@ -215,6 +217,39 @@ void NPCManager::npcVendorStart(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_START_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_START_SUCC)); } +void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY)) + return; // malformed packet + + sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + + int cost = req->Item.iOpt * 100; + if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost) { // sanity check + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp); + failResp.iErrorCode = 0; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL)); + } + + plr->money -= cost; + plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 10 : 0; + plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 10 : 0; + + // caps + if (plr->batteryW > 9999) + plr->batteryW = 9999; + if (plr->batteryN > 9999) + plr->batteryN = 9999; + + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp); + + resp.iCandy = plr->money; + resp.iBatteryW = plr->batteryW; + resp.iBatteryN = plr->batteryN; + + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC)); +} + void NPCManager::updatePlayerNPCS(CNSocket* sock, PlayerView& view) { std::list yesView; std::list noView; diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index c77d92b..9231442 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -28,7 +28,8 @@ namespace NPCManager { void npcVendorTable(CNSocket* sock, CNPacketData* data); void npcVendorBuy(CNSocket* sock, CNPacketData* data); void npcVendorSell(CNSocket* sock, CNPacketData* data); - void npcVendorRestore(CNSocket* sock, CNPacketData* data); + void npcVendorBuyback(CNSocket* sock, CNPacketData* data); + void npcVendorBuyBattery(CNSocket* sock, CNPacketData* data); void updatePlayerNPCS(CNSocket* sock, PlayerView& plr); } diff --git a/src/Player.hpp b/src/Player.hpp index 7bb514c..42536fa 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -19,6 +19,8 @@ struct Player { int slot; // player slot, not nano slot int32_t money; int32_t fusionmatter; + int32_t batteryW; + int32_t batteryN; sPCStyle PCStyle; sPCStyle2 PCStyle2; sNano Nanos[37]; // acquired nanos diff --git a/src/TableData.cpp b/src/TableData.cpp index f3748c9..dbe6de0 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -129,8 +129,8 @@ void TableData::init() { for (int i = 0; i < 12; i++) { itemSet = xdtData[setNames[i]]["m_pItemData"]; for (nlohmann::json::iterator item = itemSet.begin(); item != itemSet.end(); item++) - ItemManager::ItemData[std::pair(item.value()["m_iItemNumber"], i == 11 ? 9 : (i == 10 ? 7 : item.value()["m_iEquipLoc"]))] - = { item.value()["m_iTradeAble"] == 1, item.value()["m_iSellAble"] == 1, item.value()["m_iItemPrice"], item.value()["m_iItemSellPrice"], item.value()["m_iStackNumber"], i > 9 ? 0 : item.value()["m_iMinReqLev"] }; + ItemManager::ItemData[std::pair(item.value()["m_iItemNumber"], i == 11 ? 9 : (i == 10 ? 7 : (int)item.value()["m_iEquipLoc"]))] + = { item.value()["m_iTradeAble"] == 1, item.value()["m_iSellAble"] == 1, item.value()["m_iItemPrice"], item.value()["m_iItemSellPrice"], item.value()["m_iStackNumber"], i > 9 ? 0 : (int)item.value()["m_iMinReqLev"] }; } std::cout << "[INFO] Loaded " << ItemManager::ItemData.size() << " items" << std::endl;