230 lines
6.3 KiB
C#
230 lines
6.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Controller.CombatModules;
|
|
|
|
internal sealed class ItemUseModule : ICombatModule
|
|
{
|
|
private readonly IServiceProvider _serviceProvider;
|
|
|
|
private readonly ICondition _condition;
|
|
|
|
private readonly ILogger<ItemUseModule> _logger;
|
|
|
|
private ICombatModule? _delegate;
|
|
|
|
private CombatController.CombatData? _combatData;
|
|
|
|
private bool _isDoingRotation;
|
|
|
|
private long _continueAt;
|
|
|
|
private bool _isWaitingToUseItem;
|
|
|
|
private bool _itemUsePending;
|
|
|
|
private long _itemUsePendingUntil;
|
|
|
|
private int _itemCountBeforeUse;
|
|
|
|
public ICombatModule? Delegate => _delegate;
|
|
|
|
public bool IsItemUseInProgress
|
|
{
|
|
get
|
|
{
|
|
if (_isWaitingToUseItem || _itemUsePending)
|
|
{
|
|
return Environment.TickCount64 < _itemUsePendingUntil;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger<ItemUseModule> logger)
|
|
{
|
|
_serviceProvider = serviceProvider;
|
|
_condition = condition;
|
|
_logger = logger;
|
|
}
|
|
|
|
public bool CanHandleFight(CombatController.CombatData combatData)
|
|
{
|
|
if (combatData.CombatItemUse == null)
|
|
{
|
|
return false;
|
|
}
|
|
_delegate = (from x in _serviceProvider.GetRequiredService<IEnumerable<ICombatModule>>()
|
|
where !(x is ItemUseModule)
|
|
select x).FirstOrDefault((ICombatModule x) => x.CanHandleFight(combatData));
|
|
_logger.LogDebug("ItemUse delegate: {Delegate}", _delegate?.GetType().Name);
|
|
return _delegate != null;
|
|
}
|
|
|
|
public bool Start(CombatController.CombatData combatData)
|
|
{
|
|
if (_delegate.Start(combatData))
|
|
{
|
|
_combatData = combatData;
|
|
_isDoingRotation = true;
|
|
_continueAt = Environment.TickCount64;
|
|
_isWaitingToUseItem = false;
|
|
_itemUsePending = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool Stop()
|
|
{
|
|
if (_isDoingRotation)
|
|
{
|
|
_delegate.Stop();
|
|
}
|
|
_isDoingRotation = false;
|
|
_isWaitingToUseItem = false;
|
|
_itemUsePending = false;
|
|
_combatData = null;
|
|
_delegate = null;
|
|
_continueAt = Environment.TickCount64;
|
|
return true;
|
|
}
|
|
|
|
public unsafe void Update(IGameObject nextTarget)
|
|
{
|
|
if (_delegate == null || _continueAt > Environment.TickCount64)
|
|
{
|
|
return;
|
|
}
|
|
if (_combatData?.CombatItemUse == null)
|
|
{
|
|
_delegate.Update(nextTarget);
|
|
}
|
|
else if (_combatData.KillEnemyDataIds.Contains(nextTarget.BaseId) || _combatData.ComplexCombatDatas.Any((ComplexCombatData x) => x.DataId == nextTarget.BaseId && (!x.NameId.HasValue || (nextTarget is ICharacter character && x.NameId == character.NameId))))
|
|
{
|
|
int inventoryItemCount = InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0);
|
|
if (_itemUsePending)
|
|
{
|
|
if (inventoryItemCount < _itemCountBeforeUse)
|
|
{
|
|
_itemUsePending = false;
|
|
return;
|
|
}
|
|
if (_condition[ConditionFlag.Casting])
|
|
{
|
|
_itemUsePendingUntil = Environment.TickCount64 + 1000;
|
|
}
|
|
if (Environment.TickCount64 < _itemUsePendingUntil)
|
|
{
|
|
_logger.LogDebug("Item use pending; inventory count={Count}", inventoryItemCount);
|
|
return;
|
|
}
|
|
_logger.LogDebug("Item use was not observed; retrying");
|
|
_itemUsePending = false;
|
|
_isWaitingToUseItem = true;
|
|
_itemUsePendingUntil = Environment.TickCount64 + 4000;
|
|
_continueAt = Environment.TickCount64 + 1000;
|
|
}
|
|
else if (_isDoingRotation)
|
|
{
|
|
if (inventoryItemCount == 0)
|
|
{
|
|
_delegate.Update(nextTarget);
|
|
}
|
|
else if (ShouldUseItem(nextTarget))
|
|
{
|
|
_logger.LogDebug("Pausing rotation before using item {ItemId}", _combatData.CombatItemUse.ItemId);
|
|
_isDoingRotation = false;
|
|
_isWaitingToUseItem = true;
|
|
_delegate.Stop();
|
|
_continueAt = Environment.TickCount64 + 3000;
|
|
_itemUsePendingUntil = Environment.TickCount64 + 4000;
|
|
}
|
|
else
|
|
{
|
|
_delegate.Update(nextTarget);
|
|
}
|
|
}
|
|
else if (_isWaitingToUseItem)
|
|
{
|
|
if (_condition[ConditionFlag.Casting])
|
|
{
|
|
_continueAt = Environment.TickCount64 + 500;
|
|
_itemUsePendingUntil = Environment.TickCount64 + 4000;
|
|
return;
|
|
}
|
|
if (!ShouldUseItem(nextTarget) || inventoryItemCount == 0)
|
|
{
|
|
_isWaitingToUseItem = false;
|
|
return;
|
|
}
|
|
_logger.LogDebug("Attempting to use item {ItemId}", _combatData.CombatItemUse.ItemId);
|
|
long num = AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0);
|
|
_logger.LogDebug("UseItem result: {Result}", num);
|
|
bool itemUsePending = (((ulong)num <= 1uL) ? true : false);
|
|
_itemUsePending = itemUsePending;
|
|
_isWaitingToUseItem = !_itemUsePending;
|
|
_itemCountBeforeUse = inventoryItemCount;
|
|
_itemUsePendingUntil = Environment.TickCount64 + 3000;
|
|
_continueAt = Environment.TickCount64 + (_itemUsePending ? 2000 : 500);
|
|
}
|
|
else if (_condition[ConditionFlag.Casting])
|
|
{
|
|
long num2 = Environment.TickCount64 + 500;
|
|
if (num2 > _continueAt)
|
|
{
|
|
_continueAt = num2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_isDoingRotation = true;
|
|
_delegate.Start(_combatData);
|
|
}
|
|
}
|
|
else if (_isDoingRotation)
|
|
{
|
|
_delegate.Update(nextTarget);
|
|
}
|
|
}
|
|
|
|
private unsafe bool ShouldUseItem(IGameObject gameObject)
|
|
{
|
|
if (_combatData?.CombatItemUse == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (gameObject is IBattleChara)
|
|
{
|
|
BattleChara* address = (BattleChara*)gameObject.Address;
|
|
if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.Incapacitated)
|
|
{
|
|
return (address->ActorControlFlags & 0x40) != 0;
|
|
}
|
|
if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.HealthPercent)
|
|
{
|
|
return 100f * (float)address->Health / (float)address->MaxHealth < (float)_combatData.CombatItemUse.Value;
|
|
}
|
|
if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.MissingStatus)
|
|
{
|
|
return !address->StatusManager.HasStatus((uint)_combatData.CombatItemUse.Value);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool CanAttack(IBattleNpc target)
|
|
{
|
|
return _delegate.CanAttack(target);
|
|
}
|
|
}
|