diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index b6b2bc2..77b39bf 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -9,6 +9,7 @@ std::map, Item> ItemManager::ItemData; std::map> ItemManager::VendorTables; +std::map ItemManager::CrocPotTable; void ItemManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler); diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 218f09c..7c627fb 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -5,11 +5,15 @@ struct Item { bool tradeable, sellable; - int buyPrice, sellPrice, stackSize, level; // TODO: implement more as needed + int buyPrice, sellPrice, stackSize, level, rarity; // TODO: implement more as needed }; struct VendorListing { int sort, type, iID; }; +struct CrocPotEntry { + int multStats, multLooks; + float base, rd0, rd1, rd2, rd3; +}; namespace ItemManager { enum class SlotType { @@ -20,6 +24,7 @@ namespace ItemManager { // hopefully this is fine since it's never modified after load extern std::map, Item> ItemData; // -> data extern std::map> VendorTables; + extern std::map CrocPotTable; // level gap -> entry void init(); diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 6db9dfa..156b035 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -25,6 +25,7 @@ void NPCManager::init() { 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); } void NPCManager::addNPC(std::vector viewableChunks, int32_t id) { @@ -279,6 +280,95 @@ void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) { 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); + + if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { // sanity check 1 + INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); + failResp.iCostumeItemSlot = req->iCostumeItemSlot; + failResp.iStatItemSlot = req->iStatItemSlot; + failResp.iErrorCode = 0; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); + std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl; + return; + } + + sItemBase* itemStats = &plr->Inven[req->iStatItemSlot]; + sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot]; + Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType); + Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType); + + if (itemStatsDat == nullptr || itemLooksDat == nullptr + || ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) // sanity check 2 + { + INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); + failResp.iCostumeItemSlot = req->iCostumeItemSlot; + failResp.iStatItemSlot = req->iStatItemSlot; + failResp.iErrorCode = 0; + 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; + } + + 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; + + // 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; + 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->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) {} // stubbed for now void NPCManager::npcSummonHandler(CNSocket* sock, CNPacketData* data) { diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index b23e72d..697a870 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -33,4 +33,6 @@ namespace NPCManager { 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 updatePlayerNPCS(CNSocket* sock, PlayerView& plr); } diff --git a/src/TableData.cpp b/src/TableData.cpp index 82a9b9a..84d5cc5 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -110,11 +110,11 @@ void TableData::init() { nlohmann::json itemSet; for (int i = 0; i < 12; i++) { itemSet = xdtData[setNames[i]]["m_pItemData"]; - for (nlohmann::json::iterator _item = itemSet.begin(); _item != itemSet.end(); _item++) { auto item = _item.value(); ItemManager::ItemData[std::pair(item["m_iItemNumber"], i == 11 ? 9 : (i == 10 ? 7 : (int)item["m_iEquipLoc"]))] - = { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"] }; + = { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"], + i > 9 ? 1 : (int)item["m_iRarity"] }; } } @@ -130,6 +130,17 @@ void TableData::init() { } std::cout << "[INFO] Loaded " << ItemManager::VendorTables.size() << " vendor tables" << std::endl; + + // load crocpot entries + nlohmann::json crocs = xdtData["m_pCombiningTable"]["m_pCombiningData"]; + + for (nlohmann::json::iterator croc = crocs.begin(); croc != crocs.end(); croc++) { + CrocPotEntry crocEntry = { croc.value()["m_iStatConstant"], croc.value()["m_iLookConstant"], croc.value()["m_fLevelGapStandard"], + croc.value()["m_fSameGrade"], croc.value()["m_fOneGrade"], croc.value()["m_fTwoGrade"], croc.value()["m_fThreeGrade"] }; + ItemManager::CrocPotTable[croc.value()["m_iLevelGap"]] = crocEntry; + } + + std::cout << "[INFO] Loaded " << ItemManager::CrocPotTable.size() << " croc pot value sets" << std::endl; } catch (const std::exception& err) { std::cerr << "[WARN] Malformed xdt.json file! Reason:" << err.what() << std::endl;