forked from aly/qstbak
283 lines
7.4 KiB
C#
283 lines
7.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using LLib.GameUI;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller.GameUi;
|
|
using SmartNav.Model;
|
|
|
|
namespace Questionable.Functions;
|
|
|
|
internal sealed class AethernetTeleportService : IDisposable
|
|
{
|
|
public enum ETeleportState
|
|
{
|
|
Idle,
|
|
WaitingForAddon,
|
|
Complete,
|
|
Failed
|
|
}
|
|
|
|
private static readonly TimeSpan AddonTimeout = TimeSpan.FromSeconds(15L);
|
|
|
|
private const long FireRetryIntervalMs = 100L;
|
|
|
|
private static readonly Dictionary<EAetheryteLocation, uint> FirmamentPlaceNameIds = new Dictionary<EAetheryteLocation, uint>
|
|
{
|
|
{
|
|
EAetheryteLocation.FirmamentMendicantsCourt,
|
|
3436u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmamentMattock,
|
|
3473u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmamentNewNest,
|
|
3475u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmanentSaintRoellesDais,
|
|
3474u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmamentFeatherfall,
|
|
3525u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmamentHoarfrostHall,
|
|
3528u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmamentWesternRisensongQuarter,
|
|
3646u
|
|
},
|
|
{
|
|
EAetheryteLocation.FirmamentEasternRisensongQuarter,
|
|
3645u
|
|
}
|
|
};
|
|
|
|
private readonly IFramework _framework;
|
|
|
|
private readonly IGameGui _gameGui;
|
|
|
|
private readonly IDataManager _dataManager;
|
|
|
|
private readonly ICondition _condition;
|
|
|
|
private readonly InteractionUiController _interactionUiController;
|
|
|
|
private readonly ILogger<AethernetTeleportService> _logger;
|
|
|
|
private long _requestedAtMs;
|
|
|
|
private bool _selectStringDismissed;
|
|
|
|
private bool _fired;
|
|
|
|
private bool _entriesLogged;
|
|
|
|
private long _nextFireAtMs;
|
|
|
|
public ETeleportState State { get; private set; }
|
|
|
|
public EAetheryteLocation? PendingDestination { get; private set; }
|
|
|
|
public AethernetTeleportService(IFramework framework, IGameGui gameGui, IDataManager dataManager, ICondition condition, InteractionUiController interactionUiController, ILogger<AethernetTeleportService> logger)
|
|
{
|
|
_framework = framework;
|
|
_gameGui = gameGui;
|
|
_dataManager = dataManager;
|
|
_condition = condition;
|
|
_interactionUiController = interactionUiController;
|
|
_logger = logger;
|
|
_framework.Update += OnFrameworkUpdate;
|
|
}
|
|
|
|
public void RequestTeleport(EAetheryteLocation destination)
|
|
{
|
|
if (State != ETeleportState.Idle)
|
|
{
|
|
Reset();
|
|
}
|
|
if (FirmamentPlaceNameIds.TryGetValue(destination, out var value))
|
|
{
|
|
string text = ResolvePlaceName(value);
|
|
if (text != null)
|
|
{
|
|
RequestCustomZoneTeleport(destination, text);
|
|
return;
|
|
}
|
|
_logger.LogWarning("Failed to resolve PlaceName {Id} for Firmament destination {Dest}", value, destination);
|
|
}
|
|
_logger.LogInformation("Requesting aethernet teleport to {Destination}", destination);
|
|
PendingDestination = destination;
|
|
_requestedAtMs = Environment.TickCount64;
|
|
State = ETeleportState.WaitingForAddon;
|
|
}
|
|
|
|
public void RequestCustomZoneTeleport(EAetheryteLocation destination, string destinationName)
|
|
{
|
|
if (State != ETeleportState.Idle)
|
|
{
|
|
_logger.LogWarning("RequestCustomZoneTeleport called while in state {State}, resetting first", State);
|
|
Reset();
|
|
}
|
|
_logger.LogInformation("Requesting custom zone aethernet teleport to {Destination} ({Name})", destination, destinationName);
|
|
PendingDestination = destination;
|
|
_interactionUiController.AethernetDestinationName = destinationName;
|
|
State = ETeleportState.Complete;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
if (State != ETeleportState.Idle)
|
|
{
|
|
_logger.LogDebug("Resetting AethernetTeleportService from state {State}", State);
|
|
}
|
|
State = ETeleportState.Idle;
|
|
PendingDestination = null;
|
|
_requestedAtMs = 0L;
|
|
_selectStringDismissed = false;
|
|
_fired = false;
|
|
_entriesLogged = false;
|
|
_nextFireAtMs = 0L;
|
|
ClearCustomZoneState();
|
|
}
|
|
|
|
private void ClearCustomZoneState()
|
|
{
|
|
_interactionUiController.AethernetDestinationName = null;
|
|
}
|
|
|
|
private string? ResolvePlaceName(uint placeNameId)
|
|
{
|
|
return _dataManager.GetExcelSheet<PlaceName>().GetRowOrDefault(placeNameId)?.Name.ToString();
|
|
}
|
|
|
|
private string? ResolveAethernetDisplayName(EAetheryteLocation aetheryte)
|
|
{
|
|
Aetheryte? rowOrDefault = _dataManager.GetExcelSheet<Aetheryte>().GetRowOrDefault((uint)aetheryte);
|
|
if (!rowOrDefault.HasValue)
|
|
{
|
|
return null;
|
|
}
|
|
return rowOrDefault.Value.AethernetName.ValueNullable?.Name.ToString();
|
|
}
|
|
|
|
private unsafe void OnFrameworkUpdate(IFramework _)
|
|
{
|
|
if (State != ETeleportState.WaitingForAddon)
|
|
{
|
|
return;
|
|
}
|
|
AddonSelectString* addonPtr;
|
|
AtkUnitBase* addonPtr2;
|
|
if (_fired && (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]))
|
|
{
|
|
_logger.LogDebug("Aethernet teleport to {Destination} accepted (between areas)", PendingDestination);
|
|
State = ETeleportState.Complete;
|
|
}
|
|
else if (Environment.TickCount64 - _requestedAtMs > (long)AddonTimeout.TotalMilliseconds)
|
|
{
|
|
_logger.LogWarning("Aethernet teleport request to {Destination} timed out after {Timeout}s", PendingDestination, AddonTimeout.TotalSeconds);
|
|
State = ETeleportState.Failed;
|
|
}
|
|
else if (_gameGui.TryGetAddonByName<AddonSelectString>("SelectString", out addonPtr) && LAddon.IsAddonReady(&addonPtr->AtkUnitBase))
|
|
{
|
|
_logger.LogDebug("Dismissing aetheryte SelectString (selecting Aethernet)");
|
|
addonPtr->AtkUnitBase.FireCallbackInt(0);
|
|
_selectStringDismissed = true;
|
|
}
|
|
else if (_selectStringDismissed)
|
|
{
|
|
_selectStringDismissed = false;
|
|
}
|
|
else if (!_gameGui.TryGetAddonByName<AtkUnitBase>("TelepotTown", out addonPtr2))
|
|
{
|
|
if (_fired)
|
|
{
|
|
_logger.LogDebug("Aethernet teleport to {Destination} accepted (menu closed)", PendingDestination);
|
|
State = ETeleportState.Complete;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!LAddon.IsAddonReady(addonPtr2))
|
|
{
|
|
return;
|
|
}
|
|
string text = ResolveAethernetDisplayName(PendingDestination.Value);
|
|
if (text == null)
|
|
{
|
|
_logger.LogWarning("Failed to resolve display name for {Destination}", PendingDestination);
|
|
State = ETeleportState.Failed;
|
|
return;
|
|
}
|
|
int num = -1;
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
int num2 = 262 + i;
|
|
if (num2 >= addonPtr2->AtkValuesCount)
|
|
{
|
|
break;
|
|
}
|
|
AtkValue atkValue = addonPtr2->AtkValues[num2];
|
|
if (atkValue.Type == AtkValueType.Undefined)
|
|
{
|
|
break;
|
|
}
|
|
string text2 = atkValue.ReadAtkString();
|
|
int num3 = 6 + i * 4 + 3;
|
|
int num4 = ((num3 < addonPtr2->AtkValuesCount) ? addonPtr2->AtkValues[num3].Int : (-1));
|
|
if (!_entriesLogged)
|
|
{
|
|
_logger.LogDebug("TelepotTown entry {Index}: name='{Name}' callback={Callback}", i, text2, num4);
|
|
}
|
|
if (num < 0 && text2 != null && GameFunctions.GameStringEquals(text2, text))
|
|
{
|
|
num = num4;
|
|
}
|
|
}
|
|
_entriesLogged = true;
|
|
if (num < 0)
|
|
{
|
|
return;
|
|
}
|
|
long tickCount = Environment.TickCount64;
|
|
if (tickCount >= _nextFireAtMs)
|
|
{
|
|
if (!_fired)
|
|
{
|
|
_logger.LogDebug("Selecting aethernet destination {Destination} ({Name}) via callback {Callback}", PendingDestination, text, num);
|
|
}
|
|
AtkValue* values = stackalloc AtkValue[2]
|
|
{
|
|
new AtkValue
|
|
{
|
|
Type = AtkValueType.Int,
|
|
Int = 11
|
|
},
|
|
new AtkValue
|
|
{
|
|
Type = AtkValueType.Int,
|
|
Int = num
|
|
}
|
|
};
|
|
addonPtr2->FireCallback(2u, values, close: true);
|
|
_fired = true;
|
|
_nextFireAtMs = tickCount + 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_framework.Update -= OnFrameworkUpdate;
|
|
}
|
|
}
|