Add experimental combat module for Magiteknical Failure (aether current quest)

This commit is contained in:
Liza 2024-09-01 15:14:28 +02:00
parent d7aa4243d7
commit 7f348207d3
Signed by: liza
GPG Key ID: 7199F8D727D55F67
10 changed files with 125 additions and 26 deletions

View File

@ -49,8 +49,7 @@
}, },
"StopDistance": 0.5, "StopDistance": 0.5,
"TerritoryId": 612, "TerritoryId": 612,
"InteractionType": "Instruction", "InteractionType": "Combat",
"Comment": "Manual combat",
"EnemySpawnType": "AutoOnEnterArea", "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
7504 7504
@ -69,8 +68,7 @@
}, },
"StopDistance": 2, "StopDistance": 2,
"TerritoryId": 612, "TerritoryId": 612,
"InteractionType": "Instruction", "InteractionType": "Combat",
"Comment": "Manual combat",
"EnemySpawnType": "AutoOnEnterArea", "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
7505 7505

View File

@ -15,6 +15,8 @@ public enum EAction
BuffetSanuwa = 4931, BuffetSanuwa = 4931,
BuffetGriffin = 4583, BuffetGriffin = 4583,
Fumigate = 5872, Fumigate = 5872,
MagitekPulse = 8624,
MagitekThunder = 8625,
SiphonSnout = 18187, SiphonSnout = 18187,
Cannonfire = 20121, Cannonfire = 20121,
RedGulal = 29382, RedGulal = 29382,

View File

@ -191,6 +191,9 @@ internal sealed class CombatController : IDisposable
{ {
if (gameObject is IBattleNpc battleNpc) if (gameObject is IBattleNpc battleNpc)
{ {
if (_currentFight != null && !_currentFight.Module.CanAttack(battleNpc))
return 0;
// TODO this works as somewhat of a delay between killing enemies if certain items/flags are checked // TODO this works as somewhat of a delay between killing enemies if certain items/flags are checked
// but also delays killing the next enemy a little // but also delays killing the next enemy a little
if (_currentFight == null || _currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies || if (_currentFight == null || _currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies ||

View File

@ -13,4 +13,6 @@ internal interface ICombatModule
void Update(IGameObject nextTarget); void Update(IGameObject nextTarget);
void MoveToTarget(IGameObject nextTarget); void MoveToTarget(IGameObject nextTarget);
bool CanAttack(IBattleNpc target);
} }

View File

@ -0,0 +1,51 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.CombatModules;
/// <summary>
/// Commandeered Magitek Armor; used in 'Magiteknical Failure' quest.
/// </summary>
internal sealed class Mount128Module : ICombatModule
{
public const ushort MountId = 128;
private readonly EAction[] _actions = [EAction.MagitekThunder, EAction.MagitekPulse];
private readonly MovementController _movementController;
private readonly GameFunctions _gameFunctions;
public Mount128Module(MovementController movementController, GameFunctions gameFunctions)
{
_movementController = movementController;
_gameFunctions = gameFunctions;
}
public bool IsLoaded => _gameFunctions.GetMountId() == MountId;
public bool Start() => true;
public bool Stop() => true;
public void Update(IGameObject gameObject)
{
if (_movementController.IsPathfinding || _movementController.IsPathRunning)
return;
foreach (EAction action in _actions)
{
if (_gameFunctions.UseAction(gameObject, action, checkCanUse: false))
return;
}
}
public void MoveToTarget(IGameObject gameObject)
{
}
public bool CanAttack(IBattleNpc target) => target.DataId is 7504 or 7505;
}

View File

@ -119,6 +119,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
} }
} }
public bool CanAttack(IBattleNpc target) => true;
public void Dispose() => Stop(); public void Dispose() => Stop();
[PublicAPI] [PublicAPI]

View File

@ -3,16 +3,20 @@ using System.Linq;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Questionable.Windows; using Questionable.Windows;
using Questionable.Windows.QuestComponents; using Questionable.Windows.QuestComponents;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller; namespace Questionable.Controller;
internal sealed class CommandHandler : IDisposable internal sealed class CommandHandler : IDisposable
{ {
private const string MessageTag = "Questionable";
private const ushort TagColor = 576;
private readonly ICommandManager _commandManager; private readonly ICommandManager _commandManager;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly QuestController _questController; private readonly QuestController _questController;
@ -24,6 +28,8 @@ internal sealed class CommandHandler : IDisposable
private readonly QuestSelectionWindow _questSelectionWindow; private readonly QuestSelectionWindow _questSelectionWindow;
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly QuestFunctions _questFunctions; private readonly QuestFunctions _questFunctions;
private readonly GameFunctions _gameFunctions;
private readonly IDataManager _dataManager;
public CommandHandler( public CommandHandler(
ICommandManager commandManager, ICommandManager commandManager,
@ -36,7 +42,9 @@ internal sealed class CommandHandler : IDisposable
QuestWindow questWindow, QuestWindow questWindow,
QuestSelectionWindow questSelectionWindow, QuestSelectionWindow questSelectionWindow,
ITargetManager targetManager, ITargetManager targetManager,
QuestFunctions questFunctions) QuestFunctions questFunctions,
GameFunctions gameFunctions,
IDataManager dataManager)
{ {
_commandManager = commandManager; _commandManager = commandManager;
_chatGui = chatGui; _chatGui = chatGui;
@ -49,6 +57,8 @@ internal sealed class CommandHandler : IDisposable
_questSelectionWindow = questSelectionWindow; _questSelectionWindow = questSelectionWindow;
_targetManager = targetManager; _targetManager = targetManager;
_questFunctions = questFunctions; _questFunctions = questFunctions;
_gameFunctions = gameFunctions;
_dataManager = dataManager;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand) _commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{ {
@ -108,12 +118,16 @@ internal sealed class CommandHandler : IDisposable
_questSelectionWindow.OpenForCurrentZone(); _questSelectionWindow.OpenForCurrentZone();
break; break;
case "mountid":
PrintMountId();
break;
case "": case "":
_questWindow.Toggle(); _questWindow.Toggle();
break; break;
default: default:
_chatGui.PrintError($"Unknown subcommand {parts[0]}", "Questionable"); _chatGui.PrintError($"Unknown subcommand {parts[0]}", MessageTag, TagColor);
break; break;
} }
} }
@ -122,7 +136,7 @@ internal sealed class CommandHandler : IDisposable
{ {
if (!_debugOverlay.DrawConditions()) if (!_debugOverlay.DrawConditions())
{ {
_chatGui.PrintError("[Questionable] You don't have the debug overlay enabled."); _chatGui.PrintError("You don't have the debug overlay enabled.", MessageTag, TagColor);
return; return;
} }
@ -131,15 +145,15 @@ internal sealed class CommandHandler : IDisposable
if (_questRegistry.TryGetQuest(questId, out Quest? quest)) if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{ {
_debugOverlay.HighlightedQuest = quest.Id; _debugOverlay.HighlightedQuest = quest.Id;
_chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name})."); _chatGui.Print($"Set highlighted quest to {questId} ({quest.Info.Name}).", MessageTag, TagColor);
} }
else else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}."); _chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
} }
else else
{ {
_debugOverlay.HighlightedQuest = null; _debugOverlay.HighlightedQuest = null;
_chatGui.Print("[Questionable] Cleared highlighted quest."); _chatGui.Print("Cleared highlighted quest.", MessageTag, TagColor);
} }
} }
@ -148,21 +162,21 @@ internal sealed class CommandHandler : IDisposable
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null) if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
{ {
if (_questFunctions.IsQuestLocked(questId)) if (_questFunctions.IsQuestLocked(questId))
_chatGui.PrintError($"[Questionable] Quest {questId} is locked."); _chatGui.PrintError($"Quest {questId} is locked.", MessageTag, TagColor);
else if (_questRegistry.TryGetQuest(questId, out Quest? quest)) else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{ {
_questController.SetNextQuest(quest); _questController.SetNextQuest(quest);
_chatGui.Print($"[Questionable] Set next quest to {questId} ({quest.Info.Name})."); _chatGui.Print($"Set next quest to {questId} ({quest.Info.Name}).", MessageTag, TagColor);
} }
else else
{ {
_chatGui.PrintError($"[Questionable] Unknown quest {questId}."); _chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
} }
} }
else else
{ {
_questController.SetNextQuest(null); _questController.SetNextQuest(null);
_chatGui.Print("[Questionable] Cleared next quest."); _chatGui.Print("Cleared next quest.", MessageTag, TagColor);
} }
} }
@ -173,18 +187,32 @@ internal sealed class CommandHandler : IDisposable
if (_questRegistry.TryGetQuest(questId, out Quest? quest)) if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{ {
_questController.SimulateQuest(quest); _questController.SimulateQuest(quest);
_chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name})."); _chatGui.Print($"Simulating quest {questId} ({quest.Info.Name}).", MessageTag, TagColor);
} }
else else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}."); _chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
} }
else else
{ {
_questController.SimulateQuest(null); _questController.SimulateQuest(null);
_chatGui.Print("[Questionable] Cleared simulated quest."); _chatGui.Print("Cleared simulated quest.", MessageTag, TagColor);
} }
} }
private void PrintMountId()
{
ushort? mountId = _gameFunctions.GetMountId();
if (mountId != null)
{
var row = _dataManager.GetExcelSheet<Mount>()!.GetRow(mountId.Value);
_chatGui.Print(
$"Mount ID: {mountId}, Name: {row?.Singular}, Obtainable: {(row?.Order == -1 ? "No" : "Yes")}",
MessageTag, TagColor);
}
else
_chatGui.Print("You are not mounted.", MessageTag, TagColor);
}
public void Dispose() public void Dispose()
{ {
_commandManager.RemoveHandler("/qst"); _commandManager.RemoveHandler("/qst");

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Questionable.Controller.CombatModules;
using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils; using Questionable.Controller.Utils;
@ -19,7 +19,8 @@ internal static class Combat
Mount.Factory mountFactory, Mount.Factory mountFactory,
UseItem.Factory useItemFactory, UseItem.Factory useItemFactory,
Action.Factory actionFactory, Action.Factory actionFactory,
QuestFunctions questFunctions) : ITaskFactory QuestFunctions questFunctions,
GameFunctions gameFunctions) : ITaskFactory
{ {
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{ {
@ -28,6 +29,7 @@ internal static class Combat
ArgumentNullException.ThrowIfNull(step.EnemySpawnType); ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
if (gameFunctions.GetMountId() != Mount128Module.MountId)
yield return mountFactory.Unmount(); yield return mountFactory.Unmount();
if (step.CombatDelaySecondsAtStart != null) if (step.CombatDelaySecondsAtStart != null)
@ -70,7 +72,7 @@ internal static class Combat
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1)); yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
yield return CreateTask(quest, sequence, step); yield return CreateTask(quest, sequence, step);
break; break;
} ; }
case EEnemySpawnType.AutoOnEnterArea: case EEnemySpawnType.AutoOnEnterArea:
if (step.CombatDelaySecondsAtStart == null) if (step.CombatDelaySecondsAtStart == null)

View File

@ -81,8 +81,9 @@ internal sealed unsafe class GameFunctions
if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted]) if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
{ {
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0); // special quest amaro, not the normal one
if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one // TODO Check if this also applies to beast tribe mounts
if (GetMountId() == 198)
return true; return true;
} }
@ -92,6 +93,15 @@ internal sealed unsafe class GameFunctions
playerState->IsAetherCurrentZoneComplete(aetherCurrentCompFlgSet); playerState->IsAetherCurrentZoneComplete(aetherCurrentCompFlgSet);
} }
public ushort? GetMountId()
{
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
if (battleChara != null && battleChara->Mount.MountId != 0)
return battleChara->Mount.MountId;
else
return null;
}
public bool IsFlyingUnlockedInCurrentZone() => IsFlyingUnlocked(_clientState.TerritoryType); public bool IsFlyingUnlockedInCurrentZone() => IsFlyingUnlocked(_clientState.TerritoryType);
public bool IsAetherCurrentUnlocked(uint aetherCurrentId) public bool IsAetherCurrentUnlocked(uint aetherCurrentId)
@ -210,10 +220,10 @@ internal sealed unsafe class GameFunctions
return false; return false;
} }
public bool UseAction(IGameObject gameObject, EAction action) public bool UseAction(IGameObject gameObject, EAction action, bool checkCanUse = true)
{ {
var actionRow = _dataManager.GetExcelSheet<Action>()!.GetRow((uint)action)!; var actionRow = _dataManager.GetExcelSheet<Action>()!.GetRow((uint)action)!;
if (!ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address)) if (checkCanUse && !ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address))
{ {
_logger.LogWarning("Can not use action {Action} on target {Target}", action, gameObject); _logger.LogWarning("Can not use action {Action} on target {Target}", action, gameObject);
return false; return false;

View File

@ -185,6 +185,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<InteractionUiController>(); serviceCollection.AddSingleton<InteractionUiController>();
serviceCollection.AddSingleton<LeveUiController>(); serviceCollection.AddSingleton<LeveUiController>();
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>(); serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
} }