muffin v7.38.8

This commit is contained in:
alydev 2025-11-30 10:36:46 +10:00
parent 5e2d8f648b
commit 3e10cbbbf2
51 changed files with 2585 additions and 1972 deletions

View file

@ -6,7 +6,6 @@ using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -323,12 +322,13 @@ internal sealed class CombatController : IDisposable
}
}
}
IGameObject localPlayer = _objectTable[0];
IGameObject gameObject = (from x in _objectTable
select new
{
GameObject = x,
Priority = GetKillPriority(x).Priority,
Distance = Vector3.Distance(x.Position, _clientState.LocalPlayer.Position)
Distance = Vector3.Distance(x.Position, localPlayer?.Position ?? Vector3.Zero)
} into x
where x.Priority > 0
orderby x.Priority descending, x.Distance
@ -355,7 +355,8 @@ internal sealed class CombatController : IDisposable
}
if (gameObject is IBattleNpc battleNpc && battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat))
{
if (gameObject.TargetObjectId == _clientState.LocalPlayer?.GameObjectId)
IGameObject? gameObject2 = _objectTable[0];
if (gameObject.TargetObjectId == gameObject2?.GameObjectId)
{
return (Priority: num.Value + 150, Reason: text + "/Targeted");
}
@ -393,11 +394,12 @@ internal sealed class CombatController : IDisposable
}
List<ComplexCombatData> complexCombatDatas = _currentFight.Data.ComplexCombatDatas;
GameObject* address = (GameObject*)gameObject.Address;
if (address->FateId != 0 && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies && gameObject.TargetObjectId != _clientState.LocalPlayer?.GameObjectId)
IGameObject gameObject2 = _objectTable[0];
if (address->FateId != 0 && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies && gameObject.TargetObjectId != gameObject2?.GameObjectId)
{
return (Priority: null, Reason: "FATE mob");
}
Vector3 value = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
Vector3 value = gameObject2?.Position ?? Vector3.Zero;
bool flag = _currentFight.Data.SpawnType != EEnemySpawnType.FinishCombatIfAny && (_currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies || !(Vector3.Distance(value, battleNpc.Position) >= 50f)) && _currentFight.Data.SpawnType != EEnemySpawnType.FateEnemies;
if (complexCombatDatas.Count > 0)
{
@ -430,6 +432,7 @@ internal sealed class CombatController : IDisposable
private void SetTarget(IGameObject? target)
{
IGameObject gameObject = _objectTable[0];
if (target == null)
{
if (_targetManager.Target != null)
@ -438,9 +441,9 @@ internal sealed class CombatController : IDisposable
_targetManager.Target = null;
}
}
else if (Vector3.Distance(_clientState.LocalPlayer.Position, target.Position) > 55f)
else if (gameObject != null && Vector3.Distance(gameObject.Position, target.Position) > 55f)
{
_logger.LogInformation("Moving to target, distance: {Distance:N2}", Vector3.Distance(_clientState.LocalPlayer.Position, target.Position));
_logger.LogInformation("Moving to target, distance: {Distance:N2}", Vector3.Distance(gameObject.Position, target.Position));
MoveToTarget(target);
}
else
@ -468,39 +471,45 @@ internal sealed class CombatController : IDisposable
private void MoveToTarget(IGameObject gameObject)
{
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
IGameObject gameObject2 = _objectTable[0];
if (gameObject2 == null)
{
return;
}
float num = localPlayer.HitboxRadius + gameObject.HitboxRadius;
float num2 = Vector3.Distance(localPlayer.Position, gameObject.Position);
byte? b = localPlayer.ClassJob.ValueNullable?.Role;
bool flag;
if (b.HasValue)
float num = gameObject2.HitboxRadius + gameObject.HitboxRadius;
float num2 = Vector3.Distance(gameObject2.Position, gameObject.Position);
ICharacter character = gameObject2 as ICharacter;
bool flag = character != null;
bool flag2;
if (flag)
{
byte valueOrDefault = b.GetValueOrDefault();
if ((uint)(valueOrDefault - 3) <= 1u)
byte? b = character.ClassJob.ValueNullable?.Role;
if (b.HasValue)
{
flag = true;
goto IL_008e;
byte valueOrDefault = b.GetValueOrDefault();
if ((uint)(valueOrDefault - 3) <= 1u)
{
flag2 = true;
goto IL_00a3;
}
}
flag2 = false;
goto IL_00a3;
}
flag = false;
goto IL_008e;
IL_008e:
goto IL_00a7;
IL_00a7:
float num3 = (flag ? 20f : 2.9f);
bool flag2 = num2 - num >= num3;
bool flag3 = IsInLineOfSight(gameObject);
if (flag2 || !flag3)
bool flag3 = num2 - num >= num3;
bool flag4 = IsInLineOfSight(gameObject);
if (flag3 || !flag4)
{
bool flag4 = num2 - num > 5f;
if (!flag2 && !flag3)
bool flag5 = num2 - num > 5f;
if (!flag3 && !flag4)
{
num3 = Math.Min(num3, num2) / 2f;
flag4 = true;
flag5 = true;
}
if (!flag4)
if (!flag5)
{
_logger.LogInformation("Moving to {TargetName} ({BaseId}) to attack", gameObject.Name, gameObject.BaseId);
MovementController movementController = _movementController;
@ -518,11 +527,20 @@ internal sealed class CombatController : IDisposable
_movementController.NavigateTo(EMovementType.Combat, null, gameObject.Position, fly: false, sprint: false, num3 + num - 0.25f, float.MaxValue);
}
}
return;
IL_00a3:
flag = flag2;
goto IL_00a7;
}
internal unsafe bool IsInLineOfSight(IGameObject target)
{
Vector3 position = _clientState.LocalPlayer.Position;
IGameObject gameObject = _objectTable[0];
if (gameObject == null)
{
return false;
}
Vector3 position = gameObject.Position;
position.Y += 2f;
Vector3 position2 = target.Position;
position2.Y += 2f;

View file

@ -1,20 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Common;
using Questionable.Model.Questing;
using Questionable.Model.Questing.Converter;
using Questionable.Windows;
namespace Questionable.Controller;
@ -33,12 +21,8 @@ internal sealed class CommandHandler : IDisposable
private readonly MovementController _movementController;
private readonly QuestRegistry _questRegistry;
private readonly ConfigWindow _configWindow;
private readonly DebugOverlay _debugOverlay;
private readonly OneTimeSetupWindow _oneTimeSetupWindow;
private readonly QuestWindow _questWindow;
@ -53,33 +37,19 @@ internal sealed class CommandHandler : IDisposable
private readonly ITargetManager _targetManager;
private readonly QuestFunctions _questFunctions;
private readonly GameFunctions _gameFunctions;
private readonly AetheryteFunctions _aetheryteFunctions;
private readonly IDataManager _dataManager;
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly Configuration _configuration;
private readonly ChangelogWindow _changelogWindow;
private IReadOnlyList<uint> _previouslyUnlockedUnlockLinks = Array.Empty<uint>();
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, MovementController movementController, QuestRegistry questRegistry, ConfigWindow configWindow, DebugOverlay debugOverlay, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, QuestSelectionWindow questSelectionWindow, QuestSequenceWindow questSequenceWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ITargetManager targetManager, QuestFunctions questFunctions, GameFunctions gameFunctions, AetheryteFunctions aetheryteFunctions, IDataManager dataManager, IClientState clientState, IObjectTable objectTable, Configuration configuration, ChangelogWindow changelogWindow)
{
_commandManager = commandManager;
_chatGui = chatGui;
_questController = questController;
_movementController = movementController;
_questRegistry = questRegistry;
_configWindow = configWindow;
_debugOverlay = debugOverlay;
_oneTimeSetupWindow = oneTimeSetupWindow;
_questWindow = questWindow;
_questSelectionWindow = questSelectionWindow;
@ -87,12 +57,7 @@ internal sealed class CommandHandler : IDisposable
_journalProgressWindow = journalProgressWindow;
_priorityWindow = priorityWindow;
_targetManager = targetManager;
_questFunctions = questFunctions;
_gameFunctions = gameFunctions;
_aetheryteFunctions = aetheryteFunctions;
_dataManager = dataManager;
_clientState = clientState;
_objectTable = objectTable;
_configuration = configuration;
_changelogWindow = changelogWindow;
_clientState.Logout += OnLogout;
@ -185,465 +150,8 @@ internal sealed class CommandHandler : IDisposable
}
}
private unsafe void ProcessDebugCommand(string command, string arguments)
private void ProcessDebugCommand(string command, string arguments)
{
if (OpenSetupIfNeeded(arguments))
{
return;
}
string[] array = arguments.Split(' ');
string text = array[0];
if (text == null)
{
return;
}
switch (text.Length)
{
case 4:
switch (text[0])
{
case 'n':
if (text == "next")
{
SetNextQuest(array.Skip(1).ToArray());
}
break;
case 't':
{
if (!(text == "taxi"))
{
break;
}
List<string> list5 = new List<string>();
ExcelSheet<ChocoboTaxiStand> excelSheet = _dataManager.GetExcelSheet<ChocoboTaxiStand>();
UIState* ptr = UIState.Instance();
if (ptr == null)
{
_chatGui.PrintError("UIState is null", "Questionable", 576);
break;
}
for (int num10 = 0; num10 < 192; num10++)
{
uint num11 = (uint)(num10 + 1179648);
try
{
if (excelSheet.HasRow(num11) && ptr->IsChocoboTaxiStandUnlocked(num11))
{
string value14 = excelSheet.GetRow(num11).PlaceName.ToString();
if (string.IsNullOrEmpty(value14))
{
value14 = "Unknown";
}
list5.Add($"{value14} (ID: {num10}, Row: 0x{num11:X})");
}
}
catch
{
}
}
_chatGui.Print($"Unlocked taxi stands ({list5.Count}):", "Questionable", 576);
if (list5.Count == 0)
{
_chatGui.Print(" (No unlocked taxi stands found)", "Questionable", 576);
break;
}
{
foreach (string item5 in list5)
{
_chatGui.Print(" - " + item5, "Questionable", 576);
}
break;
}
}
}
break;
case 3:
switch (text[1])
{
default:
return;
case 'i':
if (text == "sim")
{
SetSimulatedQuest(array.Skip(1).ToArray());
}
return;
case 'e':
break;
}
if (!(text == "seq"))
{
break;
}
goto IL_0209;
case 12:
switch (text[0])
{
case 'a':
if (text == "abandon-duty")
{
_gameFunctions.AbandonDuty();
}
break;
case 'u':
{
if (!(text == "unlock-links"))
{
break;
}
IReadOnlyList<uint> unlockLinks = _gameFunctions.GetUnlockLinks();
if (unlockLinks.Count >= 0)
{
_chatGui.Print($"Saved {unlockLinks.Count} unlock links to log.", "Questionable", 576);
List<uint> list6 = unlockLinks.Except(_previouslyUnlockedUnlockLinks).ToList();
if (_previouslyUnlockedUnlockLinks.Count > 0 && list6.Count > 0)
{
_chatGui.Print("New unlock links: " + string.Join(", ", list6), "Questionable", 576);
}
}
else
{
_chatGui.PrintError("Could not query unlock links.", "Questionable", 576);
}
_previouslyUnlockedUnlockLinks = unlockLinks;
break;
}
}
break;
case 9:
switch (text[0])
{
default:
return;
case 's':
break;
case 'f':
{
if (!(text == "festivals"))
{
return;
}
List<string> list4 = new List<string>();
for (byte b8 = 0; b8 < 4; b8++)
{
GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[b8];
if (festival.Id == 0)
{
list4.Add($"Slot {b8}: None");
}
else
{
list4.Add($"Slot {b8}: {festival.Id}({festival.Phase})");
}
}
_chatGui.Print("Festival slots:", "Questionable", 576);
{
foreach (string item6 in list4)
{
_chatGui.Print(" " + item6, "Questionable", 576);
}
return;
}
}
case 'a':
{
if (!(text == "aethernet"))
{
return;
}
ushort territoryType = _clientState.TerritoryType;
Dictionary<EAetheryteLocation, string> values = AethernetShardConverter.Values;
AetheryteData aetheryteData = new AetheryteData(_dataManager);
HashSet<string> hashSet2 = new HashSet<string>();
Dictionary<string, List<(EAetheryteLocation, string, bool)>> dictionary = new Dictionary<string, List<(EAetheryteLocation, string, bool)>>();
EAetheryteLocation key;
string value10;
foreach (KeyValuePair<EAetheryteLocation, string> item7 in values)
{
item7.Deconstruct(out key, out value10);
EAetheryteLocation key2 = key;
string text2 = value10;
if (aetheryteData.TerritoryIds.TryGetValue(key2, out var value11) && value11 == territoryType)
{
int num8 = text2.IndexOf(']', StringComparison.Ordinal);
if (num8 > 0)
{
string item3 = text2.Substring(1, num8 - 1);
hashSet2.Add(item3);
}
}
}
if (hashSet2.Count == 0)
{
_chatGui.Print("No aethernet shards found in current zone.", "Questionable", 576);
return;
}
foreach (KeyValuePair<EAetheryteLocation, string> item8 in values)
{
item8.Deconstruct(out key, out value10);
EAetheryteLocation eAetheryteLocation = key;
string text3 = value10;
int num9 = text3.IndexOf(']', StringComparison.Ordinal);
if (num9 <= 0)
{
continue;
}
string text4 = text3.Substring(1, num9 - 1);
if (hashSet2.Contains(text4))
{
if (!dictionary.ContainsKey(text4))
{
dictionary[text4] = new List<(EAetheryteLocation, string, bool)>();
}
bool item4 = _aetheryteFunctions.IsAetheryteUnlocked(eAetheryteLocation);
dictionary[text4].Add((eAetheryteLocation, text3, item4));
}
}
{
foreach (KeyValuePair<string, List<(EAetheryteLocation, string, bool)>> item9 in dictionary.OrderBy<KeyValuePair<string, List<(EAetheryteLocation, string, bool)>>, string>((KeyValuePair<string, List<(EAetheryteLocation Location, string Name, bool Unlocked)>> x) => x.Key))
{
item9.Deconstruct(out value10, out var value12);
string value13 = value10;
List<(EAetheryteLocation, string, bool)> list = value12;
List<(EAetheryteLocation, string, bool)> list2 = list.Where<(EAetheryteLocation, string, bool)>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Unlocked).ToList();
List<(EAetheryteLocation, string, bool)> list3 = list.Where<(EAetheryteLocation, string, bool)>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => !x.Unlocked).ToList();
_chatGui.Print($"Aethernet Shards in {value13} ({list.Count} total):", "Questionable", 576);
_chatGui.Print($" Unlocked: {list2.Count}", "Questionable", 576);
_chatGui.Print($" Missing: {list3.Count}", "Questionable", 576);
_chatGui.Print("", "Questionable", 576);
if (list3.Count > 0)
{
_chatGui.Print("Missing/Unattuned Aethernet Shards:", "Questionable", 576);
foreach (var item10 in list3.OrderBy<(EAetheryteLocation, string, bool), string>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Name))
{
_chatGui.Print(" " + item10.Item2, "Questionable", 576);
}
_chatGui.Print("", "Questionable", 576);
}
if (list2.Count > 0)
{
_chatGui.Print("Unlocked Aethernet Shards:", "Questionable", 576);
foreach (var item11 in list2.OrderBy<(EAetheryteLocation, string, bool), string>(((EAetheryteLocation Location, string Name, bool Unlocked) x) => x.Name))
{
_chatGui.Print(" " + item11.Item2, "Questionable", 576);
}
}
if (dictionary.Count > 1)
{
_chatGui.Print("", "Questionable", 576);
}
}
return;
}
}
}
if (!(text == "sequences"))
{
break;
}
goto IL_0209;
case 5:
if (text == "setup")
{
_oneTimeSetupWindow.IsOpenAndUncollapsed = true;
}
break;
case 2:
if (text == "do")
{
ConfigureDebugOverlay(array.Skip(1).ToArray());
}
break;
case 7:
if (text == "mountid")
{
PrintMountId();
}
break;
case 11:
{
if (!(text == "quest-kills"))
{
break;
}
(QuestController.QuestProgress, QuestController.ECurrentQuestType)? currentQuestDetails = _questController.CurrentQuestDetails;
if (!currentQuestDetails.HasValue)
{
_chatGui.PrintError("No active quest.", "Questionable", 576);
break;
}
QuestController.QuestProgress item = currentQuestDetails.Value.Item1;
Questionable.Model.Quest quest = item.Quest;
QuestProgressInfo questProgressInfo = null;
if (quest.Id is QuestId elementId)
{
questProgressInfo = _questFunctions.GetQuestProgressInfo(elementId);
}
if (questProgressInfo == null)
{
_chatGui.PrintError("Unable to retrieve quest progress information.", "Questionable", 576);
break;
}
QuestSequence questSequence = quest.FindSequence(item.Sequence);
if (questSequence == null)
{
_chatGui.PrintError($"Sequence {item.Sequence} not found for quest {quest.Id}.", "Questionable", 576);
break;
}
QuestStep questStep = ((item.Step < questSequence.Steps.Count) ? questSequence.Steps[item.Step] : null);
if (questStep == null)
{
_chatGui.PrintError($"Step {item.Step} not found in sequence {item.Sequence}.", "Questionable", 576);
break;
}
_chatGui.Print($"Quest: {quest.Info.Name} ({quest.Id})", "Questionable", 576);
_chatGui.Print($"Sequence: {item.Sequence}, Step: {item.Step}", "Questionable", 576);
_chatGui.Print("", "Questionable", 576);
_chatGui.Print("Quest Variables: " + string.Join(", ", questProgressInfo.Variables.Select((byte v, int i) => $"[{i}]={v}")), "Questionable", 576);
_chatGui.Print("", "Questionable", 576);
ExcelSheet<BNpcName> bnpcNameSheet = _dataManager.GetExcelSheet<BNpcName>();
HashSet<uint> hashSet = new HashSet<uint>(questStep.KillEnemyDataIds);
foreach (ComplexCombatData complexCombatDatum in questStep.ComplexCombatData)
{
hashSet.Add(complexCombatDatum.DataId);
}
if (hashSet.Count > 0)
{
_chatGui.Print($"All Enemy DataIds Found: {hashSet.Count}", "Questionable", 576);
foreach (uint item12 in hashSet.OrderBy((uint x) => x))
{
(string Name, bool Found) tuple = GetEnemyName(item12);
var (value, _) = tuple;
if (tuple.Found)
{
_chatGui.Print($" - {value} (DataId: {item12})", "Questionable", 576);
}
else
{
_chatGui.Print($" - DataId: {item12}", "Questionable", 576);
}
}
_chatGui.Print("", "Questionable", 576);
}
if (questStep.ComplexCombatData.Count > 0)
{
_chatGui.Print($"Complex Combat Data Entries: {questStep.ComplexCombatData.Count}", "Questionable", 576);
_chatGui.Print("Kill Progress:", "Questionable", 576);
if (questStep.ComplexCombatData.Count == 1 && hashSet.Count > 1)
{
ComplexCombatData complexCombatData = questStep.ComplexCombatData[0];
int num = -1;
byte? b = null;
for (int num2 = 0; num2 < complexCombatData.CompletionQuestVariablesFlags.Count; num2++)
{
QuestWorkValue questWorkValue = complexCombatData.CompletionQuestVariablesFlags[num2];
if (questWorkValue != null && questWorkValue.Low.HasValue)
{
num = num2;
b = questWorkValue.Low;
break;
}
}
byte b2 = (byte)(((num >= 0 && num < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num] : 0) & 0xF);
string value2 = (b.HasValue ? $" {b2}/{b}" : "");
string value3 = ((b.HasValue && b2 >= b) ? "✓" : "○");
foreach (uint item13 in hashSet.OrderBy((uint x) => x))
{
(string Name, bool Found) tuple3 = GetEnemyName(item13);
var (value4, _) = tuple3;
if (tuple3.Found)
{
_chatGui.Print($" {value3} Slay {value4}.{value2} (DataId: {item13})", "Questionable", 576);
}
else
{
_chatGui.Print($" {value3} Slay enemy.{value2} (DataId: {item13})", "Questionable", 576);
}
}
}
else
{
for (int num3 = 0; num3 < questStep.ComplexCombatData.Count; num3++)
{
ComplexCombatData complexCombatData2 = questStep.ComplexCombatData[num3];
int num4 = -1;
byte? b3 = null;
bool flag = false;
for (int num5 = 0; num5 < complexCombatData2.CompletionQuestVariablesFlags.Count; num5++)
{
QuestWorkValue questWorkValue2 = complexCombatData2.CompletionQuestVariablesFlags[num5];
if (questWorkValue2 != null)
{
if (questWorkValue2.Low.HasValue)
{
num4 = num5;
b3 = questWorkValue2.Low;
flag = false;
break;
}
if (questWorkValue2.High.HasValue)
{
num4 = num5;
b3 = questWorkValue2.High;
flag = true;
break;
}
}
}
byte b4 = (byte)((num4 >= 0 && num4 < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num4] : 0);
byte b5 = (flag ? ((byte)(b4 >> 4)) : ((byte)(b4 & 0xF)));
string value5;
if (complexCombatData2.NameId.HasValue)
{
BNpcName? bNpcName = bnpcNameSheet?.GetRowOrDefault(complexCombatData2.NameId.Value);
value5 = ((!bNpcName.HasValue || string.IsNullOrEmpty(bNpcName.Value.Singular.ToString())) ? "enemy" : bNpcName.Value.Singular.ToString());
}
else
{
(string Name, bool Found) tuple5 = GetEnemyName(complexCombatData2.DataId);
string item2 = tuple5.Name;
value5 = (tuple5.Found ? item2 : "enemy");
}
string value6 = (b3.HasValue ? $" {b5}/{b3}" : "");
string value7 = ((b3.HasValue && b5 >= b3) ? "✓" : "○");
string value8 = (complexCombatData2.NameId.HasValue ? $" (DataId: {complexCombatData2.DataId}, NameId: {complexCombatData2.NameId})" : $" (DataId: {complexCombatData2.DataId})");
_chatGui.Print($" {value7} Slay {value5}.{value6}{value8}", "Questionable", 576);
}
}
_chatGui.Print("", "Questionable", 576);
}
else if (questStep.KillEnemyDataIds.Count == 0)
{
_chatGui.Print("No kill enemy data for this step.", "Questionable", 576);
_chatGui.Print("", "Questionable", 576);
}
if (questStep.CompletionQuestVariablesFlags.Count <= 0 || !questStep.CompletionQuestVariablesFlags.Any((QuestWorkValue x) => x != null))
{
break;
}
_chatGui.Print("Completion Flags (Debug):", "Questionable", 576);
for (int num6 = 0; num6 < questStep.CompletionQuestVariablesFlags.Count; num6++)
{
QuestWorkValue questWorkValue3 = questStep.CompletionQuestVariablesFlags[num6];
if (questWorkValue3 != null)
{
int num7 = ((num6 < questProgressInfo.Variables.Count) ? questProgressInfo.Variables[num6] : 0);
byte b6 = (byte)(num7 >> 4);
byte b7 = (byte)(num7 & 0xF);
string value9 = (((!questWorkValue3.High.HasValue || questWorkValue3.High == b6) && (!questWorkValue3.Low.HasValue || questWorkValue3.Low == b7)) ? " ✓" : " ✗");
_chatGui.Print($" [{num6}] Expected: H={questWorkValue3.High?.ToString(CultureInfo.InvariantCulture) ?? "any"} L={questWorkValue3.Low?.ToString(CultureInfo.InvariantCulture) ?? "any"} | Actual: H={b6.ToString(CultureInfo.InvariantCulture)} L={b7.ToString(CultureInfo.InvariantCulture)}{value9}", "Questionable", 576);
}
}
break;
}
case 6:
case 8:
case 10:
break;
IL_0209:
_questSequenceWindow.ToggleOrUncollapse();
break;
}
}
private bool OpenSetupIfNeeded(string arguments)
@ -663,110 +171,8 @@ internal sealed class CommandHandler : IDisposable
return false;
}
private void ConfigureDebugOverlay(string[] arguments)
{
ElementId elementId;
if (!_debugOverlay.DrawConditions())
{
_chatGui.PrintError("You don't have the debug overlay enabled.", "Questionable", 576);
}
else if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out elementId) && elementId != null)
{
if (_questRegistry.TryGetQuest(elementId, out Questionable.Model.Quest quest))
{
_debugOverlay.HighlightedQuest = quest.Id;
_chatGui.Print($"Set highlighted quest to {elementId} ({quest.Info.Name}).", "Questionable", 576);
}
else
{
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
}
}
else
{
_debugOverlay.HighlightedQuest = null;
_chatGui.Print("Cleared highlighted quest.", "Questionable", 576);
}
}
private void SetNextQuest(string[] arguments)
{
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
{
Questionable.Model.Quest quest;
if (_questFunctions.IsQuestLocked(elementId))
{
_chatGui.PrintError($"Quest {elementId} is locked.", "Questionable", 576);
}
else if (_questRegistry.TryGetQuest(elementId, out quest))
{
_questController.SetNextQuest(quest);
_chatGui.Print($"Set next quest to {elementId} ({quest.Info.Name}).", "Questionable", 576);
}
else
{
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
}
}
else
{
_questController.SetNextQuest(null);
_chatGui.Print("Cleared next quest.", "Questionable", 576);
}
}
private void SetSimulatedQuest(string[] arguments)
{
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId elementId) && elementId != null)
{
if (_questRegistry.TryGetQuest(elementId, out Questionable.Model.Quest quest))
{
byte sequence = 0;
int step = 0;
if (arguments.Length >= 2 && byte.TryParse(arguments[1], out var result))
{
QuestSequence questSequence = quest.FindSequence(result);
if (questSequence != null)
{
sequence = questSequence.Sequence;
if (arguments.Length >= 3 && int.TryParse(arguments[2], out var result2) && questSequence.FindStep(result2) != null)
{
step = result2;
}
}
}
_questController.SimulateQuest(quest, sequence, step);
_chatGui.Print($"Simulating quest {elementId} ({quest.Info.Name}).", "Questionable", 576);
}
else
{
_chatGui.PrintError($"Unknown quest {elementId}.", "Questionable", 576);
}
}
else
{
_questController.SimulateQuest(null, 0, 0);
_chatGui.Print("Cleared simulated quest.", "Questionable", 576);
}
}
private void PrintMountId()
{
ushort? mountId = _gameFunctions.GetMountId();
if (mountId.HasValue)
{
Mount? rowOrDefault = _dataManager.GetExcelSheet<Mount>().GetRowOrDefault(mountId.Value);
_chatGui.Print($"Mount ID: {mountId}, Name: {rowOrDefault?.Singular}, Obtainable: {((rowOrDefault?.Order == -1) ? "No" : "Yes")}", "Questionable", 576);
}
else
{
_chatGui.Print("You are not mounted.", "Questionable", 576);
}
}
private void OnLogout(int type, int code)
{
_previouslyUnlockedUnlockLinks = Array.Empty<uint>();
}
public void Dispose()

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
@ -43,9 +44,11 @@ internal sealed class ContextMenuController : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly ILogger<ContextMenuController> _logger;
public ContextMenuController(IContextMenu contextMenu, QuestController questController, GatheringPointRegistry gatheringPointRegistry, GatheringData gatheringData, QuestRegistry questRegistry, QuestData questData, GameFunctions gameFunctions, QuestFunctions questFunctions, IGameGui gameGui, IChatGui chatGui, IClientState clientState, ILogger<ContextMenuController> logger)
public ContextMenuController(IContextMenu contextMenu, QuestController questController, GatheringPointRegistry gatheringPointRegistry, GatheringData gatheringData, QuestRegistry questRegistry, QuestData questData, GameFunctions gameFunctions, QuestFunctions questFunctions, IGameGui gameGui, IChatGui chatGui, IClientState clientState, IObjectTable objectTable, ILogger<ContextMenuController> logger)
{
_contextMenu = contextMenu;
_questController = questController;
@ -58,6 +61,7 @@ internal sealed class ContextMenuController : IDisposable
_gameGui = gameGui;
_chatGui = chatGui;
_clientState = clientState;
_objectTable = objectTable;
_logger = logger;
_contextMenu.OnMenuOpened += MenuOpened;
}
@ -113,7 +117,11 @@ internal sealed class ContextMenuController : IDisposable
private unsafe void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EClassJob classJob, string verb)
{
EClassJob rowId = (EClassJob)_clientState.LocalPlayer.ClassJob.RowId;
if (!(_objectTable[0] is ICharacter { ClassJob: var classJob2 }))
{
return;
}
EClassJob rowId = (EClassJob)classJob2.RowId;
bool flag = classJob != rowId;
if (flag)
{

View file

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -135,15 +136,18 @@ internal sealed class InterruptHandler : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly TerritoryData _territoryData;
private readonly ILogger<InterruptHandler> _logger;
public event EventHandler? Interrupted;
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, TerritoryData territoryData, ILogger<InterruptHandler> logger)
public unsafe InterruptHandler(IGameInteropProvider gameInteropProvider, IClientState clientState, IObjectTable objectTable, TerritoryData territoryData, ILogger<InterruptHandler> logger)
{
_clientState = clientState;
_objectTable = objectTable;
_territoryData = territoryData;
_logger = logger;
_processActionEffectHook = gameInteropProvider.HookFromSignature<ProcessActionEffect>("40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48", HandleProcessActionEffect);
@ -158,11 +162,12 @@ internal sealed class InterruptHandler : IDisposable
{
return;
}
IGameObject gameObject = _objectTable[0];
for (int i = 0; i < effectHeader->TargetCount; i++)
{
int num = (int)(effectTail[i] & 0xFFFFFFFFu);
EffectEntry* ptr = effectArray + 8 * i;
bool flag = (uint)num == _clientState.LocalPlayer?.GameObjectId;
bool flag = (uint)num == gameObject?.GameObjectId;
if (flag)
{
EActionEffectType type = ptr->Type;

View file

@ -73,6 +73,8 @@ internal sealed class MovementController : IDisposable
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly GameFunctions _gameFunctions;
private readonly ChatFunctions _chatFunctions;
@ -89,6 +91,8 @@ internal sealed class MovementController : IDisposable
private Task<List<Vector3>>? _pathfindTask;
private long _pathfindStartTime;
public bool IsNavmeshReady
{
get
@ -138,10 +142,15 @@ internal sealed class MovementController : IDisposable
public int BuiltNavmeshPercent => _navmeshIpc.GetBuildProgress();
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
public bool IsNavmeshPathfindInProgress => _navmeshIpc.IsPathfindInProgress;
public int NumQueuedPathfindRequests => _navmeshIpc.NumQueuedPathfindRequests;
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
{
_navmeshIpc = navmeshIpc;
_clientState = clientState;
_objectTable = objectTable;
_gameFunctions = gameFunctions;
_chatFunctions = chatFunctions;
_condition = condition;
@ -154,16 +163,51 @@ internal sealed class MovementController : IDisposable
{
if (_pathfindTask != null && Destination != null)
{
if (!_pathfindTask.IsCompleted && Environment.TickCount64 - _pathfindStartTime > 30000 && _navmeshIpc.NumQueuedPathfindRequests > 5)
{
_logger.LogWarning("Pathfinding appears stuck: {QueuedRequests} queued requests, task running for {Duration}ms", _navmeshIpc.NumQueuedPathfindRequests, Environment.TickCount64 - _pathfindStartTime);
}
if (_pathfindTask.IsCompletedSuccessfully)
{
_logger.LogInformation("Pathfinding complete, got {Count} points", _pathfindTask.Result.Count);
if (_pathfindTask.Result.Count == 0)
{
if (Destination.NavmeshCalculations == 1)
{
_logger.LogWarning("Initial pathfinding returned 0 points, attempting to find accessible destination");
Vector3? vector = TryFindAccessibleDestination(Destination.Position, Destination.IsFlying, Destination.Land);
if (vector.HasValue && Vector3.Distance(Destination.Position, vector.Value) < 30f)
{
_logger.LogInformation("Retrying pathfinding with adjusted destination: {AdjustedDestination}", vector.Value.ToString("G", CultureInfo.InvariantCulture));
Restart(Destination with
{
Position = vector.Value
});
return;
}
if (Destination.IsFlying && Destination.Land)
{
_logger.LogWarning("Adjusted destination failed, trying tolerance-based pathfinding");
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
if (Destination.IsFlying)
{
Vector3 vector3 = vector2;
vector3.Y = vector2.Y + 0.2f;
vector2 = vector3;
}
_pathfindStartTime = Environment.TickCount64;
_pathfindTask = _navmeshIpc.PathfindWithTolerance(vector2, Destination.Position, Destination.IsFlying, 10f, _cancellationTokenSource.Token);
Destination.NavmeshCalculations++;
return;
}
}
ResetPathfinding();
throw new PathfindingFailedException();
}
List<Vector3> list = _pathfindTask.Result.Skip(1).ToList();
Vector3 p = _clientState.LocalPlayer?.Position ?? list[0];
Vector3 p = _objectTable[0]?.Position ?? list[0];
if (Destination.IsFlying && !_condition[ConditionFlag.InFlight] && _condition[ConditionFlag.Mounted] && (IsOnFlightPath(p) || list.Any(IsOnFlightPath)))
{
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
@ -179,6 +223,7 @@ internal sealed class MovementController : IDisposable
_logger.LogInformation("Running navmesh recalculation with fudged point ({From} to {To})", list.Last(), Destination.Position);
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
_pathfindStartTime = Environment.TickCount64;
_pathfindTask = _navmeshIpc.Pathfind(list.Last(), Destination.Position, Destination.IsFlying, _cancellationTokenSource.Token);
return;
}
@ -220,7 +265,7 @@ internal sealed class MovementController : IDisposable
Restart(Destination);
return;
}
Vector3 vector = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
Vector3 vector4 = _objectTable[0]?.Position ?? Vector3.Zero;
if (Destination.MovementType == EMovementType.Landing)
{
if (!_condition[ConditionFlag.InFlight])
@ -228,9 +273,9 @@ internal sealed class MovementController : IDisposable
Stop();
}
}
else if ((vector - Destination.Position).Length() < Destination.StopDistance)
else if ((vector4 - Destination.Position).Length() < Destination.StopDistance)
{
if (vector.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
if (vector4.Y - Destination.Position.Y <= Destination.VerticalStopDistance)
{
Stop();
}
@ -239,7 +284,7 @@ internal sealed class MovementController : IDisposable
IGameObject gameObject = _gameFunctions.FindObjectByDataId(Destination.DataId.Value);
if ((gameObject is ICharacter || gameObject is IEventObj) ? true : false)
{
if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
if (Math.Abs(vector4.Y - gameObject.Position.Y) < 1.95f)
{
Stop();
}
@ -250,7 +295,7 @@ internal sealed class MovementController : IDisposable
{
Stop();
}
else if (Math.Abs(vector.Y - gameObject.Position.Y) < 1.95f)
else if (Math.Abs(vector4.Y - gameObject.Position.Y) < 1.95f)
{
Stop();
}
@ -268,10 +313,10 @@ internal sealed class MovementController : IDisposable
else
{
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
Vector3? vector2 = _clientState.LocalPlayer?.Position;
if (vector2.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector2.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
Vector3? vector5 = _objectTable[0]?.Position;
if (vector5.HasValue && (!Destination.ShouldRecalculateNavmesh() || !RecalculateNavmesh(waypoints, vector5.Value)) && !Destination.IsFlying && !_condition[ConditionFlag.Mounted] && !_gameFunctions.HasStatusPreventingSprint() && Destination.CanSprint)
{
TriggerSprintIfNeeded(waypoints, vector2.Value);
TriggerSprintIfNeeded(waypoints, vector5.Value);
}
}
}
@ -331,7 +376,7 @@ internal sealed class MovementController : IDisposable
Destination.NavmeshCalculations++;
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
Vector3 vector2 = _clientState.LocalPlayer.Position;
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
if (fly && _aetheryteData.CalculateDistance(vector2, _clientState.TerritoryType, EAetheryteLocation.CoerthasCentralHighlandsCampDragonhead) < 11f)
{
Vector3 vector = vector2;
@ -345,7 +390,16 @@ internal sealed class MovementController : IDisposable
vector.Y = vector2.Y + 0.2f;
vector2 = vector;
}
_pathfindTask = _navmeshIpc.Pathfind(vector2, to, fly, _cancellationTokenSource.Token);
_pathfindStartTime = Environment.TickCount64;
if (fly && land)
{
_logger.LogInformation("Using tolerance-based pathfinding for landing (tolerance: 5.0)");
_pathfindTask = _navmeshIpc.PathfindWithTolerance(vector2, to, fly, 5f, _cancellationTokenSource.Token);
}
else
{
_pathfindTask = _navmeshIpc.Pathfind(vector2, to, fly, _cancellationTokenSource.Token);
}
}
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, bool sprint, float? stopDistance, float? verticalStopDistance = null, bool land = false)
@ -380,6 +434,46 @@ internal sealed class MovementController : IDisposable
_pathfindTask = null;
}
private Vector3? TryFindAccessibleDestination(Vector3 target, bool flying, bool landing)
{
float[] array = ((!(flying && landing)) ? ((!flying) ? new float[3] { 1f, 3f, 5f } : new float[3] { 2f, 5f, 10f }) : new float[3] { 5f, 10f, 15f });
float[] array2 = ((!flying) ? new float[3] { 1f, 2f, 3f } : new float[3] { 3f, 5f, 10f });
for (int i = 0; i < array.Length; i++)
{
float num = array[i];
float num2 = array2[Math.Min(i, array2.Length - 1)];
Vector3? vector = _navmeshIpc.FindNearestMeshPoint(target, num, num2);
if (vector.HasValue)
{
float num3 = Vector3.Distance(target, vector.Value);
if (num3 <= num * 1.5f)
{
if (i > 0)
{
_logger.LogInformation("Adjusted destination from {Original} to {Adjusted} (distance: {Distance:F2}, extent: {ExtentXZ}/{ExtentY})", target.ToString("G", CultureInfo.InvariantCulture), vector.Value.ToString("G", CultureInfo.InvariantCulture), num3, num, num2);
}
return vector.Value;
}
}
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(target, flying, num);
if (!pointOnFloor.HasValue)
{
continue;
}
float num4 = Vector3.Distance(target, pointOnFloor.Value);
if (num4 <= num * 1.5f)
{
if (i > 0)
{
_logger.LogInformation("Adjusted destination via floor point from {Original} to {Adjusted} (distance: {Distance:F2}, extent: {ExtentXZ})", target.ToString("G", CultureInfo.InvariantCulture), pointOnFloor.Value.ToString("G", CultureInfo.InvariantCulture), num4, num);
}
return pointOnFloor.Value;
}
}
_logger.LogWarning("Could not find accessible mesh point near {Target} within max extent {MaxExtent}", target.ToString("G", CultureInfo.InvariantCulture), array[^1]);
return null;
}
private unsafe bool RecalculateNavmesh(List<Vector3> navPoints, Vector3 start)
{
if (Destination == null)

View file

@ -6,6 +6,7 @@ using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
@ -84,6 +85,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly GameFunctions _gameFunctions;
private readonly QuestFunctions _questFunctions;
@ -231,10 +234,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
public QuestController(IClientState clientState, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent)
public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{
_clientState = clientState;
_objectTable = objectTable;
_gameFunctions = gameFunctions;
_questFunctions = questFunctions;
_movementController = movementController;
@ -425,12 +429,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_lastProgressUpdate = DateTime.Now;
return;
}
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
IGameObject gameObject = _objectTable[0];
if (gameObject == null)
{
return;
}
Vector3 position = localPlayer.Position;
Vector3 position = gameObject.Position;
if (CurrentQuest == null)
{
return;
@ -463,7 +467,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
if (_configuration.General.AutoStepRefreshEnabled && AutomationType == EAutomationType.Automatic && IsRunning && CurrentQuest != null && _clientState.IsLoggedIn)
{
return _clientState.LocalPlayer != null;
return _objectTable[0] != null;
}
return false;
}
@ -651,9 +655,9 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
_logger.LogInformation("New quest: {QuestName}", quest.Info.Name);
_startedQuest = new QuestProgress(quest, b);
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.Level < quest.Info.Level)
if (_objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level < quest.Info.Level)
{
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel}", _clientState.LocalPlayer.Level, quest.Info.Level);
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel}", playerCharacter.Level, quest.Info.Level);
Stop("Quest level too high");
return;
}