forked from liza/Questionable
master #3
@ -188,14 +188,15 @@
|
||||
"Z": 94.77368
|
||||
},
|
||||
"TerritoryId": 958,
|
||||
"InteractionType": "Instruction",
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AfterInteraction",
|
||||
"KillEnemyDataIds": [
|
||||
14079
|
||||
],
|
||||
"Comment": "TODO Needs item use?",
|
||||
"CombatItemUse": {
|
||||
"ItemId": 2003231,
|
||||
"ItemUseHealthMaxPercent": 10,
|
||||
"Condition": "Incapacitated"
|
||||
},
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
@ -279,14 +280,15 @@
|
||||
"Z": 396.96338
|
||||
},
|
||||
"TerritoryId": 958,
|
||||
"InteractionType": "Instruction",
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AfterInteraction",
|
||||
"KillEnemyDataIds": [
|
||||
14080
|
||||
],
|
||||
"Comment": "TODO Needs item use?",
|
||||
"CombatItemUse": {
|
||||
"ItemId": 2003231,
|
||||
"ItemUseHealthMaxPercent": 10,
|
||||
"Condition": "Incapacitated"
|
||||
},
|
||||
"DisableNavmesh": true,
|
||||
"CompletionQuestVariablesFlags": [
|
||||
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": {
|
||||
"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 List<uint> KillEnemyDataIds { get; set; } = [];
|
||||
public List<ComplexCombatData> ComplexCombatData { get; set; } = [];
|
||||
public CombatItemUse? CombatItemUse { get; set; }
|
||||
public float? CombatDelaySecondsAtStart { get; set; }
|
||||
|
||||
public JumpDestination? JumpDestination { get; set; }
|
||||
|
@ -65,11 +65,11 @@ internal sealed class CombatController : IDisposable
|
||||
{
|
||||
Stop("Starting combat");
|
||||
|
||||
var combatModule = _combatModules.FirstOrDefault(x => x.IsLoaded);
|
||||
var combatModule = _combatModules.FirstOrDefault(x => x.CanHandleFight(combatData));
|
||||
if (combatModule == null)
|
||||
return false;
|
||||
|
||||
if (combatModule.Start())
|
||||
if (combatModule.Start(combatData))
|
||||
{
|
||||
_currentFight = new CurrentFight
|
||||
{
|
||||
@ -364,6 +364,7 @@ internal sealed class CombatController : IDisposable
|
||||
public required EEnemySpawnType SpawnType { get; init; }
|
||||
public required List<uint> KillEnemyDataIds { get; init; }
|
||||
public required List<ComplexCombatData> ComplexCombatDatas { get; init; }
|
||||
public required CombatItemUse? CombatItemUse { get; init; }
|
||||
|
||||
public HashSet<int> CompletedComplexDatas { get; } = new();
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ namespace Questionable.Controller.CombatModules;
|
||||
|
||||
internal interface ICombatModule
|
||||
{
|
||||
bool IsLoaded { get; }
|
||||
bool CanHandleFight(CombatController.CombatData combatData);
|
||||
|
||||
bool Start();
|
||||
bool Start(CombatController.CombatData combatData);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -32,9 +32,7 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
|
||||
pluginInterface.GetIpcSubscriber<StateCommandType, object>("RotationSolverReborn.ChangeOperatingMode");
|
||||
}
|
||||
|
||||
public bool IsLoaded
|
||||
{
|
||||
get
|
||||
public bool CanHandleFight(CombatController.CombatData combatData)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -46,9 +44,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Start()
|
||||
public bool Start(CombatController.CombatData combatData)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -169,7 +169,7 @@ internal abstract class MiniTaskController<T>
|
||||
if (_condition[ConditionFlag.Mounted])
|
||||
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());
|
||||
_taskQueue.InterruptWith(tasks);
|
||||
}
|
||||
|
@ -97,12 +97,12 @@ internal static class Combat
|
||||
|
||||
bool isLastStep = sequence.Steps.Last() == step;
|
||||
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,
|
||||
IList<uint> killEnemyDataIds, IList<QuestWorkValue?> completionQuestVariablesFlags,
|
||||
IList<ComplexCombatData> complexCombatData)
|
||||
IList<ComplexCombatData> complexCombatData, CombatItemUse? combatItemUse)
|
||||
{
|
||||
return new Task(new CombatController.CombatData
|
||||
{
|
||||
@ -110,6 +110,7 @@ internal static class Combat
|
||||
SpawnType = enemySpawnType,
|
||||
KillEnemyDataIds = killEnemyDataIds.ToList(),
|
||||
ComplexCombatDatas = complexCombatData.ToList(),
|
||||
CombatItemUse = combatItemUse,
|
||||
}, completionQuestVariablesFlags, isLastStep);
|
||||
}
|
||||
}
|
||||
|
@ -234,6 +234,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<LeveUiController>();
|
||||
|
||||
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
|
||||
serviceCollection.AddSingleton<ICombatModule, ItemUseModule>();
|
||||
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user