From 78b17aea723c393d8ed7ff8c549e7fb9d1276642 Mon Sep 17 00:00:00 2001 From: FinnHornhoover Date: Sun, 28 Mar 2021 13:57:43 -0700 Subject: [PATCH] added better drop handling, parsing, rng --- Makefile | 2 + src/BuiltinCommands.cpp | 5 +- src/Combat.cpp | 29 ++-- src/Items.cpp | 307 +++++++++++++++++++++++++--------------- src/Items.hpp | 63 +++++++-- src/MobAI.cpp | 17 +-- src/NPCManager.cpp | 3 +- src/Rand.cpp | 44 ++++++ src/Rand.hpp | 20 +++ src/TableData.cpp | 228 ++++++++++++++++++----------- src/Vendors.cpp | 3 +- src/main.cpp | 5 +- 12 files changed, 486 insertions(+), 240 deletions(-) create mode 100644 src/Rand.cpp create mode 100644 src/Rand.hpp diff --git a/Makefile b/Makefile index a6b28bf..fa2f70a 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ CXXSRC=\ src/Racing.cpp\ src/Vendors.cpp\ src/Trading.cpp\ + src/Rand.cpp\ # headers (for timestamp purposes) CXXHDR=\ @@ -117,6 +118,7 @@ CXXHDR=\ src/Racing.hpp\ src/Vendors.hpp\ src/Trading.hpp\ + src/Rand.hpp\ COBJ=$(CSRC:.c=.o) CXXOBJ=$(CXXSRC:.cpp=.o) diff --git a/src/BuiltinCommands.cpp b/src/BuiltinCommands.cpp index d8954ac..5f24423 100644 --- a/src/BuiltinCommands.cpp +++ b/src/BuiltinCommands.cpp @@ -4,6 +4,7 @@ #include "Items.hpp" #include "Missions.hpp" #include "Nanos.hpp" +#include "Rand.hpp" // helper function, not a packet handler void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { @@ -271,8 +272,8 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) { case eCN_GM_TeleportMapType__Unstick: targetPlr = PlayerManager::getPlayer(targetSock); - PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + rand() % unstickRange, - targetPlr->y - unstickRange/2 + rand() % unstickRange, targetPlr->z + 80); + PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange), + targetPlr->y - unstickRange/2 + Rand::rand(unstickRange), targetPlr->z + 80); break; } } diff --git a/src/Combat.cpp b/src/Combat.cpp index 5803f73..18236f4 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -8,6 +8,7 @@ #include "Transport.hpp" #include "Racing.hpp" #include "Abilities.hpp" +#include "Rand.hpp" #include @@ -26,7 +27,7 @@ static std::pair getDamage(int attackPower, int defensePower, bool shou // base calculation int damage = attackPower * attackPower / (attackPower + defensePower); damage = std::max(10 + attackPower / 10, damage - (defensePower - attackPower / 6) * difficulty / 100); - damage = damage * (rand() % 40 + 80) / 100; + damage = damage * (Rand::rand(40) + 80) / 100; // Adaptium/Blastons/Cosmix if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) { @@ -34,7 +35,7 @@ static std::pair getDamage(int attackPower, int defensePower, bool shou defenderStyle += 3; if (defenderStyle - attackerStyle == 2) defenderStyle -= 3; - if (attackerStyle < defenderStyle) + if (attackerStyle < defenderStyle) damage = damage * 5 / 4; else damage = damage * 4 / 5; @@ -47,7 +48,7 @@ static std::pair getDamage(int attackPower, int defensePower, bool shou ret.first = damage; ret.second = 1; - if (shouldCrit && rand() % 20 == 0) { + if (shouldCrit && Rand::rand(20) == 0) { ret.first *= 2; // critical hit ret.second = 2; } @@ -117,7 +118,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { int difficulty = (int)mob->data["m_iNpcLevel"]; damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); - + if (plr->batteryW >= 6 + difficulty) plr->batteryW -= 6 + difficulty; else @@ -224,12 +225,12 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { if (sock != nullptr) { Player* plr = PlayerManager::getPlayer(sock); - int rolledBoosts = rand(); - int rolledPotions = rand(); - int rolledCrate = rand(); - int rolledCrateType = rand(); - int rolledEvent = rand(); - int rolledQItem = rand(); + int rolledBoosts = Rand::rand(); + int rolledPotions = Rand::rand(); + int rolledCrate = Rand::rand(); + int rolledCrateType = Rand::rand(); + int rolledEvent = Rand::rand(); + int rolledQItem = Rand::rand(); if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { Items::giveMobDrop(sock, mob, rolledBoosts, rolledPotions, rolledCrate, rolledCrateType, rolledEvent); @@ -522,7 +523,7 @@ static int8_t addBullet(Player* plr, bool isGrenade) { // temp solution Jade fix plz toAdd.weaponBoost = plr->batteryW > 0; if (toAdd.weaponBoost) { - int boostCost = rand() % 11 + 20; + int boostCost = Rand::rand(11) + 20; plr->batteryW = boostCost > plr->batteryW ? 0 : plr->batteryW - boostCost; } @@ -634,7 +635,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { * initialize response struct * rocket style hit doesn't work properly, so we're always sending this one */ - + size_t resplen = sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT) + pkt->iTargetCnt * sizeof(sAttackResult); uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; @@ -655,7 +656,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { // not sure how to best handle this std::cout << "[WARN] projectileHit: NPC ID not found" << std::endl; return; - } + } BaseNPC* npc = NPCManager::NPCs[pktdata[i]]; if (npc->type != EntityType::MOB) { @@ -684,7 +685,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { resp->Bullet.iID = bullet->bulletType; sock->sendPacket((void*)respbuf, P_FE2CL_PC_GRENADE_STYLE_HIT, resplen); PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_GRENADE_STYLE_HIT, resplen); - + Bullets[plr->iID].erase(resp->iBulletID); } diff --git a/src/Items.cpp b/src/Items.cpp index 2fad0ea..137d4a5 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -7,6 +7,7 @@ #include "Abilities.hpp" #include "Missions.hpp" #include "Eggs.hpp" +#include "Rand.hpp" #include // for memset() #include @@ -15,14 +16,18 @@ using namespace Items; std::map, Items::Item> Items::ItemData; std::map Items::CrocPotTable; -std::map> Items::RarityRatios; +std::map> Items::RarityWeights; std::map Items::Crates; -// pair Itemset, Rarity -> vector of pointers (map iterators) to records in ItemData -std::map, std::vector, Items::Item>::iterator>> Items::CrateItems; +std::map Items::DroppableItems; std::map>> Items::CodeItems; -std::map Items::MobDropChances; +std::map Items::CrateDropChances; +std::map> Items::CrateDropTypes; +std::map Items::MiscDropChances; +std::map Items::MiscDropTypes; std::map Items::MobDrops; +std::map Items::ItemSetTypes; +std::map Items::ItemSetChances; #ifdef ACADEMY std::map Items::NanoCapsules; // crate id -> nano id @@ -58,7 +63,7 @@ static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { item->iSlotNum = slot; item->eIL = 1; - + // update player serverside plr->Inven[slot] = item->sItem; @@ -81,14 +86,29 @@ static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { } #endif -static int getRarity(Crate& crate, int itemSetId) { +static int choice(const std::vector& weights, int rolled) { + int total = std::accumulate(weights.begin(), weights.end(), 0); + int randValue = rolled % total; + int currentIndex = -1; + + do { + currentIndex++; + randValue -= weights[currentIndex]; + } while (randValue >= 0); + + return currentIndex; +} + +static int getRarity(int crateId, int itemSetTypeId) { + Crate& crate = Items::Crates[crateId]; + // find rarity ratio - if (RarityRatios.find(crate.rarityRatioId) == RarityRatios.end()) { - std::cout << "[WARN] Rarity Ratio " << crate.rarityRatioId << " not found!" << std::endl; + if (Items::RarityWeights.find(crate.rarityWeightId) == Items::RarityWeights.end()) { + std::cout << "[WARN] Rarity Weight " << crate.rarityWeightId << " not found!" << std::endl; return -1; } - std::vector rarityRatio = RarityRatios[crate.rarityRatioId]; + std::vector& rarityWeights = Items::RarityWeights[crate.rarityWeightId]; /* * First we have to check if specified item set contains items with all specified rarities, @@ -97,74 +117,137 @@ static int getRarity(Crate& crate, int itemSetId) { */ // 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; + std::set rarityIndices; + for (int droppableItemId : Items::ItemSetTypes[itemSetTypeId].droppableItemIds) { + if (Items::DroppableItems.find(droppableItemId) == Items::DroppableItems.end()) + continue; + + rarityIndices.insert(Items::DroppableItems[droppableItemId].rarity - 1); + + // shortcut + if (rarityIndices.size() == rarityWeights.size()) + break; } - int total = 0; - for (int value : rarityRatio) - total += value; - - if (total == 0) { - std::cout << "Item Set " << itemSetId << " has no items assigned?!" << std::endl; + if (rarityIndices.empty()) { + std::cout << "[WARN] Item Set " << crate.itemSetTypeId << " has no valid items assigned?!" << std::endl; return -1; } - // now return a random rarity number - int randomNum = rand() % total; - int rarity = 0; - int sum = 0; - do { - sum += rarityRatio[rarity]; - rarity++; - } while (sum <= randomNum); + std::vector relevantWeights; + for (int index : rarityIndices) + relevantWeights.push_back(rarityWeights[index]); - return rarity; + // now return a random rarity number (starting from 1) + return Rand::randWeighted(relevantWeights) + 1; } -static int getCrateItem(sItemBase& result, int itemSetId, int rarity, int playerGender) { - auto key = std::make_pair(itemSetId, rarity); +static int getCrateItem(sItemBase* result, int itemSetTypeId, int itemSetChanceId, int rarity, int playerGender) { + // (int, vector) + auto& [ignoreGender, droppableItemIds] = Items::ItemSetTypes[itemSetTypeId]; - if (CrateItems.find(key) == CrateItems.end()) { - std::cout << "[WARN] Item Set ID " << itemSetId << " Rarity " << rarity << " does not exist" << std::endl; - return -1; - } + // collect valid items that match the rarity and (if not ignored) gender + std::vector> validItems; + for (int i = 0; i < droppableItemIds.size(); i++) { + int droppableItemId = droppableItemIds[i]; - // 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) + if (Items::DroppableItems.find(droppableItemId) == Items::DroppableItems.end()) { + std::cout << "[WARN] Droppable item " << droppableItemId << " was not found, skipping..." << std::endl; continue; - items.push_back(crateitem); + } + + DroppableItem* droppableItem = &Items::DroppableItems[droppableItemId]; + + if (droppableItem->rarity != rarity) + continue; + + auto key = std::make_pair(droppableItem->itemId, droppableItem->type); + + if (Items::ItemData.find(key) == Items::ItemData.end()) { + std::cout << "[WARN] Item-Type pair (" << key.first << ", " << key.second << ") specified by droppable item " + << droppableItemId << " was not found, skipping..." << std::endl; + continue; + } + + // if gender is incorrect, exclude item + int itemGender = Items::ItemData[key].gender; + if (!ignoreGender && itemGender != 0 && itemGender != playerGender) + continue; + + validItems.push_back(std::make_pair(i, droppableItem)); } - if (items.size() == 0) { - std::cout << "[WARN] Set ID " << itemSetId << " Rarity " << rarity << " contains no valid items" << std::endl; + if (validItems.empty()) { + std::cout << "[WARN] Set ID " << itemSetTypeId << " Chance ID " << itemSetChanceId + << " Rarity " << rarity << " contains no valid items" << std::endl; return -1; } - auto item = items[rand() % items.size()]; + // (int, map) + auto& [defaultWeight, specialWeights] = Items::ItemSetChances[itemSetChanceId]; - result.iID = item->first.first; - result.iType = item->first.second; - result.iOpt = 1; + // initialize all weights as the default weight for all item slots + std::vector itemWeights(validItems.size(), defaultWeight); + + if (!specialWeights.empty()) { + for (int i = 0; i < validItems.size(); i++) { + // (int, DroppableItem*) + auto& [dropIndex, droppableItem] = validItems[i]; + + if (specialWeights.find(dropIndex) == specialWeights.end()) + continue; + + int weight = specialWeights[dropIndex]; + // allow 0 weights for convenience + if (weight > -1) + itemWeights[i] = weight; + } + } + + int chosenIndex = Rand::randWeighted(itemWeights); + DroppableItem* item = validItems[chosenIndex].second; + + result->iID = item->itemId; + result->iType = item->type; + result->iOpt = 1; return 0; } -static int getItemSetId(Crate& crate, int crateId) { - int itemSetsCount = crate.itemSets.size(); - if (itemSetsCount == 0) { - std::cout << "[WARN] Crate " << crateId << " has no item sets assigned?!" << std::endl; +static int getValidCrateId(int crateId) { + // find the crate + if (Items::Crates.find(crateId) == Items::Crates.end()) { + std::cout << "[WARN] Crate " << crateId << " not found!" << std::endl; return -1; } - // if crate points to multiple itemSets, choose a random one - int itemSetIndex = rand() % itemSetsCount; - return crate.itemSets[itemSetIndex]; + return crateId; +} + +static int getValidItemSetTypeId(int crateId) { + Crate& crate = Items::Crates[crateId]; + + // find item set type + if (Items::ItemSetTypes.find(crate.itemSetTypeId) == Items::ItemSetTypes.end()) { + std::cout << "[WARN] Crate " << crateId << " was assigned item set " + << crate.itemSetTypeId << " which is invalid!" << std::endl; + return -1; + } + + return crate.itemSetTypeId; +} + +static int getValidItemSetChanceId(int crateId) { + Crate& crate = Items::Crates[crateId]; + + // find item set chances + if (Items::ItemSetChances.find(crate.itemSetChanceId) == Items::ItemSetChances.end()) { + std::cout << "[WARN] Crate " << crateId << " was assigned item set chance object " + << crate.itemSetChanceId << " which is invalid!" << std::endl; + return -1; + } + + return crate.itemSetChanceId; } static void itemMoveHandler(CNSocket* sock, CNPacketData* data) { @@ -486,36 +569,34 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) { item->iSlotNum = pkt->iSlotNum; item->eIL = 1; - int itemSetId = -1, rarity = -1, ret = -1; - bool failing = false; + int validItemSetTypeId = -1, validItemSetChanceId = -1, rarity = -1, ret = -1; - // find the crate - if (Crates.find(chest->iID) == Crates.end()) { - std::cout << "[WARN] Crate " << chest->iID << " not found!" << std::endl; - failing = true; - } - Crate& crate = Crates[chest->iID]; + int validCrateId = getValidCrateId(chest->iID); + bool failing = (validCrateId == -1); if (!failing) - itemSetId = getItemSetId(crate, chest->iID); - if (itemSetId == -1) - failing = true; + validItemSetTypeId = getValidItemSetTypeId(validCrateId); + failing = (validItemSetTypeId == -1); if (!failing) - rarity = getRarity(crate, itemSetId); - if (rarity == -1) - failing = true; + validItemSetChanceId = getValidItemSetChanceId(validCrateId); + failing = (validItemSetChanceId == -1); if (!failing) - ret = getCrateItem(item->sItem, itemSetId, rarity, plr->PCStyle.iGender); - if (ret == -1) - failing = true; + rarity = getRarity(validCrateId, validItemSetTypeId); + failing = (rarity == -1); + + if (!failing) + ret = getCrateItem(&item->sItem, validItemSetTypeId, validItemSetChanceId, rarity, plr->PCStyle.iGender); + failing = (ret == -1); // if we failed to open a crate, at least give the player a gumball (suggested by Jade) if (failing) { item->sItem.iType = 7; - item->sItem.iID = 119 + (rand() % 3); + item->sItem.iID = 119 + Rand::rand(3); item->sItem.iOpt = 1; + + std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl; } // update player plr->Inven[pkt->iSlotNum] = item->sItem; @@ -617,29 +698,17 @@ void Items::updateEquips(CNSocket* sock, Player* plr) { } } -static void getMobDrop(sItemBase *reward, MobDrop* drop, MobDropChance* chance, int rolled) { +static void getMobDrop(sItemBase* reward, const std::vector& weights, const std::vector& crateIds, int rolled) { + int chosenIndex = choice(weights, rolled); + reward->iType = 9; reward->iOpt = 1; - - int total = 0; - for (int ratio : chance->cratesRatio) - total += ratio; - - // randomizing a crate - int randomNum = rolled % total; - int i = 0; - int sum = 0; - do { - reward->iID = drop->crateIDs[i]; - sum += chance->cratesRatio[i]; - i++; - } - while (sum<=randomNum); + reward->iID = crateIds[chosenIndex]; } static void giveEventDrop(CNSocket* sock, Player* player, int rolled) { // random drop chance - if (rand() % 100 > settings::EVENTCRATECHANCE) + if (Rand::rand(100) > settings::EVENTCRATECHANCE) return; // no slot = no reward @@ -715,18 +784,47 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, int rolledBoosts, int rolledPo // find correct mob drop MobDrop& drop = MobDrops[mob->dropType]; - plr->money += drop.taros; + // use the keys to fetch data from other maps + // sanity check + if (Items::CrateDropChances.find(drop.crateDropChanceId) == Items::CrateDropChances.end()) { + std::cout << "[WARN] Crate Drop Chance Object " << drop.crateDropChanceId << " was not found" << std::endl; + return; + } + CrateDropChance crateDropChance = Items::CrateDropChances[drop.crateDropChanceId]; + + // sanity check + if (Items::CrateDropTypes.find(drop.crateDropTypeId) == Items::CrateDropTypes.end()) { + std::cout << "[WARN] Crate Drop Type Object " << drop.crateDropTypeId << " was not found" << std::endl; + return; + } + std::vector crateDropType = Items::CrateDropTypes[drop.crateDropTypeId]; + + // sanity check + if (Items::MiscDropChances.find(drop.miscDropChanceId) == Items::MiscDropChances.end()) { + std::cout << "[WARN] Misc Drop Chance Object " << drop.miscDropChanceId << " was not found" << std::endl; + return; + } + MiscDropChance miscDropChance = Items::MiscDropChances[drop.miscDropChanceId]; + + // sanity check + if (Items::MiscDropTypes.find(drop.miscDropTypeId) == Items::MiscDropTypes.end()) { + std::cout << "[WARN] Misc Drop Type Object " << drop.miscDropTypeId << " was not found" << std::endl; + return; + } + MiscDropType miscDropType = Items::MiscDropTypes[drop.miscDropTypeId]; + + plr->money += miscDropType.taroAmount; // money nano boost if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; - plr->money += drop.taros * (5 + boost) / 25; + plr->money += miscDropType.taroAmount * (5 + boost) / 25; } // formula for scaling FM with player/mob level difference // TODO: adjust this better int levelDifference = plr->level - mob->level; - int fm = drop.fm; + int fm = miscDropType.fmAmount; if (levelDifference > 0) fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0; // scavenger nano boost @@ -739,13 +837,11 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, int rolledBoosts, int rolledPo Missions::updateFusionMatter(sock, fm); - // give boosts 1 in 3 times - if (drop.boosts > 0) { - if (rolledPotions % 3 == 0) - plr->batteryN += drop.boosts; - if (rolledBoosts % 3 == 0) - plr->batteryW += drop.boosts; - } + if (rolledPotions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance) + plr->batteryN += miscDropType.potionAmount; + if (rolledBoosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance) + plr->batteryW += miscDropType.boostAmount; + // caps if (plr->batteryW > 9999) plr->batteryW = 9999; @@ -763,25 +859,14 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, int rolledBoosts, int rolledPo int slot = findFreeSlot(plr); - bool awardDrop = false; - MobDropChance *chance = nullptr; - // sanity check - if (MobDropChances.find(drop.dropChanceType) == MobDropChances.end()) { - std::cout << "[WARN] Unknown Drop Chance Type: " << drop.dropChanceType << std::endl; - return; // this also prevents holiday crate drops, but oh well - } else { - chance = &MobDropChances[drop.dropChanceType]; - awardDrop = (rolledCrate % 1000 < chance->dropChance); - } - // no drop - if (slot == -1 || !awardDrop) { + if (slot == -1 || rolledCrate % crateDropChance.dropChanceTotal >= crateDropChance.dropChance) { // 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 - getMobDrop(&item->sItem, &drop, chance, rolledCrateType); + getMobDrop(&item->sItem, crateDropChance.crateWeights, crateDropType, rolledCrateType); item->iSlotNum = slot; item->eIL = 1; // Inventory Location. 1 means player inventory. diff --git a/src/Items.hpp b/src/Items.hpp index d2a6a4b..60eb4f8 100644 --- a/src/Items.hpp +++ b/src/Items.hpp @@ -10,21 +10,51 @@ struct CrocPotEntry { }; struct Crate { - int rarityRatioId; - std::vector itemSets; + int itemSetChanceId; + int itemSetTypeId; + int rarityWeightId; }; -struct MobDropChance { - int dropChance; - std::vector cratesRatio; +struct CrateDropChance { + int dropChance, dropChanceTotal; + std::vector crateWeights; +}; + +struct MiscDropChance { + int potionDropChance, potionDropChanceTotal; + int boostDropChance, boostDropChanceTotal; + int taroDropChance, taroDropChanceTotal; + int fmDropChance, fmDropChanceTotal; +}; + +struct MiscDropType { + int potionAmount; + int boostAmount; + int taroAmount; + int fmAmount; }; struct MobDrop { - std::vector crateIDs; - int dropChanceType; - int taros; - int fm; - int boosts; + int crateDropChanceId; + int crateDropTypeId; + int miscDropChanceId; + int miscDropTypeId; +}; + +struct ItemSetType { + bool ignoreGender; + std::vector droppableItemIds; +}; + +struct ItemSetChance { + int defaultItemWeight; + std::map specialItemWeights; +}; + +struct DroppableItem { + int itemId; + int rarity; + int type; }; namespace Items { @@ -44,16 +74,19 @@ namespace Items { // hopefully this is fine since it's never modified after load extern std::map, Item> ItemData; // -> data extern std::map CrocPotTable; // level gap -> entry - extern std::map> RarityRatios; + extern std::map> RarityWeights; 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; + extern std::map DroppableItems; extern std::map>> CodeItems; // code -> vector of // mob drops - extern std::map MobDropChances; + extern std::map CrateDropChances; + extern std::map> CrateDropTypes; + extern std::map MiscDropChances; + extern std::map MiscDropTypes; extern std::map MobDrops; + extern std::map ItemSetTypes; + extern std::map ItemSetChances; void init(); diff --git a/src/MobAI.cpp b/src/MobAI.cpp index a90b758..c6d546b 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -5,6 +5,7 @@ #include "Nanos.hpp" #include "Combat.hpp" #include "Abilities.hpp" +#include "Rand.hpp" #include #include @@ -334,7 +335,7 @@ static void useAbilities(Mob *mob, time_t currTime) { return; } - int random = rand() % 2000 * 1000; + int random = Rand::rand(2000) * 1000; int prob1 = (int)mob->data["m_iActiveSkill1Prob"]; // active skill probability int prob2 = (int)mob->data["m_iCorruptionTypeProb"]; // corruption probability int prob3 = (int)mob->data["m_iMegaTypeProb"]; // eruption probability @@ -364,7 +365,7 @@ static void useAbilities(Mob *mob, time_t currTime) { if (mob->skillStyle == -1) mob->skillStyle = 2; if (mob->skillStyle == -2) - mob->skillStyle = rand() % 3; + mob->skillStyle = Rand::rand(3); pkt.iStyle = mob->skillStyle; NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_SKILL_CORRUPTION_READY, sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_READY)); mob->nextAttack = currTime + 1800; @@ -546,7 +547,7 @@ static void combatStep(Mob *mob, time_t currTime) { pkt1.iID = mob->appearanceData.iNPC_ID; pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - + it = mob->unbuffTimes.erase(it); } else { it++; @@ -563,7 +564,7 @@ static void combatStep(Mob *mob, time_t currTime) { int mobRange = (int)mob->data["m_iAtkRange"] + (int)mob->data["m_iRadius"]; if (currTime >= mob->nextAttack) { - if (mob->skillStyle != -1 || distance <= mobRange || rand() % 20 == 0) // while not in attack range, 1 / 20 chance. + if (mob->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. useAbilities(mob, currTime); if (mob->target == nullptr) return; @@ -610,7 +611,7 @@ static void combatStep(Mob *mob, time_t currTime) { NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } - /* attack logic + /* attack logic * 2/5 represents 400 ms which is the time interval mobs use per movement logic step * if the mob is one move interval away, we should just start attacking anyways. */ @@ -638,7 +639,7 @@ void MobAI::incNextMovement(Mob *mob, time_t currTime) { currTime = getTime(); int delay = (int)mob->data["m_iDelayTime"] * 1000; - mob->nextMovement = currTime + delay/2 + rand() % (delay/2); + mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2); } static void roamingStep(Mob *mob, time_t currTime) { @@ -681,8 +682,8 @@ static void roamingStep(Mob *mob, time_t currTime) { int minDistance = mob->idleRange / 2; // pick a random destination - farX = xStart + rand() % mob->idleRange; - farY = yStart + rand() % mob->idleRange; + farX = xStart + Rand::rand(mob->idleRange); + farY = yStart + Rand::rand(mob->idleRange); distance = std::abs(std::max(farX - mob->x, farY - mob->y)); if (distance == 0) diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 6fc248b..e63aa2e 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -10,6 +10,7 @@ #include "Racing.hpp" #include "Vendors.hpp" #include "Abilities.hpp" +#include "Rand.hpp" #include #include @@ -104,7 +105,7 @@ static void npcBarkHandler(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_BARKER, resp); resp.iNPC_ID = req->iNPC_ID; - resp.iMissionStringID = barks[rand() % barks.size()]; + resp.iMissionStringID = barks[Rand::rand(barks.size())]; sock->sendPacket(resp, P_FE2CL_REP_BARKER); } diff --git a/src/Rand.cpp b/src/Rand.cpp new file mode 100644 index 0000000..f92dc75 --- /dev/null +++ b/src/Rand.cpp @@ -0,0 +1,44 @@ +#include "Rand.hpp" + +#include + +std::unique_ptr Rand::generator; + +int32_t Rand::rand(int32_t startInclusive, int32_t endExclusive) { + std::uniform_int_distribution dist(startInclusive, endExclusive - 1); + return dist(*Rand::generator); +} + +int32_t Rand::rand(int32_t endExclusive) { + return Rand::rand(0, endExclusive); +} + +int32_t Rand::rand() { + return Rand::rand(0, INT32_MAX); +} + +int32_t Rand::randWeighted(const std::vector& weights) { + std::discrete_distribution dist(weights.begin(), weights.end()); + return dist(*Rand::generator); +} + +float Rand::randFloat(float startInclusive, float endExclusive) { + std::uniform_real_distribution dist(startInclusive, endExclusive); + return dist(*Rand::generator); +} + +float Rand::randFloat(float endExclusive) { + std::uniform_real_distribution dist(0.0f, endExclusive); + return dist(*Rand::generator); +} + +float Rand::randFloat() { + std::uniform_real_distribution dist(0.0f, 1.0f); + return dist(*Rand::generator); +} + +void Rand::init() { + // modern equivalent of srand(time(0)) + uint64_t seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + Rand::generator = std::make_unique(std::mt19937(seed)); +} diff --git a/src/Rand.hpp b/src/Rand.hpp new file mode 100644 index 0000000..4240910 --- /dev/null +++ b/src/Rand.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace Rand { + extern std::unique_ptr generator; + + void init(); + + int32_t rand(int32_t startInclusive, int32_t endExclusive); + int32_t rand(int32_t endExclusive); + int32_t rand(); + + int32_t randWeighted(const std::vector& weights); + + float randFloat(float startInclusive, float endExclusive); + float randFloat(float endExclusive); + float randFloat(); +}; diff --git a/src/TableData.cpp b/src/TableData.cpp index 5677585..ceab826 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -202,117 +202,156 @@ static void loadDrops() { // 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()); - } - Items::MobDropChances[(int)dropChance["Type"]] = toAdd; + // CrateDropChances + nlohmann::json crateDropChances = dropData["CrateDropChances"]; + for (nlohmann::json::iterator _crateDropChance = crateDropChances.begin(); _crateDropChance != crateDropChances.end(); _crateDropChance++) { + auto crateDropChance = _crateDropChance.value(); + CrateDropChance toAdd = {}; + + toAdd.dropChance = (int)crateDropChance["DropChance"]; + toAdd.dropChanceTotal = (int)crateDropChance["DropChanceTotal"]; + + nlohmann::json crateWeights = crateDropChance["CrateTypeDropWeights"]; + for (nlohmann::json::iterator _crateWeight = crateWeights.begin(); _crateWeight != crateWeights.end(); _crateWeight++) + toAdd.crateWeights.push_back((int)_crateWeight.value()); + + Items::CrateDropChances[(int)crateDropChance["CrateDropChanceID"]] = toAdd; + } + + // CrateDropTypes + nlohmann::json crateDropTypes = dropData["CrateDropTypes"]; + for (nlohmann::json::iterator _crateDropType = crateDropTypes.begin(); _crateDropType != crateDropTypes.end(); _crateDropType++) { + auto crateDropType = _crateDropType.value(); + std::vector toAdd; + + nlohmann::json crateIds = crateDropType["CrateIDs"]; + for (nlohmann::json::iterator _crateId = crateIds.begin(); _crateId != crateIds.end(); _crateId++) + toAdd.push_back((int)_crateId.value()); + + Items::CrateDropTypes[(int)crateDropType["CrateDropTypeID"]] = toAdd; + } + + // MiscDropChances + nlohmann::json miscDropChances = dropData["MiscDropChances"]; + for (nlohmann::json::iterator _miscDropChance = miscDropChances.begin(); _miscDropChance != miscDropChances.end(); _miscDropChance++) { + auto miscDropChance = _miscDropChance.value(); + + Items::MiscDropChances[(int)miscDropChance["MiscDropChanceID"]] = { + (int)miscDropChance["PotionDropChance"], + (int)miscDropChance["PotionDropChanceTotal"], + (int)miscDropChance["BoostDropChance"], + (int)miscDropChance["BoostDropChanceTotal"], + (int)miscDropChance["TaroDropChance"], + (int)miscDropChance["TaroDropChanceTotal"], + (int)miscDropChance["FMDropChance"], + (int)miscDropChance["FMDropChanceTotal"] + }; + } + + // MiscDropTypes + nlohmann::json miscDropTypes = dropData["MiscDropTypes"]; + for (nlohmann::json::iterator _miscDropType = miscDropTypes.begin(); _miscDropType != miscDropTypes.end(); _miscDropType++) { + auto miscDropType = _miscDropType.value(); + + Items::MiscDropTypes[(int)miscDropType["MiscDropTypeID"]] = { + (int)miscDropType["PotionAmount"], + (int)miscDropType["BoostAmount"], + (int)miscDropType["TaroAmount"], + (int)miscDropType["FMAmount"] + }; } // 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()); - } + for (nlohmann::json::iterator _mobDrop = mobDrops.begin(); _mobDrop != mobDrops.end(); _mobDrop++) { + auto mobDrop = _mobDrop.value(); - toAdd.dropChanceType = (int)drop["DropChance"]; - // Check if DropChance exists - if (Items::MobDropChances.find(toAdd.dropChanceType) == Items::MobDropChances.end()) { - throw TableException(" MobDropChance not found: " + std::to_string((toAdd.dropChanceType))); - } - // Check if number of crates is correct - if (!(Items::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) { - throw TableException(" DropType " + std::to_string((int)drop["DropType"]) + " contains invalid number of crates"); - } - - toAdd.taros = (int)drop["Taros"]; - toAdd.fm = (int)drop["FM"]; - toAdd.boosts = (int)drop["Boosts"]; - Items::MobDrops[(int)drop["DropType"]] = toAdd; + Items::MobDrops[(int)mobDrop["MobDropID"]] = { + (int)mobDrop["CrateDropChanceID"], + (int)mobDrop["CrateDropTypeID"], + (int)mobDrop["MiscDropChanceID"], + (int)mobDrop["MiscDropTypeID"], + }; } - std::cout << "[INFO] Loaded " << Items::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(); + // RarityWeights + nlohmann::json rarityWeights = dropData["RarityWeights"]; + for (nlohmann::json::iterator _rarityWeightsObject = rarityWeights.begin(); _rarityWeightsObject != rarityWeights.end(); _rarityWeightsObject++) { + auto rarityWeightsObject = _rarityWeightsObject.value(); std::vector toAdd; - for (nlohmann::json::iterator _ratio = rarity["Ratio"].begin(); _ratio != rarity["Ratio"].end(); _ratio++){ - toAdd.push_back((int)_ratio.value()); - } - Items::RarityRatios[(int)rarity["Type"]] = toAdd; + + nlohmann::json weights = rarityWeightsObject["Weights"]; + for (nlohmann::json::iterator _weight = weights.begin(); _weight != weights.end(); _weight++) + toAdd.push_back((int)_weight.value()); + + Items::RarityWeights[(int)rarityWeightsObject["RarityWeightID"]] = toAdd; + } + + // ItemSetTypes + nlohmann::json itemSetTypes = dropData["ItemSetTypes"]; + for (nlohmann::json::iterator _itemSetType = itemSetTypes.begin(); _itemSetType != itemSetTypes.end(); _itemSetType++) { + auto itemSetType = _itemSetType.value(); + ItemSetType toAdd = {}; + + toAdd.ignoreGender = (bool)itemSetType["IgnoreGender"]; + + nlohmann::json droppableItemIds = itemSetType["DroppableItemIDs"]; + for (nlohmann::json::iterator _droppableItemId = droppableItemIds.begin(); _droppableItemId != droppableItemIds.end(); _droppableItemId++) + toAdd.droppableItemIds.push_back((int)_droppableItemId.value()); + + Items::ItemSetTypes[(int)itemSetType["ItemSetTypeID"]] = toAdd; + } + + // ItemSetChances + nlohmann::json itemSetChances = dropData["ItemSetChances"]; + for (nlohmann::json::iterator _itemSetChanceObject = itemSetChances.begin(); _itemSetChanceObject != itemSetChances.end(); _itemSetChanceObject++) { + auto itemSetChanceObject = _itemSetChanceObject.value(); + ItemSetChance toAdd = {}; + + toAdd.defaultItemWeight = (int)itemSetChanceObject["DefaultItemWeight"]; + + nlohmann::json specialItemWeightIndices = itemSetChanceObject["SpecialItemWeightIndices"]; + nlohmann::json specialItemWeightVector = itemSetChanceObject["SpecialItemWeights"]; + for (nlohmann::json::iterator _specialItemWeightIndex = specialItemWeightIndices.begin(), _specialItemWeight = specialItemWeightVector.begin(); + _specialItemWeightIndex != specialItemWeightIndices.end() && _specialItemWeight != specialItemWeightVector.end(); + _specialItemWeightIndex++, _specialItemWeight++) + toAdd.specialItemWeights[(int)_specialItemWeightIndex.value()] = (int)_specialItemWeight.value(); + + Items::ItemSetChances[(int)itemSetChanceObject["ItemSetChanceID"]] = 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()); - } - Items::Crates[(int)crate["Id"]] = toAdd; + + Items::Crates[(int)crate["CrateID"]] = { + (int)crate["ItemSetChanceID"], + (int)crate["ItemSetTypeID"], + (int)crate["RarityWeightID"] + }; } - // 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"]); + // DroppableItems + nlohmann::json droppableItems = dropData["DroppableItems"]; + for (nlohmann::json::iterator _droppableItem = droppableItems.begin(); _droppableItem != droppableItems.end(); _droppableItem++) { + auto droppableItem = _droppableItem.value(); - if (Items::ItemData.find(itemDataKey) == Items::ItemData.end()) { - char buff[255]; - sprintf(buff, "Unknown item with Id %d and Type %d", (int)item["Id"], (int)item["Type"]); - throw TableException(std::string(buff)); - } - - std::map, Items::Item>::iterator toAdd = Items::ItemData.find(itemDataKey); - - // if item collection doesn't exist, start a new one - if (Items::CrateItems.find(itemSetkey) == Items::CrateItems.end()) { - std::vector, Items::Item>::iterator> vector; - vector.push_back(toAdd); - Items::CrateItems[itemSetkey] = vector; - } else // else add a new element to existing collection - Items::CrateItems[itemSetkey].push_back(toAdd); - - itemCount++; + Items::DroppableItems[(int)droppableItem["DroppableItemID"]] = { + (int)droppableItem["ItemID"], + (int)droppableItem["Rarity"], + (int)droppableItem["Type"] + }; } #ifdef ACADEMY + // NanoCapsules nlohmann::json capsules = dropData["NanoCapsules"]; - for (nlohmann::json::iterator _capsule = capsules.begin(); _capsule != capsules.end(); _capsule++) { auto capsule = _capsule.value(); - Items::NanoCapsules[(int)capsule["Crate"]] = (int)capsule["Nano"]; + Items::NanoCapsules[(int)capsule["CrateID"]] = (int)capsule["Nano"]; } #endif - nlohmann::json codes = dropData["CodeItems"]; - for (nlohmann::json::iterator _code = codes.begin(); _code != codes.end(); _code++) { - auto code = _code.value(); - std::string codeStr = code["Code"]; - std::pair item = std::make_pair((int)code["Id"], (int)code["Type"]); - - if (Items::CodeItems.find(codeStr) == Items::CodeItems.end()) - Items::CodeItems[codeStr] = std::vector>(); - - Items::CodeItems[codeStr].push_back(item); - } - - std::cout << "[INFO] Loaded " << Items::Crates.size() << " Crates containing " - << itemCount << " items" << std::endl; // Racing rewards nlohmann::json racing = dropData["Racing"]; @@ -359,6 +398,23 @@ static void loadDrops() { std::cout << "[INFO] Loaded rewards for " << Racing::EPRewards.size() << " IZ races" << std::endl; + // CodeItems + nlohmann::json codes = dropData["CodeItems"]; + for (nlohmann::json::iterator _code = codes.begin(); _code != codes.end(); _code++) { + auto code = _code.value(); + std::string codeStr = code["Code"]; + std::vector> itemVector; + + nlohmann::json items = code["Items"]; + for (nlohmann::json::iterator _item = items.begin(); _item != items.end(); _item++) + itemVector.push_back(std::make_pair((int)code["ItemID"], (int)code["Type"])); + + Items::CodeItems[codeStr] = itemVector; + } + + std::cout << "[INFO] Loaded " << Items::Crates.size() << " Crates containing " + << Items::DroppableItems.size() << " unique items" << std::endl; + } catch (const std::exception& err) { std::cerr << "[FATAL] Malformed drops.json file! Reason:" << err.what() << std::endl; diff --git a/src/Vendors.cpp b/src/Vendors.cpp index 7d6e93f..c849f71 100644 --- a/src/Vendors.cpp +++ b/src/Vendors.cpp @@ -1,4 +1,5 @@ #include "Vendors.hpp" +#include "Rand.hpp" using namespace Vendors; @@ -328,7 +329,7 @@ static void vendorCombineItems(CNSocket* sock, CNPacketData* data) { break; } - float rolled = (rand() * 1.0f / RAND_MAX) * 100.0f; // success chance out of 100 + float rolled = Rand::randFloat(100.0f); // success chance out of 100 //std::cout << rolled << " vs " << successChance << std::endl; plr->money -= cost; diff --git a/src/main.cpp b/src/main.cpp index cd09be1..6a7eca6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ #include "Vendors.hpp" #include "Chat.hpp" #include "Eggs.hpp" +#include "Rand.hpp" #include "settings.hpp" @@ -56,7 +57,7 @@ void terminate(int arg) { if (shardServer != nullptr && shardThread != nullptr) shardServer->kill(); - + Database::close(); exit(0); } @@ -93,7 +94,7 @@ int main() { #else initsignals(); #endif - srand(getTime()); + Rand::init(); settings::init(); std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl; std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;