Experimentally handle item use in 'Strange Bedfellows'
This commit is contained in:
parent
4f5721e67b
commit
7a3bab3d51
@ -188,14 +188,15 @@
|
|||||||
"Z": 94.77368
|
"Z": 94.77368
|
||||||
},
|
},
|
||||||
"TerritoryId": 958,
|
"TerritoryId": 958,
|
||||||
"InteractionType": "Instruction",
|
"InteractionType": "Combat",
|
||||||
"EnemySpawnType": "AfterInteraction",
|
"EnemySpawnType": "AfterInteraction",
|
||||||
"KillEnemyDataIds": [
|
"KillEnemyDataIds": [
|
||||||
14079
|
14079
|
||||||
],
|
],
|
||||||
"Comment": "TODO Needs item use?",
|
"CombatItemUse": {
|
||||||
"ItemId": 2003231,
|
"ItemId": 2003231,
|
||||||
"ItemUseHealthMaxPercent": 10,
|
"Condition": "Incapacitated"
|
||||||
|
},
|
||||||
"CompletionQuestVariablesFlags": [
|
"CompletionQuestVariablesFlags": [
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -279,14 +280,15 @@
|
|||||||
"Z": 396.96338
|
"Z": 396.96338
|
||||||
},
|
},
|
||||||
"TerritoryId": 958,
|
"TerritoryId": 958,
|
||||||
"InteractionType": "Instruction",
|
"InteractionType": "Combat",
|
||||||
"EnemySpawnType": "AfterInteraction",
|
"EnemySpawnType": "AfterInteraction",
|
||||||
"KillEnemyDataIds": [
|
"KillEnemyDataIds": [
|
||||||
14080
|
14080
|
||||||
],
|
],
|
||||||
"Comment": "TODO Needs item use?",
|
"CombatItemUse": {
|
||||||
"ItemId": 2003231,
|
"ItemId": 2003231,
|
||||||
"ItemUseHealthMaxPercent": 10,
|
"Condition": "Incapacitated"
|
||||||
|
},
|
||||||
"DisableNavmesh": true,
|
"DisableNavmesh": true,
|
||||||
"CompletionQuestVariablesFlags": [
|
"CompletionQuestVariablesFlags": [
|
||||||
null,
|
null,
|
||||||
|
@ -617,6 +617,25 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CombatItemUse": {
|
||||||
|
"description": "Unlike the 'AfterItemUse' condition that is used for spawning an enemy in the first place, interacting with an item at a certain stage of combat is required",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ItemId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"Condition": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Incapacitated"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"ItemId",
|
||||||
|
"Condition"
|
||||||
|
]
|
||||||
|
},
|
||||||
"CombatDelaySecondsAtStart": {
|
"CombatDelaySecondsAtStart": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
|
12
Questionable.Model/Questing/CombatItemUse.cs
Normal file
12
Questionable.Model/Questing/CombatItemUse.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Questionable.Model.Questing.Converter;
|
||||||
|
|
||||||
|
namespace Questionable.Model.Questing;
|
||||||
|
|
||||||
|
public sealed class CombatItemUse
|
||||||
|
{
|
||||||
|
public uint ItemId { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(CombatItemUseConditionConverter))]
|
||||||
|
public ECombatItemUseCondition Condition { get; set; }
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Questionable.Model.Common.Converter;
|
||||||
|
|
||||||
|
namespace Questionable.Model.Questing.Converter;
|
||||||
|
|
||||||
|
public sealed class CombatItemUseConditionConverter() : EnumConverter<ECombatItemUseCondition>(Values)
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<ECombatItemUseCondition, string> Values = new()
|
||||||
|
{
|
||||||
|
{ ECombatItemUseCondition.Incapacitated, "Incapacitated" },
|
||||||
|
};
|
||||||
|
}
|
7
Questionable.Model/Questing/ECombatItemUseCondition.cs
Normal file
7
Questionable.Model/Questing/ECombatItemUseCondition.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Questionable.Model.Questing;
|
||||||
|
|
||||||
|
public enum ECombatItemUseCondition
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Incapacitated,
|
||||||
|
}
|
@ -67,6 +67,7 @@ public sealed class QuestStep
|
|||||||
public EEnemySpawnType? EnemySpawnType { get; set; }
|
public EEnemySpawnType? EnemySpawnType { get; set; }
|
||||||
public List<uint> KillEnemyDataIds { get; set; } = [];
|
public List<uint> KillEnemyDataIds { get; set; } = [];
|
||||||
public List<ComplexCombatData> ComplexCombatData { get; set; } = [];
|
public List<ComplexCombatData> ComplexCombatData { get; set; } = [];
|
||||||
|
public CombatItemUse? CombatItemUse { get; set; }
|
||||||
public float? CombatDelaySecondsAtStart { get; set; }
|
public float? CombatDelaySecondsAtStart { get; set; }
|
||||||
|
|
||||||
public JumpDestination? JumpDestination { get; set; }
|
public JumpDestination? JumpDestination { get; set; }
|
||||||
|
@ -65,11 +65,11 @@ internal sealed class CombatController : IDisposable
|
|||||||
{
|
{
|
||||||
Stop("Starting combat");
|
Stop("Starting combat");
|
||||||
|
|
||||||
var combatModule = _combatModules.FirstOrDefault(x => x.IsLoaded);
|
var combatModule = _combatModules.FirstOrDefault(x => x.CanHandleFight(combatData));
|
||||||
if (combatModule == null)
|
if (combatModule == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (combatModule.Start())
|
if (combatModule.Start(combatData))
|
||||||
{
|
{
|
||||||
_currentFight = new CurrentFight
|
_currentFight = new CurrentFight
|
||||||
{
|
{
|
||||||
@ -364,6 +364,7 @@ internal sealed class CombatController : IDisposable
|
|||||||
public required EEnemySpawnType SpawnType { get; init; }
|
public required EEnemySpawnType SpawnType { get; init; }
|
||||||
public required List<uint> KillEnemyDataIds { get; init; }
|
public required List<uint> KillEnemyDataIds { get; init; }
|
||||||
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
||||||
|
public required CombatItemUse? CombatItemUse { get; init; }
|
||||||
|
|
||||||
public HashSet<int> CompletedComplexDatas { get; } = new();
|
public HashSet<int> CompletedComplexDatas { get; } = new();
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ namespace Questionable.Controller.CombatModules;
|
|||||||
|
|
||||||
internal interface ICombatModule
|
internal interface ICombatModule
|
||||||
{
|
{
|
||||||
bool IsLoaded { get; }
|
bool CanHandleFight(CombatController.CombatData combatData);
|
||||||
|
|
||||||
bool Start();
|
bool Start(CombatController.CombatData combatData);
|
||||||
|
|
||||||
bool Stop();
|
bool Stop();
|
||||||
|
|
||||||
|
142
Questionable/Controller/CombatModules/ItemUseModule.cs
Normal file
142
Questionable/Controller/CombatModules/ItemUseModule.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
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 Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.CombatModules;
|
||||||
|
|
||||||
|
internal sealed class ItemUseModule : ICombatModule
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly GameFunctions _gameFunctions;
|
||||||
|
private readonly ICondition _condition;
|
||||||
|
private readonly ILogger<ItemUseModule> _logger;
|
||||||
|
|
||||||
|
private ICombatModule? _delegate;
|
||||||
|
private CombatController.CombatData? _combatData;
|
||||||
|
private bool _isDoingRotation;
|
||||||
|
|
||||||
|
public ItemUseModule(IServiceProvider serviceProvider, GameFunctions gameFunctions, ICondition condition,
|
||||||
|
ILogger<ItemUseModule> logger)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_gameFunctions = gameFunctions;
|
||||||
|
_condition = condition;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHandleFight(CombatController.CombatData combatData)
|
||||||
|
{
|
||||||
|
if (combatData.CombatItemUse == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_delegate = _serviceProvider.GetRequiredService<IEnumerable<ICombatModule>>()
|
||||||
|
.Where(x => x is not ItemUseModule)
|
||||||
|
.FirstOrDefault(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;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Stop()
|
||||||
|
{
|
||||||
|
if (_isDoingRotation)
|
||||||
|
{
|
||||||
|
_delegate!.Stop();
|
||||||
|
_isDoingRotation = false;
|
||||||
|
_combatData = null;
|
||||||
|
_delegate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(IGameObject nextTarget)
|
||||||
|
{
|
||||||
|
if (_delegate == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_combatData?.CombatItemUse == null)
|
||||||
|
{
|
||||||
|
_delegate.Update(nextTarget);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_combatData.KillEnemyDataIds.Contains(nextTarget.DataId) ||
|
||||||
|
_combatData.ComplexCombatDatas.Any(x => x.DataId == nextTarget.DataId))
|
||||||
|
{
|
||||||
|
if (_isDoingRotation)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
|
if (inventoryManager->GetInventoryItemCount(_combatData.CombatItemUse.ItemId) == 0)
|
||||||
|
{
|
||||||
|
_isDoingRotation = false;
|
||||||
|
_delegate.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShouldUseItem(nextTarget))
|
||||||
|
{
|
||||||
|
_isDoingRotation = false;
|
||||||
|
_delegate.Stop();
|
||||||
|
_gameFunctions.UseItem(nextTarget.DataId, _combatData.CombatItemUse.ItemId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_delegate.Update(nextTarget);
|
||||||
|
}
|
||||||
|
else if (_condition[ConditionFlag.Casting])
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
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* battleChara = (BattleChara*)gameObject.Address;
|
||||||
|
if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.Incapacitated)
|
||||||
|
return (battleChara->Flags2 & 128u) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveToTarget(IGameObject nextTarget) => _delegate!.MoveToTarget(nextTarget);
|
||||||
|
|
||||||
|
public bool CanAttack(IBattleNpc target) => _delegate!.CanAttack(target);
|
||||||
|
}
|
@ -25,9 +25,9 @@ internal sealed class Mount128Module : ICombatModule
|
|||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoaded => _gameFunctions.GetMountId() == MountId;
|
public bool CanHandleFight(CombatController.CombatData combatData) => _gameFunctions.GetMountId() == MountId;
|
||||||
|
|
||||||
public bool Start() => true;
|
public bool Start(CombatController.CombatData combatData) => true;
|
||||||
|
|
||||||
public bool Stop() => true;
|
public bool Stop() => true;
|
||||||
|
|
||||||
|
@ -32,23 +32,20 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
|
|||||||
pluginInterface.GetIpcSubscriber<StateCommandType, object>("RotationSolverReborn.ChangeOperatingMode");
|
pluginInterface.GetIpcSubscriber<StateCommandType, object>("RotationSolverReborn.ChangeOperatingMode");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoaded
|
public bool CanHandleFight(CombatController.CombatData combatData)
|
||||||
{
|
{
|
||||||
get
|
try
|
||||||
{
|
{
|
||||||
try
|
_test.InvokeAction("Validate RSR is callable from Questionable");
|
||||||
{
|
return true;
|
||||||
_test.InvokeAction("Validate RSR is callable from Questionable");
|
}
|
||||||
return true;
|
catch (IpcError)
|
||||||
}
|
{
|
||||||
catch (IpcError)
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start()
|
public bool Start(CombatController.CombatData combatData)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -169,7 +169,7 @@ internal abstract class MiniTaskController<T>
|
|||||||
if (_condition[ConditionFlag.Mounted])
|
if (_condition[ConditionFlag.Mounted])
|
||||||
tasks.Add(new Mount.UnmountTask());
|
tasks.Add(new Mount.UnmountTask());
|
||||||
|
|
||||||
tasks.Add(Combat.Factory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], []));
|
tasks.Add(Combat.Factory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], [], null));
|
||||||
tasks.Add(new WaitAtEnd.WaitDelay());
|
tasks.Add(new WaitAtEnd.WaitDelay());
|
||||||
_taskQueue.InterruptWith(tasks);
|
_taskQueue.InterruptWith(tasks);
|
||||||
}
|
}
|
||||||
|
@ -97,12 +97,12 @@ internal static class Combat
|
|||||||
|
|
||||||
bool isLastStep = sequence.Steps.Last() == step;
|
bool isLastStep = sequence.Steps.Last() == step;
|
||||||
return CreateTask(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
|
return CreateTask(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
|
||||||
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
step.CompletionQuestVariablesFlags, step.ComplexCombatData, step.CombatItemUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Task CreateTask(ElementId? elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
internal static Task CreateTask(ElementId? elementId, bool isLastStep, EEnemySpawnType enemySpawnType,
|
||||||
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||||
IList<ComplexCombatData> complexCombatData)
|
IList<ComplexCombatData> complexCombatData, CombatItemUse? combatItemUse)
|
||||||
{
|
{
|
||||||
return new Task(new CombatController.CombatData
|
return new Task(new CombatController.CombatData
|
||||||
{
|
{
|
||||||
@ -110,6 +110,7 @@ internal static class Combat
|
|||||||
SpawnType = enemySpawnType,
|
SpawnType = enemySpawnType,
|
||||||
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
||||||
ComplexCombatDatas = complexCombatData.ToList(),
|
ComplexCombatDatas = complexCombatData.ToList(),
|
||||||
|
CombatItemUse = combatItemUse,
|
||||||
}, completionQuestVariablesFlags, isLastStep);
|
}, completionQuestVariablesFlags, isLastStep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddSingleton<LeveUiController>();
|
serviceCollection.AddSingleton<LeveUiController>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
|
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
|
||||||
|
serviceCollection.AddSingleton<ICombatModule, ItemUseModule>();
|
||||||
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
|
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user