added better drop handling, parsing, rng

This commit is contained in:
FinnHornhoover 2021-03-28 13:57:43 -07:00 committed by Gent Semaj
parent aa028392f0
commit 78b17aea72
12 changed files with 486 additions and 240 deletions

View File

@ -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)

View File

@ -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;
}
}

View File

@ -8,6 +8,7 @@
#include "Transport.hpp"
#include "Racing.hpp"
#include "Abilities.hpp"
#include "Rand.hpp"
#include <assert.h>
@ -26,7 +27,7 @@ static std::pair<int,int> 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<int,int> 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<int,int> 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);
}

View File

@ -7,6 +7,7 @@
#include "Abilities.hpp"
#include "Missions.hpp"
#include "Eggs.hpp"
#include "Rand.hpp"
#include <string.h> // for memset()
#include <assert.h>
@ -15,14 +16,18 @@ using namespace Items;
std::map<std::pair<int32_t, int32_t>, Items::Item> Items::ItemData;
std::map<int32_t, CrocPotEntry> Items::CrocPotTable;
std::map<int32_t, std::vector<int>> Items::RarityRatios;
std::map<int32_t, std::vector<int32_t>> Items::RarityWeights;
std::map<int32_t, Crate> Items::Crates;
// pair Itemset, Rarity -> vector of pointers (map iterators) to records in ItemData
std::map<std::pair<int32_t, int32_t>, std::vector<std::map<std::pair<int32_t, int32_t>, Items::Item>::iterator>> Items::CrateItems;
std::map<int32_t, DroppableItem> Items::DroppableItems;
std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> Items::CodeItems;
std::map<int32_t, MobDropChance> Items::MobDropChances;
std::map<int32_t, CrateDropChance> Items::CrateDropChances;
std::map<int32_t, std::vector<int32_t>> Items::CrateDropTypes;
std::map<int32_t, MiscDropChance> Items::MiscDropChances;
std::map<int32_t, MiscDropType> Items::MiscDropTypes;
std::map<int32_t, MobDrop> Items::MobDrops;
std::map<int32_t, ItemSetType> Items::ItemSetTypes;
std::map<int32_t, ItemSetChance> Items::ItemSetChances;
#ifdef ACADEMY
std::map<int32_t, int32_t> 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<int>& 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<int> rarityRatio = RarityRatios[crate.rarityRatioId];
std::vector<int>& 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<int> 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<int> 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<int>)
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<std::pair<int, DroppableItem*>> validItems;
for (int i = 0; i < droppableItemIds.size(); i++) {
int droppableItemId = droppableItemIds[i];
// only take into account items that have correct gender
std::vector<std::map<std::pair<int32_t, int32_t>, 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<int, int>)
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<int> 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<int>& weights, const std::vector<int>& 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<int> 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.

View File

@ -10,21 +10,51 @@ struct CrocPotEntry {
};
struct Crate {
int rarityRatioId;
std::vector<int> itemSets;
int itemSetChanceId;
int itemSetTypeId;
int rarityWeightId;
};
struct MobDropChance {
int dropChance;
std::vector<int> cratesRatio;
struct CrateDropChance {
int dropChance, dropChanceTotal;
std::vector<int> 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<int> crateIDs;
int dropChanceType;
int taros;
int fm;
int boosts;
int crateDropChanceId;
int crateDropTypeId;
int miscDropChanceId;
int miscDropTypeId;
};
struct ItemSetType {
bool ignoreGender;
std::vector<int> droppableItemIds;
};
struct ItemSetChance {
int defaultItemWeight;
std::map<int, int> 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<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
extern std::map<int32_t, CrocPotEntry> CrocPotTable; // level gap -> entry
extern std::map<int32_t, std::vector<int>> RarityRatios;
extern std::map<int32_t, std::vector<int32_t>> RarityWeights;
extern std::map<int32_t, Crate> Crates;
// pair <Itemset, Rarity> -> vector of pointers (map iterators) to records in ItemData (it looks a lot scarier than it is)
extern std::map<std::pair<int32_t, int32_t>,
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator>> CrateItems;
extern std::map<int32_t, DroppableItem> DroppableItems;
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, CrateDropChance> CrateDropChances;
extern std::map<int32_t, std::vector<int32_t>> CrateDropTypes;
extern std::map<int32_t, MiscDropChance> MiscDropChances;
extern std::map<int32_t, MiscDropType> MiscDropTypes;
extern std::map<int32_t, MobDrop> MobDrops;
extern std::map<int32_t, ItemSetType> ItemSetTypes;
extern std::map<int32_t, ItemSetChance> ItemSetChances;
void init();

View File

@ -5,6 +5,7 @@
#include "Nanos.hpp"
#include "Combat.hpp"
#include "Abilities.hpp"
#include "Rand.hpp"
#include <cmath>
#include <limits.h>
@ -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)

View File

@ -10,6 +10,7 @@
#include "Racing.hpp"
#include "Vendors.hpp"
#include "Abilities.hpp"
#include "Rand.hpp"
#include <cmath>
#include <algorithm>
@ -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);
}

44
src/Rand.cpp Normal file
View File

@ -0,0 +1,44 @@
#include "Rand.hpp"
#include <chrono>
std::unique_ptr<std::mt19937> Rand::generator;
int32_t Rand::rand(int32_t startInclusive, int32_t endExclusive) {
std::uniform_int_distribution<int32_t> 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<int32_t>& weights) {
std::discrete_distribution<int32_t> dist(weights.begin(), weights.end());
return dist(*Rand::generator);
}
float Rand::randFloat(float startInclusive, float endExclusive) {
std::uniform_real_distribution<float> dist(startInclusive, endExclusive);
return dist(*Rand::generator);
}
float Rand::randFloat(float endExclusive) {
std::uniform_real_distribution<float> dist(0.0f, endExclusive);
return dist(*Rand::generator);
}
float Rand::randFloat() {
std::uniform_real_distribution<float> 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>(std::mt19937(seed));
}

20
src/Rand.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <random>
#include <memory>
namespace Rand {
extern std::unique_ptr<std::mt19937> 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<int32_t>& weights);
float randFloat(float startInclusive, float endExclusive);
float randFloat(float endExclusive);
float randFloat();
};

View File

@ -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<int> 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<int> 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<int32_t, int32_t> itemSetkey = std::make_pair((int)item["ItemSet"], (int)item["Rarity"]);
std::pair<int32_t, int32_t> 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<std::pair<int32_t, int32_t>, 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<std::map<std::pair<int32_t, int32_t>, 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<int32_t, int32_t> item = std::make_pair((int)code["Id"], (int)code["Type"]);
if (Items::CodeItems.find(codeStr) == Items::CodeItems.end())
Items::CodeItems[codeStr] = std::vector<std::pair<int32_t, int32_t>>();
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<std::pair<int32_t, int32_t>> 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;

View File

@ -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;

View File

@ -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;