Add experimental combat module for Magiteknical Failure (aether current quest)
This commit is contained in:
parent
d7aa4243d7
commit
7f348207d3
@ -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
|
||||
|
@ -15,6 +15,8 @@ public enum EAction
|
||||
BuffetSanuwa = 4931,
|
||||
BuffetGriffin = 4583,
|
||||
Fumigate = 5872,
|
||||
MagitekPulse = 8624,
|
||||
MagitekThunder = 8625,
|
||||
SiphonSnout = 18187,
|
||||
Cannonfire = 20121,
|
||||
RedGulal = 29382,
|
||||
|
@ -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 ||
|
||||
|
@ -13,4 +13,6 @@ internal interface ICombatModule
|
||||
void Update(IGameObject nextTarget);
|
||||
|
||||
void MoveToTarget(IGameObject nextTarget);
|
||||
|
||||
bool CanAttack(IBattleNpc target);
|
||||
}
|
||||
|
51
Questionable/Controller/CombatModules/Mount128Module.cs
Normal file
51
Questionable/Controller/CombatModules/Mount128Module.cs
Normal 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;
|
||||
}
|
@ -119,6 +119,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanAttack(IBattleNpc target) => true;
|
||||
|
||||
public void Dispose() => Stop();
|
||||
|
||||
[PublicAPI]
|
||||
|
@ -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<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()
|
||||
{
|
||||
_commandManager.RemoveHandler("/qst");
|
||||
|
@ -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<ITask> 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)
|
||||
|
@ -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<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);
|
||||
return false;
|
||||
|
@ -185,6 +185,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<InteractionUiController>();
|
||||
serviceCollection.AddSingleton<LeveUiController>();
|
||||
|
||||
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
|
||||
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user