DI: Initial Draft
This commit is contained in:
parent
faa35feade
commit
c52341eb0d
141
Pal.Client/Commands/PalCommand.cs
Normal file
141
Pal.Client/Commands/PalCommand.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using ECommons.Schedulers;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
using Pal.Client.Extensions;
|
||||||
|
using Pal.Client.Properties;
|
||||||
|
using Pal.Client.Rendering;
|
||||||
|
using Pal.Client.Windows;
|
||||||
|
|
||||||
|
namespace Pal.Client.Commands
|
||||||
|
{
|
||||||
|
// should restructure this when more commands exist, if that ever happens
|
||||||
|
// this command is more-or-less a debug/troubleshooting command, if anything
|
||||||
|
internal sealed class PalCommand : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly CommandManager _commandManager;
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
private readonly StatisticsService _statisticsService;
|
||||||
|
private readonly ConfigWindow _configWindow;
|
||||||
|
private readonly TerritoryState _territoryState;
|
||||||
|
private readonly FloorService _floorService;
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
|
||||||
|
public PalCommand(
|
||||||
|
IPalacePalConfiguration configuration,
|
||||||
|
CommandManager commandManager,
|
||||||
|
ChatGui chatGui,
|
||||||
|
StatisticsService statisticsService,
|
||||||
|
ConfigWindow configWindow,
|
||||||
|
TerritoryState territoryState,
|
||||||
|
FloorService floorService,
|
||||||
|
ClientState clientState)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_commandManager = commandManager;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
_statisticsService = statisticsService;
|
||||||
|
_configWindow = configWindow;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
_floorService = floorService;
|
||||||
|
_clientState = clientState;
|
||||||
|
|
||||||
|
_commandManager.AddHandler("/pal", new CommandInfo(OnCommand)
|
||||||
|
{
|
||||||
|
HelpMessage = Localization.Command_pal_HelpText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_commandManager.RemoveHandler("/pal");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCommand(string command, string arguments)
|
||||||
|
{
|
||||||
|
if (_configuration.FirstUse)
|
||||||
|
{
|
||||||
|
_chatGui.PalError(Localization.Error_FirstTimeSetupRequired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
arguments = arguments.Trim();
|
||||||
|
switch (arguments)
|
||||||
|
{
|
||||||
|
case "stats":
|
||||||
|
_statisticsService.ShowGlobalStatistics();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "test-connection":
|
||||||
|
case "tc":
|
||||||
|
_configWindow.IsOpen = true;
|
||||||
|
var _ = new TickScheduler(() => _configWindow.TestConnection());
|
||||||
|
break;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
case "update-saves":
|
||||||
|
LocalState.UpdateAll();
|
||||||
|
Service.Chat.Print(Localization.Command_pal_updatesaves);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case "":
|
||||||
|
case "config":
|
||||||
|
_configWindow.Toggle();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "near":
|
||||||
|
DebugNearest(_ => true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tnear":
|
||||||
|
DebugNearest(m => m.Type == Marker.EType.Trap);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "hnear":
|
||||||
|
DebugNearest(m => m.Type == Marker.EType.Hoard);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_chatGui.PalError(string.Format(Localization.Command_pal_UnknownSubcommand, arguments,
|
||||||
|
command));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_chatGui.PalError(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DebugNearest(Predicate<Marker> predicate)
|
||||||
|
{
|
||||||
|
if (!_territoryState.IsInDeepDungeon())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var state = _floorService.GetFloorMarkers(_clientState.TerritoryType);
|
||||||
|
var playerPosition = _clientState.LocalPlayer?.Position;
|
||||||
|
if (playerPosition == null)
|
||||||
|
return;
|
||||||
|
_chatGui.Print($"[Palace Pal] {playerPosition}");
|
||||||
|
|
||||||
|
var nearbyMarkers = state.Markers
|
||||||
|
.Where(m => predicate(m))
|
||||||
|
.Where(m => m.RenderElement != null && m.RenderElement.Color != RenderData.ColorInvisible)
|
||||||
|
.Select(m => new { m, distance = (playerPosition - m.Position)?.Length() ?? float.MaxValue })
|
||||||
|
.OrderBy(m => m.distance)
|
||||||
|
.Take(5)
|
||||||
|
.ToList();
|
||||||
|
foreach (var nearbyMarker in nearbyMarkers)
|
||||||
|
_chatGui.Print(
|
||||||
|
$"{nearbyMarker.distance:F2} - {nearbyMarker.m.Type} {nearbyMarker.m.NetworkId?.ToPartialId(length: 8)} - {nearbyMarker.m.Position}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
@ -6,6 +7,8 @@ using System.Text.Json;
|
|||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
using Pal.Client.Scheduled;
|
||||||
using NJson = Newtonsoft.Json;
|
using NJson = Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Pal.Client.Configuration
|
namespace Pal.Client.Configuration
|
||||||
@ -14,12 +17,16 @@ namespace Pal.Client.Configuration
|
|||||||
{
|
{
|
||||||
private readonly DalamudPluginInterface _pluginInterface;
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
|
public event EventHandler<IPalacePalConfiguration>? Saved;
|
||||||
|
|
||||||
public ConfigurationManager(DalamudPluginInterface pluginInterface)
|
public ConfigurationManager(DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
|
|
||||||
|
Migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
|
private string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
|
||||||
|
|
||||||
public IPalacePalConfiguration Load()
|
public IPalacePalConfiguration Load()
|
||||||
{
|
{
|
||||||
@ -27,16 +34,20 @@ namespace Pal.Client.Configuration
|
|||||||
new ConfigurationV7();
|
new ConfigurationV7();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(IConfigurationInConfigDirectory config)
|
public void Save(IConfigurationInConfigDirectory config, bool queue = true)
|
||||||
{
|
{
|
||||||
File.WriteAllText(ConfigPath,
|
File.WriteAllText(ConfigPath,
|
||||||
JsonSerializer.Serialize(config, config.GetType(), new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }),
|
JsonSerializer.Serialize(config, config.GetType(),
|
||||||
|
new JsonSerializerOptions
|
||||||
|
{ WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }),
|
||||||
Encoding.UTF8);
|
Encoding.UTF8);
|
||||||
|
if (queue && config is ConfigurationV7 v7)
|
||||||
|
Saved?.Invoke(this, v7);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS0612
|
#pragma warning disable CS0612
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
public void Migrate()
|
private void Migrate()
|
||||||
{
|
{
|
||||||
if (_pluginInterface.ConfigFile.Exists)
|
if (_pluginInterface.ConfigFile.Exists)
|
||||||
{
|
{
|
||||||
@ -49,7 +60,7 @@ namespace Pal.Client.Configuration
|
|||||||
configurationV1.Save();
|
configurationV1.Save();
|
||||||
|
|
||||||
var v7 = MigrateToV7(configurationV1);
|
var v7 = MigrateToV7(configurationV1);
|
||||||
Save(v7);
|
Save(v7, queue: false);
|
||||||
|
|
||||||
File.Move(_pluginInterface.ConfigFile.FullName, _pluginInterface.ConfigFile.FullName + ".old", true);
|
File.Move(_pluginInterface.ConfigFile.FullName, _pluginInterface.ConfigFile.FullName + ".old", true);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ namespace Pal.Client.Configuration
|
|||||||
{
|
{
|
||||||
// 2.2 had a bug that would mark chests as traps, there's no easy way to detect this -- or clean this up.
|
// 2.2 had a bug that would mark chests as traps, there's no easy way to detect this -- or clean this up.
|
||||||
// Not a problem for online players, but offline players might be fucked.
|
// Not a problem for online players, but offline players might be fucked.
|
||||||
bool changedAnyFile = false;
|
//bool changedAnyFile = false;
|
||||||
LocalState.ForEach(s =>
|
LocalState.ForEach(s =>
|
||||||
{
|
{
|
||||||
foreach (var marker in s.Markers)
|
foreach (var marker in s.Markers)
|
||||||
@ -104,7 +104,7 @@ namespace Pal.Client.Configuration
|
|||||||
s.Markers = new ConcurrentBag<Marker>(s.Markers.Where(m => m.SinceVersion != "0.0" || m.Type == Marker.EType.Hoard || m.WasImported));
|
s.Markers = new ConcurrentBag<Marker>(s.Markers.Where(m => m.SinceVersion != "0.0" || m.Type == Marker.EType.Hoard || m.WasImported));
|
||||||
s.Save();
|
s.Save();
|
||||||
|
|
||||||
changedAnyFile = true;
|
//changedAnyFile = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -113,6 +113,7 @@ namespace Pal.Client.Configuration
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
// Only notify offline users - we can just re-download the backup markers from the server seamlessly.
|
// Only notify offline users - we can just re-download the backup markers from the server seamlessly.
|
||||||
if (Mode == EMode.Offline && changedAnyFile)
|
if (Mode == EMode.Offline && changedAnyFile)
|
||||||
{
|
{
|
||||||
@ -123,6 +124,7 @@ namespace Pal.Client.Configuration
|
|||||||
Service.Chat.PrintError("You can also manually restore .json.bak files (by removing the '.bak') if you have not been in any deep dungeon since February 2, 2023.");
|
Service.Chat.PrintError("You can also manually restore .json.bak files (by removing the '.bak') if you have not been in any deep dungeon since February 2, 2023.");
|
||||||
}, 2500);
|
}, 2500);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
Version = 5;
|
Version = 5;
|
||||||
Save();
|
Save();
|
||||||
@ -144,7 +146,6 @@ namespace Pal.Client.Configuration
|
|||||||
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
TypeNameHandling = TypeNameHandling.Objects
|
TypeNameHandling = TypeNameHandling.Objects
|
||||||
}));
|
}));
|
||||||
Service.Plugin.EarlyEventQueue.Enqueue(new QueuedConfigUpdate());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AccountInfo
|
public class AccountInfo
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Pal.Client.Net;
|
||||||
|
|
||||||
namespace Pal.Client.Configuration;
|
namespace Pal.Client.Configuration;
|
||||||
|
|
||||||
@ -45,4 +46,13 @@ public class ConfigurationV7 : IPalacePalConfiguration, IConfigurationInConfigDi
|
|||||||
{
|
{
|
||||||
Accounts.RemoveAll(a => a.Server == server && a.IsUsable);
|
Accounts.RemoveAll(a => a.Server == server && a.IsUsable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasRoleOnCurrentServer(string role)
|
||||||
|
{
|
||||||
|
if (Mode != EMode.Online)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var account = FindAccount(RemoteApi.RemoteUrl);
|
||||||
|
return account == null || account.CachedRoles.Contains(role);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ namespace Pal.Client.Configuration
|
|||||||
IAccountConfiguration CreateAccount(string server, Guid accountId);
|
IAccountConfiguration CreateAccount(string server, Guid accountId);
|
||||||
IAccountConfiguration? FindAccount(string server);
|
IAccountConfiguration? FindAccount(string server);
|
||||||
void RemoveAccount(string server);
|
void RemoveAccount(string server);
|
||||||
|
|
||||||
|
bool HasRoleOnCurrentServer(string role);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeepDungeonConfiguration
|
public class DeepDungeonConfiguration
|
||||||
|
109
Pal.Client/DependencyInjection/ChatService.cs
Normal file
109
Pal.Client/DependencyInjection/ChatService.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
internal sealed class ChatService : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
private readonly TerritoryState _territoryState;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly DataManager _dataManager;
|
||||||
|
private readonly LocalizedChatMessages _localizedChatMessages;
|
||||||
|
|
||||||
|
public ChatService(ChatGui chatGui, TerritoryState territoryState, IPalacePalConfiguration configuration,
|
||||||
|
DataManager dataManager)
|
||||||
|
{
|
||||||
|
_chatGui = chatGui;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
_configuration = configuration;
|
||||||
|
_dataManager = dataManager;
|
||||||
|
|
||||||
|
_localizedChatMessages = LoadLanguageStrings();
|
||||||
|
|
||||||
|
_chatGui.ChatMessage += OnChatMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _chatGui.ChatMessage -= OnChatMessage;
|
||||||
|
|
||||||
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString seMessage,
|
||||||
|
ref bool isHandled)
|
||||||
|
{
|
||||||
|
if (_configuration.FirstUse)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (type != (XivChatType)2105)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string message = seMessage.ToString();
|
||||||
|
if (_localizedChatMessages.FloorChanged.IsMatch(message))
|
||||||
|
{
|
||||||
|
_territoryState.PomanderOfSight = PomanderState.Inactive;
|
||||||
|
|
||||||
|
if (_territoryState.PomanderOfIntuition == PomanderState.FoundOnCurrentFloor)
|
||||||
|
_territoryState.PomanderOfIntuition = PomanderState.Inactive;
|
||||||
|
}
|
||||||
|
else if (message.EndsWith(_localizedChatMessages.MapRevealed))
|
||||||
|
{
|
||||||
|
_territoryState.PomanderOfSight = PomanderState.Active;
|
||||||
|
}
|
||||||
|
else if (message.EndsWith(_localizedChatMessages.AllTrapsRemoved))
|
||||||
|
{
|
||||||
|
_territoryState.PomanderOfSight = PomanderState.PomanderOfSafetyUsed;
|
||||||
|
}
|
||||||
|
else if (message.EndsWith(_localizedChatMessages.HoardNotOnCurrentFloor) ||
|
||||||
|
message.EndsWith(_localizedChatMessages.HoardOnCurrentFloor))
|
||||||
|
{
|
||||||
|
// There is no functional difference between these - if you don't open the marked coffer,
|
||||||
|
// going to higher floors will keep the pomander active.
|
||||||
|
_territoryState.PomanderOfIntuition = PomanderState.Active;
|
||||||
|
}
|
||||||
|
else if (message.EndsWith(_localizedChatMessages.HoardCofferOpened))
|
||||||
|
{
|
||||||
|
_territoryState.PomanderOfIntuition = PomanderState.FoundOnCurrentFloor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalizedChatMessages LoadLanguageStrings()
|
||||||
|
{
|
||||||
|
return new LocalizedChatMessages
|
||||||
|
{
|
||||||
|
MapRevealed = GetLocalizedString(7256),
|
||||||
|
AllTrapsRemoved = GetLocalizedString(7255),
|
||||||
|
HoardOnCurrentFloor = GetLocalizedString(7272),
|
||||||
|
HoardNotOnCurrentFloor = GetLocalizedString(7273),
|
||||||
|
HoardCofferOpened = GetLocalizedString(7274),
|
||||||
|
FloorChanged =
|
||||||
|
new Regex("^" + GetLocalizedString(7270).Replace("\u0002 \u0003\ufffd\u0002\u0003", @"(\d+)") +
|
||||||
|
"$"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLocalizedString(uint id)
|
||||||
|
{
|
||||||
|
return _dataManager.GetExcelSheet<LogMessage>()?.GetRow(id)?.Text?.ToString() ?? "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalizedChatMessages
|
||||||
|
{
|
||||||
|
public string MapRevealed { get; init; } = "???"; //"The map for this floor has been revealed!";
|
||||||
|
public string AllTrapsRemoved { get; init; } = "???"; // "All the traps on this floor have disappeared!";
|
||||||
|
public string HoardOnCurrentFloor { get; init; } = "???"; // "You sense the Accursed Hoard calling you...";
|
||||||
|
|
||||||
|
public string HoardNotOnCurrentFloor { get; init; } =
|
||||||
|
"???"; // "You do not sense the call of the Accursed Hoard on this floor...";
|
||||||
|
|
||||||
|
public string HoardCofferOpened { get; init; } = "???"; // "You discover a piece of the Accursed Hoard!";
|
||||||
|
|
||||||
|
public Regex FloorChanged { get; init; } =
|
||||||
|
new(@"This isn't a game message, but will be replaced"); // new Regex(@"^Floor (\d+)$");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,21 @@
|
|||||||
using Dalamud.Data;
|
using System.Globalization;
|
||||||
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Pal.Client.Commands;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.Net;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
|
using Pal.Client.Rendering;
|
||||||
|
using Pal.Client.Scheduled;
|
||||||
|
using Pal.Client.Windows;
|
||||||
|
|
||||||
namespace Pal.Client.DependencyInjection
|
namespace Pal.Client.DependencyInjection
|
||||||
{
|
{
|
||||||
@ -35,6 +43,7 @@ namespace Pal.Client.DependencyInjection
|
|||||||
// dalamud
|
// dalamud
|
||||||
services.AddSingleton<IDalamudPlugin>(this);
|
services.AddSingleton<IDalamudPlugin>(this);
|
||||||
services.AddSingleton(pluginInterface);
|
services.AddSingleton(pluginInterface);
|
||||||
|
services.AddSingleton(clientState);
|
||||||
services.AddSingleton(gameGui);
|
services.AddSingleton(gameGui);
|
||||||
services.AddSingleton(chatGui);
|
services.AddSingleton(chatGui);
|
||||||
services.AddSingleton(objectTable);
|
services.AddSingleton(objectTable);
|
||||||
@ -42,9 +51,38 @@ namespace Pal.Client.DependencyInjection
|
|||||||
services.AddSingleton(condition);
|
services.AddSingleton(condition);
|
||||||
services.AddSingleton(commandManager);
|
services.AddSingleton(commandManager);
|
||||||
services.AddSingleton(dataManager);
|
services.AddSingleton(dataManager);
|
||||||
|
services.AddSingleton(new WindowSystem(typeof(DIPlugin).AssemblyQualifiedName));
|
||||||
|
|
||||||
// palace pal
|
// plugin-specific
|
||||||
services.AddSingleton<Plugin>();
|
services.AddSingleton<Plugin>();
|
||||||
|
services.AddSingleton<DebugState>();
|
||||||
|
services.AddSingleton<Hooks>();
|
||||||
|
services.AddSingleton<RemoteApi>();
|
||||||
|
services.AddSingleton<ConfigurationManager>();
|
||||||
|
services.AddSingleton<IPalacePalConfiguration>(sp => sp.GetRequiredService<ConfigurationManager>().Load());
|
||||||
|
services.AddTransient<RepoVerification>();
|
||||||
|
services.AddSingleton<PalCommand>();
|
||||||
|
|
||||||
|
// territory handling
|
||||||
|
services.AddSingleton<TerritoryState>();
|
||||||
|
services.AddSingleton<FrameworkService>();
|
||||||
|
services.AddSingleton<ChatService>();
|
||||||
|
services.AddSingleton<FloorService>();
|
||||||
|
services.AddSingleton<QueueHandler>();
|
||||||
|
|
||||||
|
// windows & related services
|
||||||
|
services.AddSingleton<AgreementWindow>();
|
||||||
|
services.AddSingleton<ConfigWindow>();
|
||||||
|
services.AddTransient<StatisticsService>();
|
||||||
|
services.AddSingleton<StatisticsWindow>();
|
||||||
|
|
||||||
|
// these should maybe be scoped
|
||||||
|
services.AddSingleton<SimpleRenderer>();
|
||||||
|
services.AddSingleton<SplatoonRenderer>();
|
||||||
|
services.AddSingleton<RenderAdapter>();
|
||||||
|
|
||||||
|
// set up the current UI language before creating anything
|
||||||
|
Localization.Culture = new CultureInfo(pluginInterface.UiLanguage);
|
||||||
|
|
||||||
// build
|
// build
|
||||||
_serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions
|
_serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions
|
||||||
@ -54,6 +92,24 @@ namespace Pal.Client.DependencyInjection
|
|||||||
});
|
});
|
||||||
|
|
||||||
// initialize plugin
|
// initialize plugin
|
||||||
|
#if RELEASE
|
||||||
|
// You're welcome to remove this code in your fork, but please make sure that:
|
||||||
|
// - none of the links accessible within FFXIV open the original repo (e.g. in the plugin installer), and
|
||||||
|
// - you host your own server instance
|
||||||
|
//
|
||||||
|
// This is mainly to avoid this plugin being included in 'mega-repos' that, for whatever reason, decide
|
||||||
|
// that collecting all plugins is a good idea (and break half in the process).
|
||||||
|
_serviceProvider.GetService<RepoVerification>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_serviceProvider.GetRequiredService<Hooks>();
|
||||||
|
_serviceProvider.GetRequiredService<AgreementWindow>();
|
||||||
|
_serviceProvider.GetRequiredService<ConfigWindow>();
|
||||||
|
_serviceProvider.GetRequiredService<StatisticsWindow>();
|
||||||
|
_serviceProvider.GetRequiredService<PalCommand>();
|
||||||
|
_serviceProvider.GetRequiredService<FrameworkService>();
|
||||||
|
_serviceProvider.GetRequiredService<ChatService>();
|
||||||
|
|
||||||
_serviceProvider.GetRequiredService<Plugin>();
|
_serviceProvider.GetRequiredService<Plugin>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +123,6 @@ namespace Pal.Client.DependencyInjection
|
|||||||
|
|
||||||
serviceProvider.Dispose();
|
serviceProvider.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
Pal.Client/DependencyInjection/DebugState.cs
Normal file
15
Pal.Client/DependencyInjection/DebugState.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
internal class DebugState
|
||||||
|
{
|
||||||
|
public string? DebugMessage { get; set; }
|
||||||
|
|
||||||
|
public void SetFromException(Exception e)
|
||||||
|
=> DebugMessage = $"{DateTime.Now}\n{e}";
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
=> DebugMessage = null;
|
||||||
|
}
|
||||||
|
}
|
15
Pal.Client/DependencyInjection/FloorService.cs
Normal file
15
Pal.Client/DependencyInjection/FloorService.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
internal sealed class FloorService
|
||||||
|
{
|
||||||
|
public ConcurrentDictionary<ushort, LocalState> FloorMarkers { get; } = new();
|
||||||
|
public ConcurrentBag<Marker> EphemeralMarkers { get; set; } = new();
|
||||||
|
|
||||||
|
public LocalState GetFloorMarkers(ushort territoryType)
|
||||||
|
{
|
||||||
|
return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
392
Pal.Client/DependencyInjection/FrameworkService.cs
Normal file
392
Pal.Client/DependencyInjection/FrameworkService.cs
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
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 ImGuiNET;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.Extensions;
|
||||||
|
using Pal.Client.Net;
|
||||||
|
using Pal.Client.Rendering;
|
||||||
|
using Pal.Client.Scheduled;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
internal class FrameworkService : IDisposable
|
||||||
|
{
|
||||||
|
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 QueueHandler _queueHandler;
|
||||||
|
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(Framework framework,
|
||||||
|
ConfigurationManager configurationManager,
|
||||||
|
IPalacePalConfiguration configuration,
|
||||||
|
ClientState clientState,
|
||||||
|
TerritoryState territoryState,
|
||||||
|
FloorService floorService,
|
||||||
|
DebugState debugState,
|
||||||
|
RenderAdapter renderAdapter,
|
||||||
|
QueueHandler queueHandler,
|
||||||
|
ObjectTable objectTable,
|
||||||
|
RemoteApi remoteApi)
|
||||||
|
{
|
||||||
|
_framework = framework;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
|
_configuration = configuration;
|
||||||
|
_clientState = clientState;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
_floorService = floorService;
|
||||||
|
_debugState = debugState;
|
||||||
|
_renderAdapter = renderAdapter;
|
||||||
|
_queueHandler = queueHandler;
|
||||||
|
_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;
|
||||||
|
bool saveMarkers = false;
|
||||||
|
|
||||||
|
while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
|
||||||
|
_queueHandler.Handle(queued, ref recreateLayout, ref saveMarkers);
|
||||||
|
|
||||||
|
if (_territoryState.LastTerritory != _clientState.TerritoryType)
|
||||||
|
{
|
||||||
|
_territoryState.LastTerritory = _clientState.TerritoryType;
|
||||||
|
_territoryState.TerritorySyncState = SyncState.NotAttempted;
|
||||||
|
NextUpdateObjects.Clear();
|
||||||
|
|
||||||
|
if (_territoryState.IsInDeepDungeon())
|
||||||
|
_floorService.GetFloorMarkers(_territoryState.LastTerritory);
|
||||||
|
_floorService.EphemeralMarkers.Clear();
|
||||||
|
_territoryState.PomanderOfSight = PomanderState.Inactive;
|
||||||
|
_territoryState.PomanderOfIntuition = PomanderState.Inactive;
|
||||||
|
recreateLayout = true;
|
||||||
|
_debugState.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_territoryState.IsInDeepDungeon())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_configuration.Mode == EMode.Online && _territoryState.TerritorySyncState == SyncState.NotAttempted)
|
||||||
|
{
|
||||||
|
_territoryState.TerritorySyncState = SyncState.Started;
|
||||||
|
Task.Run(async () => await DownloadMarkersForTerritory(_territoryState.LastTerritory));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
|
||||||
|
_queueHandler.Handle(queued, ref recreateLayout, ref saveMarkers);
|
||||||
|
|
||||||
|
var currentFloor = _floorService.GetFloorMarkers(_territoryState.LastTerritory);
|
||||||
|
|
||||||
|
IList<Marker> visibleMarkers = GetRelevantGameObjects();
|
||||||
|
HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout);
|
||||||
|
HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugState.SetFromException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Render Markers
|
||||||
|
private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers, bool recreateLayout)
|
||||||
|
{
|
||||||
|
var currentFloorMarkers = currentFloor.Markers;
|
||||||
|
|
||||||
|
bool updateSeenMarkers = false;
|
||||||
|
var partialAccountId = _configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
||||||
|
foreach (var visibleMarker in visibleMarkers)
|
||||||
|
{
|
||||||
|
Marker? knownMarker = currentFloorMarkers.SingleOrDefault(x => x == visibleMarker);
|
||||||
|
if (knownMarker != null)
|
||||||
|
{
|
||||||
|
if (!knownMarker.Seen)
|
||||||
|
{
|
||||||
|
knownMarker.Seen = true;
|
||||||
|
saveMarkers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states,
|
||||||
|
// markers discovered afterwards are automatically marked seen.
|
||||||
|
if (partialAccountId != null && knownMarker is { NetworkId: { }, RemoteSeenRequested: false } && !knownMarker.RemoteSeenOn.Contains(partialAccountId))
|
||||||
|
updateSeenMarkers = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFloorMarkers.Add(visibleMarker);
|
||||||
|
recreateLayout = true;
|
||||||
|
saveMarkers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recreateLayout && currentFloorMarkers.Count > 0 && (_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander || _configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander))
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var marker in currentFloorMarkers)
|
||||||
|
{
|
||||||
|
uint desiredColor = DetermineColor(marker, visibleMarkers);
|
||||||
|
if (marker.RenderElement == null || !marker.RenderElement.IsValid)
|
||||||
|
{
|
||||||
|
recreateLayout = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marker.RenderElement.Color != desiredColor)
|
||||||
|
marker.RenderElement.Color = desiredColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugState.SetFromException(e);
|
||||||
|
recreateLayout = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSeenMarkers && partialAccountId != null)
|
||||||
|
{
|
||||||
|
var markersToUpdate = currentFloorMarkers.Where(x => x is { Seen: true, NetworkId: { }, RemoteSeenRequested: false } && !x.RemoteSeenOn.Contains(partialAccountId)).ToList();
|
||||||
|
foreach (var marker in markersToUpdate)
|
||||||
|
marker.RemoteSeenRequested = true;
|
||||||
|
Task.Run(async () => await SyncSeenMarkersForTerritory(_territoryState.LastTerritory, markersToUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveMarkers)
|
||||||
|
{
|
||||||
|
currentFloor.Save();
|
||||||
|
|
||||||
|
if (_territoryState.TerritorySyncState == SyncState.Complete)
|
||||||
|
{
|
||||||
|
var markersToUpload = currentFloorMarkers.Where(x => x.IsPermanent() && x.NetworkId == null && !x.UploadRequested).ToList();
|
||||||
|
if (markersToUpload.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var marker in markersToUpload)
|
||||||
|
marker.UploadRequested = true;
|
||||||
|
Task.Run(async () => await UploadMarkersForTerritory(_territoryState.LastTerritory, markersToUpload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recreateLayout)
|
||||||
|
{
|
||||||
|
_renderAdapter.ResetLayer(ELayer.TrapHoard);
|
||||||
|
|
||||||
|
List<IRenderElement> elements = new();
|
||||||
|
foreach (var marker in currentFloorMarkers)
|
||||||
|
{
|
||||||
|
if (marker.Seen || _configuration.Mode == EMode.Online || marker is { WasImported: true, Imports.Count: > 0 })
|
||||||
|
{
|
||||||
|
if (marker.Type == Marker.EType.Trap)
|
||||||
|
{
|
||||||
|
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), _configuration.DeepDungeons.Traps);
|
||||||
|
}
|
||||||
|
else if (marker.Type == Marker.EType.Hoard)
|
||||||
|
{
|
||||||
|
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), _configuration.DeepDungeons.HoardCoffers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_renderAdapter.SetLayer(ELayer.TrapHoard, elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout)
|
||||||
|
{
|
||||||
|
recreateLayout |= _floorService.EphemeralMarkers.Any(existingMarker => visibleMarkers.All(x => x != existingMarker));
|
||||||
|
recreateLayout |= visibleMarkers.Any(visibleMarker => _floorService.EphemeralMarkers.All(x => x != visibleMarker));
|
||||||
|
|
||||||
|
if (recreateLayout)
|
||||||
|
{
|
||||||
|
_renderAdapter.ResetLayer(ELayer.RegularCoffers);
|
||||||
|
_floorService.EphemeralMarkers.Clear();
|
||||||
|
|
||||||
|
List<IRenderElement> elements = new();
|
||||||
|
foreach (var marker in visibleMarkers)
|
||||||
|
{
|
||||||
|
_floorService.EphemeralMarkers.Add(marker);
|
||||||
|
|
||||||
|
if (marker.Type == Marker.EType.SilverCoffer && _configuration.DeepDungeons.SilverCoffers.Show)
|
||||||
|
{
|
||||||
|
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), _configuration.DeepDungeons.SilverCoffers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_renderAdapter.SetLayer(ELayer.RegularCoffers, elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint DetermineColor(Marker marker, IList<Marker> visibleMarkers)
|
||||||
|
{
|
||||||
|
switch (marker.Type)
|
||||||
|
{
|
||||||
|
case Marker.EType.Trap when _territoryState.PomanderOfSight == PomanderState.Inactive || !_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander || visibleMarkers.Any(x => x == marker):
|
||||||
|
return _configuration.DeepDungeons.Traps.Color;
|
||||||
|
case Marker.EType.Hoard when _territoryState.PomanderOfIntuition == PomanderState.Inactive || !_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander || visibleMarkers.Any(x => x == marker):
|
||||||
|
return _configuration.DeepDungeons.HoardCoffers.Color;
|
||||||
|
case Marker.EType.SilverCoffer:
|
||||||
|
return _configuration.DeepDungeons.SilverCoffers.Color;
|
||||||
|
case Marker.EType.Trap:
|
||||||
|
case Marker.EType.Hoard:
|
||||||
|
return RenderData.ColorInvisible;
|
||||||
|
default:
|
||||||
|
return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color, MarkerConfiguration config)
|
||||||
|
{
|
||||||
|
if (!config.Show)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var element = _renderAdapter.CreateElement(marker.Type, marker.Position, color, config.Fill);
|
||||||
|
marker.RenderElement = element;
|
||||||
|
elements.Add(element);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Up-/Download
|
||||||
|
private async Task DownloadMarkersForTerritory(ushort territoryId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (success, downloadedMarkers) = await _remoteApi.DownloadRemoteMarkers(territoryId);
|
||||||
|
LateEventQueue.Enqueue(new QueuedSyncResponse
|
||||||
|
{
|
||||||
|
Type = SyncType.Download,
|
||||||
|
TerritoryType = territoryId,
|
||||||
|
Success = success,
|
||||||
|
Markers = downloadedMarkers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugState.SetFromException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UploadMarkersForTerritory(ushort territoryId, List<Marker> markersToUpload)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (success, uploadedMarkers) = await _remoteApi.UploadMarker(territoryId, markersToUpload);
|
||||||
|
LateEventQueue.Enqueue(new QueuedSyncResponse
|
||||||
|
{
|
||||||
|
Type = SyncType.Upload,
|
||||||
|
TerritoryType = territoryId,
|
||||||
|
Success = success,
|
||||||
|
Markers = uploadedMarkers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugState.SetFromException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SyncSeenMarkersForTerritory(ushort territoryId, List<Marker> markersToUpdate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var success = await _remoteApi.MarkAsSeen(territoryId, markersToUpdate);
|
||||||
|
LateEventQueue.Enqueue(new QueuedSyncResponse
|
||||||
|
{
|
||||||
|
Type = SyncType.MarkSeen,
|
||||||
|
TerritoryType = territoryId,
|
||||||
|
Success = success,
|
||||||
|
Markers = markersToUpdate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugState.SetFromException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private IList<Marker> GetRelevantGameObjects()
|
||||||
|
{
|
||||||
|
List<Marker> result = new();
|
||||||
|
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:
|
||||||
|
result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2007542:
|
||||||
|
case 2007543:
|
||||||
|
result.Add(new Marker(Marker.EType.Hoard, obj.Position) { Seen = true });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2007357:
|
||||||
|
result.Add(new Marker(Marker.EType.SilverCoffer, obj.Position) { Seen = true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (NextUpdateObjects.TryDequeue(out nint address))
|
||||||
|
{
|
||||||
|
var obj = _objectTable.FirstOrDefault(x => x.Address == address);
|
||||||
|
if (obj != null && obj.Position.Length() > 0.1)
|
||||||
|
result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
Pal.Client/DependencyInjection/RepoVerification.cs
Normal file
25
Pal.Client/DependencyInjection/RepoVerification.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Pal.Client.Extensions;
|
||||||
|
using Pal.Client.Properties;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
public class RepoVerification
|
||||||
|
{
|
||||||
|
public RepoVerification(DalamudPluginInterface pluginInterface, ChatGui chatGui)
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Install source: {pluginInterface.SourceRepository}");
|
||||||
|
if (!pluginInterface.IsDev
|
||||||
|
&& !pluginInterface.SourceRepository.StartsWith("https://raw.githubusercontent.com/carvelli/")
|
||||||
|
&& !pluginInterface.SourceRepository.StartsWith("https://github.com/carvelli/"))
|
||||||
|
{
|
||||||
|
chatGui.PalError(string.Format(Localization.Error_WrongRepository,
|
||||||
|
"https://github.com/carvelli/Dalamud-Plugins"));
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
Pal.Client/DependencyInjection/StatisticsService.cs
Normal file
65
Pal.Client/DependencyInjection/StatisticsService.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.Extensions;
|
||||||
|
using Pal.Client.Net;
|
||||||
|
using Pal.Client.Properties;
|
||||||
|
using Pal.Client.Windows;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
internal sealed class StatisticsService
|
||||||
|
{
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly RemoteApi _remoteApi;
|
||||||
|
private readonly StatisticsWindow _statisticsWindow;
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
|
||||||
|
public StatisticsService(IPalacePalConfiguration configuration, RemoteApi remoteApi,
|
||||||
|
StatisticsWindow statisticsWindow, ChatGui chatGui)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_remoteApi = remoteApi;
|
||||||
|
_statisticsWindow = statisticsWindow;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowGlobalStatistics()
|
||||||
|
{
|
||||||
|
Task.Run(async () => await FetchFloorStatistics());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchFloorStatistics()
|
||||||
|
{
|
||||||
|
if (!_configuration.HasRoleOnCurrentServer("statistics:view"))
|
||||||
|
{
|
||||||
|
_chatGui.PalError(Localization.Command_pal_stats_CurrentFloor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (success, floorStatistics) = await _remoteApi.FetchStatistics();
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_statisticsWindow.SetFloorData(floorStatistics);
|
||||||
|
_statisticsWindow.IsOpen = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chatGui.PalError(Localization.Command_pal_stats_UnableToFetchStatistics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (RpcException e) when (e.StatusCode == StatusCode.PermissionDenied)
|
||||||
|
{
|
||||||
|
_chatGui.Print(Localization.Command_pal_stats_CurrentFloor);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_chatGui.PalError(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
Pal.Client/DependencyInjection/TerritoryState.cs
Normal file
38
Pal.Client/DependencyInjection/TerritoryState.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Pal.Client.Scheduled;
|
||||||
|
using Pal.Common;
|
||||||
|
|
||||||
|
namespace Pal.Client.DependencyInjection
|
||||||
|
{
|
||||||
|
public sealed class TerritoryState
|
||||||
|
{
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
private readonly Condition _condition;
|
||||||
|
|
||||||
|
public TerritoryState(ClientState clientState, Condition condition)
|
||||||
|
{
|
||||||
|
_clientState = clientState;
|
||||||
|
_condition = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort LastTerritory { get; set; }
|
||||||
|
public SyncState TerritorySyncState { get; set; }
|
||||||
|
public PomanderState PomanderOfSight { get; set; } = PomanderState.Inactive;
|
||||||
|
public PomanderState PomanderOfIntuition { get; set; } = PomanderState.Inactive;
|
||||||
|
|
||||||
|
public bool IsInDeepDungeon() =>
|
||||||
|
_clientState.IsLoggedIn
|
||||||
|
&& _condition[ConditionFlag.InDeepDungeon]
|
||||||
|
&& typeof(ETerritoryType).IsEnumDefined(_clientState.TerritoryType);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PomanderState
|
||||||
|
{
|
||||||
|
Inactive,
|
||||||
|
Active,
|
||||||
|
FoundOnCurrentFloor,
|
||||||
|
PomanderOfSafetyUsed,
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,17 @@ using Dalamud.Memory;
|
|||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
|
||||||
namespace Pal.Client
|
namespace Pal.Client
|
||||||
{
|
{
|
||||||
internal unsafe class Hooks
|
internal unsafe class Hooks : IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly ObjectTable _objectTable;
|
||||||
|
private readonly TerritoryState _territoryState;
|
||||||
|
private readonly FrameworkService _frameworkService;
|
||||||
|
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
private delegate nint ActorVfxCreateDelegate(char* a1, nint a2, nint a3, float a4, char a5, ushort a6, char a7);
|
private delegate nint ActorVfxCreateDelegate(char* a1, nint a2, nint a3, float a4, char a5, ushort a6, char a7);
|
||||||
|
|
||||||
@ -17,8 +23,12 @@ namespace Pal.Client
|
|||||||
private Hook<ActorVfxCreateDelegate> ActorVfxCreateHook { get; init; } = null!;
|
private Hook<ActorVfxCreateDelegate> ActorVfxCreateHook { get; init; } = null!;
|
||||||
#pragma warning restore CS0649
|
#pragma warning restore CS0649
|
||||||
|
|
||||||
public Hooks()
|
public Hooks(ObjectTable objectTable, TerritoryState territoryState, FrameworkService frameworkService)
|
||||||
{
|
{
|
||||||
|
_objectTable = objectTable;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
_frameworkService = frameworkService;
|
||||||
|
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
ActorVfxCreateHook.Enable();
|
ActorVfxCreateHook.Enable();
|
||||||
}
|
}
|
||||||
@ -55,10 +65,10 @@ namespace Pal.Client
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Service.Plugin.IsInDeepDungeon())
|
if (_territoryState.IsInDeepDungeon())
|
||||||
{
|
{
|
||||||
var vfxPath = MemoryHelper.ReadString(new nint(a1), Encoding.ASCII, 256);
|
var vfxPath = MemoryHelper.ReadString(new nint(a1), Encoding.ASCII, 256);
|
||||||
var obj = Service.ObjectTable.CreateObjectReference(a2);
|
var obj = _objectTable.CreateObjectReference(a2);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (Service.Configuration.BetaKey == "VFX")
|
if (Service.Configuration.BetaKey == "VFX")
|
||||||
@ -69,7 +79,7 @@ namespace Pal.Client
|
|||||||
{
|
{
|
||||||
if (vfxPath == "vfx/common/eff/dk05th_stdn0t.avfx" || vfxPath == "vfx/common/eff/dk05ht_ipws0t.avfx")
|
if (vfxPath == "vfx/common/eff/dk05th_stdn0t.avfx" || vfxPath == "vfx/common/eff/dk05ht_ipws0t.avfx")
|
||||||
{
|
{
|
||||||
Service.Plugin.NextUpdateObjects.Enqueue(obj.Address);
|
_frameworkService.NextUpdateObjects.Enqueue(obj.Address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +93,7 @@ namespace Pal.Client
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ActorVfxCreateHook?.Dispose();
|
ActorVfxCreateHook.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace Pal.Client.Net
|
|||||||
{
|
{
|
||||||
private async Task<(bool Success, string Error)> TryConnect(CancellationToken cancellationToken, ILoggerFactory? loggerFactory = null, bool retry = true)
|
private async Task<(bool Success, string Error)> TryConnect(CancellationToken cancellationToken, ILoggerFactory? loggerFactory = null, bool retry = true)
|
||||||
{
|
{
|
||||||
if (Service.Configuration.Mode != EMode.Online)
|
if (_configuration.Mode != EMode.Online)
|
||||||
{
|
{
|
||||||
PluginLog.Debug("TryConnect: Not Online, not attempting to establish a connection");
|
PluginLog.Debug("TryConnect: Not Online, not attempting to establish a connection");
|
||||||
return (false, Localization.ConnectionError_NotOnline);
|
return (false, Localization.ConnectionError_NotOnline);
|
||||||
@ -47,7 +47,7 @@ namespace Pal.Client.Net
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var accountClient = new AccountService.AccountServiceClient(_channel);
|
var accountClient = new AccountService.AccountServiceClient(_channel);
|
||||||
IAccountConfiguration? configuredAccount = Service.Configuration.FindAccount(RemoteUrl);
|
IAccountConfiguration? configuredAccount = _configuration.FindAccount(RemoteUrl);
|
||||||
if (configuredAccount == null)
|
if (configuredAccount == null)
|
||||||
{
|
{
|
||||||
PluginLog.Information($"TryConnect: No account information saved for {RemoteUrl}, creating new account");
|
PluginLog.Information($"TryConnect: No account information saved for {RemoteUrl}, creating new account");
|
||||||
@ -57,17 +57,17 @@ namespace Pal.Client.Net
|
|||||||
if (!Guid.TryParse(createAccountReply.AccountId, out Guid accountId))
|
if (!Guid.TryParse(createAccountReply.AccountId, out Guid accountId))
|
||||||
throw new InvalidOperationException("invalid account id returned");
|
throw new InvalidOperationException("invalid account id returned");
|
||||||
|
|
||||||
configuredAccount = Service.Configuration.CreateAccount(RemoteUrl, accountId);
|
configuredAccount = _configuration.CreateAccount(RemoteUrl, accountId);
|
||||||
PluginLog.Information($"TryConnect: Account created with id {accountId.ToPartialId()}");
|
PluginLog.Information($"TryConnect: Account created with id {accountId.ToPartialId()}");
|
||||||
|
|
||||||
Service.ConfigurationManager.Save(Service.Configuration);
|
_configurationManager.Save(_configuration);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginLog.Error($"TryConnect: Account creation failed with error {createAccountReply.Error}");
|
PluginLog.Error($"TryConnect: Account creation failed with error {createAccountReply.Error}");
|
||||||
if (createAccountReply.Error == CreateAccountError.UpgradeRequired && !_warnedAboutUpgrade)
|
if (createAccountReply.Error == CreateAccountError.UpgradeRequired && !_warnedAboutUpgrade)
|
||||||
{
|
{
|
||||||
Service.Chat.PalError(Localization.ConnectionError_OldVersion);
|
_chatGui.PalError(Localization.ConnectionError_OldVersion);
|
||||||
_warnedAboutUpgrade = true;
|
_warnedAboutUpgrade = true;
|
||||||
}
|
}
|
||||||
return (false, string.Format(Localization.ConnectionError_CreateAccountFailed, createAccountReply.Error));
|
return (false, string.Format(Localization.ConnectionError_CreateAccountFailed, createAccountReply.Error));
|
||||||
@ -102,7 +102,7 @@ namespace Pal.Client.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (save)
|
if (save)
|
||||||
Service.ConfigurationManager.Save(Service.Configuration);
|
_configurationManager.Save(_configuration);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -110,8 +110,8 @@ namespace Pal.Client.Net
|
|||||||
_loginInfo = new LoginInfo(null);
|
_loginInfo = new LoginInfo(null);
|
||||||
if (loginReply.Error == LoginError.InvalidAccountId)
|
if (loginReply.Error == LoginError.InvalidAccountId)
|
||||||
{
|
{
|
||||||
Service.Configuration.RemoveAccount(RemoteUrl);
|
_configuration.RemoveAccount(RemoteUrl);
|
||||||
Service.ConfigurationManager.Save(Service.Configuration);
|
_configurationManager.Save(_configuration);
|
||||||
if (retry)
|
if (retry)
|
||||||
{
|
{
|
||||||
PluginLog.Information("TryConnect: Attempting connection retry without account id");
|
PluginLog.Information("TryConnect: Attempting connection retry without account id");
|
||||||
@ -122,7 +122,7 @@ namespace Pal.Client.Net
|
|||||||
}
|
}
|
||||||
if (loginReply.Error == LoginError.UpgradeRequired && !_warnedAboutUpgrade)
|
if (loginReply.Error == LoginError.UpgradeRequired && !_warnedAboutUpgrade)
|
||||||
{
|
{
|
||||||
Service.Chat.PalError(Localization.ConnectionError_OldVersion);
|
_chatGui.PalError(Localization.ConnectionError_OldVersion);
|
||||||
_warnedAboutUpgrade = true;
|
_warnedAboutUpgrade = true;
|
||||||
}
|
}
|
||||||
return (false, string.Format(Localization.ConnectionError_LoginFailed, loginReply.Error));
|
return (false, string.Format(Localization.ConnectionError_LoginFailed, loginReply.Error));
|
||||||
@ -161,7 +161,7 @@ namespace Pal.Client.Net
|
|||||||
return Localization.ConnectionSuccessful;
|
return Localization.ConnectionSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LoginInfo
|
internal sealed class LoginInfo
|
||||||
{
|
{
|
||||||
public LoginInfo(string? authToken)
|
public LoginInfo(string? authToken)
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -53,14 +53,5 @@ namespace Pal.Client.Net
|
|||||||
return null;
|
return null;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasRoleOnCurrentServer(string role)
|
|
||||||
{
|
|
||||||
if (Service.Configuration.Mode != Configuration.EMode.Online)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var account = Service.Configuration.FindAccount(RemoteUrl);
|
|
||||||
return account == null || account.CachedRoles.Contains(role);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,40 @@
|
|||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using Dalamud.Game.Gui;
|
||||||
using System.Linq;
|
|
||||||
using Pal.Client.Extensions;
|
|
||||||
using Pal.Client.Configuration;
|
using Pal.Client.Configuration;
|
||||||
|
|
||||||
namespace Pal.Client.Net
|
namespace Pal.Client.Net
|
||||||
{
|
{
|
||||||
internal partial class RemoteApi : IDisposable
|
internal sealed partial class RemoteApi : IDisposable
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public const string RemoteUrl = "http://localhost:5145";
|
public const string RemoteUrl = "http://localhost:5145";
|
||||||
#else
|
#else
|
||||||
public const string RemoteUrl = "https://pal.liza.sh";
|
public const string RemoteUrl = "https://pal.liza.sh";
|
||||||
#endif
|
#endif
|
||||||
private readonly string _userAgent = $"{typeof(RemoteApi).Assembly.GetName().Name?.Replace(" ", "")}/{typeof(RemoteApi).Assembly.GetName().Version?.ToString(2)}";
|
private readonly string _userAgent =
|
||||||
|
$"{typeof(RemoteApi).Assembly.GetName().Name?.Replace(" ", "")}/{typeof(RemoteApi).Assembly.GetName().Version?.ToString(2)}";
|
||||||
|
|
||||||
private readonly ILoggerFactory _grpcToPluginLogLoggerFactory = LoggerFactory.Create(builder => builder.AddProvider(new GrpcLoggerProvider()).AddFilter("Grpc", LogLevel.Trace));
|
private readonly ILoggerFactory _grpcToPluginLogLoggerFactory = LoggerFactory.Create(builder =>
|
||||||
|
builder.AddProvider(new GrpcLoggerProvider()).AddFilter("Grpc", LogLevel.Trace));
|
||||||
|
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
private readonly ConfigurationManager _configurationManager;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
|
||||||
private GrpcChannel? _channel;
|
private GrpcChannel? _channel;
|
||||||
private LoginInfo _loginInfo = new(null);
|
private LoginInfo _loginInfo = new(null);
|
||||||
private bool _warnedAboutUpgrade;
|
private bool _warnedAboutUpgrade;
|
||||||
|
|
||||||
|
public RemoteApi(ChatGui chatGui, ConfigurationManager configurationManager,
|
||||||
|
IPalacePalConfiguration configuration)
|
||||||
|
{
|
||||||
|
_chatGui = chatGui;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
PluginLog.Debug("Disposing gRPC channel");
|
PluginLog.Debug("Disposing gRPC channel");
|
||||||
|
@ -1,709 +1,87 @@
|
|||||||
using Dalamud.Game;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Game.Command;
|
|
||||||
using Dalamud.Game.Gui;
|
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Grpc.Core;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Pal.Client.Rendering;
|
using Pal.Client.Rendering;
|
||||||
using Pal.Client.Scheduled;
|
using Pal.Client.Scheduled;
|
||||||
using Pal.Client.Windows;
|
using Pal.Client.Windows;
|
||||||
using Pal.Common;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using Dalamud.Logging;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Pal.Client.Extensions;
|
using Pal.Client.Extensions;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
using ECommons;
|
using ECommons;
|
||||||
using ECommons.Schedulers;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Pal.Client.Configuration;
|
using Pal.Client.Configuration;
|
||||||
using Pal.Client.Net;
|
|
||||||
|
|
||||||
namespace Pal.Client
|
namespace Pal.Client
|
||||||
{
|
{
|
||||||
public class Plugin : IDisposable
|
internal sealed class Plugin : IDisposable
|
||||||
{
|
{
|
||||||
internal const uint ColorInvisible = 0;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IDalamudPlugin _dalamudPlugin;
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly RenderAdapter _renderAdapter;
|
||||||
|
|
||||||
private LocalizedChatMessages _localizedChatMessages = new();
|
public Plugin(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
internal ConcurrentDictionary<ushort, LocalState> FloorMarkers { get; } = new();
|
DalamudPluginInterface pluginInterface,
|
||||||
internal ConcurrentBag<Marker> EphemeralMarkers { get; set; } = new();
|
IPalacePalConfiguration configuration,
|
||||||
internal ushort LastTerritory { get; set; }
|
RenderAdapter renderAdapter)
|
||||||
internal SyncState TerritorySyncState { get; set; }
|
|
||||||
internal PomanderState PomanderOfSight { get; private set; } = PomanderState.Inactive;
|
|
||||||
internal PomanderState PomanderOfIntuition { get; private set; } = PomanderState.Inactive;
|
|
||||||
internal string? DebugMessage { get; set; }
|
|
||||||
internal Queue<IQueueOnFrameworkThread> EarlyEventQueue { get; } = new();
|
|
||||||
internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new();
|
|
||||||
internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new();
|
|
||||||
internal IRenderer Renderer { get; private set; } = null!;
|
|
||||||
|
|
||||||
public Plugin(DalamudPluginInterface pluginInterface, ChatGui chat, IDalamudPlugin dalamudPlugin)
|
|
||||||
{
|
{
|
||||||
_dalamudPlugin = dalamudPlugin;
|
PluginLog.Information("Initializing Palace Pal");
|
||||||
|
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_configuration = configuration;
|
||||||
|
_renderAdapter = renderAdapter;
|
||||||
|
|
||||||
|
// initialize legacy services
|
||||||
|
pluginInterface.Create<Service>();
|
||||||
|
Service.Configuration = configuration;
|
||||||
|
|
||||||
LanguageChanged(pluginInterface.UiLanguage);
|
LanguageChanged(pluginInterface.UiLanguage);
|
||||||
|
|
||||||
PluginLog.Information($"Install source: {pluginInterface.SourceRepository}");
|
|
||||||
|
|
||||||
#if RELEASE
|
|
||||||
// You're welcome to remove this code in your fork, as long as:
|
|
||||||
// - none of the links accessible within FFXIV open the original repo (e.g. in the plugin installer), and
|
|
||||||
// - you host your own server instance
|
|
||||||
if (!pluginInterface.IsDev
|
|
||||||
&& !pluginInterface.SourceRepository.StartsWith("https://raw.githubusercontent.com/carvelli/")
|
|
||||||
&& !pluginInterface.SourceRepository.StartsWith("https://github.com/carvelli/"))
|
|
||||||
{
|
|
||||||
chat.PalError(string.Format(Localization.Error_WrongRepository, "https://github.com/carvelli/Dalamud-Plugins"));
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pluginInterface.Create<Service>();
|
|
||||||
Service.Plugin = this;
|
|
||||||
|
|
||||||
Service.ConfigurationManager = new(pluginInterface);
|
|
||||||
Service.ConfigurationManager.Migrate();
|
|
||||||
Service.Configuration = Service.ConfigurationManager.Load();
|
|
||||||
|
|
||||||
ResetRenderer();
|
|
||||||
|
|
||||||
Service.Hooks = new Hooks();
|
|
||||||
|
|
||||||
var agreementWindow = pluginInterface.Create<AgreementWindow>();
|
|
||||||
if (agreementWindow is not null)
|
|
||||||
{
|
|
||||||
agreementWindow.IsOpen = Service.Configuration.FirstUse;
|
|
||||||
Service.WindowSystem.AddWindow(agreementWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
var configWindow = pluginInterface.Create<ConfigWindow>();
|
|
||||||
if (configWindow is not null)
|
|
||||||
{
|
|
||||||
Service.WindowSystem.AddWindow(configWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
var statisticsWindow = pluginInterface.Create<StatisticsWindow>();
|
|
||||||
if (statisticsWindow is not null)
|
|
||||||
{
|
|
||||||
Service.WindowSystem.AddWindow(statisticsWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginInterface.UiBuilder.Draw += Draw;
|
pluginInterface.UiBuilder.Draw += Draw;
|
||||||
pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
|
pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
|
||||||
pluginInterface.LanguageChanged += LanguageChanged;
|
pluginInterface.LanguageChanged += LanguageChanged;
|
||||||
Service.Framework.Update += OnFrameworkUpdate;
|
|
||||||
Service.Chat.ChatMessage += OnChatMessage;
|
|
||||||
Service.CommandManager.AddHandler("/pal", new CommandInfo(OnCommand)
|
|
||||||
{
|
|
||||||
HelpMessage = Localization.Command_pal_HelpText
|
|
||||||
});
|
|
||||||
|
|
||||||
ReloadLanguageStrings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenConfigUi()
|
private void OpenConfigUi()
|
||||||
{
|
{
|
||||||
Window? configWindow;
|
Window configWindow;
|
||||||
if (Service.Configuration.FirstUse)
|
if (_configuration.FirstUse)
|
||||||
configWindow = Service.WindowSystem.GetWindow<AgreementWindow>();
|
configWindow = _serviceProvider.GetRequiredService<AgreementWindow>();
|
||||||
else
|
else
|
||||||
configWindow = Service.WindowSystem.GetWindow<ConfigWindow>();
|
configWindow = _serviceProvider.GetRequiredService<ConfigWindow>();
|
||||||
|
|
||||||
if (configWindow != null)
|
|
||||||
configWindow.IsOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCommand(string command, string arguments)
|
|
||||||
{
|
|
||||||
if (Service.Configuration.FirstUse)
|
|
||||||
{
|
|
||||||
Service.Chat.PalError(Localization.Error_FirstTimeSetupRequired);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
arguments = arguments.Trim();
|
|
||||||
switch (arguments)
|
|
||||||
{
|
|
||||||
case "stats":
|
|
||||||
Task.Run(async () => await FetchFloorStatistics());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "test-connection":
|
|
||||||
case "tc":
|
|
||||||
var configWindow = Service.WindowSystem.GetWindow<ConfigWindow>();
|
|
||||||
if (configWindow == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
configWindow.IsOpen = true;
|
configWindow.IsOpen = true;
|
||||||
var _ = new TickScheduler(() => configWindow.TestConnection());
|
|
||||||
break;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
case "update-saves":
|
|
||||||
LocalState.UpdateAll();
|
|
||||||
Service.Chat.Print(Localization.Command_pal_updatesaves);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case "":
|
|
||||||
case "config":
|
|
||||||
Service.WindowSystem.GetWindow<ConfigWindow>()?.Toggle();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "near":
|
|
||||||
DebugNearest(_ => true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tnear":
|
|
||||||
DebugNearest(m => m.Type == Marker.EType.Trap);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "hnear":
|
|
||||||
DebugNearest(m => m.Type == Marker.EType.Hoard);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Service.Chat.PalError(string.Format(Localization.Command_pal_UnknownSubcommand, arguments, command));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Service.Chat.PalError(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable Support
|
#region IDisposable Support
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposing) return;
|
|
||||||
|
|
||||||
Service.CommandManager.RemoveHandler("/pal");
|
|
||||||
Service.PluginInterface.UiBuilder.Draw -= Draw;
|
|
||||||
Service.PluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
|
|
||||||
Service.PluginInterface.LanguageChanged -= LanguageChanged;
|
|
||||||
Service.Framework.Update -= OnFrameworkUpdate;
|
|
||||||
Service.Chat.ChatMessage -= OnChatMessage;
|
|
||||||
|
|
||||||
Service.WindowSystem.GetWindow<ConfigWindow>()?.Dispose();
|
|
||||||
Service.WindowSystem.RemoveAllWindows();
|
|
||||||
|
|
||||||
Service.RemoteApi.Dispose();
|
|
||||||
Service.Hooks.Dispose();
|
|
||||||
|
|
||||||
if (Renderer is IDisposable disposable)
|
|
||||||
disposable.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
_pluginInterface.UiBuilder.Draw -= Draw;
|
||||||
GC.SuppressFinalize(this);
|
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
|
||||||
|
_pluginInterface.LanguageChanged -= LanguageChanged;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString seMessage, ref bool isHandled)
|
private void LanguageChanged(string languageCode)
|
||||||
{
|
{
|
||||||
if (Service.Configuration.FirstUse)
|
Localization.Culture = new CultureInfo(languageCode);
|
||||||
return;
|
_serviceProvider.GetRequiredService<WindowSystem>().Windows.OfType<ILanguageChanged>().Each(w => w.LanguageChanged());
|
||||||
|
|
||||||
if (type != (XivChatType)2105)
|
|
||||||
return;
|
|
||||||
|
|
||||||
string message = seMessage.ToString();
|
|
||||||
if (_localizedChatMessages.FloorChanged.IsMatch(message))
|
|
||||||
{
|
|
||||||
PomanderOfSight = PomanderState.Inactive;
|
|
||||||
|
|
||||||
if (PomanderOfIntuition == PomanderState.FoundOnCurrentFloor)
|
|
||||||
PomanderOfIntuition = PomanderState.Inactive;
|
|
||||||
}
|
|
||||||
else if (message.EndsWith(_localizedChatMessages.MapRevealed))
|
|
||||||
{
|
|
||||||
PomanderOfSight = PomanderState.Active;
|
|
||||||
}
|
|
||||||
else if (message.EndsWith(_localizedChatMessages.AllTrapsRemoved))
|
|
||||||
{
|
|
||||||
PomanderOfSight = PomanderState.PomanderOfSafetyUsed;
|
|
||||||
}
|
|
||||||
else if (message.EndsWith(_localizedChatMessages.HoardNotOnCurrentFloor) || message.EndsWith(_localizedChatMessages.HoardOnCurrentFloor))
|
|
||||||
{
|
|
||||||
// There is no functional difference between these - if you don't open the marked coffer,
|
|
||||||
// going to higher floors will keep the pomander active.
|
|
||||||
PomanderOfIntuition = PomanderState.Active;
|
|
||||||
}
|
|
||||||
else if (message.EndsWith(_localizedChatMessages.HoardCofferOpened))
|
|
||||||
{
|
|
||||||
PomanderOfIntuition = PomanderState.FoundOnCurrentFloor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LanguageChanged(string langcode)
|
|
||||||
{
|
|
||||||
Localization.Culture = new CultureInfo(langcode);
|
|
||||||
Service.WindowSystem.Windows.OfType<ILanguageChanged>().Each(w => w.LanguageChanged());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFrameworkUpdate(Framework framework)
|
|
||||||
{
|
|
||||||
if (Service.Configuration.FirstUse)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool recreateLayout = false;
|
|
||||||
bool saveMarkers = false;
|
|
||||||
|
|
||||||
while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
|
|
||||||
queued.Run(this, ref recreateLayout, ref saveMarkers);
|
|
||||||
|
|
||||||
if (LastTerritory != Service.ClientState.TerritoryType)
|
|
||||||
{
|
|
||||||
LastTerritory = Service.ClientState.TerritoryType;
|
|
||||||
TerritorySyncState = SyncState.NotAttempted;
|
|
||||||
NextUpdateObjects.Clear();
|
|
||||||
|
|
||||||
if (IsInDeepDungeon())
|
|
||||||
GetFloorMarkers(LastTerritory);
|
|
||||||
EphemeralMarkers.Clear();
|
|
||||||
PomanderOfSight = PomanderState.Inactive;
|
|
||||||
PomanderOfIntuition = PomanderState.Inactive;
|
|
||||||
recreateLayout = true;
|
|
||||||
DebugMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsInDeepDungeon())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Service.Configuration.Mode == Configuration.EMode.Online && TerritorySyncState == SyncState.NotAttempted)
|
|
||||||
{
|
|
||||||
TerritorySyncState = SyncState.Started;
|
|
||||||
Task.Run(async () => await DownloadMarkersForTerritory(LastTerritory));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
|
|
||||||
queued.Run(this, ref recreateLayout, ref saveMarkers);
|
|
||||||
|
|
||||||
var currentFloor = GetFloorMarkers(LastTerritory);
|
|
||||||
|
|
||||||
IList<Marker> visibleMarkers = GetRelevantGameObjects();
|
|
||||||
HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout);
|
|
||||||
HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugMessage = $"{DateTime.Now}\n{e}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal LocalState GetFloorMarkers(ushort territoryType)
|
|
||||||
{
|
|
||||||
return FloorMarkers.GetOrAdd(territoryType, tt => LocalState.Load(tt) ?? new LocalState(tt));
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Rendering markers
|
|
||||||
private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers, bool recreateLayout)
|
|
||||||
{
|
|
||||||
var config = Service.Configuration;
|
|
||||||
var currentFloorMarkers = currentFloor.Markers;
|
|
||||||
|
|
||||||
bool updateSeenMarkers = false;
|
|
||||||
var partialAccountId = Service.Configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
|
||||||
foreach (var visibleMarker in visibleMarkers)
|
|
||||||
{
|
|
||||||
Marker? knownMarker = currentFloorMarkers.SingleOrDefault(x => x == visibleMarker);
|
|
||||||
if (knownMarker != null)
|
|
||||||
{
|
|
||||||
if (!knownMarker.Seen)
|
|
||||||
{
|
|
||||||
knownMarker.Seen = true;
|
|
||||||
saveMarkers = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states,
|
|
||||||
// markers discovered afterwards are automatically marked seen.
|
|
||||||
if (partialAccountId != null && knownMarker is { NetworkId: { }, RemoteSeenRequested: false } && !knownMarker.RemoteSeenOn.Contains(partialAccountId))
|
|
||||||
updateSeenMarkers = true;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFloorMarkers.Add(visibleMarker);
|
|
||||||
recreateLayout = true;
|
|
||||||
saveMarkers = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!recreateLayout && currentFloorMarkers.Count > 0 && (config.DeepDungeons.Traps.OnlyVisibleAfterPomander || config.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander))
|
|
||||||
{
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var marker in currentFloorMarkers)
|
|
||||||
{
|
|
||||||
uint desiredColor = DetermineColor(marker, visibleMarkers);
|
|
||||||
if (marker.RenderElement == null || !marker.RenderElement.IsValid)
|
|
||||||
{
|
|
||||||
recreateLayout = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (marker.RenderElement.Color != desiredColor)
|
|
||||||
marker.RenderElement.Color = desiredColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugMessage = $"{DateTime.Now}\n{e}";
|
|
||||||
recreateLayout = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateSeenMarkers && partialAccountId != null)
|
|
||||||
{
|
|
||||||
var markersToUpdate = currentFloorMarkers.Where(x => x is { Seen: true, NetworkId: { }, RemoteSeenRequested: false } && !x.RemoteSeenOn.Contains(partialAccountId)).ToList();
|
|
||||||
foreach (var marker in markersToUpdate)
|
|
||||||
marker.RemoteSeenRequested = true;
|
|
||||||
Task.Run(async () => await SyncSeenMarkersForTerritory(LastTerritory, markersToUpdate));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveMarkers)
|
|
||||||
{
|
|
||||||
currentFloor.Save();
|
|
||||||
|
|
||||||
if (TerritorySyncState == SyncState.Complete)
|
|
||||||
{
|
|
||||||
var markersToUpload = currentFloorMarkers.Where(x => x.IsPermanent() && x.NetworkId == null && !x.UploadRequested).ToList();
|
|
||||||
if (markersToUpload.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var marker in markersToUpload)
|
|
||||||
marker.UploadRequested = true;
|
|
||||||
Task.Run(async () => await UploadMarkersForTerritory(LastTerritory, markersToUpload));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recreateLayout)
|
|
||||||
{
|
|
||||||
Renderer.ResetLayer(ELayer.TrapHoard);
|
|
||||||
|
|
||||||
List<IRenderElement> elements = new();
|
|
||||||
foreach (var marker in currentFloorMarkers)
|
|
||||||
{
|
|
||||||
if (marker.Seen || config.Mode == EMode.Online || marker is { WasImported: true, Imports.Count: > 0 })
|
|
||||||
{
|
|
||||||
if (marker.Type == Marker.EType.Trap)
|
|
||||||
{
|
|
||||||
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.DeepDungeons.Traps);
|
|
||||||
}
|
|
||||||
else if (marker.Type == Marker.EType.Hoard)
|
|
||||||
{
|
|
||||||
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.DeepDungeons.HoardCoffers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elements.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Renderer.SetLayer(ELayer.TrapHoard, elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout)
|
|
||||||
{
|
|
||||||
recreateLayout |= EphemeralMarkers.Any(existingMarker => visibleMarkers.All(x => x != existingMarker));
|
|
||||||
recreateLayout |= visibleMarkers.Any(visibleMarker => EphemeralMarkers.All(x => x != visibleMarker));
|
|
||||||
|
|
||||||
if (recreateLayout)
|
|
||||||
{
|
|
||||||
Renderer.ResetLayer(ELayer.RegularCoffers);
|
|
||||||
EphemeralMarkers.Clear();
|
|
||||||
|
|
||||||
var config = Service.Configuration;
|
|
||||||
|
|
||||||
List<IRenderElement> elements = new();
|
|
||||||
foreach (var marker in visibleMarkers)
|
|
||||||
{
|
|
||||||
EphemeralMarkers.Add(marker);
|
|
||||||
|
|
||||||
if (marker.Type == Marker.EType.SilverCoffer && config.DeepDungeons.SilverCoffers.Show)
|
|
||||||
{
|
|
||||||
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.DeepDungeons.SilverCoffers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elements.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Renderer.SetLayer(ELayer.RegularCoffers, elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private uint DetermineColor(Marker marker, IList<Marker> visibleMarkers)
|
|
||||||
{
|
|
||||||
switch (marker.Type)
|
|
||||||
{
|
|
||||||
case Marker.EType.Trap when PomanderOfSight == PomanderState.Inactive || !Service.Configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander || visibleMarkers.Any(x => x == marker):
|
|
||||||
return Service.Configuration.DeepDungeons.Traps.Color;
|
|
||||||
case Marker.EType.Hoard when PomanderOfIntuition == PomanderState.Inactive || !Service.Configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander || visibleMarkers.Any(x => x == marker):
|
|
||||||
return Service.Configuration.DeepDungeons.HoardCoffers.Color;
|
|
||||||
case Marker.EType.SilverCoffer:
|
|
||||||
return Service.Configuration.DeepDungeons.SilverCoffers.Color;
|
|
||||||
case Marker.EType.Trap:
|
|
||||||
case Marker.EType.Hoard:
|
|
||||||
return ColorInvisible;
|
|
||||||
default:
|
|
||||||
return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color, MarkerConfiguration config)
|
|
||||||
{
|
|
||||||
if (!config.Show)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var element = Renderer.CreateElement(marker.Type, marker.Position, color, config.Fill);
|
|
||||||
marker.RenderElement = element;
|
|
||||||
elements.Add(element);
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Up-/Download
|
|
||||||
private async Task DownloadMarkersForTerritory(ushort territoryId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (success, downloadedMarkers) = await Service.RemoteApi.DownloadRemoteMarkers(territoryId);
|
|
||||||
LateEventQueue.Enqueue(new QueuedSyncResponse
|
|
||||||
{
|
|
||||||
Type = SyncType.Download,
|
|
||||||
TerritoryType = territoryId,
|
|
||||||
Success = success,
|
|
||||||
Markers = downloadedMarkers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugMessage = $"{DateTime.Now}\n{e}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UploadMarkersForTerritory(ushort territoryId, List<Marker> markersToUpload)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (success, uploadedMarkers) = await Service.RemoteApi.UploadMarker(territoryId, markersToUpload);
|
|
||||||
LateEventQueue.Enqueue(new QueuedSyncResponse
|
|
||||||
{
|
|
||||||
Type = SyncType.Upload,
|
|
||||||
TerritoryType = territoryId,
|
|
||||||
Success = success,
|
|
||||||
Markers = uploadedMarkers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugMessage = $"{DateTime.Now}\n{e}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SyncSeenMarkersForTerritory(ushort territoryId, List<Marker> markersToUpdate)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var success = await Service.RemoteApi.MarkAsSeen(territoryId, markersToUpdate);
|
|
||||||
LateEventQueue.Enqueue(new QueuedSyncResponse
|
|
||||||
{
|
|
||||||
Type = SyncType.MarkSeen,
|
|
||||||
TerritoryType = territoryId,
|
|
||||||
Success = success,
|
|
||||||
Markers = markersToUpdate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugMessage = $"{DateTime.Now}\n{e}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Command Handling
|
|
||||||
private async Task FetchFloorStatistics()
|
|
||||||
{
|
|
||||||
if (!Service.RemoteApi.HasRoleOnCurrentServer("statistics:view"))
|
|
||||||
{
|
|
||||||
Service.Chat.PalError(Localization.Command_pal_stats_CurrentFloor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (success, floorStatistics) = await Service.RemoteApi.FetchStatistics();
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
var statisticsWindow = Service.WindowSystem.GetWindow<StatisticsWindow>()!;
|
|
||||||
statisticsWindow.SetFloorData(floorStatistics);
|
|
||||||
statisticsWindow.IsOpen = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Service.Chat.PalError(Localization.Command_pal_stats_UnableToFetchStatistics);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (RpcException e) when (e.StatusCode == StatusCode.PermissionDenied)
|
|
||||||
{
|
|
||||||
Service.Chat.Print(Localization.Command_pal_stats_CurrentFloor);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Service.Chat.PalError(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DebugNearest(Predicate<Marker> predicate)
|
|
||||||
{
|
|
||||||
if (!IsInDeepDungeon())
|
|
||||||
return;
|
|
||||||
|
|
||||||
var state = GetFloorMarkers(Service.ClientState.TerritoryType);
|
|
||||||
var playerPosition = Service.ClientState.LocalPlayer?.Position;
|
|
||||||
if (playerPosition == null)
|
|
||||||
return;
|
|
||||||
Service.Chat.Print($"[Palace Pal] {playerPosition}");
|
|
||||||
|
|
||||||
var nearbyMarkers = state.Markers
|
|
||||||
.Where(m => predicate(m))
|
|
||||||
.Where(m => m.RenderElement != null && m.RenderElement.Color != ColorInvisible)
|
|
||||||
.Select(m => new { m, distance = (playerPosition - m.Position)?.Length() ?? float.MaxValue })
|
|
||||||
.OrderBy(m => m.distance)
|
|
||||||
.Take(5)
|
|
||||||
.ToList();
|
|
||||||
foreach (var nearbyMarker in nearbyMarkers)
|
|
||||||
Service.Chat.Print($"{nearbyMarker.distance:F2} - {nearbyMarker.m.Type} {nearbyMarker.m.NetworkId?.ToPartialId(length: 8)} - {nearbyMarker.m.Position}");
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private IList<Marker> GetRelevantGameObjects()
|
|
||||||
{
|
|
||||||
List<Marker> result = new();
|
|
||||||
for (int i = 246; i < Service.ObjectTable.Length; i++)
|
|
||||||
{
|
|
||||||
GameObject? obj = Service.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:
|
|
||||||
result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2007542:
|
|
||||||
case 2007543:
|
|
||||||
result.Add(new Marker(Marker.EType.Hoard, obj.Position) { Seen = true });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2007357:
|
|
||||||
result.Add(new Marker(Marker.EType.SilverCoffer, obj.Position) { Seen = true });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (NextUpdateObjects.TryDequeue(out nint address))
|
|
||||||
{
|
|
||||||
var obj = Service.ObjectTable.FirstOrDefault(x => x.Address == address);
|
|
||||||
if (obj != null && obj.Position.Length() > 0.1)
|
|
||||||
result.Add(new Marker(Marker.EType.Trap, obj.Position) { Seen = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsInDeepDungeon() =>
|
|
||||||
Service.ClientState.IsLoggedIn
|
|
||||||
&& Service.Condition[ConditionFlag.InDeepDungeon]
|
|
||||||
&& typeof(ETerritoryType).IsEnumDefined(Service.ClientState.TerritoryType);
|
|
||||||
|
|
||||||
private void ReloadLanguageStrings()
|
|
||||||
{
|
|
||||||
_localizedChatMessages = new LocalizedChatMessages
|
|
||||||
{
|
|
||||||
MapRevealed = GetLocalizedString(7256),
|
|
||||||
AllTrapsRemoved = GetLocalizedString(7255),
|
|
||||||
HoardOnCurrentFloor = GetLocalizedString(7272),
|
|
||||||
HoardNotOnCurrentFloor = GetLocalizedString(7273),
|
|
||||||
HoardCofferOpened = GetLocalizedString(7274),
|
|
||||||
FloorChanged = new Regex("^" + GetLocalizedString(7270).Replace("\u0002 \u0003\ufffd\u0002\u0003", @"(\d+)") + "$"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ResetRenderer()
|
|
||||||
{
|
|
||||||
if (Renderer is SplatoonRenderer && Service.Configuration.Renderer.SelectedRenderer == ERenderer.Splatoon)
|
|
||||||
return;
|
|
||||||
else if (Renderer is SimpleRenderer && Service.Configuration.Renderer.SelectedRenderer == ERenderer.Simple)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Renderer is IDisposable disposable)
|
|
||||||
disposable.Dispose();
|
|
||||||
|
|
||||||
if (Service.Configuration.Renderer.SelectedRenderer == ERenderer.Splatoon)
|
|
||||||
Renderer = new SplatoonRenderer(Service.PluginInterface, _dalamudPlugin);
|
|
||||||
else
|
|
||||||
Renderer = new SimpleRenderer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Draw()
|
private void Draw()
|
||||||
{
|
{
|
||||||
if (Renderer is SimpleRenderer sr)
|
if (_renderAdapter.Implementation is SimpleRenderer sr)
|
||||||
sr.DrawLayers();
|
sr.DrawLayers();
|
||||||
|
|
||||||
Service.WindowSystem.Draw();
|
_serviceProvider.GetRequiredService<WindowSystem>().Draw();
|
||||||
}
|
|
||||||
|
|
||||||
private string GetLocalizedString(uint id)
|
|
||||||
{
|
|
||||||
return Service.DataManager.GetExcelSheet<LogMessage>()?.GetRow(id)?.Text?.ToString() ?? "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PomanderState
|
|
||||||
{
|
|
||||||
Inactive,
|
|
||||||
Active,
|
|
||||||
FoundOnCurrentFloor,
|
|
||||||
PomanderOfSafetyUsed,
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LocalizedChatMessages
|
|
||||||
{
|
|
||||||
public string MapRevealed { get; init; } = "???"; //"The map for this floor has been revealed!";
|
|
||||||
public string AllTrapsRemoved { get; init; } = "???"; // "All the traps on this floor have disappeared!";
|
|
||||||
public string HoardOnCurrentFloor { get; init; } = "???"; // "You sense the Accursed Hoard calling you...";
|
|
||||||
public string HoardNotOnCurrentFloor { get; init; } = "???"; // "You do not sense the call of the Accursed Hoard on this floor...";
|
|
||||||
public string HoardCofferOpened { get; init; } = "???"; // "You discover a piece of the Accursed Hoard!";
|
|
||||||
public Regex FloorChanged { get; init; } = new(@"This isn't a game message, but will be replaced"); // new Regex(@"^Floor (\d+)$");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Pal.Client.Rendering
|
namespace Pal.Client.Rendering
|
||||||
{
|
{
|
||||||
internal class MarkerConfig
|
internal sealed class MarkerConfig
|
||||||
{
|
{
|
||||||
private static readonly MarkerConfig EmptyConfig = new();
|
private static readonly MarkerConfig EmptyConfig = new();
|
||||||
private static readonly Dictionary<Marker.EType, MarkerConfig> MarkerConfigs = new()
|
private static readonly Dictionary<Marker.EType, MarkerConfig> MarkerConfigs = new()
|
||||||
@ -12,8 +12,8 @@ namespace Pal.Client.Rendering
|
|||||||
{ Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } },
|
{ Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } },
|
||||||
};
|
};
|
||||||
|
|
||||||
public float OffsetY { get; set; }
|
public float OffsetY { get; private init; }
|
||||||
public float Radius { get; set; } = 0.25f;
|
public float Radius { get; private init; } = 0.25f;
|
||||||
|
|
||||||
public static MarkerConfig ForType(Marker.EType type) => MarkerConfigs.GetValueOrDefault(type, EmptyConfig);
|
public static MarkerConfig ForType(Marker.EType type) => MarkerConfigs.GetValueOrDefault(type, EmptyConfig);
|
||||||
}
|
}
|
||||||
|
33
Pal.Client/Rendering/RenderAdapter.cs
Normal file
33
Pal.Client/Rendering/RenderAdapter.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
|
||||||
|
namespace Pal.Client.Rendering
|
||||||
|
{
|
||||||
|
internal sealed class RenderAdapter : IRenderer
|
||||||
|
{
|
||||||
|
private readonly SimpleRenderer _simpleRenderer;
|
||||||
|
private readonly SplatoonRenderer _splatoonRenderer;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
|
||||||
|
public RenderAdapter(SimpleRenderer simpleRenderer, SplatoonRenderer splatoonRenderer, IPalacePalConfiguration configuration)
|
||||||
|
{
|
||||||
|
_simpleRenderer = simpleRenderer;
|
||||||
|
_splatoonRenderer = splatoonRenderer;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRenderer Implementation => _configuration.Renderer.SelectedRenderer == ERenderer.Splatoon
|
||||||
|
? _splatoonRenderer
|
||||||
|
: _simpleRenderer;
|
||||||
|
|
||||||
|
public void SetLayer(ELayer layer, IReadOnlyList<IRenderElement> elements)
|
||||||
|
=> Implementation.SetLayer(layer, elements);
|
||||||
|
|
||||||
|
public void ResetLayer(ELayer layer)
|
||||||
|
=> Implementation.ResetLayer(layer);
|
||||||
|
|
||||||
|
public IRenderElement CreateElement(Marker.EType type, Vector3 pos, uint color, bool fill = false)
|
||||||
|
=> Implementation.CreateElement(type, pos, color, fill);
|
||||||
|
}
|
||||||
|
}
|
7
Pal.Client/Rendering/RenderData.cs
Normal file
7
Pal.Client/Rendering/RenderData.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Pal.Client.Rendering
|
||||||
|
{
|
||||||
|
internal static class RenderData
|
||||||
|
{
|
||||||
|
public static readonly uint ColorInvisible = 0;
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,14 @@
|
|||||||
using Dalamud.Game.Gui;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using ECommons.ExcelServices.TerritoryEnumeration;
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Xml.Linq;
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
|
||||||
namespace Pal.Client.Rendering
|
namespace Pal.Client.Rendering
|
||||||
{
|
{
|
||||||
@ -20,15 +19,30 @@ namespace Pal.Client.Rendering
|
|||||||
/// remade into PalacePal (which is the third or fourth iteration on the same idea
|
/// remade into PalacePal (which is the third or fourth iteration on the same idea
|
||||||
/// I made, just with a clear vision).
|
/// I made, just with a clear vision).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SimpleRenderer : IRenderer, IDisposable
|
internal sealed class SimpleRenderer : IRenderer, IDisposable
|
||||||
{
|
{
|
||||||
|
private const int SegmentCount = 20;
|
||||||
|
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
private readonly GameGui _gameGui;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly TerritoryState _territoryState;
|
||||||
private readonly ConcurrentDictionary<ELayer, SimpleLayer> _layers = new();
|
private readonly ConcurrentDictionary<ELayer, SimpleLayer> _layers = new();
|
||||||
|
|
||||||
|
public SimpleRenderer(ClientState clientState, GameGui gameGui, IPalacePalConfiguration configuration,
|
||||||
|
TerritoryState territoryState)
|
||||||
|
{
|
||||||
|
_clientState = clientState;
|
||||||
|
_gameGui = gameGui;
|
||||||
|
_configuration = configuration;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetLayer(ELayer layer, IReadOnlyList<IRenderElement> elements)
|
public void SetLayer(ELayer layer, IReadOnlyList<IRenderElement> elements)
|
||||||
{
|
{
|
||||||
_layers[layer] = new SimpleLayer
|
_layers[layer] = new SimpleLayer
|
||||||
{
|
{
|
||||||
TerritoryType = Service.ClientState.TerritoryType,
|
TerritoryType = _clientState.TerritoryType,
|
||||||
Elements = elements.Cast<SimpleElement>().ToList()
|
Elements = elements.Cast<SimpleElement>().ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -61,38 +75,88 @@ namespace Pal.Client.Rendering
|
|||||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||||
ImGuiHelpers.SetNextWindowPosRelativeMainViewport(Vector2.Zero, ImGuiCond.None, Vector2.Zero);
|
ImGuiHelpers.SetNextWindowPosRelativeMainViewport(Vector2.Zero, ImGuiCond.None, Vector2.Zero);
|
||||||
ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size);
|
ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size);
|
||||||
if (ImGui.Begin("###PalacePalSimpleRender", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysUseWindowPadding))
|
if (ImGui.Begin("###PalacePalSimpleRender",
|
||||||
|
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground |
|
||||||
|
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings |
|
||||||
|
ImGuiWindowFlags.AlwaysUseWindowPadding))
|
||||||
{
|
{
|
||||||
ushort territoryType = Service.ClientState.TerritoryType;
|
ushort territoryType = _clientState.TerritoryType;
|
||||||
|
|
||||||
foreach (var layer in _layers.Values.Where(l => l.TerritoryType == territoryType))
|
foreach (var layer in _layers.Values.Where(l => l.TerritoryType == territoryType))
|
||||||
layer.Draw();
|
{
|
||||||
|
foreach (var e in layer.Elements)
|
||||||
|
Draw(e);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var key in _layers.Where(l => l.Value.TerritoryType != territoryType).Select(l => l.Key).ToList())
|
foreach (var key in _layers.Where(l => l.Value.TerritoryType != territoryType).Select(l => l.Key)
|
||||||
|
.ToList())
|
||||||
ResetLayer(key);
|
ResetLayer(key);
|
||||||
|
|
||||||
ImGui.End();
|
ImGui.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Draw(SimpleElement e)
|
||||||
|
{
|
||||||
|
if (e.Color == RenderData.ColorInvisible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (e.Type)
|
||||||
|
{
|
||||||
|
case Marker.EType.Hoard:
|
||||||
|
// ignore distance if this is a found hoard coffer
|
||||||
|
if (_territoryState.PomanderOfIntuition == PomanderState.Active &&
|
||||||
|
_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander)
|
||||||
|
break;
|
||||||
|
|
||||||
|
goto case Marker.EType.Trap;
|
||||||
|
|
||||||
|
case Marker.EType.Trap:
|
||||||
|
var playerPos = _clientState.LocalPlayer?.Position;
|
||||||
|
if (playerPos == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((playerPos.Value - e.Position).Length() > 65)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool onScreen = false;
|
||||||
|
for (int index = 0; index < 2 * SegmentCount; ++index)
|
||||||
|
{
|
||||||
|
onScreen |= _gameGui.WorldToScreen(new Vector3(
|
||||||
|
e.Position.X + e.Radius * (float)Math.Sin(Math.PI / SegmentCount * index),
|
||||||
|
e.Position.Y,
|
||||||
|
e.Position.Z + e.Radius * (float)Math.Cos(Math.PI / SegmentCount * index)),
|
||||||
|
out Vector2 vector2);
|
||||||
|
|
||||||
|
ImGui.GetWindowDrawList().PathLineTo(vector2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onScreen)
|
||||||
|
{
|
||||||
|
if (e.Fill)
|
||||||
|
ImGui.GetWindowDrawList().PathFillConvex(e.Color);
|
||||||
|
else
|
||||||
|
ImGui.GetWindowDrawList().PathStroke(e.Color, ImDrawFlags.Closed, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ImGui.GetWindowDrawList().PathClear();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var l in _layers.Values)
|
foreach (var l in _layers.Values)
|
||||||
l.Dispose();
|
l.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SimpleLayer : IDisposable
|
public sealed class SimpleLayer : IDisposable
|
||||||
{
|
{
|
||||||
public required ushort TerritoryType { get; init; }
|
public required ushort TerritoryType { get; init; }
|
||||||
public required IReadOnlyList<SimpleElement> Elements { get; init; }
|
public required IReadOnlyList<SimpleElement> Elements { get; init; }
|
||||||
|
|
||||||
public void Draw()
|
|
||||||
{
|
|
||||||
foreach (var element in Elements)
|
|
||||||
element.Draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var e in Elements)
|
foreach (var e in Elements)
|
||||||
@ -100,63 +164,14 @@ namespace Pal.Client.Rendering
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SimpleElement : IRenderElement
|
public sealed class SimpleElement : IRenderElement
|
||||||
{
|
{
|
||||||
private const int SegmentCount = 20;
|
|
||||||
|
|
||||||
public bool IsValid { get; set; } = true;
|
public bool IsValid { get; set; } = true;
|
||||||
public required Marker.EType Type { get; init; }
|
public required Marker.EType Type { get; init; }
|
||||||
public required Vector3 Position { get; init; }
|
public required Vector3 Position { get; init; }
|
||||||
public required uint Color { get; set; }
|
public required uint Color { get; set; }
|
||||||
public required float Radius { get; init; }
|
public required float Radius { get; init; }
|
||||||
public required bool Fill { get; init; }
|
public required bool Fill { get; init; }
|
||||||
|
|
||||||
public void Draw()
|
|
||||||
{
|
|
||||||
if (Color == Plugin.ColorInvisible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (Type)
|
|
||||||
{
|
|
||||||
case Marker.EType.Hoard:
|
|
||||||
// ignore distance if this is a found hoard coffer
|
|
||||||
if (Service.Plugin.PomanderOfIntuition == Plugin.PomanderState.Active && Service.Configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander)
|
|
||||||
break;
|
|
||||||
|
|
||||||
goto case Marker.EType.Trap;
|
|
||||||
|
|
||||||
case Marker.EType.Trap:
|
|
||||||
var playerPos = Service.ClientState.LocalPlayer?.Position;
|
|
||||||
if (playerPos == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ((playerPos.Value - Position).Length() > 65)
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool onScreen = false;
|
|
||||||
for (int index = 0; index < 2 * SegmentCount; ++index)
|
|
||||||
{
|
|
||||||
onScreen |= Service.GameGui.WorldToScreen(new Vector3(
|
|
||||||
Position.X + Radius * (float)Math.Sin(Math.PI / SegmentCount * index),
|
|
||||||
Position.Y,
|
|
||||||
Position.Z + Radius * (float)Math.Cos(Math.PI / SegmentCount * index)),
|
|
||||||
out Vector2 vector2);
|
|
||||||
|
|
||||||
ImGui.GetWindowDrawList().PathLineTo(vector2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onScreen)
|
|
||||||
{
|
|
||||||
if (Fill)
|
|
||||||
ImGui.GetWindowDrawList().PathFillConvex(Color);
|
|
||||||
else
|
|
||||||
ImGui.GetWindowDrawList().PathStroke(Color, ImDrawFlags.Closed, 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ImGui.GetWindowDrawList().PathClear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,21 +11,33 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using Dalamud.Game.ClientState;
|
||||||
using System.Threading.Tasks;
|
using Dalamud.Game.Gui;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
|
||||||
namespace Pal.Client.Rendering
|
namespace Pal.Client.Rendering
|
||||||
{
|
{
|
||||||
internal class SplatoonRenderer : IRenderer, IDrawDebugItems, IDisposable
|
internal sealed class SplatoonRenderer : IRenderer, IDrawDebugItems, IDisposable
|
||||||
{
|
{
|
||||||
private const long OnTerritoryChange = -2;
|
private const long OnTerritoryChange = -2;
|
||||||
private bool IsDisposed { get; set; }
|
|
||||||
|
|
||||||
public SplatoonRenderer(DalamudPluginInterface pluginInterface, IDalamudPlugin plugin)
|
private readonly DebugState _debugState;
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
|
||||||
|
public SplatoonRenderer(DalamudPluginInterface pluginInterface, IDalamudPlugin dalamudPlugin, DebugState debugState,
|
||||||
|
ClientState clientState, ChatGui chatGui)
|
||||||
{
|
{
|
||||||
ECommonsMain.Init(pluginInterface, plugin, ECommons.Module.SplatoonAPI);
|
_debugState = debugState;
|
||||||
|
_clientState = clientState;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
|
||||||
|
PluginLog.Information("Initializing splatoon...");
|
||||||
|
ECommonsMain.Init(pluginInterface, dalamudPlugin, ECommons.Module.SplatoonAPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsDisposed { get; set; }
|
||||||
|
|
||||||
public void SetLayer(ELayer layer, IReadOnlyList<IRenderElement> elements)
|
public void SetLayer(ELayer layer, IReadOnlyList<IRenderElement> elements)
|
||||||
{
|
{
|
||||||
// we need to delay this, as the current framework update could be before splatoon's, in which case it would immediately delete the layout
|
// we need to delay this, as the current framework update could be before splatoon's, in which case it would immediately delete the layout
|
||||||
@ -33,12 +45,14 @@ namespace Pal.Client.Rendering
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Splatoon.AddDynamicElements(ToLayerName(layer), elements.Cast<SplatoonElement>().Select(x => x.Delegate).ToArray(), new[] { Environment.TickCount64 + 60 * 60 * 1000, OnTerritoryChange });
|
Splatoon.AddDynamicElements(ToLayerName(layer),
|
||||||
|
elements.Cast<SplatoonElement>().Select(x => x.Delegate).ToArray(),
|
||||||
|
new[] { Environment.TickCount64 + 60 * 60 * 1000, OnTerritoryChange });
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
PluginLog.Error(e, $"Could not create splatoon layer {layer} with {elements.Count} elements");
|
PluginLog.Error(e, $"Could not create splatoon layer {layer} with {elements.Count} elements");
|
||||||
Service.Plugin.DebugMessage = $"{DateTime.Now}\n{e}";
|
_debugState.SetFromException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -82,7 +96,7 @@ namespace Pal.Client.Rendering
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Vector3? pos = Service.ClientState.LocalPlayer?.Position;
|
Vector3? pos = _clientState.LocalPlayer?.Position;
|
||||||
if (pos != null)
|
if (pos != null)
|
||||||
{
|
{
|
||||||
var elements = new List<IRenderElement>
|
var elements = new List<IRenderElement>
|
||||||
@ -91,9 +105,11 @@ namespace Pal.Client.Rendering
|
|||||||
CreateElement(Marker.EType.Hoard, pos.Value, ImGui.ColorConvertFloat4ToU32(hoardColor)),
|
CreateElement(Marker.EType.Hoard, pos.Value, ImGui.ColorConvertFloat4ToU32(hoardColor)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!Splatoon.AddDynamicElements("PalacePal.Test", elements.Cast<SplatoonElement>().Select(x => x.Delegate).ToArray(), new[] { Environment.TickCount64 + 10000 }))
|
if (!Splatoon.AddDynamicElements("PalacePal.Test",
|
||||||
|
elements.Cast<SplatoonElement>().Select(x => x.Delegate).ToArray(),
|
||||||
|
new[] { Environment.TickCount64 + 10000 }))
|
||||||
{
|
{
|
||||||
Service.Chat.PrintError("Could not draw markers :(");
|
_chatGui.PrintError("Could not draw markers :(");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,23 +118,31 @@ namespace Pal.Client.Rendering
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pluginManager = DalamudReflector.GetPluginManager();
|
var pluginManager = DalamudReflector.GetPluginManager();
|
||||||
IList installedPlugins = pluginManager.GetType().GetProperty("InstalledPlugins")?.GetValue(pluginManager) as IList ?? new List<object>();
|
IList installedPlugins =
|
||||||
|
pluginManager.GetType().GetProperty("InstalledPlugins")?.GetValue(pluginManager) as IList ??
|
||||||
|
new List<object>();
|
||||||
|
|
||||||
foreach (var t in installedPlugins)
|
foreach (var t in installedPlugins)
|
||||||
{
|
{
|
||||||
AssemblyName? assemblyName = (AssemblyName?)t.GetType().GetProperty("AssemblyName")?.GetValue(t);
|
AssemblyName? assemblyName =
|
||||||
|
(AssemblyName?)t.GetType().GetProperty("AssemblyName")?.GetValue(t);
|
||||||
string? pluginName = (string?)t.GetType().GetProperty("Name")?.GetValue(t);
|
string? pluginName = (string?)t.GetType().GetProperty("Name")?.GetValue(t);
|
||||||
if (assemblyName?.Name == "Splatoon" && pluginName != "Splatoon")
|
if (assemblyName?.Name == "Splatoon" && pluginName != "Splatoon")
|
||||||
{
|
{
|
||||||
Service.Chat.PrintError($"[Palace Pal] Splatoon is installed under the plugin name '{pluginName}', which is incompatible with the Splatoon API.");
|
_chatGui.PrintError(
|
||||||
Service.Chat.Print("[Palace Pal] You need to install Splatoon from the official repository at https://github.com/NightmareXIV/MyDalamudPlugins.");
|
$"[Palace Pal] Splatoon is installed under the plugin name '{pluginName}', which is incompatible with the Splatoon API.");
|
||||||
|
_chatGui.Print(
|
||||||
|
"[Palace Pal] You need to install Splatoon from the official repository at https://github.com/NightmareXIV/MyDalamudPlugins.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// not relevant
|
||||||
|
}
|
||||||
|
|
||||||
Service.Chat.PrintError("Could not draw markers, is Splatoon installed and enabled?");
|
_chatGui.PrintError("Could not draw markers, is Splatoon installed and enabled?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +156,7 @@ namespace Pal.Client.Rendering
|
|||||||
ECommonsMain.Dispose();
|
ECommonsMain.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SplatoonElement : IRenderElement
|
private sealed class SplatoonElement : IRenderElement
|
||||||
{
|
{
|
||||||
private readonly SplatoonRenderer _renderer;
|
private readonly SplatoonRenderer _renderer;
|
||||||
|
|
||||||
@ -145,6 +169,7 @@ namespace Pal.Client.Rendering
|
|||||||
public Element Delegate { get; }
|
public Element Delegate { get; }
|
||||||
|
|
||||||
public bool IsValid => !_renderer.IsDisposed && Delegate.IsValid();
|
public bool IsValid => !_renderer.IsDisposed && Delegate.IsValid();
|
||||||
|
|
||||||
public uint Color
|
public uint Color
|
||||||
{
|
{
|
||||||
get => Delegate.color;
|
get => Delegate.color;
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
using System;
|
namespace Pal.Client.Scheduled
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Pal.Client.Scheduled
|
|
||||||
{
|
{
|
||||||
internal interface IQueueOnFrameworkThread
|
internal interface IQueueOnFrameworkThread
|
||||||
{
|
{
|
||||||
void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
203
Pal.Client/Scheduled/QueueHandler.cs
Normal file
203
Pal.Client/Scheduled/QueueHandler.cs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
using Pal.Client.Extensions;
|
||||||
|
using Pal.Client.Net;
|
||||||
|
using Pal.Client.Properties;
|
||||||
|
using Pal.Common;
|
||||||
|
|
||||||
|
namespace Pal.Client.Scheduled
|
||||||
|
{
|
||||||
|
// TODO The idea was to split this from the queue objects, should be in individual classes tho
|
||||||
|
internal sealed class QueueHandler
|
||||||
|
{
|
||||||
|
private readonly ConfigurationManager _configurationManager;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly FloorService _floorService;
|
||||||
|
private readonly TerritoryState _territoryState;
|
||||||
|
private readonly DebugState _debugState;
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
|
||||||
|
public QueueHandler(
|
||||||
|
ConfigurationManager configurationManager,
|
||||||
|
IPalacePalConfiguration configuration,
|
||||||
|
FloorService floorService,
|
||||||
|
TerritoryState territoryState,
|
||||||
|
DebugState debugState,
|
||||||
|
ChatGui chatGui)
|
||||||
|
{
|
||||||
|
_configurationManager = configurationManager;
|
||||||
|
_configuration = configuration;
|
||||||
|
_floorService = floorService;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
_debugState = debugState;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers)
|
||||||
|
{
|
||||||
|
if (queued is QueuedConfigUpdate)
|
||||||
|
{
|
||||||
|
ConfigUpdate(ref recreateLayout, ref saveMarkers);
|
||||||
|
}
|
||||||
|
else if (queued is QueuedSyncResponse queuedSyncResponse)
|
||||||
|
{
|
||||||
|
SyncResponse(queuedSyncResponse);
|
||||||
|
recreateLayout = true;
|
||||||
|
saveMarkers = true;
|
||||||
|
}
|
||||||
|
else if (queued is QueuedImport queuedImport)
|
||||||
|
{
|
||||||
|
Import(queuedImport);
|
||||||
|
recreateLayout = true;
|
||||||
|
saveMarkers = true;
|
||||||
|
}
|
||||||
|
else if (queued is QueuedUndoImport queuedUndoImport)
|
||||||
|
{
|
||||||
|
UndoImport(queuedUndoImport);
|
||||||
|
recreateLayout = true;
|
||||||
|
saveMarkers = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigUpdate(ref bool recreateLayout, ref bool saveMarkers)
|
||||||
|
{
|
||||||
|
if (_configuration.Mode == EMode.Offline)
|
||||||
|
{
|
||||||
|
LocalState.UpdateAll();
|
||||||
|
_floorService.FloorMarkers.Clear();
|
||||||
|
_floorService.EphemeralMarkers.Clear();
|
||||||
|
_territoryState.LastTerritory = 0;
|
||||||
|
|
||||||
|
recreateLayout = true;
|
||||||
|
saveMarkers = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncResponse(QueuedSyncResponse queued)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var remoteMarkers = queued.Markers;
|
||||||
|
var currentFloor = _floorService.GetFloorMarkers(queued.TerritoryType);
|
||||||
|
if (_configuration.Mode == EMode.Online && queued.Success && remoteMarkers.Count > 0)
|
||||||
|
{
|
||||||
|
switch (queued.Type)
|
||||||
|
{
|
||||||
|
case SyncType.Download:
|
||||||
|
case SyncType.Upload:
|
||||||
|
foreach (var remoteMarker in remoteMarkers)
|
||||||
|
{
|
||||||
|
// Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved.
|
||||||
|
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
|
||||||
|
if (localMarker != null)
|
||||||
|
{
|
||||||
|
localMarker.NetworkId = remoteMarker.NetworkId;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queued.Type == SyncType.Download)
|
||||||
|
currentFloor.Markers.Add(remoteMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SyncType.MarkSeen:
|
||||||
|
var partialAccountId =
|
||||||
|
_configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
||||||
|
if (partialAccountId == null)
|
||||||
|
break;
|
||||||
|
foreach (var remoteMarker in remoteMarkers)
|
||||||
|
{
|
||||||
|
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
|
||||||
|
if (localMarker != null)
|
||||||
|
localMarker.RemoteSeenOn.Add(partialAccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't modify state for outdated floors
|
||||||
|
if (_territoryState.LastTerritory != queued.TerritoryType)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (queued.Type == SyncType.Download)
|
||||||
|
{
|
||||||
|
if (queued.Success)
|
||||||
|
_territoryState.TerritorySyncState = SyncState.Complete;
|
||||||
|
else
|
||||||
|
_territoryState.TerritorySyncState = SyncState.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugState.SetFromException(e);
|
||||||
|
if (queued.Type == SyncType.Download)
|
||||||
|
_territoryState.TerritorySyncState = SyncState.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Import(QueuedImport queued)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!queued.Validate(_chatGui))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var oldExportIds = string.IsNullOrEmpty(queued.Export.ServerUrl)
|
||||||
|
? _configuration.ImportHistory.Where(x => x.RemoteUrl == queued.Export.ServerUrl).Select(x => x.Id)
|
||||||
|
.Where(x => x != Guid.Empty).ToList()
|
||||||
|
: new List<Guid>();
|
||||||
|
|
||||||
|
foreach (var remoteFloor in queued.Export.Floors)
|
||||||
|
{
|
||||||
|
ushort territoryType = (ushort)remoteFloor.TerritoryType;
|
||||||
|
var localState = _floorService.GetFloorMarkers(territoryType);
|
||||||
|
|
||||||
|
localState.UndoImport(oldExportIds);
|
||||||
|
queued.ImportFloor(remoteFloor, localState);
|
||||||
|
|
||||||
|
localState.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
_configuration.ImportHistory.RemoveAll(hist =>
|
||||||
|
oldExportIds.Contains(hist.Id) || hist.Id == queued.ExportId);
|
||||||
|
_configuration.ImportHistory.Add(new ConfigurationV1.ImportHistoryEntry
|
||||||
|
{
|
||||||
|
Id = queued.ExportId,
|
||||||
|
RemoteUrl = queued.Export.ServerUrl,
|
||||||
|
ExportedAt = queued.Export.CreatedAt.ToDateTime(),
|
||||||
|
ImportedAt = DateTime.UtcNow,
|
||||||
|
});
|
||||||
|
_configurationManager.Save(_configuration);
|
||||||
|
|
||||||
|
_chatGui.Print(string.Format(Localization.ImportCompleteStatistics, queued.ImportedTraps,
|
||||||
|
queued.ImportedHoardCoffers));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Error(e, "Import failed");
|
||||||
|
_chatGui.PalError(string.Format(Localization.Error_ImportFailed, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UndoImport(QueuedUndoImport queued)
|
||||||
|
{
|
||||||
|
foreach (ETerritoryType territoryType in typeof(ETerritoryType).GetEnumValues())
|
||||||
|
{
|
||||||
|
var localState = _floorService.GetFloorMarkers((ushort)territoryType);
|
||||||
|
localState.UndoImport(new List<Guid> { queued.ExportId });
|
||||||
|
localState.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
_configuration.ImportHistory.RemoveAll(hist => hist.Id == queued.ExportId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,6 @@
|
|||||||
namespace Pal.Client.Scheduled
|
namespace Pal.Client.Scheduled
|
||||||
{
|
{
|
||||||
internal class QueuedConfigUpdate : IQueueOnFrameworkThread
|
internal sealed class QueuedConfigUpdate : IQueueOnFrameworkThread
|
||||||
{
|
{
|
||||||
public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers)
|
|
||||||
{
|
|
||||||
if (Service.Configuration.Mode == Configuration.EMode.Offline)
|
|
||||||
{
|
|
||||||
LocalState.UpdateAll();
|
|
||||||
plugin.FloorMarkers.Clear();
|
|
||||||
plugin.EphemeralMarkers.Clear();
|
|
||||||
plugin.LastTerritory = 0;
|
|
||||||
|
|
||||||
recreateLayout = true;
|
|
||||||
saveMarkers = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.ResetRenderer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,98 +1,54 @@
|
|||||||
using Account;
|
using Account;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Pal.Common;
|
using Pal.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Pal.Client.Extensions;
|
using Dalamud.Game.Gui;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
using Pal.Client.Configuration;
|
|
||||||
|
|
||||||
namespace Pal.Client.Scheduled
|
namespace Pal.Client.Scheduled
|
||||||
{
|
{
|
||||||
internal class QueuedImport : IQueueOnFrameworkThread
|
internal sealed class QueuedImport : IQueueOnFrameworkThread
|
||||||
{
|
{
|
||||||
private readonly ExportRoot _export;
|
public ExportRoot Export { get; }
|
||||||
private Guid _exportId;
|
public Guid ExportId { get; private set; }
|
||||||
private int _importedTraps;
|
public int ImportedTraps { get; private set; }
|
||||||
private int _importedHoardCoffers;
|
public int ImportedHoardCoffers { get; private set; }
|
||||||
|
|
||||||
public QueuedImport(string sourcePath)
|
public QueuedImport(string sourcePath)
|
||||||
{
|
{
|
||||||
using var input = File.OpenRead(sourcePath);
|
using var input = File.OpenRead(sourcePath);
|
||||||
_export = ExportRoot.Parser.ParseFrom(input);
|
Export = ExportRoot.Parser.ParseFrom(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers)
|
public bool Validate(ChatGui chatGui)
|
||||||
{
|
{
|
||||||
try
|
if (Export.ExportVersion != ExportConfig.ExportVersion)
|
||||||
{
|
{
|
||||||
if (!Validate())
|
chatGui.PrintError(Localization.Error_ImportFailed_IncompatibleVersion);
|
||||||
return;
|
|
||||||
|
|
||||||
var config = Service.Configuration;
|
|
||||||
var oldExportIds = string.IsNullOrEmpty(_export.ServerUrl) ? config.ImportHistory.Where(x => x.RemoteUrl == _export.ServerUrl).Select(x => x.Id).Where(x => x != Guid.Empty).ToList() : new List<Guid>();
|
|
||||||
|
|
||||||
foreach (var remoteFloor in _export.Floors)
|
|
||||||
{
|
|
||||||
ushort territoryType = (ushort)remoteFloor.TerritoryType;
|
|
||||||
var localState = plugin.GetFloorMarkers(territoryType);
|
|
||||||
|
|
||||||
localState.UndoImport(oldExportIds);
|
|
||||||
ImportFloor(remoteFloor, localState);
|
|
||||||
|
|
||||||
localState.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
config.ImportHistory.RemoveAll(hist => oldExportIds.Contains(hist.Id) || hist.Id == _exportId);
|
|
||||||
config.ImportHistory.Add(new ConfigurationV1.ImportHistoryEntry
|
|
||||||
{
|
|
||||||
Id = _exportId,
|
|
||||||
RemoteUrl = _export.ServerUrl,
|
|
||||||
ExportedAt = _export.CreatedAt.ToDateTime(),
|
|
||||||
ImportedAt = DateTime.UtcNow,
|
|
||||||
});
|
|
||||||
Service.ConfigurationManager.Save(config);
|
|
||||||
|
|
||||||
recreateLayout = true;
|
|
||||||
saveMarkers = true;
|
|
||||||
|
|
||||||
Service.Chat.Print(string.Format(Localization.ImportCompleteStatistics, _importedTraps, _importedHoardCoffers));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Error(e, "Import failed");
|
|
||||||
Service.Chat.PalError(string.Format(Localization.Error_ImportFailed, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool Validate()
|
|
||||||
{
|
|
||||||
if (_export.ExportVersion != ExportConfig.ExportVersion)
|
|
||||||
{
|
|
||||||
Service.Chat.PrintError(Localization.Error_ImportFailed_IncompatibleVersion);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Guid.TryParse(_export.ExportId, out _exportId) || _exportId == Guid.Empty)
|
if (!Guid.TryParse(Export.ExportId, out Guid exportId) || ExportId == Guid.Empty)
|
||||||
{
|
{
|
||||||
Service.Chat.PrintError(Localization.Error_ImportFailed_InvalidFile);
|
chatGui.PrintError(Localization.Error_ImportFailed_InvalidFile);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_export.ServerUrl))
|
ExportId = exportId;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Export.ServerUrl))
|
||||||
{
|
{
|
||||||
// If we allow for backups as import/export, this should be removed
|
// If we allow for backups as import/export, this should be removed
|
||||||
Service.Chat.PrintError(Localization.Error_ImportFailed_InvalidFile);
|
chatGui.PrintError(Localization.Error_ImportFailed_InvalidFile);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ImportFloor(ExportFloor remoteFloor, LocalState localState)
|
public void ImportFloor(ExportFloor remoteFloor, LocalState localState)
|
||||||
{
|
{
|
||||||
var remoteMarkers = remoteFloor.Objects.Select(m => new Marker((Marker.EType)m.Type, new Vector3(m.X, m.Y, m.Z)) { WasImported = true });
|
var remoteMarkers = remoteFloor.Objects.Select(m => new Marker((Marker.EType)m.Type, new Vector3(m.X, m.Y, m.Z)) { WasImported = true });
|
||||||
foreach (var remoteMarker in remoteMarkers)
|
foreach (var remoteMarker in remoteMarkers)
|
||||||
@ -104,12 +60,12 @@ namespace Pal.Client.Scheduled
|
|||||||
localMarker = remoteMarker;
|
localMarker = remoteMarker;
|
||||||
|
|
||||||
if (localMarker.Type == Marker.EType.Trap)
|
if (localMarker.Type == Marker.EType.Trap)
|
||||||
_importedTraps++;
|
ImportedTraps++;
|
||||||
else if (localMarker.Type == Marker.EType.Hoard)
|
else if (localMarker.Type == Marker.EType.Hoard)
|
||||||
_importedHoardCoffers++;
|
ImportedHoardCoffers++;
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteMarker.Imports.Add(_exportId);
|
remoteMarker.Imports.Add(ExportId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,13 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Pal.Client.Extensions;
|
|
||||||
using Pal.Client.Net;
|
|
||||||
using static Pal.Client.Plugin;
|
|
||||||
|
|
||||||
namespace Pal.Client.Scheduled
|
namespace Pal.Client.Scheduled
|
||||||
{
|
{
|
||||||
internal class QueuedSyncResponse : IQueueOnFrameworkThread
|
internal sealed class QueuedSyncResponse : IQueueOnFrameworkThread
|
||||||
{
|
{
|
||||||
public required SyncType Type { get; init; }
|
public required SyncType Type { get; init; }
|
||||||
public required ushort TerritoryType { get; init; }
|
public required ushort TerritoryType { get; init; }
|
||||||
public required bool Success { get; init; }
|
public required bool Success { get; init; }
|
||||||
public required List<Marker> Markers { get; init; }
|
public required List<Marker> Markers { get; init; }
|
||||||
|
|
||||||
public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers)
|
|
||||||
{
|
|
||||||
recreateLayout = true;
|
|
||||||
saveMarkers = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var remoteMarkers = Markers;
|
|
||||||
var currentFloor = plugin.GetFloorMarkers(TerritoryType);
|
|
||||||
if (Service.Configuration.Mode == Configuration.EMode.Online && Success && remoteMarkers.Count > 0)
|
|
||||||
{
|
|
||||||
switch (Type)
|
|
||||||
{
|
|
||||||
case SyncType.Download:
|
|
||||||
case SyncType.Upload:
|
|
||||||
foreach (var remoteMarker in remoteMarkers)
|
|
||||||
{
|
|
||||||
// Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved.
|
|
||||||
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
|
|
||||||
if (localMarker != null)
|
|
||||||
{
|
|
||||||
localMarker.NetworkId = remoteMarker.NetworkId;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Type == SyncType.Download)
|
|
||||||
currentFloor.Markers.Add(remoteMarker);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SyncType.MarkSeen:
|
|
||||||
var partialAccountId = Service.Configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
|
|
||||||
if (partialAccountId == null)
|
|
||||||
break;
|
|
||||||
foreach (var remoteMarker in remoteMarkers)
|
|
||||||
{
|
|
||||||
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
|
|
||||||
if (localMarker != null)
|
|
||||||
localMarker.RemoteSeenOn.Add(partialAccountId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't modify state for outdated floors
|
|
||||||
if (plugin.LastTerritory != TerritoryType)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Type == SyncType.Download)
|
|
||||||
{
|
|
||||||
if (Success)
|
|
||||||
plugin.TerritorySyncState = SyncState.Complete;
|
|
||||||
else
|
|
||||||
plugin.TerritorySyncState = SyncState.Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
plugin.DebugMessage = $"{DateTime.Now}\n{e}";
|
|
||||||
if (Type == SyncType.Download)
|
|
||||||
plugin.TerritorySyncState = SyncState.Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SyncState
|
public enum SyncState
|
||||||
|
@ -1,35 +1,14 @@
|
|||||||
using ECommons.Configuration;
|
using System;
|
||||||
using Pal.Common;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Pal.Client.Scheduled
|
namespace Pal.Client.Scheduled
|
||||||
{
|
{
|
||||||
internal class QueuedUndoImport : IQueueOnFrameworkThread
|
internal sealed class QueuedUndoImport : IQueueOnFrameworkThread
|
||||||
{
|
{
|
||||||
private readonly Guid _exportId;
|
|
||||||
|
|
||||||
public QueuedUndoImport(Guid exportId)
|
public QueuedUndoImport(Guid exportId)
|
||||||
{
|
{
|
||||||
_exportId = exportId;
|
ExportId = exportId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run(Plugin plugin, ref bool recreateLayout, ref bool saveMarkers)
|
public Guid ExportId { get; }
|
||||||
{
|
|
||||||
recreateLayout = true;
|
|
||||||
saveMarkers = true;
|
|
||||||
|
|
||||||
foreach (ETerritoryType territoryType in typeof(ETerritoryType).GetEnumValues())
|
|
||||||
{
|
|
||||||
var localState = plugin.GetFloorMarkers((ushort)territoryType);
|
|
||||||
localState.UndoImport(new List<Guid> { _exportId });
|
|
||||||
localState.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Service.Configuration.ImportHistory.RemoveAll(hist => hist.Id == _exportId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,15 @@
|
|||||||
using Dalamud.Data;
|
using System;
|
||||||
using Dalamud.Game;
|
|
||||||
using Dalamud.Game.ClientState;
|
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Game.ClientState.Objects;
|
|
||||||
using Dalamud.Game.Command;
|
|
||||||
using Dalamud.Game.Gui;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Pal.Client.Configuration;
|
using Pal.Client.Configuration;
|
||||||
using Pal.Client.Net;
|
|
||||||
|
|
||||||
namespace Pal.Client
|
namespace Pal.Client
|
||||||
{
|
{
|
||||||
|
[Obsolete]
|
||||||
public class Service
|
public class Service
|
||||||
{
|
{
|
||||||
[PluginService] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
[PluginService] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
||||||
[PluginService] public static ClientState ClientState { get; set; } = null!;
|
|
||||||
[PluginService] public static ChatGui Chat { get; private set; } = null!;
|
|
||||||
[PluginService] public static ObjectTable ObjectTable { get; private set; } = null!;
|
|
||||||
[PluginService] public static Framework Framework { get; set; } = null!;
|
|
||||||
[PluginService] public static Condition Condition { get; set; } = null!;
|
|
||||||
[PluginService] public static CommandManager CommandManager { get; set; } = null!;
|
|
||||||
[PluginService] public static DataManager DataManager { get; set; } = null!;
|
|
||||||
[PluginService] public static GameGui GameGui { get; set; } = null!;
|
|
||||||
|
|
||||||
internal static Plugin Plugin { get; set; } = null!;
|
|
||||||
internal static WindowSystem WindowSystem { get; } = new(typeof(Service).AssemblyQualifiedName);
|
|
||||||
internal static RemoteApi RemoteApi { get; } = new();
|
|
||||||
internal static ConfigurationManager ConfigurationManager { get; set; } = null!;
|
|
||||||
internal static IPalacePalConfiguration Configuration { get; set; } = null!;
|
internal static IPalacePalConfiguration Configuration { get; set; } = null!;
|
||||||
internal static Hooks Hooks { get; set; } = null!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
using Dalamud.Interface.Colors;
|
using System;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using ECommons;
|
using ECommons;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Pal.Client.Configuration;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
|
|
||||||
namespace Pal.Client.Windows
|
namespace Pal.Client.Windows
|
||||||
{
|
{
|
||||||
internal class AgreementWindow : Window, ILanguageChanged
|
internal sealed class AgreementWindow : Window, IDisposable, ILanguageChanged
|
||||||
{
|
{
|
||||||
private const string WindowId = "###PalPalaceAgreement";
|
private const string WindowId = "###PalPalaceAgreement";
|
||||||
|
private readonly WindowSystem _windowSystem;
|
||||||
|
private readonly ConfigurationManager _configurationManager;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
private int _choice;
|
private int _choice;
|
||||||
|
|
||||||
public AgreementWindow() : base(WindowId)
|
public AgreementWindow(
|
||||||
|
WindowSystem windowSystem,
|
||||||
|
ConfigurationManager configurationManager,
|
||||||
|
IPalacePalConfiguration configuration)
|
||||||
|
: base(WindowId)
|
||||||
{
|
{
|
||||||
|
_windowSystem = windowSystem;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
|
_configuration = configuration;
|
||||||
|
|
||||||
LanguageChanged();
|
LanguageChanged();
|
||||||
|
|
||||||
Flags = ImGuiWindowFlags.NoCollapse;
|
Flags = ImGuiWindowFlags.NoCollapse;
|
||||||
@ -27,8 +40,14 @@ namespace Pal.Client.Windows
|
|||||||
MinimumSize = new Vector2(500, 500),
|
MinimumSize = new Vector2(500, 500),
|
||||||
MaximumSize = new Vector2(2000, 2000),
|
MaximumSize = new Vector2(2000, 2000),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IsOpen = configuration.FirstUse;
|
||||||
|
_windowSystem.AddWindow(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _windowSystem.RemoveWindow(this);
|
||||||
|
|
||||||
public void LanguageChanged()
|
public void LanguageChanged()
|
||||||
=> WindowName = $"{Localization.Palace_Pal}{WindowId}";
|
=> WindowName = $"{Localization.Palace_Pal}{WindowId}";
|
||||||
|
|
||||||
@ -39,8 +58,6 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
var config = Service.Configuration;
|
|
||||||
|
|
||||||
ImGui.TextWrapped(Localization.Explanation_1);
|
ImGui.TextWrapped(Localization.Explanation_1);
|
||||||
ImGui.TextWrapped(Localization.Explanation_2);
|
ImGui.TextWrapped(Localization.Explanation_2);
|
||||||
|
|
||||||
@ -49,8 +66,8 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.TextWrapped(Localization.Explanation_3);
|
ImGui.TextWrapped(Localization.Explanation_3);
|
||||||
ImGui.TextWrapped(Localization.Explanation_4);
|
ImGui.TextWrapped(Localization.Explanation_4);
|
||||||
|
|
||||||
ImGui.RadioButton(Localization.Config_UploadMyDiscoveries_ShowOtherTraps, ref _choice, (int)Configuration.EMode.Online);
|
ImGui.RadioButton(Localization.Config_UploadMyDiscoveries_ShowOtherTraps, ref _choice, (int)EMode.Online);
|
||||||
ImGui.RadioButton(Localization.Config_NeverUploadDiscoveries_ShowMyTraps, ref _choice, (int)Configuration.EMode.Offline);
|
ImGui.RadioButton(Localization.Config_NeverUploadDiscoveries_ShowMyTraps, ref _choice, (int)EMode.Offline);
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
@ -67,12 +84,13 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.BeginDisabled(_choice == -1);
|
ImGui.BeginDisabled(_choice == -1);
|
||||||
if (ImGui.Button(Localization.Agreement_UsingThisOnMyOwnRisk))
|
if (ImGui.Button(Localization.Agreement_UsingThisOnMyOwnRisk))
|
||||||
{
|
{
|
||||||
config.Mode = (Configuration.EMode)_choice;
|
_configuration.Mode = (EMode)_choice;
|
||||||
config.FirstUse = false;
|
_configuration.FirstUse = false;
|
||||||
Service.ConfigurationManager.Save(config);
|
_configurationManager.Save(_configuration);
|
||||||
|
|
||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
@ -18,14 +18,28 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
using Pal.Client.Configuration;
|
using Pal.Client.Configuration;
|
||||||
|
using Pal.Client.DependencyInjection;
|
||||||
|
|
||||||
namespace Pal.Client.Windows
|
namespace Pal.Client.Windows
|
||||||
{
|
{
|
||||||
internal class ConfigWindow : Window, ILanguageChanged, IDisposable
|
internal sealed class ConfigWindow : Window, ILanguageChanged, IDisposable
|
||||||
{
|
{
|
||||||
private const string WindowId = "###PalPalaceConfig";
|
private const string WindowId = "###PalPalaceConfig";
|
||||||
|
|
||||||
|
private readonly WindowSystem _windowSystem;
|
||||||
|
private readonly ConfigurationManager _configurationManager;
|
||||||
|
private readonly IPalacePalConfiguration _configuration;
|
||||||
|
private readonly RenderAdapter _renderAdapter;
|
||||||
|
private readonly TerritoryState _territoryState;
|
||||||
|
private readonly FrameworkService _frameworkService;
|
||||||
|
private readonly FloorService _floorService;
|
||||||
|
private readonly DebugState _debugState;
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
private readonly RemoteApi _remoteApi;
|
||||||
|
|
||||||
private int _mode;
|
private int _mode;
|
||||||
private int _renderer;
|
private int _renderer;
|
||||||
private ConfigurableMarker _trapConfig = new();
|
private ConfigurableMarker _trapConfig = new();
|
||||||
@ -43,8 +57,30 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
private CancellationTokenSource? _testConnectionCts;
|
private CancellationTokenSource? _testConnectionCts;
|
||||||
|
|
||||||
public ConfigWindow() : base(WindowId)
|
public ConfigWindow(
|
||||||
|
WindowSystem windowSystem,
|
||||||
|
ConfigurationManager configurationManager,
|
||||||
|
IPalacePalConfiguration configuration,
|
||||||
|
RenderAdapter renderAdapter,
|
||||||
|
TerritoryState territoryState,
|
||||||
|
FrameworkService frameworkService,
|
||||||
|
FloorService floorService,
|
||||||
|
DebugState debugState,
|
||||||
|
ChatGui chatGui,
|
||||||
|
RemoteApi remoteApi)
|
||||||
|
: base(WindowId)
|
||||||
{
|
{
|
||||||
|
_windowSystem = windowSystem;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
|
_configuration = configuration;
|
||||||
|
_renderAdapter = renderAdapter;
|
||||||
|
_territoryState = territoryState;
|
||||||
|
_frameworkService = frameworkService;
|
||||||
|
_floorService = floorService;
|
||||||
|
_debugState = debugState;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
_remoteApi = remoteApi;
|
||||||
|
|
||||||
LanguageChanged();
|
LanguageChanged();
|
||||||
|
|
||||||
Size = new Vector2(500, 400);
|
Size = new Vector2(500, 400);
|
||||||
@ -52,8 +88,18 @@ namespace Pal.Client.Windows
|
|||||||
Position = new Vector2(300, 300);
|
Position = new Vector2(300, 300);
|
||||||
PositionCondition = ImGuiCond.FirstUseEver;
|
PositionCondition = ImGuiCond.FirstUseEver;
|
||||||
|
|
||||||
_importDialog = new FileDialogManager { AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking };
|
_importDialog = new FileDialogManager
|
||||||
_exportDialog = new FileDialogManager { AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking };
|
{ AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking };
|
||||||
|
_exportDialog = new FileDialogManager
|
||||||
|
{ AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking };
|
||||||
|
|
||||||
|
_windowSystem.AddWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_windowSystem.RemoveWindow(this);
|
||||||
|
_testConnectionCts?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LanguageChanged()
|
public void LanguageChanged()
|
||||||
@ -62,19 +108,13 @@ namespace Pal.Client.Windows
|
|||||||
WindowName = $"{Localization.Palace_Pal} v{version}{WindowId}";
|
WindowName = $"{Localization.Palace_Pal} v{version}{WindowId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_testConnectionCts?.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
{
|
{
|
||||||
var config = Service.Configuration;
|
_mode = (int)_configuration.Mode;
|
||||||
_mode = (int)config.Mode;
|
_renderer = (int)_configuration.Renderer.SelectedRenderer;
|
||||||
_renderer = (int)config.Renderer.SelectedRenderer;
|
_trapConfig = new ConfigurableMarker(_configuration.DeepDungeons.Traps);
|
||||||
_trapConfig = new ConfigurableMarker(config.DeepDungeons.Traps);
|
_hoardConfig = new ConfigurableMarker(_configuration.DeepDungeons.HoardCoffers);
|
||||||
_hoardConfig = new ConfigurableMarker(config.DeepDungeons.HoardCoffers);
|
_silverConfig = new ConfigurableMarker(_configuration.DeepDungeons.SilverCoffers);
|
||||||
_silverConfig = new ConfigurableMarker(config.DeepDungeons.SilverCoffers);
|
|
||||||
_connectionText = null;
|
_connectionText = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,14 +146,13 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
if (save || saveAndClose)
|
if (save || saveAndClose)
|
||||||
{
|
{
|
||||||
var config = Service.Configuration;
|
_configuration.Mode = (EMode)_mode;
|
||||||
config.Mode = (EMode)_mode;
|
_configuration.Renderer.SelectedRenderer = (ERenderer)_renderer;
|
||||||
config.Renderer.SelectedRenderer = (ERenderer)_renderer;
|
_configuration.DeepDungeons.Traps = _trapConfig.Build();
|
||||||
config.DeepDungeons.Traps = _trapConfig.Build();
|
_configuration.DeepDungeons.HoardCoffers = _hoardConfig.Build();
|
||||||
config.DeepDungeons.HoardCoffers = _hoardConfig.Build();
|
_configuration.DeepDungeons.SilverCoffers = _silverConfig.Build();
|
||||||
config.DeepDungeons.SilverCoffers = _silverConfig.Build();
|
|
||||||
|
|
||||||
Service.ConfigurationManager.Save(config);
|
_configurationManager.Save(_configuration);
|
||||||
|
|
||||||
if (saveAndClose)
|
if (saveAndClose)
|
||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
@ -141,8 +180,10 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
ImGui.BeginDisabled(!_hoardConfig.Show);
|
ImGui.BeginDisabled(!_hoardConfig.Show);
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
ImGui.ColorEdit4(Localization.Config_HoardCoffers_Color, ref _hoardConfig.Color, ImGuiColorEditFlags.NoInputs);
|
ImGui.ColorEdit4(Localization.Config_HoardCoffers_Color, ref _hoardConfig.Color,
|
||||||
ImGui.Checkbox(Localization.Config_HoardCoffers_HideImpossible, ref _hoardConfig.OnlyVisibleAfterPomander);
|
ImGuiColorEditFlags.NoInputs);
|
||||||
|
ImGui.Checkbox(Localization.Config_HoardCoffers_HideImpossible,
|
||||||
|
ref _hoardConfig.OnlyVisibleAfterPomander);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGuiComponents.HelpMarker(Localization.Config_HoardCoffers_HideImpossible_ToolTip);
|
ImGuiComponents.HelpMarker(Localization.Config_HoardCoffers_HideImpossible_ToolTip);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
@ -155,7 +196,8 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
ImGui.BeginDisabled(!_silverConfig.Show);
|
ImGui.BeginDisabled(!_silverConfig.Show);
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
ImGui.ColorEdit4(Localization.Config_SilverCoffer_Color, ref _silverConfig.Color, ImGuiColorEditFlags.NoInputs);
|
ImGui.ColorEdit4(Localization.Config_SilverCoffer_Color, ref _silverConfig.Color,
|
||||||
|
ImGuiColorEditFlags.NoInputs);
|
||||||
ImGui.Checkbox(Localization.Config_SilverCoffer_Filled, ref _silverConfig.Fill);
|
ImGui.Checkbox(Localization.Config_SilverCoffer_Filled, ref _silverConfig.Fill);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
ImGui.Unindent();
|
ImGui.Unindent();
|
||||||
@ -172,7 +214,8 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
private void DrawCommunityTab(ref bool saveAndClose)
|
private void DrawCommunityTab(ref bool saveAndClose)
|
||||||
{
|
{
|
||||||
if (BeginTabItemEx($"{Localization.ConfigTab_Community}###TabCommunity", _switchToCommunityTab ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None))
|
if (BeginTabItemEx($"{Localization.ConfigTab_Community}###TabCommunity",
|
||||||
|
_switchToCommunityTab ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None))
|
||||||
{
|
{
|
||||||
_switchToCommunityTab = false;
|
_switchToCommunityTab = false;
|
||||||
|
|
||||||
@ -180,12 +223,13 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.TextWrapped(Localization.Explanation_4);
|
ImGui.TextWrapped(Localization.Explanation_4);
|
||||||
|
|
||||||
ImGui.RadioButton(Localization.Config_UploadMyDiscoveries_ShowOtherTraps, ref _mode, (int)EMode.Online);
|
ImGui.RadioButton(Localization.Config_UploadMyDiscoveries_ShowOtherTraps, ref _mode, (int)EMode.Online);
|
||||||
ImGui.RadioButton(Localization.Config_NeverUploadDiscoveries_ShowMyTraps, ref _mode, (int)EMode.Offline);
|
ImGui.RadioButton(Localization.Config_NeverUploadDiscoveries_ShowMyTraps, ref _mode,
|
||||||
|
(int)EMode.Offline);
|
||||||
saveAndClose = ImGui.Button(Localization.SaveAndClose);
|
saveAndClose = ImGui.Button(Localization.SaveAndClose);
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.BeginDisabled(Service.Configuration.Mode != EMode.Online);
|
ImGui.BeginDisabled(_configuration.Mode != EMode.Online);
|
||||||
if (ImGui.Button(Localization.Config_TestConnection))
|
if (ImGui.Button(Localization.Config_TestConnection))
|
||||||
TestConnection();
|
TestConnection();
|
||||||
|
|
||||||
@ -205,7 +249,8 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.TextWrapped(Localization.Config_ImportExplanation2);
|
ImGui.TextWrapped(Localization.Config_ImportExplanation2);
|
||||||
ImGui.TextWrapped(Localization.Config_ImportExplanation3);
|
ImGui.TextWrapped(Localization.Config_ImportExplanation3);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextWrapped(string.Format(Localization.Config_ImportDownloadLocation, "https://github.com/carvelli/PalacePal/releases/"));
|
ImGui.TextWrapped(string.Format(Localization.Config_ImportDownloadLocation,
|
||||||
|
"https://github.com/carvelli/PalacePal/releases/"));
|
||||||
if (ImGui.Button(Localization.Config_Import_VisitGitHub))
|
if (ImGui.Button(Localization.Config_Import_VisitGitHub))
|
||||||
GenericHelpers.ShellStart("https://github.com/carvelli/PalacePal/releases/latest");
|
GenericHelpers.ShellStart("https://github.com/carvelli/PalacePal/releases/latest");
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
@ -215,14 +260,16 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
|
||||||
{
|
{
|
||||||
_importDialog.OpenFileDialog(Localization.Palace_Pal, $"{Localization.Palace_Pal} (*.pal) {{.pal}}", (success, paths) =>
|
_importDialog.OpenFileDialog(Localization.Palace_Pal, $"{Localization.Palace_Pal} (*.pal) {{.pal}}",
|
||||||
|
(success, paths) =>
|
||||||
{
|
{
|
||||||
if (success && paths.Count == 1)
|
if (success && paths.Count == 1)
|
||||||
{
|
{
|
||||||
_openImportPath = paths.First();
|
_openImportPath = paths.First();
|
||||||
}
|
}
|
||||||
}, selectionCountMax: 1, startPath: _openImportDialogStartPath, isModal: false);
|
}, selectionCountMax: 1, startPath: _openImportDialogStartPath, isModal: false);
|
||||||
_openImportDialogStartPath = null; // only use this once, FileDialogManager will save path between calls
|
_openImportDialogStartPath =
|
||||||
|
null; // only use this once, FileDialogManager will save path between calls
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.BeginDisabled(string.IsNullOrEmpty(_openImportPath) || !File.Exists(_openImportPath));
|
ImGui.BeginDisabled(string.IsNullOrEmpty(_openImportPath) || !File.Exists(_openImportPath));
|
||||||
@ -230,11 +277,13 @@ namespace Pal.Client.Windows
|
|||||||
DoImport(_openImportPath);
|
DoImport(_openImportPath);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
var importHistory = Service.Configuration.ImportHistory.OrderByDescending(x => x.ImportedAt).ThenBy(x => x.Id).FirstOrDefault();
|
var importHistory = _configuration.ImportHistory.OrderByDescending(x => x.ImportedAt)
|
||||||
|
.ThenBy(x => x.Id).FirstOrDefault();
|
||||||
if (importHistory != null)
|
if (importHistory != null)
|
||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextWrapped(string.Format(Localization.Config_UndoImportExplanation1, importHistory.ImportedAt, importHistory.RemoteUrl, importHistory.ExportedAt));
|
ImGui.TextWrapped(string.Format(Localization.Config_UndoImportExplanation1,
|
||||||
|
importHistory.ImportedAt, importHistory.RemoteUrl, importHistory.ExportedAt));
|
||||||
ImGui.TextWrapped(Localization.Config_UndoImportExplanation2);
|
ImGui.TextWrapped(Localization.Config_UndoImportExplanation2);
|
||||||
if (ImGui.Button(Localization.Config_UndoImport))
|
if (ImGui.Button(Localization.Config_UndoImport))
|
||||||
UndoImport(importHistory.Id);
|
UndoImport(importHistory.Id);
|
||||||
@ -246,7 +295,8 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
private void DrawExportTab()
|
private void DrawExportTab()
|
||||||
{
|
{
|
||||||
if (Service.RemoteApi.HasRoleOnCurrentServer("export:run") && ImGui.BeginTabItem($"{Localization.ConfigTab_Export}###TabExport"))
|
if (_configuration.HasRoleOnCurrentServer("export:run") &&
|
||||||
|
ImGui.BeginTabItem($"{Localization.ConfigTab_Export}###TabExport"))
|
||||||
{
|
{
|
||||||
string todaysFileName = $"export-{DateTime.Today:yyyy-MM-dd}.pal";
|
string todaysFileName = $"export-{DateTime.Today:yyyy-MM-dd}.pal";
|
||||||
if (string.IsNullOrEmpty(_saveExportPath) && !string.IsNullOrEmpty(_saveExportDialogStartPath))
|
if (string.IsNullOrEmpty(_saveExportPath) && !string.IsNullOrEmpty(_saveExportDialogStartPath))
|
||||||
@ -259,14 +309,16 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
|
||||||
{
|
{
|
||||||
_importDialog.SaveFileDialog(Localization.Palace_Pal, $"{Localization.Palace_Pal} (*.pal) {{.pal}}", todaysFileName, "pal", (success, path) =>
|
_importDialog.SaveFileDialog(Localization.Palace_Pal, $"{Localization.Palace_Pal} (*.pal) {{.pal}}",
|
||||||
|
todaysFileName, "pal", (success, path) =>
|
||||||
{
|
{
|
||||||
if (success && !string.IsNullOrEmpty(path))
|
if (success && !string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
_saveExportPath = path;
|
_saveExportPath = path;
|
||||||
}
|
}
|
||||||
}, startPath: _saveExportDialogStartPath, isModal: false);
|
}, startPath: _saveExportDialogStartPath, isModal: false);
|
||||||
_saveExportDialogStartPath = null; // only use this once, FileDialogManager will save path between calls
|
_saveExportDialogStartPath =
|
||||||
|
null; // only use this once, FileDialogManager will save path between calls
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.BeginDisabled(string.IsNullOrEmpty(_saveExportPath) || File.Exists(_saveExportPath));
|
ImGui.BeginDisabled(string.IsNullOrEmpty(_saveExportPath) || File.Exists(_saveExportPath));
|
||||||
@ -283,8 +335,11 @@ namespace Pal.Client.Windows
|
|||||||
if (ImGui.BeginTabItem($"{Localization.ConfigTab_Renderer}###TabRenderer"))
|
if (ImGui.BeginTabItem($"{Localization.ConfigTab_Renderer}###TabRenderer"))
|
||||||
{
|
{
|
||||||
ImGui.Text(Localization.Config_SelectRenderBackend);
|
ImGui.Text(Localization.Config_SelectRenderBackend);
|
||||||
ImGui.RadioButton($"{Localization.Config_Renderer_Splatoon} ({Localization.Config_Renderer_Splatoon_Hint})", ref _renderer, (int)ERenderer.Splatoon);
|
ImGui.RadioButton(
|
||||||
ImGui.RadioButton($"{Localization.Config_Renderer_Simple} ({Localization.Config_Renderer_Simple_Hint})", ref _renderer, (int)ERenderer.Simple);
|
$"{Localization.Config_Renderer_Splatoon} ({Localization.Config_Renderer_Splatoon_Hint})",
|
||||||
|
ref _renderer, (int)ERenderer.Splatoon);
|
||||||
|
ImGui.RadioButton($"{Localization.Config_Renderer_Simple} ({Localization.Config_Renderer_Simple_Hint})",
|
||||||
|
ref _renderer, (int)ERenderer.Simple);
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
@ -294,9 +349,9 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.Text(Localization.Config_Splatoon_Test);
|
ImGui.Text(Localization.Config_Splatoon_Test);
|
||||||
ImGui.BeginDisabled(!(Service.Plugin.Renderer is IDrawDebugItems));
|
ImGui.BeginDisabled(!(_renderAdapter.Implementation is IDrawDebugItems));
|
||||||
if (ImGui.Button(Localization.Config_Splatoon_DrawCircles))
|
if (ImGui.Button(Localization.Config_Splatoon_DrawCircles))
|
||||||
(Service.Plugin.Renderer as IDrawDebugItems)?.DrawDebugItems(_trapConfig.Color, _hoardConfig.Color);
|
(_renderAdapter.Implementation as IDrawDebugItems)?.DrawDebugItems(_trapConfig.Color, _hoardConfig.Color);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
@ -307,39 +362,43 @@ namespace Pal.Client.Windows
|
|||||||
{
|
{
|
||||||
if (ImGui.BeginTabItem($"{Localization.ConfigTab_Debug}###TabDebug"))
|
if (ImGui.BeginTabItem($"{Localization.ConfigTab_Debug}###TabDebug"))
|
||||||
{
|
{
|
||||||
var plugin = Service.Plugin;
|
if (_territoryState.IsInDeepDungeon())
|
||||||
if (plugin.IsInDeepDungeon())
|
|
||||||
{
|
{
|
||||||
ImGui.Text($"You are in a deep dungeon, territory type {plugin.LastTerritory}.");
|
ImGui.Text($"You are in a deep dungeon, territory type {_territoryState.LastTerritory}.");
|
||||||
ImGui.Text($"Sync State = {plugin.TerritorySyncState}");
|
ImGui.Text($"Sync State = {_territoryState.TerritorySyncState}");
|
||||||
ImGui.Text($"{plugin.DebugMessage}");
|
ImGui.Text($"{_debugState.DebugMessage}");
|
||||||
|
|
||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloor))
|
if (_floorService.FloorMarkers.TryGetValue(_territoryState.LastTerritory, out var currentFloor))
|
||||||
{
|
{
|
||||||
if (_trapConfig.Show)
|
if (_trapConfig.Show)
|
||||||
{
|
{
|
||||||
int traps = currentFloor.Markers.Count(x => x.Type == Marker.EType.Trap);
|
int traps = currentFloor.Markers.Count(x => x.Type == Marker.EType.Trap);
|
||||||
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
|
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hoardConfig.Show)
|
if (_hoardConfig.Show)
|
||||||
{
|
{
|
||||||
int hoardCoffers = currentFloor.Markers.Count(x => x.Type == Marker.EType.Hoard);
|
int hoardCoffers = currentFloor.Markers.Count(x => x.Type == Marker.EType.Hoard);
|
||||||
ImGui.Text($"{hoardCoffers} known hoard coffer{(hoardCoffers == 1 ? "" : "s")}");
|
ImGui.Text($"{hoardCoffers} known hoard coffer{(hoardCoffers == 1 ? "" : "s")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_silverConfig.Show)
|
if (_silverConfig.Show)
|
||||||
{
|
{
|
||||||
int silverCoffers = plugin.EphemeralMarkers.Count(x => x.Type == Marker.EType.SilverCoffer);
|
int silverCoffers = _floorService.EphemeralMarkers.Count(x => x.Type == Marker.EType.SilverCoffer);
|
||||||
ImGui.Text($"{silverCoffers} silver coffer{(silverCoffers == 1 ? "" : "s")} visible on current floor");
|
ImGui.Text(
|
||||||
|
$"{silverCoffers} silver coffer{(silverCoffers == 1 ? "" : "s")} visible on current floor");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Text($"Pomander of Sight: {plugin.PomanderOfSight}");
|
ImGui.Text($"Pomander of Sight: {_territoryState.PomanderOfSight}");
|
||||||
ImGui.Text($"Pomander of Intuition: {plugin.PomanderOfIntuition}");
|
ImGui.Text($"Pomander of Intuition: {_territoryState.PomanderOfIntuition}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ImGui.Text("Could not query current trap/coffer count.");
|
ImGui.Text("Could not query current trap/coffer count.");
|
||||||
|
|
||||||
ImGui.Unindent();
|
ImGui.Unindent();
|
||||||
ImGui.TextWrapped("Traps and coffers may not be discovered even after using a pomander if they're far away (around 1,5-2 rooms).");
|
ImGui.TextWrapped(
|
||||||
|
"Traps and coffers may not be discovered even after using a pomander if they're far away (around 1,5-2 rooms).");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ImGui.Text(Localization.Config_Debug_NotInADeepDungeon);
|
ImGui.Text(Localization.Config_Debug_NotInADeepDungeon);
|
||||||
@ -378,7 +437,7 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_connectionText = await Service.RemoteApi.VerifyConnection(cts.Token);
|
_connectionText = await _remoteApi.VerifyConnection(cts.Token);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -388,19 +447,20 @@ namespace Pal.Client.Windows
|
|||||||
_connectionText = e.ToString();
|
_connectionText = e.ToString();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
PluginLog.Warning(e, "Could not establish a remote connection, but user also clicked 'test connection' again so not updating UI");
|
PluginLog.Warning(e,
|
||||||
|
"Could not establish a remote connection, but user also clicked 'test connection' again so not updating UI");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoImport(string sourcePath)
|
private void DoImport(string sourcePath)
|
||||||
{
|
{
|
||||||
Service.Plugin.EarlyEventQueue.Enqueue(new QueuedImport(sourcePath));
|
_frameworkService.EarlyEventQueue.Enqueue(new QueuedImport(sourcePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UndoImport(Guid importId)
|
private void UndoImport(Guid importId)
|
||||||
{
|
{
|
||||||
Service.Plugin.EarlyEventQueue.Enqueue(new QueuedUndoImport(importId));
|
_frameworkService.EarlyEventQueue.Enqueue(new QueuedUndoImport(importId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoExport(string destinationPath)
|
private void DoExport(string destinationPath)
|
||||||
@ -409,28 +469,28 @@ namespace Pal.Client.Windows
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
(bool success, ExportRoot export) = await Service.RemoteApi.DoExport();
|
(bool success, ExportRoot export) = await _remoteApi.DoExport();
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
await using var output = File.Create(destinationPath);
|
await using var output = File.Create(destinationPath);
|
||||||
export.WriteTo(output);
|
export.WriteTo(output);
|
||||||
|
|
||||||
Service.Chat.Print($"Export saved as {destinationPath}.");
|
_chatGui.Print($"Export saved as {destinationPath}.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Service.Chat.PrintError("Export failed due to server error.");
|
_chatGui.PrintError("Export failed due to server error.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
PluginLog.Error(e, "Export failed");
|
PluginLog.Error(e, "Export failed");
|
||||||
Service.Chat.PrintError($"Export failed: {e}");
|
_chatGui.PrintError($"Export failed: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConfigurableMarker
|
private sealed class ConfigurableMarker
|
||||||
{
|
{
|
||||||
public bool Show;
|
public bool Show;
|
||||||
public Vector4 Color;
|
public Vector4 Color;
|
||||||
|
@ -13,13 +13,17 @@ using System.Reflection;
|
|||||||
|
|
||||||
namespace Pal.Client.Windows
|
namespace Pal.Client.Windows
|
||||||
{
|
{
|
||||||
internal class StatisticsWindow : Window, ILanguageChanged
|
internal class StatisticsWindow : Window, IDisposable, ILanguageChanged
|
||||||
{
|
{
|
||||||
private const string WindowId = "###PalacePalStats";
|
private const string WindowId = "###PalacePalStats";
|
||||||
|
private readonly WindowSystem _windowSystem;
|
||||||
private readonly SortedDictionary<ETerritoryType, TerritoryStatistics> _territoryStatistics = new();
|
private readonly SortedDictionary<ETerritoryType, TerritoryStatistics> _territoryStatistics = new();
|
||||||
|
|
||||||
public StatisticsWindow() : base(WindowId)
|
public StatisticsWindow(WindowSystem windowSystem)
|
||||||
|
: base(WindowId)
|
||||||
{
|
{
|
||||||
|
_windowSystem = windowSystem;
|
||||||
|
|
||||||
LanguageChanged();
|
LanguageChanged();
|
||||||
|
|
||||||
Size = new Vector2(500, 500);
|
Size = new Vector2(500, 500);
|
||||||
@ -30,8 +34,13 @@ namespace Pal.Client.Windows
|
|||||||
{
|
{
|
||||||
_territoryStatistics[territory] = new TerritoryStatistics(territory.ToString());
|
_territoryStatistics[territory] = new TerritoryStatistics(territory.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_windowSystem.AddWindow(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _windowSystem.RemoveWindow(this);
|
||||||
|
|
||||||
public void LanguageChanged()
|
public void LanguageChanged()
|
||||||
=> WindowName = $"{Localization.Palace_Pal} - {Localization.Statistics}{WindowId}";
|
=> WindowName = $"{Localization.Palace_Pal} - {Localization.Statistics}{WindowId}";
|
||||||
|
|
||||||
@ -39,8 +48,10 @@ namespace Pal.Client.Windows
|
|||||||
{
|
{
|
||||||
if (ImGui.BeginTabBar("Tabs"))
|
if (ImGui.BeginTabBar("Tabs"))
|
||||||
{
|
{
|
||||||
DrawDungeonStats("Palace of the Dead", Localization.PalaceOfTheDead, ETerritoryType.Palace_1_10, ETerritoryType.Palace_191_200);
|
DrawDungeonStats("Palace of the Dead", Localization.PalaceOfTheDead, ETerritoryType.Palace_1_10,
|
||||||
DrawDungeonStats("Heaven on High", Localization.HeavenOnHigh, ETerritoryType.HeavenOnHigh_1_10, ETerritoryType.HeavenOnHigh_91_100);
|
ETerritoryType.Palace_191_200);
|
||||||
|
DrawDungeonStats("Heaven on High", Localization.HeavenOnHigh, ETerritoryType.HeavenOnHigh_1_10,
|
||||||
|
ETerritoryType.HeavenOnHigh_91_100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +59,8 @@ namespace Pal.Client.Windows
|
|||||||
{
|
{
|
||||||
if (ImGui.BeginTabItem($"{name}###{id}"))
|
if (ImGui.BeginTabItem($"{name}###{id}"))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTable($"TrapHoardStatistics{id}", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
|
if (ImGui.BeginTable($"TrapHoardStatistics{id}", 4,
|
||||||
|
ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn(Localization.Statistics_TerritoryId);
|
ImGui.TableSetupColumn(Localization.Statistics_TerritoryId);
|
||||||
ImGui.TableSetupColumn(Localization.Statistics_InstanceName);
|
ImGui.TableSetupColumn(Localization.Statistics_InstanceName);
|
||||||
@ -56,7 +68,9 @@ namespace Pal.Client.Windows
|
|||||||
ImGui.TableSetupColumn(Localization.Statistics_HoardCoffers);
|
ImGui.TableSetupColumn(Localization.Statistics_HoardCoffers);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var (territoryType, stats) in _territoryStatistics.Where(x => x.Key >= minTerritory && x.Key <= maxTerritory).OrderBy(x => x.Key.GetOrder() ?? (int)x.Key))
|
foreach (var (territoryType, stats) in _territoryStatistics
|
||||||
|
.Where(x => x.Key >= minTerritory && x.Key <= maxTerritory)
|
||||||
|
.OrderBy(x => x.Key.GetOrder() ?? (int)x.Key))
|
||||||
{
|
{
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
if (ImGui.TableNextColumn())
|
if (ImGui.TableNextColumn())
|
||||||
@ -71,8 +85,10 @@ namespace Pal.Client.Windows
|
|||||||
if (ImGui.TableNextColumn())
|
if (ImGui.TableNextColumn())
|
||||||
ImGui.Text(stats.HoardCofferCount?.ToString() ?? "-");
|
ImGui.Text(stats.HoardCofferCount?.ToString() ?? "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +103,8 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
foreach (var floor in floorStatistics)
|
foreach (var floor in floorStatistics)
|
||||||
{
|
{
|
||||||
if (_territoryStatistics.TryGetValue((ETerritoryType)floor.TerritoryType, out TerritoryStatistics? territoryStatistics))
|
if (_territoryStatistics.TryGetValue((ETerritoryType)floor.TerritoryType,
|
||||||
|
out TerritoryStatistics? territoryStatistics))
|
||||||
{
|
{
|
||||||
territoryStatistics.TrapCount = floor.TrapCount;
|
territoryStatistics.TrapCount = floor.TrapCount;
|
||||||
territoryStatistics.HoardCofferCount = floor.HoardCount;
|
territoryStatistics.HoardCofferCount = floor.HoardCount;
|
||||||
@ -97,7 +114,7 @@ namespace Pal.Client.Windows
|
|||||||
|
|
||||||
private class TerritoryStatistics
|
private class TerritoryStatistics
|
||||||
{
|
{
|
||||||
public string TerritoryName { get; set; }
|
public string TerritoryName { get; }
|
||||||
public uint? TrapCount { get; set; }
|
public uint? TrapCount { get; set; }
|
||||||
public uint? HoardCofferCount { get; set; }
|
public uint? HoardCofferCount { get; set; }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user