mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-17 03:20:06 +00:00
f4f5f2e0bd
Storing certain things in appearance data and others in their own fields was gross. Now everything is stored on the same level and functions have been added to generate appearance data when it's needed by the client.
1370 lines
56 KiB
C++
1370 lines
56 KiB
C++
#include "TableData.hpp"
|
|
#include "NPCManager.hpp"
|
|
#include "Transport.hpp"
|
|
#include "Items.hpp"
|
|
#include "settings.hpp"
|
|
#include "Missions.hpp"
|
|
#include "Chunking.hpp"
|
|
#include "Nanos.hpp"
|
|
#include "Racing.hpp"
|
|
#include "Vendors.hpp"
|
|
#include "Abilities.hpp"
|
|
#include "Eggs.hpp"
|
|
|
|
#include "JSON.hpp"
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <cmath>
|
|
|
|
using namespace TableData;
|
|
|
|
std::map<int32_t, std::vector<Vec3>> TableData::RunningSkywayRoutes;
|
|
std::map<int32_t, int> TableData::RunningNPCRotations;
|
|
std::map<int32_t, int> TableData::RunningNPCMapNumbers;
|
|
std::unordered_map<int32_t, std::pair<BaseNPC*, std::vector<BaseNPC*>>> TableData::RunningNPCPaths;
|
|
std::vector<NPCPath> TableData::FinishedNPCPaths;
|
|
std::map<int32_t, BaseNPC*> TableData::RunningMobs;
|
|
std::map<int32_t, BaseNPC*> TableData::RunningGroups;
|
|
std::map<int32_t, BaseNPC*> TableData::RunningEggs;
|
|
|
|
class TableException : public std::exception {
|
|
public:
|
|
std::string msg;
|
|
|
|
TableException(std::string m) : std::exception() { msg = m; }
|
|
|
|
const char *what() const throw() { return msg.c_str(); }
|
|
};
|
|
|
|
/*
|
|
* We must refuse to run if an invalid NPC type is found in the JSONs, especially
|
|
* the gruntwork file. If we were to just skip loading invalid NPCs, they would get
|
|
* silently dropped from the gruntwork file, which would be confusing in situations
|
|
* where a gruntwork file for the wrong game build was accidentally loaded.
|
|
*/
|
|
static void ensureValidNPCType(int type, std::string filename) {
|
|
// last known NPC type
|
|
int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"];
|
|
|
|
if (type > npcLimit) {
|
|
std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a full and properly-paced path by interpolating between keyframes.
|
|
*/
|
|
static void constructPathSkyway(json& pathData) {
|
|
// Interpolate
|
|
json pathPoints = pathData["aPoints"];
|
|
std::queue<Vec3> points;
|
|
json::iterator _point = pathPoints.begin();
|
|
auto point = _point.value();
|
|
Vec3 last = { point["iX"] , point["iY"] , point["iZ"] }; // start pos
|
|
// use some for loop trickery; start position should not be a point
|
|
for (_point++; _point != pathPoints.end(); _point++) {
|
|
point = _point.value();
|
|
Vec3 coords = { point["iX"] , point["iY"] , point["iZ"] };
|
|
Transport::lerp(&points, last, coords, pathData["iMonkeySpeed"]);
|
|
points.push(coords); // add keyframe to the queue
|
|
last = coords; // update start pos
|
|
}
|
|
Transport::SkywayPaths[pathData["iRouteID"]] = points;
|
|
}
|
|
|
|
/*
|
|
* Load all relevant data from the XDT into memory
|
|
* This should be called first, before any of the other load functions
|
|
*/
|
|
static void loadXDT(json& xdtData) {
|
|
// data we'll need for summoned mobs
|
|
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
|
|
|
|
try {
|
|
// load warps
|
|
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
|
|
|
|
for (json::iterator _warp = warpData.begin(); _warp != warpData.end(); _warp++) {
|
|
auto warp = _warp.value();
|
|
WarpLocation warpLoc = { warp["m_iToX"], warp["m_iToY"], warp["m_iToZ"], warp["m_iToMapNum"], warp["m_iIsInstance"], warp["m_iLimit_TaskID"], warp["m_iNpcNumber"] };
|
|
int warpID = warp["m_iWarpNumber"];
|
|
NPCManager::Warps[warpID] = warpLoc;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << NPCManager::Warps.size() << " Warps" << std::endl;
|
|
|
|
// load transport routes and locations
|
|
json transRouteData = xdtData["m_pTransportationTable"]["m_pTransportationData"];
|
|
json transLocData = xdtData["m_pTransportationTable"]["m_pTransportationWarpLocation"];
|
|
|
|
for (json::iterator _tLoc = transLocData.begin(); _tLoc != transLocData.end(); _tLoc++) {
|
|
auto tLoc = _tLoc.value();
|
|
TransportLocation transLoc = { tLoc["m_iNPCID"], tLoc["m_iXpos"], tLoc["m_iYpos"], tLoc["m_iZpos"] };
|
|
Transport::Locations[tLoc["m_iLocationID"]] = transLoc;
|
|
}
|
|
std::cout << "[INFO] Loaded " << Transport::Locations.size() << " S.C.A.M.P.E.R. locations" << std::endl;
|
|
|
|
for (json::iterator _tRoute = transRouteData.begin(); _tRoute != transRouteData.end(); _tRoute++) {
|
|
auto tRoute = _tRoute.value();
|
|
TransportRoute transRoute = { tRoute["m_iMoveType"], tRoute["m_iStartLocation"], tRoute["m_iEndLocation"],
|
|
tRoute["m_iCost"] , tRoute["m_iSpeed"], tRoute["m_iRouteNum"] };
|
|
Transport::Routes[tRoute["m_iVehicleID"]] = transRoute;
|
|
}
|
|
std::cout << "[INFO] Loaded " << Transport::Routes.size() << " transportation routes" << std::endl;
|
|
|
|
// load mission-related data
|
|
json tasks = xdtData["m_pMissionTable"]["m_pMissionData"];
|
|
|
|
for (auto _task = tasks.begin(); _task != tasks.end(); _task++) {
|
|
auto task = _task.value();
|
|
|
|
// rewards
|
|
if (task["m_iSUReward"] != 0) {
|
|
auto _rew = xdtData["m_pMissionTable"]["m_pRewardData"][(int)task["m_iSUReward"]];
|
|
Reward* rew = new Reward(_rew["m_iMissionRewardID"], _rew["m_iMissionRewarItemType"],
|
|
_rew["m_iMissionRewardItemID"], _rew["m_iCash"], _rew["m_iFusionMatter"]);
|
|
|
|
Missions::Rewards[task["m_iHTaskID"]] = rew;
|
|
}
|
|
|
|
// everything else lol. see TaskData comment.
|
|
Missions::Tasks[task["m_iHTaskID"]] = new TaskData(task);
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded mission-related data" << std::endl;
|
|
|
|
/*
|
|
* load all equipment data. i'm sorry. it has to be done
|
|
* NOTE: please don't change the ordering. it determines the types, since type and equipLoc are used inconsistently
|
|
*/
|
|
const char* setNames[11] = { "m_pWeaponItemTable", "m_pShirtsItemTable", "m_pPantsItemTable", "m_pShoesItemTable",
|
|
"m_pHatItemTable", "m_pGlassItemTable", "m_pBackItemTable", "m_pGeneralItemTable", "",
|
|
"m_pChestItemTable", "m_pVehicleItemTable" };
|
|
json itemSet;
|
|
for (int i = 0; i < 11; i++) {
|
|
if (i == 8)
|
|
continue; // there is no type 8, of course
|
|
|
|
itemSet = xdtData[setNames[i]]["m_pItemData"];
|
|
for (json::iterator _item = itemSet.begin(); _item != itemSet.end(); _item++) {
|
|
auto item = _item.value();
|
|
int itemID = item["m_iItemNumber"];
|
|
INITSTRUCT(Items::Item, itemData);
|
|
itemData.tradeable = item["m_iTradeAble"] == 1;
|
|
itemData.sellable = item["m_iSellAble"] == 1;
|
|
itemData.buyPrice = item["m_iItemPrice"];
|
|
itemData.sellPrice = item["m_iItemSellPrice"];
|
|
itemData.stackSize = item["m_iStackNumber"];
|
|
if (i != 7 && i != 9) {
|
|
itemData.rarity = item["m_iRarity"];
|
|
itemData.level = item["m_iMinReqLev"];
|
|
itemData.pointDamage = item["m_iPointRat"];
|
|
itemData.groupDamage = item["m_iGroupRat"];
|
|
itemData.fireRate = item["m_iDelayTime"];
|
|
itemData.defense = item["m_iDefenseRat"];
|
|
itemData.gender = item["m_iReqSex"];
|
|
itemData.weaponType = item["m_iEquipType"];
|
|
}
|
|
else {
|
|
itemData.rarity = 1;
|
|
}
|
|
Items::ItemData[std::make_pair(itemID, i)] = itemData;
|
|
}
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Items::ItemData.size() << " items" << std::endl;
|
|
|
|
// load player limits from m_pAvatarTable.m_pAvatarGrowData
|
|
|
|
json growth = xdtData["m_pAvatarTable"]["m_pAvatarGrowData"];
|
|
|
|
for (int i = 0; i < 37; i++) {
|
|
Missions::AvatarGrowth[i] = growth[i];
|
|
}
|
|
|
|
// load vendor listings
|
|
json listings = xdtData["m_pVendorTable"]["m_pItemData"];
|
|
|
|
for (json::iterator _lst = listings.begin(); _lst != listings.end(); _lst++) {
|
|
auto lst = _lst.value();
|
|
VendorListing vListing = { lst["m_iSortNumber"], lst["m_iItemType"], lst["m_iitemID"] };
|
|
Vendors::VendorTables[lst["m_iNpcNumber"]].push_back(vListing);
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Vendors::VendorTables.size() << " vendor tables" << std::endl;
|
|
|
|
// load crocpot entries
|
|
json crocs = xdtData["m_pCombiningTable"]["m_pCombiningData"];
|
|
|
|
for (json::iterator croc = crocs.begin(); croc != crocs.end(); croc++) {
|
|
CrocPotEntry crocEntry = { croc.value()["m_iStatConstant"], croc.value()["m_iLookConstant"], croc.value()["m_fLevelGapStandard"],
|
|
croc.value()["m_fSameGrade"], croc.value()["m_fOneGrade"], croc.value()["m_fTwoGrade"], croc.value()["m_fThreeGrade"] };
|
|
Items::CrocPotTable[croc.value()["m_iLevelGap"]] = crocEntry;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Items::CrocPotTable.size() << " croc pot value sets" << std::endl;
|
|
|
|
// load nano info
|
|
json nanoInfo = xdtData["m_pNanoTable"]["m_pNanoData"];
|
|
for (json::iterator _nano = nanoInfo.begin(); _nano != nanoInfo.end(); _nano++) {
|
|
auto nano = _nano.value();
|
|
NanoData nanoData;
|
|
nanoData.style = nano["m_iStyle"];
|
|
Nanos::NanoTable[Nanos::NanoTable.size()] = nanoData;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Nanos::NanoTable.size() << " nanos" << std::endl;
|
|
|
|
json nanoTuneInfo = xdtData["m_pNanoTable"]["m_pNanoTuneData"];
|
|
for (json::iterator _nano = nanoTuneInfo.begin(); _nano != nanoTuneInfo.end(); _nano++) {
|
|
auto nano = _nano.value();
|
|
NanoTuning nanoData;
|
|
nanoData.reqItems = nano["m_iReqItemID"];
|
|
nanoData.reqItemCount = nano["m_iReqItemCount"];
|
|
Nanos::NanoTunings[nano["m_iSkillID"]] = nanoData;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Nanos::NanoTable.size() << " nano tunings" << std::endl;
|
|
|
|
// load nano powers
|
|
json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
|
|
|
|
for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) {
|
|
auto skills = _skills.value();
|
|
SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] };
|
|
for (int i = 0; i < 4; i++) {
|
|
skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i];
|
|
skillData.durationTime[i] = skills["m_iDurationTime"][i];
|
|
skillData.powerIntensity[i] = skills["m_iValueA"][i];
|
|
}
|
|
Abilities::SkillTable[skills["m_iSkillNumber"]] = skillData;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl;
|
|
|
|
// load EP data
|
|
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
|
|
|
|
for (json::iterator _instance = instances.begin(); _instance != instances.end(); _instance++) {
|
|
auto instance = _instance.value();
|
|
EPInfo epInfo = { instance["m_iZoneX"], instance["m_iZoneY"], instance["m_iIsEP"], (int)instance["m_ScoreMax"] };
|
|
Racing::EPData[instance["m_iInstanceNameID"]] = epInfo;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Racing::EPData.size() << " instances" << std::endl;
|
|
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed xdt.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load paths from JSON
|
|
*/
|
|
static void loadPaths(json& pathData, int32_t* nextId) {
|
|
try {
|
|
// skyway paths
|
|
json pathDataSkyway = pathData["skyway"];
|
|
for (json::iterator skywayPath = pathDataSkyway.begin(); skywayPath != pathDataSkyway.end(); skywayPath++) {
|
|
constructPathSkyway(*skywayPath);
|
|
}
|
|
std::cout << "[INFO] Loaded " << Transport::SkywayPaths.size() << " skyway paths" << std::endl;
|
|
|
|
// slider circuit
|
|
json pathDataSlider = pathData["slider"];
|
|
// lerp between keyframes
|
|
std::queue<Vec3> route;
|
|
// initial point
|
|
json::iterator _point = pathDataSlider.begin(); // iterator
|
|
auto point = _point.value();
|
|
Vec3 from = { point["iX"] , point["iY"] , point["iZ"] }; // point A coords
|
|
int stopTime = point["bStop"] ? SLIDER_STOP_TICKS : 0; // arbitrary stop length
|
|
// remaining points
|
|
for (_point++; _point != pathDataSlider.end(); _point++) { // loop through all point Bs
|
|
point = _point.value();
|
|
for (int i = 0; i < stopTime + 1; i++) { // repeat point if it's a stop
|
|
route.push(from); // add point A to the queue
|
|
}
|
|
Vec3 to = { point["iX"] , point["iY"] , point["iZ"] }; // point B coords
|
|
// we may need to change this later; right now, the speed is cut before and after stops (no accel)
|
|
float curve = 1;
|
|
if (stopTime > 0) { // point A is a stop
|
|
curve = 0.375f;//2.0f;
|
|
} else if (point["bStop"]) { // point B is a stop
|
|
curve = 0.375f;//0.35f;
|
|
}
|
|
Transport::lerp(&route, from, to, SLIDER_SPEED * curve, 1); // lerp from A to B (arbitrary speed)
|
|
from = to; // update point A
|
|
stopTime = point["bStop"] ? SLIDER_STOP_TICKS : 0; // set stop ticks for next point A
|
|
}
|
|
// Uniform distance calculation
|
|
int passedDistance = 0;
|
|
// initial point
|
|
int pos = 0;
|
|
Vec3 lastPoint = route.front();
|
|
route.pop();
|
|
route.push(lastPoint);
|
|
for (pos = 1; pos < route.size(); pos++) {
|
|
Vec3 point = route.front();
|
|
passedDistance += hypot(point.x - lastPoint.x, point.y - lastPoint.y);
|
|
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
|
|
passedDistance -= SLIDER_GAP_SIZE; // step down
|
|
// spawn a slider
|
|
Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
|
NPCManager::NPCs[slider->id] = slider;
|
|
NPCManager::updateNPCPosition(slider->id, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0);
|
|
Transport::NPCQueues[slider->id] = route;
|
|
}
|
|
// rotate
|
|
route.pop();
|
|
route.push(point);
|
|
lastPoint = point;
|
|
}
|
|
|
|
// preset npc paths
|
|
json pathDataNPC = pathData["npc"];
|
|
for (json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
|
|
json pathVal = npcPath.value();
|
|
|
|
std::vector<int32_t> targetIDs;
|
|
std::vector<int32_t> targetTypes;
|
|
std::vector<Vec3> pathPoints;
|
|
int speed = pathVal.find("iBaseSpeed") == pathVal.end() ? NPC_DEFAULT_SPEED : (int)pathVal["iBaseSpeed"];
|
|
int taskID = pathVal.find("iTaskID") == pathVal.end() ? -1 : (int)pathVal["iTaskID"];
|
|
bool relative = pathVal.find("bRelative") == pathVal.end() ? false : (bool)pathVal["bRelative"];
|
|
bool loop = pathVal.find("bLoop") == pathVal.end() ? true : (bool)pathVal["bLoop"]; // loop by default
|
|
|
|
// target IDs
|
|
for (json::iterator _tID = pathVal["aNPCIDs"].begin(); _tID != pathVal["aNPCIDs"].end(); _tID++)
|
|
targetIDs.push_back(_tID.value());
|
|
// target types
|
|
for (json::iterator _tType = pathVal["aNPCTypes"].begin(); _tType != pathVal["aNPCTypes"].end(); _tType++)
|
|
targetTypes.push_back(_tType.value());
|
|
// points
|
|
for (json::iterator _point = pathVal["aPoints"].begin(); _point != pathVal["aPoints"].end(); _point++) {
|
|
json point = _point.value();
|
|
for (int stopTicks = 0; stopTicks < (int)point["iStopTicks"] + 1; stopTicks++)
|
|
pathPoints.push_back({point["iX"], point["iY"], point["iZ"]});
|
|
}
|
|
|
|
NPCPath pathTemplate;
|
|
pathTemplate.targetIDs = targetIDs;
|
|
pathTemplate.targetTypes = targetTypes;
|
|
pathTemplate.points = pathPoints;
|
|
pathTemplate.speed = speed;
|
|
pathTemplate.isRelative = relative;
|
|
pathTemplate.isLoop = loop;
|
|
pathTemplate.escortTaskID = taskID;
|
|
|
|
Transport::NPCPaths.push_back(pathTemplate);
|
|
}
|
|
std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl;
|
|
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load drops data from JSON
|
|
* This has to be called after reading xdt because it reffers to ItemData!!!
|
|
*/
|
|
static void loadDrops(json& dropData) {
|
|
try {
|
|
// CrateDropChances
|
|
json crateDropChances = dropData["CrateDropChances"];
|
|
for (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"];
|
|
|
|
json crateWeights = crateDropChance["CrateTypeDropWeights"];
|
|
for (json::iterator _crateWeight = crateWeights.begin(); _crateWeight != crateWeights.end(); _crateWeight++)
|
|
toAdd.crateTypeDropWeights.push_back((int)_crateWeight.value());
|
|
|
|
Items::CrateDropChances[(int)crateDropChance["CrateDropChanceID"]] = toAdd;
|
|
}
|
|
|
|
// CrateDropTypes
|
|
json crateDropTypes = dropData["CrateDropTypes"];
|
|
for (json::iterator _crateDropType = crateDropTypes.begin(); _crateDropType != crateDropTypes.end(); _crateDropType++) {
|
|
auto crateDropType = _crateDropType.value();
|
|
std::vector<int> toAdd;
|
|
|
|
json crateIds = crateDropType["CrateIDs"];
|
|
for (json::iterator _crateId = crateIds.begin(); _crateId != crateIds.end(); _crateId++)
|
|
toAdd.push_back((int)_crateId.value());
|
|
|
|
Items::CrateDropTypes[(int)crateDropType["CrateDropTypeID"]] = toAdd;
|
|
}
|
|
|
|
// MiscDropChances
|
|
json miscDropChances = dropData["MiscDropChances"];
|
|
for (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
|
|
json miscDropTypes = dropData["MiscDropTypes"];
|
|
for (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
|
|
json mobDrops = dropData["MobDrops"];
|
|
for (json::iterator _mobDrop = mobDrops.begin(); _mobDrop != mobDrops.end(); _mobDrop++) {
|
|
auto mobDrop = _mobDrop.value();
|
|
|
|
Items::MobDrops[(int)mobDrop["MobDropID"]] = {
|
|
(int)mobDrop["CrateDropChanceID"],
|
|
(int)mobDrop["CrateDropTypeID"],
|
|
(int)mobDrop["MiscDropChanceID"],
|
|
(int)mobDrop["MiscDropTypeID"],
|
|
};
|
|
}
|
|
|
|
// Events
|
|
json events = dropData["Events"];
|
|
for (json::iterator _event = events.begin(); _event != events.end(); _event++) {
|
|
auto event = _event.value();
|
|
|
|
Items::EventToDropMap[(int)event["EventID"]] = (int)event["MobDropID"];
|
|
}
|
|
|
|
// Mobs
|
|
json mobs = dropData["Mobs"];
|
|
for (json::iterator _mob = mobs.begin(); _mob != mobs.end(); _mob++) {
|
|
auto mob = _mob.value();
|
|
|
|
Items::MobToDropMap[(int)mob["MobID"]] = (int)mob["MobDropID"];
|
|
}
|
|
|
|
// RarityWeights
|
|
json rarityWeights = dropData["RarityWeights"];
|
|
for (json::iterator _rarityWeightsObject = rarityWeights.begin(); _rarityWeightsObject != rarityWeights.end(); _rarityWeightsObject++) {
|
|
auto rarityWeightsObject = _rarityWeightsObject.value();
|
|
std::vector<int> toAdd;
|
|
|
|
json weights = rarityWeightsObject["Weights"];
|
|
for (json::iterator _weight = weights.begin(); _weight != weights.end(); _weight++)
|
|
toAdd.push_back((int)_weight.value());
|
|
|
|
Items::RarityWeights[(int)rarityWeightsObject["RarityWeightID"]] = toAdd;
|
|
}
|
|
|
|
// ItemSets
|
|
json itemSets = dropData["ItemSets"];
|
|
for (json::iterator _itemSet = itemSets.begin(); _itemSet != itemSets.end(); _itemSet++) {
|
|
auto itemSet = _itemSet.value();
|
|
ItemSet toAdd = {};
|
|
|
|
toAdd.ignoreRarity = (bool)itemSet["IgnoreRarity"];
|
|
toAdd.ignoreGender = (bool)itemSet["IgnoreGender"];
|
|
toAdd.defaultItemWeight = (int)itemSet["DefaultItemWeight"];
|
|
|
|
json alterRarityMap = itemSet["AlterRarityMap"];
|
|
for (json::iterator _alterRarityMapEntry = alterRarityMap.begin(); _alterRarityMapEntry != alterRarityMap.end(); _alterRarityMapEntry++)
|
|
toAdd.alterRarityMap[std::atoi(_alterRarityMapEntry.key().c_str())] = (int)_alterRarityMapEntry.value();
|
|
|
|
json alterGenderMap = itemSet["AlterGenderMap"];
|
|
for (json::iterator _alterGenderMapEntry = alterGenderMap.begin(); _alterGenderMapEntry != alterGenderMap.end(); _alterGenderMapEntry++)
|
|
toAdd.alterGenderMap[std::atoi(_alterGenderMapEntry.key().c_str())] = (int)_alterGenderMapEntry.value();
|
|
|
|
json alterItemWeightMap = itemSet["AlterItemWeightMap"];
|
|
for (json::iterator _alterItemWeightMapEntry = alterItemWeightMap.begin(); _alterItemWeightMapEntry != alterItemWeightMap.end(); _alterItemWeightMapEntry++)
|
|
toAdd.alterItemWeightMap[std::atoi(_alterItemWeightMapEntry.key().c_str())] = (int)_alterItemWeightMapEntry.value();
|
|
|
|
json itemReferenceIds = itemSet["ItemReferenceIDs"];
|
|
for (json::iterator itemReferenceId = itemReferenceIds.begin(); itemReferenceId != itemReferenceIds.end(); itemReferenceId++)
|
|
toAdd.itemReferenceIds.push_back((int)itemReferenceId.value());
|
|
|
|
Items::ItemSets[(int)itemSet["ItemSetID"]] = toAdd;
|
|
}
|
|
|
|
// Crates
|
|
json crates = dropData["Crates"];
|
|
for (json::iterator _crate = crates.begin(); _crate != crates.end(); _crate++) {
|
|
auto crate = _crate.value();
|
|
|
|
Items::Crates[(int)crate["CrateID"]] = {
|
|
(int)crate["ItemSetID"],
|
|
(int)crate["RarityWeightID"]
|
|
};
|
|
}
|
|
|
|
// ItemReferences
|
|
json itemReferences = dropData["ItemReferences"];
|
|
for (json::iterator _itemReference = itemReferences.begin(); _itemReference != itemReferences.end(); _itemReference++) {
|
|
auto itemReference = _itemReference.value();
|
|
|
|
int itemReferenceId = (int)itemReference["ItemReferenceID"];
|
|
int itemId = (int)itemReference["ItemID"];
|
|
int type = (int)itemReference["Type"];
|
|
|
|
// validate and fetch relevant fields as they're loaded
|
|
auto key = std::make_pair(itemId, type);
|
|
if (Items::ItemData.find(key) == Items::ItemData.end()) {
|
|
std::cout << "[WARN] Item-Type pair (" << key.first << ", " << key.second << ") specified by item reference "
|
|
<< itemReferenceId << " was not found, skipping..." << std::endl;
|
|
continue;
|
|
}
|
|
Items::Item& item = Items::ItemData[key];
|
|
|
|
Items::ItemReferences[itemReferenceId] = {
|
|
itemId,
|
|
type,
|
|
item.rarity,
|
|
item.gender
|
|
};
|
|
}
|
|
|
|
#ifdef ACADEMY
|
|
// NanoCapsules
|
|
json capsules = dropData["NanoCapsules"];
|
|
for (json::iterator _capsule = capsules.begin(); _capsule != capsules.end(); _capsule++) {
|
|
auto capsule = _capsule.value();
|
|
Items::NanoCapsules[(int)capsule["CrateID"]] = (int)capsule["Nano"];
|
|
}
|
|
#endif
|
|
|
|
// Racing rewards
|
|
json racing = dropData["Racing"];
|
|
for (json::iterator _race = racing.begin(); _race != racing.end(); _race++) {
|
|
auto race = _race.value();
|
|
int raceEPID = race["EPID"];
|
|
|
|
// find the instance data corresponding to the EPID
|
|
int EPMap = -1;
|
|
for (auto it = Racing::EPData.begin(); it != Racing::EPData.end(); it++) {
|
|
if (it->second.EPID == raceEPID) {
|
|
EPMap = it->first;
|
|
}
|
|
}
|
|
|
|
if (EPMap == -1) { // not found
|
|
std::cout << "[WARN] EP with ID " << raceEPID << " not found, skipping" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// time limit isn't stored in the XDT, so we include it in the reward table instead
|
|
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
|
|
|
|
// score cutoffs
|
|
std::vector<int> rankScores;
|
|
for (json::iterator _rankScore = race["RankScores"].begin(); _rankScore != race["RankScores"].end(); _rankScore++) {
|
|
rankScores.push_back((int)_rankScore.value());
|
|
}
|
|
|
|
// reward IDs for each rank
|
|
std::vector<int> rankRewards;
|
|
for (json::iterator _rankReward = race["Rewards"].begin(); _rankReward != race["Rewards"].end(); _rankReward++) {
|
|
rankRewards.push_back((int)_rankReward.value());
|
|
}
|
|
|
|
if (rankScores.size() != 5 || rankScores.size() != rankRewards.size()) {
|
|
char buff[255];
|
|
sprintf(buff, "Race in EP %d doesn't have exactly 5 score/reward pairs", raceEPID);
|
|
throw TableException(std::string(buff));
|
|
}
|
|
|
|
Racing::EPRewards[raceEPID] = std::make_pair(rankScores, rankRewards);
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded rewards for " << Racing::EPRewards.size() << " IZ races" << std::endl;
|
|
|
|
// CodeItems
|
|
json codes = dropData["CodeItems"];
|
|
for (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;
|
|
|
|
json itemReferenceIds = code["ItemReferenceIDs"];
|
|
for (json::iterator _itemReferenceId = itemReferenceIds.begin(); _itemReferenceId != itemReferenceIds.end(); _itemReferenceId++) {
|
|
int itemReferenceId = (int)_itemReferenceId.value();
|
|
|
|
// validate and convert here
|
|
if (Items::ItemReferences.find(itemReferenceId) == Items::ItemReferences.end()) {
|
|
std::cout << "[WARN] Item reference " << itemReferenceId << " for code "
|
|
<< codeStr << " was not found, skipping..." << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// no need to further check whether this is a real item or not, we already did this!
|
|
ItemReference& itemReference = Items::ItemReferences[itemReferenceId];
|
|
itemVector.push_back(std::make_pair(itemReference.itemId, itemReference.type));
|
|
}
|
|
|
|
Items::CodeItems[codeStr] = itemVector;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << Items::Crates.size() << " Crates containing "
|
|
<< Items::ItemReferences.size() << " unique items" << std::endl;
|
|
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed drops.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load eggs from JSON
|
|
*/
|
|
static void loadEggs(json& eggData, int32_t* nextId) {
|
|
try {
|
|
// EggTypes
|
|
json eggTypes = eggData["EggTypes"];
|
|
for (json::iterator _eggType = eggTypes.begin(); _eggType != eggTypes.end(); _eggType++) {
|
|
auto eggType = _eggType.value();
|
|
EggType toAdd = {};
|
|
toAdd.dropCrateId = (int)eggType["DropCrateId"];
|
|
toAdd.effectId = (int)eggType["EffectId"];
|
|
toAdd.duration = (int)eggType["Duration"];
|
|
toAdd.regen= (int)eggType["Regen"];
|
|
Eggs::EggTypes[(int)eggType["Id"]] = toAdd;
|
|
}
|
|
|
|
// Egg instances
|
|
auto eggs = eggData["Eggs"];
|
|
int eggCount = 0;
|
|
for (auto _egg = eggs.begin(); _egg != eggs.end(); _egg++) {
|
|
auto egg = _egg.value();
|
|
int id = (*nextId)--;
|
|
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
|
|
|
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false);
|
|
NPCManager::NPCs[id] = addEgg;
|
|
eggCount++;
|
|
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << eggCount << " eggs" <<std::endl;
|
|
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed eggs.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load gruntwork output, if it exists
|
|
*/
|
|
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
|
if (gruntwork.is_null())
|
|
return;
|
|
|
|
try {
|
|
auto paths = gruntwork["paths"];
|
|
for (auto _path = paths.begin(); _path != paths.end(); _path++) {
|
|
auto path = _path.value();
|
|
|
|
std::vector<int32_t> targetIDs;
|
|
std::vector<int32_t> targetTypes; // target types are not exportable from gw, but load them anyway
|
|
std::vector<Vec3> pathPoints;
|
|
int speed = path.find("iBaseSpeed") == path.end() ? NPC_DEFAULT_SPEED : (int)path["iBaseSpeed"];
|
|
int taskID = path.find("iTaskID") == path.end() ? -1 : (int)path["iTaskID"];
|
|
bool relative = path.find("bRelative") == path.end() ? false : (bool)path["bRelative"];
|
|
bool loop = path.find("bLoop") == path.end() ? true : (bool)path["bLoop"]; // loop by default
|
|
|
|
// target IDs
|
|
for (json::iterator _tID = path["aNPCIDs"].begin(); _tID != path["aNPCIDs"].end(); _tID++)
|
|
targetIDs.push_back(_tID.value());
|
|
// target types
|
|
for (json::iterator _tType = path["aNPCTypes"].begin(); _tType != path["aNPCTypes"].end(); _tType++)
|
|
targetTypes.push_back(_tType.value());
|
|
// points
|
|
for (json::iterator _point = path["aPoints"].begin(); _point != path["aPoints"].end(); _point++) {
|
|
json point = _point.value();
|
|
for (int stopTicks = 0; stopTicks < (int)point["iStopTicks"] + 1; stopTicks++)
|
|
pathPoints.push_back({ point["iX"], point["iY"], point["iZ"] });
|
|
}
|
|
|
|
NPCPath pathTemplate;
|
|
pathTemplate.targetIDs = targetIDs;
|
|
pathTemplate.targetTypes = targetTypes;
|
|
pathTemplate.points = pathPoints;
|
|
pathTemplate.speed = speed;
|
|
pathTemplate.isRelative = relative;
|
|
pathTemplate.escortTaskID = taskID;
|
|
pathTemplate.isLoop = loop;
|
|
|
|
Transport::NPCPaths.push_back(pathTemplate);
|
|
TableData::FinishedNPCPaths.push_back(pathTemplate); // keep in gruntwork
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded gruntwork.json (pre)" << std::endl;
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed gruntwork.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|
if (gruntwork.is_null())
|
|
return;
|
|
|
|
try {
|
|
// skyway paths
|
|
auto skyway = gruntwork["skyway"];
|
|
for (auto _route = skyway.begin(); _route != skyway.end(); _route++) {
|
|
auto route = _route.value();
|
|
std::vector<Vec3> points;
|
|
|
|
for (auto _point = route["points"].begin(); _point != route["points"].end(); _point++) {
|
|
auto point = _point.value();
|
|
points.push_back(Vec3{point["x"], point["y"], point["z"]});
|
|
}
|
|
|
|
RunningSkywayRoutes[(int)route["iRouteID"]] = points;
|
|
}
|
|
|
|
// npc rotations
|
|
auto npcRot = gruntwork["rotations"];
|
|
for (auto _rot = npcRot.begin(); _rot != npcRot.end(); _rot++) {
|
|
int32_t npcID = _rot.value()["iNPCID"];
|
|
int angle = _rot.value()["iAngle"];
|
|
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
|
continue; // NPC not found
|
|
BaseNPC* npc = NPCManager::NPCs[npcID];
|
|
npc->angle = angle;
|
|
|
|
RunningNPCRotations[npcID] = angle;
|
|
}
|
|
|
|
// npc map numbers
|
|
auto npcMap = gruntwork["instances"];
|
|
for (auto _map = npcMap.begin(); _map != npcMap.end(); _map++) {
|
|
int32_t npcID = _map.value()["iNPCID"];
|
|
uint64_t instanceID = _map.value()["iMapNum"];
|
|
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
|
continue; // NPC not found
|
|
BaseNPC* npc = NPCManager::NPCs[npcID];
|
|
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y,
|
|
npc->z, instanceID, npc->angle);
|
|
|
|
RunningNPCMapNumbers[npcID] = instanceID;
|
|
}
|
|
|
|
// mobs
|
|
auto mobs = gruntwork["mobs"];
|
|
for (auto _mob = mobs.begin(); _mob != mobs.end(); _mob++) {
|
|
auto mob = _mob.value();
|
|
BaseNPC *npc;
|
|
int id = (*nextId)--;
|
|
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
|
|
|
ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON);
|
|
|
|
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
|
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
|
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
|
|
|
// re-enable respawning
|
|
((Mob*)npc)->summoned = false;
|
|
} else {
|
|
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id);
|
|
}
|
|
|
|
NPCManager::NPCs[npc->id] = npc;
|
|
RunningMobs[npc->id] = npc;
|
|
NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
|
}
|
|
|
|
// mob groups
|
|
auto groups = gruntwork["groups"];
|
|
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
|
auto leader = _group.value();
|
|
|
|
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
|
|
|
|
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
|
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
|
|
|
Mob* tmp = new Mob(leader["iX"], leader["iY"], leader["iZ"], leader["iAngle"], instanceID, leader["iNPCType"], td, *nextId);
|
|
|
|
// re-enable respawning
|
|
((Mob*)tmp)->summoned = false;
|
|
|
|
NPCManager::NPCs[*nextId] = tmp;
|
|
NPCManager::updateNPCPosition(*nextId, leader["iX"], leader["iY"], leader["iZ"], instanceID, leader["iAngle"]);
|
|
|
|
tmp->groupLeader = *nextId;
|
|
|
|
(*nextId)--;
|
|
|
|
auto followers = leader["aFollowers"];
|
|
if (followers.size() < 5) {
|
|
int followerCount = 0;
|
|
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
|
auto follower = _fol.value();
|
|
|
|
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
|
|
|
|
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
|
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
|
|
|
// re-enable respawning
|
|
((Mob*)tmp)->summoned = false;
|
|
|
|
NPCManager::NPCs[*nextId] = tmpFol;
|
|
NPCManager::updateNPCPosition(*nextId, (int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], instanceID, leader["iAngle"]);
|
|
|
|
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
|
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
|
tmpFol->groupLeader = tmp->id;
|
|
tmp->groupMember[followerCount++] = *nextId;
|
|
|
|
(*nextId)--;
|
|
}
|
|
}
|
|
else {
|
|
std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n";
|
|
}
|
|
|
|
RunningGroups[tmp->id] = tmp; // store as running
|
|
}
|
|
|
|
auto eggs = gruntwork["eggs"];
|
|
for (auto _egg = eggs.begin(); _egg != eggs.end(); _egg++) {
|
|
auto egg = _egg.value();
|
|
int id = (*nextId)--;
|
|
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
|
|
|
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false);
|
|
NPCManager::NPCs[id] = addEgg;
|
|
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
|
RunningEggs[id] = addEgg;
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded gruntwork.json (post)" << std::endl;
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed gruntwork.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load NPCs from JSON
|
|
*/
|
|
static void loadNPCs(json& npcData) {
|
|
try {
|
|
npcData = npcData["NPCs"];
|
|
for (json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) {
|
|
auto npc = _npc.value();
|
|
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
|
npcID += NPC_ID_OFFSET;
|
|
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
|
int type = (int)npc["iNPCType"];
|
|
|
|
ensureValidNPCType(type, settings::NPCJSON);
|
|
|
|
#ifdef ACADEMY
|
|
// do not spawn NPCs in the future
|
|
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
|
continue;
|
|
#endif
|
|
BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID);
|
|
|
|
NPCManager::NPCs[npcID] = tmp;
|
|
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
|
|
|
if (type == 641 || type == 642)
|
|
NPCManager::RespawnPoints.push_back({ npc["iX"], npc["iY"], ((int)npc["iZ"]) + RESURRECT_HEIGHT, instanceID });
|
|
|
|
// see if any paths target this NPC
|
|
NPCPath* npcPath = Transport::findApplicablePath(npcID, type);
|
|
if (npcPath != nullptr) {
|
|
//std::cout << "[INFO] Found path for NPC " << npcID << std::endl;
|
|
Transport::constructPathNPC(npcID, npcPath);
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load mobs from JSON
|
|
*/
|
|
static void loadMobs(json& npcData, int32_t* nextId) {
|
|
try {
|
|
json groupData = npcData["groups"];
|
|
npcData = npcData["mobs"];
|
|
|
|
// single mobs
|
|
for (json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) {
|
|
auto npc = _npc.value();
|
|
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
|
npcID += MOB_ID_OFFSET;
|
|
int type = (int)npc["iNPCType"];
|
|
|
|
ensureValidNPCType(type, settings::MOBJSON);
|
|
|
|
auto td = NPCManager::NPCData[type];
|
|
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
|
|
|
#ifdef ACADEMY
|
|
// do not spawn NPCs in the future
|
|
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
|
continue;
|
|
#endif
|
|
|
|
Mob* tmp = new Mob(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, npc["iNPCType"], td, npcID);
|
|
|
|
NPCManager::NPCs[npcID] = tmp;
|
|
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
|
|
|
// see if any paths target this mob
|
|
NPCPath* npcPath = Transport::findApplicablePath(npcID, npc["iNPCType"]);
|
|
if (npcPath != nullptr) {
|
|
//std::cout << "[INFO] Found path for mob " << npcID << std::endl;
|
|
Transport::constructPathNPC(npcID, npcPath);
|
|
}
|
|
}
|
|
|
|
// mob groups
|
|
// single mobs (have static IDs)
|
|
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
|
auto leader = _group.value();
|
|
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
|
|
|
leadID += MOB_GROUP_ID_OFFSET;
|
|
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
|
|
|
|
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
|
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
|
auto followers = leader["aFollowers"];
|
|
|
|
#ifdef ACADEMY
|
|
// do not spawn NPCs in the future
|
|
if (leader["iX"] > 512000 && leader["iY"] < 256000)
|
|
continue;
|
|
#endif
|
|
|
|
Mob* tmp = new Mob(leader["iX"], leader["iY"], leader["iZ"], leader["iAngle"], instanceID, leader["iNPCType"], td, leadID);
|
|
|
|
NPCManager::NPCs[leadID] = tmp;
|
|
NPCManager::updateNPCPosition(leadID, leader["iX"], leader["iY"], leader["iZ"], instanceID, leader["iAngle"]);
|
|
|
|
// see if any paths target this group leader
|
|
NPCPath* npcPath = Transport::findApplicablePath(leadID, leader["iNPCType"]);
|
|
if (npcPath != nullptr) {
|
|
//std::cout << "[INFO] Found path for mob " << leadID << std::endl;
|
|
Transport::constructPathNPC(leadID, npcPath);
|
|
}
|
|
|
|
tmp->groupLeader = leadID;
|
|
|
|
// followers (have dynamic IDs)
|
|
if (followers.size() < 5) {
|
|
int followerCount = 0;
|
|
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
|
auto follower = _fol.value();
|
|
|
|
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
|
|
|
|
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
|
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
|
|
|
NPCManager::NPCs[*nextId] = tmpFol;
|
|
NPCManager::updateNPCPosition(*nextId, (int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], instanceID, leader["iAngle"]);
|
|
|
|
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
|
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
|
tmpFol->groupLeader = tmp->id;
|
|
tmp->groupMember[followerCount++] = *nextId;
|
|
|
|
(*nextId)--;
|
|
}
|
|
}
|
|
else {
|
|
std::cout << "[WARN] Mob group leader with ID " << leadID << " has too many followers (" << followers.size() << ")\n";
|
|
}
|
|
}
|
|
|
|
std::cout << "[INFO] Loaded " << NPCManager::NPCs.size() << " NPCs" << std::endl;
|
|
}
|
|
catch (const std::exception& err) {
|
|
std::cerr << "[FATAL] Malformed mobs.json file! Reason:" << err.what() << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Transform `base` based on the value of `patch`.
|
|
* Parameters must be of the same type and must not be null.
|
|
* Array <- Array: All elements in patch array get added to base array.
|
|
* Object <- Object: Combine properties recursively and save to base.
|
|
* All other types <- Same type: Base value gets overwritten by patch value.
|
|
*/
|
|
static void patchJSON(json* base, json* patch) {
|
|
if (patch->is_null() || base->is_null())
|
|
return; // no nulls allowed!!
|
|
|
|
if ((json::value_t)(*base) != (json::value_t)(*patch)) {
|
|
// verify type mismatch. unsigned <-> integer is ok.
|
|
if (!((base->is_number_integer() && patch->is_number_unsigned())
|
|
|| (base->is_number_unsigned() && patch->is_number_integer())))
|
|
return;
|
|
}
|
|
|
|
// case 1: type is array
|
|
if (patch->is_array()) {
|
|
// loop through all array elements
|
|
for (json::iterator _element = patch->begin(); _element != patch->end(); _element++)
|
|
base->push_back(*_element); // add element to base
|
|
}
|
|
// case 2: type is object
|
|
else if (patch->is_object()) {
|
|
// loop through all object properties
|
|
for (json::iterator _prop = patch->begin(); _prop != patch->end(); _prop++) {
|
|
std::string key = _prop.key(); // static identifier
|
|
json* valLoc = &(*_prop); // pointer to json data
|
|
|
|
// special casing for forced replacement.
|
|
// the ! is stripped, then the property is forcibly replaced without a recursive call
|
|
// that means no type checking, so use at your own risk
|
|
if (key.c_str()[0] == '!') {
|
|
key = key.substr(1, key.length() - 1);
|
|
(*base)[key] = *valLoc;
|
|
continue;
|
|
}
|
|
|
|
// search for matching property in base object
|
|
json::iterator _match = base->find(key);
|
|
if (_match != base->end()) {
|
|
// match found
|
|
if (valLoc->is_null()) // prop value is null; erase match
|
|
base->erase(key);
|
|
else { // combine objects
|
|
json* match = &(*_match);
|
|
patchJSON(match, valLoc);
|
|
}
|
|
} else {
|
|
// no match found; add the property to the base object
|
|
(*base)[key] = *valLoc;
|
|
}
|
|
}
|
|
}
|
|
// case 3: all other types
|
|
else {
|
|
*base = *patch; // complete overwrite
|
|
}
|
|
}
|
|
|
|
void TableData::init() {
|
|
int32_t nextId = INT32_MAX; // next dynamic ID to hand out
|
|
|
|
// base JSON tables
|
|
json xdt, paths, drops, eggs, npcs, mobs, gruntwork;
|
|
std::pair<json*, std::string> tables[7] = {
|
|
std::make_pair(&xdt, settings::XDTJSON),
|
|
std::make_pair(&paths, settings::PATHJSON),
|
|
std::make_pair(&drops, settings::DROPSJSON),
|
|
std::make_pair(&eggs, settings::EGGSJSON),
|
|
std::make_pair(&npcs, settings::NPCJSON),
|
|
std::make_pair(&mobs, settings::MOBJSON),
|
|
std::make_pair(&gruntwork, settings::GRUNTWORKJSON)
|
|
};
|
|
|
|
// load JSON data into tables
|
|
for (int i = 0; i < 7; i++) {
|
|
std::pair<json*, std::string>& table = tables[i];
|
|
|
|
// scope for fstream
|
|
{
|
|
std::ifstream fstream;
|
|
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
|
|
|
// did we fail to open the file?
|
|
if (fstream.fail()) {
|
|
// gruntwork isn't critical
|
|
if (table.first == &gruntwork)
|
|
continue;
|
|
|
|
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
// is the file empty?
|
|
if (fstream.peek() == std::ifstream::traits_type::eof()) {
|
|
// tolerate empty gruntwork file
|
|
if (table.first == &gruntwork) {
|
|
std::cout << "[WARN] The gruntwork file is empty" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
std::cerr << "[FATAL] Critical tdata file is empty: " << table.second << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
// load file contents into table
|
|
fstream >> *table.first;
|
|
}
|
|
|
|
// patching: load each patch directory specified in the config file
|
|
|
|
// split config field into individual patch entries
|
|
std::stringstream ss(settings::ENABLEDPATCHES);
|
|
std::istream_iterator<std::string> begin(ss);
|
|
std::istream_iterator<std::string> end;
|
|
|
|
json patch;
|
|
for (auto it = begin; it != end; it++) {
|
|
// this is the theoretical path of a corresponding patch for this file
|
|
std::string patchModuleName = *it;
|
|
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
|
try {
|
|
std::ifstream fstream;
|
|
fstream.open(patchFile);
|
|
fstream >> patch; // load into temporary json object
|
|
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
|
patchJSON(table.first, &patch); // patch
|
|
} catch (const std::exception& err) {
|
|
// no-op
|
|
}
|
|
}
|
|
}
|
|
|
|
// fetch data from patched tables and load them appropriately
|
|
// note: the order of these is important
|
|
std::cout << "[INFO] Loading tabledata..." << std::endl;
|
|
loadXDT(xdt);
|
|
loadGruntworkPre(gruntwork, &nextId);
|
|
loadPaths(paths, &nextId);
|
|
loadNPCs(npcs);
|
|
loadMobs(mobs, &nextId);
|
|
loadDrops(drops);
|
|
loadEggs(eggs, &nextId);
|
|
loadGruntworkPost(gruntwork, &nextId);
|
|
|
|
NPCManager::nextId = nextId;
|
|
}
|
|
|
|
/*
|
|
* Write gruntwork output to file
|
|
*/
|
|
void TableData::flush() {
|
|
std::ofstream file(settings::TDATADIR + "/" + settings::GRUNTWORKJSON);
|
|
json gruntwork;
|
|
|
|
for (auto& pair : RunningSkywayRoutes) {
|
|
json route;
|
|
|
|
route["iRouteID"] = (int)pair.first;
|
|
route["iMonkeySpeed"] = 1500;
|
|
|
|
std::cout << "serializing mss route " << (int)pair.first << std::endl;
|
|
for (Vec3& point : pair.second) {
|
|
json tmp;
|
|
|
|
tmp["x"] = point.x;
|
|
tmp["y"] = point.y;
|
|
tmp["z"] = point.z;
|
|
|
|
route["points"].push_back(tmp);
|
|
}
|
|
|
|
gruntwork["skyway"].push_back(route);
|
|
}
|
|
|
|
for (auto& pair : RunningNPCRotations) {
|
|
json rotation;
|
|
|
|
rotation["iNPCID"] = (int)pair.first;
|
|
rotation["iAngle"] = pair.second;
|
|
|
|
gruntwork["rotations"].push_back(rotation);
|
|
}
|
|
|
|
for (auto& pair : RunningNPCMapNumbers) {
|
|
json mapNumber;
|
|
|
|
mapNumber["iNPCID"] = (int)pair.first;
|
|
mapNumber["iMapNum"] = pair.second;
|
|
|
|
gruntwork["instances"].push_back(mapNumber);
|
|
}
|
|
|
|
for (auto& pair : RunningMobs) {
|
|
json mob;
|
|
BaseNPC *npc = pair.second;
|
|
|
|
if (NPCManager::NPCs.find(pair.first) == NPCManager::NPCs.end())
|
|
continue;
|
|
|
|
int x, y, z;
|
|
if (npc->kind == EntityType::MOB) {
|
|
Mob *m = (Mob*)npc;
|
|
x = m->spawnX;
|
|
y = m->spawnY;
|
|
z = m->spawnZ;
|
|
} else {
|
|
x = npc->x;
|
|
y = npc->y;
|
|
z = npc->z;
|
|
}
|
|
|
|
// NOTE: this format deviates slightly from the one in mobs.json
|
|
mob["iNPCType"] = (int)npc->type;
|
|
mob["iX"] = x;
|
|
mob["iY"] = y;
|
|
mob["iZ"] = z;
|
|
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
|
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
|
|
mob["iAngle"] = npc->angle;
|
|
|
|
// it's called mobs, but really it's everything
|
|
gruntwork["mobs"].push_back(mob);
|
|
}
|
|
|
|
for (auto& pair : RunningGroups) {
|
|
json mob;
|
|
BaseNPC* npc = pair.second;
|
|
|
|
if (NPCManager::NPCs.find(pair.first) == NPCManager::NPCs.end())
|
|
continue;
|
|
|
|
int x, y, z;
|
|
std::vector<Mob*> followers;
|
|
if (npc->kind == EntityType::MOB) {
|
|
Mob* m = (Mob*)npc;
|
|
x = m->spawnX;
|
|
y = m->spawnY;
|
|
z = m->spawnZ;
|
|
if (m->groupLeader != m->id) { // make sure this is a leader
|
|
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
|
|
continue;
|
|
}
|
|
|
|
// add follower data to vector; go until OOB or until follower ID is 0
|
|
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
|
|
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityType::MOB) {
|
|
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
|
continue;
|
|
}
|
|
followers.push_back((Mob*)NPCManager::NPCs[m->groupMember[i]]);
|
|
}
|
|
}
|
|
else {
|
|
x = npc->x;
|
|
y = npc->y;
|
|
z = npc->z;
|
|
}
|
|
|
|
// NOTE: this format deviates slightly from the one in mobs.json
|
|
mob["iNPCType"] = (int)npc->type;
|
|
mob["iX"] = x;
|
|
mob["iY"] = y;
|
|
mob["iZ"] = z;
|
|
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
|
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
|
|
mob["iAngle"] = npc->angle;
|
|
|
|
// followers
|
|
while (followers.size() > 0) {
|
|
Mob* follower = followers.back();
|
|
followers.pop_back(); // remove from vector
|
|
|
|
// populate JSON entry
|
|
json fol;
|
|
fol["iNPCType"] = follower->type;
|
|
fol["iOffsetX"] = follower->offsetX;
|
|
fol["iOffsetY"] = follower->offsetY;
|
|
|
|
mob["aFollowers"].push_back(fol); // add to follower array
|
|
}
|
|
|
|
// it's called mobs, but really it's everything
|
|
gruntwork["groups"].push_back(mob);
|
|
}
|
|
|
|
for (auto& pair : RunningEggs) {
|
|
json egg;
|
|
BaseNPC* npc = pair.second;
|
|
|
|
if (NPCManager::NPCs.find(pair.first) == NPCManager::NPCs.end())
|
|
continue;
|
|
// we can trust that if it exists, it probably is indeed an egg
|
|
|
|
egg["iX"] = npc->x;
|
|
egg["iY"] = npc->y;
|
|
egg["iZ"] = npc->z;
|
|
int mapnum = MAPNUM(npc->instanceID);
|
|
if (mapnum != 0)
|
|
egg["iMapNum"] = mapnum;
|
|
egg["iType"] = npc->type;
|
|
|
|
gruntwork["eggs"].push_back(egg);
|
|
}
|
|
|
|
for (auto& path : FinishedNPCPaths) {
|
|
json pathObj;
|
|
json points;
|
|
json targetIDs;
|
|
json targetTypes;
|
|
|
|
for (Vec3& coord : path.points) {
|
|
json point;
|
|
point["iX"] = coord.x;
|
|
point["iY"] = coord.y;
|
|
point["iZ"] = coord.z;
|
|
point["iStopTicks"] = 0;
|
|
points.push_back(point);
|
|
}
|
|
|
|
for (int32_t tID : path.targetIDs)
|
|
targetIDs.push_back(tID);
|
|
for (int32_t tType : path.targetTypes)
|
|
targetTypes.push_back(tType);
|
|
|
|
pathObj["iBaseSpeed"] = path.speed;
|
|
pathObj["iTaskID"] = path.escortTaskID;
|
|
pathObj["bRelative"] = path.isRelative;
|
|
pathObj["aPoints"] = points;
|
|
pathObj["bLoop"] = path.isLoop;
|
|
|
|
// don't write 'null' if there aren't any targets
|
|
if(targetIDs.size() > 0)
|
|
pathObj["aNPCIDs"] = targetIDs;
|
|
if (targetTypes.size() > 0)
|
|
pathObj["aNPCTypes"] = targetTypes;
|
|
|
|
gruntwork["paths"].push_back(pathObj);
|
|
}
|
|
|
|
file << gruntwork << std::endl;
|
|
}
|