diff --git a/.editorconfig b/.editorconfig index 5007347..ed4f912 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,6 @@ # IDE0022: Use block body for methods csharp_style_expression_bodied_methods = when_on_single_line + +# RCS1123: Add parentheses when necessary. +dotnet_diagnostic.RCS1123.severity = silent diff --git a/BlueprintExplorer/BlueprintControl.cs b/BlueprintExplorer/BlueprintControl.cs index 76d1f8b..ec5b3c5 100644 --- a/BlueprintExplorer/BlueprintControl.cs +++ b/BlueprintExplorer/BlueprintControl.cs @@ -18,21 +18,27 @@ public class BlueprintControl : ScrollableControl public event LinkClickedDelegate OnLinkClicked; public event PathDelegate OnPathHovered; - private BlueprintHandle blueprint; + private Dictionary ScrollPositionCache = new(); + + private IDisplayableElementCollection DisplayedObject; private Timer ToastTimer; private Stopwatch Timer = new(); private long LastTick = 0; - public BlueprintHandle Blueprint + public IDisplayableElementCollection Blueprint { - get => blueprint; + get => DisplayedObject; set { - if (blueprint == value) return; - blueprint = value; - blueprint.EnsureParsed(); - ValidateBlueprint(); + if (DisplayedObject == value) return; + if (DisplayedObject != null) + { + ScrollPositionCache[DisplayedObject.GuidText] = VerticalScroll.Value; + } + DisplayedObject = value; + DisplayedObject.EnsureParsed(); + ValidateBlueprint(true); } } @@ -70,7 +76,7 @@ public StyleSpan(string value, StyleFlags flags = 0) { public StyleSpan[] Spans; - public String Raw => string.Join("", Spans.Select(s => s.Value)); + public String Raw => string.Concat(Spans.Select(s => s.Value)); } public class RowElement @@ -266,7 +272,7 @@ private void ValidateFilter() int StringWidthAllowed => Width - NameColumnWidth - 32; - private void ValidateBlueprint() + private void ValidateBlueprint(bool scroll) { Elements.Clear(); var oneRow = Font.Height; @@ -274,25 +280,49 @@ private void ValidateBlueprint() int strWidthAllowed = StringWidthAllowed; currentHover = -1; int totalRows = 0; - if (blueprint != null) + if (DisplayedObject != null) { Elements.Add(new () { key = "Blueprint ID", - value = blueprint.GuidText, + value = DisplayedObject.GuidText, level = 0, link = null, Parent = null, String = null, RowCount = 1, + Collapsed = true, + }); + Elements.Add(new () + { + key = "Blueprint Name", + value = DisplayedObject.Name, + level = 1, + link = null, + Parent = Elements[0], + String = null, + RowCount = 1, + Collapsed = false, + }); + Elements.Add(new () + { + key = "Blueprint Type", + value = DisplayedObject.TypeName, + level = 1, + link = null, + Parent = Elements[0], + String = null, + RowCount = 1, Collapsed = false, }); - totalRows = 1; + Elements[0].Children.Add(Elements[1]); + Elements[0].Children.Add(Elements[2]); + totalRows = 3; int level = 0; Stack stack = new(); stack.Push(null); - foreach (var e in blueprint.Elements) + foreach (var e in DisplayedObject.DisplayableElements) { int currentLevel = level; @@ -322,18 +352,17 @@ private void ValidateBlueprint() String = JsonExtensions.ParseAsString(e.Node), RowCount = 1, IsObj = e.isObj, - Collapsed = totalRows != 0 && !BubblePrints.Settings.EagerExpand, + Collapsed = totalRows != 0 && !BubblePrints.Settings.EagerExpand && currentLevel > 0, }; - if (e.isObj && e.Node.TryGetProperty("$type", out var rawType)) + if (e.isObj && e.HasType) { - var (typeGuid, typeName, typeNameFull) = rawType.NewTypeStr(); List spans = new(); - spans.Add(new(typeName + " ", StyleFlags.Bold)); - spans.Add(new("typeId: " + typeGuid)); + spans.Add(new(e.MaybeType.Name + " ", StyleFlags.Bold)); + spans.Add(new("typeId: " + e.MaybeType.Guid)); row.ValueStyled = new(spans); - row.Type = typeName; - row.TypeFull = typeNameFull; + row.Type = e.MaybeType.Name; + row.TypeFull = e.MaybeType.FullName; } if (row.key == "$type" && row.Parent != null) @@ -341,7 +370,7 @@ private void ValidateBlueprint() if (e.levelDelta == 0 && row.Parent != null) { - row.Default = BlueprintDB.DefaultForField(row.Parent?.TypeFull, e.key); + row.Default = BlueprintDB.Instance.DefaultForField(row.Parent?.TypeFull, e.key); } if (row.String != null) @@ -393,6 +422,18 @@ private void ValidateBlueprint() } } AutoScroll = true; + if (scroll) + { + if (ScrollPositionCache.TryGetValue(DisplayedObject.GuidText, out var scrollPosition)) + { + VerticalScroll.Value = scrollPosition; + } + else + { + VerticalScroll.Value = 0; + + } + } ValidateFilter(); } @@ -409,7 +450,7 @@ private RowElement GetElement(int row) protected override void OnResize(EventArgs e) { base.OnResize(e); - ValidateBlueprint(); + ValidateBlueprint(false); } private string _Filter; diff --git a/BlueprintExplorer/BlueprintDB.cs b/BlueprintExplorer/BlueprintDB.cs index c3f9af9..9fcdba1 100644 --- a/BlueprintExplorer/BlueprintDB.cs +++ b/BlueprintExplorer/BlueprintDB.cs @@ -25,7 +25,7 @@ public partial class BlueprintDB #region DEV bool generateOutput = false; bool importNew = false; - bool forceLastKnown = true; + bool forceLastKnown = false; #endregion private static BlueprintDB _Instance; @@ -100,7 +100,7 @@ public int CompareTo(GameVersion other) public List Available = new() { }; - private readonly GameVersion LastKnown = new(1, 2, 0, 'f', 1); + private readonly GameVersion LastKnown = new(1, 2, 0, 'A', 1); private readonly string filenameRoot = "blueprints_raw"; private readonly string extension = "binz"; @@ -196,8 +196,8 @@ public GoingToLoad GetLoadType() } - private static Dictionary> defaults = new(); - public static string DefaultForField(string typename, string field) + private Dictionary> defaults = new(); + public string DefaultForField(string typename, string field) { if (typename == null || !defaults.TryGetValue(typename, out var map)) return null; @@ -213,7 +213,7 @@ public class ConnectionProgress public string Status => EstimatedTotal == 0 ? "??" : $"{Current}/{EstimatedTotal} - {(Current / (double)EstimatedTotal):P1}"; } - public async Task TryConnect(ConnectionProgress progress) + public async Task TryConnect(ConnectionProgress progress, string forceFileName = null) { if (importNew) { @@ -324,32 +324,37 @@ public async Task TryConnect(ConnectionProgress progress) string fileToOpen = null; - switch (GetLoadType()) + if (forceFileName != null) + fileToOpen = forceFileName; + else { - case GoingToLoad.FromWeb: - Console.WriteLine("Settings file does not exist, downloading"); - var host = "https://github.com/factubsio/BubblePrintsData/releases/download"; - var latestVersionUrl = new Uri($"{host}/{Latest}/{filenameRoot}_{Latest}.{extension}"); - - var client = new WebClient(); - if (!Directory.Exists(CacheDir)) - Directory.CreateDirectory(CacheDir); - - fileToOpen = Path.Combine(CacheDir, FileName); - progress.EstimatedTotal = 100; - client.DownloadProgressChanged += (sender, e) => - { - progress.Current = e.ProgressPercentage; - }; - await client.DownloadFileTaskAsync(latestVersionUrl, fileToOpen); - break; - case GoingToLoad.FromCache: - fileToOpen = Path.Combine(CacheDir, FileName); - break; - case GoingToLoad.FromLocalFile: - Console.WriteLine("reading from local dev..."); - fileToOpen = FileName; - break; + switch (GetLoadType()) + { + case GoingToLoad.FromWeb: + Console.WriteLine("Settings file does not exist, downloading"); + var host = "https://github.com/factubsio/BubblePrintsData/releases/download"; + var latestVersionUrl = new Uri($"{host}/{Latest}/{filenameRoot}_{Latest}.{extension}"); + + var client = new WebClient(); + if (!Directory.Exists(CacheDir)) + Directory.CreateDirectory(CacheDir); + + fileToOpen = Path.Combine(CacheDir, FileName); + progress.EstimatedTotal = 100; + client.DownloadProgressChanged += (sender, e) => + { + progress.Current = e.ProgressPercentage; + }; + await client.DownloadFileTaskAsync(latestVersionUrl, fileToOpen); + break; + case GoingToLoad.FromCache: + fileToOpen = Path.Combine(CacheDir, FileName); + break; + case GoingToLoad.FromLocalFile: + Console.WriteLine("reading from local dev..."); + fileToOpen = FileName; + break; + } } BPFile.Reader reader = new(fileToOpen); @@ -490,6 +495,34 @@ public async Task TryConnect(ConnectionProgress progress) BubblePrints.SaveSettings(); ctx.Dispose(); + //File.WriteAllLines(@"D:\areas.txt", cache.Where(b => b.TypeName == "BlueprintAbilityAreaEffect").SelectMany(bp => + //{ + // return new String[] + // { + // $"//{bp.Name}", + // "{", + // $" \"ability\": \"{bp.GuidText}\"", + // " \"type\": \"bad\"", + // "},", + + // }; + //})); + + //var featureType = BubblePrints.Wrath.GetType("Kingmaker.Blueprints.Classes.BlueprintFeature"); + //int featuresFound = 0; + //List<(string, string)> features = new(); + //foreach (var bp in cache) + //{ + // var type = BubblePrints.Wrath.GetType(bp.Type); + // if (type == null) continue; + // if (type.IsAssignableTo(featureType)) + // { + // featuresFound++; + // features.Add((bp.Name, bp.GuidText)); + // } + //} + //Console.WriteLine("Features found: " + featuresFound); + //File.WriteAllLines(@"D:\features.txt", features.Select(f => $"{f.Item2} {f.Item1}")); } diff --git a/BlueprintExplorer/BlueprintHandle.cs b/BlueprintExplorer/BlueprintHandle.cs index 4c102a4..8bbb765 100644 --- a/BlueprintExplorer/BlueprintHandle.cs +++ b/BlueprintExplorer/BlueprintHandle.cs @@ -15,6 +15,24 @@ namespace BlueprintExplorer { + + public interface IDisplayableElement + { + public string key { get; } + public string value { get; } + public int levelDelta { get; } + public bool isObj { get; } + public string link { get; } + //public string linkTarget; + public bool Empty { get; } + public JsonElement Node { get; } + public (string Guid, string Name, string FullName) MaybeType { get; } + public bool HasType { get; } + public bool Last { get; } + + } + + static class JsonExtensions { public static bool ContainsIgnoreCase(this string haystack, string needle) => haystack.Contains(needle, StringComparison.OrdinalIgnoreCase); @@ -179,13 +197,25 @@ public static void Visit(this JsonElement elem, Action arrayIt } } - public class BlueprintHandle : ISearchable + public interface IDisplayableElementCollection + { + public void EnsureParsed(); + public IEnumerable DisplayableElements { get; } + string GuidText { get; } + string Name { get; } + string Type { get; } + string TypeName { get; } + } + + public class BlueprintHandle : ISearchable, IDisplayableElementCollection { //public byte[] guid; + + public object UserData = null; public string GuidText { get; set; } public string Name { get; set; } public string Type { get; set; } - public string TypeName; + public string TypeName { get; set; } public string Namespace; public string Raw { get; set; } public JsonElement obj; @@ -244,7 +274,7 @@ internal JsonElement EnsureObj } - internal void EnsureParsed() + public void EnsureParsed() { if (!Parsed) { @@ -285,17 +315,19 @@ public class ElementVisitor } - public class VisitedElement + public class VisitedElement : IDisplayableElement { - public string key; - public string value; - public int levelDelta; - public bool isObj; - public string link; + public string key { get; set; } + public string value { get; set; } + public int levelDelta { get; set; } + public bool isObj { get; set; } + public string link { get; set; } //public string linkTarget; - public bool Empty; - public JsonElement Node; - public bool Last; + public bool Empty { get; set; } + public JsonElement Node { get; set; } + public (string Guid, string Name, string FullName) MaybeType { get; set; } + public bool HasType => MaybeType.Name != null; + public bool Last { get; set; } } public static string ParseReference(string val) @@ -329,6 +361,8 @@ public IEnumerable Elements } } + IEnumerable IDisplayableElementCollection.DisplayableElements => Elements; + public static void VisitObjects(JsonElement node, HashSet types) { if (node.ValueKind == JsonValueKind.Array) @@ -376,7 +410,10 @@ public static IEnumerable Visit(JsonElement node, string name) } else { - yield return new VisitedElement { key = name, levelDelta = 1, isObj = true, Node = node }; + (string, string, string) maybeType = (null, null, null); + if (node.TryGetProperty("$type", out var rawType)) + maybeType = rawType.NewTypeStr(); + yield return new VisitedElement { key = name, levelDelta = 1, isObj = true, Node = node, MaybeType = maybeType }; foreach (var elem in node.EnumerateObject()) { foreach (var n in Visit(elem.Value, elem.Name)) diff --git a/BlueprintExplorer/BlueprintViewer.cs b/BlueprintExplorer/BlueprintViewer.cs index 4e73cae..e464e9a 100644 --- a/BlueprintExplorer/BlueprintViewer.cs +++ b/BlueprintExplorer/BlueprintViewer.cs @@ -47,7 +47,7 @@ public BlueprintViewer() references.CellClick += (sender, e) => ShowReferenceSelected(); - openExternal.Click += (sender, e) => OnOpenExternally?.Invoke(View.Blueprint); + openExternal.Click += (sender, e) => OnOpenExternally?.Invoke(View.Blueprint as BlueprintHandle); } public void ShowBlueprint(BlueprintHandle handle, ShowFlags flags) @@ -104,7 +104,7 @@ private void PushHistory(BlueprintHandle bp) { private void ShowReferenceSelected() { - var handle = View.Blueprint; + var handle = View.Blueprint as BlueprintHandle; if (handle == null) return; if (handle.BackReferences.Count != references.RowCount) return; int row = references.SelectedRow(); diff --git a/BlueprintExplorer/Program.cs b/BlueprintExplorer/Program.cs index b338161..ff4b6e8 100644 --- a/BlueprintExplorer/Program.cs +++ b/BlueprintExplorer/Program.cs @@ -1,8 +1,25 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; +using static BlueprintExplorer.BlueprintHandle; namespace BlueprintExplorer { + public class FieldModification + { + public string OldValue { get; set; } + public string NewValue { get; set; } + + public string Path { get; set; } + } + public class BlueprintDiff + { + public List Modifications = new(); + } + static class Program { /// @@ -13,6 +30,78 @@ static void Main() { BubblePrints.Install(); + //var prev = new BlueprintDB(); + //var prevProgress = new BlueprintDB.ConnectionProgress(); + //var prevLoad = prev.TryConnect(prevProgress, @"C:\Users\worce\AppData\Local\BubblePrints\blueprints_raw_1.2.0f_1.binz"); + + + //var curr = new BlueprintDB(); + //var currProgress = new BlueprintDB.ConnectionProgress(); + //var currLoad = curr.TryConnect(currProgress, @"C:\Users\worce\AppData\Local\BubblePrints\blueprints_raw_1.2.0A_1.binz"); + + //Task.WaitAll(prevLoad, currLoad); + + //HashSet brandNew = new(curr.Blueprints.Keys); + + //using var f = File.CreateText(@"D:\bpdiffs.txt"); + + ////foreach (var (guid, handle) in curr.Blueprints) + //var guid = Guid.Parse("d3ddec98-a1b5-0464-0916-96547113ea2c"); + //var handle = curr.Blueprints[guid]; + //{ + // if (prev.Blueprints.TryGetValue(guid, out var old)) + // { + // Dictionary valuesByPathOld = new(); + // Dictionary valuesByPathNew = new(); + + // foreach (var (node, path) in ElementVisitor.Visit(old)) + // { + // valuesByPathOld[path + "/" + node.key] = node.value; + // } + // foreach (var (node, path) in ElementVisitor.Visit(handle)) + // { + // valuesByPathNew[path + "/" + node.key] = node.value; + // } + + // File.WriteAllLines(@"D:\old.txt", valuesByPathOld.Select(kv => $"{kv.Key}: {kv.Value}")); + // File.WriteAllLines(@"D:\new.txt", valuesByPathNew.Select(kv => $"{kv.Key}: {kv.Value}")); + + // var diff = new BlueprintDiff(); + // foreach (var k in valuesByPathNew) + // { + // if (valuesByPathOld.TryGetValue(k.Key, out var oldValue)) + // { + // if (oldValue != k.Value) + // { + // diff.Modifications.Add(new() + // { + // Path = k.Key, + // NewValue = k.Value, + // OldValue = oldValue, + // }); + // } + // } + // else + // { + // diff.Modifications.Add(new() + // { + // Path = k.Key, + // NewValue = k.Value, + // OldValue = null, + // }); + // } + // } + // if (diff.Modifications.Count > 0) + // { + // f.WriteLine("diffs for: " + guid + " / " + handle.Name); + // foreach (var mod in diff.Modifications) + // f.WriteLine($"{mod.Path} :: {mod.OldValue} => {mod.NewValue}"); + // } + // handle.UserData = diff; + // brandNew.Remove(guid); + // } + //} + Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false);