423163d30c
This reverts commit cb352a6632
.
652 lines
No EOL
20 KiB
C++
652 lines
No EOL
20 KiB
C++
/*
|
|
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()) {
|
|
// 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);
|
|
m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AGGRO, target);
|
|
}
|
|
|
|
// 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);
|
|
if (m_body->GetRunbackLocation()->gridid > 0)
|
|
m_body->SetLocation(m_body->GetRunbackLocation()->gridid);
|
|
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) {
|
|
// do not aggro when running back, despite taking damage
|
|
if (m_body->IsNPC() && ((NPC*)m_body)->m_runningBack)
|
|
return;
|
|
|
|
// 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());
|
|
m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID()));
|
|
}
|
|
}
|
|
|
|
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
|
|
}
|
|
else {
|
|
m_encounter.push_back(entity->GetID());
|
|
if (entity->IsPlayer())
|
|
{
|
|
Player* plyr = (Player*)entity;
|
|
m_encounter_playerlist.insert(make_pair(plyr->GetCharacterID(), 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__);
|
|
if (entity->IsPlayer())
|
|
{
|
|
Player* plyr = (Player*)entity;
|
|
|
|
map<int32, int32>::iterator itr = m_encounter_playerlist.find(plyr->GetCharacterID());
|
|
if (itr != m_encounter_playerlist.end())
|
|
{
|
|
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
|
|
return true;
|
|
}
|
|
|
|
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
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_encounter_playerlist.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.");
|
|
}
|
|
}
|
|
} |