Implemented static paths for mobs (like Don Doom and Bad Max).

* Mobs now retreat to where they were when they were roaming; not their
spawn point
* /toggleai still sends them back to their spawn point however
* Fixed a bug where mobs would respawn where they were killed instead of
their proper spawn point
* Fixed mobs roaming despite simulatemobs being set to false
* Updated tdata submodule
This commit is contained in:
dongresource 2020-10-13 21:44:43 +02:00
parent c792fb9d0d
commit 599bbedd8c
8 changed files with 97 additions and 18 deletions

View File

@ -34,6 +34,7 @@ std::string U16toU8(char16_t* src);
size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des
time_t getTime();
time_t getTimestamp();
void terminate(int);
// The PROTOCOL_VERSION definition is defined by the build system.
#if !defined(PROTOCOL_VERSION)

View File

@ -45,7 +45,6 @@ bool runCmd(std::string full, CNSocket* sock) {
void helpCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
ChatManager::sendServerMessage(sock, "Commands available to you");
Player *plr = PlayerManager::getPlayer(sock);
int i = 1;
for (auto& cmd : ChatManager::commands) {
if (cmd.second.requiredAccLevel >= plr->accountId)
@ -280,6 +279,10 @@ void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket*
for (auto& pair : MobManager::Mobs) {
pair.second->state = MobState::RETREAT;
pair.second->target = nullptr;
pair.second->roamX = pair.second->spawnX;
pair.second->roamY = pair.second->spawnY;
pair.second->roamZ = pair.second->spawnZ;
}
}

View File

@ -210,6 +210,10 @@ int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) {
mob->state = MobState::COMBAT;
mob->nextMovement = getTime();
mob->nextAttack = 0;
mob->roamX = mob->appearanceData.iX;
mob->roamY = mob->appearanceData.iY;
mob->roamZ = mob->appearanceData.iZ;
}
mob->appearanceData.iHP -= damage;
@ -254,7 +258,29 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) {
}
}
// delay the despawn animation
mob->despawned = false;
auto it = TransportManager::NPCQueues.find(mob->appearanceData.iNPC_ID);
if (it == TransportManager::NPCQueues.end() || it->second.empty())
return;
// rewind or empty the movement queue
if (mob->staticPath) {
/*
* This is inelegant, but we wind forward in the path until we find the point that
* corresponds with the Mob's spawn point.
*
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
*/
auto& queue = it->second;
for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) {
queue.pop();
queue.push(point);
}
} else {
TransportManager::NPCQueues.erase(mob->appearanceData.iNPC_ID);
}
}
void MobManager::deadStep(Mob *mob, time_t currTime) {
@ -284,6 +310,11 @@ void MobManager::deadStep(Mob *mob, time_t currTime) {
mob->appearanceData.iHP = mob->maxHealth;
mob->state = MobState::ROAMING;
// reset position
mob->appearanceData.iX = mob->spawnX;
mob->appearanceData.iY = mob->spawnY;
mob->appearanceData.iZ = mob->spawnZ;
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
pkt.NPCAppearanceData = mob->appearanceData;
@ -360,7 +391,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
}
// retreat if the player leaves combat range
distance = hypot(plr->x - mob->spawnX, plr->y - mob->spawnY);
distance = hypot(plr->x - mob->roamX, plr->y - mob->roamY);
if (distance >= mob->data["m_iCombatRange"]) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
@ -390,14 +421,18 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) {
if (mob->idleRange == 0)
return;
// no random roaming if the mob already has a set path
if (mob->staticPath)
return;
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
return;
incNextMovement(mob, currTime);
// only calculate a new route if we're not already on one
auto it = TransportManager::NPCQueues.find(mob->appearanceData.iNPC_ID);
if (it != TransportManager::NPCQueues.end() && it->second.empty())
return;
/*
* mob->nextMovement is also updated whenever the path queue is traversed in
* TransportManager::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
* so we don't have to check if there's already entries in the queue since we know there won't be.
*/
int xStart = mob->spawnX - mob->idleRange/2;
int yStart = mob->spawnY - mob->idleRange/2;
@ -437,13 +472,13 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
mob->nextMovement = currTime + 500;
int distance = hypot(mob->appearanceData.iX - mob->spawnX, mob->appearanceData.iY - mob->spawnY);
int distance = hypot(mob->appearanceData.iX - mob->roamX, mob->appearanceData.iY - mob->roamY);
//if (distance > mob->data["m_iIdleRange"]) {
if (distance > 10) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, mob->spawnX, mob->spawnY, (int)mob->data["m_iRunSpeed"] * 3);
auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, mob->roamX, mob->roamY, (int)mob->data["m_iRunSpeed"] * 3);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
pkt.iSpeed = (int)mob->data["m_iRunSpeed"] * 3;
@ -720,10 +755,12 @@ std::pair<int,int> MobManager::getDamage(int attackPower, int defensePower, bool
if (attackPower + defensePower * 2 == 0)
return ret;
// base calculation
int damage = attackPower * attackPower / (attackPower + defensePower);
damage = std::max(std::max(29, attackPower / 7), damage - defensePower * (12 + difficulty) / 65);
damage = damage * (rand() % 40 + 80) / 100;
// Adaptium/Blastons/Cosmix
if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) {
if (attackerStyle < defenderStyle || attackerStyle - defenderStyle == 2)
damage = damage * 5 / 4;
@ -731,6 +768,7 @@ std::pair<int,int> MobManager::getDamage(int attackPower, int defensePower, bool
damage = damage * 4 / 5;
}
// weapon boosts
if (batteryBoost)
damage = damage * 5 / 4;
@ -913,6 +951,11 @@ bool MobManager::aggroCheck(Mob *mob, time_t currTime) {
mob->state = MobState::COMBAT;
mob->nextMovement = currTime;
mob->nextAttack = 0;
mob->roamX = mob->appearanceData.iX;
mob->roamY = mob->appearanceData.iY;
mob->roamZ = mob->appearanceData.iZ;
return true;
}
}

View File

@ -40,6 +40,7 @@ struct Mob : public BaseNPC {
// combat
CNSocket *target = nullptr;
time_t nextAttack = 0;
int roamX, roamY, roamZ;
// temporary; until we're sure what's what
nlohmann::json data;
@ -57,9 +58,9 @@ struct Mob : public BaseNPC {
if (regenTime >= 300000000)
regenTime = 1500;
spawnX = appearanceData.iX;
spawnY = appearanceData.iY;
spawnZ = appearanceData.iZ;
roamX = spawnX = appearanceData.iX;
roamY = spawnY = appearanceData.iY;
roamZ = spawnZ = appearanceData.iZ;
appearanceData.iConditionBitFlag = 0;

View File

@ -44,8 +44,6 @@ void TableData::init() {
std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
}
loadPaths(&nextId); // load paths
// load everything else from xdttable
std::cout << "[INFO] Parsing xdt.json..." << std::endl;
std::ifstream infile(settings::XDTJSON);
@ -198,6 +196,8 @@ void TableData::init() {
std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl;
}
loadPaths(&nextId); // load paths
loadGruntwork(&nextId);
NPCManager::nextId = nextId;
@ -264,6 +264,27 @@ void TableData::loadPaths(int* nextId) {
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
constructPathNPC(npcPath);
}
// mob paths
pathDataNPC = pathData["mob"];
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
for (auto& pair : MobManager::Mobs) {
if (pair.second->appearanceData.iNPCType == npcPath.value()["iNPCType"]) {
std::cout << "[INFO] Using static path for mob " << pair.second->appearanceData.iNPCType << " with ID " << pair.first << std::endl;
auto firstPoint = npcPath.value()["points"][0];
if (firstPoint["iX"] != pair.second->spawnX || firstPoint["iY"] != pair.second->spawnY) {
std::cout << "[FATAL] The first point of the route for mob " << pair.first <<
" (type " << pair.second->appearanceData.iNPCType << ") does not correspond with its spawn point." << std::endl;
terminate(0);
}
constructPathNPC(npcPath, pair.first);
pair.second->staticPath = true;
break; // only one NPC per path
}
}
}
std::cout << "[INFO] Loaded " << TransportManager::NPCQueues.size() << " NPC paths" << std::endl;
}
catch (const std::exception& err) {
@ -319,7 +340,7 @@ void TableData::constructPathSlider(nlohmann::json points, int rotations, int sl
TransportManager::NPCQueues[sliderID] = route;
}
void TableData::constructPathNPC(nlohmann::json::iterator _pathData) {
void TableData::constructPathNPC(nlohmann::json::iterator _pathData, int32_t id) {
auto pathData = _pathData.value();
// Interpolate
nlohmann::json pathPoints = pathData["points"];
@ -337,7 +358,11 @@ void TableData::constructPathNPC(nlohmann::json::iterator _pathData) {
from = to; // update point A
stopTime = point["stop"];
}
TransportManager::NPCQueues[pathData["iNPCID"]] = points;
if (id == 0)
id = pathData["iNPCID"];
TransportManager::NPCQueues[id] = points;
}
// load gruntwork output; if it exists

View File

@ -18,5 +18,5 @@ namespace TableData {
void loadPaths(int*);
void constructPathSkyway(nlohmann::json::iterator);
void constructPathSlider(nlohmann::json, int, int);
void constructPathNPC(nlohmann::json::iterator);
void constructPathNPC(nlohmann::json::iterator, int id=0);
}

View File

@ -282,6 +282,12 @@ void TransportManager::stepNPCPathing() {
continue;
}
// skip if not simulating mobs
if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) {
it++;
continue;
}
// do not roam if not roaming
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
it++;

2
tdata

@ -1 +1 @@
Subproject commit 9c595541b3803664359a19379a2cef9b1c2cdc65
Subproject commit f9430771644d21b2c7c350ab0cf46e27987e91f3