diff --git a/BossMod/BossModReborn.csproj b/BossMod/BossModReborn.csproj index 041817aa5..798c4e155 100644 --- a/BossMod/BossModReborn.csproj +++ b/BossMod/BossModReborn.csproj @@ -105,9 +105,16 @@ PreserveNewest + - + + + + + PreserveNewest + + diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index 9542bee46..9c8af37a5 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -129,7 +129,7 @@ protected AutoClear(WorldState ws, int LevelCap) : base(ws) }) ); - _trapsCurrentZone = PalacePalInterop.GetTrapLocationsForZone(ws.CurrentZone); + _trapsCurrentZone = DDTrapsData.GetTrapLocationsForZone(ws.CurrentZone); LoadedFloors = JsonSerializer.Deserialize>>(GetEmbeddedResource("Walls.json"))!; ProblematicTrapLocations = JsonSerializer.Deserialize>(GetEmbeddedResource("BadTraps.json"))!; @@ -142,6 +142,7 @@ protected override void Dispose(bool disposing) _subscriptions.Dispose(); _obstacles.Dispose(); base.Dispose(disposing); + DDTrapsData.CleanupTemporaryDatabase(); } protected virtual void OnCastStarted(Actor actor) { } @@ -830,45 +831,72 @@ private static bool IsBlocked(Bitmap map, Vector2 point, Vector2 origin, float m return false; } - private Stream GetEmbeddedResource(string name) => Assembly.GetExecutingAssembly().GetManifestResourceStream($"BossMod.Modules.Global.DeepDungeon.{name}") ?? throw new InvalidDataException($"Missing embedded resource {name}"); + private Stream GetEmbeddedResource(string name) => Assembly.GetExecutingAssembly().GetManifestResourceStream($"BossModReborn.Modules.Global.DeepDungeon.{name}") ?? throw new InvalidDataException($"Missing embedded resource {name}"); } -static class PalacePalInterop +static class DDTrapsData { - // TODO make an IPC for this? wouldn't work in uidev - private static readonly string PalacePalDbFile = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "pluginConfigs", "PalacePal", "palace-pal.data.sqlite3"); + private const string EmbeddedDbResourceName = "BossModReborn.Modules.Global.DeepDungeon.DDTrapsData.sqlite3"; + private static string? _tempDbFilePath; - public static List GetTrapLocationsForZone(uint zone) + private static string GetTempDbFilePath() { - List locations = []; + if (_tempDbFilePath != null) + { + return _tempDbFilePath; + } + + var tempFileName = Path.GetTempFileName() + ".sqlite3"; + _tempDbFilePath = tempFileName; - try + using (var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(EmbeddedDbResourceName)) { - using (var connection = new SQLiteConnection($"Data Source={PalacePalDbFile}")) + if (resourceStream == null) { - connection.Open(); + throw new FileNotFoundException($"Embedded resource '{EmbeddedDbResourceName}' not found."); + } - var command = connection.CreateCommand(); - command.CommandText = @" - select X,Z from Locations where Type = 1 and TerritoryType = $tt - "; - command.Parameters.AddWithValue("$tt", zone); + using var fileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.Write); + resourceStream.CopyTo(fileStream); + } + return tempFileName; + } - using var reader = command.ExecuteReader(); - while (reader.Read()) - { - var x = reader.GetFloat(0); - var z = reader.GetFloat(1); - locations.Add(new(x, z)); - } - } + public static List GetTrapLocationsForZone(uint zone) + { + List locations = []; + var tempDbPath = GetTempDbFilePath(); + using (var connection = new SQLiteConnection($"Data Source={tempDbPath};Version=3;Read Only=True;")) + { + connection.Open(); - return locations; + var command = connection.CreateCommand(); + command.CommandText = @"select X,Z from Locations where Type = 1 and TerritoryType = $tt"; + command.Parameters.AddWithValue("$tt", zone); + + using var reader = command.ExecuteReader(); + while (reader.Read()) + { + var x = reader.GetFloat(0); + var z = reader.GetFloat(1); + locations.Add(new(x, z)); + } } - catch (SQLiteException e) + return locations; + } + + public static void CleanupTemporaryDatabase() + { + if (_tempDbFilePath != null) { - Service.Log($"unable to load traps for zone ${zone}: ${e}"); - return []; + var baseTempPath = Path.GetFileNameWithoutExtension(_tempDbFilePath); + var tempDirPath = Path.GetDirectoryName(_tempDbFilePath) ?? ""; + + string[] extensions = [".sqlite3", ".sqlite3-shm", ".sqlite3-wal", ""]; + + for (var i = 0; i < 4; ++i) + File.Delete(Path.Combine(tempDirPath, baseTempPath + extensions[i])); } + _tempDbFilePath = null; } } diff --git a/BossMod/Modules/Global/DeepDungeon/Config.cs b/BossMod/Modules/Global/DeepDungeon/Config.cs index e12659350..75993cf94 100644 --- a/BossMod/Modules/Global/DeepDungeon/Config.cs +++ b/BossMod/Modules/Global/DeepDungeon/Config.cs @@ -19,7 +19,7 @@ public enum ClearBehavior public bool Enable = true; [PropertyDisplay("Enable minimap")] public bool EnableMinimap = true; - [PropertyDisplay("Try to avoid traps", tooltip: "Avoid known trap locations sourced from PalacePal data. (Traps revealed by a Pomander of Sight will always be avoided regardless of this setting.)")] + [PropertyDisplay("Try to avoid traps", tooltip: "Avoid known trap locations sourced from PalacePal data. Does not need PalacePal installed since data is embedded into BMR. (Traps revealed by a Pomander of Sight will always be avoided regardless of this setting.)")] public bool TrapHints = true; [PropertyDisplay("Automatically navigate to Cairn of Passage")] public bool AutoPassage = true; diff --git a/BossMod/Modules/Global/DeepDungeon/DDTrapsData.sqlite3 b/BossMod/Modules/Global/DeepDungeon/DDTrapsData.sqlite3 new file mode 100644 index 000000000..7f684d16c Binary files /dev/null and b/BossMod/Modules/Global/DeepDungeon/DDTrapsData.sqlite3 differ diff --git a/BossMod/Pathfinding/ObstacleMapManager.cs b/BossMod/Pathfinding/ObstacleMapManager.cs index 7b9662fc4..c71041417 100644 --- a/BossMod/Pathfinding/ObstacleMapManager.cs +++ b/BossMod/Pathfinding/ObstacleMapManager.cs @@ -9,7 +9,7 @@ public sealed class ObstacleMapConfig : ConfigNode [PropertyDisplay("Developer mode: load obstacle maps from source rather than from plugin distribution")] public bool LoadFromSource; - [PropertyDisplay("Developer mode: source path", tooltip: "Should be /BossMod/Pathfinding/ObstacleMaps/maplist.json")] + [PropertyDisplay("Developer mode: source path", tooltip: "Should be /BossModReborn/Pathfinding/ObstacleMaps/maplist.json")] public string SourcePath = ""; } @@ -92,5 +92,5 @@ private void LoadMaps(ushort zoneId, ushort cfcId) } } - private Stream GetEmbeddedResource(string name) => Assembly.GetExecutingAssembly().GetManifestResourceStream($"BossMod.Pathfinding.ObstacleMaps.{name}") ?? throw new InvalidDataException($"Missing embedded resource {name}"); + private Stream GetEmbeddedResource(string name) => Assembly.GetExecutingAssembly().GetManifestResourceStream($"BossModReborn.Pathfinding.ObstacleMaps.{name}") ?? throw new InvalidDataException($"Missing embedded resource {name}"); } diff --git a/BossMod/System.Data.SQLite.dll b/BossMod/System.Data.SQLite.dll new file mode 100644 index 000000000..d99b135ad Binary files /dev/null and b/BossMod/System.Data.SQLite.dll differ