From dd5466869747aaa3b1ba60efac2fcee79a3c3990 Mon Sep 17 00:00:00 2001 From: kamilprzyb Date: Sat, 10 Oct 2020 19:18:47 +0200 Subject: [PATCH] functional crates (no more plungers) (#133) * FM, Taros and Boosts awards from killing mobs should be pretty accurate now. A temporary formula for adjusting player/mob level gap is implemented, but it will probably need to be adjusted in the future * Mobs now drop correct crates * Crates can be opened and give you correct items This includes regular mob crates, world boss crates, mission crates, IZ race crates, E.G.G.E.R.s, golden Eggs, and Event Crates. Keep in mind that neither IZ races or golden Eggs are implemented, but if you spawn such a crate it can be opened. * All data is read from a json file, for which I'm going to release a tool soon so it's easily adjustable * There is a new setting for enabling events, which enables dropping extra event crates These are Knishmas, Halloween and Easter --- src/ItemManager.cpp | 144 ++++++++++++++++++++++++++++++++++++++----- src/ItemManager.hpp | 19 +++++- src/MobManager.cpp | 146 +++++++++++++++++++++++++++++++++++++++----- src/MobManager.hpp | 26 +++++++- src/TableData.cpp | 113 +++++++++++++++++++++++++++++++++- src/TableData.hpp | 1 + src/main.cpp | 8 ++- src/settings.cpp | 8 +++ src/settings.hpp | 3 + 9 files changed, 435 insertions(+), 33 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index 37beac3..1c1e36b 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -11,6 +11,13 @@ std::map, Item> ItemManager::ItemData; std::map> ItemManager::VendorTables; std::map ItemManager::CrocPotTable; +std::map> ItemManager::RarityRatios; +std::map ItemManager::Crates; +/// pair Itemset, Rarity -> vector of pointers (map iterators) to records in ItemData +std::map, std::vector, Item>::iterator>> ItemManager::CrateItems; + +// buffer for error messages +char buffer[255]; void ItemManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler); @@ -801,12 +808,12 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { if (data->size != sizeof(sP_CL2FE_REQ_ITEM_CHEST_OPEN)) return; // ignore the malformed packet - sP_CL2FE_REQ_ITEM_CHEST_OPEN *pkt = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf; - Player *plr = PlayerManager::getPlayer(sock); + sP_CL2FE_REQ_ITEM_CHEST_OPEN *chest = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf; + Player *player = PlayerManager::getPlayer(sock); - if (plr == nullptr) + if (player == nullptr) return; - + // item giving packet const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); assert(resplen < CN_PACKET_BUFFER_SIZE - 8); @@ -820,21 +827,23 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { memset(respbuf, 0, resplen); // maintain stats - reward->m_iCandy = plr->money; - reward->m_iFusionMatter = plr->fusionmatter; + reward->m_iCandy = player->money; + reward->m_iFusionMatter = player->fusionmatter; reward->iFatigue = 100; // prevents warning message reward->iFatigue_Level = 1; reward->iItemCnt = 1; // remember to update resplen if you change this - // item reward - item->sItem.iType = 0; - item->sItem.iID = 96; - item->sItem.iOpt = 1; - item->iSlotNum = pkt->iSlotNum; - item->eIL = pkt->eIL; + // item reward + if (chest->ChestItem.iType != 9) + std::cout << "[WARN] Player tried to open a crate with incorrect iType ?!" << std::endl; + else + item->sItem = openCrate(chest->ChestItem.iID, player->PCStyle.iGender); + + item->iSlotNum = chest->iSlotNum; + item->eIL = chest->eIL; // update player - plr->Inven[pkt->iSlotNum] = item->sItem; + player->Inven[chest->iSlotNum] = item->sItem; // transmit item sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); @@ -842,12 +851,119 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { // chest opening acknowledgement packet INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp); - resp.iSlotNum = pkt->iSlotNum; + resp.iSlotNum = chest->iSlotNum; std::cout << "opening chest..." << std::endl; sock->sendPacket((void*)&resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, sizeof(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC)); } +sItemBase ItemManager::openCrate(int crateId, int playerGender) { + sItemBase reward = {}; + try { + Crate crate = getCrate(crateId); + int itemSetId = getItemSetId(crate, crateId); + int rarity = getRarity(crate, itemSetId); + reward = getCrateItem(itemSetId, rarity, playerGender); + } + catch (const std::exception& err) { + std::cerr << "[WARN] An error has occured while trying to open a crate. Error description: \n" << err.what() << std::endl; + // if we failed to open a crate, at least give the player a gumball (suggested by Jade) + reward.iType = 7; + reward.iID = 119 + (rand() % 3); + reward.iOpt = 1; + } + + return reward; +} + +Crate ItemManager::getCrate(int crateId) { + if (Crates.find(crateId) == Crates.end()) + throwError(sprintf(buffer, "Crate %d was not found!", crateId)); + return Crates[crateId]; +} + +int ItemManager::getItemSetId(Crate crate, int crateId) { + int itemSetsCount = crate.itemSets.size(); + if (itemSetsCount == 0) + throwError(sprintf(buffer, "Crate %d has no item sets assigned?!", crateId)); + + // if crate points to multiple itemSets, choose a random one + int itemSetIndex = rand() % itemSetsCount; + return crate.itemSets[itemSetIndex]; +} + +int ItemManager::getRarity(Crate crate , int itemSetId) { + // find rarity ratio + if (RarityRatios.find(crate.rarityRatioId) == RarityRatios.end()) + throwError(sprintf(buffer, "Rarity Ratio %d not found!", crate.rarityRatioId)); + + std::vector rarityRatio = RarityRatios[crate.rarityRatioId]; + + /* + * First we have to check if specified item set contains items with all specified rarities, + * and if not eliminate them from the draw + * it is simpler to do here than to fix individually in the file + */ + + // remember that rarities start from 1 ! + for (int i = 0; i < rarityRatio.size(); i++){ + if (CrateItems.find(std::make_pair(itemSetId, i+1)) == CrateItems.end()) + rarityRatio[i] = 0; + } + + int total = 0; + for (int value : rarityRatio) + total += value; + + // if we didn't find any items, throw exception + if (total == 0) + throwError(sprintf(buffer, "Item Set %d has no items assigned?!", itemSetId)); + + // now return a random rarity number + int randomNum = rand() % total; + int rarity = 0; + int sum = 0; + do { + sum += rarityRatio[rarity]; + rarity++; + } while (sum <= randomNum); + + return rarity; +} + +sItemBase ItemManager::getCrateItem(int itemSetId, int rarity, int playerGender) { + std::pair key = std::make_pair(itemSetId, rarity); + + if (CrateItems.find(key) == CrateItems.end()) + throwError(sprintf(buffer, "Item Set ID %d Rarity %d items have not been found", itemSetId, rarity)); + + // only take into account items that have correct gender + std::vector, Item>::iterator> items; + for (auto crateitem : CrateItems[key]) + { + int gender = crateitem->second.gender; + // if gender is incorrect, exclude item + if (gender != 0 && gender != playerGender) + continue; + items.push_back(crateitem); + } + + if (items.size() == 0) + throwError(sprintf(buffer, "Gender inequality! Set ID %d Rarity %d contains only %s items?!", itemSetId, rarity, playerGender==2 ? "boys" : "girls")); + auto item = items[rand() % items.size()]; + sItemBase result = {}; + result.iID = item->first.first; + result.iType = item->first.second; + result.iOpt = 1; + + return result; +} + +// argument is here only so we can call sprintf in brackets +void ItemManager::throwError(int ignore) { + throw buffer; +} + // TODO: use this in cleaned up ItemManager int ItemManager::findFreeSlot(Player *plr) { int i; diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 819075e..9d9a8cd 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -5,7 +5,7 @@ struct Item { bool tradeable, sellable; - int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, defense; // TODO: implement more as needed + int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, defense, gender; // TODO: implement more as needed }; struct VendorListing { int sort, type, iID; @@ -14,6 +14,10 @@ struct CrocPotEntry { int multStats, multLooks; float base, rd0, rd1, rd2, rd3; }; +struct Crate { + int rarityRatioId; + std::vector itemSets; +}; namespace ItemManager { enum class SlotType { @@ -25,6 +29,11 @@ namespace ItemManager { extern std::map, Item> ItemData; // -> data extern std::map> VendorTables; extern std::map CrocPotTable; // level gap -> entry + extern std::map> RarityRatios; + extern std::map Crates; + // pair -> vector of pointers (map iterators) to records in ItemData (it looks a lot scarier than it is) + extern std::map, + std::vector, Item>::iterator>> CrateItems; void init(); @@ -46,6 +55,14 @@ namespace ItemManager { void itemTradeChatHandler(CNSocket* sock, CNPacketData* data); void chestOpenHandler(CNSocket* sock, CNPacketData* data); + // crate opening logic with all helper functions + sItemBase openCrate(int crateId, int playerGender); + Crate getCrate(int crateId); + int getItemSetId(Crate crate, int crateId); + int getRarity(Crate crate, int itemSetId); + sItemBase getCrateItem(int itemSetId, int rarity, int playerGender); + void throwError(int ignore); + int findFreeSlot(Player *plr); Item* getItemData(int32_t id, int32_t type); void checkItemExpire(CNSocket* sock, Player* player); diff --git a/src/MobManager.cpp b/src/MobManager.cpp index d2a33f1..af54a4c 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -11,6 +11,8 @@ #include std::map MobManager::Mobs; +std::map MobManager::MobDropChances; +std::map MobManager::MobDrops; std::queue MobManager::RemovalQueue; bool MobManager::simulateMobs; @@ -143,7 +145,7 @@ void MobManager::npcAttackPc(Mob *mob, time_t currTime) { } } -void MobManager::giveReward(CNSocket *sock) { +void MobManager::giveReward(CNSocket *sock, Mob* mob) { Player *plr = PlayerManager::getPlayer(sock); if (plr == nullptr) @@ -160,9 +162,31 @@ void MobManager::giveReward(CNSocket *sock) { // don't forget to zero the buffer! memset(respbuf, 0, resplen); - // NOTE: these will need to be scaled according to the player/mob level difference - plr->money += (int)MissionManager::AvatarGrowth[plr->level]["m_iMobFM"]; // this one's innacurate, but close enough for now - MissionManager::updateFusionMatter(sock, MissionManager::AvatarGrowth[plr->level]["m_iMobFM"]); + // sanity check + if (MobDrops.find(mob->dropType) == MobDrops.end()) { + std::cout << "[WARN] Drop Type " << mob->dropType << " was not found" << std::endl; + return; + } + // find correct mob drop + MobDrop drop = MobDrops[mob->dropType]; + + plr->money += drop.taros; + // formula for scaling FM with player/mob level difference + // TODO: adjust this better + int levelDifference = plr->level - mob->level; + int fm = drop.fm; + if (levelDifference > 0) + fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0; + + MissionManager::updateFusionMatter(sock, fm); + + // give boosts 1 in 3 times + if (drop.boosts > 0) { + if (rand() % 3 == 0) + plr->batteryN += drop.boosts; + if (rand() % 3 == 0) + plr->batteryW += drop.boosts; + } // simple rewards reward->m_iCandy = plr->money; @@ -173,20 +197,29 @@ void MobManager::giveReward(CNSocket *sock) { reward->iFatigue_Level = 1; reward->iItemCnt = 1; // remember to update resplen if you change this -#if 0 + int slot = ItemManager::findFreeSlot(plr); - if (slot == -1) { -#else - int slot = -1; - if (true) { -#endif + + bool awardDrop = false; + MobDropChance chance; + // sanity check + if (MobDropChances.find(drop.dropChanceType) == MobDropChances.end()) + std::cout << "[WARN] Unknown Drop Chance Type: " << drop.dropChanceType << std::endl; + else { + chance = MobDropChances[drop.dropChanceType]; + awardDrop = (rand() % 1000 < chance.dropChance); + } + + + // no drop + if (slot == -1 || !awardDrop) { + // no room for an item, but you still get FM and taros reward->iItemCnt = 0; sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM)); } else { // item reward - item->sItem.iType = 9; - item->sItem.iID = 1; + item->sItem = getReward(&drop, &chance); item->iSlotNum = slot; item->eIL = 1; // Inventory Location. 1 means player inventory. @@ -194,8 +227,91 @@ void MobManager::giveReward(CNSocket *sock) { plr->Inven[slot] = item->sItem; sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); + } + // event crates + if (settings::EVENTMODE != 0) + giveEventReward(sock, plr); +} + +sItemBase MobManager::getReward(MobDrop* drop, MobDropChance* chance) { + sItemBase reward = {}; + reward.iType = 9; + reward.iOpt = 1; + + int total = 0; + for (int ratio : chance->cratesRatio) + total += ratio; + + // randomizing a crate + int randomNum = rand() % total; + int i = 0; + int sum = 0; + do { + reward.iID = drop->crateIDs[i]; + sum += chance->cratesRatio[i]; + i++; + } + while (sum<=randomNum); + return reward; +} + +void MobManager::giveEventReward(CNSocket* sock, Player* player) { + + // random drop chance + if (rand() % 100 > settings::EVENTCRATECHANCE) + return; + // no slot = no award + int slot = ItemManager::findFreeSlot(player); + if (slot == -1) + return; + + const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); + assert(resplen < CN_PACKET_BUFFER_SIZE - 8); + // we know it's only one trailing struct, so we can skip full validation + + uint8_t respbuf[resplen]; // not a variable length array, don't worry + sP_FE2CL_REP_REWARD_ITEM* reward = (sP_FE2CL_REP_REWARD_ITEM*)respbuf; + sItemReward* item = (sItemReward*)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); + + // don't forget to zero the buffer! + memset(respbuf, 0, resplen); + + // leave everything here as it is + reward->m_iCandy = player->money; + reward->m_iFusionMatter = player->fusionmatter; + reward->m_iBatteryN = player->batteryN; + reward->m_iBatteryW = player->batteryW; + reward->iFatigue = 100; // prevents warning message + reward->iFatigue_Level = 1; + reward->iItemCnt = 1; // remember to update resplen if you change this + + // which crate to drop + int crateId; + switch (settings::EVENTMODE) + { + // knishmas + case 1: crateId = 1187; break; + // halloween + case 2: crateId = 1181; break; + // spring + case 3: crateId = 1126; break; + // what + default: + std::cout << "[WARN] Unknown event Id " << settings::EVENTMODE << std::endl; + return; + } + + item->sItem.iType = 9; + item->sItem.iID = crateId; + item->sItem.iOpt = 1; + item->iSlotNum = slot; + item->eIL = 1; // Inventory Location. 1 means player inventory. + + // update player + player->Inven[slot] = item->sItem; + sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); } int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) { @@ -233,7 +349,7 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) { mob->target = nullptr; mob->appearanceData.iConditionBitFlag = 0; mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? - + // check for the edge case where hitting the mob did not aggro it if (sock != nullptr) { Player* plr = PlayerManager::getPlayer(sock); @@ -242,7 +358,7 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) { return; if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { - giveReward(sock); + giveReward(sock, mob); MissionManager::mobKilled(sock, mob->appearanceData.iNPCType); } else { plr = PlayerManager::getPlayerFromID(plr->iIDGroup); @@ -252,7 +368,7 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) { for (int i = 0; i < plr->groupCnt; i++) { CNSocket* sockTo = PlayerManager::getSockFromID(plr->groupIDs[i]); - giveReward(sockTo); + giveReward(sockTo, mob); MissionManager::mobKilled(sockTo, mob->appearanceData.iNPCType); } } diff --git a/src/MobManager.hpp b/src/MobManager.hpp index 2555175..a6e79eb 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -25,6 +25,7 @@ struct Mob : public BaseNPC { int spawnX; int spawnY; int spawnZ; + int level; // dead time_t killedTime = 0; @@ -42,6 +43,9 @@ struct Mob : public BaseNPC { time_t nextAttack = 0; int roamX, roamY, roamZ; + //drop + int dropType; + // temporary; until we're sure what's what nlohmann::json data; @@ -53,6 +57,8 @@ struct Mob : public BaseNPC { regenTime = data["m_iRegenTime"]; idleRange = (int)data["m_iIdleRange"] * 2; // TODO: tuning? + dropType = data["m_iDropType"]; + level = data["m_iNpcLevel"]; // XXX: temporarily force respawns for Fusions until we implement instancing //if (regenTime >= 300000000) @@ -84,9 +90,24 @@ struct Mob : public BaseNPC { } }; +struct MobDropChance { + int dropChance; + std::vector cratesRatio; +}; + +struct MobDrop { + std::vector crateIDs; + int dropChanceType; + int taros; + int fm; + int boosts; +}; + namespace MobManager { extern std::map Mobs; extern std::queue RemovalQueue; + extern std::map MobDropChances; + extern std::map MobDrops; extern bool simulateMobs; void init(); @@ -107,7 +128,10 @@ namespace MobManager { void npcAttackPc(Mob *mob, time_t currTime); int hitMob(CNSocket *sock, Mob *mob, int damage); void killMob(CNSocket *sock, Mob *mob); - void giveReward(CNSocket *sock); + void giveReward(CNSocket *sock, Mob *mob); + sItemBase getReward(MobDrop *drop, MobDropChance *chance); + void giveEventReward(CNSocket* sock, Player* player); + std::pair lerp(int, int, int, int, int); std::pair getDamage(int, int, bool, bool, int, int, int); diff --git a/src/TableData.cpp b/src/TableData.cpp index 2844a58..619a3c9 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -119,7 +119,7 @@ void TableData::init() { auto item = _item.value(); int typeOverride = getItemType(i); // used for special cases where iEquipLoc doesn't indicate item type ItemManager::ItemData[std::pair(item["m_iItemNumber"], typeOverride != -1 ? typeOverride : (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"], i > 9 ? 1 : (int)item["m_iRarity"], i > 9 ? 0 : (int)item["m_iPointRat"], i > 9 ? 0 : (int)item["m_iGroupRat"], i > 9 ? 0 : (int)item["m_iDefenseRat"] }; + = { 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"], i > 9 ? 0 : (int)item["m_iPointRat"], i > 9 ? 0 : (int)item["m_iGroupRat"], i > 9 ? 0 : (int)item["m_iDefenseRat"], i > 9 ? 0 : (int)item["m_iReqSex"] }; } } @@ -197,6 +197,8 @@ void TableData::init() { std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl; } + loadDrops(); + loadPaths(&nextId); // load paths loadGruntwork(&nextId); @@ -293,6 +295,115 @@ void TableData::loadPaths(int* nextId) { } } +/* + * Load drops data from JSON. + * This has to be called after reading xdt because it reffers to ItemData!!! + */ +void TableData::loadDrops() { + try { + std::ifstream inFile(settings::DROPSJSON); + nlohmann::json dropData; + + // read file into json + inFile >> dropData; + + // MobDropChances + nlohmann::json mobDropChances = dropData["MobDropChances"]; + for (nlohmann::json::iterator _dropChance = mobDropChances.begin(); _dropChance != mobDropChances.end(); _dropChance++) { + auto dropChance = _dropChance.value(); + MobDropChance toAdd = {}; + toAdd.dropChance = (int)dropChance["DropChance"]; + for (nlohmann::json::iterator _cratesRatio = dropChance["CratesRatio"].begin(); _cratesRatio != dropChance["CratesRatio"].end(); _cratesRatio++) { + toAdd.cratesRatio.push_back((int)_cratesRatio.value()); + } + MobManager::MobDropChances[(int)dropChance["Type"]] = toAdd; + } + + // MobDrops + nlohmann::json mobDrops = dropData["MobDrops"]; + for (nlohmann::json::iterator _drop = mobDrops.begin(); _drop != mobDrops.end(); _drop++) { + auto drop = _drop.value(); + MobDrop toAdd = {}; + for (nlohmann::json::iterator _crates = drop["CrateIDs"].begin(); _crates != drop["CrateIDs"].end(); _crates++) { + toAdd.crateIDs.push_back((int)_crates.value()); + } + + toAdd.dropChanceType = (int)drop["DropChance"]; + // Check if DropChance exists + if (MobManager::MobDropChances.find(toAdd.dropChanceType) == MobManager::MobDropChances.end()) + { + std::string errorMessage = " MobDropChance not found: " + std::to_string((toAdd.dropChanceType)); + throw (std::exception((errorMessage).c_str())); + } + // Check if number of crates is correct + if (!(MobManager::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) + { + std::string errorMessage = " DropType " + std::to_string((int)drop["DropType"]) + " contains invalid number of crates"; + throw (std::exception((errorMessage).c_str())); + } + + toAdd.taros = (int)drop["Taros"]; + toAdd.fm = (int)drop["FM"]; + toAdd.boosts = (int)drop["Boosts"]; + MobManager::MobDrops[(int)drop["DropType"]] = toAdd; + } + std::cout << "[INFO] Loaded " << MobManager::MobDrops.size() << " Mob Drop Types"<< std::endl; + // Rarity Ratios + nlohmann::json rarities = dropData["RarityRatios"]; + for (nlohmann::json::iterator _rarity = rarities.begin(); _rarity != rarities.end(); _rarity++) { + auto rarity = _rarity.value(); + std::vector toAdd; + for (nlohmann::json::iterator _ratio = rarity["Ratio"].begin(); _ratio != rarity["Ratio"].end(); _ratio++){ + toAdd.push_back((int)_ratio.value()); + } + ItemManager::RarityRatios[(int)rarity["Type"]] = toAdd; + } + // Crates + nlohmann::json crates = dropData["Crates"]; + for (nlohmann::json::iterator _crate = crates.begin(); _crate != crates.end(); _crate++) { + auto crate = _crate.value(); + Crate toAdd; + toAdd.rarityRatioId = (int)crate["RarityRatio"]; + for (nlohmann::json::iterator _itemSet = crate["ItemSets"].begin(); _itemSet != crate["ItemSets"].end(); _itemSet++) { + toAdd.itemSets.push_back((int)_itemSet.value()); + } + ItemManager::Crates[(int)crate["Id"]] = toAdd; + } + // Crate Items + nlohmann::json items = dropData["Items"]; + int itemCount = 0; + for (nlohmann::json::iterator _item = items.begin(); _item != items.end(); _item++) { + auto item = _item.value(); + std::pair itemSetkey = std::make_pair((int)item["ItemSet"], (int)item["Rarity"]); + std::pair itemDataKey = std::make_pair((int)item["Id"], (int)item["Type"]); + if (ItemManager::ItemData.find(itemDataKey) == ItemManager::ItemData.end()) + { + char buff[255]; + sprintf(buff, "Unknown item with Id %d and Type %d", (int)item["Id"], (int)item["Type"]); + throw (std::exception(buff)); + } + std::map, Item>::iterator toAdd = ItemManager::ItemData.find(itemDataKey); + // if item collection doesn't exist, start a new one + if (ItemManager::CrateItems.find(itemSetkey) == ItemManager::CrateItems.end()) { + std::vector, Item>::iterator> vector; + vector.push_back(toAdd); + ItemManager::CrateItems[itemSetkey] = vector; + } + // else add a new element to existing collection + else + ItemManager::CrateItems[itemSetkey].push_back(toAdd); + itemCount++; + } + + std::cout << "[INFO] Loaded " << ItemManager::Crates.size() << " Crates containing " + <