New configuration format

This commit is contained in:
Liza 2023-02-15 02:38:04 +01:00
parent 4be0bdf637
commit b0de113ad2
18 changed files with 510 additions and 223 deletions

View File

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using Dalamud.Logging;
namespace Pal.Client.Configuration
{
public class AccountConfigurationV7 : IAccountConfiguration
{
[JsonConstructor]
public AccountConfigurationV7()
{
}
public AccountConfigurationV7(string server, Guid accountId)
{
Server = server;
EncryptedId = EncryptAccountId(accountId);
}
[Obsolete("for V1 import")]
public AccountConfigurationV7(string server, string accountId)
{
Server = server;
if (accountId.StartsWith("s:"))
EncryptedId = accountId;
else if (Guid.TryParse(accountId, out Guid guid))
EncryptedId = EncryptAccountId(guid);
else
throw new InvalidOperationException("invalid account id format");
}
public string EncryptedId { get; init; } = null!;
public string Server { get; set; } = null!;
[JsonIgnore] public bool IsUsable => DecryptAccountId(EncryptedId) != null;
[JsonIgnore] public Guid AccountId => DecryptAccountId(EncryptedId) ?? throw new InvalidOperationException();
public List<string> CachedRoles { get; set; } = new();
private Guid? DecryptAccountId(string id)
{
if (Guid.TryParse(id, out Guid guid) && guid != Guid.Empty)
return guid;
if (!id.StartsWith("s:"))
throw new InvalidOperationException("invalid prefix");
try
{
byte[] guidBytes = ProtectedData.Unprotect(Convert.FromBase64String(id.Substring(2)),
ConfigurationData.Entropy, DataProtectionScope.CurrentUser);
return new Guid(guidBytes);
}
catch (CryptographicException e)
{
PluginLog.Verbose(e, $"Could not load account id {id}");
return null;
}
}
private string EncryptAccountId(Guid g)
{
try
{
byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), ConfigurationData.Entropy,
DataProtectionScope.CurrentUser);
return $"s:{Convert.ToBase64String(guidBytes)}";
}
catch (CryptographicException)
{
return g.ToString();
}
}
}
}

View File

@ -0,0 +1,7 @@
namespace Pal.Client.Configuration
{
internal static class ConfigurationData
{
internal static readonly byte[] Entropy = { 0x22, 0x4b, 0xe7, 0x21, 0x44, 0x83, 0x69, 0x55, 0x80, 0x38 };
}
}

View File

@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using Dalamud.Logging;
using Dalamud.Plugin;
using ImGuiNET;
using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Pal.Client.Configuration
{
internal class ConfigurationManager
{
private readonly DalamudPluginInterface _pluginInterface;
public ConfigurationManager(DalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
}
public string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
#pragma warning disable CS0612
#pragma warning disable CS0618
public void Migrate()
{
if (_pluginInterface.ConfigFile.Exists)
{
PluginLog.Information("Migrating config file from v1-v6 format");
ConfigurationV1 configurationV1 =
JsonConvert.DeserializeObject<ConfigurationV1>(
File.ReadAllText(_pluginInterface.ConfigFile.FullName)) ?? new ConfigurationV1();
configurationV1.Migrate();
configurationV1.Save();
var v7 = MigrateToV7(configurationV1);
Save(v7);
File.Move(_pluginInterface.ConfigFile.FullName, _pluginInterface.ConfigFile.FullName + ".old", true);
}
}
public IPalacePalConfiguration Load()
{
return JsonSerializer.Deserialize<ConfigurationV7>(File.ReadAllText(ConfigPath, Encoding.UTF8)) ??
new ConfigurationV7();
}
public void Save(IConfigurationInConfigDirectory config)
{
File.WriteAllText(ConfigPath,
JsonSerializer.Serialize(config, config.GetType(), new JsonSerializerOptions { WriteIndented = true }),
Encoding.UTF8);
}
private ConfigurationV7 MigrateToV7(ConfigurationV1 v1)
{
ConfigurationV7 v7 = new()
{
Version = 7,
FirstUse = v1.FirstUse,
Mode = v1.Mode,
BetaKey = v1.BetaKey,
DeepDungeons = new DeepDungeonConfiguration
{
Traps = new MarkerConfiguration
{
Show = v1.ShowTraps,
Color = ImGui.ColorConvertFloat4ToU32(v1.TrapColor),
Fill = false
},
HoardCoffers = new MarkerConfiguration
{
Show = v1.ShowHoard,
Color = ImGui.ColorConvertFloat4ToU32(v1.HoardColor),
Fill = false
},
SilverCoffers = new MarkerConfiguration
{
Show = v1.ShowSilverCoffers,
Color = ImGui.ColorConvertFloat4ToU32(v1.SilverCofferColor),
Fill = v1.FillSilverCoffers
}
}
};
foreach (var (server, oldAccount) in v1.Accounts)
{
string? accountId = oldAccount.Id;
if (string.IsNullOrEmpty(accountId))
continue;
IAccountConfiguration newAccount = v7.CreateAccount(server, accountId);
newAccount.CachedRoles = oldAccount.CachedRoles.ToList();
}
// TODO Migrate ImportHistory
return v7;
}
#pragma warning restore CS0618
#pragma warning restore CS0612
}
}

View File

@ -1,5 +1,4 @@
using Dalamud.Configuration;
using Dalamud.Logging;
using Dalamud.Logging;
using ECommons.Schedulers;
using Newtonsoft.Json;
using Pal.Client.Scheduled;
@ -9,15 +8,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using Pal.Client.Extensions;
namespace Pal.Client
namespace Pal.Client.Configuration
{
public class Configuration : IPluginConfiguration
[Obsolete]
public class ConfigurationV1
{
private static readonly byte[] Entropy = { 0x22, 0x4b, 0xe7, 0x21, 0x44, 0x83, 0x69, 0x55, 0x80, 0x38 };
public int Version { get; set; } = 6;
#region Saved configuration values
@ -55,7 +52,6 @@ namespace Pal.Client
public string BetaKey { get; set; } = "";
#endregion
#pragma warning disable CS0612 // Type or member is obsolete
public void Migrate()
{
if (Version == 1)
@ -78,7 +74,7 @@ namespace Pal.Client
Accounts = AccountIds.ToDictionary(x => x.Key, x => new AccountInfo
{
Id = x.Value
Id = x.Value.ToString() // encryption happens in V7 migration at latest
});
Version = 3;
Save();
@ -140,108 +136,23 @@ namespace Pal.Client
Save();
}
}
#pragma warning restore CS0612 // Type or member is obsolete
public void Save()
{
Service.PluginInterface.SavePluginConfig(this);
File.WriteAllText(Service.PluginInterface.ConfigFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Objects
}));
Service.Plugin.EarlyEventQueue.Enqueue(new QueuedConfigUpdate());
}
public enum EMode
{
/// <summary>
/// Fetches trap locations from remote server.
/// </summary>
Online = 1,
/// <summary>
/// Only shows traps found by yourself uisng a pomander of sight.
/// </summary>
Offline = 2,
}
public enum ERenderer
{
/// <see cref="Rendering.SimpleRenderer"/>
Simple = 0,
/// <see cref="Rendering.SplatoonRenderer"/>
Splatoon = 1,
}
public class AccountInfo
{
[JsonConverter(typeof(AccountIdConverter))]
public Guid? Id { get; set; }
/// <summary>
/// This is taken from the JWT, and is only refreshed on a successful login.
///
/// If you simply reload the plugin without any server interaction, this doesn't change.
///
/// This has no impact on what roles the JWT actually contains, but is just to make it
/// easier to draw a consistent UI. The server will still reject unauthorized calls.
/// </summary>
public string? Id { get; set; }
public List<string> CachedRoles { get; set; } = new();
}
public class AccountIdConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
string? text = reader.Value?.ToString();
if (string.IsNullOrEmpty(text))
return null;
if (Guid.TryParse(text, out Guid guid) && guid != Guid.Empty)
return guid;
if (text.StartsWith("s:"))
{
try
{
byte[] guidBytes = ProtectedData.Unprotect(Convert.FromBase64String(text.Substring(2)), Entropy, DataProtectionScope.CurrentUser);
return new Guid(guidBytes);
}
catch (CryptographicException e)
{
PluginLog.Error(e, "Could not load account id");
return null;
}
}
}
throw new JsonSerializationException();
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
Guid g = (Guid)value;
string text;
try
{
byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), Entropy, DataProtectionScope.CurrentUser);
text = $"s:{Convert.ToBase64String(guidBytes)}";
}
catch (CryptographicException)
{
text = g.ToString();
}
writer.WriteValue(text);
}
}
public class ImportHistoryEntry
{
public Guid Id { get; set; }

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
namespace Pal.Client.Configuration;
public class ConfigurationV7 : IPalacePalConfiguration, IConfigurationInConfigDirectory
{
public int Version { get; set; } = 7;
public bool FirstUse { get; set; } = true;
public EMode Mode { get; set; }
public string BetaKey { get; init; } = "";
public DeepDungeonConfiguration DeepDungeons { get; set; } = new();
public RendererConfiguration Renderer { get; set; } = new();
public List<AccountConfigurationV7> Accounts { get; set; } = new();
[JsonIgnore]
[Obsolete]
public List<ConfigurationV1.ImportHistoryEntry> ImportHistory { get; set; } = new();
public IAccountConfiguration CreateAccount(string server, Guid accountId)
{
var account = new AccountConfigurationV7(server, accountId);
Accounts.Add(account);
return account;
}
[Obsolete("for V1 import")]
internal IAccountConfiguration CreateAccount(string server, string accountId)
{
var account = new AccountConfigurationV7(server, accountId);
Accounts.Add(account);
return account;
}
public IAccountConfiguration? FindAccount(string server)
{
return Accounts.FirstOrDefault(a => a.Server == server && a.IsUsable);
}
public void RemoveAccount(string server)
{
Accounts.RemoveAll(a => a.Server == server && a.IsUsable);
}
}

View File

@ -0,0 +1,15 @@
namespace Pal.Client.Configuration
{
public enum EMode
{
/// <summary>
/// Fetches trap locations from remote server.
/// </summary>
Online = 1,
/// <summary>
/// Only shows traps found by yourself uisng a pomander of sight.
/// </summary>
Offline = 2,
}
}

View File

@ -0,0 +1,11 @@
namespace Pal.Client.Configuration
{
public enum ERenderer
{
/// <see cref="Rendering.SimpleRenderer"/>
Simple = 0,
/// <see cref="Rendering.SplatoonRenderer"/>
Splatoon = 1,
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using ImGuiNET;
namespace Pal.Client.Configuration
{
public interface IVersioned
{
int Version { get; set; }
}
public interface IConfigurationInConfigDirectory : IVersioned
{
}
public interface IPalacePalConfiguration : IConfigurationInConfigDirectory
{
bool FirstUse { get; set; }
EMode Mode { get; set; }
string BetaKey { get; }
DeepDungeonConfiguration DeepDungeons { get; set; }
RendererConfiguration Renderer { get; set; }
[Obsolete]
List<ConfigurationV1.ImportHistoryEntry> ImportHistory { get; }
IAccountConfiguration CreateAccount(string server, Guid accountId);
IAccountConfiguration? FindAccount(string server);
void RemoveAccount(string server);
}
public class DeepDungeonConfiguration
{
public MarkerConfiguration Traps { get; set; } = new()
{
Show = true,
Color = ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0, 0, 0.4f)),
OnlyVisibleAfterPomander = true,
Fill = false
};
public MarkerConfiguration HoardCoffers { get; set; } = new()
{
Show = true,
Color = ImGui.ColorConvertFloat4ToU32(new Vector4(0, 1, 1, 0.4f)),
OnlyVisibleAfterPomander = true,
Fill = false
};
public MarkerConfiguration SilverCoffers { get; set; } = new()
{
Show = false,
Color = ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 0.4f)),
OnlyVisibleAfterPomander = false,
Fill = true
};
}
public class MarkerConfiguration
{
public bool Show { get; set; }
public uint Color { get; set; }
public bool OnlyVisibleAfterPomander { get; set; }
public bool Fill { get; set; }
}
public class RendererConfiguration
{
public ERenderer SelectedRenderer { get; set; } = ERenderer.Splatoon;
}
public interface IAccountConfiguration
{
public bool IsUsable { get; }
public string Server { get; }
public Guid AccountId { get; }
/// <summary>
/// This is taken from the JWT, and is only refreshed on a successful login.
///
/// If you simply reload the plugin without any server interaction, this doesn't change.
///
/// This has no impact on what roles the JWT actually contains, but is just to make it
/// easier to draw a consistent UI. The server will still reject unauthorized calls.
/// </summary>
public List<string> CachedRoles { get; set; }
}
}

View File

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using Pal.Client.Extensions;
using Pal.Client.Properties;
using Pal.Client.Configuration;
namespace Pal.Client.Net
{
@ -18,7 +19,7 @@ namespace Pal.Client.Net
{
private async Task<(bool Success, string Error)> TryConnect(CancellationToken cancellationToken, ILoggerFactory? loggerFactory = null, bool retry = true)
{
if (Service.Configuration.Mode != Configuration.EMode.Online)
if (Service.Configuration.Mode != EMode.Online)
{
PluginLog.Debug("TryConnect: Not Online, not attempting to establish a connection");
return (false, Localization.ConnectionError_NotOnline);
@ -46,19 +47,20 @@ namespace Pal.Client.Net
cancellationToken.ThrowIfCancellationRequested();
var accountClient = new AccountService.AccountServiceClient(_channel);
if (AccountId == null)
IAccountConfiguration? configuredAccount = Service.Configuration.FindAccount(RemoteUrl);
if (configuredAccount == null)
{
PluginLog.Information($"TryConnect: No account information saved for {RemoteUrl}, creating new account");
var createAccountReply = await accountClient.CreateAccountAsync(new CreateAccountRequest(), headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken);
if (createAccountReply.Success)
{
Account = new Configuration.AccountInfo
{
Id = Guid.Parse(createAccountReply.AccountId),
};
PluginLog.Information($"TryConnect: Account created with id {FormattedPartialAccountId}");
if (Guid.TryParse(createAccountReply.AccountId, out Guid accountId))
throw new InvalidOperationException("invalid account id returned");
Service.Configuration.Save();
configuredAccount = Service.Configuration.CreateAccount(RemoteUrl, accountId);
PluginLog.Information($"TryConnect: Account created with id {accountId.ToPartialId()}");
Service.ConfigurationManager.Save(Service.Configuration);
}
else
{
@ -74,27 +76,24 @@ namespace Pal.Client.Net
cancellationToken.ThrowIfCancellationRequested();
if (AccountId == null)
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (configuredAccount == null)
{
PluginLog.Warning("TryConnect: No account id to login with");
PluginLog.Warning("TryConnect: No account to login with");
return (false, Localization.ConnectionError_CreateAccountReturnedNoId);
}
if (!_loginInfo.IsValid)
{
PluginLog.Information($"TryConnect: Logging in with account id {FormattedPartialAccountId}");
LoginReply loginReply = await accountClient.LoginAsync(new LoginRequest { AccountId = AccountId?.ToString() }, headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken);
PluginLog.Information($"TryConnect: Logging in with account id {configuredAccount.AccountId.ToPartialId()}");
LoginReply loginReply = await accountClient.LoginAsync(new LoginRequest { AccountId = configuredAccount.AccountId.ToString() }, headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken);
if (loginReply.Success)
{
PluginLog.Information($"TryConnect: Login successful with account id: {FormattedPartialAccountId}");
PluginLog.Information($"TryConnect: Login successful with account id: {configuredAccount.AccountId.ToPartialId()}");
_loginInfo = new LoginInfo(loginReply.AuthToken);
var account = Account;
if (account != null)
{
account.CachedRoles = _loginInfo.Claims?.Roles.ToList() ?? new List<string>();
Service.Configuration.Save();
}
configuredAccount.CachedRoles = _loginInfo.Claims?.Roles.ToList() ?? new List<string>();
Service.ConfigurationManager.Save(Service.Configuration);
}
else
{
@ -102,8 +101,8 @@ namespace Pal.Client.Net
_loginInfo = new LoginInfo(null);
if (loginReply.Error == LoginError.InvalidAccountId)
{
Account = null;
Service.Configuration.Save();
Service.Configuration.RemoveAccount(RemoteUrl);
Service.ConfigurationManager.Save(Service.Configuration);
if (retry)
{
PluginLog.Information("TryConnect: Attempting connection retry without account id");

View File

@ -59,7 +59,7 @@ namespace Pal.Client.Net
if (Service.Configuration.Mode != Configuration.EMode.Online)
return false;
var account = Account;
var account = Service.Configuration.FindAccount(RemoteUrl);
return account == null || account.CachedRoles.Contains(role);
}
}

View File

@ -2,16 +2,19 @@
using Grpc.Net.Client;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using Pal.Client.Extensions;
using Pal.Client.Configuration;
namespace Pal.Client.Net
{
internal partial class RemoteApi : IDisposable
{
#if DEBUG
public static string RemoteUrl { get; } = "http://localhost:5145";
public const string RemoteUrl = "http://localhost:5145";
#else
public static string RemoteUrl { get; } = "https://pal.μ.tv";
public const string RemoteUrl = "https://pal.μ.tv";
#endif
private readonly string _userAgent = $"{typeof(RemoteApi).Assembly.GetName().Name?.Replace(" ", "")}/{typeof(RemoteApi).Assembly.GetName().Version?.ToString(2)}";
@ -21,24 +24,6 @@ namespace Pal.Client.Net
private LoginInfo _loginInfo = new(null);
private bool _warnedAboutUpgrade;
public Configuration.AccountInfo? Account
{
get => Service.Configuration.Accounts.TryGetValue(RemoteUrl, out Configuration.AccountInfo? accountInfo) ? accountInfo : null;
set
{
if (value != null)
Service.Configuration.Accounts[RemoteUrl] = value;
else
Service.Configuration.Accounts.Remove(RemoteUrl);
}
}
public Guid? AccountId => Account?.Id;
public string? PartialAccountId => Account?.Id?.ToPartialId();
private string FormattedPartialAccountId => PartialAccountId ?? "[no account id]";
public void Dispose()
{
PluginLog.Debug("Disposing gRPC channel");

View File

@ -27,6 +27,9 @@ using System.Threading.Tasks;
using Pal.Client.Extensions;
using Pal.Client.Properties;
using ECommons;
using ECommons.Schedulers;
using Pal.Client.Configuration;
using Pal.Client.Net;
namespace Pal.Client
{
@ -71,8 +74,10 @@ namespace Pal.Client
pluginInterface.Create<Service>();
Service.Plugin = this;
Service.Configuration = (Configuration?)pluginInterface.GetPluginConfig() ?? pluginInterface.Create<Configuration>()!;
Service.Configuration.Migrate();
Service.ConfigurationManager = new(pluginInterface);
Service.ConfigurationManager.Migrate();
Service.Configuration = Service.ConfigurationManager.Load();
ResetRenderer();
@ -146,7 +151,7 @@ namespace Pal.Client
return;
configWindow.IsOpen = true;
configWindow.TestConnection();
var _ = new TickScheduler(() => configWindow.TestConnection());
break;
#if DEBUG
@ -196,6 +201,7 @@ namespace Pal.Client
Service.Framework.Update -= OnFrameworkUpdate;
Service.Chat.ChatMessage -= OnChatMessage;
Service.WindowSystem.GetWindow<ConfigWindow>()?.Dispose();
Service.WindowSystem.RemoveAllWindows();
Service.RemoteApi.Dispose();
@ -318,7 +324,7 @@ namespace Pal.Client
var currentFloorMarkers = currentFloor.Markers;
bool updateSeenMarkers = false;
var partialAccountId = Service.RemoteApi.PartialAccountId;
var partialAccountId = Service.Configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
foreach (var visibleMarker in visibleMarkers)
{
Marker? knownMarker = currentFloorMarkers.SingleOrDefault(x => x == visibleMarker);
@ -343,7 +349,7 @@ namespace Pal.Client
saveMarkers = true;
}
if (!recreateLayout && currentFloorMarkers.Count > 0 && (config.OnlyVisibleTrapsAfterPomander || config.OnlyVisibleHoardAfterPomander))
if (!recreateLayout && currentFloorMarkers.Count > 0 && (config.DeepDungeons.Traps.OnlyVisibleAfterPomander || config.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander))
{
try
@ -399,15 +405,15 @@ namespace Pal.Client
List<IRenderElement> elements = new();
foreach (var marker in currentFloorMarkers)
{
if (marker.Seen || config.Mode == Configuration.EMode.Online || marker is { WasImported: true, Imports.Count: > 0 })
if (marker.Seen || config.Mode == EMode.Online || marker is { WasImported: true, Imports.Count: > 0 })
{
if (marker.Type == Marker.EType.Trap && config.ShowTraps)
if (marker.Type == Marker.EType.Trap)
{
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers));
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.DeepDungeons.Traps);
}
else if (marker.Type == Marker.EType.Hoard && config.ShowHoard)
else if (marker.Type == Marker.EType.Hoard)
{
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers));
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.DeepDungeons.HoardCoffers);
}
}
}
@ -436,9 +442,9 @@ namespace Pal.Client
{
EphemeralMarkers.Add(marker);
if (marker.Type == Marker.EType.SilverCoffer && config.ShowSilverCoffers)
if (marker.Type == Marker.EType.SilverCoffer && config.DeepDungeons.SilverCoffers.Show)
{
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.FillSilverCoffers);
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), config.DeepDungeons.SilverCoffers);
}
}
@ -453,12 +459,12 @@ namespace Pal.Client
{
switch (marker.Type)
{
case Marker.EType.Trap when PomanderOfSight == PomanderState.Inactive || !Service.Configuration.OnlyVisibleTrapsAfterPomander || visibleMarkers.Any(x => x == marker):
return ImGui.ColorConvertFloat4ToU32(Service.Configuration.TrapColor);
case Marker.EType.Hoard when PomanderOfIntuition == PomanderState.Inactive || !Service.Configuration.OnlyVisibleHoardAfterPomander || visibleMarkers.Any(x => x == marker):
return ImGui.ColorConvertFloat4ToU32(Service.Configuration.HoardColor);
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 ImGui.ColorConvertFloat4ToU32(Service.Configuration.SilverCofferColor);
return Service.Configuration.DeepDungeons.SilverCoffers.Color;
case Marker.EType.Trap:
case Marker.EType.Hoard:
return ColorInvisible;
@ -467,9 +473,12 @@ namespace Pal.Client
}
}
private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color, bool fill = false)
private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color, MarkerConfiguration config)
{
var element = Renderer.CreateElement(marker.Type, marker.Position, color, fill);
if (!config.Show)
return;
var element = Renderer.CreateElement(marker.Type, marker.Position, color, config.Fill);
marker.RenderElement = element;
elements.Add(element);
}
@ -651,15 +660,15 @@ namespace Pal.Client
internal void ResetRenderer()
{
if (Renderer is SplatoonRenderer && Service.Configuration.Renderer == Configuration.ERenderer.Splatoon)
if (Renderer is SplatoonRenderer && Service.Configuration.Renderer.SelectedRenderer == ERenderer.Splatoon)
return;
else if (Renderer is SimpleRenderer && Service.Configuration.Renderer == Configuration.ERenderer.Simple)
else if (Renderer is SimpleRenderer && Service.Configuration.Renderer.SelectedRenderer == ERenderer.Simple)
return;
if (Renderer is IDisposable disposable)
disposable.Dispose();
if (Service.Configuration.Renderer == Configuration.ERenderer.Splatoon)
if (Service.Configuration.Renderer.SelectedRenderer == ERenderer.Splatoon)
Renderer = new SplatoonRenderer(Service.PluginInterface, this);
else
Renderer = new SimpleRenderer();

View File

@ -120,7 +120,7 @@ namespace Pal.Client.Rendering
{
case Marker.EType.Hoard:
// ignore distance if this is a found hoard coffer
if (Service.Plugin.PomanderOfIntuition == Plugin.PomanderState.Active && Service.Configuration.OnlyVisibleHoardAfterPomander)
if (Service.Plugin.PomanderOfIntuition == Plugin.PomanderState.Active && Service.Configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander)
break;
goto case Marker.EType.Trap;

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Numerics;
using Pal.Client.Extensions;
using Pal.Client.Properties;
using Pal.Client.Configuration;
namespace Pal.Client.Scheduled
{
@ -46,14 +47,14 @@ namespace Pal.Client.Scheduled
}
config.ImportHistory.RemoveAll(hist => oldExportIds.Contains(hist.Id) || hist.Id == _exportId);
config.ImportHistory.Add(new Configuration.ImportHistoryEntry
config.ImportHistory.Add(new ConfigurationV1.ImportHistoryEntry
{
Id = _exportId,
RemoteUrl = _export.ServerUrl,
ExportedAt = _export.CreatedAt.ToDateTime(),
ImportedAt = DateTime.UtcNow,
});
config.Save();
Service.ConfigurationManager.Save(config);
recreateLayout = true;
saveMarkers = true;

View File

@ -3,6 +3,8 @@ 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
@ -45,7 +47,7 @@ namespace Pal.Client.Scheduled
break;
case SyncType.MarkSeen:
var partialAccountId = Service.RemoteApi.PartialAccountId;
var partialAccountId = Service.Configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
if (partialAccountId == null)
break;
foreach (var remoteMarker in remoteMarkers)

View File

@ -8,6 +8,7 @@ using Dalamud.Game.Gui;
using Dalamud.Interface.Windowing;
using Dalamud.IoC;
using Dalamud.Plugin;
using Pal.Client.Configuration;
using Pal.Client.Net;
namespace Pal.Client
@ -27,7 +28,8 @@ namespace Pal.Client
internal static Plugin Plugin { get; set; } = null!;
internal static WindowSystem WindowSystem { get; } = new(typeof(Service).AssemblyQualifiedName);
internal static RemoteApi RemoteApi { get; } = new();
internal static Configuration Configuration { get; set; } = null!;
internal static ConfigurationManager ConfigurationManager { get; set; } = null!;
internal static IPalacePalConfiguration Configuration { get; set; } = null!;
internal static Hooks Hooks { get; set; } = null!;
}
}

View File

@ -69,7 +69,7 @@ namespace Pal.Client.Windows
{
config.Mode = (Configuration.EMode)_choice;
config.FirstUse = false;
config.Save();
Service.ConfigurationManager.Save(config);
IsOpen = false;
}

View File

@ -19,23 +19,18 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Pal.Client.Properties;
using Pal.Client.Configuration;
namespace Pal.Client.Windows
{
internal class ConfigWindow : Window, ILanguageChanged
internal class ConfigWindow : Window, ILanguageChanged, IDisposable
{
private const string WindowId = "###PalPalaceConfig";
private int _mode;
private int _renderer;
private bool _showTraps;
private Vector4 _trapColor;
private bool _onlyVisibleTrapsAfterPomander;
private bool _showHoard;
private Vector4 _hoardColor;
private bool _onlyVisibleHoardAfterPomander;
private bool _showSilverCoffers;
private Vector4 _silverCofferColor;
private bool _fillSilverCoffers;
private ConfigurableMarker _trapConfig = new();
private ConfigurableMarker _hoardConfig = new();
private ConfigurableMarker _silverConfig = new();
private string? _connectionText;
private bool _switchToCommunityTab;
@ -67,20 +62,19 @@ namespace Pal.Client.Windows
WindowName = $"{Localization.Palace_Pal} v{version}{WindowId}";
}
public void Dispose()
{
_testConnectionCts?.Cancel();
}
public override void OnOpen()
{
var config = Service.Configuration;
_mode = (int)config.Mode;
_renderer = (int)config.Renderer;
_showTraps = config.ShowTraps;
_trapColor = config.TrapColor;
_onlyVisibleTrapsAfterPomander = config.OnlyVisibleTrapsAfterPomander;
_showHoard = config.ShowHoard;
_hoardColor = config.HoardColor;
_onlyVisibleHoardAfterPomander = config.OnlyVisibleHoardAfterPomander;
_showSilverCoffers = config.ShowSilverCoffers;
_silverCofferColor = config.SilverCofferColor;
_fillSilverCoffers = config.FillSilverCoffers;
_renderer = (int)config.Renderer.SelectedRenderer;
_trapConfig = new ConfigurableMarker(config.DeepDungeons.Traps);
_hoardConfig = new ConfigurableMarker(config.DeepDungeons.HoardCoffers);
_silverConfig = new ConfigurableMarker(config.DeepDungeons.SilverCoffers);
_connectionText = null;
}
@ -113,18 +107,13 @@ namespace Pal.Client.Windows
if (save || saveAndClose)
{
var config = Service.Configuration;
config.Mode = (Configuration.EMode)_mode;
config.Renderer = (Configuration.ERenderer)_renderer;
config.ShowTraps = _showTraps;
config.TrapColor = _trapColor;
config.OnlyVisibleTrapsAfterPomander = _onlyVisibleTrapsAfterPomander;
config.ShowHoard = _showHoard;
config.HoardColor = _hoardColor;
config.OnlyVisibleHoardAfterPomander = _onlyVisibleHoardAfterPomander;
config.ShowSilverCoffers = _showSilverCoffers;
config.SilverCofferColor = _silverCofferColor;
config.FillSilverCoffers = _fillSilverCoffers;
config.Save();
config.Mode = (EMode)_mode;
config.Renderer.SelectedRenderer = (ERenderer)_renderer;
config.DeepDungeons.Traps = _trapConfig.Build();
config.DeepDungeons.HoardCoffers = _hoardConfig.Build();
config.DeepDungeons.SilverCoffers = _silverConfig.Build();
Service.ConfigurationManager.Save(config);
if (saveAndClose)
IsOpen = false;
@ -135,12 +124,12 @@ namespace Pal.Client.Windows
{
if (ImGui.BeginTabItem($"{Localization.ConfigTab_DeepDungeons}###TabDeepDungeons"))
{
ImGui.Checkbox(Localization.Config_Traps_Show, ref _showTraps);
ImGui.Checkbox(Localization.Config_Traps_Show, ref _trapConfig.Show);
ImGui.Indent();
ImGui.BeginDisabled(!_showTraps);
ImGui.BeginDisabled(!_trapConfig.Show);
ImGui.Spacing();
ImGui.ColorEdit4(Localization.Config_Traps_Color, ref _trapColor, ImGuiColorEditFlags.NoInputs);
ImGui.Checkbox(Localization.Config_Traps_HideImpossible, ref _onlyVisibleTrapsAfterPomander);
ImGui.ColorEdit4(Localization.Config_Traps_Color, ref _trapConfig.Color, ImGuiColorEditFlags.NoInputs);
ImGui.Checkbox(Localization.Config_Traps_HideImpossible, ref _trapConfig.OnlyVisibleAfterPomander);
ImGui.SameLine();
ImGuiComponents.HelpMarker(Localization.Config_Traps_HideImpossible_ToolTip);
ImGui.EndDisabled();
@ -148,12 +137,12 @@ namespace Pal.Client.Windows
ImGui.Separator();
ImGui.Checkbox(Localization.Config_HoardCoffers_Show, ref _showHoard);
ImGui.Checkbox(Localization.Config_HoardCoffers_Show, ref _hoardConfig.Show);
ImGui.Indent();
ImGui.BeginDisabled(!_showHoard);
ImGui.BeginDisabled(!_hoardConfig.Show);
ImGui.Spacing();
ImGui.ColorEdit4(Localization.Config_HoardCoffers_Color, ref _hoardColor, ImGuiColorEditFlags.NoInputs);
ImGui.Checkbox(Localization.Config_HoardCoffers_HideImpossible, ref _onlyVisibleHoardAfterPomander);
ImGui.ColorEdit4(Localization.Config_HoardCoffers_Color, ref _hoardConfig.Color, ImGuiColorEditFlags.NoInputs);
ImGui.Checkbox(Localization.Config_HoardCoffers_HideImpossible, ref _hoardConfig.OnlyVisibleAfterPomander);
ImGui.SameLine();
ImGuiComponents.HelpMarker(Localization.Config_HoardCoffers_HideImpossible_ToolTip);
ImGui.EndDisabled();
@ -161,13 +150,13 @@ namespace Pal.Client.Windows
ImGui.Separator();
ImGui.Checkbox(Localization.Config_SilverCoffer_Show, ref _showSilverCoffers);
ImGui.Checkbox(Localization.Config_SilverCoffer_Show, ref _silverConfig.Show);
ImGuiComponents.HelpMarker(Localization.Config_SilverCoffers_ToolTip);
ImGui.Indent();
ImGui.BeginDisabled(!_showSilverCoffers);
ImGui.BeginDisabled(!_silverConfig.Show);
ImGui.Spacing();
ImGui.ColorEdit4(Localization.Config_SilverCoffer_Color, ref _silverCofferColor, ImGuiColorEditFlags.NoInputs);
ImGui.Checkbox(Localization.Config_SilverCoffer_Filled, ref _fillSilverCoffers);
ImGui.ColorEdit4(Localization.Config_SilverCoffer_Color, ref _silverConfig.Color, ImGuiColorEditFlags.NoInputs);
ImGui.Checkbox(Localization.Config_SilverCoffer_Filled, ref _silverConfig.Fill);
ImGui.EndDisabled();
ImGui.Unindent();
@ -190,13 +179,13 @@ namespace Pal.Client.Windows
ImGui.TextWrapped(Localization.Explanation_3);
ImGui.TextWrapped(Localization.Explanation_4);
ImGui.RadioButton(Localization.Config_UploadMyDiscoveries_ShowOtherTraps, ref _mode, (int)Configuration.EMode.Online);
ImGui.RadioButton(Localization.Config_NeverUploadDiscoveries_ShowMyTraps, ref _mode, (int)Configuration.EMode.Offline);
ImGui.RadioButton(Localization.Config_UploadMyDiscoveries_ShowOtherTraps, ref _mode, (int)EMode.Online);
ImGui.RadioButton(Localization.Config_NeverUploadDiscoveries_ShowMyTraps, ref _mode, (int)EMode.Offline);
saveAndClose = ImGui.Button(Localization.SaveAndClose);
ImGui.Separator();
ImGui.BeginDisabled(Service.Configuration.Mode != Configuration.EMode.Online);
ImGui.BeginDisabled(Service.Configuration.Mode != EMode.Online);
if (ImGui.Button(Localization.Config_TestConnection))
TestConnection();
@ -294,8 +283,8 @@ namespace Pal.Client.Windows
if (ImGui.BeginTabItem($"{Localization.ConfigTab_Renderer}###TabRenderer"))
{
ImGui.Text(Localization.Config_SelectRenderBackend);
ImGui.RadioButton($"{Localization.Config_Renderer_Splatoon} ({Localization.Config_Renderer_Splatoon_Hint})", ref _renderer, (int)Configuration.ERenderer.Splatoon);
ImGui.RadioButton($"{Localization.Config_Renderer_Simple} ({Localization.Config_Renderer_Simple_Hint})", ref _renderer, (int)Configuration.ERenderer.Simple);
ImGui.RadioButton($"{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();
@ -307,7 +296,7 @@ namespace Pal.Client.Windows
ImGui.Text(Localization.Config_Splatoon_Test);
ImGui.BeginDisabled(!(Service.Plugin.Renderer is IDrawDebugItems));
if (ImGui.Button(Localization.Config_Splatoon_DrawCircles))
(Service.Plugin.Renderer as IDrawDebugItems)?.DrawDebugItems(_trapColor, _hoardColor);
(Service.Plugin.Renderer as IDrawDebugItems)?.DrawDebugItems(_trapConfig.Color, _hoardConfig.Color);
ImGui.EndDisabled();
ImGui.EndTabItem();
@ -328,17 +317,17 @@ namespace Pal.Client.Windows
ImGui.Indent();
if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloor))
{
if (_showTraps)
if (_trapConfig.Show)
{
int traps = currentFloor.Markers.Count(x => x.Type == Marker.EType.Trap);
ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}");
}
if (_showHoard)
if (_hoardConfig.Show)
{
int hoardCoffers = currentFloor.Markers.Count(x => x.Type == Marker.EType.Hoard);
ImGui.Text($"{hoardCoffers} known hoard coffer{(hoardCoffers == 1 ? "" : "s")}");
}
if (_showSilverCoffers)
if (_silverConfig.Show)
{
int silverCoffers = plugin.EphemeralMarkers.Count(x => x.Type == Marker.EType.SilverCoffer);
ImGui.Text($"{silverCoffers} silver coffer{(silverCoffers == 1 ? "" : "s")} visible on current floor");
@ -440,5 +429,36 @@ namespace Pal.Client.Windows
}
});
}
private class ConfigurableMarker
{
public bool Show;
public Vector4 Color;
public bool OnlyVisibleAfterPomander;
public bool Fill;
public ConfigurableMarker()
{
}
public ConfigurableMarker(MarkerConfiguration config)
{
Show = config.Show;
Color = ImGui.ColorConvertU32ToFloat4(config.Color);
OnlyVisibleAfterPomander = config.OnlyVisibleAfterPomander;
Fill = config.Fill;
}
public MarkerConfiguration Build()
{
return new MarkerConfiguration
{
Show = Show,
Color = ImGui.ColorConvertFloat4ToU32(Color),
OnlyVisibleAfterPomander = OnlyVisibleAfterPomander,
Fill = Fill
};
}
}
}
}