(WIP) Point 2: Generalization

This commit is contained in:
gsemaj 2022-04-12 22:22:12 -04:00
parent 82bc94c01c
commit f0cf6326e5
7 changed files with 74 additions and 82 deletions

View File

@ -153,7 +153,7 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target
respdata[i].eCT = 4; respdata[i].eCT = 4;
respdata[i].iID = mob->id; respdata[i].iID = mob->id;
respdata[i].bProtected = 1; respdata[i].bProtected = 1;
if (mob->skillStyle < 0 && mob->state != MobState::RETREAT if (mob->skillStyle < 0 && mob->state != AIState::RETREAT
&& !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom
mob->cbf |= bitFlag; mob->cbf |= bitFlag;
mob->unbuffTimes[bitFlag] = getTime() + duration * 100; mob->unbuffTimes[bitFlag] = getTime() + duration * 100;
@ -227,7 +227,7 @@ bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int
respdata[i].iID = mob->id; respdata[i].iID = mob->id;
respdata[i].iHP = mob->hp; respdata[i].iHP = mob->hp;
respdata[i].bProtected = 1; respdata[i].bProtected = 1;
if (mob->skillStyle < 0 && mob->state != MobState::RETREAT if (mob->skillStyle < 0 && mob->state != AIState::RETREAT
&& !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom && !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom
mob->cbf |= bitFlag; mob->cbf |= bitFlag;
mob->unbuffTimes[bitFlag] = getTime() + duration * 100; mob->unbuffTimes[bitFlag] = getTime() + duration * 100;
@ -450,7 +450,7 @@ bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, in
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
if (!MobAI::aggroCheck(mob, getTime())) { if (!MobAI::aggroCheck(mob, getTime())) {
MobAI::clearDebuff(mob); MobAI::clearDebuff(mob);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)
@ -530,7 +530,7 @@ bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID,
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
if (!MobAI::aggroCheck(mob, getTime())) { if (!MobAI::aggroCheck(mob, getTime())) {
MobAI::clearDebuff(mob); MobAI::clearDebuff(mob);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)
@ -588,7 +588,7 @@ bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID,
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
if (!MobAI::aggroCheck(mob, getTime())) { if (!MobAI::aggroCheck(mob, getTime())) {
MobAI::clearDebuff(mob); MobAI::clearDebuff(mob);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)

View File

@ -50,14 +50,14 @@ int CombatNPC::takeDamage(EntityRef src, int amt) {
Mob* mob = (Mob*)this; Mob* mob = (Mob*)this;
// cannot kill mobs multiple times; cannot harm retreating mobs // cannot kill mobs multiple times; cannot harm retreating mobs
if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { if (mob->state != AIState::ROAMING && mob->state != AIState::COMBAT) {
return 0; // no damage return 0; // no damage
} }
if (mob->skillStyle >= 0) if (mob->skillStyle >= 0)
return 0; // don't hurt a mob casting corruption return 0; // don't hurt a mob casting corruption
if (mob->state == MobState::ROAMING) { if (mob->state == AIState::ROAMING) {
assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now
MobAI::enterCombat(src.sock, mob); MobAI::enterCombat(src.sock, mob);
@ -101,7 +101,31 @@ int32_t CombatNPC::getID() {
} }
void CombatNPC::step(time_t currTime) { void CombatNPC::step(time_t currTime) {
// stubbed if (playersInView < 0)
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
// skip movement and combat if disabled or not in view
if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD
&& state != AIState::RETREAT)
return;
switch (state) {
case AIState::INACTIVE:
// no-op
break;
case AIState::ROAMING:
roamingStep(currTime);
break;
case AIState::COMBAT:
combatStep(currTime);
break;
case AIState::RETREAT:
retreatStep(currTime);
break;
case AIState::DEAD:
deadStep(currTime);
break;
}
} }
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit, static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
@ -259,7 +283,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
if (!MobAI::aggroCheck(mob, currTime)) { if (!MobAI::aggroCheck(mob, currTime)) {
MobAI::clearDebuff(mob); MobAI::clearDebuff(mob);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)
@ -293,7 +317,7 @@ static void genQItemRolls(Player *leader, std::map<int, int>& rolls) {
} }
void Combat::killMob(CNSocket *sock, Mob *mob) { void Combat::killMob(CNSocket *sock, Mob *mob) {
mob->state = MobState::DEAD; mob->state = AIState::DEAD;
mob->target = nullptr; mob->target = nullptr;
mob->cbf = 0; mob->cbf = 0;
mob->skillStyle = -1; mob->skillStyle = -1;

View File

@ -328,7 +328,7 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN
continue; continue;
Mob* mob = (Mob*)pair.second; Mob* mob = (Mob*)pair.second;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
mob->target = nullptr; mob->target = nullptr;
mob->nextMovement = getTime(); mob->nextMovement = getTime();

View File

@ -16,6 +16,16 @@ enum class EntityType : uint8_t {
BUS BUS
}; };
enum class AIState {
INACTIVE,
ROAMING,
COMBAT,
RETREAT,
DEAD
};
class Chunk;
struct Entity { struct Entity {
EntityType kind = EntityType::INVALID; EntityType kind = EntityType::INVALID;
int x = 0, y = 0, z = 0; int x = 0, y = 0, z = 0;
@ -123,6 +133,8 @@ struct CombatNPC : public BaseNPC, public ICombatant {
int spawnZ = 0; int spawnZ = 0;
int level = 0; int level = 0;
int speed = 300; int speed = 300;
AIState state = AIState::INACTIVE;
int playersInView = 0; // for optimizing away AI in empty chunks
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) { : BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
@ -138,12 +150,16 @@ struct CombatNPC : public BaseNPC, public ICombatant {
virtual bool isAlive() override; virtual bool isAlive() override;
virtual int getCurrentHP() override; virtual int getCurrentHP() override;
virtual int32_t getID() override; virtual int32_t getID() override;
virtual void step(time_t currTime) override; virtual void step(time_t currTime) override;
virtual void roamingStep(time_t currTime) {} // no-op by default
virtual void combatStep(time_t currTime) {}
virtual void retreatStep(time_t currTime) {}
virtual void deadStep(time_t currTime) {}
}; };
// Mob is in MobAI.hpp, Player is in Player.hpp // Mob is in MobAI.hpp, Player is in Player.hpp
// TODO: decouple from BaseNPC
struct Egg : public BaseNPC { struct Egg : public BaseNPC {
bool summoned = false; bool summoned = false;
bool dead = false; bool dead = false;
@ -161,7 +177,6 @@ struct Egg : public BaseNPC {
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
}; };
// TODO: decouple from BaseNPC
struct Bus : public BaseNPC { struct Bus : public BaseNPC {
Bus(int angle, uint64_t iID, int t, int id) : Bus(int angle, uint64_t iID, int t, int id) :
BaseNPC(angle, iID, t, id) { BaseNPC(angle, iID, t, id) {

View File

@ -68,13 +68,13 @@ void MobAI::followToCombat(Mob *mob) {
} }
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
if (followerMob->state != MobState::ROAMING) // only roaming mobs should transition to combat if (followerMob->state != AIState::ROAMING) // only roaming mobs should transition to combat
continue; continue;
enterCombat(mob->target, followerMob); enterCombat(mob->target, followerMob);
} }
if (leadMob->state != MobState::ROAMING) if (leadMob->state != AIState::ROAMING)
return; return;
enterCombat(mob->target, leadMob); enterCombat(mob->target, leadMob);
@ -96,19 +96,19 @@ void MobAI::groupRetreat(Mob *mob) {
} }
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
if (followerMob->state != MobState::COMBAT) if (followerMob->state != AIState::COMBAT)
continue; continue;
followerMob->target = nullptr; followerMob->target = nullptr;
followerMob->state = MobState::RETREAT; followerMob->state = AIState::RETREAT;
clearDebuff(followerMob); clearDebuff(followerMob);
} }
if (leadMob->state != MobState::COMBAT) if (leadMob->state != AIState::COMBAT)
return; return;
leadMob->target = nullptr; leadMob->target = nullptr;
leadMob->state = MobState::RETREAT; leadMob->state = AIState::RETREAT;
clearDebuff(leadMob); clearDebuff(leadMob);
} }
@ -145,7 +145,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
if (levelDifference > -10) if (levelDifference > -10)
mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3; mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3;
if (mob->state != MobState::ROAMING && plr->inCombat) // freshly out of aggro mobs if (mob->state != AIState::ROAMING && plr->inCombat) // freshly out of aggro mobs
mobRange = mob->sightRange * 2; // should not be impacted by the above mobRange = mob->sightRange * 2; // should not be impacted by the above
if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE)) if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE))
@ -267,7 +267,7 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
if (!aggroCheck(mob, getTime())) { if (!aggroCheck(mob, getTime())) {
clearDebuff(mob); clearDebuff(mob);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)
@ -395,7 +395,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
void MobAI::enterCombat(CNSocket *sock, Mob *mob) { void MobAI::enterCombat(CNSocket *sock, Mob *mob) {
mob->target = sock; mob->target = sock;
mob->state = MobState::COMBAT; mob->state = AIState::COMBAT;
mob->nextMovement = getTime(); mob->nextMovement = getTime();
mob->nextAttack = 0; mob->nextAttack = 0;
@ -473,7 +473,7 @@ void Mob::deadStep(time_t currTime) {
std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl; std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl;
hp = maxHealth; hp = maxHealth;
state = MobState::ROAMING; state = AIState::ROAMING;
// if mob is a group leader/follower, spawn where the group is. // if mob is a group leader/follower, spawn where the group is.
if (groupLeader != 0) { if (groupLeader != 0) {
@ -501,7 +501,7 @@ void Mob::combatStep(time_t currTime) {
// lose aggro if the player lost connection // lose aggro if the player lost connection
if (PlayerManager::players.find(target) == PlayerManager::players.end()) { if (PlayerManager::players.find(target) == PlayerManager::players.end()) {
target = nullptr; target = nullptr;
state = MobState::RETREAT; state = AIState::RETREAT;
if (!aggroCheck(this, currTime)) { if (!aggroCheck(this, currTime)) {
clearDebuff(this); clearDebuff(this);
if (groupLeader != 0) if (groupLeader != 0)
@ -516,7 +516,7 @@ void Mob::combatStep(time_t currTime) {
if (plr->HP <= 0 if (plr->HP <= 0
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
target = nullptr; target = nullptr;
state = MobState::RETREAT; state = AIState::RETREAT;
if (!aggroCheck(this, currTime)) { if (!aggroCheck(this, currTime)) {
clearDebuff(this); clearDebuff(this);
if (groupLeader != 0) if (groupLeader != 0)
@ -572,7 +572,6 @@ void Mob::combatStep(time_t currTime) {
} }
int distanceToTravel = INT_MAX; int distanceToTravel = INT_MAX;
int speed = speed;
// movement logic: move when out of range but don't move while casting a skill // movement logic: move when out of range but don't move while casting a skill
if (distance > mobRange && skillStyle == -1) { if (distance > mobRange && skillStyle == -1) {
if (nextMovement != 0 && currTime < nextMovement) if (nextMovement != 0 && currTime < nextMovement)
@ -628,7 +627,7 @@ void Mob::combatStep(time_t currTime) {
distance = hypot(xyDistance, plr->z - roamZ); distance = hypot(xyDistance, plr->z - roamZ);
if (distance >= data["m_iCombatRange"]) { if (distance >= data["m_iCombatRange"]) {
target = nullptr; target = nullptr;
state = MobState::RETREAT; state = AIState::RETREAT;
clearDebuff(this); clearDebuff(this);
if (groupLeader != 0) if (groupLeader != 0)
groupRetreat(this); groupRetreat(this);
@ -649,7 +648,7 @@ void Mob::roamingStep(time_t currTime) {
* do so more often than if we waited for nextMovement (which is way too slow). * 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. * In the case of group leaders, this step will be called by dead mobs, so disable attack.
*/ */
if (state != MobState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) { if (state != AIState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) {
nextAttack = currTime + 500; nextAttack = currTime + 500;
if (aggroCheck(this, currTime)) if (aggroCheck(this, currTime))
return; return;
@ -673,7 +672,6 @@ void Mob::roamingStep(time_t currTime) {
int xStart = spawnX - idleRange/2; int xStart = spawnX - idleRange/2;
int yStart = spawnY - idleRange/2; int yStart = spawnY - idleRange/2;
int speed = speed;
// some mobs don't move (and we mustn't divide/modulus by zero) // some mobs don't move (and we mustn't divide/modulus by zero)
if (idleRange == 0 || speed == 0) if (idleRange == 0 || speed == 0)
@ -760,7 +758,7 @@ void Mob::retreatStep(time_t currTime) {
// if we got there // if we got there
//if (distance <= mob->data["m_iIdleRange"]) { //if (distance <= mob->data["m_iIdleRange"]) {
if (distance <= 10) { // retreat back to the spawn point if (distance <= 10) { // retreat back to the spawn point
state = MobState::ROAMING; state = AIState::ROAMING;
hp = maxHealth; hp = maxHealth;
killedTime = 0; killedTime = 0;
nextAttack = 0; nextAttack = 0;
@ -775,33 +773,3 @@ void Mob::retreatStep(time_t currTime) {
clearDebuff(this); clearDebuff(this);
} }
} }
void Mob::step(time_t currTime) {
assert(kind == EntityType::MOB);
if (playersInView < 0)
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
// skip mob movement and combat if disabled or not in view
if ((!simulateMobs || playersInView == 0) && state != MobState::DEAD
&& state != MobState::RETREAT)
return;
switch (state) {
case MobState::INACTIVE:
// no-op
break;
case MobState::ROAMING:
roamingStep(currTime);
break;
case MobState::COMBAT:
combatStep(currTime);
break;
case MobState::RETREAT:
retreatStep(currTime);
break;
case MobState::DEAD:
deadStep(currTime);
break;
}
}

View File

@ -2,19 +2,10 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Entities.hpp"
enum class MobState {
INACTIVE,
ROAMING,
COMBAT,
RETREAT,
DEAD
};
struct Mob : public CombatNPC { struct Mob : public CombatNPC {
// general // general
MobState state = MobState::INACTIVE;
std::unordered_map<int32_t,time_t> unbuffTimes = {}; std::unordered_map<int32_t,time_t> unbuffTimes = {};
// dead // dead
@ -42,16 +33,13 @@ struct Mob : public CombatNPC {
int offsetX = 0, offsetY = 0; int offsetX = 0, offsetY = 0;
int groupMember[4] = {}; int groupMember[4] = {};
// for optimizing away AI in empty chunks
int playersInView = 0;
// temporary; until we're sure what's what // temporary; until we're sure what's what
nlohmann::json data = {}; nlohmann::json data = {};
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]), : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
sightRange(d["m_iSightRange"]) { sightRange(d["m_iSightRange"]) {
state = MobState::ROAMING; state = AIState::ROAMING;
data = d; data = d;
@ -83,13 +71,10 @@ struct Mob : public CombatNPC {
~Mob() {} ~Mob() {}
virtual void step(time_t currTime) override; virtual void roamingStep(time_t currTime) override;
virtual void combatStep(time_t currTime) override;
// we may or may not want these to be generalized to all CombatNPCs later virtual void retreatStep(time_t currTime) override;
void roamingStep(time_t currTime); virtual void deadStep(time_t currTime) override;
void combatStep(time_t currTime);
void retreatStep(time_t currTime);
void deadStep(time_t currTime);
auto operator[](std::string s) { auto operator[](std::string s) {
return data[s]; return data[s];

View File

@ -263,7 +263,7 @@ static void stepNPCPathing() {
} }
// do not roam if not roaming // do not roam if not roaming
if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
it++; it++;
continue; continue;
} }