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 _logger; private ICombatModule? _delegate; private CombatController.CombatData? _combatData; private bool _isDoingRotation; private long _continueAt; private bool _itemUsePending; private long _itemUsePendingUntil; public ICombatModule? Delegate => _delegate; public ItemUseModule(IServiceProvider serviceProvider, ICondition condition, ILogger 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>() 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; _itemUsePending = false; return true; } return false; } public bool Stop() { if (_isDoingRotation) { _delegate.Stop(); _isDoingRotation = 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)))) { if (_isDoingRotation) { int inventoryItemCount = InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0); if (_itemUsePending) { if (Environment.TickCount64 < _itemUsePendingUntil) { _logger.LogDebug("Item use pending; ignoring temporary inventory count={Count}", inventoryItemCount); } else { _itemUsePending = false; } } if (!_itemUsePending && inventoryItemCount == 0) { _delegate.Update(nextTarget); } else if (ShouldUseItem(nextTarget)) { _logger.LogDebug("Attempting to use item {ItemId}", _combatData.CombatItemUse.ItemId); _itemUsePending = true; _itemUsePendingUntil = Environment.TickCount64 + 3000; AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0); _continueAt = Environment.TickCount64 + 2000; } else { _delegate.Update(nextTarget); } } else if (_condition[ConditionFlag.Casting]) { long num = Environment.TickCount64 + 500; if (num > _continueAt) { _continueAt = num; } } 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); } }