1156 lines
43 KiB
C#
1156 lines
43 KiB
C#
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using Dalamud.Game.Text;
|
|
using Dalamud.Plugin.Services;
|
|
using Dalamud.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using LightlessSync.API.Dto.CharaData;
|
|
using LightlessSync.Interop;
|
|
using LightlessSync.LightlessConfiguration;
|
|
using LightlessSync.PlayerData.Factories;
|
|
using LightlessSync.PlayerData.Handlers;
|
|
using LightlessSync.PlayerData.Pairs;
|
|
using LightlessSync.Services.ActorTracking;
|
|
using LightlessSync.Services.Mediator;
|
|
using LightlessSync.Utils;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
|
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
|
using Map = Lumina.Excel.Sheets.Map;
|
|
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
|
|
|
namespace LightlessSync.Services;
|
|
|
|
public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|
{
|
|
private readonly List<uint> _classJobIdsIgnoredForPets = [30];
|
|
private readonly IClientState _clientState;
|
|
private readonly ICondition _condition;
|
|
private readonly IDataManager _gameData;
|
|
private readonly IGameConfig _gameConfig;
|
|
private readonly IPlayerState _playerState;
|
|
private readonly BlockedCharacterHandler _blockedCharacterHandler;
|
|
private readonly IFramework _framework;
|
|
private readonly IGameGui _gameGui;
|
|
private readonly ILogger<DalamudUtilService> _logger;
|
|
private readonly IObjectTable _objectTable;
|
|
private readonly ActorObjectService _actorObjectService;
|
|
private readonly ITargetManager _targetManager;
|
|
private readonly PerformanceCollectorService _performanceCollector;
|
|
private readonly LightlessConfigService _configService;
|
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
|
private readonly Lazy<PairFactory> _pairFactory;
|
|
private PairUniqueIdentifier? _FocusPairIdent;
|
|
private IGameObject? _FocusOriginalTarget;
|
|
private uint? _classJobId = 0;
|
|
private DateTime _delayedFrameworkUpdateCheck = DateTime.UtcNow;
|
|
private string _lastGlobalBlockPlayer = string.Empty;
|
|
private string _lastGlobalBlockReason = string.Empty;
|
|
private ushort _lastZone = 0;
|
|
private ushort _lastWorldId = 0;
|
|
private bool _sentBetweenAreas = false;
|
|
private Lazy<ulong> _cid;
|
|
|
|
public DalamudUtilService(ILogger<DalamudUtilService> logger, IClientState clientState, IObjectTable objectTable, IFramework framework,
|
|
IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, IGameConfig gameConfig, IPlayerState playerState,
|
|
ActorObjectService actorObjectService, BlockedCharacterHandler blockedCharacterHandler, LightlessMediator mediator, PerformanceCollectorService performanceCollector,
|
|
LightlessConfigService configService, PlayerPerformanceConfigService playerPerformanceConfigService, Lazy<PairFactory> pairFactory)
|
|
{
|
|
_logger = logger;
|
|
_clientState = clientState;
|
|
_objectTable = objectTable;
|
|
_framework = framework;
|
|
_gameGui = gameGui;
|
|
_condition = condition;
|
|
_gameData = gameData;
|
|
_gameConfig = gameConfig;
|
|
_playerState = playerState;
|
|
_actorObjectService = actorObjectService;
|
|
_targetManager = targetManager;
|
|
_blockedCharacterHandler = blockedCharacterHandler;
|
|
Mediator = mediator;
|
|
_performanceCollector = performanceCollector;
|
|
_configService = configService;
|
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
|
_pairFactory = pairFactory;
|
|
WorldData = new(() =>
|
|
{
|
|
return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(Dalamud.Game.ClientLanguage.English)!
|
|
.Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0])))
|
|
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
|
});
|
|
JobData = new(() =>
|
|
{
|
|
return gameData.GetExcelSheet<ClassJob>(Dalamud.Game.ClientLanguage.English)!
|
|
.ToDictionary(k => k.RowId, k => k.NameEnglish.ToString());
|
|
});
|
|
var clientLanguage = _clientState.ClientLanguage;
|
|
TerritoryData = new(() => BuildTerritoryData(clientLanguage));
|
|
TerritoryDataEnglish = new(() => BuildTerritoryData(Dalamud.Game.ClientLanguage.English));
|
|
MapData = new(() => BuildMapData(clientLanguage));
|
|
ContentFinderData = new Lazy<Dictionary<uint, string>>(() =>
|
|
{
|
|
return _gameData.GetExcelSheet<TerritoryType>()!
|
|
.Where(w => w.RowId != 0 && !string.IsNullOrEmpty(w.ContentFinderCondition.ValueNullable?.Name.ToString()))
|
|
.ToDictionary(w => w.RowId, w => w.ContentFinderCondition.Value.Name.ToString());
|
|
});
|
|
mediator.Subscribe<TargetPairMessage>(this, (msg) =>
|
|
{
|
|
if (clientState.IsPvP) return;
|
|
if (!ResolvePairAddress(msg.Pair, out var pair, out var addr)) return;
|
|
var useFocusTarget = _configService.Current.UseFocusTarget;
|
|
_ = RunOnFrameworkThread(() =>
|
|
{
|
|
var gameObject = CreateGameObject(addr);
|
|
if (gameObject is null) return;
|
|
if (useFocusTarget)
|
|
{
|
|
_targetManager.FocusTarget = gameObject;
|
|
if (_FocusPairIdent.HasValue && _FocusPairIdent.Value.Equals(pair.UniqueIdent))
|
|
{
|
|
_FocusOriginalTarget = _targetManager.FocusTarget;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_targetManager.Target = gameObject;
|
|
}
|
|
}).ConfigureAwait(false);
|
|
});
|
|
IsWine = Util.IsWine();
|
|
_cid = RebuildCID();
|
|
}
|
|
|
|
private Lazy<ulong> RebuildCID() => new(GetCID);
|
|
|
|
public bool IsWine { get; init; }
|
|
private Dictionary<uint, string> BuildTerritoryData(Dalamud.Game.ClientLanguage language)
|
|
{
|
|
var placeNames = _gameData.GetExcelSheet<PlaceName>(language)!;
|
|
return _gameData.GetExcelSheet<TerritoryType>(language)!
|
|
.Where(w => w.RowId != 0)
|
|
.ToDictionary(w => w.RowId, w =>
|
|
{
|
|
var regionName = GetPlaceName(placeNames, w.PlaceNameRegion.RowId);
|
|
var placeName = GetPlaceName(placeNames, w.PlaceName.RowId);
|
|
return BuildPlaceName(regionName, placeName, string.Empty);
|
|
});
|
|
}
|
|
|
|
private Dictionary<uint, (Map Map, string MapName)> BuildMapData(Dalamud.Game.ClientLanguage language)
|
|
{
|
|
var placeNames = _gameData.GetExcelSheet<PlaceName>(language)!;
|
|
return _gameData.GetExcelSheet<Map>(language)!
|
|
.Where(w => w.RowId != 0)
|
|
.ToDictionary(w => w.RowId, w =>
|
|
{
|
|
var regionName = GetPlaceName(placeNames, w.PlaceNameRegion.RowId);
|
|
var placeName = GetPlaceName(placeNames, w.PlaceName.RowId);
|
|
var subPlaceName = GetPlaceName(placeNames, w.PlaceNameSub.RowId);
|
|
var displayName = BuildPlaceName(regionName, placeName, subPlaceName);
|
|
return (w, displayName);
|
|
});
|
|
}
|
|
private static string GetPlaceName(Lumina.Excel.ExcelSheet<PlaceName> placeNames, uint rowId)
|
|
{
|
|
if (rowId == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return placeNames.GetRow(rowId).Name.ToString();
|
|
}
|
|
|
|
private static string BuildPlaceName(string regionName, string placeName, string subPlaceName)
|
|
{
|
|
StringBuilder sb = new();
|
|
if (!string.IsNullOrWhiteSpace(regionName))
|
|
{
|
|
sb.Append(regionName);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(placeName))
|
|
{
|
|
if (sb.Length > 0)
|
|
{
|
|
sb.Append(" - ");
|
|
}
|
|
sb.Append(placeName);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(subPlaceName))
|
|
{
|
|
if (sb.Length > 0)
|
|
{
|
|
sb.Append(" - ");
|
|
}
|
|
sb.Append(subPlaceName);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
private bool ResolvePairAddress(Pair pair, out Pair resolvedPair, out nint address)
|
|
{
|
|
resolvedPair = _pairFactory.Value.Create(pair.UniqueIdent) ?? pair;
|
|
address = nint.Zero;
|
|
var name = resolvedPair.PlayerName;
|
|
if (string.IsNullOrEmpty(name)) return false;
|
|
if (!_actorObjectService.TryGetPlayerByName(name, out var descriptor))
|
|
return false;
|
|
address = descriptor.Address;
|
|
return address != nint.Zero;
|
|
}
|
|
|
|
public void FocusVisiblePair(Pair pair)
|
|
{
|
|
if (_clientState.IsPvP) return;
|
|
if (!ResolvePairAddress(pair, out var resolvedPair, out var address)) return;
|
|
_ = RunOnFrameworkThread(() => FocusPairUnsafe(address, resolvedPair.UniqueIdent));
|
|
}
|
|
|
|
public void ReleaseVisiblePairFocus()
|
|
{
|
|
_ = RunOnFrameworkThread(ReleaseFocusUnsafe);
|
|
}
|
|
|
|
private void FocusPairUnsafe(nint address, PairUniqueIdentifier pairIdent)
|
|
{
|
|
var target = CreateGameObject(address);
|
|
if (target is null) return;
|
|
|
|
if (!_FocusPairIdent.HasValue)
|
|
{
|
|
_FocusOriginalTarget = _targetManager.FocusTarget;
|
|
}
|
|
|
|
_targetManager.FocusTarget = target;
|
|
_FocusPairIdent = pairIdent;
|
|
}
|
|
|
|
private void ReleaseFocusUnsafe()
|
|
{
|
|
if (!_FocusPairIdent.HasValue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var previous = _FocusOriginalTarget;
|
|
if (previous != null && !IsObjectPresent(previous))
|
|
{
|
|
previous = null;
|
|
}
|
|
|
|
_targetManager.FocusTarget = previous;
|
|
_FocusPairIdent = null;
|
|
_FocusOriginalTarget = null;
|
|
}
|
|
|
|
public unsafe GameObject* GposeTarget
|
|
{
|
|
get => TargetSystem.Instance()->GPoseTarget;
|
|
set => TargetSystem.Instance()->GPoseTarget = value;
|
|
}
|
|
|
|
private unsafe bool HasGposeTarget => GposeTarget != null;
|
|
private unsafe int GPoseTargetIdx => !HasGposeTarget ? -1 : GposeTarget->ObjectIndex;
|
|
|
|
public async Task<IGameObject?> GetGposeTargetGameObjectAsync()
|
|
{
|
|
if (!HasGposeTarget)
|
|
return null;
|
|
|
|
return await _framework.RunOnFrameworkThread(() => _objectTable[GPoseTargetIdx]).ConfigureAwait(true);
|
|
}
|
|
public bool IsAnythingDrawing { get; private set; } = false;
|
|
public bool IsInCutscene { get; private set; } = false;
|
|
public bool IsInGpose { get; private set; } = false;
|
|
public bool IsLoggedIn { get; private set; }
|
|
public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread;
|
|
public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
|
|
public bool IsInCombat { get; private set; } = false;
|
|
public bool IsPerforming { get; private set; } = false;
|
|
public bool IsInInstance { get; private set; } = false;
|
|
public bool IsInDuty => _condition[ConditionFlag.BoundByDuty];
|
|
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
|
|
public uint ClassJobId => _classJobId!.Value;
|
|
public Lazy<Dictionary<uint, string>> JobData { get; private set; }
|
|
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
|
|
public Lazy<Dictionary<uint, string>> TerritoryData { get; private set; }
|
|
public Lazy<Dictionary<uint, string>> TerritoryDataEnglish { get; private set; }
|
|
public Lazy<Dictionary<uint, (Map Map, string MapName)>> MapData { get; private set; }
|
|
public Lazy<Dictionary<uint, string>> ContentFinderData { get; private set; }
|
|
public bool IsLodEnabled { get; private set; }
|
|
public LightlessMediator Mediator { get; }
|
|
|
|
public bool IsInFieldOperation
|
|
{
|
|
get
|
|
{
|
|
if (!IsInDuty)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var territoryId = _clientState.TerritoryType;
|
|
if (territoryId == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TerritoryDataEnglish.Value.TryGetValue(territoryId, out var name) || string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return name.Contains("Eureka", StringComparison.OrdinalIgnoreCase)
|
|
|| name.Contains("Bozja", StringComparison.OrdinalIgnoreCase)
|
|
|| name.Contains("Zadnor", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
|
|
public IGameObject? CreateGameObject(IntPtr reference)
|
|
{
|
|
EnsureIsOnFramework();
|
|
return _objectTable.CreateObjectReference(reference);
|
|
}
|
|
|
|
public async Task<IGameObject?> CreateGameObjectAsync(IntPtr reference)
|
|
{
|
|
return await RunOnFrameworkThread(() => _objectTable.CreateObjectReference(reference)).ConfigureAwait(false);
|
|
}
|
|
|
|
public void EnsureIsOnFramework()
|
|
{
|
|
if (!_framework.IsInFrameworkUpdateThread) throw new InvalidOperationException("Can only be run on Framework");
|
|
}
|
|
|
|
public ICharacter? GetCharacterFromObjectTableByIndex(int index)
|
|
{
|
|
EnsureIsOnFramework();
|
|
var objTableObj = _objectTable[index];
|
|
if (objTableObj!.ObjectKind != DalamudObjectKind.Player) return null;
|
|
return (ICharacter)objTableObj;
|
|
}
|
|
|
|
public unsafe IntPtr GetCompanionPtr(IntPtr? playerPointer = null)
|
|
{
|
|
EnsureIsOnFramework();
|
|
var mgr = CharacterManager.Instance();
|
|
playerPointer ??= GetPlayerPtr();
|
|
if (playerPointer == IntPtr.Zero || (IntPtr)mgr == IntPtr.Zero) return IntPtr.Zero;
|
|
return (IntPtr)mgr->LookupBuddyByOwnerObject((BattleChara*)playerPointer);
|
|
}
|
|
|
|
public async Task<IntPtr> GetCompanionAsync(IntPtr? playerPointer = null)
|
|
{
|
|
return await RunOnFrameworkThread(() => GetCompanionPtr(playerPointer)).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<ICharacter?> GetGposeCharacterFromObjectTableByNameAsync(string name, bool onlyGposeCharacters = false)
|
|
{
|
|
return await RunOnFrameworkThread(() => GetGposeCharacterFromObjectTableByName(name, onlyGposeCharacters)).ConfigureAwait(false);
|
|
}
|
|
|
|
public ICharacter? GetGposeCharacterFromObjectTableByName(string name, bool onlyGposeCharacters = false)
|
|
{
|
|
EnsureIsOnFramework();
|
|
return (ICharacter?)_objectTable
|
|
.FirstOrDefault(i => (!onlyGposeCharacters || i.ObjectIndex >= 200) && string.Equals(i.Name.ToString(), name, StringComparison.Ordinal));
|
|
}
|
|
|
|
public IEnumerable<ICharacter?> GetGposeCharactersFromObjectTable()
|
|
{
|
|
foreach (var actor in _objectTable
|
|
.Where(a => a.ObjectIndex > 200 && a.ObjectKind == DalamudObjectKind.Player))
|
|
{
|
|
var character = _objectTable.CreateObjectReference(actor.Address) as ICharacter;
|
|
if (character != null)
|
|
yield return character;
|
|
}
|
|
}
|
|
|
|
public bool GetIsPlayerPresent()
|
|
{
|
|
EnsureIsOnFramework();
|
|
return _objectTable.LocalPlayer != null && _objectTable.LocalPlayer.IsValid() && _playerState.IsLoaded;
|
|
}
|
|
|
|
public async Task<bool> GetIsPlayerPresentAsync()
|
|
{
|
|
return await RunOnFrameworkThread(GetIsPlayerPresent).ConfigureAwait(false);
|
|
}
|
|
|
|
public unsafe IntPtr GetMinionOrMountPtr(IntPtr? playerPointer = null)
|
|
{
|
|
EnsureIsOnFramework();
|
|
playerPointer ??= GetPlayerPtr();
|
|
if (playerPointer == IntPtr.Zero) return IntPtr.Zero;
|
|
|
|
var playerAddress = playerPointer.Value;
|
|
var ownerEntityId = ((Character*)playerAddress)->EntityId;
|
|
var candidateAddress = _objectTable.GetObjectAddress(((GameObject*)playerAddress)->ObjectIndex + 1);
|
|
if (ownerEntityId == 0) return candidateAddress;
|
|
|
|
if (playerAddress == _actorObjectService.LocalPlayerAddress)
|
|
{
|
|
var localOwned = _actorObjectService.LocalMinionOrMountAddress;
|
|
if (localOwned != nint.Zero)
|
|
{
|
|
return localOwned;
|
|
}
|
|
}
|
|
|
|
if (candidateAddress != nint.Zero)
|
|
{
|
|
var candidate = (GameObject*)candidateAddress;
|
|
var candidateKind = (DalamudObjectKind)candidate->ObjectKind;
|
|
if ((candidateKind == DalamudObjectKind.MountType || candidateKind == DalamudObjectKind.Companion)
|
|
&& ResolveOwnerId(candidate) == ownerEntityId)
|
|
{
|
|
return candidateAddress;
|
|
}
|
|
}
|
|
|
|
var ownedObject = FindOwnedObject(ownerEntityId, playerAddress, static kind =>
|
|
kind == DalamudObjectKind.MountType || kind == DalamudObjectKind.Companion);
|
|
if (ownedObject != nint.Zero)
|
|
{
|
|
return ownedObject;
|
|
}
|
|
|
|
return candidateAddress;
|
|
}
|
|
|
|
public async Task<IntPtr> GetMinionOrMountAsync(IntPtr? playerPointer = null)
|
|
{
|
|
return await RunOnFrameworkThread(() => GetMinionOrMountPtr(playerPointer)).ConfigureAwait(false);
|
|
}
|
|
|
|
public unsafe IntPtr GetPetPtr(IntPtr? playerPointer = null)
|
|
{
|
|
EnsureIsOnFramework();
|
|
if (_classJobIdsIgnoredForPets.Contains(_classJobId ?? 0)) return IntPtr.Zero;
|
|
var mgr = CharacterManager.Instance();
|
|
playerPointer ??= GetPlayerPtr();
|
|
if (playerPointer == IntPtr.Zero || (IntPtr)mgr == IntPtr.Zero) return IntPtr.Zero;
|
|
return (IntPtr)mgr->LookupPetByOwnerObject((BattleChara*)playerPointer);
|
|
}
|
|
|
|
public async Task<IntPtr> GetPetAsync(IntPtr? playerPointer = null)
|
|
{
|
|
return await RunOnFrameworkThread(() => GetPetPtr(playerPointer)).ConfigureAwait(false);
|
|
}
|
|
|
|
private unsafe nint FindOwnedObject(uint ownerEntityId, nint ownerAddress, Func<DalamudObjectKind, bool> matchesKind)
|
|
{
|
|
if (ownerEntityId == 0)
|
|
{
|
|
return nint.Zero;
|
|
}
|
|
|
|
foreach (var obj in _objectTable)
|
|
{
|
|
if (obj is null || obj.Address == nint.Zero || obj.Address == ownerAddress)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!matchesKind(obj.ObjectKind))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var candidate = (GameObject*)obj.Address;
|
|
if (ResolveOwnerId(candidate) == ownerEntityId)
|
|
{
|
|
return obj.Address;
|
|
}
|
|
}
|
|
|
|
return nint.Zero;
|
|
}
|
|
|
|
private static unsafe uint ResolveOwnerId(GameObject* gameObject)
|
|
{
|
|
if (gameObject == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (gameObject->OwnerId != 0)
|
|
{
|
|
return gameObject->OwnerId;
|
|
}
|
|
|
|
var character = (Character*)gameObject;
|
|
if (character == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (character->CompanionOwnerId != 0)
|
|
{
|
|
return character->CompanionOwnerId;
|
|
}
|
|
|
|
var parent = character->GetParentCharacter();
|
|
return parent != null ? parent->EntityId : 0;
|
|
}
|
|
|
|
public async Task<IPlayerCharacter> GetPlayerCharacterAsync()
|
|
{
|
|
return await RunOnFrameworkThread(GetPlayerCharacter).ConfigureAwait(false);
|
|
}
|
|
|
|
public IPlayerCharacter GetPlayerCharacter()
|
|
{
|
|
EnsureIsOnFramework();
|
|
return _objectTable.LocalPlayer!;
|
|
}
|
|
|
|
public IntPtr GetPlayerCharacterFromCachedTableByIdent(string characterName)
|
|
{
|
|
if (_actorObjectService.TryGetValidatedActorByHash(characterName, out var actor))
|
|
return actor.Address;
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
public string GetPlayerName()
|
|
{
|
|
return _playerState.CharacterName;
|
|
}
|
|
|
|
public unsafe ulong GetCID()
|
|
{
|
|
return _playerState.ContentId;
|
|
}
|
|
|
|
public string GetPlayerNameHashed()
|
|
{
|
|
return _cid.Value.ToString().GetHash256();
|
|
}
|
|
|
|
public static unsafe bool TryGetHashedCID(IPlayerCharacter? playerCharacter, out string hashedCid)
|
|
{
|
|
hashedCid = string.Empty;
|
|
if (playerCharacter == null)
|
|
return false;
|
|
|
|
var address = playerCharacter.Address;
|
|
if (address == nint.Zero)
|
|
return false;
|
|
|
|
var cid = ((BattleChara*)address)->Character.ContentId;
|
|
if (cid == 0)
|
|
return false;
|
|
|
|
hashedCid = cid.ToString().GetHash256();
|
|
return true;
|
|
}
|
|
|
|
public unsafe static string GetHashedCIDFromPlayerPointer(nint ptr)
|
|
{
|
|
return ((BattleChara*)ptr)->Character.ContentId.ToString().GetHash256();
|
|
}
|
|
|
|
public IntPtr GetPlayerPtr()
|
|
{
|
|
EnsureIsOnFramework();
|
|
return _objectTable.LocalPlayer?.Address ?? IntPtr.Zero;
|
|
}
|
|
|
|
public async Task<IntPtr> GetPlayerPointerAsync()
|
|
{
|
|
return await RunOnFrameworkThread(GetPlayerPtr).ConfigureAwait(false);
|
|
}
|
|
|
|
public uint GetHomeWorldId()
|
|
{
|
|
return _playerState.HomeWorld.RowId;
|
|
}
|
|
|
|
public uint GetWorldId()
|
|
{
|
|
return _playerState.CurrentWorld.RowId;
|
|
}
|
|
|
|
public unsafe LocationInfo GetMapData()
|
|
{
|
|
var houseMan = HousingManager.Instance();
|
|
|
|
var location = new LocationInfo();
|
|
location.ServerId = _playerState.CurrentWorld.RowId;
|
|
location.InstanceId = UIState.Instance()->PublicInstance.InstanceId;
|
|
location.TerritoryId = _clientState.TerritoryType;
|
|
location.MapId = _clientState.MapId;
|
|
if (houseMan != null)
|
|
{
|
|
if (houseMan->IsInside())
|
|
{
|
|
location.TerritoryId = HousingManager.GetOriginalHouseTerritoryTypeId();
|
|
var house = houseMan->GetCurrentIndoorHouseId();
|
|
location.WardId = house.WardIndex + 1u;
|
|
location.HouseId = house.IsApartment ? 100 : house.PlotIndex + 1u;
|
|
location.RoomId = (uint)house.RoomNumber;
|
|
location.DivisionId = house.IsApartment ? house.ApartmentDivision + 1u : houseMan->GetCurrentDivision();
|
|
}
|
|
else if (houseMan->IsInWorkshop())
|
|
{
|
|
var workShop = houseMan->WorkshopTerritory;
|
|
var house = workShop->HouseId;
|
|
location.WardId = house.WardIndex + 1u;
|
|
location.HouseId = house.PlotIndex + 1u;
|
|
}
|
|
else if (houseMan->IsOutside())
|
|
{
|
|
var outside = houseMan->OutdoorTerritory;
|
|
var house = outside->HouseId;
|
|
location.WardId = house.WardIndex + 1u;
|
|
location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
|
|
location.DivisionId = houseMan->GetCurrentDivision();
|
|
}
|
|
//_logger.LogWarning(LocationToString(location));
|
|
}
|
|
return location;
|
|
}
|
|
|
|
public string LocationToString(LocationInfo location)
|
|
{
|
|
if (location.ServerId is 0 || location.TerritoryId is 0) return String.Empty;
|
|
var str = WorldData.Value[(ushort)location.ServerId];
|
|
|
|
if (ContentFinderData.Value.TryGetValue(location.TerritoryId , out var dutyName))
|
|
{
|
|
str += $" - [In Duty]{dutyName}";
|
|
}
|
|
else
|
|
{
|
|
if (location.HouseId is not 0 || location.MapId is 0) // Dont show mapName when in house/no map available
|
|
{
|
|
str += $" - {TerritoryData.Value[(ushort)location.TerritoryId]}";
|
|
}
|
|
else
|
|
{
|
|
str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
|
|
}
|
|
|
|
if (location.InstanceId is not 0)
|
|
{
|
|
str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
|
|
}
|
|
|
|
if (location.WardId is not 0)
|
|
{
|
|
str += $" Ward #{location.WardId}";
|
|
}
|
|
|
|
if (location.HouseId is not 0 and not 100)
|
|
{
|
|
str += $" House #{location.HouseId}";
|
|
}
|
|
else if (location.HouseId is 100)
|
|
{
|
|
str += $" {(location.DivisionId == 2 ? "[Subdivision]" : "")} Apartment";
|
|
}
|
|
|
|
if (location.RoomId is not 0)
|
|
{
|
|
str += $" Room #{location.RoomId}";
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
public unsafe void SetMarkerAndOpenMap(Vector3 position, Map map)
|
|
{
|
|
EnsureIsOnFramework();
|
|
var agentMap = AgentMap.Instance();
|
|
if (agentMap == null) return;
|
|
agentMap->OpenMapByMapId(map.RowId);
|
|
agentMap->SetFlagMapMarker(map.TerritoryType.RowId, map.RowId, position);
|
|
}
|
|
|
|
public unsafe bool IsGameObjectPresent(IntPtr key)
|
|
{
|
|
return _objectTable.Any(f => f.Address == key);
|
|
}
|
|
|
|
public bool IsObjectPresent(IGameObject? obj)
|
|
{
|
|
EnsureIsOnFramework();
|
|
return obj != null && obj.IsValid();
|
|
}
|
|
|
|
public async Task<bool> IsObjectPresentAsync(IGameObject? obj)
|
|
{
|
|
return await RunOnFrameworkThread(() => IsObjectPresent(obj)).ConfigureAwait(false);
|
|
}
|
|
|
|
public IPlayerCharacter? GetPlayerByNameAndWorld(string name, ushort homeWorldId)
|
|
{
|
|
EnsureIsOnFramework();
|
|
return _objectTable
|
|
.OfType<IPlayerCharacter>()
|
|
.FirstOrDefault(p =>
|
|
string.Equals(p.Name.TextValue, name, StringComparison.Ordinal) &&
|
|
p.HomeWorld.RowId == homeWorldId);
|
|
}
|
|
|
|
public async Task RunOnFrameworkThread(System.Action act, [CallerMemberName] string callerMember = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
|
{
|
|
var fileName = Path.GetFileNameWithoutExtension(callerFilePath);
|
|
await _performanceCollector.LogPerformance(this, $"RunOnFramework:Act/{fileName}>{callerMember}:{callerLineNumber}", async () =>
|
|
{
|
|
if (_framework.IsInFrameworkUpdateThread)
|
|
{
|
|
act();
|
|
return;
|
|
}
|
|
|
|
await _framework.RunOnFrameworkThread(act).ConfigureAwait(false);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<T> RunOnFrameworkThread<T>(Func<T> func, [CallerMemberName] string callerMember = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
|
|
{
|
|
var fileName = Path.GetFileNameWithoutExtension(callerFilePath);
|
|
return await _performanceCollector.LogPerformance(this, $"RunOnFramework:Func<{typeof(T)}>/{fileName}>{callerMember}:{callerLineNumber}", async () =>
|
|
{
|
|
if (_framework.IsInFrameworkUpdateThread)
|
|
{
|
|
return func.Invoke();
|
|
}
|
|
|
|
return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogInformation("Starting DalamudUtilService");
|
|
_framework.Update += FrameworkOnUpdate;
|
|
if (IsLoggedIn)
|
|
{
|
|
_classJobId = _objectTable.LocalPlayer!.ClassJob.RowId;
|
|
}
|
|
|
|
_logger.LogInformation("Started DalamudUtilService");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogTrace("Stopping {type}", GetType());
|
|
|
|
Mediator.UnsubscribeAll(this);
|
|
_framework.Update -= FrameworkOnUpdate;
|
|
if (_FocusPairIdent.HasValue)
|
|
{
|
|
if (_framework.IsInFrameworkUpdateThread)
|
|
{
|
|
ReleaseFocusUnsafe();
|
|
}
|
|
else
|
|
{
|
|
_ = RunOnFrameworkThread(ReleaseFocusUnsafe);
|
|
}
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public async Task WaitWhileCharacterIsDrawing(ILogger logger, GameObjectHandler handler, Guid redrawId, int timeOut = 5000, CancellationToken? ct = null)
|
|
{
|
|
if (!_clientState.IsLoggedIn) return;
|
|
|
|
if (ct == null)
|
|
ct = CancellationToken.None;
|
|
|
|
const int tick = 250;
|
|
int curWaitTime = 0;
|
|
try
|
|
{
|
|
logger.LogTrace("[{redrawId}] Starting wait for {handler} to draw", redrawId, handler);
|
|
await Task.Delay(tick, ct.Value).ConfigureAwait(true);
|
|
curWaitTime += tick;
|
|
|
|
while ((!ct.Value.IsCancellationRequested)
|
|
&& curWaitTime < timeOut
|
|
&& await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something
|
|
{
|
|
logger.LogTrace("[{redrawId}] Waiting for {handler} to finish drawing", redrawId, handler);
|
|
curWaitTime += tick;
|
|
await Task.Delay(tick, ct.Value).ConfigureAwait(true);
|
|
}
|
|
|
|
logger.LogTrace("[{redrawId}] Finished drawing after {curWaitTime}ms", redrawId, curWaitTime);
|
|
}
|
|
catch (AccessViolationException ex)
|
|
{
|
|
logger.LogWarning(ex, "Error accessing {handler}, object does not exist anymore?", handler);
|
|
}
|
|
}
|
|
|
|
public unsafe void WaitWhileGposeCharacterIsDrawing(IntPtr characterAddress, int timeOut = 5000)
|
|
{
|
|
Thread.Sleep(500);
|
|
var obj = (GameObject*)characterAddress;
|
|
const int tick = 250;
|
|
int curWaitTime = 0;
|
|
_logger.LogTrace("RenderFlags: {flags}", obj->RenderFlags.ToString("X"));
|
|
while (obj->RenderFlags != VisibilityFlags.None && curWaitTime < timeOut)
|
|
{
|
|
_logger.LogTrace($"Waiting for gpose actor to finish drawing");
|
|
curWaitTime += tick;
|
|
Thread.Sleep(tick);
|
|
}
|
|
Thread.Sleep(tick * 2);
|
|
}
|
|
|
|
public Vector2 WorldToScreen(IGameObject? obj)
|
|
{
|
|
if (obj == null) return Vector2.Zero;
|
|
return _gameGui.WorldToScreen(obj.Position, out var screenPos) ? screenPos : Vector2.Zero;
|
|
}
|
|
|
|
internal (string Name, nint Address) FindPlayerByNameHash(string ident)
|
|
{
|
|
if (_actorObjectService.TryGetValidatedActorByHash(ident, out var descriptor))
|
|
{
|
|
return (descriptor.Name, descriptor.Address);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
public string? GetWorldNameFromPlayerAddress(nint address)
|
|
{
|
|
if (address == nint.Zero) return null;
|
|
|
|
EnsureIsOnFramework();
|
|
var playerCharacter = _objectTable.OfType<IPlayerCharacter>().FirstOrDefault(p => p.Address == address);
|
|
if (playerCharacter == null) return null;
|
|
|
|
var worldId = (ushort)playerCharacter.HomeWorld.RowId;
|
|
return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null;
|
|
}
|
|
|
|
private unsafe void CheckCharacterForDrawing(nint address, string characterName)
|
|
{
|
|
var gameObj = (GameObject*)address;
|
|
var drawObj = gameObj->DrawObject;
|
|
bool isDrawing = false;
|
|
bool isDrawingChanged = false;
|
|
if ((nint)drawObj != IntPtr.Zero)
|
|
{
|
|
isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000;
|
|
if (!isDrawing)
|
|
{
|
|
isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0;
|
|
if (!isDrawing)
|
|
{
|
|
isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0;
|
|
if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
|
&& !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal))
|
|
{
|
|
_lastGlobalBlockPlayer = characterName;
|
|
_lastGlobalBlockReason = "HasModelFilesInSlotLoaded";
|
|
isDrawingChanged = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
|
&& !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal))
|
|
{
|
|
_lastGlobalBlockPlayer = characterName;
|
|
_lastGlobalBlockReason = "HasModelInSlotLoaded";
|
|
isDrawingChanged = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
|
&& !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal))
|
|
{
|
|
_lastGlobalBlockPlayer = characterName;
|
|
_lastGlobalBlockReason = "RenderFlags";
|
|
isDrawingChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isDrawingChanged)
|
|
{
|
|
_logger.LogTrace("Global draw block: START => {name} ({reason})", characterName, _lastGlobalBlockReason);
|
|
}
|
|
|
|
IsAnythingDrawing |= isDrawing;
|
|
}
|
|
|
|
private void FrameworkOnUpdate(IFramework framework)
|
|
{
|
|
_performanceCollector.LogPerformance(this, $"FrameworkOnUpdate", FrameworkOnUpdateInternal);
|
|
}
|
|
|
|
private unsafe void FrameworkOnUpdateInternal()
|
|
{
|
|
if ((_objectTable.LocalPlayer?.IsDead ?? false) && _condition[ConditionFlag.BoundByDuty])
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isNormalFrameworkUpdate = DateTime.UtcNow < _delayedFrameworkUpdateCheck.AddSeconds(1);
|
|
|
|
_performanceCollector.LogPerformance(this, $"FrameworkOnUpdateInternal+{(isNormalFrameworkUpdate ? "Regular" : "Delayed")}", () =>
|
|
{
|
|
IsAnythingDrawing = false;
|
|
_performanceCollector.LogPerformance(this, $"TrackedActorsToState",
|
|
() =>
|
|
{
|
|
if (!_actorObjectService.HooksActive || !isNormalFrameworkUpdate || _actorObjectService.HasPendingHashResolutions)
|
|
{
|
|
_actorObjectService.RefreshTrackedActors();
|
|
}
|
|
|
|
var playerDescriptors = _actorObjectService.PlayerDescriptors;
|
|
for (var i = 0; i < playerDescriptors.Count; i++)
|
|
{
|
|
var actor = playerDescriptors[i];
|
|
|
|
var playerAddress = actor.Address;
|
|
if (playerAddress == nint.Zero)
|
|
continue;
|
|
|
|
if (actor.ObjectIndex >= 200)
|
|
continue;
|
|
|
|
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, out bool firstTime) && firstTime)
|
|
{
|
|
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
|
|
continue;
|
|
}
|
|
|
|
if (!IsAnythingDrawing)
|
|
{
|
|
var gameObj = (GameObject*)playerAddress;
|
|
var currentName = gameObj != null ? gameObj->NameString ?? string.Empty : string.Empty;
|
|
var charaName = string.IsNullOrEmpty(currentName) ? actor.Name : currentName;
|
|
CheckCharacterForDrawing(playerAddress, charaName);
|
|
if (IsAnythingDrawing)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!IsAnythingDrawing && !string.IsNullOrEmpty(_lastGlobalBlockPlayer))
|
|
{
|
|
_logger.LogTrace("Global draw block: END => {name}", _lastGlobalBlockPlayer);
|
|
_lastGlobalBlockPlayer = string.Empty;
|
|
_lastGlobalBlockReason = string.Empty;
|
|
}
|
|
|
|
// Checks on conditions
|
|
var shouldBeInGpose = _clientState.IsGPosing;
|
|
var shouldBeInCombat = _condition[ConditionFlag.InCombat] && !IsInInstance && _playerPerformanceConfigService.Current.PauseInCombat;
|
|
var shouldBePerforming = _condition[ConditionFlag.Performing] && _playerPerformanceConfigService.Current.PauseWhilePerforming;
|
|
var shouldBeInInstance = _condition[ConditionFlag.BoundByDuty] && _playerPerformanceConfigService.Current.PauseInInstanceDuty;
|
|
var shouldBeInCutscene = _condition[ConditionFlag.WatchingCutscene];
|
|
|
|
// Gpose
|
|
HandleStateTransition(() => IsInGpose, v => IsInGpose = v, shouldBeInGpose, "Gpose",
|
|
onEnter: () =>
|
|
{
|
|
Mediator.Publish(new GposeStartMessage());
|
|
},
|
|
onExit: () =>
|
|
{
|
|
Mediator.Publish(new GposeEndMessage());
|
|
});
|
|
|
|
// Combat
|
|
HandleStateTransition(() => IsInCombat, v => IsInCombat = v, shouldBeInCombat, "Combat",
|
|
onEnter: () =>
|
|
{
|
|
Mediator.Publish(new CombatStartMessage());
|
|
Mediator.Publish(new HaltScanMessage(nameof(IsInCombat)));
|
|
},
|
|
onExit: () =>
|
|
{
|
|
Mediator.Publish(new CombatEndMessage());
|
|
Mediator.Publish(new ResumeScanMessage(nameof(IsInCombat)));
|
|
});
|
|
|
|
// Performance
|
|
HandleStateTransition(() => IsPerforming, v => IsPerforming = v, shouldBePerforming, "Performance",
|
|
onEnter: () =>
|
|
{
|
|
Mediator.Publish(new PerformanceStartMessage());
|
|
Mediator.Publish(new HaltScanMessage(nameof(IsPerforming)));
|
|
},
|
|
onExit: () =>
|
|
{
|
|
Mediator.Publish(new PerformanceEndMessage());
|
|
Mediator.Publish(new ResumeScanMessage(nameof(IsPerforming)));
|
|
});
|
|
|
|
// Instance / Duty
|
|
HandleStateTransition(() => IsInInstance, v => IsInInstance = v, shouldBeInInstance, "Instance",
|
|
onEnter: () =>
|
|
{
|
|
Mediator.Publish(new InstanceOrDutyStartMessage());
|
|
Mediator.Publish(new HaltScanMessage(nameof(IsInInstance)));
|
|
},
|
|
onExit: () =>
|
|
{
|
|
Mediator.Publish(new InstanceOrDutyEndMessage());
|
|
Mediator.Publish(new ResumeScanMessage(nameof(IsInInstance)));
|
|
});
|
|
|
|
// Cutscene
|
|
HandleStateTransition(() => IsInCutscene,v => IsInCutscene = v, shouldBeInCutscene, "Cutscene",
|
|
onEnter: () =>
|
|
{
|
|
Mediator.Publish(new CutsceneStartMessage());
|
|
Mediator.Publish(new HaltScanMessage(nameof(IsInCutscene)));
|
|
},
|
|
onExit: () =>
|
|
{
|
|
Mediator.Publish(new CutsceneEndMessage());
|
|
Mediator.Publish(new ResumeScanMessage(nameof(IsInCutscene)));
|
|
});
|
|
|
|
if (IsInCutscene)
|
|
{
|
|
Mediator.Publish(new CutsceneFrameworkUpdateMessage());
|
|
return;
|
|
}
|
|
|
|
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51])
|
|
{
|
|
var zone = _clientState.TerritoryType;
|
|
if (_lastZone != zone)
|
|
{
|
|
_lastZone = zone;
|
|
if (!_sentBetweenAreas)
|
|
{
|
|
_logger.LogDebug("Zone switch start");
|
|
_sentBetweenAreas = true;
|
|
Mediator.Publish(new ZoneSwitchStartMessage());
|
|
Mediator.Publish(new HaltScanMessage(nameof(ConditionFlag.BetweenAreas)));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (_sentBetweenAreas)
|
|
{
|
|
_logger.LogDebug("Zone switch end");
|
|
_sentBetweenAreas = false;
|
|
Mediator.Publish(new ZoneSwitchEndMessage());
|
|
Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas)));
|
|
}
|
|
|
|
var localPlayer = _objectTable.LocalPlayer;
|
|
if (localPlayer != null)
|
|
{
|
|
_classJobId = localPlayer.ClassJob.RowId;
|
|
|
|
var currentWorldId = (ushort)localPlayer.CurrentWorld.RowId;
|
|
if (currentWorldId != _lastWorldId)
|
|
{
|
|
var previousWorldId = _lastWorldId;
|
|
_lastWorldId = currentWorldId;
|
|
Mediator.Publish(new WorldChangedMessage(previousWorldId, currentWorldId));
|
|
}
|
|
}
|
|
else if (_lastWorldId != 0)
|
|
{
|
|
_lastWorldId = 0;
|
|
}
|
|
|
|
if (!IsInCombat || !IsPerforming || !IsInInstance)
|
|
Mediator.Publish(new FrameworkUpdateMessage());
|
|
|
|
Mediator.Publish(new PriorityFrameworkUpdateMessage());
|
|
|
|
if (isNormalFrameworkUpdate)
|
|
return;
|
|
|
|
if (localPlayer != null && !IsLoggedIn)
|
|
{
|
|
_logger.LogDebug("Logged in");
|
|
IsLoggedIn = true;
|
|
_lastZone = _clientState.TerritoryType;
|
|
_lastWorldId = (ushort)localPlayer.CurrentWorld.RowId;
|
|
_cid = RebuildCID();
|
|
Mediator.Publish(new DalamudLoginMessage());
|
|
}
|
|
else if (localPlayer == null && IsLoggedIn)
|
|
{
|
|
_logger.LogDebug("Logged out");
|
|
IsLoggedIn = false;
|
|
_lastWorldId = 0;
|
|
Mediator.Publish(new DalamudLogoutMessage());
|
|
}
|
|
|
|
if (_gameConfig != null
|
|
&& _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled))
|
|
{
|
|
IsLodEnabled = lodEnabled;
|
|
}
|
|
|
|
if (IsInCombat || IsPerforming || IsInInstance)
|
|
Mediator.Publish(new FrameworkUpdateMessage());
|
|
|
|
Mediator.Publish(new DelayedFrameworkUpdateMessage());
|
|
|
|
_delayedFrameworkUpdateCheck = DateTime.UtcNow;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for the transition of different states of game
|
|
/// </summary>
|
|
/// <param name="getState">Get state of condition</param>
|
|
/// <param name="setState">Set state of condition</param>
|
|
/// <param name="shouldBeActive">Correction of the state of the condition</param>
|
|
/// <param name="stateName">Condition name</param>
|
|
/// <param name="onEnter">Function for on entering the state</param>
|
|
/// <param name="onExit">Function for on leaving the state</param>
|
|
private void HandleStateTransition(Func<bool> getState, Action<bool> setState, bool shouldBeActive, string stateName, System.Action onEnter, System.Action onExit)
|
|
{
|
|
var isActive = getState();
|
|
|
|
if (shouldBeActive && !isActive)
|
|
{
|
|
_logger.LogDebug("{stateName} start", stateName);
|
|
setState(true);
|
|
onEnter();
|
|
}
|
|
else if (!shouldBeActive && isActive)
|
|
{
|
|
_logger.LogDebug("{stateName} end", stateName);
|
|
setState(false);
|
|
onExit();
|
|
}
|
|
}
|
|
}
|