mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
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:
parent
c792fb9d0d
commit
599bbedd8c
@ -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
|
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 getTime();
|
||||||
time_t getTimestamp();
|
time_t getTimestamp();
|
||||||
|
void terminate(int);
|
||||||
|
|
||||||
// The PROTOCOL_VERSION definition is defined by the build system.
|
// The PROTOCOL_VERSION definition is defined by the build system.
|
||||||
#if !defined(PROTOCOL_VERSION)
|
#if !defined(PROTOCOL_VERSION)
|
||||||
|
@ -45,7 +45,6 @@ bool runCmd(std::string full, CNSocket* sock) {
|
|||||||
void helpCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
void helpCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
ChatManager::sendServerMessage(sock, "Commands available to you");
|
ChatManager::sendServerMessage(sock, "Commands available to you");
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
int i = 1;
|
|
||||||
|
|
||||||
for (auto& cmd : ChatManager::commands) {
|
for (auto& cmd : ChatManager::commands) {
|
||||||
if (cmd.second.requiredAccLevel >= plr->accountId)
|
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) {
|
for (auto& pair : MobManager::Mobs) {
|
||||||
pair.second->state = MobState::RETREAT;
|
pair.second->state = MobState::RETREAT;
|
||||||
pair.second->target = nullptr;
|
pair.second->target = nullptr;
|
||||||
|
|
||||||
|
pair.second->roamX = pair.second->spawnX;
|
||||||
|
pair.second->roamY = pair.second->spawnY;
|
||||||
|
pair.second->roamZ = pair.second->spawnZ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +210,10 @@ int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) {
|
|||||||
mob->state = MobState::COMBAT;
|
mob->state = MobState::COMBAT;
|
||||||
mob->nextMovement = getTime();
|
mob->nextMovement = getTime();
|
||||||
mob->nextAttack = 0;
|
mob->nextAttack = 0;
|
||||||
|
|
||||||
|
mob->roamX = mob->appearanceData.iX;
|
||||||
|
mob->roamY = mob->appearanceData.iY;
|
||||||
|
mob->roamZ = mob->appearanceData.iZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
mob->appearanceData.iHP -= damage;
|
mob->appearanceData.iHP -= damage;
|
||||||
@ -254,7 +258,29 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delay the despawn animation
|
||||||
mob->despawned = false;
|
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) {
|
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->appearanceData.iHP = mob->maxHealth;
|
||||||
mob->state = MobState::ROAMING;
|
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);
|
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
|
||||||
|
|
||||||
pkt.NPCAppearanceData = mob->appearanceData;
|
pkt.NPCAppearanceData = mob->appearanceData;
|
||||||
@ -360,7 +391,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retreat if the player leaves combat range
|
// 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"]) {
|
if (distance >= mob->data["m_iCombatRange"]) {
|
||||||
mob->target = nullptr;
|
mob->target = nullptr;
|
||||||
mob->state = MobState::RETREAT;
|
mob->state = MobState::RETREAT;
|
||||||
@ -390,14 +421,18 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) {
|
|||||||
if (mob->idleRange == 0)
|
if (mob->idleRange == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// no random roaming if the mob already has a set path
|
||||||
|
if (mob->staticPath)
|
||||||
|
return;
|
||||||
|
|
||||||
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
|
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
|
||||||
return;
|
return;
|
||||||
incNextMovement(mob, currTime);
|
incNextMovement(mob, currTime);
|
||||||
|
/*
|
||||||
// only calculate a new route if we're not already on one
|
* mob->nextMovement is also updated whenever the path queue is traversed in
|
||||||
auto it = TransportManager::NPCQueues.find(mob->appearanceData.iNPC_ID);
|
* TransportManager::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
|
||||||
if (it != TransportManager::NPCQueues.end() && it->second.empty())
|
* so we don't have to check if there's already entries in the queue since we know there won't be.
|
||||||
return;
|
*/
|
||||||
|
|
||||||
int xStart = mob->spawnX - mob->idleRange/2;
|
int xStart = mob->spawnX - mob->idleRange/2;
|
||||||
int yStart = mob->spawnY - 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;
|
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 > mob->data["m_iIdleRange"]) {
|
||||||
if (distance > 10) {
|
if (distance > 10) {
|
||||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
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.iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||||
pkt.iSpeed = (int)mob->data["m_iRunSpeed"] * 3;
|
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)
|
if (attackPower + defensePower * 2 == 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
// base calculation
|
||||||
int damage = attackPower * attackPower / (attackPower + defensePower);
|
int damage = attackPower * attackPower / (attackPower + defensePower);
|
||||||
damage = std::max(std::max(29, attackPower / 7), damage - defensePower * (12 + difficulty) / 65);
|
damage = std::max(std::max(29, attackPower / 7), damage - defensePower * (12 + difficulty) / 65);
|
||||||
damage = damage * (rand() % 40 + 80) / 100;
|
damage = damage * (rand() % 40 + 80) / 100;
|
||||||
|
|
||||||
|
// Adaptium/Blastons/Cosmix
|
||||||
if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) {
|
if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) {
|
||||||
if (attackerStyle < defenderStyle || attackerStyle - defenderStyle == 2)
|
if (attackerStyle < defenderStyle || attackerStyle - defenderStyle == 2)
|
||||||
damage = damage * 5 / 4;
|
damage = damage * 5 / 4;
|
||||||
@ -731,6 +768,7 @@ std::pair<int,int> MobManager::getDamage(int attackPower, int defensePower, bool
|
|||||||
damage = damage * 4 / 5;
|
damage = damage * 4 / 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// weapon boosts
|
||||||
if (batteryBoost)
|
if (batteryBoost)
|
||||||
damage = damage * 5 / 4;
|
damage = damage * 5 / 4;
|
||||||
|
|
||||||
@ -913,6 +951,11 @@ bool MobManager::aggroCheck(Mob *mob, time_t currTime) {
|
|||||||
mob->state = MobState::COMBAT;
|
mob->state = MobState::COMBAT;
|
||||||
mob->nextMovement = currTime;
|
mob->nextMovement = currTime;
|
||||||
mob->nextAttack = 0;
|
mob->nextAttack = 0;
|
||||||
|
|
||||||
|
mob->roamX = mob->appearanceData.iX;
|
||||||
|
mob->roamY = mob->appearanceData.iY;
|
||||||
|
mob->roamZ = mob->appearanceData.iZ;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ struct Mob : public BaseNPC {
|
|||||||
// combat
|
// combat
|
||||||
CNSocket *target = nullptr;
|
CNSocket *target = nullptr;
|
||||||
time_t nextAttack = 0;
|
time_t nextAttack = 0;
|
||||||
|
int roamX, roamY, roamZ;
|
||||||
|
|
||||||
// temporary; until we're sure what's what
|
// temporary; until we're sure what's what
|
||||||
nlohmann::json data;
|
nlohmann::json data;
|
||||||
@ -57,9 +58,9 @@ struct Mob : public BaseNPC {
|
|||||||
if (regenTime >= 300000000)
|
if (regenTime >= 300000000)
|
||||||
regenTime = 1500;
|
regenTime = 1500;
|
||||||
|
|
||||||
spawnX = appearanceData.iX;
|
roamX = spawnX = appearanceData.iX;
|
||||||
spawnY = appearanceData.iY;
|
roamY = spawnY = appearanceData.iY;
|
||||||
spawnZ = appearanceData.iZ;
|
roamZ = spawnZ = appearanceData.iZ;
|
||||||
|
|
||||||
appearanceData.iConditionBitFlag = 0;
|
appearanceData.iConditionBitFlag = 0;
|
||||||
|
|
||||||
|
@ -44,8 +44,6 @@ void TableData::init() {
|
|||||||
std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
|
std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPaths(&nextId); // load paths
|
|
||||||
|
|
||||||
// load everything else from xdttable
|
// load everything else from xdttable
|
||||||
std::cout << "[INFO] Parsing xdt.json..." << std::endl;
|
std::cout << "[INFO] Parsing xdt.json..." << std::endl;
|
||||||
std::ifstream infile(settings::XDTJSON);
|
std::ifstream infile(settings::XDTJSON);
|
||||||
@ -198,6 +196,8 @@ void TableData::init() {
|
|||||||
std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl;
|
std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadPaths(&nextId); // load paths
|
||||||
|
|
||||||
loadGruntwork(&nextId);
|
loadGruntwork(&nextId);
|
||||||
|
|
||||||
NPCManager::nextId = nextId;
|
NPCManager::nextId = nextId;
|
||||||
@ -264,6 +264,27 @@ void TableData::loadPaths(int* nextId) {
|
|||||||
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
|
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
|
||||||
constructPathNPC(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;
|
std::cout << "[INFO] Loaded " << TransportManager::NPCQueues.size() << " NPC paths" << std::endl;
|
||||||
}
|
}
|
||||||
catch (const std::exception& err) {
|
catch (const std::exception& err) {
|
||||||
@ -319,7 +340,7 @@ void TableData::constructPathSlider(nlohmann::json points, int rotations, int sl
|
|||||||
TransportManager::NPCQueues[sliderID] = route;
|
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();
|
auto pathData = _pathData.value();
|
||||||
// Interpolate
|
// Interpolate
|
||||||
nlohmann::json pathPoints = pathData["points"];
|
nlohmann::json pathPoints = pathData["points"];
|
||||||
@ -337,7 +358,11 @@ void TableData::constructPathNPC(nlohmann::json::iterator _pathData) {
|
|||||||
from = to; // update point A
|
from = to; // update point A
|
||||||
stopTime = point["stop"];
|
stopTime = point["stop"];
|
||||||
}
|
}
|
||||||
TransportManager::NPCQueues[pathData["iNPCID"]] = points;
|
|
||||||
|
if (id == 0)
|
||||||
|
id = pathData["iNPCID"];
|
||||||
|
|
||||||
|
TransportManager::NPCQueues[id] = points;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load gruntwork output; if it exists
|
// load gruntwork output; if it exists
|
||||||
|
@ -18,5 +18,5 @@ namespace TableData {
|
|||||||
void loadPaths(int*);
|
void loadPaths(int*);
|
||||||
void constructPathSkyway(nlohmann::json::iterator);
|
void constructPathSkyway(nlohmann::json::iterator);
|
||||||
void constructPathSlider(nlohmann::json, int, int);
|
void constructPathSlider(nlohmann::json, int, int);
|
||||||
void constructPathNPC(nlohmann::json::iterator);
|
void constructPathNPC(nlohmann::json::iterator, int id=0);
|
||||||
}
|
}
|
||||||
|
@ -282,6 +282,12 @@ void TransportManager::stepNPCPathing() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip if not simulating mobs
|
||||||
|
if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) {
|
||||||
|
it++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// do not roam if not roaming
|
// do not roam if not roaming
|
||||||
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
|
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
|
||||||
it++;
|
it++;
|
||||||
|
2
tdata
2
tdata
@ -1 +1 @@
|
|||||||
Subproject commit 9c595541b3803664359a19379a2cef9b1c2cdc65
|
Subproject commit f9430771644d21b2c7c350ab0cf46e27987e91f3
|
Loading…
Reference in New Issue
Block a user