Files
TCPlayerbotCore/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
T
Machiavelli dbbac0bdaa Core/Movement: Implement spline movement subsystem.
Spline movement controls movements of server-side controlled units (monster movement, taxi movement, etc).
Proper implementation of effects such as charge, jump, cyclic movement will rely on it.
However, need improve our states system before.

Technical changes:

* Added linear, catmullrom and bezier3 splines which based on client's algorthims. They can be reused for proper transport position interpolation.
* Precission increased. There are no more position desync issues since client's position calculation formulas used.
* Now possible to move by paths with multiple points, send whole path to client.

--
Original author of research and implementation: SilverIce. Massive kudos.
Original port for Trinity (ref #4629) Chaplain and Venugh
With the following incremental fixes during my review:

- Restore flightmaster end grid pre-loading
- Fix uninitialized Creature::m_path_id
- Add missing trinity_string entries for .movegens command
- Fix a bug in WaypointMovementGenerator that would trigger unexpected pausing at waypoints for various amounts of time

Known issues:
- Errors like WaypointMovementGenerator::LoadPath creature XXX (Entry: YYYYY GUID: ZZZZZZ) doesn't have waypoint path id: 0.
This is caused by bad DB data. This commit didn't "break" it.

Do not forget to re-run CMake before compiling.
2012-01-14 15:36:07 +01:00

686 lines
19 KiB
C++

/* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
*
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
*
* This program is free software licensed under GPL version 2
* Please see the included DOCS/LICENSE.TXT for more information */
#include "ScriptPCH.h"
#include "Item.h"
#include "Spell.h"
#include "ObjectMgr.h"
#include "TemporarySummon.h"
// Spell summary for ScriptedAI::SelectSpell
struct TSpellSummary
{
uint8 Targets; // set of enum SelectTarget
uint8 Effects; // set of enum SelectEffect
} extern* SpellSummary;
void SummonList::DoZoneInCombat(uint32 entry)
{
for (iterator i = begin(); i != end();)
{
Creature* summon = Unit::GetCreature(*me, *i);
++i;
if (summon && summon->IsAIEnabled
&& (!entry || summon->GetEntry() == entry))
summon->AI()->DoZoneInCombat();
}
}
void SummonList::DoAction(uint32 entry, int32 info)
{
for (iterator i = begin(); i != end();)
{
Creature* summon = Unit::GetCreature(*me, *i);
++i;
if (summon && summon->IsAIEnabled
&& (!entry || summon->GetEntry() == entry))
summon->AI()->DoAction(info);
}
}
void SummonList::DespawnEntry(uint32 entry)
{
for (iterator i = begin(); i != end();)
{
Creature* summon = Unit::GetCreature(*me, *i);
if (!summon)
erase(i++);
else if (summon->GetEntry() == entry)
{
erase(i++);
summon->DespawnOrUnsummon();
}
else
++i;
}
}
void SummonList::DespawnAll()
{
while (!empty())
{
Creature* summon = Unit::GetCreature(*me, *begin());
if (!summon)
erase(begin());
else
{
erase(begin());
summon->DespawnOrUnsummon();
}
}
}
void SummonList::RemoveNotExisting()
{
for (iterator i = begin(); i != end();)
{
if (Unit::GetCreature(*me, *i))
++i;
else
erase(i++);
}
}
bool SummonList::HasEntry(uint32 entry)
{
for (iterator i = begin(); i != end();)
{
Creature* summon = Unit::GetCreature(*me, *i);
if (!summon)
erase(i++);
else if (summon->GetEntry() == entry)
return true;
else
++i;
}
return false;
}
ScriptedAI::ScriptedAI(Creature* creature) : CreatureAI(creature),
me(creature),
IsFleeing(false),
_evadeCheckCooldown(2500),
_isCombatMovementAllowed(true)
{
_isHeroic = me->GetMap()->IsHeroic();
_difficulty = Difficulty(me->GetMap()->GetSpawnMode());
}
void ScriptedAI::AttackStartNoMove(Unit* who)
{
if (!who)
return;
if (me->Attack(who, false))
DoStartNoMovement(who);
}
void ScriptedAI::UpdateAI(uint32 const /*diff*/)
{
//Check if we have a current target
if (!UpdateVictim())
return;
if (me->isAttackReady())
{
//If we are within range melee the target
if (me->IsWithinMeleeRange(me->getVictim()))
{
me->AttackerStateUpdate(me->getVictim());
me->resetAttackTimer();
}
}
}
void ScriptedAI::DoStartMovement(Unit* victim, float distance, float angle)
{
if (victim)
me->GetMotionMaster()->MoveChase(victim, distance, angle);
}
void ScriptedAI::DoStartNoMovement(Unit* victim)
{
if (!victim)
return;
me->GetMotionMaster()->MoveIdle();
}
void ScriptedAI::DoStopAttack()
{
if (me->getVictim())
me->AttackStop();
}
void ScriptedAI::DoCastSpell(Unit* target, SpellInfo const* spellInfo, bool triggered)
{
if (!target || me->IsNonMeleeSpellCasted(false))
return;
me->StopMoving();
me->CastSpell(target, spellInfo, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
}
void ScriptedAI::DoPlaySoundToSet(WorldObject* source, uint32 soundId)
{
if (!source)
return;
if (!GetSoundEntriesStore()->LookupEntry(soundId))
{
sLog->outError("TSCR: Invalid soundId %u used in DoPlaySoundToSet (Source: TypeId %u, GUID %u)", soundId, source->GetTypeId(), source->GetGUIDLow());
return;
}
source->PlayDirectSound(soundId);
}
Creature* ScriptedAI::DoSpawnCreature(uint32 entry, float offsetX, float offsetY, float offsetZ, float angle, uint32 type, uint32 despawntime)
{
return me->SummonCreature(entry, me->GetPositionX() + offsetX, me->GetPositionY() + offsetY, me->GetPositionZ() + offsetZ, angle, TempSummonType(type), despawntime);
}
SpellInfo const* ScriptedAI::SelectSpell(Unit* target, uint32 school, uint32 mechanic, SelectTargetType targets, uint32 powerCostMin, uint32 powerCostMax, float rangeMin, float rangeMax, SelectEffect effects)
{
//No target so we can't cast
if (!target)
return false;
//Silenced so we can't cast
if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
return false;
//Using the extended script system we first create a list of viable spells
SpellInfo const* apSpell[CREATURE_MAX_SPELLS];
memset(apSpell, 0, CREATURE_MAX_SPELLS * sizeof(SpellInfo*));
uint32 spellCount = 0;
SpellInfo const* tempSpell = NULL;
//Check if each spell is viable(set it to null if not)
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; i++)
{
tempSpell = sSpellMgr->GetSpellInfo(me->m_spells[i]);
//This spell doesn't exist
if (!tempSpell)
continue;
// Targets and Effects checked first as most used restrictions
//Check the spell targets if specified
if (targets && !(SpellSummary[me->m_spells[i]].Targets & (1 << (targets-1))))
continue;
//Check the type of spell if we are looking for a specific spell type
if (effects && !(SpellSummary[me->m_spells[i]].Effects & (1 << (effects-1))))
continue;
//Check for school if specified
if (school && (tempSpell->SchoolMask & school) == 0)
continue;
//Check for spell mechanic if specified
if (mechanic && tempSpell->Mechanic != mechanic)
continue;
//Make sure that the spell uses the requested amount of power
if (powerCostMin && tempSpell->ManaCost < powerCostMin)
continue;
if (powerCostMax && tempSpell->ManaCost > powerCostMax)
continue;
//Continue if we don't have the mana to actually cast this spell
if (tempSpell->ManaCost > me->GetPower(Powers(tempSpell->PowerType)))
continue;
//Check if the spell meets our range requirements
if (rangeMin && me->GetSpellMinRangeForTarget(target, tempSpell) < rangeMin)
continue;
if (rangeMax && me->GetSpellMaxRangeForTarget(target, tempSpell) > rangeMax)
continue;
//Check if our target is in range
if (me->IsWithinDistInMap(target, float(me->GetSpellMinRangeForTarget(target, tempSpell))) || !me->IsWithinDistInMap(target, float(me->GetSpellMaxRangeForTarget(target, tempSpell))))
continue;
//All good so lets add it to the spell list
apSpell[spellCount] = tempSpell;
++spellCount;
}
//We got our usable spells so now lets randomly pick one
if (!spellCount)
return NULL;
return apSpell[urand(0, spellCount - 1)];
}
void ScriptedAI::DoResetThreat()
{
if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty())
{
sLog->outError("TSCR: DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry());
return;
}
std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
{
Unit* unit = Unit::GetUnit((*me), (*itr)->getUnitGuid());
if (unit && DoGetThreat(unit))
DoModifyThreatPercent(unit, -100);
}
}
float ScriptedAI::DoGetThreat(Unit* unit)
{
if (!unit)
return 0.0f;
return me->getThreatManager().getThreat(unit);
}
void ScriptedAI::DoModifyThreatPercent(Unit* unit, int32 pct)
{
if (!unit)
return;
me->getThreatManager().modifyThreatPercent(unit, pct);
}
void ScriptedAI::DoTeleportTo(float x, float y, float z, uint32 time)
{
me->Relocate(x, y, z);
float speed = me->GetDistance(x, y, z) / ((float)time * 0.001f);
me->MonsterMoveWithSpeed(x, y, z, speed);
}
void ScriptedAI::DoTeleportTo(const float position[4])
{
me->NearTeleportTo(position[0], position[1], position[2], position[3]);
}
void ScriptedAI::DoTeleportPlayer(Unit* unit, float x, float y, float z, float o)
{
if (!unit || unit->GetTypeId() != TYPEID_PLAYER)
{
if (unit)
sLog->outError("TSCR: Creature " UI64FMTD " (Entry: %u) Tried to teleport non-player unit (Type: %u GUID: " UI64FMTD ") to x: %f y:%f z: %f o: %f. Aborted.", me->GetGUID(), me->GetEntry(), unit->GetTypeId(), unit->GetGUID(), x, y, z, o);
return;
}
CAST_PLR(unit)->TeleportTo(unit->GetMapId(), x, y, z, o, TELE_TO_NOT_LEAVE_COMBAT);
}
void ScriptedAI::DoTeleportAll(float x, float y, float z, float o)
{
Map* map = me->GetMap();
if (!map->IsDungeon())
return;
Map::PlayerList const& PlayerList = map->GetPlayers();
for (Map::PlayerList::const_iterator itr = PlayerList.begin(); itr != PlayerList.end(); ++itr)
if (Player* player = itr->getSource())
if (player->isAlive())
player->TeleportTo(me->GetMapId(), x, y, z, o, TELE_TO_NOT_LEAVE_COMBAT);
}
Unit* ScriptedAI::DoSelectLowestHpFriendly(float range, uint32 minHPDiff)
{
Unit* unit = NULL;
Trinity::MostHPMissingInRange u_check(me, range, minHPDiff);
Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, unit, u_check);
me->VisitNearbyObject(range, searcher);
return unit;
}
std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float range)
{
std::list<Creature*> list;
Trinity::FriendlyCCedInRange u_check(me, range);
Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, list, u_check);
me->VisitNearbyObject(range, searcher);
return list;
}
std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float range, uint32 uiSpellid)
{
std::list<Creature*> list;
Trinity::FriendlyMissingBuffInRange u_check(me, range, uiSpellid);
Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, list, u_check);
me->VisitNearbyObject(range, searcher);
return list;
}
Player* ScriptedAI::GetPlayerAtMinimumRange(float minimumRange)
{
Player* player = NULL;
CellCoord pair(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY()));
Cell cell(pair);
cell.SetNoCreate();
Trinity::PlayerAtMinimumRangeAway check(me, minimumRange);
Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway> searcher(me, player, check);
TypeContainerVisitor<Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway>, GridTypeMapContainer> visitor(searcher);
cell.Visit(pair, visitor, *me->GetMap(), *me, minimumRange);
return player;
}
void ScriptedAI::SetEquipmentSlots(bool loadDefault, int32 mainHand /*= EQUIP_NO_CHANGE*/, int32 offHand /*= EQUIP_NO_CHANGE*/, int32 ranged /*= EQUIP_NO_CHANGE*/)
{
if (loadDefault)
{
if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(me->GetEntry()))
me->LoadEquipment(creatureInfo->equipmentId, true);
return;
}
if (mainHand >= 0)
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(mainHand));
if (offHand >= 0)
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(offHand));
if (ranged >= 0)
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(ranged));
}
void ScriptedAI::SetCombatMovement(bool allowMovement)
{
_isCombatMovementAllowed = allowMovement;
}
enum eNPCs
{
NPC_BROODLORD = 12017,
NPC_VOID_REAVER = 19516,
NPC_JAN_ALAI = 23578,
NPC_SARTHARION = 28860
};
// Hacklike storage used for misc creatures that are expected to evade of outside of a certain area.
// It is assumed the information is found elswehere and can be handled by the core. So far no luck finding such information/way to extract it.
bool ScriptedAI::EnterEvadeIfOutOfCombatArea(uint32 const diff)
{
if (_evadeCheckCooldown <= diff)
_evadeCheckCooldown = 2500;
else
{
_evadeCheckCooldown -= diff;
return false;
}
if (me->IsInEvadeMode() || !me->getVictim())
return false;
float x = me->GetPositionX();
float y = me->GetPositionY();
float z = me->GetPositionZ();
switch (me->GetEntry())
{
case NPC_BROODLORD: // broodlord (not move down stairs)
if (z > 448.60f)
return false;
break;
case NPC_VOID_REAVER: // void reaver (calculate from center of room)
if (me->GetDistance2d(432.59f, 371.93f) < 105.0f)
return false;
break;
case NPC_JAN_ALAI: // jan'alai (calculate by Z)
if (z > 12.0f)
return false;
break;
case NPC_SARTHARION: // sartharion (calculate box)
if (x > 3218.86f && x < 3275.69f && y < 572.40f && y > 484.68f)
return false;
break;
default: // For most of creatures that certain area is their home area.
sLog->outDetail("TSCR: EnterEvadeIfOutOfCombatArea used for creature entry %u, but does not have any definition. Using the default one.", me->GetEntry());
uint32 homeAreaId = me->GetMap()->GetAreaId(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY(), me->GetHomePosition().GetPositionZ());
if (me->GetAreaId() == homeAreaId)
return false;
}
EnterEvadeMode();
return true;
}
void Scripted_NoMovementAI::AttackStart(Unit* target)
{
if (!target)
return;
if (me->Attack(target, true))
DoStartNoMovement(target);
}
// BossAI - for instanced bosses
BossAI::BossAI(Creature* creature, uint32 bossId) : ScriptedAI(creature),
instance(creature->GetInstanceScript()),
summons(creature),
_boundary(instance ? instance->GetBossBoundary(bossId) : NULL),
_bossId(bossId)
{
}
void BossAI::_Reset()
{
if (!me->isAlive())
return;
me->ResetLootMode();
events.Reset();
summons.DespawnAll();
if (instance)
instance->SetBossState(_bossId, NOT_STARTED);
}
void BossAI::_JustDied()
{
events.Reset();
summons.DespawnAll();
if (instance)
{
instance->SetBossState(_bossId, DONE);
instance->SaveToDB();
}
}
void BossAI::_EnterCombat()
{
me->setActive(true);
DoZoneInCombat();
if (instance)
{
// bosses do not respawn, check only on enter combat
if (!instance->CheckRequiredBosses(_bossId))
{
EnterEvadeMode();
return;
}
instance->SetBossState(_bossId, IN_PROGRESS);
}
}
void BossAI::TeleportCheaters()
{
float x, y, z;
me->GetPosition(x, y, z);
std::list<HostileReference*>& threatList = me->getThreatManager().getThreatList();
for (std::list<HostileReference*>::iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
if (Unit* target = (*itr)->getTarget())
if (target->GetTypeId() == TYPEID_PLAYER && !CheckBoundary(target))
target->NearTeleportTo(x, y, z, 0);
}
bool BossAI::CheckBoundary(Unit* who)
{
if (!GetBoundary() || !who)
return true;
for (BossBoundaryMap::const_iterator itr = GetBoundary()->begin(); itr != GetBoundary()->end(); ++itr)
{
switch (itr->first)
{
case BOUNDARY_N:
if (me->GetPositionX() > itr->second)
return false;
break;
case BOUNDARY_S:
if (me->GetPositionX() < itr->second)
return false;
break;
case BOUNDARY_E:
if (me->GetPositionY() < itr->second)
return false;
break;
case BOUNDARY_W:
if (me->GetPositionY() > itr->second)
return false;
break;
case BOUNDARY_NW:
if (me->GetPositionX() + me->GetPositionY() > itr->second)
return false;
break;
case BOUNDARY_SE:
if (me->GetPositionX() + me->GetPositionY() < itr->second)
return false;
break;
case BOUNDARY_NE:
if (me->GetPositionX() - me->GetPositionY() > itr->second)
return false;
break;
case BOUNDARY_SW:
if (me->GetPositionX() - me->GetPositionY() < itr->second)
return false;
break;
default:
break;
}
}
return true;
}
void BossAI::JustSummoned(Creature* summon)
{
summons.Summon(summon);
if (me->isInCombat())
DoZoneInCombat(summon);
}
void BossAI::SummonedCreatureDespawn(Creature* summon)
{
summons.Despawn(summon);
}
void BossAI::UpdateAI(uint32 const diff)
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STAT_CASTING))
return;
while (uint32 eventId = events.ExecuteEvent())
ExecuteEvent(eventId);
DoMeleeAttackIfReady();
}
// WorldBossAI - for non-instanced bosses
WorldBossAI::WorldBossAI(Creature* creature) :
ScriptedAI(creature),
summons(creature)
{
}
void WorldBossAI::_Reset()
{
if (!me->isAlive())
return;
events.Reset();
summons.DespawnAll();
}
void WorldBossAI::_JustDied()
{
events.Reset();
summons.DespawnAll();
}
void WorldBossAI::_EnterCombat()
{
Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true);
if (target)
AttackStart(target);
}
void WorldBossAI::JustSummoned(Creature* summon)
{
summons.Summon(summon);
Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true);
if (target)
summon->AI()->AttackStart(target);
}
void WorldBossAI::SummonedCreatureDespawn(Creature* summon)
{
summons.Despawn(summon);
}
void WorldBossAI::UpdateAI(uint32 const diff)
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STAT_CASTING))
return;
while (uint32 eventId = events.ExecuteEvent())
ExecuteEvent(eventId);
DoMeleeAttackIfReady();
}
// SD2 grid searchers.
Creature* GetClosestCreatureWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool alive /*= true*/)
{
return source->FindNearestCreature(entry, maxSearchRange, alive);
}
GameObject* GetClosestGameObjectWithEntry(WorldObject* source, uint32 entry, float maxSearchRange)
{
return source->FindNearestGameObject(entry, maxSearchRange);
}
void GetCreatureListWithEntryInGrid(std::list<Creature*>& list, WorldObject* source, uint32 entry, float maxSearchRange)
{
source->GetCreatureListWithEntryInGrid(list, entry, maxSearchRange);
}
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& list, WorldObject* source, uint32 entry, float maxSearchRange)
{
source->GetGameObjectListWithEntryInGrid(list, entry, maxSearchRange);
}