diff options
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartAI.cpp | 5 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartAI.h | 5 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartScript.cpp | 160 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartScript.h | 7 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartScriptMgr.cpp | 24 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartScriptMgr.h | 7 |
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 |