Skip to content

Commit

Permalink
Merge pull request #339 from FFXIV-CombatReborn/QOL-pass
Browse files Browse the repository at this point in the history
Refactor and improve null checks, add PvP handling
  • Loading branch information
LTS-FFXIV authored Sep 8, 2024
2 parents 7425817 + f96e747 commit f427d2a
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 100 deletions.
59 changes: 38 additions & 21 deletions RotationSolver.Basic/Actions/ActionTargetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using RotationSolver.Basic.Configuration;
using System.Net.Mail;
using static RotationSolver.Basic.Configuration.ConfigTypes;

namespace RotationSolver.Basic.Actions;
Expand Down Expand Up @@ -39,24 +40,31 @@ public struct ActionTargetInfo(IBaseAction action)
/// Is this action friendly.
/// </summary>
public readonly bool IsTargetFriendly => action.Setting.IsFriendly;
#region Target Stuff

#region Target Finder.
private readonly IEnumerable<IBattleChara> GetCanTargets(bool skipStatusProvideCheck, TargetType type)
{
var items = TargetFilter.GetObjectInRadius(DataCenter.AllTargets, Range);
var objs = new List<IBattleChara>(items.Count());
var allTargets = DataCenter.AllTargets;
if (allTargets == null) return Enumerable.Empty<IBattleChara>();

foreach (var obj in items)
{
if (type == TargetType.Heal && obj.GetHealthRatio() == 1) continue;
var filteredTargets = TargetFilter.GetObjectInRadius(allTargets, Range);
var validTargets = new List<IBattleChara>(filteredTargets.Count());

if (!GeneralCheck(obj, skipStatusProvideCheck)) continue;
objs.Add(obj);
foreach (var target in filteredTargets)
{
if (type == TargetType.Heal && target.GetHealthRatio() == 1) continue;
if (!GeneralCheck(target, skipStatusProvideCheck)) continue;
validTargets.Add(target);
}

var isAuto = !DataCenter.IsManual || IsTargetFriendly;
return objs.Where(b => isAuto || b.GameObjectId == Svc.Targets.Target?.GameObjectId || b.GameObjectId == Player.Object.GameObjectId)
.Where(InViewTarget).Where(CanUseTo).Where(action.Setting.CanTarget);
var playerObjectId = Player.Object?.GameObjectId;
var targetObjectId = Svc.Targets.Target?.GameObjectId;

return validTargets.Where(b => isAuto || b.GameObjectId == targetObjectId || b.GameObjectId == playerObjectId)
.Where(InViewTarget)
.Where(CanUseTo)
.Where(action.Setting.CanTarget);
}

private readonly List<IBattleChara> GetCanAffects(bool skipStatusProvideCheck, TargetType type)
Expand Down Expand Up @@ -198,8 +206,6 @@ private readonly bool CheckResistance(IGameObject IGameObject)
return false;
}
}


return true;
}

Expand Down Expand Up @@ -690,16 +696,27 @@ private readonly bool CanGetTarget(IGameObject target, IGameObject subTarget)

IBattleChara? FindHostileRaw()
{
IGameObjects = DataCenter.TargetingType switch
if (DataCenter.IsPvP)
{
TargetingType.Small => IGameObjects.OrderBy(p => p.HitboxRadius),
TargetingType.HighHP => IGameObjects.OrderByDescending(p => p is IBattleChara b ? b.CurrentHp : 0),
TargetingType.LowHP => IGameObjects.OrderBy(p => p is IBattleChara b ? b.CurrentHp : 0),
TargetingType.HighMaxHP => IGameObjects.OrderByDescending(p => p is IBattleChara b ? b.MaxHp : 0),
TargetingType.LowMaxHP => IGameObjects.OrderBy(p => p is IBattleChara b ? b.MaxHp : 0),
_ => IGameObjects.OrderByDescending(p => p.HitboxRadius),
};
return IGameObjects.FirstOrDefault();
return IGameObjects
.OfType<IBattleChara>()
.OrderBy(p => p.CurrentHp)
.FirstOrDefault();
}
else
{
var orderedGameObjects = DataCenter.TargetingType switch
{
TargetingType.Small => IGameObjects.OrderBy(p => p.HitboxRadius),
TargetingType.HighHP => IGameObjects.OrderByDescending(p => p is IBattleChara b ? b.CurrentHp : 0),
TargetingType.LowHP => IGameObjects.OrderBy(p => p is IBattleChara b ? b.CurrentHp : 0),
TargetingType.HighMaxHP => IGameObjects.OrderByDescending(p => p is IBattleChara b ? b.MaxHp : 0),
TargetingType.LowMaxHP => IGameObjects.OrderBy(p => p is IBattleChara b ? b.MaxHp : 0),
_ => IGameObjects.OrderByDescending(p => p.HitboxRadius),
};

return orderedGameObjects.OfType<IBattleChara>().FirstOrDefault();
}
}

IBattleChara? FindBeAttackedTarget()
Expand Down
4 changes: 4 additions & 0 deletions RotationSolver.Basic/Configuration/Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public const string
Filter = TargetConfig)]
private static readonly bool _filterStopMark = true;

[ConditionBool, UI("Treat 1hp targets as invincible.",
Filter = TargetConfig)]
private static readonly bool _filterOneHPInvincible = true;

[ConditionBool, UI("Teaching mode", Filter = UiInformation)]
private static readonly bool _teachingMode = false;

Expand Down
20 changes: 13 additions & 7 deletions RotationSolver.Basic/DataCenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,17 @@ public static IBattleChara[] AllHostileTargets
get
{
return AllTargets.Where(b =>
{
{
//Not enemy.
if (!b.IsEnemy()) return false;
//Dead.
if (b.CurrentHp <= 1) return false;
//Not targetable.
if (!b.IsTargetable) return false;
//Invincible.
if (b.StatusList.Any(StatusHelper.IsInvincible)) return false;
return true;
}).ToArray();
Expand All @@ -318,20 +321,22 @@ public static IBattleChara? DeathTarget
{
get
{
// Ensure AllianceMembers and PartyMembers are not null
if (AllianceMembers == null || PartyMembers == null) return null;

var deathAll = AllianceMembers.GetDeath();
var deathParty = PartyMembers.GetDeath();

if (deathParty.Any())
{
var deathT = deathParty.GetJobCategory(JobRole.Tank);
var deathT = deathParty.GetJobCategory(JobRole.Tank).ToList();
var deathH = deathParty.GetJobCategory(JobRole.Healer).ToList();

if (deathT.Count() > 1)
if (deathT.Count > 1)
{
return deathT.FirstOrDefault();
}

var deathH = deathParty.GetJobCategory(JobRole.Healer);

if (deathH.Any()) return deathH.FirstOrDefault();

if (deathT.Any()) return deathT.FirstOrDefault();
Expand All @@ -341,10 +346,11 @@ public static IBattleChara? DeathTarget

if (deathAll.Any() && Service.Config.RaiseAll)
{
var deathAllH = deathAll.GetJobCategory(JobRole.Healer);
var deathAllH = deathAll.GetJobCategory(JobRole.Healer).ToList();
var deathAllT = deathAll.GetJobCategory(JobRole.Tank).ToList();

if (deathAllH.Any()) return deathAllH.FirstOrDefault();

var deathAllT = deathAll.GetJobCategory(JobRole.Tank);
if (deathAllT.Any()) return deathAllT.FirstOrDefault();

return deathAll.FirstOrDefault();
Expand Down
80 changes: 33 additions & 47 deletions RotationSolver.Basic/Helpers/ObjectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using Lumina.Excel.GeneratedSheets;
using RotationSolver.Basic.Configuration;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace RotationSolver.Basic.Helpers;
Expand All @@ -22,10 +20,10 @@ namespace RotationSolver.Basic.Helpers;
public static class ObjectHelper
{
static readonly EventHandlerType[] _eventType =
[
{
EventHandlerType.TreasureHuntDirector,
EventHandlerType.Quest,
];
};

internal static BNpcBase? GetObjectNPC(this IGameObject obj)
{
Expand All @@ -35,8 +33,10 @@ public static class ObjectHelper

internal static bool CanProvoke(this IGameObject target)
{
if (target == null) return false;

//Removed the listed names.
IEnumerable<string> names = [];
IEnumerable<string> names = Array.Empty<string>();
if (OtherConfiguration.NoProvokeNames.TryGetValue(Svc.ClientState.TerritoryType, out var ns1))
names = names.Union(ns1);

Expand Down Expand Up @@ -73,79 +73,58 @@ internal static unsafe bool IsOthersPlayers(this IGameObject obj)
return false;
}

internal static bool IsAttackable(this IBattleChara IBattleChara)
internal static bool IsAttackable(this IBattleChara battleChara)
{
//Dead.
if (IBattleChara.CurrentHp <= 1) return false;
if (Service.Config.FilterOneHpInvincible && battleChara.CurrentHp <= 1) return false;

if (IBattleChara.StatusList.Any(StatusHelper.IsInvincible)) return false;
if (battleChara.StatusList.Any(StatusHelper.IsInvincible)) return false;

if (Svc.ClientState == null) return false;

//In No Hostiles Names
IEnumerable<string> names = [];
IEnumerable<string> names = Array.Empty<string>();
if (OtherConfiguration.NoHostileNames.TryGetValue(Svc.ClientState.TerritoryType, out var ns1))
names = names.Union(ns1);

if (names.Any(n => !string.IsNullOrEmpty(n) && new Regex(n).Match(IBattleChara.Name.TextValue).Success)) return false;
if (names.Any(n => !string.IsNullOrEmpty(n) && new Regex(n).Match(battleChara.Name.TextValue).Success)) return false;

//Fate
if (DataCenter.TerritoryContentType != TerritoryContentType.Eureka)
{
var tarFateId = IBattleChara.FateId();
var tarFateId = battleChara.FateId();
if (tarFateId != 0 && tarFateId != DataCenter.FateId) return false;
}

if (Service.Config.AddEnemyListToHostile)
{
if (IBattleChara.IsInEnemiesList()) return true;
if (battleChara.IsInEnemiesList()) return true;
//Only attack
if (Service.Config.OnlyAttackInEnemyList) return false;
}

//Tar on me
if (IBattleChara.TargetObject == Player.Object
|| IBattleChara.TargetObject?.OwnerId == Player.Object.GameObjectId) return true;
if (battleChara.TargetObject == Player.Object
|| battleChara.TargetObject?.OwnerId == Player.Object.GameObjectId) return true;

//Remove other's treasure.
if (IBattleChara.IsOthersPlayers()) return false;
if (battleChara.IsOthersPlayers()) return false;

if (IBattleChara.IsTopPriorityHostile()) return true;
if (battleChara.IsTopPriorityHostile()) return true;

if (Service.CountDownTime > 0 || DataCenter.IsPvP) return true;

return DataCenter.RightNowTargetToHostileType switch
{
TargetHostileType.AllTargetsCanAttack => true,
TargetHostileType.TargetsHaveTarget => IBattleChara.TargetObject is IBattleChara,
TargetHostileType.AllTargetsWhenSolo => DataCenter.PartyMembers.Length < 2 || IBattleChara.TargetObject is IBattleChara,
TargetHostileType.TargetsHaveTarget => battleChara.TargetObject is IBattleChara,
TargetHostileType.AllTargetsWhenSolo => DataCenter.PartyMembers.Length < 2 || battleChara.TargetObject is IBattleChara,
TargetHostileType.AllTargetsWhenSoloInDuty => (DataCenter.PartyMembers.Length < 2 && Svc.Condition[ConditionFlag.BoundByDuty])
|| IBattleChara.TargetObject is IBattleChara,
|| battleChara.TargetObject is IBattleChara,
_ => true,
};
}


internal static string EncryptString(this IPlayerCharacter player)
{
if (player == null) return string.Empty;

try
{
byte[] inputByteArray = Encoding.UTF8.GetBytes(player.HomeWorld.GameData!.InternalName.ToString()
+ " - " + player.Name.ToString() + "U6Wy.zCG");

var tmpHash = MD5.HashData(inputByteArray);
var retB = Convert.ToBase64String(tmpHash);
return retB;
}
catch (Exception ex)
{
Svc.Log.Warning(ex, "Failed to read the player's name and world.");
return string.Empty;
}
}

internal static unsafe bool IsInEnemiesList(this IBattleChara IBattleChara)
{
var addons = Service.GetAddons<AddonEnemyList>();
Expand All @@ -155,10 +134,14 @@ internal static unsafe bool IsInEnemiesList(this IBattleChara IBattleChara)
var enemy = (AddonEnemyList*)addon;

var numArray = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUIModule()->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder.NumberArrays[19];
List<uint> list = new(enemy->EnemyCount);
if (numArray == null) return false;

const int baseIndex = 8;
const int step = 6;

for (var i = 0; i < enemy->EnemyCount; i++)
{
var id = (uint)numArray->IntArray[8 + i * 6];
var id = (uint)numArray->IntArray[baseIndex + i * step];

if (IBattleChara.GameObjectId == id) return true;
}
Expand Down Expand Up @@ -221,6 +204,11 @@ internal static bool IsAlive(this IGameObject obj)
internal static bool IsTopPriorityHostile(this IGameObject obj)
{
var fateId = DataCenter.FateId;

if (obj is IBattleChara b && b.StatusList != null && b.StatusList.Any(StatusHelper.IsPriority)) return true;

if (Service.Config.ChooseAttackMark && MarkingHelper.AttackSignTargets.FirstOrDefault(id => id != 0) == (long)obj.GameObjectId) return true;

//Fate
if (Service.Config.TargetFatePriority && fateId != 0 && obj.FateId() == fateId) return true;

Expand All @@ -240,11 +228,6 @@ or 71224 //Other Quest
or 71344 //Major Quest
|| obj.GetEventType() is EventHandlerType.Quest)) return true;

if (obj is IBattleChara b && b.StatusList != null && b.StatusList.Any(StatusHelper.IsPriority)) return true;

if (Service.Config.ChooseAttackMark && MarkingHelper.AttackSignTargets.FirstOrDefault(id => id != 0) == (long)obj.GameObjectId) return true;


var npc = obj as IBattleChara;
if (npc != null && DataCenter.PrioritizedNameIds.Contains(npc.NameId)) return true;

Expand Down Expand Up @@ -327,6 +310,7 @@ public static bool IsDying(this IBattleChara b)

internal static unsafe bool InCombat(this IBattleChara obj)
{
if (obj == null || obj.Struct() == null) return false;
return obj.Struct()->Character.InCombat;
}

Expand Down Expand Up @@ -355,6 +339,7 @@ internal static float GetTimeToKill(this IBattleChara b, bool wholeTime = false)
if (startTime == DateTime.MinValue || timespan < CheckSpan) return float.NaN;

var ratioNow = b.GetHealthRatio();
if (float.IsNaN(ratioNow)) return float.NaN;

var ratioReduce = thatTimeRatio - ratioNow;
if (ratioReduce <= 0) return float.NaN;
Expand All @@ -364,6 +349,7 @@ internal static float GetTimeToKill(this IBattleChara b, bool wholeTime = false)

internal static bool IsAttacked(this IBattleChara b)
{
if (b == null) return false;
foreach (var (id, time) in DataCenter.AttackedTargets)
{
if (id == b.GameObjectId)
Expand Down
Loading

0 comments on commit f427d2a

Please sign in to comment.