From f0cf6326e5d2c57bb8daa8f8b0b638c489421d03 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 12 Apr 2022 22:22:12 -0400 Subject: [PATCH] (WIP) Point 2: Generalization --- src/Abilities.cpp | 10 +++---- src/Combat.cpp | 34 +++++++++++++++++++---- src/CustomCommands.cpp | 2 +- src/Entities.hpp | 19 +++++++++++-- src/MobAI.cpp | 62 ++++++++++-------------------------------- src/MobAI.hpp | 27 ++++-------------- src/Transport.cpp | 2 +- 7 files changed, 74 insertions(+), 82 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index a2ed2cf..a84fddb 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -153,7 +153,7 @@ bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t target respdata[i].eCT = 4; respdata[i].iID = mob->id; 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 |= bitFlag; 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].iHP = mob->hp; 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 |= bitFlag; 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) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, getTime())) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) @@ -530,7 +530,7 @@ bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, getTime())) { MobAI::clearDebuff(mob); 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) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, getTime())) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) diff --git a/src/Combat.cpp b/src/Combat.cpp index c40bb95..d881183 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -50,14 +50,14 @@ int CombatNPC::takeDamage(EntityRef src, int amt) { Mob* mob = (Mob*)this; // 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 } if (mob->skillStyle >= 0) 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 MobAI::enterCombat(src.sock, mob); @@ -101,7 +101,31 @@ int32_t CombatNPC::getID() { } 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 getDamage(int attackPower, int defensePower, bool shouldCrit, @@ -259,7 +283,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!MobAI::aggroCheck(mob, currTime)) { MobAI::clearDebuff(mob); if (mob->groupLeader != 0) @@ -293,7 +317,7 @@ static void genQItemRolls(Player *leader, std::map& rolls) { } void Combat::killMob(CNSocket *sock, Mob *mob) { - mob->state = MobState::DEAD; + mob->state = AIState::DEAD; mob->target = nullptr; mob->cbf = 0; mob->skillStyle = -1; diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 2d92c21..4c2bb9e 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -328,7 +328,7 @@ static void toggleAiCommand(std::string full, std::vector& args, CN continue; Mob* mob = (Mob*)pair.second; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; mob->target = nullptr; mob->nextMovement = getTime(); diff --git a/src/Entities.hpp b/src/Entities.hpp index 013fc7a..8c3bfea 100644 --- a/src/Entities.hpp +++ b/src/Entities.hpp @@ -16,6 +16,16 @@ enum class EntityType : uint8_t { BUS }; +enum class AIState { + INACTIVE, + ROAMING, + COMBAT, + RETREAT, + DEAD +}; + +class Chunk; + struct Entity { EntityType kind = EntityType::INVALID; int x = 0, y = 0, z = 0; @@ -123,6 +133,8 @@ struct CombatNPC : public BaseNPC, public ICombatant { int spawnZ = 0; int level = 0; 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) : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { @@ -138,12 +150,16 @@ struct CombatNPC : public BaseNPC, public ICombatant { virtual bool isAlive() override; virtual int getCurrentHP() override; virtual int32_t getID() 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 -// TODO: decouple from BaseNPC struct Egg : public BaseNPC { bool summoned = false; bool dead = false; @@ -161,7 +177,6 @@ struct Egg : public BaseNPC { virtual void disappearFromViewOf(CNSocket *sock) override; }; -// TODO: decouple from BaseNPC struct Bus : public BaseNPC { Bus(int angle, uint64_t iID, int t, int id) : BaseNPC(angle, iID, t, id) { diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 0ddeca6..a16478c 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -68,13 +68,13 @@ void MobAI::followToCombat(Mob *mob) { } 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; enterCombat(mob->target, followerMob); } - if (leadMob->state != MobState::ROAMING) + if (leadMob->state != AIState::ROAMING) return; enterCombat(mob->target, leadMob); @@ -96,19 +96,19 @@ void MobAI::groupRetreat(Mob *mob) { } Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; - if (followerMob->state != MobState::COMBAT) + if (followerMob->state != AIState::COMBAT) continue; followerMob->target = nullptr; - followerMob->state = MobState::RETREAT; + followerMob->state = AIState::RETREAT; clearDebuff(followerMob); } - if (leadMob->state != MobState::COMBAT) + if (leadMob->state != AIState::COMBAT) return; leadMob->target = nullptr; - leadMob->state = MobState::RETREAT; + leadMob->state = AIState::RETREAT; clearDebuff(leadMob); } @@ -145,7 +145,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { if (levelDifference > -10) 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 if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE)) @@ -267,7 +267,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i if (plr->HP <= 0) { mob->target = nullptr; - mob->state = MobState::RETREAT; + mob->state = AIState::RETREAT; if (!aggroCheck(mob, getTime())) { clearDebuff(mob); if (mob->groupLeader != 0) @@ -395,7 +395,7 @@ static void useAbilities(Mob *mob, time_t currTime) { void MobAI::enterCombat(CNSocket *sock, Mob *mob) { mob->target = sock; - mob->state = MobState::COMBAT; + mob->state = AIState::COMBAT; mob->nextMovement = getTime(); mob->nextAttack = 0; @@ -473,7 +473,7 @@ void Mob::deadStep(time_t currTime) { std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl; hp = maxHealth; - state = MobState::ROAMING; + state = AIState::ROAMING; // if mob is a group leader/follower, spawn where the group is. if (groupLeader != 0) { @@ -501,7 +501,7 @@ void Mob::combatStep(time_t currTime) { // lose aggro if the player lost connection if (PlayerManager::players.find(target) == PlayerManager::players.end()) { target = nullptr; - state = MobState::RETREAT; + state = AIState::RETREAT; if (!aggroCheck(this, currTime)) { clearDebuff(this); if (groupLeader != 0) @@ -516,7 +516,7 @@ void Mob::combatStep(time_t currTime) { if (plr->HP <= 0 || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { target = nullptr; - state = MobState::RETREAT; + state = AIState::RETREAT; if (!aggroCheck(this, currTime)) { clearDebuff(this); if (groupLeader != 0) @@ -572,7 +572,6 @@ void Mob::combatStep(time_t currTime) { } int distanceToTravel = INT_MAX; - int speed = speed; // movement logic: move when out of range but don't move while casting a skill if (distance > mobRange && skillStyle == -1) { if (nextMovement != 0 && currTime < nextMovement) @@ -628,7 +627,7 @@ void Mob::combatStep(time_t currTime) { distance = hypot(xyDistance, plr->z - roamZ); if (distance >= data["m_iCombatRange"]) { target = nullptr; - state = MobState::RETREAT; + state = AIState::RETREAT; clearDebuff(this); if (groupLeader != 0) 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). * 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; if (aggroCheck(this, currTime)) return; @@ -673,7 +672,6 @@ void Mob::roamingStep(time_t currTime) { int xStart = spawnX - idleRange/2; int yStart = spawnY - idleRange/2; - int speed = speed; // some mobs don't move (and we mustn't divide/modulus by zero) if (idleRange == 0 || speed == 0) @@ -760,7 +758,7 @@ void Mob::retreatStep(time_t currTime) { // if we got there //if (distance <= mob->data["m_iIdleRange"]) { if (distance <= 10) { // retreat back to the spawn point - state = MobState::ROAMING; + state = AIState::ROAMING; hp = maxHealth; killedTime = 0; nextAttack = 0; @@ -775,33 +773,3 @@ void Mob::retreatStep(time_t currTime) { 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; - } -} diff --git a/src/MobAI.hpp b/src/MobAI.hpp index 1c20c2d..1c6c178 100644 --- a/src/MobAI.hpp +++ b/src/MobAI.hpp @@ -2,19 +2,10 @@ #include "core/Core.hpp" #include "NPCManager.hpp" - -enum class MobState { - INACTIVE, - ROAMING, - COMBAT, - RETREAT, - DEAD -}; +#include "Entities.hpp" struct Mob : public CombatNPC { // general - MobState state = MobState::INACTIVE; - std::unordered_map unbuffTimes = {}; // dead @@ -42,16 +33,13 @@ struct Mob : public CombatNPC { int offsetX = 0, offsetY = 0; int groupMember[4] = {}; - // for optimizing away AI in empty chunks - int playersInView = 0; - // temporary; until we're sure what's what nlohmann::json data = {}; 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"]), sightRange(d["m_iSightRange"]) { - state = MobState::ROAMING; + state = AIState::ROAMING; data = d; @@ -83,13 +71,10 @@ struct Mob : public CombatNPC { ~Mob() {} - virtual void step(time_t currTime) override; - - // we may or may not want these to be generalized to all CombatNPCs later - void roamingStep(time_t currTime); - void combatStep(time_t currTime); - void retreatStep(time_t currTime); - void deadStep(time_t currTime); + virtual void roamingStep(time_t currTime) override; + virtual void combatStep(time_t currTime) override; + virtual void retreatStep(time_t currTime) override; + virtual void deadStep(time_t currTime) override; auto operator[](std::string s) { return data[s]; diff --git a/src/Transport.cpp b/src/Transport.cpp index 36b0de6..9fe2ccc 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -263,7 +263,7 @@ static void stepNPCPathing() { } // 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++; continue; }