Core/SAI: Implement waiting for actions on action list to finish before continuing the action list

Port From (https://github.com/TrinityCore/TrinityCore/commit/5dfac0ef142c1b59e41c51ab2cca48083be4cb9e)
This commit is contained in:
Hondacrx
2024-08-08 17:28:27 -04:00
parent 0b1f2fe34e
commit 2f71dbfda0
18 changed files with 335 additions and 79 deletions
+144 -18
View File
@@ -9,6 +9,7 @@ using Game.Groups;
using Game.Maps;
using Game.Misc;
using Game.Movement;
using Game.Scripting.v2;
using Game.Spells;
using System;
using System.Collections.Generic;
@@ -30,6 +31,7 @@ namespace Game.AI
List<SmartScriptHolder> _installEvents = new();
List<SmartScriptHolder> _timedActionList = new();
ObjectGuid mTimedActionListInvoker;
ActionBase mTimedActionWaitEvent;
Creature _me;
ObjectGuid _meOrigGUID;
GameObject _go;
@@ -185,27 +187,29 @@ namespace Game.AI
_talkerEntry = talker.GetEntry();
_lastTextID = e.Action.talk.textGroupId;
_textTimer = e.Action.talk.duration;
_useTextTimer = true;
Global.CreatureTextMgr.SendChat(talker, (byte)e.Action.talk.textGroupId, talkTarget);
uint duration = Global.CreatureTextMgr.SendChat(talker, (byte)e.Action.talk.textGroupId, talkTarget);
mTimedActionWaitEvent = CreateTimedActionListWaitEventFor<WaitAction>(e, [GameTime.Now() + TimeSpan.FromMilliseconds(duration)]);
Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_TALK: talker: {0} (Guid: {1}), textGuid: {2}",
talker.GetName(), talker.GetGUID().ToString(), _textGUID.ToString());
break;
}
case SmartActions.SimpleTalk:
{
uint duration = 0;
foreach (var target in targets)
{
if (IsCreature(target))
Global.CreatureTextMgr.SendChat(target.ToCreature(), (byte)e.Action.simpleTalk.textGroupId, IsPlayer(GetLastInvoker()) ? GetLastInvoker() : null);
duration = Math.Max(Global.CreatureTextMgr.SendChat(target.ToCreature(), (byte)e.Action.simpleTalk.textGroupId, IsPlayer(GetLastInvoker()) ? GetLastInvoker() : null), duration);
else if (IsPlayer(target) && _me != null)
{
Unit templastInvoker = GetLastInvoker();
Global.CreatureTextMgr.SendChat(_me, (byte)e.Action.simpleTalk.textGroupId, IsPlayer(templastInvoker) ? templastInvoker : null, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, SoundKitPlayType.Normal, Team.Other, false, target.ToPlayer());
duration = Math.Max(Global.CreatureTextMgr.SendChat(_me, (byte)e.Action.simpleTalk.textGroupId, IsPlayer(templastInvoker) ? templastInvoker : null, ChatMsg.Addon, Language.Addon, CreatureTextRange.Normal, 0, SoundKitPlayType.Normal, Team.Other, false, target.ToPlayer()), duration);
}
Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_SIMPLE_TALK: talker: {0} (GuidLow: {1}), textGroupId: {2}",
target.GetName(), target.GetGUID().ToString(), e.Action.simpleTalk.textGroupId);
}
mTimedActionWaitEvent = CreateTimedActionListWaitEventFor<WaitAction>(e, [GameTime.Now() + TimeSpan.FromMilliseconds(duration)]);
break;
}
case SmartActions.PlayEmote:
@@ -439,6 +443,9 @@ namespace Game.AI
}
case SmartActions.Cast:
{
if (targets.Empty())
break;
if (e.Action.cast.targetsLimit > 0 && targets.Count > e.Action.cast.targetsLimit)
targets.RandomResize(e.Action.cast.targetsLimit);
@@ -454,6 +461,8 @@ namespace Game.AI
args.TriggerFlags = TriggerCastFlags.FullMask;
}
MultiActionResult<SpellCastResult> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<SpellCastResult>>(e);
foreach (WorldObject target in targets)
{
if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.AuraNotPresent) && (!target.IsUnit() || target.ToUnit().HasAura(e.Action.cast.spell)))
@@ -462,6 +471,11 @@ namespace Game.AI
continue;
}
if (waitEvent != null)
{
args.SetScriptResult(ActionResult<SpellCastResult>.GetResultSetter(waitEvent.CreateAndGetResult()));
args.SetScriptWaitsForSpellHit(e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit));
}
SpellCastResult result = SpellCastResult.BadTargets;
if (_me != null)
@@ -489,6 +503,9 @@ namespace Game.AI
Log.outDebug(LogFilter.ScriptsAi, $"SmartScript::ProcessAction:: SMART_ACTION_CAST:: {(_me != null ? _me.GetGUID() : _go.GetGUID())} casts spell {e.Action.cast.spell} on target {target.GetGUID()} with castflags {e.Action.cast.castFlags}");
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
// If there is at least 1 failed cast and no successful casts at all, retry again on next loop
if (failedSpellCast && !successfulSpellCast)
{
@@ -507,6 +524,8 @@ namespace Game.AI
if (e.Action.cast.targetsLimit != 0)
targets.RandomResize(e.Action.cast.targetsLimit);
MultiActionResult<SpellCastResult> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<SpellCastResult>>(e);
CastSpellExtraArgs args = new();
if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.Triggered))
{
@@ -521,11 +540,20 @@ namespace Game.AI
if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.AuraNotPresent) && (!target.IsUnit() || target.ToUnit().HasAura(e.Action.cast.spell)))
continue;
if (waitEvent != null)
{
args.SetScriptResult(ActionResult<SpellCastResult>.GetResultSetter(waitEvent.CreateAndGetResult()));
args.SetScriptWaitsForSpellHit(e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit));
}
if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.InterruptPrevious) && target.IsUnit())
target.ToUnit().InterruptNonMeleeSpells(false);
target.CastSpell(target, e.Action.cast.spell, args);
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.InvokerCast:
@@ -549,6 +577,8 @@ namespace Game.AI
args.TriggerFlags = TriggerCastFlags.FullMask;
}
MultiActionResult<SpellCastResult> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<SpellCastResult>>(e);
foreach (var target in targets)
{
if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.AuraNotPresent) && (!target.IsUnit() || target.ToUnit().HasAura(e.Action.cast.spell)))
@@ -560,9 +590,18 @@ namespace Game.AI
if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.InterruptPrevious))
tempLastInvoker.InterruptNonMeleeSpells(false);
if (waitEvent != null)
{
args.SetScriptResult(ActionResult<SpellCastResult>.GetResultSetter(waitEvent.CreateAndGetResult()));
args.SetScriptWaitsForSpellHit(e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit));
}
tempLastInvoker.CastSpell(target, e.Action.cast.spell, args);
Log.outDebug(LogFilter.ScriptsAi, $"SmartScript::ProcessAction:: SMART_ACTION_INVOKER_CAST: Invoker {tempLastInvoker.GetGUID()} casts spell {e.Action.cast.spell} on target {target.GetGUID()} with castflags {e.Action.cast.castFlags}");
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.ActivateGobject:
@@ -1073,6 +1112,8 @@ namespace Game.AI
}
case SmartActions.MoveOffset:
{
MultiActionResult<MovementStopReason> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<MovementStopReason>>(e);
foreach (var target in targets)
{
if (!IsCreature(target))
@@ -1088,8 +1129,16 @@ namespace Game.AI
float x = (float)(pos.GetPositionX() + (Math.Cos(o - (Math.PI / 2)) * e.Target.x) + (Math.Cos(o) * e.Target.y));
float y = (float)(pos.GetPositionY() + (Math.Sin(o - (Math.PI / 2)) * e.Target.x) + (Math.Sin(o) * e.Target.y));
float z = pos.GetPositionZ() + e.Target.z;
target.ToCreature().GetMotionMaster().MovePoint(e.Action.moveOffset.PointId, x, y, z);
ActionResultSetter<MovementStopReason> scriptResult = null;
if (waitEvent != null)
scriptResult = ActionResult<MovementStopReason>.GetResultSetter(waitEvent.CreateAndGetResult());
target.ToCreature().GetMotionMaster().MovePoint(e.Action.moveOffset.PointId, x, y, z, true, null, null, MovementWalkRunSpeedSelectionMode.Default, null, scriptResult);
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.SetVisibility:
@@ -1294,7 +1343,13 @@ namespace Game.AI
}
}
_me.GetAI<SmartAI>().StartPath(entry, repeat, unit);
ActionResult<MovementStopReason> waitEvent = CreateTimedActionListWaitEventFor<MovementStopReason, ActionResult>(e);
ActionResultSetter<MovementStopReason> scriptResult = null;
if (waitEvent != null)
scriptResult = ActionResult<MovementStopReason>.GetResultSetter(waitEvent);
_me.GetAI<SmartAI>().StartPath(entry, repeat, unit, 0, scriptResult);
mTimedActionWaitEvent = waitEvent;
uint quest = e.Action.wpStart.quest;
uint DespawnTime = e.Action.wpStart.despawnTime;
@@ -1368,6 +1423,11 @@ namespace Game.AI
if (!targets.Empty())
target = targets.SelectRandom();
ActionResult<MovementStopReason> waitEvent = CreateTimedActionListWaitEventFor<MovementStopReason, ActionResult>(e);
ActionResultSetter<MovementStopReason> scriptResult = null;
if (waitEvent != null)
scriptResult = ActionResult<MovementStopReason>.GetResultSetter(waitEvent);
if (target != null)
{
float x, y, z;
@@ -1375,6 +1435,7 @@ namespace Game.AI
if (e.Action.moveToPos.contactDistance > 0)
target.GetContactPoint(_me, out x, out y, out 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 = waitEvent;
}
if (e.GetTargetType() != SmartTargets.Position)
@@ -1388,7 +1449,8 @@ namespace Game.AI
trans.CalculatePassengerPosition(ref dest.posX, ref dest.posY, ref dest.posZ, ref dest.Orientation);
}
_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, null, null, MovementWalkRunSpeedSelectionMode.Default, null, scriptResult);
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.EnableTempGobj:
@@ -1577,6 +1639,8 @@ namespace Game.AI
List<WorldObject> casters = GetTargets(CreateSmartEvent(SmartEvents.UpdateIc, 0, 0, 0, 0, 0, 0, SmartActions.None, 0, 0, 0, 0, 0, 0, 0, (SmartTargets)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);
MultiActionResult<SpellCastResult> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<SpellCastResult>>(e);
CastSpellExtraArgs args = new();
if (e.Action.crossCast.castFlags.HasAnyFlag((uint)SmartCastFlags.Triggered))
args.TriggerFlags = TriggerCastFlags.FullMask;
@@ -1603,9 +1667,18 @@ namespace Game.AI
interruptedSpell = true;
}
if (waitEvent != null)
{
args.SetScriptResult(ActionResult<SpellCastResult>.GetResultSetter(waitEvent.CreateAndGetResult()));
args.SetScriptWaitsForSpellHit(e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit));
}
casterUnit.CastSpell(target, e.Action.crossCast.spell, args);
}
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.CallRandomTimedActionlist:
@@ -1694,9 +1767,24 @@ namespace Game.AI
}
case SmartActions.ActivateTaxi:
{
MultiActionResult<MovementStopReason> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<MovementStopReason>>(e);
foreach (var target in targets)
{
if (IsPlayer(target))
target.ToPlayer().ActivateTaxiPathTo(e.Action.taxi.id);
{
ActionResultSetter<MovementStopReason> scriptResult = null;
if (waitEvent != null)
scriptResult = ActionResult<MovementStopReason>.GetResultSetter(waitEvent.CreateAndGetResult());
if (!target.ToPlayer().ActivateTaxiPathTo(e.Action.taxi.id, 0, null, scriptResult))
if (scriptResult != null)
scriptResult.SetResult(MovementStopReason.Interrupted);
}
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.RandomMove:
@@ -1813,13 +1901,20 @@ namespace Game.AI
else if (e.GetTargetType() != SmartTargets.Position)
break;
ActionResult<MovementStopReason> waitEvent = CreateTimedActionListWaitEventFor<MovementStopReason, ActionResult>(e);
ActionResultSetter<MovementStopReason> actionResultSetter = null;
if (waitEvent != null)
actionResultSetter = ActionResult<MovementStopReason>.GetResultSetter(waitEvent);
if (e.Action.jump.Gravity != 0 || e.Action.jump.UseDefaultGravity != 0)
{
float gravity = e.Action.jump.UseDefaultGravity != 0 ? (float)MotionMaster.gravity : e.Action.jump.Gravity;
_me.GetMotionMaster().MoveJumpWithGravity(pos, e.Action.jump.SpeedXY, gravity, e.Action.jump.PointId);
_me.GetMotionMaster().MoveJumpWithGravity(pos, e.Action.jump.SpeedXY, gravity, e.Action.jump.PointId, false, null, null, actionResultSetter);
}
else
_me.GetMotionMaster().MoveJump(pos, e.Action.jump.SpeedXY, e.Action.jump.SpeedZ, e.Action.jump.PointId);
_me.GetMotionMaster().MoveJump(pos, e.Action.jump.SpeedXY, e.Action.jump.SpeedZ, e.Action.jump.PointId, false, null, null, actionResultSetter);
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.GoSetLootState:
@@ -1997,11 +2092,7 @@ namespace Game.AI
}
case SmartActions.StartClosestWaypoint:
{
List<uint> waypoints = new();
var closestWaypointFromList = e.Action.closestWaypointFromList;
foreach (var id in new[] { closestWaypointFromList.wp1, closestWaypointFromList.wp2, closestWaypointFromList.wp3, closestWaypointFromList.wp4, closestWaypointFromList.wp5, closestWaypointFromList.wp6 })
if (id != 0)
waypoints.Add(id);
MultiActionResult<MovementStopReason> waitEvent = CreateTimedActionListWaitEventFor<MultiActionResult<MovementStopReason>>(e);
float distanceToClosest = float.MaxValue;
uint closestPathId = 0;
@@ -2014,12 +2105,12 @@ namespace Game.AI
{
if (IsSmart(creature))
{
foreach (uint pathId in waypoints)
var closestWaypointFromList = e.Action.closestWaypointFromList;
foreach (uint pathId in new[] { closestWaypointFromList.wp1, closestWaypointFromList.wp2, closestWaypointFromList.wp3, closestWaypointFromList.wp4, closestWaypointFromList.wp5, closestWaypointFromList.wp6 })
{
WaypointPath path = Global.WaypointMgr.GetPath(pathId);
if (path == null || path.Nodes.Empty())
continue;
foreach (var waypoint in path.Nodes)
{
float distanceToThisNode = creature.GetDistance(waypoint.X, waypoint.Y, waypoint.Z);
@@ -2033,10 +2124,19 @@ namespace Game.AI
}
if (closestPathId != 0)
((SmartAI)creature.GetAI()).StartPath(closestPathId, true, null, closestWaypointId);
{
ActionResultSetter<MovementStopReason> actionResultSetter = null;
if (waitEvent != null)
actionResultSetter = ActionResult<MovementStopReason>.GetResultSetter(waitEvent.CreateAndGetResult());
creature.GetAI<SmartAI>().StartPath(closestPathId, true, null, closestWaypointId, actionResultSetter);
}
}
}
}
if (waitEvent != null && !waitEvent.Results.Empty())
mTimedActionWaitEvent = waitEvent;
break;
}
case SmartActions.RandomSound:
@@ -3614,6 +3714,9 @@ namespace Game.AI
if (e.GetEventType() == SmartEvents.UpdateOoc && (_me != null && _me.IsEngaged()))//can be used with me=NULL (go script)
return;
if (e.GetScriptType() == SmartScriptType.TimedActionlist && mTimedActionWaitEvent != null && !mTimedActionWaitEvent.IsReady())
return;
if (e.Timer < diff)
{
// delay spell cast event if another spell is being casted
@@ -4362,6 +4465,29 @@ namespace Game.AI
}
}
}
//template<typename Result, typename ConcreteActionImpl = Scripting::v2::ActionResult<Result>, typename...Args>
public static ActionResult<Result> CreateTimedActionListWaitEventFor<Result, ConcreteActionImpl>(SmartScriptHolder e, object[] args = null) where ConcreteActionImpl : ActionBase
{
if (e.GetScriptType() != SmartScriptType.TimedActionlist)
return null;
if (!e.Event.event_flags.HasFlag(SmartEventFlags.ActionlistWaits))
return null;
return (ActionResult<Result>)Activator.CreateInstance(typeof(ActionResult<Result>), args);
}
public static ConcreteActionImpl CreateTimedActionListWaitEventFor<ConcreteActionImpl>(SmartScriptHolder e, object[] args = null) where ConcreteActionImpl : ActionBase
{
if (e.GetScriptType() != SmartScriptType.TimedActionlist)
return null;
if (!e.Event.event_flags.HasFlag(SmartEventFlags.ActionlistWaits))
return null;
return (ConcreteActionImpl)Activator.CreateInstance(typeof(ConcreteActionImpl), args);
}
}
class ObjectGuidList