diff --git a/Pal.Client/LocalState.cs b/Pal.Client/LocalState.cs new file mode 100644 index 0000000..312fd2b --- /dev/null +++ b/Pal.Client/LocalState.cs @@ -0,0 +1,100 @@ +using Pal.Common; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace Pal.Client +{ + /// + /// JSON for a single floor set (e.g. 51-60). + /// + internal class LocalState + { + private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions { IncludeFields = true }; + private static readonly int _currentVersion = 2; + + public uint TerritoryType { get; set; } + public ConcurrentBag Markers { get; set; } = new(); + + public LocalState(uint territoryType) + { + TerritoryType = territoryType; + } + + private void ApplyFilters() + { + if (Service.Configuration.Mode == Configuration.EMode.Offline) + Markers = new ConcurrentBag(Markers.Where(x => x.Seen)); + } + + public static LocalState Load(uint territoryType) + { + string path = GetSaveLocation(territoryType); + if (!File.Exists(path)) + return null; + + string content = File.ReadAllText(path); + if (content.Length == 0) + return null; + + LocalState localState; + int version = 1; + if (content[0] == '[') + { + // v1 only had a list of markers, not a JSON object as root + localState = new LocalState(territoryType) + { + Markers = new ConcurrentBag(JsonSerializer.Deserialize>(content, _jsonSerializerOptions)), + }; + } + else + { + var save = JsonSerializer.Deserialize(content, _jsonSerializerOptions); + localState = new LocalState(territoryType) + { + Markers = new ConcurrentBag(save.Markers), + }; + version = save.Version; + } + + localState.ApplyFilters(); + + if (version < _currentVersion) + localState.Save(); + + return localState; + } + + public void Save() + { + string path = GetSaveLocation(TerritoryType); + + ApplyFilters(); + File.WriteAllText(path, JsonSerializer.Serialize(new SaveFile + { + Version = _currentVersion, + Markers = new HashSet(Markers) + }, _jsonSerializerOptions)); + } + + private static string GetSaveLocation(uint territoryType) => Path.Join(Service.PluginInterface.GetPluginConfigDirectory(), $"{territoryType}.json"); + + public static void UpdateAll() + { + foreach (ETerritoryType territory in typeof(ETerritoryType).GetEnumValues()) + { + LocalState localState = Load((ushort)territory); + if (localState != null) + localState.Save(); + } + } + + public class SaveFile + { + public int Version { get; set; } + public HashSet Markers { get; set; } = new(); + } + } +} diff --git a/Pal.Client/Pal.Client.csproj b/Pal.Client/Pal.Client.csproj index f4cee03..4cce4a1 100644 --- a/Pal.Client/Pal.Client.csproj +++ b/Pal.Client/Pal.Client.csproj @@ -3,7 +3,7 @@ net6.0-windows 9.0 - 1.8.0.0 + 1.9.0.0 diff --git a/Pal.Client/Plugin.cs b/Pal.Client/Plugin.cs index 991d281..0af2afc 100644 --- a/Pal.Client/Plugin.cs +++ b/Pal.Client/Plugin.cs @@ -16,11 +16,9 @@ using Pal.Client.Windows; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; -using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -43,7 +41,7 @@ namespace Pal.Client private bool _configUpdated = false; private LocalizedChatMessages _localizedChatMessages = new(); - internal ConcurrentDictionary> FloorMarkers { get; } = new(); + internal ConcurrentDictionary FloorMarkers { get; } = new(); internal ConcurrentBag EphemeralMarkers { get; set; } = new(); internal ushort LastTerritory { get; private set; } public SyncState TerritorySyncState { get; set; } @@ -114,15 +112,29 @@ namespace Pal.Client return; } - switch (arguments) + try { - case "stats": - Task.Run(async () => await FetchFloorStatistics()); - break; + switch (arguments) + { + case "stats": + Task.Run(async () => await FetchFloorStatistics()); + break; - default: - Service.WindowSystem.GetWindow()?.Toggle(); - break; +#if DEBUG + case "update-saves": + LocalState.UpdateAll(); + Service.Chat.Print("Updated all locally cached marker files to latest version."); + break; +#endif + + default: + Service.WindowSystem.GetWindow()?.Toggle(); + break; + } + } + catch (Exception e) + { + Service.Chat.PrintError($"[Palace Pal] {e}"); } } @@ -210,15 +222,7 @@ namespace Pal.Client { if (Service.Configuration.Mode == Configuration.EMode.Offline) { - foreach (var path in Directory.GetFiles(Service.PluginInterface.GetPluginConfigDirectory())) - { - if (path.EndsWith(".json")) - { - var markers = JsonSerializer.Deserialize>(File.ReadAllText(path), new JsonSerializerOptions { IncludeFields = true }).Where(x => x.Seen).ToList(); - File.WriteAllText(path, JsonSerializer.Serialize(markers, new JsonSerializerOptions { IncludeFields = true })); - } - } - + LocalState.UpdateAll(); FloorMarkers.Clear(); EphemeralMarkers.Clear(); LastTerritory = 0; @@ -234,7 +238,7 @@ namespace Pal.Client TerritorySyncState = SyncState.NotAttempted; if (IsInPotdOrHoh()) - FloorMarkers[LastTerritory] = new ConcurrentBag(LoadSavedMarkers()); + FloorMarkers[LastTerritory] = LocalState.Load(LastTerritory) ?? new LocalState(LastTerritory); EphemeralMarkers.Clear(); PomanderOfSight = PomanderState.Inactive; PomanderOfIntuition = PomanderState.Inactive; @@ -258,11 +262,11 @@ namespace Pal.Client saveMarkers = true; } - if (!FloorMarkers.TryGetValue(LastTerritory, out var currentFloorMarkers)) - FloorMarkers[LastTerritory] = currentFloorMarkers = new ConcurrentBag(); + if (!FloorMarkers.TryGetValue(LastTerritory, out var currentFloor)) + FloorMarkers[LastTerritory] = currentFloor = new LocalState(LastTerritory); IList visibleMarkers = GetRelevantGameObjects(); - HandlePersistentMarkers(currentFloorMarkers, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout); + HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout); HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout); } catch (Exception e) @@ -271,9 +275,10 @@ namespace Pal.Client } } - private void HandlePersistentMarkers(ConcurrentBag currentFloorMarkers, IList visibleMarkers, bool saveMarkers, bool recreateLayout) + private void HandlePersistentMarkers(LocalState currentFloor, IList visibleMarkers, bool saveMarkers, bool recreateLayout) { var config = Service.Configuration; + var currentFloorMarkers = currentFloor.Markers; foreach (var visibleMarker in visibleMarkers) { @@ -320,7 +325,7 @@ namespace Pal.Client if (saveMarkers) { - SaveMarkers(); + currentFloor.Save(); if (TerritorySyncState == SyncState.Complete) { @@ -431,23 +436,6 @@ namespace Pal.Client } } - public string GetSaveForCurrentTerritory() => Path.Join(Service.PluginInterface.GetPluginConfigDirectory(), $"{LastTerritory}.json"); - - private List LoadSavedMarkers() - { - string path = GetSaveForCurrentTerritory(); - if (File.Exists(path)) - return JsonSerializer.Deserialize>(File.ReadAllText(path), new JsonSerializerOptions { IncludeFields = true }).Where(x => x.Seen || Service.Configuration.Mode == Configuration.EMode.Online).ToList(); - else - return new List(); - } - - private void SaveMarkers() - { - string path = GetSaveForCurrentTerritory(); - File.WriteAllText(path, JsonSerializer.Serialize(FloorMarkers[LastTerritory], new JsonSerializerOptions { IncludeFields = true })); - } - private async Task DownloadMarkersForTerritory(ushort territoryId) { try @@ -498,18 +486,18 @@ namespace Pal.Client while (_remoteDownloads.TryDequeue(out var download)) { var (territoryId, success, downloadedMarkers) = download; - if (Service.Configuration.Mode == Configuration.EMode.Online && success && FloorMarkers.TryGetValue(territoryId, out var currentFloorMarkers) && downloadedMarkers.Count > 0) + if (Service.Configuration.Mode == Configuration.EMode.Online && success && FloorMarkers.TryGetValue(territoryId, out var currentFloor) && downloadedMarkers.Count > 0) { foreach (var downloadedMarker in downloadedMarkers) { - Marker seenMarker = currentFloorMarkers.SingleOrDefault(x => x == downloadedMarker); + Marker seenMarker = currentFloor.Markers.SingleOrDefault(x => x == downloadedMarker); if (seenMarker != null) { seenMarker.RemoteSeen = true; continue; } - currentFloorMarkers.Add(downloadedMarker); + currentFloor.Markers.Add(downloadedMarker); } } diff --git a/Pal.Client/Windows/ConfigWindow.cs b/Pal.Client/Windows/ConfigWindow.cs index 7046f46..e9e2ac4 100644 --- a/Pal.Client/Windows/ConfigWindow.cs +++ b/Pal.Client/Windows/ConfigWindow.cs @@ -149,16 +149,16 @@ namespace Pal.Client.Windows ImGui.Text($"{plugin.DebugMessage}"); ImGui.Indent(); - if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloorMarkers)) + if (plugin.FloorMarkers.TryGetValue(plugin.LastTerritory, out var currentFloor)) { if (_showTraps) { - int traps = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Trap); + int traps = currentFloor.Markers.Count(x => x != null && x.Type == Marker.EType.Trap); ImGui.Text($"{traps} known trap{(traps == 1 ? "" : "s")}"); } if (_showHoard) { - int hoardCoffers = currentFloorMarkers.Count(x => x != null && x.Type == Marker.EType.Hoard); + int hoardCoffers = currentFloor.Markers.Count(x => x != null && x.Type == Marker.EType.Hoard); ImGui.Text($"{hoardCoffers} known hoard coffer{(hoardCoffers == 1 ? "" : "s")}"); } if (_showSilverCoffers)