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 DateTime _continueAt; 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.LogInformation("ItemUse delegate: {Delegate}", _delegate?.GetType().Name); return _delegate != null; } public bool Start(CombatController.CombatData combatData) { if (_delegate.Start(combatData)) { _combatData = combatData; _isDoingRotation = true; _continueAt = DateTime.Now; return true; } return false; } public bool Stop() { if (_isDoingRotation) { _delegate.Stop(); _isDoingRotation = false; _combatData = null; _delegate = null; _continueAt = DateTime.Now; } return true; } public unsafe void Update(IGameObject nextTarget) { if (_delegate == null || _continueAt > DateTime.Now) { 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) { if (InventoryManager.Instance()->GetInventoryItemCount(_combatData.CombatItemUse.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0) { _isDoingRotation = false; _delegate.Stop(); } else if (ShouldUseItem(nextTarget)) { _isDoingRotation = false; _delegate.Stop(); _logger.LogInformation("Using item {ItemId}", _combatData.CombatItemUse.ItemId); AgentInventoryContext.Instance()->UseItem(_combatData.CombatItemUse.ItemId, InventoryType.Invalid, 0u, 0); _continueAt = DateTime.Now.AddSeconds(2.0); } else { _delegate.Update(nextTarget); } } else if (_condition[ConditionFlag.Casting]) { DateTime dateTime = DateTime.Now.AddSeconds(0.5); if (dateTime > _continueAt) { _continueAt = dateTime; } } 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); } }