From dd41d5b6102e5251c8fc8d72c508fc5c91a9f0e3 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Mon, 15 Mar 2021 10:48:27 -0400 Subject: [PATCH] [refactor] Split vendor functions and crocpot out of NPCManager --- src/ItemManager.cpp | 97 +++++++++++ src/NPCManager.cpp | 383 +------------------------------------------- src/NPCManager.hpp | 8 - src/Vendor.cpp | 293 ++++++++++++++++++++++++++++++++- src/Vendor.hpp | 16 +- src/main.cpp | 2 + 6 files changed, 407 insertions(+), 392 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index 8a65d19..0c8d646 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -344,6 +344,101 @@ void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_BANK_OPEN_SUCC, sizeof(sP_FE2CL_REP_PC_BANK_OPEN_SUCC)); } +void itemCombineHandler(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_ITEM_COMBINATION)) + return; // malformed packet + + sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + + // prepare fail packet + INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); + failResp.iCostumeItemSlot = req->iCostumeItemSlot; + failResp.iStatItemSlot = req->iStatItemSlot; + failResp.iErrorCode = 0; + + // sanity check slot indices + if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { + std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); + return; + } + + sItemBase* itemStats = &plr->Inven[req->iStatItemSlot]; + sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot]; + ItemManager::Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType); + ItemManager::Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType); + + // sanity check item and combination entry existence + if (itemStatsDat == nullptr || itemLooksDat == nullptr + || ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { + std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); + return; + } + + // sanity check matching item types + if (itemStats->iType != itemLooks->iType + || (itemStats->iType == 0 && itemStatsDat->weaponType != itemLooksDat->weaponType)) { + std::cout << "[WARN] Player attempted to combine mismatched items" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); + return; + } + + CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)]; + int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks; + float successChance = recipe->base / 100.0f; // base success chance + + // rarity gap multiplier + switch (abs(itemStatsDat->rarity - itemLooksDat->rarity)) { + case 0: + successChance *= recipe->rd0; + break; + case 1: + successChance *= recipe->rd1; + break; + case 2: + successChance *= recipe->rd2; + break; + case 3: + successChance *= recipe->rd3; + break; + default: + break; + } + + float rolled = (rand() * 1.0f / RAND_MAX) * 100.0f; // success chance out of 100 + //std::cout << rolled << " vs " << successChance << std::endl; + plr->money -= cost; + + + INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp); + if (rolled < successChance) { + // success + resp.iSuccessFlag = 1; + + // modify the looks item with the new stats and set the appearance through iOpt + itemLooks->iOpt = (int32_t)((itemLooks->iOpt) >> 16 > 0 ? (itemLooks->iOpt >> 16) : itemLooks->iID) << 16; + itemLooks->iID = itemStats->iID; + + // delete stats item + itemStats->iID = 0; + itemStats->iOpt = 0; + itemStats->iTimeLimit = 0; + itemStats->iType = 0; + } + else { + // failure; don't do anything? + resp.iSuccessFlag = 0; + } + resp.iCandy = plr->money; + resp.iNewItemSlot = req->iCostumeItemSlot; + resp.iStatItemSlot = req->iStatItemSlot; + resp.sNewItem = *itemLooks; + + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC)); +} + void chestOpenHandler(CNSocket *sock, CNPacketData *data) { if (data->size != sizeof(sP_CL2FE_REQ_ITEM_CHEST_OPEN)) return; // ignore the malformed packet @@ -622,4 +717,6 @@ void ItemManager::init() { // Bank REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BANK_OPEN, itemBankOpenHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_CHEST_OPEN, chestOpenHandler); + // Croc Pot + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, itemCombineHandler); } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index bd3ccf5..f1875b8 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -45,13 +45,7 @@ void NPCManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_SUMMON, npcSummonHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler); - REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_START, npcVendorStart); - 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, npcVendorBuyback); - REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery); - REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, npcCombineItems); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_PICKUP, eggPickup); REGISTER_SHARD_TIMER(eggStep, 1000); @@ -115,381 +109,6 @@ void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t s } } -void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { - if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY)) - return; // malformed packet - - sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf; - Player *plr = PlayerManager::getPlayer(sock); - - // prepare fail packet - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); - failResp.iErrorCode = 0; - - ItemManager::Item* itemDat = ItemManager::getItemData(req->Item.iID, req->Item.iType); - - if (itemDat == nullptr) { - std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); - return; - } - - int itemCost = itemDat->buyPrice * (itemDat->stackSize > 1 ? req->Item.iOpt : 1); - int slot = ItemManager::findFreeSlot(plr); - if (itemCost > plr->money || slot == -1) { - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); - return; - } - - // crates don't have a stack size in TableData, so we can't check those - if (itemDat->stackSize != 0 && req->Item.iOpt > itemDat->stackSize) { - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); - return; - } - - // if vehicle - if (req->Item.iType == 10) { - // set time limit: current time + 7days - req->Item.iTimeLimit = getTimestamp() + 604800; - } - - if (slot != req->iInvenSlotNum) { - // possible item stacking? - std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl; - } - - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp); - - plr->money = plr->money - itemCost; - plr->Inven[slot] = req->Item; - - resp.iCandy = plr->money; - resp.iInvenSlotNum = slot; - resp.Item = req->Item; - - sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC)); -} - -void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) { - if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL)) - return; // malformed packet - - sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf; - Player* plr = PlayerManager::getPlayer(sock); - - // prepare a fail packet - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp); - failResp.iErrorCode = 0; - - if (req->iInvenSlotNum < 0 || req->iInvenSlotNum >= AINVEN_COUNT || req->iItemCnt < 0) { - std::cout << "[WARN] Client failed to sell item in slot " << req->iInvenSlotNum << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL)); - return; - } - - sItemBase* item = &plr->Inven[req->iInvenSlotNum]; - ItemManager::Item* itemData = ItemManager::getItemData(item->iID, item->iType); - - if (itemData == nullptr || !itemData->sellable || item->iOpt < req->iItemCnt) { // sanity + sellable check - std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL)); - return; - } - - // fail to sell croc-potted items - if (item->iOpt >= 1 << 16) { - 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); - - // increment taros - plr->money += itemData->sellPrice * req->iItemCnt; - - // modify item - if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack - item->iOpt -= req->iItemCnt; - original.iOpt = req->iItemCnt; - } else { // selling entire slot - // make sure it's fully zeroed, even the padding and non-104 members - memset(item, 0, sizeof(*item)); - } - - // add to buyback list - plr->buyback->push_back(original); - // forget oldest member if there's more than 5 - if (plr->buyback->size() > 5) - plr->buyback->erase(plr->buyback->begin()); - //std::cout << (int)plr->buyback->size() << " items in buyback\n"; - - // response parameters - resp.iInvenSlotNum = req->iInvenSlotNum; - resp.iCandy = plr->money; - 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::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); - - // prepare fail packet - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp); - failResp.iErrorCode = 0; - - //std::cout << "buying back from index " << (int)req->iListID << " into " << (int)req->iInvenSlotNum << - // " from " << (int)req->iNPC_ID << " (vendor = " << (int)req->iVendorID << ")\n"; - - int idx = req->iListID - 1; - - // sanity check - if (idx < 0 || idx >= plr->buyback->size()) { - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL)); - return; - } - - // get the item out of the buyback list - sItemBase item = (*plr->buyback)[idx]; - /* - * NOTE: The client sends the index of the exact item the user clicked on. - * We then operate on that item, but we remove the *first* identical item - * from the buyback list, instead of the one at the supplied index. - * - * This was originally a mistake on my part, but it turns out the client - * does the exact same thing, so this *is* the correct thing to do to keep - * them in sync. - */ - for (auto it = plr->buyback->begin(); it != plr->buyback->end(); it++) { - /* - * XXX: we really need a standard item comparison function that - * will work properly across all builds (ex. with iSerial) - */ - if (it->iType == item.iType && it->iID == item.iID && it->iOpt == item.iOpt - && it->iTimeLimit == item.iTimeLimit) { - plr->buyback->erase(it); - break; - } - } - //std::cout << (int)plr->buyback->size() << " items in buyback\n"; - - ItemManager::Item* itemDat = ItemManager::getItemData(item.iID, item.iType); - - if (itemDat == nullptr) { - std::cout << "[WARN] Item id " << item.iID << " with type " << item.iType << " not found (rebuy)" << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL)); - return; - } - - // sell price is used on rebuy. ternary identifies stacked items - int itemCost = itemDat->sellPrice * (itemDat->stackSize > 1 ? item.iOpt : 1); - int slot = ItemManager::findFreeSlot(plr); - if (itemCost > plr->money || slot == -1) { - 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] = item; - - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp); - // response parameters - resp.iCandy = plr->money; - resp.iInvenSlotNum = slot; - resp.Item = 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 - - sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE* req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf; - - if (req->iVendorID != req->iNPC_ID || Vendor::VendorTables.find(req->iVendorID) == Vendor::VendorTables.end()) - return; - - std::vector listings = Vendor::VendorTables[req->iVendorID]; - - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp); - - for (int i = 0; i < (int)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; - - 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 - - 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)); -} - -void NPCManager::npcVendorStart(CNSocket* sock, CNPacketData* data) { - if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_START)) - return; // malformed packet - - sP_CL2FE_REQ_PC_VENDOR_START* req = (sP_CL2FE_REQ_PC_VENDOR_START*)data->buf; - INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_START_SUCC, resp); - - resp.iNPC_ID = req->iNPC_ID; - resp.iVendorID = req->iVendorID; - - 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 || req->Item.iOpt < 0) { // 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)); - return; - } - - cost = plr->batteryW + plr->batteryN; - plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 100 : 0; - plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 100 : 0; - - // caps - if (plr->batteryW > 9999) - plr->batteryW = 9999; - if (plr->batteryN > 9999) - plr->batteryN = 9999; - - cost = plr->batteryW + plr->batteryN - cost; - plr->money -= cost; - - 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::npcCombineItems(CNSocket* sock, CNPacketData* data) { - if (data->size != sizeof(sP_CL2FE_REQ_PC_ITEM_COMBINATION)) - return; // malformed packet - - sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf; - Player* plr = PlayerManager::getPlayer(sock); - - // prepare fail packet - INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); - failResp.iCostumeItemSlot = req->iCostumeItemSlot; - failResp.iStatItemSlot = req->iStatItemSlot; - failResp.iErrorCode = 0; - - // sanity check slot indices - if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { - std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); - return; - } - - sItemBase* itemStats = &plr->Inven[req->iStatItemSlot]; - sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot]; - ItemManager::Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType); - ItemManager::Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType); - - // sanity check item and combination entry existence - if (itemStatsDat == nullptr || itemLooksDat == nullptr - || ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { - std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); - return; - } - - // sanity check matching item types - if (itemStats->iType != itemLooks->iType - || (itemStats->iType == 0 && itemStatsDat->weaponType != itemLooksDat->weaponType)) { - std::cout << "[WARN] Player attempted to combine mismatched items" << std::endl; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); - return; - } - - CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)]; - int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks; - float successChance = recipe->base / 100.0f; // base success chance - - // rarity gap multiplier - switch(abs(itemStatsDat->rarity - itemLooksDat->rarity)) { - case 0: - successChance *= recipe->rd0; - break; - case 1: - successChance *= recipe->rd1; - break; - case 2: - successChance *= recipe->rd2; - break; - case 3: - successChance *= recipe->rd3; - break; - default: - break; - } - - float rolled = (rand() * 1.0f / RAND_MAX) * 100.0f; // success chance out of 100 - //std::cout << rolled << " vs " << successChance << std::endl; - plr->money -= cost; - - - INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp); - if (rolled < successChance) { - // success - resp.iSuccessFlag = 1; - - // modify the looks item with the new stats and set the appearance through iOpt - itemLooks->iOpt = (int32_t)((itemLooks->iOpt) >> 16 > 0 ? (itemLooks->iOpt >> 16) : itemLooks->iID) << 16; - itemLooks->iID = itemStats->iID; - - // delete stats item - itemStats->iID = 0; - itemStats->iOpt = 0; - itemStats->iTimeLimit = 0; - itemStats->iType = 0; - } else { - // failure; don't do anything? - resp.iSuccessFlag = 0; - } - resp.iCandy = plr->money; - resp.iNewItemSlot = req->iCostumeItemSlot; - resp.iStatItemSlot = req->iStatItemSlot; - resp.sNewItem = *itemLooks; - - sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC)); -} - void NPCManager::npcBarkHandler(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_BARKER)) return; // malformed packet diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 601ba6f..e9d0164 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -75,14 +75,6 @@ namespace NPCManager { void npcWarpHandler(CNSocket* sock, CNPacketData* data); void npcWarpTimeMachine(CNSocket* sock, CNPacketData* data); - void npcVendorStart(CNSocket* sock, CNPacketData* data); - void npcVendorTable(CNSocket* sock, CNPacketData* data); - void npcVendorBuy(CNSocket* sock, CNPacketData* data); - void npcVendorSell(CNSocket* sock, CNPacketData* data); - void npcVendorBuyback(CNSocket* sock, CNPacketData* data); - void npcVendorBuyBattery(CNSocket* sock, CNPacketData* data); - void npcCombineItems(CNSocket* sock, CNPacketData* data); - void handleWarp(CNSocket* sock, int32_t warpId); BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false); diff --git a/src/Vendor.cpp b/src/Vendor.cpp index 04c0741..70426e8 100644 --- a/src/Vendor.cpp +++ b/src/Vendor.cpp @@ -1,3 +1,294 @@ #include "Vendor.hpp" -std::map> Vendor::VendorTables; \ No newline at end of file +std::map> Vendor::VendorTables; + +void Vendor::vendorBuy(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY)) + return; // malformed packet + + sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + + // prepare fail packet + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); + failResp.iErrorCode = 0; + + ItemManager::Item* itemDat = ItemManager::getItemData(req->Item.iID, req->Item.iType); + + if (itemDat == nullptr) { + std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); + return; + } + + int itemCost = itemDat->buyPrice * (itemDat->stackSize > 1 ? req->Item.iOpt : 1); + int slot = ItemManager::findFreeSlot(plr); + if (itemCost > plr->money || slot == -1) { + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); + return; + } + + // crates don't have a stack size in TableData, so we can't check those + if (itemDat->stackSize != 0 && req->Item.iOpt > itemDat->stackSize) { + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL)); + return; + } + + // if vehicle + if (req->Item.iType == 10) { + // set time limit: current time + 7days + req->Item.iTimeLimit = getTimestamp() + 604800; + } + + if (slot != req->iInvenSlotNum) { + // possible item stacking? + std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl; + } + + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp); + + plr->money = plr->money - itemCost; + plr->Inven[slot] = req->Item; + + resp.iCandy = plr->money; + resp.iInvenSlotNum = slot; + resp.Item = req->Item; + + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC)); +} + +void Vendor::vendorSell(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL)) + return; // malformed packet + + sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + + // prepare a fail packet + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp); + failResp.iErrorCode = 0; + + if (req->iInvenSlotNum < 0 || req->iInvenSlotNum >= AINVEN_COUNT || req->iItemCnt < 0) { + std::cout << "[WARN] Client failed to sell item in slot " << req->iInvenSlotNum << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL)); + return; + } + + sItemBase* item = &plr->Inven[req->iInvenSlotNum]; + ItemManager::Item* itemData = ItemManager::getItemData(item->iID, item->iType); + + if (itemData == nullptr || !itemData->sellable || item->iOpt < req->iItemCnt) { // sanity + sellable check + std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL)); + return; + } + + // fail to sell croc-potted items + if (item->iOpt >= 1 << 16) { + 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); + + // increment taros + plr->money += itemData->sellPrice * req->iItemCnt; + + // modify item + if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack + item->iOpt -= req->iItemCnt; + original.iOpt = req->iItemCnt; + } + else { // selling entire slot + // make sure it's fully zeroed, even the padding and non-104 members + memset(item, 0, sizeof(*item)); + } + + // add to buyback list + plr->buyback->push_back(original); + // forget oldest member if there's more than 5 + if (plr->buyback->size() > 5) + plr->buyback->erase(plr->buyback->begin()); + //std::cout << (int)plr->buyback->size() << " items in buyback\n"; + + // response parameters + resp.iInvenSlotNum = req->iInvenSlotNum; + resp.iCandy = plr->money; + 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 Vendor::vendorBuyback(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); + + // prepare fail packet + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp); + failResp.iErrorCode = 0; + + //std::cout << "buying back from index " << (int)req->iListID << " into " << (int)req->iInvenSlotNum << + // " from " << (int)req->iNPC_ID << " (vendor = " << (int)req->iVendorID << ")\n"; + + int idx = req->iListID - 1; + + // sanity check + if (idx < 0 || idx >= plr->buyback->size()) { + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL)); + return; + } + + // get the item out of the buyback list + sItemBase item = (*plr->buyback)[idx]; + /* + * NOTE: The client sends the index of the exact item the user clicked on. + * We then operate on that item, but we remove the *first* identical item + * from the buyback list, instead of the one at the supplied index. + * + * This was originally a mistake on my part, but it turns out the client + * does the exact same thing, so this *is* the correct thing to do to keep + * them in sync. + */ + for (auto it = plr->buyback->begin(); it != plr->buyback->end(); it++) { + /* + * XXX: we really need a standard item comparison function that + * will work properly across all builds (ex. with iSerial) + */ + if (it->iType == item.iType && it->iID == item.iID && it->iOpt == item.iOpt + && it->iTimeLimit == item.iTimeLimit) { + plr->buyback->erase(it); + break; + } + } + //std::cout << (int)plr->buyback->size() << " items in buyback\n"; + + ItemManager::Item* itemDat = ItemManager::getItemData(item.iID, item.iType); + + if (itemDat == nullptr) { + std::cout << "[WARN] Item id " << item.iID << " with type " << item.iType << " not found (rebuy)" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL)); + return; + } + + // sell price is used on rebuy. ternary identifies stacked items + int itemCost = itemDat->sellPrice * (itemDat->stackSize > 1 ? item.iOpt : 1); + int slot = ItemManager::findFreeSlot(plr); + if (itemCost > plr->money || slot == -1) { + 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] = item; + + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp); + // response parameters + resp.iCandy = plr->money; + resp.iInvenSlotNum = slot; + resp.Item = 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 Vendor::vendorTable(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; + + if (req->iVendorID != req->iNPC_ID || Vendor::VendorTables.find(req->iVendorID) == Vendor::VendorTables.end()) + return; + + std::vector listings = Vendor::VendorTables[req->iVendorID]; + + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp); + + for (int i = 0; i < (int)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; + + 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 + + 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)); +} + +void Vendor::vendorStart(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_START)) + return; // malformed packet + + sP_CL2FE_REQ_PC_VENDOR_START* req = (sP_CL2FE_REQ_PC_VENDOR_START*)data->buf; + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_START_SUCC, resp); + + resp.iNPC_ID = req->iNPC_ID; + resp.iVendorID = req->iVendorID; + + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_START_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_START_SUCC)); +} + +void Vendor::vendorBuyBattery(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 || req->Item.iOpt < 0) { // 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)); + return; + } + + cost = plr->batteryW + plr->batteryN; + plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 100 : 0; + plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 100 : 0; + + // caps + if (plr->batteryW > 9999) + plr->batteryW = 9999; + if (plr->batteryN > 9999) + plr->batteryN = 9999; + + cost = plr->batteryW + plr->batteryN - cost; + plr->money -= cost; + + 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 Vendor::init() { + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_START, vendorStart); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, vendorTable); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, vendorBuy); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, vendorSell); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, vendorBuyback); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, vendorBuyBattery); +} diff --git a/src/Vendor.hpp b/src/Vendor.hpp index a06dbb1..f6a7b1d 100644 --- a/src/Vendor.hpp +++ b/src/Vendor.hpp @@ -1,6 +1,11 @@ #pragma once +#include "CNProtocol.hpp" +#include "CNStructs.hpp" +#include "CNShardServer.hpp" + #include "ItemManager.hpp" +#include "PlayerManager.hpp" struct VendorListing { int sort, type, iID; @@ -8,4 +13,13 @@ struct VendorListing { namespace Vendor { extern std::map> VendorTables; -} \ No newline at end of file + + void init(); + + void vendorStart(CNSocket* sock, CNPacketData* data); + void vendorTable(CNSocket* sock, CNPacketData* data); + void vendorBuy(CNSocket* sock, CNPacketData* data); + void vendorSell(CNSocket* sock, CNPacketData* data); + void vendorBuyback(CNSocket* sock, CNPacketData* data); + void vendorBuyBattery(CNSocket* sock, CNPacketData* data); +} diff --git a/src/main.cpp b/src/main.cpp index 50e2ddd..372a3db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include "RacingManager.hpp" #include "Trading.hpp" #include "Email.hpp" +#include "Vendor.hpp" #include "settings.hpp" @@ -109,6 +110,7 @@ int main() { MissionManager::init(); NanoManager::init(); NPCManager::init(); + Vendor::init(); TransportManager::init(); BuddyManager::init(); Email::init();