aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2024-04-14 14:28:40 +0200
committerShauren <shauren.trinity@gmail.com>2024-04-14 14:28:40 +0200
commit5dfac0ef142c1b59e41c51ab2cca48083be4cb9e (patch)
treef3c84b2f35acef78b6b590e8c387bdc320f0f806
parentec5111af6a34dbf2a22881b9b191d1ebd6f0a26d (diff)
Core/SAI: Implement waiting for actions on action list to finish before continuing the action list
This is enabled by setting event_flags to SMART_EVENT_FLAG_ACTIONLIST_WAITS on the action that is waited for Supported actions: * SMART_ACTION_TALK * SMART_ACTION_SIMPLE_TALK * SMART_ACTION_CAST * SMART_ACTION_SELF_CAST * SMART_ACTION_INVOKER_CAST * SMART_ACTION_MOVE_OFFSET * SMART_ACTION_WP_START * SMART_ACTION_MOVE_TO_POS * SMART_ACTION_CROSS_CAST * SMART_ACTION_ACTIVATE_TAXI * SMART_ACTION_JUMP_TO_POS * SMART_ACTION_START_CLOSEST_WAYPOINT
-rw-r--r--src/server/game/AI/SmartScripts/SmartAI.cpp5
-rw-r--r--src/server/game/AI/SmartScripts/SmartAI.h5
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.cpp160
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.h7
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.cpp24
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.h7
6 files changed, 187 insertions, 21 deletions
diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp
index 60da36ec545..88810a68851 100644
--- a/src/server/game/AI/SmartScripts/SmartAI.cpp
+++ b/src/server/game/AI/SmartScripts/SmartAI.cpp
@@ -45,7 +45,8 @@ bool SmartAI::IsAIControlled() const
return !_charmed;
}
-void SmartAI::StartPath(uint32 pathId/* = 0*/, bool repeat/* = false*/, Unit* invoker/* = nullptr*/, uint32 nodeId/* = 0*/)
+void SmartAI::StartPath(uint32 pathId/* = 0*/, bool repeat/* = false*/, Unit* invoker/* = nullptr*/, uint32 nodeId/* = 0*/,
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>>&& scriptResult/* = {}*/)
{
if (HasEscortState(SMART_ESCORT_ESCORTING))
StopPath();
@@ -71,7 +72,7 @@ void SmartAI::StartPath(uint32 pathId/* = 0*/, bool repeat/* = false*/, Unit* in
me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
}
- me->GetMotionMaster()->MovePath(pathId, _repeatWaypointPath);
+ me->GetMotionMaster()->MovePath(pathId, _repeatWaypointPath, {}, {}, MovementWalkRunSpeedSelectionMode::Default, {}, {}, {}, true, std::move(scriptResult));
}
WaypointPath const* SmartAI::LoadPath(uint32 entry)
diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h
index e42e0b53b44..b6d71be9c91 100644
--- a/src/server/game/AI/SmartScripts/SmartAI.h
+++ b/src/server/game/AI/SmartScripts/SmartAI.h
@@ -25,6 +25,8 @@
#include "SmartScript.h"
#include "WaypointDefines.h"
+enum class MovementStopReason : uint8;
+
enum SmartEscortState : uint8
{
SMART_ESCORT_NONE = 0x00, // nothing in progress
@@ -49,7 +51,8 @@ class TC_GAME_API SmartAI : public CreatureAI
bool IsAIControlled() const;
// Start moving to the desired MovePoint
- void StartPath(uint32 pathId = 0, bool repeat = false, Unit* invoker = nullptr, uint32 nodeId = 0);
+ void StartPath(uint32 pathId = 0, bool repeat = false, Unit* invoker = nullptr, uint32 nodeId = 0,
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>>&& scriptResult = {});
WaypointPath const* LoadPath(uint32 entry);
void PausePath(uint32 delay, bool forced = false);
bool CanResumePath();
diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp
index 3ae063fc57e..373cdb97e12 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScript.cpp
@@ -25,6 +25,7 @@
#include "GameEventMgr.h"
#include "GameEventSender.h"
#include "GameObject.h"
+#include "GameTime.h"
#include "GossipDef.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
@@ -38,6 +39,7 @@
#include "ObjectMgr.h"
#include "PhasingHandler.h"
#include "Random.h"
+#include "ScriptActions.h"
#include "SmartAI.h"
#include "SpellAuras.h"
#include "TemporarySummon.h"
@@ -263,6 +265,34 @@ void SmartScript::ProcessEventsFor(SMART_EVENT e, Unit* unit, uint32 var0, uint3
--mNestedEventsCounter;
}
+namespace
+{
+template <typename Result, typename ConcreteActionImpl = Scripting::v2::ActionResult<Result>, typename... Args>
+static std::shared_ptr<ConcreteActionImpl> CreateTimedActionListWaitEventFor(SmartScriptHolder const& e, Args&&... args)
+{
+ if (e.GetScriptType() != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST)
+ return nullptr;
+
+ if (!(e.event.event_flags & SMART_EVENT_FLAG_ACTIONLIST_WAITS))
+ return nullptr;
+
+ return std::make_shared<ConcreteActionImpl>(std::forward<Args>(args)...);
+}
+
+template <typename InnerResult>
+struct MultiActionResult : Scripting::v2::ActionResult<void>
+{
+ std::vector<Scripting::v2::ActionResult<InnerResult>> Results;
+
+ explicit MultiActionResult(std::size_t estimatedSize) { Results.reserve(estimatedSize); }
+
+ bool IsReady() const noexcept override
+ {
+ return std::ranges::all_of(Results, [](Scripting::v2::ActionResult<InnerResult> const& result) { return result.IsReady(); });
+ }
+};
+}
+
void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob, std::string const& varString)
{
e.runOnce = true; //used for repeat check
@@ -324,25 +354,28 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
mLastTextID = e.action.talk.textGroupID;
mTextTimer = e.action.talk.duration;
mUseTextTimer = true;
- sCreatureTextMgr->SendChat(talker, uint8(e.action.talk.textGroupID), talkTarget);
+ uint32 duration = sCreatureTextMgr->SendChat(talker, uint8(e.action.talk.textGroupID), talkTarget);
+ mTimedActionWaitEvent = CreateTimedActionListWaitEventFor<void, Scripting::v2::WaitAction>(e, GameTime::Now() + Milliseconds(duration));
TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_TALK: talker: {} {}, textGuid: {}",
talker->GetName(), talker->GetGUID().ToString(), talkTarget ? talkTarget->GetGUID().ToString().c_str() : "Empty");
break;
}
case SMART_ACTION_SIMPLE_TALK:
{
+ uint32 duration = 0;
for (WorldObject* target : targets)
{
if (IsCreature(target))
- sCreatureTextMgr->SendChat(target->ToCreature(), uint8(e.action.simpleTalk.textGroupID), IsPlayer(GetLastInvoker()) ? GetLastInvoker() : nullptr);
+ duration = std::max(sCreatureTextMgr->SendChat(target->ToCreature(), uint8(e.action.simpleTalk.textGroupID), IsPlayer(GetLastInvoker()) ? GetLastInvoker() : nullptr), duration);
else if (IsPlayer(target) && me)
{
Unit* templastInvoker = GetLastInvoker();
- sCreatureTextMgr->SendChat(me, uint8(e.action.simpleTalk.textGroupID), IsPlayer(templastInvoker) ? templastInvoker : nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, target->ToPlayer());
+ duration = std::max(sCreatureTextMgr->SendChat(me, uint8(e.action.simpleTalk.textGroupID), IsPlayer(templastInvoker) ? templastInvoker : nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, target->ToPlayer()), duration);
}
TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SIMPLE_TALK: talker: {} {}, textGroupId: {}",
target->GetName(), target->GetGUID().ToString(), uint8(e.action.simpleTalk.textGroupID));
}
+ mTimedActionWaitEvent = CreateTimedActionListWaitEventFor<void, Scripting::v2::WaitAction>(e, GameTime::Now() + Milliseconds(duration));
break;
}
case SMART_ACTION_PLAY_EMOTE:
@@ -582,6 +615,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
args.TriggerFlags = TRIGGERED_FULL_MASK;
}
+ std::shared_ptr<MultiActionResult<SpellCastResult>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<SpellCastResult>>(e, targets.size());
+
for (WorldObject* target : targets)
{
if (e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.cast.spell)))
@@ -590,6 +625,12 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
continue;
}
+ if (waitEvent)
+ {
+ args.SetScriptResult(Scripting::v2::ActionResult<SpellCastResult>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }));
+ args.SetScriptWaitsForSpellHit((e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0);
+ }
+
SpellCastResult result = SPELL_FAILED_BAD_TARGETS;
if (me)
{
@@ -617,6 +658,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
me ? me->GetGUID() : go->GetGUID(), e.action.cast.spell, target->GetGUID(), e.action.cast.castFlags);
}
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
+
// If there is at least 1 failed cast and no successful casts at all, retry again on next loop
if (failedSpellCast && !successfulSpellCast)
{
@@ -634,6 +678,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.cast.targetsLimit)
Trinity::Containers::RandomResize(targets, e.action.cast.targetsLimit);
+ std::shared_ptr<MultiActionResult<SpellCastResult>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<SpellCastResult>>(e, targets.size());
+
CastSpellExtraArgs args;
if (e.action.cast.castFlags & SMARTCAST_TRIGGERED)
{
@@ -648,11 +694,19 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.cast.spell)))
continue;
+ if (waitEvent)
+ {
+ args.SetScriptResult(Scripting::v2::ActionResult<SpellCastResult>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }));
+ args.SetScriptWaitsForSpellHit((e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0);
+ }
+
if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS && target->IsUnit())
target->ToUnit()->InterruptNonMeleeSpells(false);
target->CastSpell(target, e.action.cast.spell, args);
}
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_INVOKER_CAST:
@@ -676,6 +730,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
args.TriggerFlags = TRIGGERED_FULL_MASK;
}
+ std::shared_ptr<MultiActionResult<SpellCastResult>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<SpellCastResult>>(e, targets.size());
+
for (WorldObject* target : targets)
{
if (e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.cast.spell)))
@@ -687,10 +743,18 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS)
tempLastInvoker->InterruptNonMeleeSpells(false);
+ if (waitEvent)
+ {
+ args.SetScriptResult(Scripting::v2::ActionResult<SpellCastResult>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }));
+ args.SetScriptWaitsForSpellHit((e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0);
+ }
+
tempLastInvoker->CastSpell(target, e.action.cast.spell, args);
TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_INVOKER_CAST: Invoker {} casts spell {} on target {} with castflags {}",
tempLastInvoker->GetGUID(), e.action.cast.spell, target->GetGUID(), e.action.cast.castFlags);
}
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_ACTIVATE_GOBJECT:
@@ -1172,6 +1236,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_MOVE_OFFSET:
{
+ std::shared_ptr<MultiActionResult<MovementStopReason>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<MovementStopReason>>(e, targets.size());
+
for (WorldObject* target : targets)
{
if (!IsCreature(target))
@@ -1188,8 +1254,16 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
x = pos.GetPositionX() + (std::cos(o - (M_PI / 2))*e.target.x) + (std::cos(o)*e.target.y);
y = pos.GetPositionY() + (std::sin(o - (M_PI / 2))*e.target.x) + (std::sin(o)*e.target.y);
z = pos.GetPositionZ() + e.target.z;
- target->ToCreature()->GetMotionMaster()->MovePoint(e.action.moveOffset.PointId, x, y, z);
+
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>> scriptResult;
+ if (waitEvent)
+ scriptResult = Scripting::v2::ActionResult<MovementStopReason>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() });
+
+ target->ToCreature()->GetMotionMaster()->MovePoint(e.action.moveOffset.PointId, x, y, z,
+ true, {}, {}, MovementWalkRunSpeedSelectionMode::Default, {}, std::move(scriptResult));
}
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_SET_VISIBILITY:
@@ -1386,7 +1460,13 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
}
- ENSURE_AI(SmartAI, me->AI())->StartPath(entry, repeat, unit);
+ std::shared_ptr<Scripting::v2::ActionResult<MovementStopReason>> waitEvent = CreateTimedActionListWaitEventFor<MovementStopReason>(e);
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>> scriptResult;
+ if (waitEvent)
+ scriptResult = Scripting::v2::ActionResult<MovementStopReason>::GetResultSetter(waitEvent);
+
+ ENSURE_AI(SmartAI, me->AI())->StartPath(entry, repeat, unit, 0, std::move(scriptResult));
+ mTimedActionWaitEvent = std::move(waitEvent);
uint32 quest = e.action.wpStart.quest;
uint32 DespawnTime = e.action.wpStart.despawnTime;
@@ -1459,6 +1539,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (!targets.empty())
target = Trinity::Containers::SelectRandomContainerElement(targets);
+ std::shared_ptr<Scripting::v2::ActionResult<MovementStopReason>> waitEvent = CreateTimedActionListWaitEventFor<MovementStopReason>(e);
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>> scriptResult;
+ if (waitEvent)
+ scriptResult = Scripting::v2::ActionResult<MovementStopReason>::GetResultSetter(waitEvent);
+
if (target)
{
float x, y, z;
@@ -1466,6 +1551,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.moveToPos.ContactDistance > 0)
target->GetContactPoint(me, x, y, z, e.action.moveToPos.ContactDistance);
me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, x + e.target.x, y + e.target.y, z + e.target.z, e.action.moveToPos.disablePathfinding == 0);
+ mTimedActionWaitEvent = std::move(waitEvent);
}
if (e.GetTargetType() != SMART_TARGET_POSITION)
@@ -1476,7 +1562,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (TransportBase* trans = me->GetDirectTransport())
trans->CalculatePassengerPosition(dest.m_positionX, dest.m_positionY, dest.m_positionZ);
- me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, dest, e.action.moveToPos.disablePathfinding == 0);
+ me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, dest, e.action.moveToPos.disablePathfinding == 0, {}, {},
+ MovementWalkRunSpeedSelectionMode::Default, {}, std::move(scriptResult));
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_ENABLE_TEMP_GOBJ:
@@ -1658,6 +1746,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
ObjectVector casters;
GetTargets(casters, CreateSmartEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, 0, 0, 0, SMART_ACTION_NONE, 0, 0, 0, 0, 0, 0, 0, (SMARTAI_TARGETS)e.action.crossCast.targetType, e.action.crossCast.targetParam1, e.action.crossCast.targetParam2, e.action.crossCast.targetParam3, e.action.crossCast.targetParam4, e.action.param_string, 0), unit);
+ std::shared_ptr<MultiActionResult<SpellCastResult>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<SpellCastResult>>(e, casters.size()* targets.size());
+
CastSpellExtraArgs args;
if (e.action.crossCast.castFlags & SMARTCAST_TRIGGERED)
args.TriggerFlags = TRIGGERED_FULL_MASK;
@@ -1684,9 +1774,17 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
interruptedSpell = true;
}
+ if (waitEvent)
+ {
+ args.SetScriptResult(Scripting::v2::ActionResult<SpellCastResult>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }));
+ args.SetScriptWaitsForSpellHit((e.action.crossCast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0);
+ }
+
casterUnit->CastSpell(target, e.action.crossCast.spell, args);
}
}
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_CALL_RANDOM_TIMED_ACTIONLIST:
@@ -1749,9 +1847,23 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_ACTIVATE_TAXI:
{
+ std::shared_ptr<MultiActionResult<MovementStopReason>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<MovementStopReason>>(e, targets.size());
+
for (WorldObject* target : targets)
+ {
if (IsPlayer(target))
- target->ToPlayer()->ActivateTaxiPathTo(e.action.taxi.id);
+ {
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>> scriptResult;
+ if (waitEvent)
+ scriptResult = Scripting::v2::ActionResult<MovementStopReason>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() });
+
+ if (!target->ToPlayer()->ActivateTaxiPathTo(e.action.taxi.id, 0, {}, scriptResult))
+ if (scriptResult)
+ scriptResult->SetResult(MovementStopReason::Interrupted);
+ }
+ }
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_RANDOM_MOVE:
@@ -1852,14 +1964,22 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
else if (e.GetTargetType() != SMART_TARGET_POSITION)
break;
+ std::shared_ptr<Scripting::v2::ActionResult<MovementStopReason>> waitEvent = CreateTimedActionListWaitEventFor<MovementStopReason>(e);
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>> actionResultSetter;
+ if (waitEvent)
+ actionResultSetter = Scripting::v2::ActionResult<MovementStopReason>::GetResultSetter(waitEvent);
+
if (e.action.jump.Gravity || e.action.jump.UseDefaultGravity)
{
float gravity = e.action.jump.UseDefaultGravity ? Movement::gravity : e.action.jump.Gravity;
- me->GetMotionMaster()->MoveJumpWithGravity(pos, float(e.action.jump.SpeedXY), gravity, e.action.jump.PointId);
+ me->GetMotionMaster()->MoveJumpWithGravity(pos, float(e.action.jump.SpeedXY), gravity, e.action.jump.PointId,
+ false, nullptr, nullptr, std::move(actionResultSetter));
}
else
- me->GetMotionMaster()->MoveJump(pos, float(e.action.jump.SpeedXY), float(e.action.jump.SpeedZ), e.action.jump.PointId);
+ me->GetMotionMaster()->MoveJump(pos, float(e.action.jump.SpeedXY), float(e.action.jump.SpeedZ), e.action.jump.PointId,
+ false, nullptr, nullptr, std::move(actionResultSetter));
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_GO_SET_LOOT_STATE:
@@ -2035,20 +2155,18 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_START_CLOSEST_WAYPOINT:
{
- std::vector<uint32> waypoints;
- std::copy_if(std::begin(e.action.closestWaypointFromList.wps), std::end(e.action.closestWaypointFromList.wps),
- std::back_inserter(waypoints), [](uint32 wp) { return wp != 0; });
-
float distanceToClosest = std::numeric_limits<float>::max();
std::pair<uint32, uint32> closest = { 0, 0 };
+ std::shared_ptr<MultiActionResult<MovementStopReason>> waitEvent = CreateTimedActionListWaitEventFor<void, MultiActionResult<MovementStopReason>>(e, targets.size());
+
for (WorldObject* target : targets)
{
if (Creature* creature = target->ToCreature())
{
if (IsSmart(creature))
{
- for (uint32 pathId : waypoints)
+ for (uint32 pathId : e.action.closestWaypointFromList.wps)
{
WaypointPath const* path = sWaypointMgr->GetPath(pathId);
if (!path || path->Nodes.empty())
@@ -2067,10 +2185,19 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
if (closest.first != 0)
- ENSURE_AI(SmartAI, creature->AI())->StartPath(closest.first, true, nullptr, closest.second);
+ {
+ Optional<Scripting::v2::ActionResultSetter<MovementStopReason>> actionResultSetter;
+ if (waitEvent)
+ actionResultSetter = Scripting::v2::ActionResult<MovementStopReason>::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() });
+
+ ENSURE_AI(SmartAI, creature->AI())->StartPath(closest.first, true, nullptr, closest.second, std::move(actionResultSetter));
+ }
}
}
}
+
+ if (waitEvent && !waitEvent->Results.empty())
+ mTimedActionWaitEvent = std::move(waitEvent);
break;
}
case SMART_ACTION_RANDOM_SOUND:
@@ -3588,6 +3715,9 @@ void SmartScript::UpdateTimer(SmartScriptHolder& e, uint32 const diff)
if (e.GetEventType() == SMART_EVENT_UPDATE_OOC && (me && me->IsEngaged())) //can be used with me=nullptr (go script)
return;
+ if (e.GetScriptType() == SMART_SCRIPT_TYPE_TIMED_ACTIONLIST && mTimedActionWaitEvent && !mTimedActionWaitEvent->IsReady())
+ return;
+
if (e.timer < diff)
{
// delay spell cast event if another spell is being cast
diff --git a/src/server/game/AI/SmartScripts/SmartScript.h b/src/server/game/AI/SmartScripts/SmartScript.h
index 3f5e11de9c5..d83f4e01439 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.h
+++ b/src/server/game/AI/SmartScripts/SmartScript.h
@@ -20,6 +20,7 @@
#include "Define.h"
#include "SmartScriptMgr.h"
+#include <memory>
class AreaTrigger;
class Creature;
@@ -32,6 +33,11 @@ class WorldObject;
struct AreaTriggerEntry;
struct SceneTemplate;
+namespace Scripting::v2
+{
+class ActionBase;
+}
+
class TC_GAME_API SmartScript
{
public:
@@ -111,6 +117,7 @@ class TC_GAME_API SmartScript
SmartAIEventList mInstallEvents;
SmartAIEventList mTimedActionList;
ObjectGuid mTimedActionListInvoker;
+ std::shared_ptr<Scripting::v2::ActionBase> mTimedActionWaitEvent;
bool isProcessingTimedActionList;
Creature* me;
ObjectGuid meOrigGUID;
diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
index 4b368626203..fed55249eba 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
@@ -1568,6 +1568,12 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled event_type({}), Entry {} SourceType {} Event {} Action {}, skipped.", e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
return false;
}
+ if (e.event.event_flags & SMART_EVENT_FLAG_ACTIONLIST_WAITS)
+ {
+ TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {}, uses SMART_EVENT_FLAG_ACTIONLIST_WAITS but is not part of a timed actionlist.",
+ e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
+ return false;
+ }
}
if (!CheckUnusedEventParams(e))
@@ -1723,6 +1729,12 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.cast.spell, spellEffectInfo.TargetA.GetTarget(), spellEffectInfo.TargetB.GetTarget());
}
}
+ if (e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT && !(e.event.event_flags & SMART_EVENT_FLAG_ACTIONLIST_WAITS))
+ {
+ TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} uses SMARTCAST_WAIT_FOR_HIT but is not part of actionlist event that has SMART_EVENT_FLAG_ACTIONLIST_WAITS",
+ e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
+ return false;
+ }
break;
}
case SMART_ACTION_CROSS_CAST:
@@ -1755,6 +1767,12 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
return false;
}
}
+ if (e.action.crossCast.castFlags & SMARTCAST_WAIT_FOR_HIT && !(e.event.event_flags & SMART_EVENT_FLAG_ACTIONLIST_WAITS))
+ {
+ TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} uses SMARTCAST_WAIT_FOR_HIT but is not part of actionlist event that has SMART_EVENT_FLAG_ACTIONLIST_WAITS",
+ e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
+ return false;
+ }
break;
}
case SMART_ACTION_INVOKER_CAST:
@@ -1767,6 +1785,12 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
case SMART_ACTION_SELF_CAST:
if (!IsSpellValid(e, e.action.cast.spell))
return false;
+ if (e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT && !(e.event.event_flags & SMART_EVENT_FLAG_ACTIONLIST_WAITS))
+ {
+ TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} uses SMARTCAST_WAIT_FOR_HIT but is not part of actionlist event that has SMART_EVENT_FLAG_ACTIONLIST_WAITS",
+ e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
+ return false;
+ }
break;
case SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS:
case SMART_ACTION_CALL_GROUPEVENTHAPPENS:
diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h
index ff1ccf5e4a9..50b8ade0fef 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h
@@ -1599,14 +1599,14 @@ enum SmartEventFlags
SMART_EVENT_FLAG_DIFFICULTY_1_DEPRECATED = 0x004, // UNUSED, DO NOT REUSE
SMART_EVENT_FLAG_DIFFICULTY_2_DEPRECATED = 0x008, // UNUSED, DO NOT REUSE
SMART_EVENT_FLAG_DIFFICULTY_3_DEPRECATED = 0x010, // UNUSED, DO NOT REUSE
- SMART_EVENT_FLAG_RESERVED_5 = 0x020,
+ SMART_EVENT_FLAG_ACTIONLIST_WAITS = 0x020, // Timed action list will wait for action from this event to finish before moving on to next action
SMART_EVENT_FLAG_RESERVED_6 = 0x040,
SMART_EVENT_FLAG_DEBUG_ONLY = 0x080, //Event only occurs in debug build
SMART_EVENT_FLAG_DONT_RESET = 0x100, //Event will not reset in SmartScript::OnReset()
SMART_EVENT_FLAG_WHILE_CHARMED = 0x200, //Event occurs even if AI owner is charmed
SMART_EVENT_FLAGS_DEPRECATED = (SMART_EVENT_FLAG_DIFFICULTY_0_DEPRECATED | SMART_EVENT_FLAG_DIFFICULTY_1_DEPRECATED | SMART_EVENT_FLAG_DIFFICULTY_2_DEPRECATED | SMART_EVENT_FLAG_DIFFICULTY_3_DEPRECATED),
- SMART_EVENT_FLAGS_ALL = (SMART_EVENT_FLAG_NOT_REPEATABLE| SMART_EVENT_FLAGS_DEPRECATED | SMART_EVENT_FLAG_RESERVED_5 | SMART_EVENT_FLAG_RESERVED_6 | SMART_EVENT_FLAG_DEBUG_ONLY | SMART_EVENT_FLAG_DONT_RESET | SMART_EVENT_FLAG_WHILE_CHARMED),
+ SMART_EVENT_FLAGS_ALL = (SMART_EVENT_FLAG_NOT_REPEATABLE| SMART_EVENT_FLAGS_DEPRECATED | SMART_EVENT_FLAG_ACTIONLIST_WAITS | SMART_EVENT_FLAG_RESERVED_6 | SMART_EVENT_FLAG_DEBUG_ONLY | SMART_EVENT_FLAG_DONT_RESET | SMART_EVENT_FLAG_WHILE_CHARMED),
// Temp flags, used only at runtime, never stored in DB
SMART_EVENT_FLAG_TEMP_IGNORE_CHANCE_ROLL = 0x40000000, //Event occurs no matter what roll_chance_i(e.event.event_chance) returns.
@@ -1620,7 +1620,8 @@ enum SmartCastFlags
//SMARTCAST_NO_MELEE_IF_OOM = 0x08, //Prevents creature from entering melee if out of mana or out of range
//SMARTCAST_FORCE_TARGET_SELF = 0x10, //Forces the target to cast this spell on itself
SMARTCAST_AURA_NOT_PRESENT = 0x20, // Only casts the spell if the target does not have an aura from the spell
- SMARTCAST_COMBAT_MOVE = 0x40 // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
+ SMARTCAST_COMBAT_MOVE = 0x40, // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
+ SMARTCAST_WAIT_FOR_HIT = 0x80, // When used in combination with SMART_EVENT_FLAG_ACTIONLIST_WAITS, AI will wait for the spell to hit its target instead of just cast bar to finish
};
// one line in DB is one event