[refactor] Move mob drop logic from Combat to ItemManager

This commit is contained in:
dongresource 2021-03-16 19:41:20 +01:00
parent c5776b9322
commit 2017b38e23
5 changed files with 214 additions and 207 deletions

View File

@ -11,8 +11,6 @@
#include <assert.h> #include <assert.h>
std::map<int32_t, MobDropChance> Combat::MobDropChances;
std::map<int32_t, MobDrop> Combat::MobDrops;
/// Player Id -> Bullet Id -> Bullet /// Player Id -> Bullet Id -> Bullet
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets; std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
@ -161,185 +159,6 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
} }
} }
void Combat::giveReward(CNSocket *sock, Mob* mob, int rolledBoosts, int rolledPotions,
int rolledCrate, int rolledCrateType, int rolledEvent) {
Player *plr = PlayerManager::getPlayer(sock);
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);
// 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;
// money nano boost
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) {
int boost = 0;
if (NanoManager::getNanoBoost(plr)) // for gumballs
boost = 1;
plr->money += drop.taros * (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;
if (levelDifference > 0)
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
// scavenger nano boost
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) {
int boost = 0;
if (NanoManager::getNanoBoost(plr)) // for gumballs
boost = 1;
fm += fm * (5 + boost) / 25;
}
MissionManager::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;
}
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
if (plr->batteryN > 9999)
plr->batteryN = 9999;
// simple rewards
reward->m_iCandy = plr->money;
reward->m_iFusionMatter = plr->fusionmatter;
reward->m_iBatteryN = plr->batteryN;
reward->m_iBatteryW = plr->batteryW;
reward->iFatigue = 100; // prevents warning message
reward->iFatigue_Level = 1;
reward->iItemCnt = 1; // remember to update resplen if you change this
int slot = ItemManager::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) {
// 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
getReward(&item->sItem, &drop, chance, rolledCrateType);
item->iSlotNum = slot;
item->eIL = 1; // Inventory Location. 1 means player inventory.
// update player
plr->Inven[slot] = item->sItem;
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
}
// event crates
if (settings::EVENTMODE != 0)
giveEventReward(sock, plr, rolledEvent);
}
void Combat::getReward(sItemBase *reward, MobDrop* drop, MobDropChance* chance, int 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);
}
void Combat::giveEventReward(CNSocket* sock, Player* player, int rolled) {
// random drop chance
if (rand() % 100 > settings::EVENTCRATECHANCE)
return;
// no slot = no reward
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);
uint8_t respbuf[resplen];
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 Combat::hitMob(CNSocket *sock, Mob *mob, int damage) { int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
// cannot kill mobs multiple times; cannot harm retreating mobs // cannot kill mobs multiple times; cannot harm retreating mobs
if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) {
@ -396,7 +215,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
int rolledQItem = rand(); int rolledQItem = rand();
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
giveReward(sock, mob, rolledBoosts, rolledPotions, rolledCrate, rolledCrateType, rolledEvent); ItemManager::giveMobDrop(sock, mob, rolledBoosts, rolledPotions, rolledCrate, rolledCrateType, rolledEvent);
MissionManager::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem); MissionManager::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem);
} else { } else {
Player* otherPlayer = PlayerManager::getPlayerFromID(plr->iIDGroup); Player* otherPlayer = PlayerManager::getPlayerFromID(plr->iIDGroup);
@ -416,7 +235,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
if (dist > 5000) if (dist > 5000)
continue; continue;
giveReward(sockTo, mob, rolledBoosts, rolledPotions, rolledCrate, rolledCrateType, rolledEvent); ItemManager::giveMobDrop(sockTo, mob, rolledBoosts, rolledPotions, rolledCrate, rolledCrateType, rolledEvent);
MissionManager::mobKilled(sockTo, mob->appearanceData.iNPCType, rolledQItem); MissionManager::mobKilled(sockTo, mob->appearanceData.iNPCType, rolledQItem);
} }
} }

View File

@ -12,19 +12,6 @@
#include <unordered_map> #include <unordered_map>
#include <queue> #include <queue>
struct MobDropChance {
int dropChance;
std::vector<int> cratesRatio;
};
struct MobDrop {
std::vector<int> crateIDs;
int dropChanceType;
int taros;
int fm;
int boosts;
};
struct Bullet { struct Bullet {
int pointDamage; int pointDamage;
int groupDamage; int groupDamage;
@ -33,8 +20,6 @@ struct Bullet {
}; };
namespace Combat { namespace Combat {
extern std::map<int32_t, MobDropChance> MobDropChances;
extern std::map<int32_t, MobDrop> MobDrops;
extern std::map<int32_t, std::map<int8_t, Bullet>> Bullets; extern std::map<int32_t, std::map<int8_t, Bullet>> Bullets;
void init(); void init();
@ -49,9 +34,6 @@ namespace Combat {
void npcAttackPc(Mob *mob, time_t currTime); void npcAttackPc(Mob *mob, time_t currTime);
int hitMob(CNSocket *sock, Mob *mob, int damage); int hitMob(CNSocket *sock, Mob *mob, int damage);
void killMob(CNSocket *sock, Mob *mob); void killMob(CNSocket *sock, Mob *mob);
void giveReward(CNSocket *sock, Mob *mob, int rolledBoosts, int rolledPotions, int rolledCrate, int rolledCrateType, int rolledEvent);
void getReward(sItemBase *reward, MobDrop *drop, MobDropChance *chance, int rolled);
void giveEventReward(CNSocket* sock, Player* player, int rolled);
std::pair<int,int> lerp(int, int, int, int, int); std::pair<int,int> lerp(int, int, int, int, int);
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int); std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);

View File

@ -6,6 +6,7 @@
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "MissionManager.hpp"
#include <string.h> // for memset() #include <string.h> // for memset()
#include <assert.h> #include <assert.h>
@ -18,6 +19,9 @@ std::map<int32_t, Crate> ItemManager::Crates;
std::map<std::pair<int32_t, int32_t>, std::vector<std::map<std::pair<int32_t, int32_t>, ItemManager::Item>::iterator>> ItemManager::CrateItems; std::map<std::pair<int32_t, int32_t>, std::vector<std::map<std::pair<int32_t, int32_t>, ItemManager::Item>::iterator>> ItemManager::CrateItems;
std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> ItemManager::CodeItems; std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> ItemManager::CodeItems;
std::map<int32_t, MobDropChance> ItemManager::MobDropChances;
std::map<int32_t, MobDrop> ItemManager::MobDrops;
#ifdef ACADEMY #ifdef ACADEMY
std::map<int32_t, int32_t> ItemManager::NanoCapsules; // crate id -> nano id std::map<int32_t, int32_t> ItemManager::NanoCapsules; // crate id -> nano id
@ -709,6 +713,185 @@ void ItemManager::updateEquips(CNSocket* sock, Player* plr) {
} }
} }
void ItemManager::giveMobDrop(CNSocket *sock, Mob* mob, int rolledBoosts, int rolledPotions,
int rolledCrate, int rolledCrateType, int rolledEvent) {
Player *plr = PlayerManager::getPlayer(sock);
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);
// 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;
// money nano boost
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) {
int boost = 0;
if (NanoManager::getNanoBoost(plr)) // for gumballs
boost = 1;
plr->money += drop.taros * (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;
if (levelDifference > 0)
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
// scavenger nano boost
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) {
int boost = 0;
if (NanoManager::getNanoBoost(plr)) // for gumballs
boost = 1;
fm += fm * (5 + boost) / 25;
}
MissionManager::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;
}
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
if (plr->batteryN > 9999)
plr->batteryN = 9999;
// simple rewards
reward->m_iCandy = plr->money;
reward->m_iFusionMatter = plr->fusionmatter;
reward->m_iBatteryN = plr->batteryN;
reward->m_iBatteryW = plr->batteryW;
reward->iFatigue = 100; // prevents warning message
reward->iFatigue_Level = 1;
reward->iItemCnt = 1; // remember to update resplen if you change this
int slot = ItemManager::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) {
// 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);
item->iSlotNum = slot;
item->eIL = 1; // Inventory Location. 1 means player inventory.
// update player
plr->Inven[slot] = item->sItem;
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
}
// event crates
if (settings::EVENTMODE != 0)
giveEventDrop(sock, plr, rolledEvent);
}
void ItemManager::getMobDrop(sItemBase *reward, MobDrop* drop, MobDropChance* chance, int 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);
}
void ItemManager::giveEventDrop(CNSocket* sock, Player* player, int rolled) {
// random drop chance
if (rand() % 100 > settings::EVENTCRATECHANCE)
return;
// no slot = no reward
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);
uint8_t respbuf[resplen];
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);
}
void ItemManager::init() { void ItemManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler);

View File

@ -2,16 +2,31 @@
#include "CNShardServer.hpp" #include "CNShardServer.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "MobAI.hpp"
struct CrocPotEntry { struct CrocPotEntry {
int multStats, multLooks; int multStats, multLooks;
float base, rd0, rd1, rd2, rd3; float base, rd0, rd1, rd2, rd3;
}; };
struct Crate { struct Crate {
int rarityRatioId; int rarityRatioId;
std::vector<int> itemSets; std::vector<int> itemSets;
}; };
struct MobDropChance {
int dropChance;
std::vector<int> cratesRatio;
};
struct MobDrop {
std::vector<int> crateIDs;
int dropChanceType;
int taros;
int fm;
int boosts;
};
namespace ItemManager { namespace ItemManager {
enum class SlotType { enum class SlotType {
EQUIP = 0, EQUIP = 0,
@ -36,6 +51,10 @@ namespace ItemManager {
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator>> CrateItems; std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator>> CrateItems;
extern std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> CodeItems; // code -> vector of <id, type> extern std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> CodeItems; // code -> vector of <id, type>
// mob drops
extern std::map<int32_t, MobDropChance> MobDropChances;
extern std::map<int32_t, MobDrop> MobDrops;
void init(); void init();
// crate opening logic with all helper functions // crate opening logic with all helper functions
@ -43,6 +62,11 @@ namespace ItemManager {
int getRarity(Crate& crate, int itemSetId); int getRarity(Crate& crate, int itemSetId);
int getCrateItem(sItemBase& reward, int itemSetId, int rarity, int playerGender); int getCrateItem(sItemBase& reward, int itemSetId, int rarity, int playerGender);
// mob drops
void giveMobDrop(CNSocket *sock, Mob *mob, int rolledBoosts, int rolledPotions, int rolledCrate, int rolledCrateType, int rolledEvent);
void getMobDrop(sItemBase *reward, MobDrop *drop, MobDropChance *chance, int rolled);
void giveEventDrop(CNSocket* sock, Player* player, int rolled);
int findFreeSlot(Player *plr); int findFreeSlot(Player *plr);
Item* getItemData(int32_t id, int32_t type); Item* getItemData(int32_t id, int32_t type);
void checkItemExpire(CNSocket* sock, Player* player); void checkItemExpire(CNSocket* sock, Player* player);

View File

@ -4,7 +4,6 @@
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "MissionManager.hpp" #include "MissionManager.hpp"
#include "Combat.hpp"
#include "ChunkManager.hpp" #include "ChunkManager.hpp"
#include "NanoManager.hpp" #include "NanoManager.hpp"
#include "RacingManager.hpp" #include "RacingManager.hpp"
@ -518,7 +517,7 @@ void TableData::loadDrops() {
for (nlohmann::json::iterator _cratesRatio = dropChance["CratesRatio"].begin(); _cratesRatio != dropChance["CratesRatio"].end(); _cratesRatio++) { for (nlohmann::json::iterator _cratesRatio = dropChance["CratesRatio"].begin(); _cratesRatio != dropChance["CratesRatio"].end(); _cratesRatio++) {
toAdd.cratesRatio.push_back((int)_cratesRatio.value()); toAdd.cratesRatio.push_back((int)_cratesRatio.value());
} }
Combat::MobDropChances[(int)dropChance["Type"]] = toAdd; ItemManager::MobDropChances[(int)dropChance["Type"]] = toAdd;
} }
// MobDrops // MobDrops
@ -532,21 +531,21 @@ void TableData::loadDrops() {
toAdd.dropChanceType = (int)drop["DropChance"]; toAdd.dropChanceType = (int)drop["DropChance"];
// Check if DropChance exists // Check if DropChance exists
if (Combat::MobDropChances.find(toAdd.dropChanceType) == Combat::MobDropChances.end()) { if (ItemManager::MobDropChances.find(toAdd.dropChanceType) == ItemManager::MobDropChances.end()) {
throw TableException(" MobDropChance not found: " + std::to_string((toAdd.dropChanceType))); throw TableException(" MobDropChance not found: " + std::to_string((toAdd.dropChanceType)));
} }
// Check if number of crates is correct // Check if number of crates is correct
if (!(Combat::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) { if (!(ItemManager::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) {
throw TableException(" DropType " + std::to_string((int)drop["DropType"]) + " contains invalid number of crates"); throw TableException(" DropType " + std::to_string((int)drop["DropType"]) + " contains invalid number of crates");
} }
toAdd.taros = (int)drop["Taros"]; toAdd.taros = (int)drop["Taros"];
toAdd.fm = (int)drop["FM"]; toAdd.fm = (int)drop["FM"];
toAdd.boosts = (int)drop["Boosts"]; toAdd.boosts = (int)drop["Boosts"];
Combat::MobDrops[(int)drop["DropType"]] = toAdd; ItemManager::MobDrops[(int)drop["DropType"]] = toAdd;
} }
std::cout << "[INFO] Loaded " << Combat::MobDrops.size() << " Mob Drop Types"<< std::endl; std::cout << "[INFO] Loaded " << ItemManager::MobDrops.size() << " Mob Drop Types"<< std::endl;
// Rarity Ratios // Rarity Ratios
nlohmann::json rarities = dropData["RarityRatios"]; nlohmann::json rarities = dropData["RarityRatios"];