Skip to content

Commit

Permalink
Merge pull request #135 from FFXIV-CombatReborn/mergeWIP2
Browse files Browse the repository at this point in the history
Tower of Babil modules
  • Loading branch information
CarnifexOptimus authored Jun 18, 2024
2 parents 6e42f3c + 2a2001d commit 55c0374
Show file tree
Hide file tree
Showing 48 changed files with 1,135 additions and 842 deletions.
8 changes: 2 additions & 6 deletions BossMod/AI/AIManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace BossMod.AI;

sealed class AIManager : IDisposable
{
public static AIManager? Instance { get; private set; }
public readonly Autorotation Autorot;
public readonly AIController Controller;
private readonly AIConfig _config;
Expand All @@ -17,6 +18,7 @@ sealed class AIManager : IDisposable

public AIManager(Autorotation autorot)
{
Instance = this;
_wndAI = new AIManagementWindow(this);
Autorot = autorot;
Controller = new();
Expand Down Expand Up @@ -292,9 +294,7 @@ private bool ToggleFollowCombat()
_config.FollowDuringActiveBossModule = false;
}
else
{
_config.FollowDuringCombat = true;
}
Service.Log($"[AI] Follow during combat is now {(_config.FollowDuringCombat ? "enabled" : "disabled")}");
Service.Log($"[AI] Follow during active boss module is now {(_config.FollowDuringActiveBossModule ? "enabled" : "disabled")}");
return true;
Expand All @@ -303,9 +303,7 @@ private bool ToggleFollowCombat()
private bool ToggleFollowModule()
{
if (_config.FollowDuringActiveBossModule)
{
_config.FollowDuringActiveBossModule = false;
}
else
{
_config.FollowDuringActiveBossModule = true;
Expand All @@ -319,9 +317,7 @@ private bool ToggleFollowModule()
private bool ToggleFollowTarget(string[] messageData)
{
if (messageData.Length == 1)
{
_config.FollowTarget = !_config.FollowTarget;
}
else
{
switch (messageData[1].ToUpperInvariant())
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Autorotation/CommonActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ protected void FillStrategyPositionals(CommonRotation.Strategy strategy, (Positi

// smart targeting utility: return target (if friendly) or mouseover (if friendly) or null (otherwise)
protected Actor? SmartTargetFriendly(Actor? primaryTarget)
=> primaryTarget?.Type is ActorType.Player or ActorType.Chocobo ? primaryTarget : Autorot.SecondaryTarget?.Type is ActorType.Player or ActorType.Chocobo ? Autorot.SecondaryTarget : null;
=> primaryTarget?.Type is ActorType.Player or ActorType.Chocobo or ActorType.Buddy ? primaryTarget : Autorot.SecondaryTarget?.Type is ActorType.Player or ActorType.Chocobo or ActorType.Buddy ? Autorot.SecondaryTarget : null;

// smart targeting utility: return mouseover (if hostile and allowed) or target (otherwise)
protected Actor? SmartTargetHostile(Actor? primaryTarget)
Expand Down
126 changes: 85 additions & 41 deletions BossMod/BossModule/AOEShapes.cs

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions BossMod/BossModule/BossModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void ActivateComponent<T>() where T : BossComponent
// execute callbacks for existing state
foreach (var actor in WorldState.Actors)
{
bool nonPlayer = actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo;
bool nonPlayer = actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy;
if (nonPlayer)
{
comp.OnActorCreated(actor);
Expand Down Expand Up @@ -357,29 +357,29 @@ private void DrawPartyMembers(int pcSlot, Actor pc)
private void OnActorCreated(Actor actor)
{
_relevantEnemies.GetValueOrDefault(actor.OID)?.Add(actor);
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo)
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy)
foreach (var comp in _components)
comp.OnActorCreated(actor);
}

private void OnActorDestroyed(Actor actor)
{
_relevantEnemies.GetValueOrDefault(actor.OID)?.Remove(actor);
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo)
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy)
foreach (var comp in _components)
comp.OnActorDestroyed(actor);
}

private void OnActorCastStarted(Actor actor)
{
if ((actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo) && (actor.CastInfo?.IsSpell() ?? false))
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy && (actor.CastInfo?.IsSpell() ?? false))
foreach (var comp in _components)
comp.OnCastStarted(actor, actor.CastInfo);
}

private void OnActorCastFinished(Actor actor)
{
if ((actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo) && (actor.CastInfo?.IsSpell() ?? false))
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy && (actor.CastInfo?.IsSpell() ?? false))
foreach (var comp in _components)
comp.OnCastFinished(actor, actor.CastInfo);
}
Expand Down Expand Up @@ -416,7 +416,7 @@ private void OnActorIcon(Actor actor, uint iconID)

private void OnActorCastEvent(Actor actor, ActorCastEvent cast)
{
if ((actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo) && cast.IsSpell())
if (actor.Type is not ActorType.Player and not ActorType.Pet and not ActorType.Chocobo and not ActorType.Buddy && cast.IsSpell())
foreach (var comp in _components)
comp.OnEventCast(actor, cast);
}
Expand Down
118 changes: 113 additions & 5 deletions BossMod/BossModule/Shapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,67 @@ public override Func<WPos, float> Distance()
public override string ComputeHash() => ComputeSHA512($"{nameof(Circle)}:{Center.X},{Center.Z},{Radius}");
}

// for custom polygons defined by a list of vertices
// for custom polygons, automatically checking if convex or concave
public record class PolygonCustom(IEnumerable<WPos> Vertices) : Shape
{
private static readonly Dictionary<string, bool> propertyCache = [];

public override List<WDir> Contour(WPos center)
=> GetOrCreateContour(center, () => Vertices.Select(v => v - center).ToList());

public override RelSimplifiedComplexPolygon ToPolygon(WPos center)
=> GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))]));

public override Func<WPos, float> Distance() => ShapeDistance.ConcavePolygon(Vertices);
private bool IsConvex()
{
var hash = ComputeHash() + "IsConvex";
if (propertyCache.TryGetValue(hash, out var isConvex))
return isConvex;

var vertices = Vertices.ToList();
var n = vertices.Count;
isConvex = true;
for (var i = 0; i < n; i++)
{
var p0 = vertices[i];
var p1 = vertices[(i + 1) % n];
var p2 = vertices[(i + 2) % n];

var crossProduct = (p1.X - p0.X) * (p2.Z - p1.Z) - (p1.Z - p0.Z) * (p2.X - p1.X);
if (i == 0)
isConvex = crossProduct > 0;
else
if ((crossProduct > 0) != isConvex)
return propertyCache[hash] = false;
}
propertyCache[hash] = isConvex;
return isConvex;
}

private bool IsCounterClockwise()
{
var hash = ComputeHash() + "IsCounterClockwise";
if (propertyCache.TryGetValue(hash, out var isCounterClockwise))
return isCounterClockwise;

var vertices = Vertices.ToList();
float area = 0;
for (var i = 0; i < vertices.Count; i++)
{
var p0 = vertices[i];
var p1 = vertices[(i + 1) % vertices.Count];
area += (p1.X - p0.X) * (p1.Z + p0.Z);
}
isCounterClockwise = area > 0;
propertyCache[hash] = isCounterClockwise;
return isCounterClockwise;
}

public override Func<WPos, float> Distance()
{
return IsConvex() ? IsCounterClockwise() ? ShapeDistance.ConvexPolygon(Vertices, false) : ShapeDistance.ConvexPolygon(Vertices, true)
: ShapeDistance.ConcavePolygon(Vertices);
}

public override string ComputeHash()
{
Expand Down Expand Up @@ -185,7 +236,25 @@ public override RelSimplifiedComplexPolygon ToPolygon(WPos center)
=> GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))]));

public override Func<WPos, float> Distance()
=> ShapeDistance.Tri(Center, new RelTriangle(new WDir(-Radius, 0), new WDir(Radius, 0), new WDir(0, -Radius)));
{
var sqrt3 = MathF.Sqrt(3);
var halfSide = Radius;
var height = halfSide * sqrt3;
var a = new WDir(-halfSide, height / 3);
var b = new WDir(halfSide, height / 3);
var c = new WDir(0, -2 * height / 3);

var cos = MathF.Cos(Rotation.Rad);
var sin = MathF.Sin(Rotation.Rad);

var rotatedA = new WDir(a.X * cos - a.Z * sin, a.X * sin + a.Z * cos);
var rotatedB = new WDir(b.X * cos - b.Z * sin, b.X * sin + b.Z * cos);
var rotatedC = new WDir(c.X * cos - c.Z * sin, c.X * sin + c.Z * cos);

var relTriangle = new RelTriangle(rotatedA, rotatedB, rotatedC);

return ShapeDistance.Tri(Center, relTriangle);
}

public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleE)}:{Center.X},{Center.Z},{Radius},{Rotation.Rad}");
}
Expand Down Expand Up @@ -223,7 +292,28 @@ public override RelSimplifiedComplexPolygon ToPolygon(WPos center)
=> GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))]));

public override Func<WPos, float> Distance()
=> ShapeDistance.Tri(Center, new RelTriangle(new WDir(-SideA / 2, 0), new WDir(SideA / 2, 0), new WDir(0, -SideB)));
{
var sides = new[] { SideA, SideB, SideC }.OrderByDescending(s => s).ToArray();
var a = sides[0];
var b = sides[1];
var c = sides[2];
var vertex1 = new WDir(0, 0);
var vertex2 = new WDir(a, 0);
var cosC = (b * b + a * a - c * c) / (2 * a * b);
var sinC = MathF.Sqrt(1 - cosC * cosC);
var vertex3 = new WDir(b * cosC, b * sinC);

var cos = MathF.Cos(Rotation.Rad);
var sin = MathF.Sin(Rotation.Rad);

var rotatedVertex1 = new WDir(vertex1.X * cos - vertex1.Z * sin, vertex1.X * sin + vertex1.Z * cos);
var rotatedVertex2 = new WDir(vertex2.X * cos - vertex2.Z * sin, vertex2.X * sin + vertex2.Z * cos);
var rotatedVertex3 = new WDir(vertex3.X * cos - vertex3.Z * sin, vertex3.X * sin + vertex3.Z * cos);

var relTriangle = new RelTriangle(rotatedVertex1, rotatedVertex2, rotatedVertex3);

return ShapeDistance.Tri(Center, relTriangle);
}

public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleS)}:{Center.X},{Center.Z},{SideA},{SideB},{SideC},{Rotation.Rad}");
}
Expand Down Expand Up @@ -258,7 +348,25 @@ public override RelSimplifiedComplexPolygon ToPolygon(WPos center)
=> GetOrCreatePolygon(center, () => new RelSimplifiedComplexPolygon([new RelPolygonWithHoles(Contour(center))]));

public override Func<WPos, float> Distance()
=> ShapeDistance.Tri(Center, new RelTriangle(new WDir(-BaseLength / 2, 0), new WDir(BaseLength / 2, 0), new WDir(0, -BaseLength / 2 / MathF.Tan(ApexAngle.Rad / 2))));
{
var apexAngleRad = ApexAngle.Rad;
var height = BaseLength / 2 / MathF.Tan(apexAngleRad / 2);
var halfBase = BaseLength / 2;
var vertex1 = new WDir(-halfBase, 0);
var vertex2 = new WDir(halfBase, 0);
var vertex3 = new WDir(0, -height);

var cos = MathF.Cos(Rotation.Rad);
var sin = MathF.Sin(Rotation.Rad);

var rotatedVertex1 = new WDir(vertex1.X * cos - vertex1.Z * sin, vertex1.X * sin + vertex1.Z * cos);
var rotatedVertex2 = new WDir(vertex2.X * cos - vertex2.Z * sin, vertex2.X * sin + vertex2.Z * cos);
var rotatedVertex3 = new WDir(vertex3.X * cos - vertex3.Z * sin, vertex3.X * sin + vertex3.Z * cos);

var relTriangle = new RelTriangle(rotatedVertex1, rotatedVertex2, rotatedVertex3);

return ShapeDistance.Tri(Center, relTriangle);
}

public override string ComputeHash() => ComputeSHA512($"{nameof(TriangleA)}:{Center.X},{Center.Z},{BaseLength},{ApexAngle.Rad},{Rotation.Rad}");
}
Expand Down
11 changes: 5 additions & 6 deletions BossMod/Components/BaitAway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ public override void DrawArenaForeground(int pcSlot, Actor pc)
public override void OnTethered(Actor source, ActorTetherInfo tether)
{
var (player, enemy) = DetermineTetherSides(source, tether);
if (player != null && enemy != null)
if (enemyOID == default || _enemies.Contains(source))
CurrentBaits.Add(new(enemy, player, Shape, WorldState.FutureTime(ActivationDelay)));
if (player != null && enemy != null && (enemyOID == default || _enemies.Contains(source)))
CurrentBaits.Add(new(enemy, player, Shape, WorldState.FutureTime(ActivationDelay)));
}

public override void OnUntethered(Actor source, ActorTetherInfo tether)
Expand All @@ -124,7 +123,7 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether)
}

// we support both player->enemy and enemy->player tethers
private (Actor? player, Actor? enemy) DetermineTetherSides(Actor source, ActorTetherInfo tether)
public (Actor? player, Actor? enemy) DetermineTetherSides(Actor source, ActorTetherInfo tether)
{
if (tether.ID != TID)
return (null, null);
Expand All @@ -133,8 +132,8 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether)
if (target == null)
return (null, null);

var (player, enemy) = source.Type == ActorType.Player ? (source, target) : (target, source);
if (player.Type != ActorType.Player || enemy.Type == ActorType.Player)
var (player, enemy) = source.Type is ActorType.Player or ActorType.Buddy ? (source, target) : (target, source);
if (player.Type is not ActorType.Player and not ActorType.Buddy || enemy.Type is ActorType.Player or ActorType.Buddy)
{
ReportError($"Unexpected tether pair: {source.InstanceID:X} -> {target.InstanceID:X}");
return (null, null);
Expand Down
11 changes: 11 additions & 0 deletions BossMod/Components/ChasingAOEs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ public bool Advance(WPos pos, float moveDistance, DateTime currentTime, bool rem
}
return true;
}

public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
{
base.AddAIHints(slot, actor, assignment, hints);
// TODO: for some reason the AI only dodges the first hit correctly and then fails the rest (looks like running against a wall)
// this is a hack that tries to counter the problem
// 11 is the biggest radius of known chasing AOE (from Zeromus ex) + 1
if (Chasers.Count > 0)
foreach (var c in Chasers.Where(x => x.Target == actor))
hints.AddForbiddenZone(ShapeDistance.Circle(c.PredictedPosition(), 11));
}
}

// standard chasing aoe; first cast is long - assume it is baited on the nearest allowed target; successive casts are instant
Expand Down
13 changes: 11 additions & 2 deletions BossMod/Components/Knockback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public enum Kind
{
None,
AwayFromOrigin, // standard knockback - specific distance along ray from origin to target
TowardsOrigin, // standard pull - "knockback" to source - forward along source's direction + 180 degrees
TowardsOrigin, // standard pull - "knockback" to source - specific distance along ray from origin to target + 180 degrees
DirBackward, // standard pull - "knockback" to source - forward along source's direction + 180 degrees
DirForward, // directional knockback - forward along source's direction
DirLeft, // directional knockback - forward along source's direction + 90 degrees
DirRight, // directional knockback - forward along source's direction - 90 degrees
Expand Down Expand Up @@ -157,6 +158,7 @@ public override void OnStatusLose(Actor actor, ActorStatus status)
{
Kind.AwayFromOrigin => from != s.Origin ? (from - s.Origin).Normalized() : default,
Kind.TowardsOrigin => from != s.Origin ? (s.Origin - from).Normalized() : default,
Kind.DirBackward => (s.Direction + 180.Degrees()).ToDirection(),
Kind.DirForward => s.Direction.ToDirection(),
Kind.DirLeft => s.Direction.ToDirection().OrthoL(),
Kind.DirRight => s.Direction.ToDirection().OrthoR(),
Expand All @@ -166,8 +168,15 @@ public override void OnStatusLose(Actor actor, ActorStatus status)
continue; // couldn't determine direction for some reason

var distance = s.Distance;
if (s.Kind is Kind.TowardsOrigin)
if (s.Kind == Kind.TowardsOrigin)
distance = Math.Min(s.Distance, (s.Origin - from).Length() - s.MinDistance);
if (s.Kind == Kind.DirBackward)
{
var perpendicularDir = s.Direction.ToDirection().OrthoL();
var perpendicularDistance = Math.Abs((from - s.Origin).Cross(perpendicularDir) / perpendicularDir.Length());
distance = Math.Min(s.Distance, perpendicularDistance);
}

if (distance <= 0)
continue; // this could happen if attract starts from < min distance

Expand Down
Loading

0 comments on commit 55c0374

Please sign in to comment.