mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-12-22 19:30:05 +00:00
[refactor] Split MobManager.cpp into MobAI.cpp and Combat.cpp
This is terrible. It was a mistake to do this before cleaning up the actual code. It might be better not to use this commit and to do this refactor in a different order or something.
This commit is contained in:
parent
ae279100d7
commit
2024fb4969
6
Makefile
6
Makefile
@ -39,7 +39,8 @@ CXXSRC=\
|
||||
src/Defines.cpp\
|
||||
src/main.cpp\
|
||||
src/MissionManager.cpp\
|
||||
src/MobManager.cpp\
|
||||
src/MobAI.cpp\
|
||||
src/Combat.cpp\
|
||||
src/NanoManager.cpp\
|
||||
src/Abilities.cpp\
|
||||
src/ItemManager.cpp\
|
||||
@ -82,7 +83,8 @@ CXXHDR=\
|
||||
vendor/INIReader.hpp\
|
||||
vendor/JSON.hpp\
|
||||
src/MissionManager.hpp\
|
||||
src/MobManager.hpp\
|
||||
src/MobAI.hpp\
|
||||
src/Combat.hpp\
|
||||
src/NanoManager.hpp\
|
||||
src/Abilities.hpp\
|
||||
src/ItemManager.hpp\
|
||||
|
@ -136,13 +136,13 @@ int NanoManager::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int3
|
||||
namespace NanoManager {
|
||||
|
||||
bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
||||
if (MobManager::Mobs.find(targetID) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(targetID) == MobAI::Mobs.end()) {
|
||||
std::cout << "[WARN] doDebuff: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* mob = MobManager::Mobs[targetID];
|
||||
MobManager::hitMob(sock, mob, 0);
|
||||
Mob* mob = MobAI::Mobs[targetID];
|
||||
Combat::hitMob(sock, mob, 0);
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iID = mob->appearanceData.iNPC_ID;
|
||||
@ -200,15 +200,15 @@ bool doBuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID
|
||||
}
|
||||
|
||||
bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
||||
if (MobManager::Mobs.find(targetID) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(targetID) == MobAI::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] doDamageNDebuff: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* mob = MobManager::Mobs[targetID];
|
||||
Mob* mob = MobAI::Mobs[targetID];
|
||||
|
||||
MobManager::hitMob(sock, mob, 0); // just to gain aggro
|
||||
Combat::hitMob(sock, mob, 0); // just to gain aggro
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iDamage = duration / 10;
|
||||
@ -262,16 +262,16 @@ bool doHeal(CNSocket *sock, sSkillResult_Heal_HP *respdata, int i, int32_t targe
|
||||
}
|
||||
|
||||
bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
||||
if (MobManager::Mobs.find(targetID) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(targetID) == MobAI::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] doDamage: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Mob* mob = MobManager::Mobs[targetID];
|
||||
Mob* mob = MobAI::Mobs[targetID];
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, std::max(PC_MAXHEALTH(plr->level) * amount / 1000, mob->maxHealth * amount / 1000));
|
||||
int damage = Combat::hitMob(sock, mob, std::max(PC_MAXHEALTH(plr->level) * amount / 1000, mob->maxHealth * amount / 1000));
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iDamage = damage;
|
||||
@ -312,14 +312,14 @@ bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targ
|
||||
healdata->iHP = plr->HP;
|
||||
healdata->iHealHP = healedAmount;
|
||||
|
||||
if (MobManager::Mobs.find(targetID) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(targetID) == MobAI::Mobs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] doLeech: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
Mob* mob = MobManager::Mobs[targetID];
|
||||
Mob* mob = MobAI::Mobs[targetID];
|
||||
|
||||
int damage = MobManager::hitMob(sock, mob, amount * 2);
|
||||
int damage = Combat::hitMob(sock, mob, amount * 2);
|
||||
|
||||
damagedata->eCT = 4;
|
||||
damagedata->iDamage = damage;
|
||||
@ -481,7 +481,7 @@ std::vector<NanoPower> NanoPowers = {
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Mob Powers
|
||||
namespace MobManager {
|
||||
namespace Combat {
|
||||
bool doDamageNDebuff(Mob *mob, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
||||
CNSocket *sock = nullptr;
|
||||
Player *plr = nullptr;
|
||||
@ -528,10 +528,10 @@ bool doDamageNDebuff(Mob *mob, sSkillResult_Damage_N_Debuff *respdata, int i, in
|
||||
if (plr->HP <= 0) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!aggroCheck(mob, getTime())) {
|
||||
clearDebuff(mob);
|
||||
if (!MobAI::aggroCheck(mob, getTime())) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
groupRetreat(mob);
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,12 +539,12 @@ bool doDamageNDebuff(Mob *mob, sSkillResult_Damage_N_Debuff *respdata, int i, in
|
||||
}
|
||||
|
||||
bool doHeal(Mob *mob, sSkillResult_Heal_HP *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
||||
if (MobManager::Mobs.find(targetID) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(targetID) == MobAI::Mobs.end()) {
|
||||
std::cout << "[WARN] doDebuff: mob ID not found" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Mob* targetMob = MobManager::Mobs[targetID];
|
||||
Mob* targetMob = MobAI::Mobs[targetID];
|
||||
|
||||
int healedAmount = amount * targetMob->maxHealth / 1000;
|
||||
targetMob->appearanceData.iHP += healedAmount;
|
||||
@ -602,10 +602,10 @@ bool doDamage(Mob *mob, sSkillResult_Damage *respdata, int i, int32_t targetID,
|
||||
if (plr->HP <= 0) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!aggroCheck(mob, getTime())) {
|
||||
clearDebuff(mob);
|
||||
if (!MobAI::aggroCheck(mob, getTime())) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
groupRetreat(mob);
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
}
|
||||
|
||||
@ -660,10 +660,10 @@ bool doLeech(Mob *mob, sSkillResult_Heal_HP *healdata, int i, int32_t targetID,
|
||||
if (plr->HP <= 0) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!aggroCheck(mob, getTime())) {
|
||||
clearDebuff(mob);
|
||||
if (!MobAI::aggroCheck(mob, getTime())) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
groupRetreat(mob);
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
|
||||
typedef void (*PowerHandler)(CNSocket*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t);
|
||||
|
||||
@ -57,6 +57,6 @@ namespace NanoManager {
|
||||
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags);
|
||||
}
|
||||
|
||||
namespace MobManager {
|
||||
namespace Combat {
|
||||
extern std::vector<MobPower> MobPowers;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "CNStructs.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "Database.hpp"
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
|
||||
std::map<ChunkPos, Chunk*> ChunkManager::chunks;
|
||||
|
||||
@ -465,7 +465,7 @@ void ChunkManager::createInstance(uint64_t instanceID) {
|
||||
Mob* newMob = new Mob(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
|
||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId++);
|
||||
NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob;
|
||||
MobManager::Mobs[newMob->appearanceData.iNPC_ID] = newMob;
|
||||
MobAI::Mobs[newMob->appearanceData.iNPC_ID] = newMob;
|
||||
|
||||
// if in a group, copy over group members as well
|
||||
if (((Mob*)baseNPC)->groupLeader != 0) {
|
||||
@ -480,7 +480,7 @@ void ChunkManager::createInstance(uint64_t instanceID) {
|
||||
instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID);
|
||||
// add follower to NPC maps
|
||||
NPCManager::NPCs[followerID] = newMobFollower;
|
||||
MobManager::Mobs[followerID] = newMobFollower;
|
||||
MobAI::Mobs[followerID] = newMobFollower;
|
||||
// set follower-specific properties
|
||||
newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID;
|
||||
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
||||
|
File diff suppressed because it is too large
Load Diff
65
src/Combat.hpp
Normal file
65
src/Combat.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "NPC.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
struct MobDropChance {
|
||||
int dropChance;
|
||||
std::vector<int> cratesRatio;
|
||||
};
|
||||
|
||||
struct MobDrop {
|
||||
std::vector<int> crateIDs;
|
||||
int dropChanceType;
|
||||
int taros;
|
||||
int fm;
|
||||
int boosts;
|
||||
};
|
||||
|
||||
struct Bullet {
|
||||
int pointDamage;
|
||||
int groupDamage;
|
||||
bool weaponBoost;
|
||||
int bulletType;
|
||||
};
|
||||
|
||||
namespace Combat {
|
||||
extern std::map<int32_t, MobDropChance> MobDropChances;
|
||||
extern std::map<int32_t, MobDrop> MobDrops;
|
||||
extern std::map<int32_t, std::map<int8_t, Bullet>> Bullets;
|
||||
|
||||
void init();
|
||||
void playerTick(CNServer*, time_t);
|
||||
|
||||
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
|
||||
void combatBegin(CNSocket *sock, CNPacketData *data);
|
||||
void combatEnd(CNSocket *sock, CNPacketData *data);
|
||||
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
|
||||
void dealGooDamage(CNSocket *sock, int amount);
|
||||
|
||||
void npcAttackPc(Mob *mob, time_t currTime);
|
||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
||||
void killMob(CNSocket *sock, Mob *mob);
|
||||
void giveReward(CNSocket *sock, Mob *mob, int rolledBoosts, int rolledPotions, int rolledCrate, int rolledCrateType, int rolledEvent);
|
||||
void getReward(sItemBase *reward, MobDrop *drop, MobDropChance *chance, int rolled);
|
||||
void giveEventReward(CNSocket* sock, Player* player, int rolled);
|
||||
|
||||
std::pair<int,int> lerp(int, int, int, int, int);
|
||||
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);
|
||||
|
||||
void pcAttackChars(CNSocket *sock, CNPacketData *data);
|
||||
void grenadeFire(CNSocket* sock, CNPacketData* data);
|
||||
void rocketFire(CNSocket* sock, CNPacketData* data);
|
||||
void projectileHit(CNSocket* sock, CNPacketData* data);
|
||||
/// returns bullet id
|
||||
int8_t addBullet(Player* plr, bool isGrenade);
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
#include "PlayerManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
@ -269,18 +269,18 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
return;
|
||||
}
|
||||
|
||||
if (MobManager::Mobs.find(npc->appearanceData.iNPC_ID) != MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(npc->appearanceData.iNPC_ID) != MobAI::Mobs.end()) {
|
||||
int leadId = ((Mob*)npc)->groupLeader;
|
||||
if (leadId != 0) {
|
||||
if (MobManager::Mobs.find(leadId) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(leadId) == MobAI::Mobs.end()) {
|
||||
std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
|
||||
}
|
||||
Mob* leadNpc = MobManager::Mobs[leadId];
|
||||
Mob* leadNpc = MobAI::Mobs[leadId];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (leadNpc->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (MobManager::Mobs.find(leadNpc->groupMember[i]) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(leadNpc->groupMember[i]) == MobAI::Mobs.end()) {
|
||||
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
@ -303,13 +303,13 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
}
|
||||
|
||||
static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
MobManager::simulateMobs = !MobManager::simulateMobs;
|
||||
MobAI::simulateMobs = !MobAI::simulateMobs;
|
||||
|
||||
if (MobManager::simulateMobs)
|
||||
if (MobAI::simulateMobs)
|
||||
return;
|
||||
|
||||
// return all mobs to their spawn points
|
||||
for (auto& pair : MobManager::Mobs) {
|
||||
for (auto& pair : MobAI::Mobs) {
|
||||
pair.second->state = MobState::RETREAT;
|
||||
pair.second->target = nullptr;
|
||||
pair.second->nextMovement = getTime();
|
||||
@ -596,7 +596,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
|
||||
if (team == 2 && i > 0) {
|
||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
||||
Mob* mob = MobManager::Mobs[npc->appearanceData.iNPC_ID];
|
||||
Mob* mob = MobAI::Mobs[npc->appearanceData.iNPC_ID];
|
||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
||||
mob->offsetX = x - plr->x;
|
||||
mob->offsetY = y - plr->y;
|
||||
@ -611,7 +611,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
|
||||
if (team == 2 && i > 0) {
|
||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
||||
Mob* mob = MobManager::Mobs[npc->appearanceData.iNPC_ID];
|
||||
Mob* mob = MobAI::Mobs[npc->appearanceData.iNPC_ID];
|
||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
||||
mob->offsetX = x - plr->x;
|
||||
mob->offsetY = y - plr->y;
|
||||
@ -626,7 +626,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
|
||||
if (i == 0 && team == 2) {
|
||||
type = type2;
|
||||
leadNpc = MobManager::Mobs[npc->appearanceData.iNPC_ID];
|
||||
leadNpc = MobAI::Mobs[npc->appearanceData.iNPC_ID];
|
||||
leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
||||
}
|
||||
}
|
||||
|
805
src/MobAI.cpp
Normal file
805
src/MobAI.cpp
Normal file
@ -0,0 +1,805 @@
|
||||
#include "MobAI.hpp"
|
||||
#include "Player.hpp"
|
||||
#include "RacingManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits.h>
|
||||
|
||||
std::map<int32_t, Mob*> MobAI::Mobs;
|
||||
static std::queue<int32_t> RemovalQueue;
|
||||
|
||||
bool MobAI::simulateMobs;
|
||||
|
||||
static void roamingStep(Mob *mob, time_t currTime);
|
||||
|
||||
/*
|
||||
* Dynamic lerp; distinct from TransportManager::lerp(). This one doesn't care about height and
|
||||
* only returns the first step, since the rest will need to be recalculated anyway if chasing player.
|
||||
*/
|
||||
static std::pair<int,int> lerp(int x1, int y1, int x2, int y2, int speed) {
|
||||
std::pair<int,int> ret = {x1, y1};
|
||||
|
||||
if (speed == 0)
|
||||
return ret;
|
||||
|
||||
int distance = hypot(x1 - x2, y1 - y2);
|
||||
|
||||
if (distance > speed) {
|
||||
|
||||
int lerps = distance / speed;
|
||||
|
||||
// interpolate only the first point
|
||||
float frac = 1.0f / lerps;
|
||||
|
||||
ret.first = (x1 + (x2 - x1) * frac);
|
||||
ret.second = (y1 + (y2 - y1) * frac);
|
||||
} else {
|
||||
ret.first = x2;
|
||||
ret.second = y2;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MobAI::clearDebuff(Mob *mob) {
|
||||
mob->skillStyle = -1;
|
||||
mob->appearanceData.iConditionBitFlag = 0;
|
||||
mob->unbuffTimes.clear();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
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));
|
||||
}
|
||||
|
||||
void MobAI::followToCombat(Mob *mob) {
|
||||
if (Mobs.find(mob->groupLeader) != Mobs.end()) {
|
||||
Mob* leadMob = Mobs[mob->groupLeader];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (leadMob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (Mobs.find(leadMob->groupMember[i]) == Mobs.end()) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
Mob* followerMob = Mobs[leadMob->groupMember[i]];
|
||||
|
||||
if (followerMob->state != MobState::ROAMING) // only roaming mobs should transition to combat
|
||||
continue;
|
||||
|
||||
enterCombat(mob->target, followerMob);
|
||||
}
|
||||
|
||||
if (leadMob->state != MobState::ROAMING)
|
||||
return;
|
||||
|
||||
enterCombat(mob->target, leadMob);
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::groupRetreat(Mob *mob) {
|
||||
if (Mobs.find(mob->groupLeader) == Mobs.end())
|
||||
return;
|
||||
|
||||
Mob* leadMob = Mobs[mob->groupLeader];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (leadMob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (Mobs.find(leadMob->groupMember[i]) == Mobs.end()) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
Mob* followerMob = Mobs[leadMob->groupMember[i]];
|
||||
|
||||
followerMob->target = nullptr;
|
||||
followerMob->state = MobState::RETREAT;
|
||||
clearDebuff(followerMob);
|
||||
}
|
||||
|
||||
leadMob->target = nullptr;
|
||||
leadMob->state = MobState::RETREAT;
|
||||
clearDebuff(leadMob);
|
||||
}
|
||||
|
||||
/*
|
||||
* Aggro on nearby players.
|
||||
* Even if they're in range, we can't assume they're all in the same one chunk
|
||||
* as the mob, since it might be near a chunk boundary.
|
||||
*/
|
||||
bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
||||
CNSocket *closest = nullptr;
|
||||
int closestDistance = INT_MAX;
|
||||
|
||||
for (auto it = mob->viewableChunks->begin(); it != mob->viewableChunks->end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (CNSocket *s : chunk->players) {
|
||||
Player *plr = PlayerManager::getPlayer(s);
|
||||
|
||||
if (plr->HP <= 0)
|
||||
continue;
|
||||
|
||||
int mobRange = mob->sightRange;
|
||||
|
||||
if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH
|
||||
|| RacingManager::EPRaces.find(s) != RacingManager::EPRaces.end())
|
||||
mobRange /= 3;
|
||||
|
||||
// 0.33x - 1.66x the range
|
||||
int levelDifference = plr->level - mob->level;
|
||||
if (levelDifference > -10)
|
||||
mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3;
|
||||
|
||||
if (mob->state != MobState::ROAMING && plr->inCombat) // freshly out of aggro mobs
|
||||
mobRange = mob->sightRange * 2; // should not be impacted by the above
|
||||
|
||||
if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||
mobRange = -1;
|
||||
|
||||
// height is relevant for aggro distance because of platforming
|
||||
int xyDistance = hypot(mob->appearanceData.iX - plr->x, mob->appearanceData.iY - plr->y);
|
||||
int distance = hypot(xyDistance, (mob->appearanceData.iZ - plr->z) * 2); // difference in Z counts twice
|
||||
|
||||
if (distance > mobRange || distance > closestDistance)
|
||||
continue;
|
||||
|
||||
// found a player
|
||||
closest = s;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closest != nullptr) {
|
||||
// found closest player. engage.
|
||||
enterCombat(closest, mob);
|
||||
|
||||
if (mob->groupLeader != 0)
|
||||
followToCombat(mob);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int style) {
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult);
|
||||
|
||||
// validate response packet
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT), targetData[0], sizeof(sCAttackResult))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_CORRUPTION_HIT packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf;
|
||||
sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT));
|
||||
|
||||
resp->iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
resp->iSkillID = skillID;
|
||||
resp->iStyle = style;
|
||||
resp->iValue1 = plr->x;
|
||||
resp->iValue2 = plr->y;
|
||||
resp->iValue3 = plr->z;
|
||||
resp->iTargetCnt = targetData[0];
|
||||
|
||||
for (int i = 0; i < targetData[0]; i++) {
|
||||
CNSocket *sock = nullptr;
|
||||
Player *plr = nullptr;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second->iID == targetData[i+1]) {
|
||||
sock = pair.first;
|
||||
plr = pair.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// player not found
|
||||
if (plr == nullptr) {
|
||||
std::cout << "[WARN] dealCorruption: player ID not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
respdata[i].eCT = 1;
|
||||
respdata[i].iID = plr->iID;
|
||||
respdata[i].bProtected = 0;
|
||||
|
||||
respdata[i].iActiveNanoSlotNum = -1;
|
||||
for (int n = 0; n < 3; n++)
|
||||
if (plr->activeNano == plr->equippedNanos[n])
|
||||
respdata[i].iActiveNanoSlotNum = n;
|
||||
respdata[i].iNanoID = plr->activeNano;
|
||||
|
||||
int style2 = NanoManager::nanoStyle(plr->activeNano);
|
||||
if (style2 == -1) { // no nano
|
||||
respdata[i].iHitFlag = 8;
|
||||
respdata[i].iDamage = NanoManager::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
} else if (style == style2) {
|
||||
respdata[i].iHitFlag = 8; // tie
|
||||
respdata[i].iDamage = 0;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
} else if (style - style2 == 1 || style2 - style == 2) {
|
||||
respdata[i].iHitFlag = 4; // win
|
||||
respdata[i].iDamage = 0;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
||||
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
|
||||
// fire damage power disguised as a corruption attack back at the enemy
|
||||
std::vector<int> targetData2 = {1, mob->appearanceData.iNPC_ID, 0, 0, 0};
|
||||
for (auto& pwr : NanoManager::NanoPowers)
|
||||
if (pwr.skillType == EST_DAMAGE)
|
||||
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);
|
||||
} else {
|
||||
respdata[i].iHitFlag = 16; // lose
|
||||
respdata[i].iDamage = NanoManager::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90;
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0) {
|
||||
respdata[i].bNanoDeactive = 1;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||
plr->HP -= respdata[i].iDamage;
|
||||
|
||||
respdata[i].iHP = plr->HP;
|
||||
respdata[i].iConditionBitFlag = plr->iConditionBitFlag;
|
||||
|
||||
if (plr->HP <= 0) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!MobAI::aggroCheck(mob, getTime())) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_NPC_SKILL_CORRUPTION_HIT, resplen);
|
||||
}
|
||||
|
||||
static void useAbilities(Mob *mob, time_t currTime) {
|
||||
/*
|
||||
* targetData approach
|
||||
* first integer is the count
|
||||
* second to fifth integers are IDs, these can be either player iID or mob's iID
|
||||
* whether the skill targets players or mobs is determined by the skill packet being fired
|
||||
*/
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
if (mob->skillStyle >= 0) { // corruption hit
|
||||
int skillID = (int)mob->data["m_iCorruptionType"];
|
||||
std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||
int temp = mob->skillStyle;
|
||||
mob->skillStyle = -3; // corruption cooldown
|
||||
mob->nextAttack = currTime + 1000;
|
||||
dealCorruption(mob, targetData, skillID, temp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mob->skillStyle == -2) { // eruption hit
|
||||
int skillID = (int)mob->data["m_iMegaType"];
|
||||
std::vector<int> targetData = {0, 0, 0, 0, 0};
|
||||
|
||||
// find the players within range of eruption
|
||||
for (auto it = mob->viewableChunks->begin(); it != mob->viewableChunks->end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (CNSocket *s : chunk->players) {
|
||||
Player *plr = PlayerManager::getPlayer(s);
|
||||
|
||||
if (plr->HP <= 0)
|
||||
continue;
|
||||
|
||||
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
||||
if (distance < NanoManager::SkillTable[skillID].effectArea) {
|
||||
targetData[0] += 1;
|
||||
targetData[targetData[0]] = plr->iID;
|
||||
if (targetData[0] > 3) // make sure not to have more than 4
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& pwr : Combat::MobPowers)
|
||||
if (pwr.skillType == NanoManager::SkillTable[skillID].skillType)
|
||||
pwr.handle(mob, targetData, skillID, NanoManager::SkillTable[skillID].durationTime[0], NanoManager::SkillTable[skillID].powerIntensity[0]);
|
||||
mob->skillStyle = -3; // eruption cooldown
|
||||
mob->nextAttack = currTime + 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mob->skillStyle == -3) { // cooldown expires
|
||||
mob->skillStyle = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
int random = 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
|
||||
|
||||
if (random < prob1) { // active skill hit
|
||||
int skillID = (int)mob->data["m_iActiveSkill1"];
|
||||
std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||
for (auto& pwr : Combat::MobPowers)
|
||||
if (pwr.skillType == NanoManager::SkillTable[skillID].skillType) {
|
||||
if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
|
||||
return; // prevent debuffing a player twice
|
||||
pwr.handle(mob, targetData, skillID, NanoManager::SkillTable[skillID].durationTime[0], NanoManager::SkillTable[skillID].powerIntensity[0]);
|
||||
}
|
||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||
return;
|
||||
}
|
||||
|
||||
if (random < prob1 + prob2) { // corruption windup
|
||||
int skillID = (int)mob->data["m_iCorruptionType"];
|
||||
INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt);
|
||||
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
pkt.iSkillID = skillID;
|
||||
pkt.iValue1 = plr->x;
|
||||
pkt.iValue2 = plr->y;
|
||||
pkt.iValue3 = plr->z;
|
||||
mob->skillStyle = NanoManager::nanoStyle(plr->activeNano) - 1;
|
||||
if (mob->skillStyle == -1)
|
||||
mob->skillStyle = 2;
|
||||
if (mob->skillStyle == -2)
|
||||
mob->skillStyle = 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;
|
||||
return;
|
||||
}
|
||||
|
||||
if (random < prob1 + prob2 + prob3) { // eruption windup
|
||||
int skillID = (int)mob->data["m_iMegaType"];
|
||||
INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt);
|
||||
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
pkt.iSkillID = skillID;
|
||||
pkt.iValue1 = mob->hitX = plr->x;
|
||||
pkt.iValue2 = mob->hitY = plr->y;
|
||||
pkt.iValue3 = mob->hitZ = plr->z;
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_SKILL_READY, sizeof(sP_FE2CL_NPC_SKILL_READY));
|
||||
mob->nextAttack = currTime + 1800;
|
||||
mob->skillStyle = -2;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void MobAI::enterCombat(CNSocket *sock, Mob *mob) {
|
||||
mob->target = sock;
|
||||
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;
|
||||
|
||||
int skillID = (int)mob->data["m_iPassiveBuff"]; // cast passive
|
||||
std::vector<int> targetData = {1, mob->appearanceData.iNPC_ID, 0, 0, 0};
|
||||
for (auto& pwr : Combat::MobPowers)
|
||||
if (pwr.skillType == NanoManager::SkillTable[skillID].skillType)
|
||||
pwr.handle(mob, targetData, skillID, NanoManager::SkillTable[skillID].durationTime[0], NanoManager::SkillTable[skillID].powerIntensity[0]);
|
||||
|
||||
for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT
|
||||
if (event.trigger == ON_COMBAT && event.npcType == mob->appearanceData.iNPCType)
|
||||
event.handler(sock, mob);
|
||||
}
|
||||
|
||||
static void drainMobHP(Mob *mob, int amount) {
|
||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
|
||||
pkt->iID = mob->appearanceData.iNPC_ID;
|
||||
pkt->eCT = 4; // mob
|
||||
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
||||
|
||||
drain->eCT = 4;
|
||||
drain->iID = mob->appearanceData.iNPC_ID;
|
||||
drain->iDamage = amount;
|
||||
drain->iHP = mob->appearanceData.iHP -= amount;
|
||||
|
||||
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||
|
||||
if (mob->appearanceData.iHP <= 0)
|
||||
Combat::killMob(mob->target, mob);
|
||||
}
|
||||
|
||||
static void deadStep(Mob *mob, time_t currTime) {
|
||||
// despawn the mob after a short delay
|
||||
if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) {
|
||||
mob->despawned = true;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||
|
||||
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
|
||||
// if it was summoned, mark it for removal
|
||||
if (mob->summoned) {
|
||||
std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl;
|
||||
RemovalQueue.push(mob->appearanceData.iNPC_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
// pre-set spawn coordinates if not marked for removal
|
||||
mob->appearanceData.iX = mob->spawnX;
|
||||
mob->appearanceData.iY = mob->spawnY;
|
||||
mob->appearanceData.iZ = mob->spawnZ;
|
||||
}
|
||||
|
||||
// to guide their groupmates, group leaders still need to move despite being dead
|
||||
if (mob->groupLeader == mob->appearanceData.iNPC_ID)
|
||||
roamingStep(mob, currTime);
|
||||
|
||||
if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100)
|
||||
return;
|
||||
|
||||
std::cout << "respawning mob " << mob->appearanceData.iNPC_ID << " with HP = " << mob->maxHealth << std::endl;
|
||||
|
||||
mob->appearanceData.iHP = mob->maxHealth;
|
||||
mob->state = MobState::ROAMING;
|
||||
|
||||
// if mob is a group leader/follower, spawn where the group is.
|
||||
if (mob->groupLeader != 0) {
|
||||
if (MobAI::Mobs.find(mob->groupLeader) != MobAI::Mobs.end()) {
|
||||
Mob* leaderMob = MobAI::Mobs[mob->groupLeader];
|
||||
mob->appearanceData.iX = leaderMob->appearanceData.iX + mob->offsetX;
|
||||
mob->appearanceData.iY = leaderMob->appearanceData.iY + mob->offsetY;
|
||||
mob->appearanceData.iZ = leaderMob->appearanceData.iZ;
|
||||
} else {
|
||||
std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
|
||||
|
||||
pkt.NPCAppearanceData = mob->appearanceData;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
|
||||
}
|
||||
|
||||
static void combatStep(Mob *mob, time_t currTime) {
|
||||
assert(mob->target != nullptr);
|
||||
|
||||
// lose aggro if the player lost connection
|
||||
if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!MobAI::aggroCheck(mob, currTime)) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
// lose aggro if the player became invulnerable or died
|
||||
if (plr->HP <= 0
|
||||
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
if (!MobAI::aggroCheck(mob, currTime)) {
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// drain
|
||||
if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000)
|
||||
&& mob->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) {
|
||||
drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second
|
||||
mob->lastDrainTime = currTime;
|
||||
}
|
||||
|
||||
// if drain killed the mob, return early
|
||||
if (mob->appearanceData.iHP <= 0)
|
||||
return;
|
||||
|
||||
// unbuffing
|
||||
std::unordered_map<int32_t, time_t>::iterator it = mob->unbuffTimes.begin();
|
||||
while (it != mob->unbuffTimes.end()) {
|
||||
|
||||
if (currTime >= it->second) {
|
||||
mob->appearanceData.iConditionBitFlag &= ~it->first;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
// skip attack if stunned or asleep
|
||||
if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
|
||||
mob->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||
return;
|
||||
}
|
||||
|
||||
int distance = hypot(plr->x - mob->appearanceData.iX, plr->y - mob->appearanceData.iY);
|
||||
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.
|
||||
useAbilities(mob, currTime);
|
||||
if (mob->target == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
int distanceToTravel = INT_MAX;
|
||||
int speed = mob->data["m_iRunSpeed"];
|
||||
// movement logic: move when out of range but don't move while casting a skill
|
||||
if (distance > mobRange && mob->skillStyle == -1) {
|
||||
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
|
||||
return;
|
||||
mob->nextMovement = currTime + 400;
|
||||
if (currTime >= mob->nextAttack)
|
||||
mob->nextAttack = 0;
|
||||
|
||||
// halve movement speed if snared
|
||||
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED)
|
||||
speed /= 2;
|
||||
|
||||
int targetX = plr->x;
|
||||
int targetY = plr->y;
|
||||
if (mob->groupLeader != 0) {
|
||||
targetX += mob->offsetX*distance/(mob->idleRange + 1);
|
||||
targetY += mob->offsetY*distance/(mob->idleRange + 1);
|
||||
}
|
||||
|
||||
distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
|
||||
auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, targetX, targetY, distanceToTravel);
|
||||
if (distanceToTravel < speed*2/5 && currTime >= mob->nextAttack)
|
||||
mob->nextAttack = 0;
|
||||
|
||||
NPCManager::updateNPCPosition(mob->appearanceData.iNPC_ID, targ.first, targ.second, mob->appearanceData.iZ, mob->instanceID, mob->appearanceData.iAngle);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||
|
||||
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
pkt.iSpeed = speed;
|
||||
pkt.iToX = mob->appearanceData.iX = targ.first;
|
||||
pkt.iToY = mob->appearanceData.iY = targ.second;
|
||||
pkt.iToZ = plr->z;
|
||||
pkt.iMoveStyle = 1;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
if (distance <= mobRange || distanceToTravel < speed*2/5) {
|
||||
if (mob->nextAttack == 0 || currTime >= mob->nextAttack) {
|
||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||
Combat::npcAttackPc(mob, currTime);
|
||||
}
|
||||
}
|
||||
|
||||
// retreat if the player leaves combat range
|
||||
int xyDistance = hypot(plr->x - mob->roamX, plr->y - mob->roamY);
|
||||
distance = hypot(xyDistance, plr->z - mob->roamZ);
|
||||
if (distance >= mob->data["m_iCombatRange"]) {
|
||||
mob->target = nullptr;
|
||||
mob->state = MobState::RETREAT;
|
||||
MobAI::clearDebuff(mob);
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::groupRetreat(mob);
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::incNextMovement(Mob *mob, time_t currTime) {
|
||||
if (currTime == 0)
|
||||
currTime = getTime();
|
||||
|
||||
int delay = (int)mob->data["m_iDelayTime"] * 1000;
|
||||
mob->nextMovement = currTime + delay/2 + rand() % (delay/2);
|
||||
}
|
||||
|
||||
static void roamingStep(Mob *mob, time_t currTime) {
|
||||
/*
|
||||
* We reuse nextAttack to avoid scanning for players all the time, but to still
|
||||
* do so more often than if we waited for nextMovement (which is way too slow).
|
||||
* In the case of group leaders, this step will be called by dead mobs, so disable attack.
|
||||
*/
|
||||
if (mob->state != MobState::DEAD && (mob->nextAttack == 0 || currTime >= mob->nextAttack)) {
|
||||
mob->nextAttack = currTime + 500;
|
||||
if (MobAI::aggroCheck(mob, currTime))
|
||||
return;
|
||||
}
|
||||
|
||||
// no random roaming if the mob already has a set path
|
||||
if (mob->staticPath)
|
||||
return;
|
||||
|
||||
if (mob->groupLeader != 0 && mob->groupLeader != mob->appearanceData.iNPC_ID) // don't roam by yourself without group leader
|
||||
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.
|
||||
*/
|
||||
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
|
||||
return;
|
||||
MobAI::incNextMovement(mob, currTime);
|
||||
|
||||
int xStart = mob->spawnX - mob->idleRange/2;
|
||||
int yStart = mob->spawnY - mob->idleRange/2;
|
||||
int speed = mob->data["m_iWalkSpeed"];
|
||||
|
||||
// some mobs don't move (and we mustn't divide/modulus by zero)
|
||||
if (mob->idleRange == 0 || speed == 0)
|
||||
return;
|
||||
|
||||
int farX, farY, distance;
|
||||
int minDistance = mob->idleRange / 2;
|
||||
|
||||
// pick a random destination
|
||||
farX = xStart + rand() % mob->idleRange;
|
||||
farY = yStart + rand() % mob->idleRange;
|
||||
|
||||
distance = std::abs(std::max(farX - mob->appearanceData.iX, farY - mob->appearanceData.iY));
|
||||
if (distance == 0)
|
||||
distance += 1; // hack to avoid FPE
|
||||
|
||||
// if it's too short a walk, go further in that direction
|
||||
farX = mob->appearanceData.iX + (farX - mob->appearanceData.iX) * minDistance / distance;
|
||||
farY = mob->appearanceData.iY + (farY - mob->appearanceData.iY) * minDistance / distance;
|
||||
|
||||
// but don't got out of bounds
|
||||
farX = std::clamp(farX, xStart, xStart + mob->idleRange);
|
||||
farY = std::clamp(farY, yStart, yStart + mob->idleRange);
|
||||
|
||||
// halve movement speed if snared
|
||||
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED)
|
||||
speed /= 2;
|
||||
|
||||
std::queue<WarpLocation> queue;
|
||||
WarpLocation from = { mob->appearanceData.iX, mob->appearanceData.iY, mob->appearanceData.iZ };
|
||||
WarpLocation to = { farX, farY, mob->appearanceData.iZ };
|
||||
|
||||
// add a route to the queue; to be processed in TransportManager::stepNPCPathing()
|
||||
TransportManager::lerp(&queue, from, to, speed);
|
||||
TransportManager::NPCQueues[mob->appearanceData.iNPC_ID] = queue;
|
||||
|
||||
if (mob->groupLeader != 0 && mob->groupLeader == mob->appearanceData.iNPC_ID) {
|
||||
// make followers follow this npc.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (mob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (MobAI::Mobs.find(mob->groupMember[i]) == MobAI::Mobs.end()) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::queue<WarpLocation> queue2;
|
||||
Mob* followerMob = MobAI::Mobs[mob->groupMember[i]];
|
||||
from = { followerMob->appearanceData.iX, followerMob->appearanceData.iY, followerMob->appearanceData.iZ };
|
||||
to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->appearanceData.iZ };
|
||||
TransportManager::lerp(&queue2, from, to, speed);
|
||||
TransportManager::NPCQueues[followerMob->appearanceData.iNPC_ID] = queue2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void retreatStep(Mob *mob, time_t currTime) {
|
||||
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
|
||||
return;
|
||||
|
||||
mob->nextMovement = currTime + 400;
|
||||
|
||||
// distance between spawn point and current location
|
||||
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->roamX, mob->roamY, (int)mob->data["m_iRunSpeed"]*4/5);
|
||||
|
||||
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
|
||||
pkt.iSpeed = (int)mob->data["m_iRunSpeed"] * 2;
|
||||
pkt.iToX = mob->appearanceData.iX = targ.first;
|
||||
pkt.iToY = mob->appearanceData.iY = targ.second;
|
||||
pkt.iToZ = mob->appearanceData.iZ = mob->spawnZ;
|
||||
pkt.iMoveStyle = 1;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
}
|
||||
|
||||
// if we got there
|
||||
//if (distance <= mob->data["m_iIdleRange"]) {
|
||||
if (distance <= 10) { // retreat back to the spawn point
|
||||
mob->state = MobState::ROAMING;
|
||||
mob->appearanceData.iHP = mob->maxHealth;
|
||||
mob->killedTime = 0;
|
||||
mob->nextAttack = 0;
|
||||
mob->appearanceData.iConditionBitFlag = 0;
|
||||
|
||||
// cast a return home heal spell, this is the right way(tm)
|
||||
std::vector<int> targetData = {1, 0, 0, 0, 0};
|
||||
for (auto& pwr : Combat::MobPowers)
|
||||
if (pwr.skillType == NanoManager::SkillTable[110].skillType)
|
||||
pwr.handle(mob, targetData, 110, NanoManager::SkillTable[110].durationTime[0], NanoManager::SkillTable[110].powerIntensity[0]);
|
||||
// clear outlying debuffs
|
||||
MobAI::clearDebuff(mob);
|
||||
}
|
||||
}
|
||||
|
||||
static void step(CNServer *serv, time_t currTime) {
|
||||
for (auto& pair : MobAI::Mobs) {
|
||||
if (pair.second->playersInView < 0)
|
||||
std::cout << "[WARN] Weird playerview value " << pair.second->playersInView << std::endl;
|
||||
|
||||
// skip mob movement and combat if disabled or not in view
|
||||
if ((!MobAI::simulateMobs || pair.second->playersInView == 0) && pair.second->state != MobState::DEAD
|
||||
&& pair.second->state != MobState::RETREAT)
|
||||
continue;
|
||||
|
||||
switch (pair.second->state) {
|
||||
case MobState::INACTIVE:
|
||||
// no-op
|
||||
break;
|
||||
case MobState::ROAMING:
|
||||
roamingStep(pair.second, currTime);
|
||||
break;
|
||||
case MobState::COMBAT:
|
||||
combatStep(pair.second, currTime);
|
||||
break;
|
||||
case MobState::RETREAT:
|
||||
retreatStep(pair.second, currTime);
|
||||
break;
|
||||
case MobState::DEAD:
|
||||
deadStep(pair.second, currTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// deallocate all NPCs queued for removal
|
||||
while (RemovalQueue.size() > 0) {
|
||||
NPCManager::destroyNPC(RemovalQueue.front());
|
||||
RemovalQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::init() {
|
||||
REGISTER_SHARD_TIMER(step, 200);
|
||||
|
||||
simulateMobs = settings::SIMULATEMOBS;
|
||||
}
|
@ -1,15 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CNProtocol.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "NPC.hpp"
|
||||
|
||||
#include "JSON.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include "NPCManager.hpp"
|
||||
|
||||
enum class MobState {
|
||||
INACTIVE,
|
||||
@ -102,74 +94,17 @@ struct Mob : public BaseNPC {
|
||||
}
|
||||
};
|
||||
|
||||
struct MobDropChance {
|
||||
int dropChance;
|
||||
std::vector<int> cratesRatio;
|
||||
};
|
||||
|
||||
struct MobDrop {
|
||||
std::vector<int> crateIDs;
|
||||
int dropChanceType;
|
||||
int taros;
|
||||
int fm;
|
||||
int boosts;
|
||||
};
|
||||
|
||||
struct Bullet {
|
||||
int pointDamage;
|
||||
int groupDamage;
|
||||
bool weaponBoost;
|
||||
int bulletType;
|
||||
};
|
||||
|
||||
namespace MobManager {
|
||||
extern std::map<int32_t, Mob*> Mobs;
|
||||
extern std::queue<int32_t> RemovalQueue;
|
||||
extern std::map<int32_t, MobDropChance> MobDropChances;
|
||||
extern std::map<int32_t, MobDrop> MobDrops;
|
||||
extern std::map<int32_t, std::map<int8_t, Bullet>> Bullets;
|
||||
namespace MobAI {
|
||||
extern bool simulateMobs;
|
||||
extern std::map<int32_t, Mob*> Mobs;
|
||||
|
||||
void init();
|
||||
void step(CNServer*, time_t);
|
||||
void playerTick(CNServer*, time_t);
|
||||
|
||||
void deadStep(Mob*, time_t);
|
||||
void combatStep(Mob*, time_t);
|
||||
void retreatStep(Mob*, time_t);
|
||||
void roamingStep(Mob*, time_t);
|
||||
|
||||
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
|
||||
void combatBegin(CNSocket *sock, CNPacketData *data);
|
||||
void combatEnd(CNSocket *sock, CNPacketData *data);
|
||||
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
|
||||
void dealGooDamage(CNSocket *sock, int amount);
|
||||
|
||||
void npcAttackPc(Mob *mob, time_t currTime);
|
||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
||||
void killMob(CNSocket *sock, Mob *mob);
|
||||
void giveReward(CNSocket *sock, Mob *mob, int rolledBoosts, int rolledPotions, int rolledCrate, int rolledCrateType, int rolledEvent);
|
||||
void getReward(sItemBase *reward, MobDrop *drop, MobDropChance *chance, int rolled);
|
||||
void giveEventReward(CNSocket* sock, Player* player, int rolled);
|
||||
|
||||
std::pair<int,int> lerp(int, int, int, int, int);
|
||||
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);
|
||||
|
||||
void pcAttackChars(CNSocket *sock, CNPacketData *data);
|
||||
void drainMobHP(Mob *mob, int amount);
|
||||
// TODO: make this internal later
|
||||
void incNextMovement(Mob *mob, time_t currTime=0);
|
||||
bool aggroCheck(Mob *mob, time_t currTime);
|
||||
void clearDebuff(Mob *mob);
|
||||
|
||||
void grenadeFire(CNSocket* sock, CNPacketData* data);
|
||||
void rocketFire(CNSocket* sock, CNPacketData* data);
|
||||
void projectileHit(CNSocket* sock, CNPacketData* data);
|
||||
/// returns bullet id
|
||||
int8_t addBullet(Player* plr, bool isGrenade);
|
||||
|
||||
void followToCombat(Mob *mob);
|
||||
void groupRetreat(Mob *mob);
|
||||
void useAbilities(Mob *mob, time_t currTime);
|
||||
void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int style);
|
||||
void enterCombat(CNSocket *sock, Mob *mob);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
@ -79,8 +79,8 @@ void NPCManager::destroyNPC(int32_t id) {
|
||||
ChunkManager::removeNPCFromChunks(ChunkManager::getViewableChunks(entity->chunkPos), id);
|
||||
|
||||
// remove from mob manager
|
||||
if (MobManager::Mobs.find(id) != MobManager::Mobs.end())
|
||||
MobManager::Mobs.erase(id);
|
||||
if (MobAI::Mobs.find(id) != MobAI::Mobs.end())
|
||||
MobAI::Mobs.erase(id);
|
||||
|
||||
// remove from eggs
|
||||
if (Eggs.find(id) != Eggs.end())
|
||||
@ -538,7 +538,7 @@ BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type,
|
||||
|
||||
if (team == 2) {
|
||||
npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id);
|
||||
MobManager::Mobs[id] = (Mob*)npc;
|
||||
MobAI::Mobs[id] = (Mob*)npc;
|
||||
|
||||
// re-enable respawning, if desired
|
||||
((Mob*)npc)->summoned = !respawn;
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "NanoManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "NPCManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "GroupManager.hpp"
|
||||
#include "Abilities.hpp"
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "ChatManager.hpp"
|
||||
#include "Database.hpp"
|
||||
#include "BuddyManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "RacingManager.hpp"
|
||||
#include "BuiltinCommands.hpp"
|
||||
#include "Abilities.hpp"
|
||||
@ -59,7 +59,7 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
||||
GroupManager::groupKickPlayer(plr);
|
||||
|
||||
// remove player's bullets
|
||||
MobManager::Bullets.erase(plr->iID);
|
||||
Combat::Bullets.erase(plr->iID);
|
||||
|
||||
// remove player's ongoing race, if it exists
|
||||
RacingManager::EPRaces.erase(key);
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "ItemManager.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "ChunkManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
#include "RacingManager.hpp"
|
||||
@ -284,7 +284,7 @@ void TableData::init() {
|
||||
Mob *tmp = new Mob(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, npc["iNPCType"], td, nextId);
|
||||
|
||||
NPCManager::NPCs[nextId] = tmp;
|
||||
MobManager::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
MobAI::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
NPCManager::updateNPCPosition(nextId, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
||||
|
||||
nextId++;
|
||||
@ -310,7 +310,7 @@ void TableData::init() {
|
||||
Mob* tmp = new Mob(leader["iX"], leader["iY"], leader["iZ"], leader["iAngle"], instanceID, leader["iNPCType"], td, nextId);
|
||||
|
||||
NPCManager::NPCs[nextId] = tmp;
|
||||
MobManager::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
MobAI::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
NPCManager::updateNPCPosition(nextId, leader["iX"], leader["iY"], leader["iZ"], instanceID, leader["iAngle"]);
|
||||
|
||||
tmp->groupLeader = nextId;
|
||||
@ -325,7 +325,7 @@ void TableData::init() {
|
||||
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;
|
||||
MobManager::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
MobAI::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
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"];
|
||||
@ -364,7 +364,7 @@ void TableData::init() {
|
||||
|
||||
if (team == 2) {
|
||||
NPCManager::NPCs[nextId] = new Mob(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, npc["iNPCType"], NPCManager::NPCData[(int)npc["iNPCType"]], nextId);
|
||||
MobManager::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
MobAI::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
|
||||
} else
|
||||
NPCManager::NPCs[nextId] = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, npc["iNPCType"], nextId);
|
||||
|
||||
@ -472,7 +472,7 @@ void TableData::loadPaths(int* nextId) {
|
||||
// mob paths
|
||||
pathDataNPC = pathData["mob"];
|
||||
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
|
||||
for (auto& pair : MobManager::Mobs) {
|
||||
for (auto& pair : MobAI::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;
|
||||
|
||||
@ -518,7 +518,7 @@ void TableData::loadDrops() {
|
||||
for (nlohmann::json::iterator _cratesRatio = dropChance["CratesRatio"].begin(); _cratesRatio != dropChance["CratesRatio"].end(); _cratesRatio++) {
|
||||
toAdd.cratesRatio.push_back((int)_cratesRatio.value());
|
||||
}
|
||||
MobManager::MobDropChances[(int)dropChance["Type"]] = toAdd;
|
||||
Combat::MobDropChances[(int)dropChance["Type"]] = toAdd;
|
||||
}
|
||||
|
||||
// MobDrops
|
||||
@ -532,21 +532,21 @@ void TableData::loadDrops() {
|
||||
|
||||
toAdd.dropChanceType = (int)drop["DropChance"];
|
||||
// Check if DropChance exists
|
||||
if (MobManager::MobDropChances.find(toAdd.dropChanceType) == MobManager::MobDropChances.end()) {
|
||||
if (Combat::MobDropChances.find(toAdd.dropChanceType) == Combat::MobDropChances.end()) {
|
||||
throw TableException(" MobDropChance not found: " + std::to_string((toAdd.dropChanceType)));
|
||||
}
|
||||
// Check if number of crates is correct
|
||||
if (!(MobManager::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) {
|
||||
if (!(Combat::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"];
|
||||
MobManager::MobDrops[(int)drop["DropType"]] = toAdd;
|
||||
Combat::MobDrops[(int)drop["DropType"]] = toAdd;
|
||||
}
|
||||
|
||||
std::cout << "[INFO] Loaded " << MobManager::MobDrops.size() << " Mob Drop Types"<< std::endl;
|
||||
std::cout << "[INFO] Loaded " << Combat::MobDrops.size() << " Mob Drop Types"<< std::endl;
|
||||
|
||||
// Rarity Ratios
|
||||
nlohmann::json rarities = dropData["RarityRatios"];
|
||||
@ -830,7 +830,7 @@ void TableData::loadGruntwork(int32_t *nextId) {
|
||||
// re-enable respawning
|
||||
((Mob*)npc)->summoned = false;
|
||||
|
||||
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
MobAI::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
|
||||
} else {
|
||||
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id);
|
||||
}
|
||||
@ -853,7 +853,7 @@ void TableData::loadGruntwork(int32_t *nextId) {
|
||||
((Mob*)tmp)->summoned = false;
|
||||
|
||||
NPCManager::NPCs[*nextId] = tmp;
|
||||
MobManager::Mobs[*nextId] = (Mob*)NPCManager::NPCs[*nextId];
|
||||
MobAI::Mobs[*nextId] = (Mob*)NPCManager::NPCs[*nextId];
|
||||
NPCManager::updateNPCPosition(*nextId, leader["iX"], leader["iY"], leader["iZ"], instanceID, leader["iAngle"]);
|
||||
|
||||
tmp->groupLeader = *nextId;
|
||||
@ -872,7 +872,7 @@ void TableData::loadGruntwork(int32_t *nextId) {
|
||||
((Mob*)tmp)->summoned = false;
|
||||
|
||||
NPCManager::NPCs[*nextId] = tmpFol;
|
||||
MobManager::Mobs[*nextId] = (Mob*)NPCManager::NPCs[*nextId];
|
||||
MobAI::Mobs[*nextId] = (Mob*)NPCManager::NPCs[*nextId];
|
||||
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"];
|
||||
@ -1008,11 +1008,11 @@ void TableData::flush() {
|
||||
|
||||
// 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 (MobManager::Mobs.find(m->groupMember[i]) == MobManager::Mobs.end()) {
|
||||
if (MobAI::Mobs.find(m->groupMember[i]) == MobAI::Mobs.end()) {
|
||||
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
||||
continue;
|
||||
}
|
||||
followers.push_back(MobManager::Mobs[m->groupMember[i]]);
|
||||
followers.push_back(MobAI::Mobs[m->groupMember[i]]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -4,7 +4,8 @@
|
||||
#include "NanoManager.hpp"
|
||||
#include "TransportManager.hpp"
|
||||
#include "TableData.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cmath>
|
||||
@ -272,7 +273,7 @@ void TransportManager::stepNPCPathing() {
|
||||
}
|
||||
|
||||
// skip if not simulating mobs
|
||||
if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) {
|
||||
if (npc->npcClass == NPC_MOB && !MobAI::simulateMobs) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
@ -307,7 +308,7 @@ void TransportManager::stepNPCPathing() {
|
||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||
break;
|
||||
case NPC_MOB:
|
||||
MobManager::incNextMovement((Mob*)npc);
|
||||
MobAI::incNextMovement((Mob*)npc);
|
||||
/* fallthrough */
|
||||
default:
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
||||
|
@ -5,7 +5,8 @@
|
||||
#include "BuiltinCommands.hpp"
|
||||
#include "ChatManager.hpp"
|
||||
#include "CustomCommands.hpp"
|
||||
#include "MobManager.hpp"
|
||||
#include "Combat.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "ItemManager.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
#include "NanoManager.hpp"
|
||||
@ -101,7 +102,8 @@ int main() {
|
||||
BuiltinCommands::init();
|
||||
ChatManager::init();
|
||||
CustomCommands::init();
|
||||
MobManager::init();
|
||||
Combat::init();
|
||||
MobAI::init();
|
||||
ItemManager::init();
|
||||
MissionManager::init();
|
||||
NanoManager::init();
|
||||
|
Loading…
Reference in New Issue
Block a user