using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic.Common; // port of dsp's ai code https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai/ namespace FFXIVClassic_Map_Server.actors.chara.ai { /// todo: what even do i summarise this as? [Flags] enum TargetFindFlags { None, /// Able to target s even if not in target's party All, /// Able to target all s in target's party/alliance Alliance, /// Able to target any in target's party/alliance Pets, /// Target all in zone, regardless of distance ZoneWide, /// Able to target dead s Dead, } /// Targeting from/to different entity types enum TargetFindCharacterType { None, /// Player can target all s in party PlayerToPlayer, /// Player can target all s (excluding player owned s) PlayerToBattleNpc, /// BattleNpc can target other s BattleNpcToBattleNpc, /// BattleNpc can target s and their s BattleNpcToPlayer, } /// Type of AOE region to create enum TargetFindAOEType { None, /// Really a cylinder, uses extents parameter in SetAOEType Circle, /// Create a cone with angle in radians Cone, /// Box using self/target coords and Box } /// Set AOE around self or target enum TargetFindAOERadiusType { /// Set AOE's origin at target's position Target, /// Set AOE's origin to own position. Self } /// Target finding helper class class TargetFind { private Character owner; private Character target; private TargetFindCharacterType findType; private TargetFindFlags findFlags; private TargetFindAOEType aoeType; private TargetFindAOERadiusType radiusType; private Vector3 targetPosition; private float extents; private float angle; private List targets; public TargetFind(Character owner) { this.owner = owner; Reset(); } public void Reset() { this.target = null; this.findType = TargetFindCharacterType.None; this.findFlags = TargetFindFlags.None; this.aoeType = TargetFindAOEType.None; this.radiusType = TargetFindAOERadiusType.Self; this.targetPosition = null; this.extents = 0.0f; this.angle = 0.0f; this.targets = new List(); } /// /// Call this before /// /// /// - radius of circle /// - height of cone /// - width of box / 2 /// /// Angle in radians of cone public void SetAOEType(TargetFindAOERadiusType radiusType, TargetFindAOEType aoeType, float extents = -1.0f, float angle = -1.0f) { this.radiusType = TargetFindAOERadiusType.Target; this.aoeType = aoeType; this.extents = extents != -1.0f ? extents : 0.0f; this.angle = angle != -1.0f ? angle : 0.0f; } /// /// Find and try to add a single target to target list /// public void FindTarget(Character target, TargetFindFlags flags) { findFlags = flags; this.target = null; // todo: maybe this should only be set if successfully added? this.targetPosition = target.GetPosAsVector3(); AddTarget(target, false); } /// /// Call SetAOEType before calling this /// Find targets within area set by /// /// Include pets? public void FindWithinArea(Character target, TargetFindFlags flags, bool withPet) { // todo: maybe we should keep a snapshot which is only updated on each tick for consistency // are we creating aoe circles around target or self if ((aoeType & TargetFindAOEType.Circle) != 0 && radiusType != TargetFindAOERadiusType.Self) this.targetPosition = owner.GetPosAsVector3(); else this.targetPosition = new Vector3(target.positionX, target.positionY, target.positionZ); this.findFlags = flags; if (aoeType == TargetFindAOEType.Box) { FindWithinBox(withPet); } else if (aoeType == TargetFindAOEType.Circle) { FindWithinCircle(withPet); } } /// /// Find targets within a box using owner's coordinates and target's coordinates as length /// with corners being `extents` yalms to either side of self and target /// private void FindWithinBox(bool withPet) { // todo: loop over party members if ((findFlags & TargetFindFlags.All) != 0) { // if we have flag set to hit all characters in zone, do it // todo: make the distance check modifiable var actors = (findFlags & TargetFindFlags.ZoneWide) != 0 ? owner.zone.GetAllActors() : owner.zone.GetActorsAroundActor(owner, 30); var myPos = owner.GetPosAsVector3(); var angle = Vector3.GetAngle(myPos, targetPosition); // todo: actually check this works.. var myCorner = myPos.NewHorizontalVector(angle, extents); var myCorner2 = myPos.NewHorizontalVector(angle, -extents); var targetCorner = targetPosition.NewHorizontalVector(angle, extents); var targetCorner2 = targetPosition.NewHorizontalVector(angle, -extents); foreach (Character actor in actors) { // dont wanna add static actors if (actor is Player || actor is BattleNpc) { if (actor.GetPosAsVector3().IsWithinBox(myCorner2, targetCorner)) { if (CanTarget(actor)) AddTarget(actor, withPet); } } } } } /// /// Find targets within circle area. /// As the name implies, it only checks horizontal coords, not vertical - /// effectively creating cylinder with infinite height /// private void FindWithinCircle(bool withPet) { var actors = (findFlags & TargetFindFlags.ZoneWide) != 0 ? owner.zone.GetAllActors() : owner.zone.GetActorsAroundActor(owner, 30); foreach (Character target in actors) { if (target is Player || target is BattleNpc) { if (target.GetPosAsVector3().IsWithinCircle(targetPosition, extents)) AddTarget(target, withPet); } } } private void AddTarget(Character target, bool withPet) { if (CanTarget(target)) { // todo: add pets too targets.Add(target); } } private void AddAllInParty(Character target, bool withPet) { // todo: } private void AddAllInAlliance(Character target, bool withPet) { // todo: } public bool CanTarget(Character target) { // already targeted, dont target again if (targets.Contains(target)) return false; // cant target dead if ((findFlags & TargetFindFlags.Dead) == 0 && target.IsDead()) return false; // cant target if player is zoning if (target is Player && ((Player)target).playerSession.isUpdatesLocked) return false; return true; } } }