Optimize combat for overworld enemies

This commit is contained in:
Liza 2025-01-12 02:28:31 +01:00
parent 97cbeada2a
commit a111a4f75b
Signed by: liza
GPG Key ID: 2C41B84815CF6445
3 changed files with 102 additions and 11 deletions

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
@ -10,7 +11,8 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Common.Math;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using Microsoft.Extensions.Logging;
using Questionable.Controller.CombatModules;
using Questionable.Controller.Steps;
@ -38,6 +40,7 @@ internal sealed class CombatController : IDisposable
private CurrentFight? _currentFight;
private bool _wasInCombat;
private ulong? _lastTargetId;
private List<byte>? _previousQuestVariables;
public CombatController(
IEnumerable<ICombatModule> combatModules,
@ -79,7 +82,9 @@ internal sealed class CombatController : IDisposable
Data = combatData,
LastDistanceCheck = DateTime.Now,
};
_wasInCombat = combatData.SpawnType is EEnemySpawnType.QuestInterruption or EEnemySpawnType.FinishCombatIfAny;
_wasInCombat =
combatData.SpawnType is EEnemySpawnType.QuestInterruption or EEnemySpawnType.FinishCombatIfAny;
UpdateLastTargetAndQuestVariables(null);
return true;
}
else
@ -115,7 +120,31 @@ internal sealed class CombatController : IDisposable
{
// wait until the game cleans up the target
if (lastTarget.IsDead)
return EStatus.InCombat;
{
ElementId? elementId = _currentFight.Data.ElementId;
QuestProgressInfo? questProgressInfo = elementId != null
? _questFunctions.GetQuestProgressInfo(elementId)
: null;
if (questProgressInfo != null &&
questProgressInfo.Sequence == _currentFight.Data.Sequence &&
QuestWorkUtils.HasCompletionFlags(_currentFight.Data.CompletionQuestVariablesFlags) &&
QuestWorkUtils.MatchesQuestWork(_currentFight.Data.CompletionQuestVariablesFlags,
questProgressInfo))
{
// would be the final enemy of the bunch
return EStatus.InCombat;
}
else if (questProgressInfo != null &&
questProgressInfo.Sequence == _currentFight.Data.Sequence &&
_previousQuestVariables != null &&
!questProgressInfo.Variables.SequenceEqual(_previousQuestVariables))
{
UpdateLastTargetAndQuestVariables(null);
}
else
return EStatus.InCombat;
}
}
else
_lastTargetId = null;
@ -372,9 +401,18 @@ internal sealed class CombatController : IDisposable
float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
float maxDistance = player.ClassJob.ValueNullable?.Role is 3 or 4 ? 20f : 2.9f;
if (actualDistance - hitboxOffset >= maxDistance)
bool outOfRange = actualDistance - hitboxOffset >= maxDistance;
bool isInLineOfSight = IsInLineOfSight(gameObject);
if (outOfRange || !isInLineOfSight)
{
if (actualDistance - hitboxOffset <= 5)
bool useNavmesh = actualDistance - hitboxOffset > 5f;
if (!outOfRange && !isInLineOfSight)
{
maxDistance = Math.Min(maxDistance, actualDistance) / 2;
useNavmesh = true;
}
if (!useNavmesh)
{
_logger.LogInformation("Moving to {TargetName} ({DataId}) to attack", gameObject.Name,
gameObject.DataId);
@ -391,6 +429,44 @@ internal sealed class CombatController : IDisposable
}
}
internal unsafe bool IsInLineOfSight(IGameObject target)
{
Vector3 sourcePos = _clientState.LocalPlayer!.Position;
sourcePos.Y += 2;
Vector3 targetPos = target.Position;
targetPos.Y += 2;
Vector3 direction = targetPos - sourcePos;
float distance = direction.Length();
direction = Vector3.Normalize(direction);
Vector3 originVect = new Vector3(sourcePos.X, sourcePos.Y, sourcePos.Z);
Vector3 directionVect = new Vector3(direction.X, direction.Y, direction.Z);
RaycastHit hit;
var flags = stackalloc int[] { 0x4000, 0, 0x4000, 0 };
var isLoSBlocked =
Framework.Instance()->BGCollisionModule->RaycastMaterialFilter(&hit, &originVect, &directionVect, distance,
1, flags);
return isLoSBlocked == false;
}
private void UpdateLastTargetAndQuestVariables(IGameObject? target)
{
_lastTargetId = target?.GameObjectId;
_previousQuestVariables = _currentFight!.Data.ElementId != null
? _questFunctions.GetQuestProgressInfo(_currentFight.Data.ElementId)?.Variables
: null;
/*
_logger.LogTrace("UpdateTargetData: {TargetId}; {QuestVariables}",
target?.GameObjectId.ToString("X8", CultureInfo.InvariantCulture) ?? "null",
_previousQuestVariables != null ? string.Join(", ", _previousQuestVariables) : "null");
*/
}
public void Stop(string label)
{
using var scope = _logger.BeginScope(label);
@ -422,6 +498,8 @@ internal sealed class CombatController : IDisposable
public sealed class CombatData
{
public required ElementId? ElementId { get; init; }
public required int Sequence { get; init; }
public required IList<QuestWorkValue?> CompletionQuestVariablesFlags { get; init; }
public required EEnemySpawnType SpawnType { get; init; }
public required List<uint> KillEnemyDataIds { get; init; }
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }

View File

@ -173,7 +173,7 @@ internal abstract class MiniTaskController<T>
if (_condition[ConditionFlag.Mounted])
tasks.Add(new Mount.UnmountTask());
tasks.Add(Combat.Factory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], [], null));
tasks.Add(Combat.Factory.CreateTask(null, -1, false, EEnemySpawnType.QuestInterruption, [], [], [], null));
tasks.Add(new WaitAtEnd.WaitDelay());
_taskQueue.InterruptWith(tasks);
}

View File

@ -102,17 +102,30 @@ internal static class Combat
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
bool isLastStep = sequence.Steps.Last() == step;
return CreateTask(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
step.CompletionQuestVariablesFlags, step.ComplexCombatData, step.CombatItemUse);
return CreateTask(quest.Id,
sequence.Sequence,
isLastStep,
step.EnemySpawnType.Value,
step.KillEnemyDataIds,
step.CompletionQuestVariablesFlags,
step.ComplexCombatData,
step.CombatItemUse);
}
internal static Task CreateTask(ElementId? elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
IList<ComplexCombatData> complexCombatData, CombatItemUse? combatItemUse)
internal static Task CreateTask(ElementId? elementId,
int sequence,
bool isLastStep,
EEnemySpawnType enemySpawnType,
IList<uint> killEnemyDataIds,
IList<QuestWorkValue?> completionQuestVariablesFlags,
IList<ComplexCombatData> complexCombatData,
CombatItemUse? combatItemUse)
{
return new Task(new CombatController.CombatData
{
ElementId = elementId,
Sequence = sequence,
CompletionQuestVariablesFlags = completionQuestVariablesFlags,
SpawnType = enemySpawnType,
KillEnemyDataIds = killEnemyDataIds.ToList(),
ComplexCombatDatas = complexCombatData.ToList(),