1
0
Emagi 5ef2458026 Rule support for R_Combat, MaxChaseDistance and info struct float max_chase_distance
Rule support for R_Combat, MaxChaseDistance to restrict by server or zone the max chase distance instead of hardcoded 80.0.
Additionally GetInfoStruct float max_chase_distance added.

info struct takes precident when set greater than 0.0 for the max chase distance, otherwise we check the rule being greater than 0.0, otherwise we use the default 80 in the hardcode define
2025-05-06 19:48:01 -04:00

921 lines
30 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() {
if (m_body->IsPet() && m_body->GetOwner() && m_body->GetOwner()->IsPlayer()) {
Player* player = (Player*)m_body->GetOwner();
if(player->GetInfoStruct()->get_pet_id() == 0) {
player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(m_body));
player->SetCharSheetChanged(true);
}
}
// 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
// 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, false);
// target needs to be set before in combat is engaged
// 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->cast_on_aggro_completed = false;
m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AGGRO, target);
}
bool breakWaterPursuit = false;
if (m_body->IsWaterCreature() && !m_body->IsFlyingCreature() && !target->InWater())
breakWaterPursuit = true;
// Check to see if the NPC has exceeded the max chase distance
float maxChaseDist = MAX_CHASE_DISTANCE;
if(m_body->GetInfoStruct()->get_max_chase_distance() > 0.0f) {
maxChaseDist = m_body->GetInfoStruct()->get_max_chase_distance();
}
else if(rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxChaseDistance)->GetFloat() > 0.0f) {
maxChaseDist = rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxChaseDistance)->GetFloat();
}
if (run_back_distance > maxChaseDist || breakWaterPursuit) {
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
if (target->IsPlayer() && ((Player*)target)->GetClient())
{
// Target is a client so send encounter break messages
if (m_body->HasSpawnGroup())
((Player*)target)->GetClient()->SimpleMessage(CHANNEL_NARRATIVE, "This encounter will no longer give encounter rewards.");
else
((Player*)target)->GetClient()->Message(CHANNEL_NARRATIVE, "%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, false);
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() && !m_body->GetOwner()->IsPlayer()))
m_body->SetHP(m_body->GetTotalHP());
}
CheckBuffs();
// If run back distance is greater then 0 then run back
if(!m_body->EngagedInCombat() && !m_body->IsPauseMovementTimerActive())
{
if (run_back_distance > 1 || (m_body->m_call_runback && !m_body->following)) {
m_body->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN);
m_body->UpdateEncounterState(ENCOUNTER_STATE_BROKEN);
m_body->GetZone()->AddChangedSpawn(m_body);
m_body->Runback(run_back_distance);
m_body->m_call_runback = false;
}
else if (m_body->GetRunbackLocation())
{
switch(m_body->GetRunbackLocation()->stage)
{
case 0:
m_body->GetZone()->movementMgr->StopNavigation((Entity*)m_body);
m_body->ClearRunningLocations();
m_body->SetX(m_body->GetRunbackLocation()->x,false);
m_body->SetZ(m_body->GetRunbackLocation()->z,false);
m_body->SetY(m_body->GetRunbackLocation()->y,false);
m_body->CalculateRunningLocation(true);
m_body->GetRunbackLocation()->stage = 1;
m_body->GetZone()->AddChangedSpawn(m_body);
break;
case 6: // artificially 1500ms per 250ms Think() call
if (m_body->GetRunbackLocation()->gridid > 0)
m_body->SetLocation(m_body->GetRunbackLocation()->gridid);
if(m_body->GetTempActionState() == 0)
m_body->SetTempActionState(-1);
m_body->SetHeading(m_body->m_runbackHeadingDir1,m_body->m_runbackHeadingDir2,false);
if(m_body->GetRunbackLocation()->reset_hp_on_runback)
m_body->SetHP(m_body->GetTotalHP());
m_body->ClearRunback();
if(m_body->GetLockedNoLoot() != ENCOUNTER_STATE_AVAILABLE && m_body->Alive()) {
m_body->SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE);
m_body->UpdateEncounterState(ENCOUNTER_STATE_AVAILABLE);
}
m_body->GetZone()->AddChangedSpawn(m_body);
break;
default: // captures case 1 up to case 5 to turn around / reset hp
m_body->GetRunbackLocation()->stage++; // artificially delay
break;
}
}
}
// 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;
else if (m_body->IsPet() && m_body->IsEntity() && ((Entity*)m_body)->GetOwner() == entity)
return;
if(m_body->IsImmune(IMMUNITY_TYPE_TAUNT))
{
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is immune to taunt from entity %s.", m_body->GetName(), entity ? entity->GetName() : "(null)");
if(entity && entity->IsPlayer())
((Player*)entity)->GetClient()->GetCurrentZone()->SendDamagePacket((Spawn*)entity, (Spawn*)m_body, DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG, DAMAGE_PACKET_RESULT_IMMUNE, 0, 0, "Hate");
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;
// take into consideration that 0 or negative hate is not valid, we need to properly reset the value
if(m_hatelist[entity->GetID()] < 1) {
m_hatelist[entity->GetID()] = 1;
}
}
else
m_hatelist.insert(std::pair<int32, sint32>(entity->GetID(), hate));
entity->MHatedBy.lock();
if (entity->HatedBy.count(m_body->GetID()) == 0)
entity->HatedBy.insert(m_body->GetID());
entity->MHatedBy.unlock();
// Unlock the list
bool ownerExistsAddHate = false;
if(entity->IsPet() && entity->GetOwner()) {
map<int32, sint32>::iterator itr = m_hatelist.find(entity->GetOwner()->GetID());
if(itr == m_hatelist.end()) {
ownerExistsAddHate = true;
}
}
MHateList.releasewritelock(__FUNCTION__, __LINE__);
if(ownerExistsAddHate) {
AddHate(entity->GetOwner(), 0);
}
}
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)->MHatedBy.lock();
((Entity*)spawn)->HatedBy.erase(m_body->GetID());
((Entity*)spawn)->MHatedBy.unlock();
}
}
// 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->MHatedBy.lock();
entity->HatedBy.erase(m_body->GetID());
entity->MHatedBy.unlock();
// 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);
}
void Brain::SendHateList(Client* client) {
MHateList.readlock(__FUNCTION__, __LINE__);
client->Message(CHANNEL_COLOR_YELLOW, "%s's HateList", m_body->GetName());
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
map<int32, sint32>::iterator itr;
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++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first);
// Compare the hate value for the current iteration to our stored highest value
if(ent) {
client->Message(CHANNEL_COLOR_YELLOW, "%s : %i", ent->GetName(), itr->second);
}
else {
client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity) : %i", itr->first, itr->second);
}
}
}
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
MHateList.releasereadlock(__FUNCTION__, __LINE__);
}
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(Spawn* target) {
if (target && m_body->GetFollowTarget() != target)
m_body->SetFollowTarget(target, rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat());
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(target, 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;
BrainCastSpell(spell, spell_target, false);
return true;
}
return false;
}
bool Brain::BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc) {
if (spell) {
if(calculate_run_loc) {
m_body->CalculateRunningLocation(true);
}
m_body->GetZone()->ProcessSpell(spell, m_body, cast_on);
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(m_body);
bool casted_on = false;
if(!(casted_on = BrainCastSpell(spell, m_body)) && m_body->IsNPC() && ((NPC*)m_body)->HasSpells()) {
Spawn* target = nullptr;
vector<Spawn*>* group = m_body->GetSpawnGroup();
if(group && group->size() > 0){
vector<Spawn*>::iterator itr;
for(itr = group->begin(); itr != group->end(); itr++){
Spawn* spawn = (*itr);
if(spawn->IsEntity() && spawn != m_body) {
if(target) {
Spell* spell = m_body->GetNextBuffSpell(spawn);
SpellEffects* se = ((Entity*)spawn)->GetSpellEffect(spell->GetSpellData()->id);
if(!se && BrainCastSpell(spell, spawn)) {
casted_on = true;
break;
}
}
}
}
}
safe_delete(group);
}
return casted_on;
}
void Brain::ProcessMelee(Entity* target, float distance) {
if(distance > rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser((Spawn*)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() && entity->GetOwner() && !entity->IsBot()) {
MEncounter.writelock(__FUNCTION__, __LINE__);
bool success = AddToEncounter(entity->GetID());
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
if(!success)
return;
entity = entity->GetOwner();
}
else if(entity->HasPet() && entity->GetPet()) {
MEncounter.writelock(__FUNCTION__, __LINE__);
bool success = AddToEncounter(entity->GetPet()->GetID());
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
if(!success)
return;
}
// 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;
PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id);
if (group)
{
std::vector<int32> raidGroups;
group->GetRaidGroups(&raidGroups);
if(raidGroups.size() < 1)
raidGroups.push_back(group_id);
std::vector<int32>::iterator group_itr;
for(group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) {
group = world.GetGroupManager()->GetGroup((*group_itr));
if(group) {
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (itr = members->begin(); itr != members->end(); itr++) {
if ((*itr)->member)
{
bool success = AddToEncounter((*itr)->member->GetID());
if((*itr)->client && success) {
m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID()));
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
else {
bool success = AddToEncounter(entity->GetID());
if (success && 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;
if (m_body)
{
if ((m_body->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && m_body->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->GetLooterSpawnID() > 0 && m_body->GetLooterSpawnID() != entity->GetID()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter spawn id %u does not match received %s(%u)", GetBody()->GetName(), m_body->GetLooterSpawnID(), entity->GetName(), entity->GetID());
return false;
}
if (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, AllowChestUnlockByDropTime)->GetInt8()
&& m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, ChestUnlockedTimeDrop)->GetInt32() * 1000)) {
return true;
}
if (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, AllowChestUnlockByTrapTime)->GetInt8()
&& m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, ChestUnlockedTimeTrap)->GetInt32() * 1000)) {
return true;
}
if ((m_body->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || m_body->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->HasSpawnLootWindowCompleted(entity->GetID())) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter %s(%u) has already completed their lotto selections.", GetBody()->GetName(), entity->GetName(), entity->GetID());
return false;
}
}
// 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;
}
bool Brain::IsPlayerInEncounter(int32 char_id) {
bool ret = false;
MEncounter.readlock(__FUNCTION__, __LINE__);
std::map<int32,int32>::iterator itr = m_encounter_playerlist.find(char_id);
if(itr != m_encounter_playerlist.end()) {
ret = true;
}
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Brain::IsEntityInEncounter(int32 id, bool skip_read_lock) {
bool ret = false;
if(!skip_read_lock) {
MEncounter.readlock(__FUNCTION__, __LINE__);
}
vector<int32>::iterator itr;
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
if ((*itr) == id) {
// found the entity in the encounter list, set return value to true and break the loop
ret = true;
break;
}
}
if(!skip_read_lock) {
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
int32 Brain::CountPlayerBotInEncounter() {
int32 count = 0;
vector<int32>::iterator itr;
MEncounter.readlock(__FUNCTION__, __LINE__);
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr));
if (ent && (ent->IsPlayer() || ent->IsBot())) {
count++;
}
}
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return count;
}
bool Brain::AddToEncounter(int32 id) {
if(!IsEntityInEncounter(id, true)) {
m_encounter.push_back(id);
return true;
}
return false;
}
void Brain::ClearEncounter() {
MEncounter.writelock(__FUNCTION__, __LINE__);
if(m_body) {
m_body->RemoveSpells(true);
}
m_encounter.clear();
m_encounter_playerlist.clear();
m_playerInEncounter = false;
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
}
void Brain::SendEncounterList(Client* client) {
client->Message(CHANNEL_COLOR_YELLOW, "%s's EncounterList", m_body->GetName());
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
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++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr));
// Compare the hate value for the current iteration to our stored highest value
if(ent) {
client->Message(CHANNEL_COLOR_YELLOW, "%s", ent->GetName());
}
else {
client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity)", (*itr));
}
}
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
MEncounter.releasereadlock(__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() && GetBody()->GetOwner()->IsPlayer() && ((Player*)GetBody()->GetOwner())->GetInfoStruct()->get_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.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser((Spawn*)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.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser((Spawn*)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()) {
// 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, false);
}
// target needs to be identified before combat setting
// If the NPC is not in combat then put them in combat
if (!GetBody()->EngagedInCombat()) {
//GetBody()->ClearRunningLocations();
GetBody()->CalculateRunningLocation(true);
GetBody()->InCombat(true);
}
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, false);
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.");
}
}
}