123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- /*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
- This file is part of EQ2Emulator.
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "NPC_AI.h"
- #include "Combat.h"
- #include "zoneserver.h"
- #include "Spells.h"
- #include "../common/Log.h"
- #include "LuaInterface.h"
- #include "World.h"
- #include "Rules/Rules.h"
- extern RuleManager rule_manager;
- extern LuaInterface* lua_interface;
- extern World world;
- /* The NEW AI code */
- Brain::Brain(NPC* npc) {
- // Set the npc this brain will controll
- m_body = npc;
- // Set the default time between calls to think to 250 miliseconds (1/4 a second)
- m_tick = 250;
- m_lastTick = Timer::GetCurrentTime2();
- m_spellRecovery = 0;
- m_playerInEncounter = false;
- // Set up the mutex for the hate list
- MHateList.SetName("Brain::m_hatelist");
- // Set up the mutex for the encounter list
- MEncounter.SetName("Brain::m_encounter");
- }
- Brain::~Brain() {
- }
- void Brain::Think() {
- // Get the entity this NPC hates the most,
- // GetMostHated() will handle dead spawns so no need to check the health in this function
- Entity* target = GetMostHated();
- // If mezzed, stunned or feared we can't do anything so skip
- if (!m_body->IsMezzedOrStunned() || !m_body->IsFeared()) {
- // Not mezzed or stunned
- // Get the distance to the runback location
- float run_back_distance = m_body->GetRunbackDistance();
- if (target) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s has %s targeted.", m_body->GetName(), target->GetName());
- // NPC has an entity that it hates
- // If the NPC is not in combat then put them in combat
- if (!m_body->EngagedInCombat()) {
- m_body->ClearRunningLocations();
- m_body->InCombat(true);
- }
- // Set the NPC's target to the most hated entity if it is not already.
- if (m_body->GetTarget() != target) {
- m_body->SetTarget(target);
- m_body->FaceTarget(target);
- }
- // Check to see if the NPC has exceeded the max chase distance
- if (run_back_distance > MAX_CHASE_DISTANCE) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Run back distance is greater then max chase distance, run_back_distance = %f", run_back_distance);
- // Over the max chase distance, Check to see if the target is is a client
- Client* client = target->GetZone()->GetClientBySpawn(target);
- if (client)
- {
- // Target is a client so send encounter break messages
- if (m_body->HasSpawnGroup())
- client->SimpleMessage(CHANNEL_COLOR_WHITE, "This encounter will no longer give encounter rewards.");
- else
- client->Message(CHANNEL_COLOR_WHITE, "%s is no longer worth any experience or treasure.", m_body->GetName());
- }
- // Clear the hate list for this NPC
- ClearHate();
- // Clear the encounter list
- ClearEncounter();
- }
- else {
- // Still within max chase distance lets to the combat stuff now
- float distance = m_body->GetDistance(target);
- if(!m_body->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", m_body->GetName(), target->GetName());
- m_body->FaceTarget(target);
- ProcessMelee(target, distance);
- }
- }
- }
- else {
- // Nothing in the hate list
- bool wasInCombat = m_body->EngagedInCombat();
- // Check to see if the NPC is still flagged as in combat for some reason
- if (m_body->EngagedInCombat()) {
- // If it is set the combat flag to false
- m_body->InCombat(false);
- // Do not set a players pet to full health once they stop combat
- if (!m_body->IsPet() || (m_body->IsPet() && !m_body->GetOwner()->IsPlayer()))
- m_body->SetHP(m_body->GetTotalHP());
- }
- CheckBuffs();
- // If run back distance is greater then 0 then run back
- if ((wasInCombat || m_body->m_runningBack) && run_back_distance > 1) {
- m_body->Runback();
- }
- else if (m_body->m_runningBack)
- {
- m_body->SetX(m_body->GetRunbackLocation()->x,false);
- m_body->SetZ(m_body->GetRunbackLocation()->z,false);
- m_body->SetY(m_body->GetRunbackLocation()->y,true);
- m_body->ClearRunback();
- m_body->GetZone()->movementMgr->StopNavigation((Entity*)m_body);
- m_body->ClearRunningLocations();
- }
- // If encounter size is greater then 0 then clear it
- if (GetEncounterSize() > 0)
- ClearEncounter();
- }
- }
- }
- sint32 Brain::GetHate(Entity* entity) {
- // We will use this variable to return the value, default to 0
- sint32 ret = 0;
- // Lock the hate list, not altering it so do a read lock
- MHateList.readlock(__FUNCTION__, __LINE__);
- // First check to see if the given entity is even in the hate list
- if (m_hatelist.count(entity->GetID()) > 0)
- // Entity in the hate list so get the hate value for the entity
- ret = m_hatelist[entity->GetID()];
- // Unlock the hate list
- MHateList.releasereadlock(__FUNCTION__, __LINE__);
- // return the hate
- return ret;
- }
- void Brain::AddHate(Entity* entity, sint32 hate) {
- // Lock the hate list, we are altering the list so use write lock
- MHateList.writelock(__FUNCTION__, __LINE__);
- if (m_hatelist.count(entity->GetID()) > 0)
- m_hatelist[entity->GetID()] += hate;
- else
- m_hatelist.insert(std::pair<int32, sint32>(entity->GetID(), hate));
- if (entity->HatedBy.count(m_body->GetID()) == 0)
- entity->HatedBy.insert(m_body->GetID());
- // Unlock the list
- MHateList.releasewritelock(__FUNCTION__, __LINE__);
- }
- void Brain::ClearHate() {
- // Lock the hate list, we are altering the list so use a write lock
- MHateList.writelock(__FUNCTION__, __LINE__);
- map<int32, sint32>::iterator itr;
- for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
- Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first);
- if (spawn && spawn->IsEntity())
- ((Entity*)spawn)->HatedBy.erase(itr->first);
- }
- // Clear the list
- m_hatelist.clear();
- // Unlock the hate list
- MHateList.releasewritelock(__FUNCTION__, __LINE__);
- }
- void Brain::ClearHate(Entity* entity) {
- // Lock the hate list, we could potentially modify the list so use write lock
- MHateList.writelock(__FUNCTION__, __LINE__);
- // Check to see if the given entity is in the hate list
- if (m_hatelist.count(entity->GetID()) > 0)
- // Erase the entity from the hate list
- m_hatelist.erase(entity->GetID());
- entity->HatedBy.erase(m_body->GetID());
- // Unlock the hate list
- MHateList.releasewritelock(__FUNCTION__, __LINE__);
- }
- Entity* Brain::GetMostHated() {
- map<int32, sint32>::iterator itr;
- int32 ret = 0;
- sint32 hate = 0;
- // Lock the hate list, not going to alter it so use a read lock
- MHateList.readlock(__FUNCTION__, __LINE__);
- if (m_hatelist.size() > 0) {
- // Loop through the list looking for the entity that this NPC hates the most
- for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
- // Compare the hate value for the current iteration to our stored highest value
- if(itr->second > hate) {
- // New high value store the entity
- ret = itr->first;
- // Store the value to compare with the rest of the entities
- hate = itr->second;
- }
- }
- }
- // Unlock the list
- MHateList.releasereadlock(__FUNCTION__, __LINE__);
- Entity* hated = (Entity*)GetBody()->GetZone()->GetSpawnByID(ret);
- // Check the reult to see if it is still alive
- if(hated && hated->GetHP() <= 0) {
- // Entity we got was dead so remove it from the list
- ClearHate(hated);
- // Call this function again now that we removed the dead entity
- hated = GetMostHated();
- }
- // Return our result
- return hated;
- }
- sint8 Brain::GetHatePercentage(Entity* entity) {
- float percentage = 0.0;
- MHateList.readlock(__FUNCTION__, __LINE__);
- if (entity && m_hatelist.count(entity->GetID()) > 0 && m_hatelist[entity->GetID()] > 0) {
- sint32 total_hate = 0;
- map<int32, sint32>::iterator itr;
- for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++)
- total_hate += itr->second;
- percentage = m_hatelist[entity->GetID()] / total_hate;
- }
- MHateList.releasereadlock(__FUNCTION__, __LINE__);
- return (sint8)(percentage * 100);
- }
- vector<Entity*>* Brain::GetHateList() {
- vector<Entity*>* ret = new vector<Entity*>;
- map<int32, sint32>::iterator itr;
- // Lock the list
- MHateList.readlock(__FUNCTION__, __LINE__);
- // Loop over the list storing the values into the new list
- for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
- Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first);
- if (ent)
- ret->push_back(ent);
- }
- // Unlock the list
- MHateList.releasereadlock(__FUNCTION__, __LINE__);
- // Return the copy of the list
- return ret;
- }
- void Brain::MoveCloser(Entity* target) {
- if (target && m_body->GetFollowTarget() != target)
- m_body->SetFollowTarget(target);
- if (m_body->GetFollowTarget() && !m_body->following) {
- m_body->CalculateRunningLocation(true);
- //m_body->ClearRunningLocations();
- m_body->following = true;
- }
- }
- bool Brain::ProcessSpell(Entity* target, float distance) {
- if(rand()%100 > m_body->GetCastPercentage() || m_body->IsStifled() || m_body->IsFeared())
- return false;
- Spell* spell = m_body->GetNextSpell(distance);
- if(spell){
- Spawn* spell_target = 0;
- if(spell->GetSpellData()->friendly_spell == 1){
- vector<Spawn*>* group = m_body->GetSpawnGroup();
- if(group && group->size() > 0){
- vector<Spawn*>::iterator itr;
- for(itr = group->begin(); itr != group->end(); itr++){
- if((!spell_target && (*itr)->GetHP() > 0 && (*itr)->GetHP() < (*itr)->GetTotalHP()) || (spell_target && (*itr)->GetHP() > 0 && spell_target->GetHP() > (*itr)->GetHP()))
- spell_target = *itr;
- }
- }
- if(!spell_target)
- spell_target = m_body;
- safe_delete(group);
- }
- else
- spell_target = target;
- m_body->GetZone()->ProcessSpell(spell, m_body, spell_target);
- m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
- return true;
- }
- return false;
- }
- bool Brain::CheckBuffs() {
- if (!m_body->GetZone()->GetSpellProcess() || m_body->EngagedInCombat() || m_body->IsCasting() || m_body->IsMezzedOrStunned() || !m_body->Alive() || m_body->IsStifled() || !HasRecovered())
- return false;
- Spell* spell = m_body->GetNextBuffSpell();
- if (spell) {
- m_body->CalculateRunningLocation(true);
- m_body->GetZone()->ProcessSpell(spell, m_body, m_body);
- m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
- return true;
- }
- return false;
- }
- void Brain::ProcessMelee(Entity* target, float distance) {
- if(distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())
- MoveCloser(target);
- else {
- if (target) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is within melee range of %s.", m_body->GetName(), target->GetName());
- if (m_body->AttackAllowed(target)) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is allowed to attack %s.", m_body->GetName(), target->GetName());
- if (m_body->PrimaryWeaponReady() && !m_body->IsDazed() && !m_body->IsFeared()) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s swings its primary weapon at %s.", m_body->GetName(), target->GetName());
- m_body->SetPrimaryLastAttackTime(Timer::GetCurrentTime2());
- m_body->MeleeAttack(target, distance, true);
- m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AUTO_ATTACK_TICK, target);
- }
- if (m_body->SecondaryWeaponReady() && !m_body->IsDazed()) {
- m_body->SetSecondaryLastAttackTime(Timer::GetCurrentTime2());
- m_body->MeleeAttack(target, distance, false);
- }
- }
- }
- }
- }
- bool Brain::HasRecovered() {
- if(m_spellRecovery > Timer::GetCurrentTime2())
- return false;
- m_spellRecovery = 0;
- return true;
- }
- void Brain::AddToEncounter(Entity* entity) {
- // If player pet then set the entity to the pets owner
- if (entity->IsPet() && ((NPC*)entity)->GetOwner()->IsPlayer())
- entity = ((NPC*)entity)->GetOwner();
- // If player or bot then get the group
- int32 group_id = 0;
- if (entity->IsPlayer() || entity->IsBot()) {
- m_playerInEncounter = true;
- if (entity->GetGroupMemberInfo())
- group_id = entity->GetGroupMemberInfo()->group_id;
- }
- // Insert the entity into the encounter list, if there is a group add all group members as well
- // TODO: add raid members
- MEncounter.writelock(__FUNCTION__, __LINE__);
- if (group_id > 0) {
- world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
- deque<GroupMemberInfo*>::iterator itr;
- deque<GroupMemberInfo*>* members = world.GetGroupManager()->GetGroupMembers(group_id);
- for (itr = members->begin(); itr != members->end(); itr++) {
- if ((*itr)->client)
- m_encounter.push_back((*itr)->client->GetPlayer()->GetID());
- }
- world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
- }
- else {
- m_encounter.push_back(entity->GetID());
- }
- MEncounter.releasewritelock(__FUNCTION__, __LINE__);
- }
- bool Brain::CheckLootAllowed(Entity* entity) {
- bool ret = false;
- vector<int32>::iterator itr;
- // Check the encounter list to see if the given entity is in it, if so return true.
- MEncounter.readlock(__FUNCTION__, __LINE__);
- for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
- if ((*itr) == entity->GetID()) {
- // found the entity in the encounter list, set return value to true and break the loop
- ret = true;
- break;
- }
- }
- MEncounter.releasereadlock(__FUNCTION__, __LINE__);
- return ret;
- }
- int8 Brain::GetEncounterSize() {
- int8 ret = 0;
- MEncounter.readlock(__FUNCTION__, __LINE__);
- ret = (int8)m_encounter.size();
- MEncounter.releasereadlock(__FUNCTION__, __LINE__);
- return ret;
- }
- vector<int32>* Brain::GetEncounter() {
- vector<int32>* ret = new vector<int32>;
- vector<int32>::iterator itr;
- // Lock the list
- MEncounter.readlock(__FUNCTION__, __LINE__);
- // Loop over the list storing the values into the new list
- for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++)
- ret->push_back(*itr);
- // Unlock the list
- MEncounter.releasereadlock(__FUNCTION__, __LINE__);
- // Return the copy of the list
- return ret;
- }
- void Brain::ClearEncounter() {
- MEncounter.writelock(__FUNCTION__, __LINE__);
- m_encounter.clear();
- m_playerInEncounter = false;
- MEncounter.releasewritelock(__FUNCTION__, __LINE__);
- }
- /* Example of how to extend the default AI */
- CombatPetBrain::CombatPetBrain(NPC* body) : Brain(body) {
- // Make sure to have the " : Brain(body)" so it calls the parent class constructor
- // to set up the AI properly
- }
- CombatPetBrain::~CombatPetBrain() {
- }
- void CombatPetBrain::Think() {
- // We are extending the base brain so make sure to call the parent Think() function.
- // If we want to override then we could remove Brain::Think()
- Brain::Think();
- // All this Brain does is make the pet follow its owner, the combat comes from the default brain
- if (GetBody()->EngagedInCombat() || !GetBody()->IsPet() || GetBody()->IsMezzedOrStunned())
- return;
-
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName());
- // If owner is a player and player has stay set then return out
- if (GetBody()->GetOwner()->IsPlayer() && ((Player*)GetBody()->GetOwner())->GetInfoStruct()->pet_movement == 1)
- return;
- // Set target to owner
- Entity* target = GetBody()->GetOwner();
- GetBody()->SetTarget(target);
- // Get distance from the owner
- float distance = GetBody()->GetDistance(target);
- // If out of melee range then move closer
- if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())
- MoveCloser(target);
- }
- /* Example of how to override the default AI */
- NonCombatPetBrain::NonCombatPetBrain(NPC* body) : Brain(body) {
- // Make sure to have the " : Brain(body)" so it calls the parent class constructor
- // to set up the AI properly
- }
- NonCombatPetBrain::~NonCombatPetBrain() {
- }
- void NonCombatPetBrain::Think() {
- // All this Brain does is make the pet follow its owner
- if (!GetBody()->IsPet() || GetBody()->IsMezzedOrStunned())
- return;
-
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName());
- // Set target to owner
- Entity* target = GetBody()->GetOwner();
- GetBody()->SetTarget(target);
- // Get distance from the owner
- float distance = GetBody()->GetDistance(target);
- // If out of melee range then move closer
- if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())
- MoveCloser(target);
- }
- BlankBrain::BlankBrain(NPC* body) : Brain(body) {
- // Make sure to have the " : Brain(body)" so it calls the parent class constructor
- // to set up the AI properly
- SetTick(50000);
- }
- BlankBrain::~BlankBrain() {
- }
- void BlankBrain::Think() {
-
- }
- LuaBrain::LuaBrain(NPC* body) : Brain(body) {
- }
- LuaBrain::~LuaBrain() {
- }
- void LuaBrain::Think() {
- if (!lua_interface)
- return;
- const char* script = GetBody()->GetSpawnScript();
- if(script) {
- if (!lua_interface->RunSpawnScript(script, "Think", GetBody(), GetBody()->GetTarget())) {
- lua_interface->LogError("LUA LuaBrain error: was unable to call the Think function in the spawn script (%s)", script);
- }
- }
- else {
- LogWrite(NPC_AI__ERROR, 0, "NPC_AI", "Lua brain set on a spawn that doesn't have a script...");
- }
- }
- DumbFirePetBrain::DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time) : Brain(body) {
- m_expireTime = Timer::GetCurrentTime2() + expire_time;
- AddHate(target, INT_MAX);
- }
- DumbFirePetBrain::~DumbFirePetBrain() {
- }
- void DumbFirePetBrain::AddHate(Entity* entity, sint32 hate) {
- if (!GetMostHated())
- Brain::AddHate(entity, hate);
- }
- void DumbFirePetBrain::Think() {
- Entity* target = GetMostHated();
- if (target) {
- if (!GetBody()->IsMezzedOrStunned()) {
- // If the NPC is not in combat then put them in combat
- if (!GetBody()->EngagedInCombat()) {
- //GetBody()->ClearRunningLocations();
- GetBody()->CalculateRunningLocation(true);
- GetBody()->InCombat(true);
- }
- // Set the NPC's target to the most hated entity if it is not already.
- if (GetBody()->GetTarget() != target) {
- GetBody()->SetTarget(target);
- GetBody()->FaceTarget(target);
- }
- float distance = GetBody()->GetDistance(target);
- if(GetBody()->CheckLoS(target) && !GetBody()->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) {
- LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", GetBody()->GetName(), target->GetName());
- GetBody()->FaceTarget(target);
- ProcessMelee(target, distance);
- }
- }
- }
- else {
- // No hated target or time expired, kill this mob
- if (GetBody()->GetHP() > 0) {
- GetBody()->KillSpawn(GetBody());
- LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because there is no target.");
- }
- }
- if (Timer::GetCurrentTime2() > m_expireTime) {
- if (GetBody()->GetHP() > 0) {
- GetBody()->KillSpawn(GetBody());
- LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because timer expired.");
- }
- }
- }
|