diff --git a/BossMod/BossModReborn.csproj b/BossMod/BossModReborn.csproj
index 8198e3ed4c..89091da793 100644
--- a/BossMod/BossModReborn.csproj
+++ b/BossMod/BossModReborn.csproj
@@ -103,4 +103,8 @@
PreserveNewest
+
+
+
+
diff --git a/BossMod/Debug/DebugObstacles.cs b/BossMod/Debug/DebugObstacles.cs
index 2144e26f8a..825121565f 100644
--- a/BossMod/Debug/DebugObstacles.cs
+++ b/BossMod/Debug/DebugObstacles.cs
@@ -51,7 +51,8 @@ protected override void DrawSidebar()
ImGui.SameLine();
if (ImGui.Button("Reload"))
{
- CheckpointNoClone(new(owner.Obstacles.RootPath + e.Filename));
+ using var stream = File.OpenRead(owner.Obstacles.RootPath + e.Filename);
+ CheckpointNoClone(new(stream));
}
ImGui.SetNextItemWidth(100);
@@ -162,6 +163,7 @@ public void Draw()
private void DrawEntries(List entries)
{
Action? modifications = null;
+ using var disableScope = ImRaii.Disabled(!Obstacles.CanEditDatabase());
for (int i = 0; i < entries.Count; ++i)
{
using var id = ImRaii.PushId(i);
@@ -174,9 +176,8 @@ private void DrawEntries(List entries)
if (ImGui.Button("Move down"))
modifications += () => (entries[index], entries[index + 1]) = (entries[index + 1], entries[index]);
ImGui.SameLine();
- using (ImRaii.Disabled(!Obstacles.CanEditDatabase()))
- if (ImGui.Button("Delete"))
- modifications += () => DeleteMap(entries, index);
+ if (ImGui.Button("Delete"))
+ modifications += () => DeleteMap(entries, index);
ImGui.SameLine();
if (ImGui.Button("Edit"))
OpenEditor(entries[index]);
@@ -230,7 +231,8 @@ private string GenerateMapName()
private void OpenEditor(ObstacleMapDatabase.Entry entry)
{
- var editor = new Editor(this, new(Obstacles.RootPath + entry.Filename), entry);
+ using var stream = File.OpenRead(Obstacles.RootPath + entry.Filename);
+ var editor = new Editor(this, new(stream), entry);
_ = new UISimpleWindow($"Obstacle map {entry.Filename}", editor.Draw, true, new(1000, 1000));
}
}
diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs
index 438674f584..e9bde39a52 100644
--- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs
+++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs
@@ -45,7 +45,24 @@ class Ceras(BossModule module) : Components.SingleTargetCast(module, ActionID.Ma
class WaveOfTurmoil(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.WaveOfTurmoil), 20, stopAtWall: true)
{
- public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false;
+ private readonly Hydrobomb _aoe = module.FindComponent()!;
+
+ public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => _aoe?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false;
+
+ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
+ {
+ var forbidden = new List>();
+ var source = Sources(slot, actor).FirstOrDefault();
+ if (source != default)
+ {
+ foreach (var c in _aoe.ActiveAOEs(slot, actor))
+ {
+ forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, Angle.FromDirection(c.Origin - Module.Center), 30.Degrees()));
+ }
+ if (forbidden.Count > 0)
+ hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation);
+ }
+ }
}
class Hydrobomb(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Hydrobomb), 10);
@@ -70,8 +87,8 @@ public GymnasiouMeganereisStates(BossModule module) : base(module)
{
TrivialPhase()
.ActivateOnEnter()
- .ActivateOnEnter()
.ActivateOnEnter()
+ .ActivateOnEnter()
.ActivateOnEnter()
.ActivateOnEnter()
.ActivateOnEnter()
diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousTyphon.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousTyphon.cs
index 437f941466..b01bab4e4c 100644
--- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousTyphon.cs
+++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousTyphon.cs
@@ -161,7 +161,7 @@ public DaenOseTheAvariciousTyphonStates(BossModule module) : base(module)
}
}
-[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 745, NameID = 9808)]
+[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 745, NameID = 9808, SortOrder = 1)]
public class DaenOseTheAvariciousTyphon(WorldState ws, Actor primary) : THTemplate(ws, primary)
{
private static readonly uint[] bonusAdds = [(uint)OID.SecretEgg, (uint)OID.SecretGarlic, (uint)OID.SecretOnion, (uint)OID.SecretTomato,
diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs
new file mode 100644
index 0000000000..661d22873b
--- /dev/null
+++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/DaenOseTheAvariciousUltros.cs
@@ -0,0 +1,136 @@
+namespace BossMod.Shadowbringers.TreasureHunt.ShiftingOubliettesOfLyheGhiah.DaenOseTheAvariciousUltros;
+
+public enum OID : uint
+{
+ Boss = 0x3030, // R0.75-5.1
+ StylishTentacle = 0x3031, // R7.2
+ SecretQueen = 0x3021, // R0.84, icon 5, needs to be killed in order from 1 to 5 for maximum rewards
+ SecretGarlic = 0x301F, // R0.84, icon 3, needs to be killed in order from 1 to 5 for maximum rewards
+ SecretTomato = 0x3020, // R0.84, icon 4, needs to be killed in order from 1 to 5 for maximum rewards
+ SecretOnion = 0x301D, // R0.84, icon 1, needs to be killed in order from 1 to 5 for maximum rewards
+ SecretEgg = 0x301E, // R0.84, icon 2, needs to be killed in order from 1 to 5 for maximum rewards
+ Helper = 0x233C
+}
+
+public enum AID : uint
+{
+ AutoAttack = 872, // Boss/SecretTomato/SecretQueen->player, no cast, single-target
+ Change = 21741, // Boss->self, 6.0s cast, single-target, boss morphs into Ultros
+
+ TentacleVisual = 21753, // Boss->self, no cast, single-target
+ Tentacle = 21754, // StylishTentacle->self, 3.0s cast, range 8 circle
+ Megavolt = 21752, // Boss->self, 3.5s cast, range 11 circle
+ Wallop = 21755, // StylishTentacle->self, 5.0s cast, range 20 width 10 rect
+ ThunderIII = 21743, // Boss->player, 4.0s cast, single-target, tankbuster
+
+ WaveOfTurmoilVisual = 21748, // Boss->self, 5.0s cast, single-target
+ WaveOfTurmoil = 21749, // Helper->self, 5.0s cast, range 40 circle, knockback 20, away from source
+
+ SoakingSplatter = 21750, // Helper->location, 6.5s cast, range 10 circle
+ AquaBreath = 21751, // Boss->self, 3.0s cast, range 13 90-degree cone
+
+ FallingWaterVisual = 21746, // Boss->self, 5.0s cast, single-target
+ FallingWater = 21747, // Helper->player, 5.0s cast, range 8 circle, spread
+
+ WaterspoutVisual = 21744, // Boss->self, 3.0s cast, single-target
+ Waterspout = 21745, // Helper->location, 3.0s cast, range 4 circle
+
+ Pollen = 6452, // SecretQueen->self, 3.5s cast, range 6+R circle
+ TearyTwirl = 6448, // SecretOnion->self, 3.5s cast, range 6+R circle
+ HeirloomScream = 6451, // SecretTomato->self, 3.5s cast, range 6+R circle
+ PluckAndPrune = 6449, // SecretEgg->self, 3.5s cast, range 6+R circle
+ PungentPirouette = 6450, // SecretGarlic->self, 3.5s cast, range 6+R circle
+ Telega = 9630 // Mandragoras->self, no cast, single-target, bonus adds disappear
+}
+
+class AquaBreath(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.AquaBreath), new AOEShapeCone(13, 45.Degrees()));
+class Tentacle(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Tentacle), new AOEShapeCircle(8));
+class Wallop(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Wallop), new AOEShapeRect(20, 5));
+class Megavolt(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Megavolt), new AOEShapeCircle(11));
+class Waterspout(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Waterspout), 4);
+class SoakingSplatter(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.SoakingSplatter), 10);
+class FallingWater(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.FallingWater), 8);
+class ThunderIII(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.ThunderIII));
+
+class WaveOfTurmoil(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.WaveOfTurmoil), 20, stopAtWall: true)
+{
+ private readonly SoakingSplatter _aoe = module.FindComponent()!;
+
+ public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => _aoe?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false;
+
+ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
+ {
+ var forbidden = new List>();
+ var source = Sources(slot, actor).FirstOrDefault();
+ if (source != default)
+ {
+ foreach (var c in _aoe.ActiveAOEs(slot, actor))
+ {
+ forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, Angle.FromDirection(c.Origin - Module.Center), 30.Degrees()));
+ }
+ if (forbidden.Count > 0)
+ hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation);
+ }
+ }
+}
+
+abstract class Mandragoras(BossModule module, AID aid) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCircle(6.84f));
+class PluckAndPrune(BossModule module) : Mandragoras(module, AID.PluckAndPrune);
+class TearyTwirl(BossModule module) : Mandragoras(module, AID.TearyTwirl);
+class HeirloomScream(BossModule module) : Mandragoras(module, AID.HeirloomScream);
+class PungentPirouette(BossModule module) : Mandragoras(module, AID.PungentPirouette);
+class Pollen(BossModule module) : Mandragoras(module, AID.Pollen);
+
+class DaenOseTheAvariciousUltrosStates : StateMachineBuilder
+{
+ public DaenOseTheAvariciousUltrosStates(BossModule module) : base(module)
+ {
+ TrivialPhase()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .ActivateOnEnter()
+ .Raw.Update = () => module.Enemies(DaenOseTheAvariciousUltros.All).All(x => x.IsDeadOrDestroyed);
+ }
+}
+
+[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 745, NameID = 9808, SortOrder = 2)]
+public class DaenOseTheAvariciousUltros(WorldState ws, Actor primary) : THTemplate(ws, primary)
+{
+ private static readonly uint[] bonusAdds = [(uint)OID.SecretEgg, (uint)OID.SecretGarlic, (uint)OID.SecretOnion, (uint)OID.SecretTomato,
+ (uint)OID.SecretQueen];
+ public static readonly uint[] All = [(uint)OID.Boss, .. bonusAdds];
+
+ protected override void DrawEnemies(int pcSlot, Actor pc)
+ {
+ Arena.Actor(PrimaryActor);
+ Arena.Actors(Enemies(bonusAdds), Colors.Vulnerable);
+ }
+
+ protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints)
+ {
+ for (var i = 0; i < hints.PotentialTargets.Count; ++i)
+ {
+ var e = hints.PotentialTargets[i];
+ e.Priority = (OID)e.Actor.OID switch
+ {
+ OID.SecretOnion => 5,
+ OID.SecretEgg => 4,
+ OID.SecretGarlic => 3,
+ OID.SecretTomato => 2,
+ OID.SecretQueen => 1,
+ _ => 0
+ };
+ }
+ }
+}
diff --git a/BossMod/Pathfinding/ObstacleMapDatabase.cs b/BossMod/Pathfinding/ObstacleMapDatabase.cs
index fb23e82c52..a2d5fab3ce 100644
--- a/BossMod/Pathfinding/ObstacleMapDatabase.cs
+++ b/BossMod/Pathfinding/ObstacleMapDatabase.cs
@@ -19,35 +19,28 @@ public sealed record class Entry(Vector3 MinBounds, Vector3 MaxBounds, WPos Orig
public readonly Dictionary> Entries = [];
- public void Load(string listPath)
+ public void Load(Stream stream)
{
Entries.Clear();
- try
+ using var json = Serialization.ReadJson(stream);
+ foreach (var jentries in json.RootElement.EnumerateObject())
{
- using var json = Serialization.ReadJson(listPath);
- foreach (var jentries in json.RootElement.EnumerateObject())
+ var sep = jentries.Name.IndexOf('.', StringComparison.Ordinal);
+ var zone = sep >= 0 ? uint.Parse(jentries.Name.AsSpan()[..sep]) : uint.Parse(jentries.Name);
+ var cfc = sep >= 0 ? uint.Parse(jentries.Name.AsSpan()[(sep + 1)..]) : 0;
+ var entries = Entries[(zone << 16) | cfc] = [];
+ foreach (var jentry in jentries.Value.EnumerateArray())
{
- var sep = jentries.Name.IndexOf('.', StringComparison.Ordinal);
- var zone = sep >= 0 ? uint.Parse(jentries.Name.AsSpan()[..sep]) : uint.Parse(jentries.Name);
- var cfc = sep >= 0 ? uint.Parse(jentries.Name.AsSpan()[(sep + 1)..]) : 0;
- var entries = Entries[(zone << 16) | cfc] = [];
- foreach (var jentry in jentries.Value.EnumerateArray())
- {
- entries.Add(new(
- ReadVec3(jentry, nameof(Entry.MinBounds)),
- ReadVec3(jentry, nameof(Entry.MaxBounds)),
- ReadWPos(jentry, nameof(Entry.Origin)),
- jentry.GetProperty(nameof(Entry.ViewWidth)).GetInt32(),
- jentry.GetProperty(nameof(Entry.ViewHeight)).GetInt32(),
- jentry.GetProperty(nameof(Entry.Filename)).GetString() ?? ""
- ));
- }
+ entries.Add(new(
+ ReadVec3(jentry, nameof(Entry.MinBounds)),
+ ReadVec3(jentry, nameof(Entry.MaxBounds)),
+ ReadWPos(jentry, nameof(Entry.Origin)),
+ jentry.GetProperty(nameof(Entry.ViewWidth)).GetInt32(),
+ jentry.GetProperty(nameof(Entry.ViewHeight)).GetInt32(),
+ jentry.GetProperty(nameof(Entry.Filename)).GetString() ?? ""
+ ));
}
}
- catch (Exception ex)
- {
- Service.Log($"Failed to load obstacle map database '{listPath}': {ex}");
- }
}
public void Save(string listPath)
diff --git a/BossMod/Pathfinding/ObstacleMapManager.cs b/BossMod/Pathfinding/ObstacleMapManager.cs
index 9e77a27f3b..7b9662fc49 100644
--- a/BossMod/Pathfinding/ObstacleMapManager.cs
+++ b/BossMod/Pathfinding/ObstacleMapManager.cs
@@ -1,4 +1,7 @@
-namespace BossMod.Pathfinding;
+using System.IO;
+using System.Reflection;
+
+namespace BossMod.Pathfinding;
[ConfigDisplay(Name = "Obstacle map development", Order = 8)]
public sealed class ObstacleMapConfig : ConfigNode
@@ -19,6 +22,8 @@ public sealed class ObstacleMapManager : IDisposable
private readonly EventSubscriptions _subscriptions;
private readonly List<(ObstacleMapDatabase.Entry entry, Bitmap data)> _entries = [];
+ public bool LoadFromSource => _config.LoadFromSource;
+
public ObstacleMapManager(WorldState ws)
{
World = ws;
@@ -41,10 +46,20 @@ public void Dispose()
public void ReloadDatabase()
{
- var dbPath = _config.LoadFromSource ? _config.SourcePath : ""; // TODO: load from near assembly instead
- Service.Log($"Loading obstacle database from '{dbPath}'");
- Database.Load(dbPath);
- RootPath = dbPath[..(dbPath.LastIndexOfAny(['\\', '/']) + 1)];
+ Service.Log($"Loading obstacle database from {(_config.LoadFromSource ? _config.SourcePath : "")}");
+ RootPath = _config.LoadFromSource ? _config.SourcePath[..(_config.SourcePath.LastIndexOfAny(['\\', '/']) + 1)] : "";
+
+ try
+ {
+ using var dbStream = _config.LoadFromSource ? File.OpenRead(_config.SourcePath) : GetEmbeddedResource("maplist.json");
+ Database.Load(dbStream);
+ }
+ catch (Exception ex)
+ {
+ Service.Log($"Failed to load obstacle database: {ex}");
+ Database.Entries.Clear();
+ }
+
LoadMaps(World.CurrentZone, World.CurrentCFCID);
}
@@ -63,17 +78,19 @@ private void LoadMaps(ushort zoneId, ushort cfcId)
{
foreach (var e in entries)
{
- var filename = RootPath + e.Filename;
try
{
- var bitmap = new Bitmap(filename);
+ using var eStream = _config.LoadFromSource ? File.OpenRead(RootPath + e.Filename) : GetEmbeddedResource(e.Filename);
+ var bitmap = new Bitmap(eStream);
_entries.Add((e, bitmap));
}
catch (Exception ex)
{
- Service.Log($"Failed to load map {filename} for {zoneId}.{cfcId}: {ex}");
+ Service.Log($"Failed to load map {e.Filename} from {(_config.LoadFromSource ? RootPath : "")} for {zoneId}.{cfcId}: {ex}");
}
}
}
}
+
+ private Stream GetEmbeddedResource(string name) => Assembly.GetExecutingAssembly().GetManifestResourceStream($"BossMod.Pathfinding.ObstacleMaps.{name}") ?? throw new InvalidDataException($"Missing embedded resource {name}");
}
diff --git a/BossMod/Util/Bitmap.cs b/BossMod/Util/Bitmap.cs
index 20d78f31e8..6a530236c4 100644
--- a/BossMod/Util/Bitmap.cs
+++ b/BossMod/Util/Bitmap.cs
@@ -150,29 +150,28 @@ public Bitmap(int width, int height, Color color0, Color color1, int resolution
Pixels = new byte[height * BytesPerRow];
}
- public Bitmap(string filename)
+ public Bitmap(Stream stream)
{
- using var fstream = File.OpenRead(filename);
- using var reader = new BinaryReader(fstream);
- var fileHeader = fstream.ReadStruct();
+ using var reader = new BinaryReader(stream);
+ var fileHeader = stream.ReadStruct();
if (fileHeader.Type != Magic)
- throw new ArgumentException($"File '{filename}' is not a bitmap: magic is {fileHeader.Type:X4}");
+ throw new ArgumentException($"Not a bitmap: magic is {fileHeader.Type:X4}");
- var header = fstream.ReadStruct();
+ var header = stream.ReadStruct();
if (header.SizeInBytes != Marshal.SizeOf())
- throw new ArgumentException($"Bitmap '{filename}' has unsupported header size {header.SizeInBytes}");
+ throw new ArgumentException($"Bitmap has unsupported header size {header.SizeInBytes}");
if (header.Width <= 0)
- throw new ArgumentException($"Bitmap '{filename}' has non-positive width {header.Width}");
+ throw new ArgumentException($"Bitmap has non-positive width {header.Width}");
if (header.Height >= 0)
- throw new ArgumentException($"Bitmap '{filename}' is not top-down (height={header.Height})");
+ throw new ArgumentException($"Bitmap is not top-down (height={header.Height})");
if (header.BitCount != 1)
- throw new ArgumentException($"Bitmap '{filename}' is not 1bpp (bitcount={header.BitCount})");
+ throw new ArgumentException($"Bitmap is not 1bpp (bitcount={header.BitCount})");
if (header.Compression != 0)
- throw new ArgumentException($"Bitmap '{filename}' has unsupported compression method {header.Compression:X8}");
+ throw new ArgumentException($"Bitmap has unsupported compression method {header.Compression:X8}");
if (header.XPixelsPerMeter != header.YPixelsPerMeter || header.XPixelsPerMeter <= 0)
- throw new ArgumentException($"Bitmap '{filename}' has inconsistent or non-positive resolution {header.XPixelsPerMeter}x{header.YPixelsPerMeter}");
+ throw new ArgumentException($"Bitmap has inconsistent or non-positive resolution {header.XPixelsPerMeter}x{header.YPixelsPerMeter}");
if (header.ColorUsedCount is not 0 or 2)
- throw new ArgumentException($"Bitmap '{filename}' has wrong palette size {header.ColorUsedCount}");
+ throw new ArgumentException($"Bitmap has wrong palette size {header.ColorUsedCount}");
Width = header.Width;
Height = -header.Height;
diff --git a/BossMod/Util/Serialization.cs b/BossMod/Util/Serialization.cs
index 97e48dc8d9..01c72b2d32 100644
--- a/BossMod/Util/Serialization.cs
+++ b/BossMod/Util/Serialization.cs
@@ -26,9 +26,10 @@ public class JsonTypeConverter : JsonConverter
public static JsonDocument ReadJson(string path)
{
using var fstream = File.OpenRead(path);
- return JsonDocument.Parse(fstream, new JsonDocumentOptions() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip });
+ return ReadJson(fstream);
}
+ public static JsonDocument ReadJson(Stream stream) => JsonDocument.Parse(stream, new JsonDocumentOptions() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip });
public static Utf8JsonWriter WriteJson(Stream fstream, bool indented = true) => new(fstream, new JsonWriterOptions() { Indented = indented });
public static unsafe T ReadStruct(this Stream stream) where T : unmanaged
diff --git a/TODO b/TODO
index bef64be35f..5c5907301d 100644
--- a/TODO
+++ b/TODO
@@ -44,8 +44,6 @@ general:
- zone modules: module info ui
- refactor ipc/dtr
- questbattles
-- collisions for pathfinding
--- embedded mode
boss modules:
- timers
diff --git a/UIDev/BitmapEditorTest.cs b/UIDev/BitmapEditorTest.cs
index da4601fc6f..1c6e0dcf94 100644
--- a/UIDev/BitmapEditorTest.cs
+++ b/UIDev/BitmapEditorTest.cs
@@ -1,5 +1,6 @@
using BossMod;
using ImGuiNET;
+using System.IO;
namespace UIDev;
@@ -43,7 +44,8 @@ protected override void DrawSidebar()
if (ImGui.Button("load"))
{
- CheckpointNoClone(new("D:\\test.bmp"));
+ using var stream = File.OpenRead("D:\\test.bmp");
+ CheckpointNoClone(new(stream));
}
}