mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-16 13:09:50 -04:00
Core/SmartAI: Various fixes and extensions for smart scripts: (#18673)
- Possible crashes fixed - Memory leak fixed - Implemented checking of vehicle conditions - Extended eventphasemask to 12 bits (sql required to change DB field type) - SMART_EVENT_GOSSIP_HELLO - added possibility to detect for gameobject reportUse call - Renamed action SMART_ACTION_SET_FLY to SMART_ACTION_SET_DISABLE_GRAVITY (to reflect actual functionality) - Added targetsLimit to action SMART_ACTION_CAST and SMART_ACTION_INVOKER_CAST to limit max amount of targets (selected randomly) - Action SMART_ACTION_TALK corrected to always work as intended - Properly call GroupEventHappens in action SMART_ACTION_CALL_GROUPEVENTHAPPENS if invoker was charmed or owned by the player - Properly utilize followAngle in action SMART_ACTION_FOLLOW (db orientation should be in degrees), but keep backward compatibility - Added action SMART_ACTION_SET_CAN_FLY (119) 0/1 - Added action SMART_ACTION_REMOVE_AURAS_BY_TYPE (120) AuraType, can be used to exit vehicle for example - Added action SMART_ACTION_SET_SIGHT_DIST (121) sightDist - Added action SMART_ACTION_FLEE (122) fleeTime - Added action SMART_ACTION_ADD_THREAT (123) +threat, -threat - Added action SMART_ACTION_LOAD_EQUIPMENT (124) equipmentId - Added action SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT (125) minId, maxId - Added action SMART_ACTION_REMOVE_ALL_GAMEOBJECTS (126), removes all owned gameobjects - Added action SMART_ACTION_STOP_MOTION (127), stopMoving, movementExpired - Extended target SMART_TARGET_HOSTILE_SECOND_AGGRO with following parameters maxdist, playerOnly, powerType + 1 - Extended target SMART_TARGET_HOSTILE_LAST_AGGRO with following parameters maxdist, playerOnly, powerType + 1 - Extended target SMART_TARGET_HOSTILE_RANDOM with following parameters maxdist, playerOnly, powerType + 1 - Extended target SMART_TARGET_HOSTILE_RANDOM_NOT_TOP with following parameters maxdist, playerOnly, powerType + 1 - Extended target SMART_TARGET_THREAT_LIST with maxdist - Extended target SMART_TARGET_OWNER_OR_SUMMONER to be able to get charmer/owner of current owner - Added new target SMART_TARGET_FARTHEST with maxDist, playerOnly, isInLos restrictions - Added SpellHit hook to GameObjectAI and extended SmartGameObjectAI to call SMART_EVENT_SPELLHIT when gameobject is hit by spell - Call GameObjectAI Reset hook on gameobject respawn (for ex. to reset one time events in smart scripts) - Fixed some logic errors in code - SmartAI Escorts properly despawn escortee if no players are in range - Disable Evading while charming creature with SmartAI - Don't call SMART_EVENT_RESPAWN for dead units before they actually respawn - Don't call SMART_EVENT_RESPAWN for not spawned gameobjects - Properly call SMART_EVENT_RESPAWN for gameobject respawn - Allow action SMART_ACTION_SET_IN_COMBAT_WITH_ZONE to utilize targetlist - Allow action SMART_ACTION_CALL_FOR_HELP to utilize targetList - Allow action SMART_ACTION_SET_INVINCIBILITY_HP_LEVEL to utilize targetList - Allow action SMART_ACTION_SET_VISIBILITY to utilize targetList - Allow action SMART_ACTION_SET_ACTIVE to utilize targetList - Allow action SMART_ACTION_ATTACK_START to select random attack target instead of first on the list - Allow gameobjects to summon gameobjects with action SMART_ACTION_SUMMON_GO - Properly store action invokers for action SMART_ACTION_WP_START, if no player invokers are found, distance despawn check won't be used - Allow action SMART_ACTION_WP_RESUME to compensate for the state the unit actually is in (eg. combat) - Allow action SMART_ACTION_MOVE_TO_POS to select random of the avaiable targets, not only the first one. - Allow action SMART_ACTION_MOVE_TO_POS to utilize x, y, z parameters as an offset to calculated coordinates - Action SMART_ACTION_RESPAWN_TARGET should never modify respawntime of already spawned gameobjects, use dedicated function - Properly delete ontime events created by SMART_ACTION_CREATE_TIMED_EVENT - If action could not be started because conditions were not satisfied, do not recalculate the waittime to action repeattime, use smaller value to recheck more frequently - Allow target SMART_TARGET_CLOSEST_PLAYER to be used by gameobjects - Allow target SMART_TARGET_OWNER_OR_SUMMONER to be used by gameobjects - Fixed SMART_EVENT_COUNTER_SET to be only called for the id that was incremented - Changed the way counters work - Protect PhaseInc from surpassing maximum phase - Added loading checks for missing NON_REPEATABLE flag if no repeatmin, repeatmax is set - Added spell validation for SMART_ACTION_CROSS_CAST
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
#include "Group.h"
|
||||
#include "SmartAI.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
SmartAI::SmartAI(Creature* c) : CreatureAI(c)
|
||||
{
|
||||
@@ -36,6 +37,7 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c)
|
||||
mCurrentWPID = 0;//first wp id is 1 !!
|
||||
mWPReached = false;
|
||||
mWPPauseTimer = 0;
|
||||
mEscortNPCFlags = 0;
|
||||
mLastWP = nullptr;
|
||||
|
||||
mCanRepeatPath = false;
|
||||
@@ -69,6 +71,8 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c)
|
||||
mInvincibilityHpLevel = 0;
|
||||
|
||||
mJustReset = false;
|
||||
mConditionsTimer = 0;
|
||||
mHasConditions = sConditionMgr->HasConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, c->GetEntry());
|
||||
}
|
||||
|
||||
bool SmartAI::IsAIControlled() const
|
||||
@@ -76,7 +80,7 @@ bool SmartAI::IsAIControlled() const
|
||||
return !mIsCharmed;
|
||||
}
|
||||
|
||||
void SmartAI::UpdateDespawn(const uint32 diff)
|
||||
void SmartAI::UpdateDespawn(uint32 diff)
|
||||
{
|
||||
if (mDespawnState <= 1 || mDespawnState > 3)
|
||||
return;
|
||||
@@ -113,28 +117,39 @@ WayPoint* SmartAI::GetNextWayPoint()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SmartAI::StartPath(bool run, uint32 path, bool repeat, Unit* /*invoker*/)
|
||||
void SmartAI::StartPath(bool run, uint32 path, bool repeat, Unit* invoker)
|
||||
{
|
||||
if (me->IsInCombat())// no wp movement in combat
|
||||
{
|
||||
TC_LOG_ERROR("misc", "SmartAI::StartPath: Creature entry %u wanted to start waypoint movement while in combat, ignoring.", me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(SMART_ESCORT_ESCORTING))
|
||||
StopPath();
|
||||
|
||||
if (path)
|
||||
{
|
||||
if (!LoadPath(path))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mWayPoints || mWayPoints->empty())
|
||||
return;
|
||||
|
||||
AddEscortState(SMART_ESCORT_ESCORTING);
|
||||
mCanRepeatPath = repeat;
|
||||
|
||||
SetRun(run);
|
||||
|
||||
if (WayPoint* wp = GetNextWayPoint())
|
||||
{
|
||||
AddEscortState(SMART_ESCORT_ESCORTING);
|
||||
mCanRepeatPath = repeat;
|
||||
|
||||
SetRun(run);
|
||||
|
||||
if (invoker && invoker->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
mEscortNPCFlags = me->GetUInt32Value(UNIT_NPC_FLAGS);
|
||||
me->SetUInt32Value(UNIT_NPC_FLAGS, 0);
|
||||
}
|
||||
|
||||
mLastOOCPos = me->GetPosition();
|
||||
me->GetMotionMaster()->MovePoint(wp->id, wp->x, wp->y, wp->z);
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_START, nullptr, wp->id, GetScript()->GetPathId());
|
||||
@@ -204,6 +219,12 @@ void SmartAI::EndPath(bool fail)
|
||||
mWPPauseTimer = 0;
|
||||
mLastWP = nullptr;
|
||||
|
||||
if (mEscortNPCFlags)
|
||||
{
|
||||
me->SetUInt32Value(UNIT_NPC_FLAGS, mEscortNPCFlags);
|
||||
mEscortNPCFlags = 0;
|
||||
}
|
||||
|
||||
if (mCanRepeatPath)
|
||||
{
|
||||
if (IsAIControlled())
|
||||
@@ -280,12 +301,21 @@ void SmartAI::UpdatePath(const uint32 diff)
|
||||
return;
|
||||
if (mEscortInvokerCheckTimer < diff)
|
||||
{
|
||||
// Escort failed, no players in range
|
||||
if (!IsEscortInvokerInRange())
|
||||
{
|
||||
StopPath(mDespawnTime, mEscortQuestID, true);
|
||||
StopPath(0, mEscortQuestID, true);
|
||||
|
||||
// allow to properly hook out of range despawn action, which in most cases should perform the same operation as dying
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, me);
|
||||
me->DespawnOrUnsummon(1);
|
||||
return;
|
||||
}
|
||||
mEscortInvokerCheckTimer = 1000;
|
||||
} else mEscortInvokerCheckTimer -= diff;
|
||||
}
|
||||
else
|
||||
mEscortInvokerCheckTimer -= diff;
|
||||
|
||||
// handle pause
|
||||
if (HasEscortState(SMART_ESCORT_PAUSED))
|
||||
{
|
||||
@@ -304,11 +334,13 @@ void SmartAI::UpdatePath(const uint32 diff)
|
||||
if (mLastWPIDReached == SMART_ESCORT_LAST_OOC_POINT)
|
||||
mWPReached = true;
|
||||
}
|
||||
|
||||
mWPPauseTimer = 0;
|
||||
} else {
|
||||
mWPPauseTimer -= diff;
|
||||
}
|
||||
else
|
||||
mWPPauseTimer -= diff;
|
||||
}
|
||||
|
||||
if (HasEscortState(SMART_ESCORT_RETURNING))
|
||||
{
|
||||
if (mWPReached)//reached OOC WP
|
||||
@@ -319,8 +351,10 @@ void SmartAI::UpdatePath(const uint32 diff)
|
||||
mWPReached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!me->HasReactState(REACT_PASSIVE) && me->IsInCombat()) || HasEscortState(SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING))
|
||||
return;
|
||||
|
||||
// handle next wp
|
||||
if (mWPReached)//reached WP
|
||||
{
|
||||
@@ -339,6 +373,7 @@ void SmartAI::UpdatePath(const uint32 diff)
|
||||
|
||||
void SmartAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
CheckConditions(diff);
|
||||
GetScript()->OnUpdate(diff);
|
||||
UpdatePath(diff);
|
||||
UpdateDespawn(diff);
|
||||
@@ -350,7 +385,7 @@ void SmartAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, true))
|
||||
{
|
||||
StopFollow();
|
||||
StopFollow(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -375,18 +410,19 @@ bool SmartAI::IsEscortInvokerInRange()
|
||||
ObjectList* targets = GetScript()->GetTargetList(SMART_ESCORT_TARGETS);
|
||||
if (targets)
|
||||
{
|
||||
float checkDist = me->GetInstanceScript() ? SMART_ESCORT_MAX_PLAYER_DIST * 2 : SMART_ESCORT_MAX_PLAYER_DIST;
|
||||
if (targets->size() == 1 && GetScript()->IsPlayer((*targets->begin())))
|
||||
{
|
||||
Player* player = (*targets->begin())->ToPlayer();
|
||||
if (me->GetDistance(player) <= SMART_ESCORT_MAX_PLAYER_DIST)
|
||||
return true;
|
||||
if (me->GetDistance(player) <= checkDist)
|
||||
return true;
|
||||
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next())
|
||||
{
|
||||
Player* groupGuy = groupRef->GetSource();
|
||||
if (groupGuy->IsInMap(player) && me->GetDistance(groupGuy) <= SMART_ESCORT_MAX_PLAYER_DIST)
|
||||
if (groupGuy->IsInMap(player) && me->GetDistance(groupGuy) <= checkDist)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -397,13 +433,18 @@ bool SmartAI::IsEscortInvokerInRange()
|
||||
{
|
||||
if (GetScript()->IsPlayer((*iter)))
|
||||
{
|
||||
if (me->GetDistance((*iter)->ToPlayer()) <= SMART_ESCORT_MAX_PLAYER_DIST)
|
||||
if (me->GetDistance((*iter)->ToPlayer()) <= checkDist)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no valid target found
|
||||
return false;
|
||||
}
|
||||
return true;//escort targets were not set, ignore range check
|
||||
|
||||
// no player invoker was stored, just ignore range check
|
||||
return true;
|
||||
}
|
||||
|
||||
void SmartAI::MovepointReached(uint32 id)
|
||||
@@ -434,8 +475,15 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsAIControlled())
|
||||
{
|
||||
me->AttackStop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_EnterEvadeMode())
|
||||
return;
|
||||
|
||||
me->AddUnitState(UNIT_STATE_EVADE);
|
||||
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_EVADE);//must be after aura clear so we can cast spells from db
|
||||
@@ -502,7 +550,7 @@ bool SmartAI::AssistPlayerInCombatAgainst(Unit* who)
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me->IsFriendlyTo(who))
|
||||
if (!me->IsValidAssistTarget(who->GetVictim()))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
@@ -605,6 +653,14 @@ void SmartAI::JustSummoned(Creature* creature)
|
||||
|
||||
void SmartAI::AttackStart(Unit* who)
|
||||
{
|
||||
// dont allow charmed npcs to act on their own
|
||||
if (!IsAIControlled())
|
||||
{
|
||||
if (who && mCanAutoAttack)
|
||||
me->Attack(who, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (who && me->Attack(who, me->IsWithinMeleeRange(who)))
|
||||
{
|
||||
if (mCanCombatMove)
|
||||
@@ -672,9 +728,11 @@ void SmartAI::InitializeAI()
|
||||
{
|
||||
GetScript()->OnInitialize(me);
|
||||
if (!me->isDead())
|
||||
mJustReset = true;
|
||||
JustReachedHome();
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN);
|
||||
{
|
||||
mJustReset = true;
|
||||
JustReachedHome();
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN);
|
||||
}
|
||||
}
|
||||
|
||||
void SmartAI::OnCharmed(bool apply)
|
||||
@@ -729,11 +787,16 @@ void SmartAI::SetRun(bool run)
|
||||
mRun = run;
|
||||
}
|
||||
|
||||
void SmartAI::SetFly(bool fly)
|
||||
void SmartAI::SetDisableGravity(bool fly)
|
||||
{
|
||||
me->SetDisableGravity(fly);
|
||||
}
|
||||
|
||||
void SmartAI::SetCanFly(bool fly)
|
||||
{
|
||||
me->SetCanFly(fly);
|
||||
}
|
||||
|
||||
void SmartAI::SetSwim(bool swim)
|
||||
{
|
||||
me->SetSwim(swim);
|
||||
@@ -807,13 +870,13 @@ void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, ui
|
||||
{
|
||||
if (!target)
|
||||
{
|
||||
StopFollow();
|
||||
StopFollow(false);
|
||||
return;
|
||||
}
|
||||
|
||||
mFollowGuid = target->GetGUID();
|
||||
mFollowDist = dist >= 0.0f ? dist : PET_FOLLOW_DIST;
|
||||
mFollowAngle = angle >= 0.0f ? angle : me->GetFollowAngle();
|
||||
mFollowDist = dist;
|
||||
mFollowAngle = angle;
|
||||
mFollowArrivedTimer = 1000;
|
||||
mFollowCredit = credit;
|
||||
mFollowArrivedEntry = end;
|
||||
@@ -822,8 +885,21 @@ void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, ui
|
||||
me->GetMotionMaster()->MoveFollow(target, mFollowDist, mFollowAngle);
|
||||
}
|
||||
|
||||
void SmartAI::StopFollow()
|
||||
void SmartAI::StopFollow(bool complete)
|
||||
{
|
||||
mFollowGuid.Clear();
|
||||
mFollowDist = 0;
|
||||
mFollowAngle = 0;
|
||||
mFollowCredit = 0;
|
||||
mFollowArrivedTimer = 1000;
|
||||
mFollowArrivedEntry = 0;
|
||||
mFollowCreditType = 0;
|
||||
me->StopMoving();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
|
||||
if (!complete)
|
||||
return;
|
||||
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, mFollowGuid))
|
||||
{
|
||||
if (!mFollowCreditType)
|
||||
@@ -832,19 +908,11 @@ void SmartAI::StopFollow()
|
||||
player->GroupEventHappens(mFollowCredit, me);
|
||||
}
|
||||
|
||||
mFollowGuid.Clear();
|
||||
mFollowDist = 0;
|
||||
mFollowAngle = 0;
|
||||
mFollowCredit = 0;
|
||||
mFollowArrivedTimer = 1000;
|
||||
mFollowArrivedEntry = 0;
|
||||
mFollowCreditType = 0;
|
||||
SetDespawnTime(5000);
|
||||
me->StopMoving();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
StartDespawn();
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED);
|
||||
}
|
||||
|
||||
void SmartAI::SetScript9(SmartScriptHolder& e, uint32 entry, Unit* invoker)
|
||||
{
|
||||
if (invoker)
|
||||
@@ -865,6 +933,35 @@ void SmartAI::OnSpellClick(Unit* clicker, bool& result)
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELLCLICK, clicker);
|
||||
}
|
||||
|
||||
void SmartAI::CheckConditions(uint32 diff)
|
||||
{
|
||||
if (!mHasConditions)
|
||||
return;
|
||||
|
||||
if (mConditionsTimer <= diff)
|
||||
{
|
||||
if (Vehicle* vehicleKit = me->GetVehicleKit())
|
||||
{
|
||||
for (SeatMap::iterator itr = vehicleKit->Seats.begin(); itr != vehicleKit->Seats.end(); ++itr)
|
||||
if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid))
|
||||
{
|
||||
if (Player* player = passenger->ToPlayer())
|
||||
{
|
||||
if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry(), player, me))
|
||||
{
|
||||
player->ExitVehicle();
|
||||
return; // check other pessanger in next tick
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mConditionsTimer = 1000;
|
||||
}
|
||||
else
|
||||
mConditionsTimer -= diff;
|
||||
}
|
||||
|
||||
int SmartGameObjectAI::Permissible(const GameObject* g)
|
||||
{
|
||||
if (g->GetAIName() == "SmartGameObjectAI")
|
||||
@@ -880,20 +977,26 @@ void SmartGameObjectAI::UpdateAI(uint32 diff)
|
||||
void SmartGameObjectAI::InitializeAI()
|
||||
{
|
||||
GetScript()->OnInitialize(go);
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN);
|
||||
|
||||
// do not call respawn event if go is not spawned
|
||||
if (go->isSpawned())
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN);
|
||||
//Reset();
|
||||
}
|
||||
|
||||
void SmartGameObjectAI::Reset()
|
||||
{
|
||||
// call respawn event on reset
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN);
|
||||
|
||||
GetScript()->OnReset();
|
||||
}
|
||||
|
||||
// Called when a player opens a gossip dialog with the gameobject.
|
||||
bool SmartGameObjectAI::GossipHello(Player* player)
|
||||
bool SmartGameObjectAI::GossipHello(Player* player, bool reportUse)
|
||||
{
|
||||
TC_LOG_DEBUG("scripts.ai", "SmartGameObjectAI::GossipHello");
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, 0, 0, false, nullptr, go);
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, uint32(reportUse), 0, false, nullptr, go);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -957,6 +1060,11 @@ void SmartGameObjectAI::EventInform(uint32 eventId)
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_GO_EVENT_INFORM, nullptr, eventId);
|
||||
}
|
||||
|
||||
void SmartGameObjectAI::SpellHit(Unit* unit, const SpellInfo* spellInfo)
|
||||
{
|
||||
GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, unit, 0, 0, false, spellInfo);
|
||||
}
|
||||
|
||||
class SmartTrigger : public AreaTriggerScript
|
||||
{
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user