PalacePal/Pal.Client/Floors/FrameworkService.cs

469 lines
18 KiB
C#
Raw Normal View History

2023-02-15 22:17:19 +00:00
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
2023-02-15 22:17:19 +00:00
using Pal.Client.Configuration;
2023-02-22 19:29:58 +00:00
using Pal.Client.Database;
2023-02-22 22:58:05 +00:00
using Pal.Client.DependencyInjection;
2023-02-15 22:17:19 +00:00
using Pal.Client.Net;
using Pal.Client.Rendering;
using Pal.Client.Scheduled;
2023-02-18 20:12:36 +00:00
using Pal.Common;
2023-02-15 22:17:19 +00:00
2023-02-22 22:58:05 +00:00
namespace Pal.Client.Floors
2023-02-15 22:17:19 +00:00
{
2023-02-15 22:51:35 +00:00
internal sealed class FrameworkService : IDisposable
2023-02-15 22:17:19 +00:00
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<FrameworkService> _logger;
2023-02-15 22:17:19 +00:00
private readonly Framework _framework;
private readonly ConfigurationManager _configurationManager;
private readonly IPalacePalConfiguration _configuration;
private readonly ClientState _clientState;
private readonly TerritoryState _territoryState;
private readonly FloorService _floorService;
private readonly DebugState _debugState;
private readonly RenderAdapter _renderAdapter;
private readonly ObjectTable _objectTable;
private readonly RemoteApi _remoteApi;
internal Queue<IQueueOnFrameworkThread> EarlyEventQueue { get; } = new();
internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new();
internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new();
public FrameworkService(
IServiceProvider serviceProvider,
ILogger<FrameworkService> logger,
Framework framework,
2023-02-15 22:17:19 +00:00
ConfigurationManager configurationManager,
IPalacePalConfiguration configuration,
ClientState clientState,
TerritoryState territoryState,
FloorService floorService,
DebugState debugState,
RenderAdapter renderAdapter,
ObjectTable objectTable,
RemoteApi remoteApi)
{
_serviceProvider = serviceProvider;
_logger = logger;
2023-02-15 22:17:19 +00:00
_framework = framework;
_configurationManager = configurationManager;
_configuration = configuration;
_clientState = clientState;
_territoryState = territoryState;
_floorService = floorService;
_debugState = debugState;
_renderAdapter = renderAdapter;
_objectTable = objectTable;
_remoteApi = remoteApi;
_framework.Update += OnUpdate;
_configurationManager.Saved += OnSaved;
}
public void Dispose()
{
_framework.Update -= OnUpdate;
_configurationManager.Saved -= OnSaved;
}
private void OnSaved(object? sender, IPalacePalConfiguration? config)
=> EarlyEventQueue.Enqueue(new QueuedConfigUpdate());
private void OnUpdate(Framework framework)
{
if (_configuration.FirstUse)
return;
try
{
bool recreateLayout = false;
while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
2023-02-18 20:12:36 +00:00
HandleQueued(queued, ref recreateLayout);
2023-02-15 22:17:19 +00:00
if (_territoryState.LastTerritory != _clientState.TerritoryType)
{
MemoryTerritory? oldTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (oldTerritory != null)
oldTerritory.SyncState = ESyncState.NotAttempted;
2023-02-15 22:17:19 +00:00
_territoryState.LastTerritory = _clientState.TerritoryType;
NextUpdateObjects.Clear();
2023-02-18 20:12:36 +00:00
_floorService.ChangeTerritory(_territoryState.LastTerritory);
2023-02-15 22:17:19 +00:00
_territoryState.PomanderOfSight = PomanderState.Inactive;
_territoryState.PomanderOfIntuition = PomanderState.Inactive;
recreateLayout = true;
_debugState.Reset();
}
2023-02-18 20:12:36 +00:00
if (!_territoryState.IsInDeepDungeon() || !_floorService.IsReady(_territoryState.LastTerritory))
2023-02-15 22:17:19 +00:00
return;
if (_renderAdapter.RequireRedraw)
{
recreateLayout = true;
_renderAdapter.RequireRedraw = false;
}
ETerritoryType territoryType = (ETerritoryType)_territoryState.LastTerritory;
MemoryTerritory memoryTerritory = _floorService.GetTerritoryIfReady(territoryType)!;
if (_configuration.Mode == EMode.Online && memoryTerritory.SyncState == ESyncState.NotAttempted)
2023-02-15 22:17:19 +00:00
{
memoryTerritory.SyncState = ESyncState.Started;
Task.Run(async () => await DownloadLocationsForTerritory(_territoryState.LastTerritory));
2023-02-15 22:17:19 +00:00
}
while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
2023-02-18 20:12:36 +00:00
HandleQueued(queued, ref recreateLayout);
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
(IReadOnlyList<PersistentLocation> visiblePersistentMarkers,
IReadOnlyList<EphemeralLocation> visibleEphemeralMarkers) =
GetRelevantGameObjects();
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
HandlePersistentLocations(territoryType, visiblePersistentMarkers, recreateLayout);
if (_floorService.MergeEphemeralLocations(visibleEphemeralMarkers, recreateLayout))
RecreateEphemeralLayout();
2023-02-15 22:17:19 +00:00
}
catch (Exception e)
{
_debugState.SetFromException(e);
}
}
#region Render Markers
2023-02-18 20:12:36 +00:00
private void HandlePersistentLocations(ETerritoryType territoryType,
IReadOnlyList<PersistentLocation> visiblePersistentMarkers,
bool recreateLayout)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
bool recreatePersistentLocations = _floorService.MergePersistentLocations(
territoryType,
visiblePersistentMarkers,
recreateLayout,
out List<PersistentLocation> locationsToSync);
recreatePersistentLocations |= CheckLocationsForPomanders(visiblePersistentMarkers);
if (locationsToSync.Count > 0)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
Task.Run(async () =>
await SyncSeenMarkersForTerritory(_territoryState.LastTerritory, locationsToSync));
}
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
UploadLocations();
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
if (recreatePersistentLocations)
RecreatePersistentLayout(visiblePersistentMarkers);
}
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
private bool CheckLocationsForPomanders(IReadOnlyList<PersistentLocation> visibleLocations)
{
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (memoryTerritory is { Locations.Count: > 0 } &&
(_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander))
2023-02-15 22:17:19 +00:00
{
try
{
2023-02-18 20:12:36 +00:00
foreach (var location in memoryTerritory.Locations)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
uint desiredColor = DetermineColor(location, visibleLocations);
if (location.RenderElement == null || !location.RenderElement.IsValid)
return true;
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
if (location.RenderElement.Color != desiredColor)
location.RenderElement.Color = desiredColor;
2023-02-15 22:17:19 +00:00
}
}
catch (Exception e)
{
_debugState.SetFromException(e);
2023-02-18 20:12:36 +00:00
return true;
2023-02-15 22:17:19 +00:00
}
}
2023-02-18 20:12:36 +00:00
return false;
}
private void UploadLocations()
{
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (memoryTerritory == null || memoryTerritory.SyncState != ESyncState.Complete)
2023-02-18 20:12:36 +00:00
return;
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
List<PersistentLocation> locationsToUpload = memoryTerritory.Locations
.Where(loc => loc.NetworkId == null && loc.UploadRequested == false)
.ToList();
if (locationsToUpload.Count > 0)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
foreach (var location in locationsToUpload)
location.UploadRequested = true;
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
Task.Run(async () =>
await UploadLocationsForTerritory(_territoryState.LastTerritory, locationsToUpload));
2023-02-15 22:17:19 +00:00
}
2023-02-18 20:12:36 +00:00
}
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
private void RecreatePersistentLayout(IReadOnlyList<PersistentLocation> visibleMarkers)
{
_renderAdapter.ResetLayer(ELayer.TrapHoard);
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (memoryTerritory == null)
return;
List<IRenderElement> elements = new();
foreach (var location in memoryTerritory.Locations)
{
if (location.Type == MemoryLocation.EType.Trap)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
CreateRenderElement(location, elements, DetermineColor(location, visibleMarkers),
_configuration.DeepDungeons.Traps);
2023-02-15 22:17:19 +00:00
}
2023-02-18 20:12:36 +00:00
else if (location.Type == MemoryLocation.EType.Hoard)
{
CreateRenderElement(location, elements, DetermineColor(location, visibleMarkers),
_configuration.DeepDungeons.HoardCoffers);
}
}
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
if (elements.Count == 0)
return;
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
_renderAdapter.SetLayer(ELayer.TrapHoard, elements);
2023-02-15 22:17:19 +00:00
}
2023-02-18 20:12:36 +00:00
private void RecreateEphemeralLayout()
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
_renderAdapter.ResetLayer(ELayer.RegularCoffers);
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
List<IRenderElement> elements = new();
foreach (var location in _floorService.EphemeralLocations)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
if (location.Type == MemoryLocation.EType.SilverCoffer &&
_configuration.DeepDungeons.SilverCoffers.Show)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
CreateRenderElement(location, elements, DetermineColor(location),
_configuration.DeepDungeons.SilverCoffers);
2023-02-15 22:17:19 +00:00
}
2023-02-26 16:31:37 +00:00
else if (location.Type == MemoryLocation.EType.GoldCoffer &&
_configuration.DeepDungeons.GoldCoffers.Show)
{
CreateRenderElement(location, elements, DetermineColor(location),
_configuration.DeepDungeons.GoldCoffers);
}
2023-02-18 20:12:36 +00:00
}
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
if (elements.Count == 0)
return;
2023-02-15 22:17:19 +00:00
2023-02-18 20:12:36 +00:00
_renderAdapter.SetLayer(ELayer.RegularCoffers, elements);
2023-02-15 22:17:19 +00:00
}
2023-02-18 20:12:36 +00:00
private uint DetermineColor(PersistentLocation location, IReadOnlyList<PersistentLocation> visibleLocations)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
switch (location.Type)
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
case MemoryLocation.EType.Trap
when _territoryState.PomanderOfSight == PomanderState.Inactive ||
!_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
visibleLocations.Any(x => x == location):
2023-02-15 22:17:19 +00:00
return _configuration.DeepDungeons.Traps.Color;
2023-02-18 20:12:36 +00:00
case MemoryLocation.EType.Hoard
when _territoryState.PomanderOfIntuition == PomanderState.Inactive ||
!_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander ||
visibleLocations.Any(x => x == location):
2023-02-15 22:17:19 +00:00
return _configuration.DeepDungeons.HoardCoffers.Color;
default:
2023-02-18 20:12:36 +00:00
return RenderData.ColorInvisible;
2023-02-15 22:17:19 +00:00
}
}
2023-02-18 20:12:36 +00:00
private uint DetermineColor(EphemeralLocation location)
{
2023-02-26 16:31:37 +00:00
return location.Type switch
{
MemoryLocation.EType.SilverCoffer => _configuration.DeepDungeons.SilverCoffers.Color,
MemoryLocation.EType.GoldCoffer => _configuration.DeepDungeons.GoldCoffers.Color,
_ => RenderData.ColorInvisible
};
2023-02-18 20:12:36 +00:00
}
private void CreateRenderElement(MemoryLocation location, List<IRenderElement> elements, uint color,
MarkerConfiguration config)
2023-02-15 22:17:19 +00:00
{
if (!config.Show)
return;
2023-02-18 20:12:36 +00:00
var element = _renderAdapter.CreateElement(location.Type, location.Position, color, config.Fill);
location.RenderElement = element;
2023-02-15 22:17:19 +00:00
elements.Add(element);
}
2023-02-15 22:17:19 +00:00
#endregion
#region Up-/Download
private async Task DownloadLocationsForTerritory(ushort territoryId)
2023-02-15 22:17:19 +00:00
{
try
{
_logger.LogInformation("Downloading territory {Territory} from server", (ETerritoryType)territoryId);
2023-02-15 22:17:19 +00:00
var (success, downloadedMarkers) = await _remoteApi.DownloadRemoteMarkers(territoryId);
LateEventQueue.Enqueue(new QueuedSyncResponse
{
Type = SyncType.Download,
TerritoryType = territoryId,
Success = success,
2023-02-18 20:12:36 +00:00
Locations = downloadedMarkers
2023-02-15 22:17:19 +00:00
});
}
catch (Exception e)
{
_debugState.SetFromException(e);
}
}
2023-02-18 20:12:36 +00:00
private async Task UploadLocationsForTerritory(ushort territoryId, List<PersistentLocation> locationsToUpload)
2023-02-15 22:17:19 +00:00
{
try
{
_logger.LogInformation("Uploading {Count} locations for territory {Territory} to server",
locationsToUpload.Count, (ETerritoryType)territoryId);
2023-02-18 20:12:36 +00:00
var (success, uploadedLocations) = await _remoteApi.UploadLocations(territoryId, locationsToUpload);
2023-02-15 22:17:19 +00:00
LateEventQueue.Enqueue(new QueuedSyncResponse
{
Type = SyncType.Upload,
TerritoryType = territoryId,
Success = success,
2023-02-18 20:12:36 +00:00
Locations = uploadedLocations
2023-02-15 22:17:19 +00:00
});
}
catch (Exception e)
{
_debugState.SetFromException(e);
}
}
2023-02-18 20:12:36 +00:00
private async Task SyncSeenMarkersForTerritory(ushort territoryId,
IReadOnlyList<PersistentLocation> locationsToUpdate)
2023-02-15 22:17:19 +00:00
{
try
{
_logger.LogInformation("Syncing {Count} seen locations for territory {Territory} to server",
locationsToUpdate.Count, (ETerritoryType)territoryId);
2023-02-18 20:12:36 +00:00
var success = await _remoteApi.MarkAsSeen(territoryId, locationsToUpdate);
2023-02-15 22:17:19 +00:00
LateEventQueue.Enqueue(new QueuedSyncResponse
{
Type = SyncType.MarkSeen,
TerritoryType = territoryId,
Success = success,
2023-02-18 20:12:36 +00:00
Locations = locationsToUpdate,
2023-02-15 22:17:19 +00:00
});
}
catch (Exception e)
{
_debugState.SetFromException(e);
}
}
2023-02-15 22:17:19 +00:00
#endregion
2023-02-18 20:12:36 +00:00
private (IReadOnlyList<PersistentLocation>, IReadOnlyList<EphemeralLocation>) GetRelevantGameObjects()
2023-02-15 22:17:19 +00:00
{
2023-02-18 20:12:36 +00:00
List<PersistentLocation> persistentLocations = new();
List<EphemeralLocation> ephemeralLocations = new();
2023-02-15 22:17:19 +00:00
for (int i = 246; i < _objectTable.Length; i++)
{
GameObject? obj = _objectTable[i];
if (obj == null)
continue;
switch ((uint)Marshal.ReadInt32(obj.Address + 128))
{
case 2007182:
case 2007183:
case 2007184:
case 2007185:
case 2007186:
case 2009504:
case 2013284:
2023-02-18 20:12:36 +00:00
persistentLocations.Add(new PersistentLocation
{
Type = MemoryLocation.EType.Trap,
Position = obj.Position,
2023-02-22 19:29:58 +00:00
Seen = true,
Source = ClientLocation.ESource.SeenLocally,
2023-02-18 20:12:36 +00:00
});
2023-02-15 22:17:19 +00:00
break;
case 2007542:
case 2007543:
2023-02-18 20:12:36 +00:00
persistentLocations.Add(new PersistentLocation
{
Type = MemoryLocation.EType.Hoard,
Position = obj.Position,
2023-02-22 19:29:58 +00:00
Seen = true,
Source = ClientLocation.ESource.SeenLocally,
2023-02-18 20:12:36 +00:00
});
2023-02-15 22:17:19 +00:00
break;
case 2007357:
2023-02-18 20:12:36 +00:00
ephemeralLocations.Add(new EphemeralLocation
{
Type = MemoryLocation.EType.SilverCoffer,
Position = obj.Position,
2023-02-22 19:29:58 +00:00
Seen = true,
2023-02-18 20:12:36 +00:00
});
2023-02-15 22:17:19 +00:00
break;
2023-02-26 16:31:37 +00:00
case 2007358:
ephemeralLocations.Add(new EphemeralLocation
{
Type = MemoryLocation.EType.GoldCoffer,
Position = obj.Position,
Seen = true
});
break;
2023-02-15 22:17:19 +00:00
}
}
while (NextUpdateObjects.TryDequeue(out nint address))
{
var obj = _objectTable.FirstOrDefault(x => x.Address == address);
if (obj != null && obj.Position.Length() > 0.1)
2023-02-18 20:12:36 +00:00
{
persistentLocations.Add(new PersistentLocation
{
Type = MemoryLocation.EType.Trap,
Position = obj.Position,
Seen = true,
2023-02-22 19:29:58 +00:00
Source = ClientLocation.ESource.ExplodedLocally,
2023-02-18 20:12:36 +00:00
});
}
2023-02-15 22:17:19 +00:00
}
2023-02-18 20:12:36 +00:00
return (persistentLocations, ephemeralLocations);
2023-02-15 22:17:19 +00:00
}
2023-02-18 20:12:36 +00:00
private void HandleQueued(IQueueOnFrameworkThread queued, ref bool recreateLayout)
{
Type handlerType = typeof(IQueueOnFrameworkThread.Handler<>).MakeGenericType(queued.GetType());
var handler = (IQueueOnFrameworkThread.IHandler)_serviceProvider.GetRequiredService(handlerType);
2023-02-18 20:12:36 +00:00
handler.RunIfCompatible(queued, ref recreateLayout);
}
2023-02-15 22:17:19 +00:00
}
}