diff --git a/QuestPaths/4.x - Stormblood/Aether Currents/The Fringes/2639_Magiteknical Failure.json b/QuestPaths/4.x - Stormblood/Aether Currents/The Fringes/2639_Magiteknical Failure.json
index 7f1657a26..ce26036ab 100644
--- a/QuestPaths/4.x - Stormblood/Aether Currents/The Fringes/2639_Magiteknical Failure.json
+++ b/QuestPaths/4.x - Stormblood/Aether Currents/The Fringes/2639_Magiteknical Failure.json
@@ -49,8 +49,7 @@
},
"StopDistance": 0.5,
"TerritoryId": 612,
- "InteractionType": "Instruction",
- "Comment": "Manual combat",
+ "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
7504
@@ -69,8 +68,7 @@
},
"StopDistance": 2,
"TerritoryId": 612,
- "InteractionType": "Instruction",
- "Comment": "Manual combat",
+ "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
7505
diff --git a/Questionable.Model/Questing/EAction.cs b/Questionable.Model/Questing/EAction.cs
index 5ca20daf6..89605e877 100644
--- a/Questionable.Model/Questing/EAction.cs
+++ b/Questionable.Model/Questing/EAction.cs
@@ -15,6 +15,8 @@ public enum EAction
BuffetSanuwa = 4931,
BuffetGriffin = 4583,
Fumigate = 5872,
+ MagitekPulse = 8624,
+ MagitekThunder = 8625,
SiphonSnout = 18187,
Cannonfire = 20121,
RedGulal = 29382,
diff --git a/Questionable/Controller/CombatController.cs b/Questionable/Controller/CombatController.cs
index 2a7c08d94..d1ff66bea 100644
--- a/Questionable/Controller/CombatController.cs
+++ b/Questionable/Controller/CombatController.cs
@@ -191,6 +191,9 @@ internal sealed class CombatController : IDisposable
{
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
// but also delays killing the next enemy a little
if (_currentFight == null || _currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies ||
diff --git a/Questionable/Controller/CombatModules/ICombatModule.cs b/Questionable/Controller/CombatModules/ICombatModule.cs
index 1aec57cc8..542e2d6f9 100644
--- a/Questionable/Controller/CombatModules/ICombatModule.cs
+++ b/Questionable/Controller/CombatModules/ICombatModule.cs
@@ -13,4 +13,6 @@ internal interface ICombatModule
void Update(IGameObject nextTarget);
void MoveToTarget(IGameObject nextTarget);
+
+ bool CanAttack(IBattleNpc target);
}
diff --git a/Questionable/Controller/CombatModules/Mount128Module.cs b/Questionable/Controller/CombatModules/Mount128Module.cs
new file mode 100644
index 000000000..39ef13cc3
--- /dev/null
+++ b/Questionable/Controller/CombatModules/Mount128Module.cs
@@ -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;
+
+///
+/// Commandeered Magitek Armor; used in 'Magiteknical Failure' quest.
+///
+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;
+}
diff --git a/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs b/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs
index 8d0484d6a..72e13ae70 100644
--- a/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs
+++ b/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs
@@ -119,6 +119,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
}
}
+ public bool CanAttack(IBattleNpc target) => true;
+
public void Dispose() => Stop();
[PublicAPI]
diff --git a/Questionable/Controller/CommandHandler.cs b/Questionable/Controller/CommandHandler.cs
index dbf8ade5e..776f615af 100644
--- a/Questionable/Controller/CommandHandler.cs
+++ b/Questionable/Controller/CommandHandler.cs
@@ -3,16 +3,20 @@ using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
+using Lumina.Excel.GeneratedSheets;
using Questionable.Functions;
-using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows;
using Questionable.Windows.QuestComponents;
+using Quest = Questionable.Model.Quest;
namespace Questionable.Controller;
internal sealed class CommandHandler : IDisposable
{
+ private const string MessageTag = "Questionable";
+ private const ushort TagColor = 576;
+
private readonly ICommandManager _commandManager;
private readonly IChatGui _chatGui;
private readonly QuestController _questController;
@@ -24,6 +28,8 @@ internal sealed class CommandHandler : IDisposable
private readonly QuestSelectionWindow _questSelectionWindow;
private readonly ITargetManager _targetManager;
private readonly QuestFunctions _questFunctions;
+ private readonly GameFunctions _gameFunctions;
+ private readonly IDataManager _dataManager;
public CommandHandler(
ICommandManager commandManager,
@@ -36,7 +42,9 @@ internal sealed class CommandHandler : IDisposable
QuestWindow questWindow,
QuestSelectionWindow questSelectionWindow,
ITargetManager targetManager,
- QuestFunctions questFunctions)
+ QuestFunctions questFunctions,
+ GameFunctions gameFunctions,
+ IDataManager dataManager)
{
_commandManager = commandManager;
_chatGui = chatGui;
@@ -49,6 +57,8 @@ internal sealed class CommandHandler : IDisposable
_questSelectionWindow = questSelectionWindow;
_targetManager = targetManager;
_questFunctions = questFunctions;
+ _gameFunctions = gameFunctions;
+ _dataManager = dataManager;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
@@ -108,12 +118,16 @@ internal sealed class CommandHandler : IDisposable
_questSelectionWindow.OpenForCurrentZone();
break;
+ case "mountid":
+ PrintMountId();
+ break;
+
case "":
_questWindow.Toggle();
break;
default:
- _chatGui.PrintError($"Unknown subcommand {parts[0]}", "Questionable");
+ _chatGui.PrintError($"Unknown subcommand {parts[0]}", MessageTag, TagColor);
break;
}
}
@@ -122,7 +136,7 @@ internal sealed class CommandHandler : IDisposable
{
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;
}
@@ -131,15 +145,15 @@ internal sealed class CommandHandler : IDisposable
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_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
- _chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
+ _chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
}
else
{
_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 (_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))
{
_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
{
- _chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
+ _chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
}
}
else
{
_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))
{
_questController.SimulateQuest(quest);
- _chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name}).");
+ _chatGui.Print($"Simulating quest {questId} ({quest.Info.Name}).", MessageTag, TagColor);
}
else
- _chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
+ _chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
}
else
{
_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()!.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()
{
_commandManager.RemoveHandler("/qst");
diff --git a/Questionable/Controller/Steps/Interactions/Combat.cs b/Questionable/Controller/Steps/Interactions/Combat.cs
index 6657da338..eab500dde 100644
--- a/Questionable/Controller/Steps/Interactions/Combat.cs
+++ b/Questionable/Controller/Steps/Interactions/Combat.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Microsoft.Extensions.DependencyInjection;
+using Questionable.Controller.CombatModules;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
@@ -19,7 +19,8 @@ internal static class Combat
Mount.Factory mountFactory,
UseItem.Factory useItemFactory,
Action.Factory actionFactory,
- QuestFunctions questFunctions) : ITaskFactory
+ QuestFunctions questFunctions,
+ GameFunctions gameFunctions) : ITaskFactory
{
public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@@ -28,7 +29,8 @@ internal static class Combat
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
- yield return mountFactory.Unmount();
+ if (gameFunctions.GetMountId() != Mount128Module.MountId)
+ yield return mountFactory.Unmount();
if (step.CombatDelaySecondsAtStart != null)
{
@@ -70,7 +72,7 @@ internal static class Combat
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
yield return CreateTask(quest, sequence, step);
break;
- } ;
+ }
case EEnemySpawnType.AutoOnEnterArea:
if (step.CombatDelaySecondsAtStart == null)
diff --git a/Questionable/Functions/GameFunctions.cs b/Questionable/Functions/GameFunctions.cs
index 517957055..90703500f 100644
--- a/Questionable/Functions/GameFunctions.cs
+++ b/Questionable/Functions/GameFunctions.cs
@@ -81,8 +81,9 @@ internal sealed unsafe class GameFunctions
if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
{
- BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
- if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one
+ // special quest amaro, not the normal one
+ // TODO Check if this also applies to beast tribe mounts
+ if (GetMountId() == 198)
return true;
}
@@ -92,6 +93,15 @@ internal sealed unsafe class GameFunctions
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 IsAetherCurrentUnlocked(uint aetherCurrentId)
@@ -210,10 +220,10 @@ internal sealed unsafe class GameFunctions
return false;
}
- public bool UseAction(IGameObject gameObject, EAction action)
+ public bool UseAction(IGameObject gameObject, EAction action, bool checkCanUse = true)
{
var actionRow = _dataManager.GetExcelSheet()!.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);
return false;
diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs
index d1a5b543f..88ebc1b36 100644
--- a/Questionable/QuestionablePlugin.cs
+++ b/Questionable/QuestionablePlugin.cs
@@ -185,6 +185,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
}