diff --git a/Better NCP Editor/EditValueForm.cs b/Better NCP Editor/EditValueForm.cs index 2ce5aa7..59dd5af 100644 --- a/Better NCP Editor/EditValueForm.cs +++ b/Better NCP Editor/EditValueForm.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; +using System.Globalization; namespace Better_NCP_Editor { @@ -188,21 +189,14 @@ namespace Better_NCP_Editor private void BtnOk_Click(object sender, EventArgs e) { - // Determine the new value based on the type of the input control. if (inputControl is ComboBox combo) { if (valueType == typeof(bool)) { - // Parse the selected string to a boolean. if (bool.TryParse(combo.SelectedItem?.ToString(), out bool boolVal)) - { NewValue = boolVal; - } else - { - // Handle parsing error as needed. NewValue = false; - } } else { @@ -212,11 +206,32 @@ namespace Better_NCP_Editor } else if (inputControl is TextBox txt) { - // If possible, parse to int; otherwise, leave as string. - if (int.TryParse(txt.Text, out int intValue)) + // Parse the value based on the expected type using invariant culture. + if (valueType == typeof(int) && + int.TryParse(txt.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intValue)) + { NewValue = intValue; + } + else if (valueType == typeof(double) && + double.TryParse(txt.Text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double doubleValue)) + { + NewValue = doubleValue; + } + else if (valueType == typeof(float) && + float.TryParse(txt.Text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float floatValue)) + { + NewValue = floatValue; + } + else if (valueType == typeof(bool) && + bool.TryParse(txt.Text, out bool boolValue)) + { + NewValue = boolValue; + } else + { + // Default to string if parsing fails. NewValue = txt.Text; + } } this.DialogResult = DialogResult.OK; this.Close(); diff --git a/Better NCP Editor/Form1.Designer.cs b/Better NCP Editor/Form1.Designer.cs index 8066b30..8b18665 100644 --- a/Better NCP Editor/Form1.Designer.cs +++ b/Better NCP Editor/Form1.Designer.cs @@ -39,6 +39,9 @@ btn_import_entityData = new Button(); btn_export_entityData = new Button(); statusTextbox = new TextBox(); + searchTextBox = new TextBox(); + btn_Search = new Button(); + btn_Search_Clear = new Button(); SuspendLayout(); // // btn_load @@ -69,7 +72,7 @@ dirTreeView.Location = new Point(15, 40); dirTreeView.Margin = new Padding(3, 2, 3, 2); dirTreeView.Name = "dirTreeView"; - dirTreeView.Size = new Size(254, 610); + dirTreeView.Size = new Size(254, 623); dirTreeView.TabIndex = 2; // // entityTreeView @@ -77,7 +80,7 @@ entityTreeView.Location = new Point(274, 40); entityTreeView.Margin = new Padding(3, 2, 3, 2); entityTreeView.Name = "entityTreeView"; - entityTreeView.Size = new Size(738, 610); + entityTreeView.Size = new Size(738, 623); entityTreeView.TabIndex = 3; // // btn_entity_del @@ -107,10 +110,10 @@ // btn_import_entityData // btn_import_entityData.Enabled = false; - btn_import_entityData.Location = new Point(431, 9); + btn_import_entityData.Location = new Point(366, 9); btn_import_entityData.Margin = new Padding(3, 2, 3, 2); btn_import_entityData.Name = "btn_import_entityData"; - btn_import_entityData.Size = new Size(61, 22); + btn_import_entityData.Size = new Size(68, 22); btn_import_entityData.TabIndex = 9; btn_import_entityData.Text = "Import"; btn_import_entityData.UseVisualStyleBackColor = true; @@ -119,10 +122,10 @@ // btn_export_entityData // btn_export_entityData.Enabled = false; - btn_export_entityData.Location = new Point(498, 9); + btn_export_entityData.Location = new Point(440, 9); btn_export_entityData.Margin = new Padding(3, 2, 3, 2); btn_export_entityData.Name = "btn_export_entityData"; - btn_export_entityData.Size = new Size(58, 22); + btn_export_entityData.Size = new Size(65, 22); btn_export_entityData.TabIndex = 10; btn_export_entityData.Text = "Export"; btn_export_entityData.UseVisualStyleBackColor = true; @@ -132,18 +135,50 @@ // statusTextbox.BorderStyle = BorderStyle.FixedSingle; statusTextbox.ImeMode = ImeMode.NoControl; - statusTextbox.Location = new Point(578, 10); + statusTextbox.Location = new Point(15, 667); statusTextbox.Margin = new Padding(3, 2, 3, 2); statusTextbox.Name = "statusTextbox"; statusTextbox.ReadOnly = true; - statusTextbox.Size = new Size(434, 23); + statusTextbox.Size = new Size(997, 23); statusTextbox.TabIndex = 11; // + // searchTextBox + // + searchTextBox.Location = new Point(725, 8); + searchTextBox.Name = "searchTextBox"; + searchTextBox.Size = new Size(287, 23); + searchTextBox.TabIndex = 12; + // + // btn_Search + // + btn_Search.Location = new Point(605, 7); + btn_Search.Margin = new Padding(3, 2, 3, 2); + btn_Search.Name = "btn_Search"; + btn_Search.Size = new Size(65, 22); + btn_Search.TabIndex = 13; + btn_Search.Text = "Search"; + btn_Search.UseVisualStyleBackColor = true; + btn_Search.Click += btn_Search_Click; + // + // btn_Search_Clear + // + btn_Search_Clear.Location = new Point(676, 7); + btn_Search_Clear.Margin = new Padding(3, 2, 3, 2); + btn_Search_Clear.Name = "btn_Search_Clear"; + btn_Search_Clear.Size = new Size(43, 22); + btn_Search_Clear.TabIndex = 14; + btn_Search_Clear.Text = "Clear"; + btn_Search_Clear.UseVisualStyleBackColor = true; + btn_Search_Clear.Click += btn_Search_Clear_Click; + // // Form1 // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1028, 661); + ClientSize = new Size(1028, 696); + Controls.Add(btn_Search_Clear); + Controls.Add(btn_Search); + Controls.Add(searchTextBox); Controls.Add(statusTextbox); Controls.Add(btn_export_entityData); Controls.Add(btn_import_entityData); @@ -155,7 +190,7 @@ Controls.Add(btn_load); Margin = new Padding(3, 2, 3, 2); Name = "Form1"; - Text = "BetterNPC Editor V1.0"; + Text = "BetterNPC"; Load += Form1_Load; ResumeLayout(false); PerformLayout(); @@ -172,5 +207,8 @@ private Button btn_import_entityData; private Button btn_export_entityData; private TextBox statusTextbox; + private TextBox searchTextBox; + private Button btn_Search; + private Button btn_Search_Clear; } } diff --git a/Better NCP Editor/Form1.cs b/Better NCP Editor/Form1.cs index 565e77c..12d667a 100644 --- a/Better NCP Editor/Form1.cs +++ b/Better NCP Editor/Form1.cs @@ -5,11 +5,14 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization.Metadata; using System.Windows.Forms; +using System.Globalization; namespace Better_NCP_Editor { public partial class Form1 : Form { + private const string formTitle = "Better NCP Editor"; + private const string version = "0.0.2-alpha"; private string currentJsonFilePath; private JsonNode currentJson; private bool fileModified = false; @@ -23,7 +26,7 @@ namespace Better_NCP_Editor private List _beltItems; private List _weaponModItems; - private Dictionary> _itemShortnameToSkinName; + private Dictionary> _itemShortnameToSkinName; // All items dictionary. private Dictionary _allItems; @@ -31,13 +34,16 @@ namespace Better_NCP_Editor public Form1() { InitializeComponent(); + this.Text = $"{formTitle} v{version}"; this.AutoScaleMode = AutoScaleMode.None; dirTreeView.AfterSelect += DirTreeView_AfterSelect; entityTreeView.NodeMouseDoubleClick += entityTreeView_NodeMouseDoubleClick; entityTreeView.AfterSelect += entityTreeView_AfterSelect; + searchTextBox.KeyDown += SearchTextBox_KeyDown; + // Prevent the form from shrinking below its current size. - this.MinimumSize = new Size(1044, 700); // Set minimum allowed size + this.MinimumSize = new Size(1044, 735); // Set minimum allowed size this.FormBorderStyle = FormBorderStyle.Sizable; // Example layout using docking: @@ -50,6 +56,7 @@ namespace Better_NCP_Editor // In the designer or constructor: dirTreeView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left; entityTreeView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + statusTextbox.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; entityTreeView.ShowNodeToolTips = false; // Configure the custom tooltip. @@ -87,7 +94,7 @@ namespace Better_NCP_Editor } } - private Dictionary LoadItemDatabase(String filepath) + private Dictionary LoadItemDatabase(String filepath) { try { @@ -101,7 +108,7 @@ namespace Better_NCP_Editor } } - private Dictionary> LoadSkinDict(String filepath) + private Dictionary> LoadSkinDict(String filepath) { try { @@ -265,9 +272,11 @@ namespace Better_NCP_Editor foreach (var kvp in obj) { TreeNode child; - if (kvp.Value is JsonValue) + if (kvp.Value is JsonValue jsonValue) { - child = new TreeNode($"{kvp.Key}: {kvp.Value}"); + // Format the value properly based on its type + string displayValue = FormatJsonValueForDisplay(jsonValue); + child = new TreeNode($"{kvp.Key}: {displayValue}"); } else { @@ -309,9 +318,11 @@ namespace Better_NCP_Editor { JsonNode item = arr[i]; TreeNode child; - if (item is JsonValue) + if (item is JsonValue jsonValue) { - child = new TreeNode($"[{i}]: {item}"); + // Format the value properly based on its type + string displayValue = FormatJsonValueForDisplay(jsonValue); + child = new TreeNode($"[{i}]: {displayValue}"); } else { @@ -324,6 +335,31 @@ namespace Better_NCP_Editor } } + // Helper method to properly format JSON values for display + private string FormatJsonValueForDisplay(JsonValue jsonValue) + { + try + { + // Get the underlying value + object value = jsonValue.GetValue(); + + if (value is double doubleVal) + return doubleVal.ToString(CultureInfo.InvariantCulture); + else if (value is float floatVal) + return floatVal.ToString(CultureInfo.InvariantCulture); + else if (value is bool boolVal) + return boolVal.ToString().ToLowerInvariant(); // Display as lowercase true/false + else if (value is string str) + return str; // Return the raw string without quotes. + else + return jsonValue.ToString(); // Default fallback. + } + catch + { + return jsonValue.ToString(); + } + } + private void entityTreeView_AfterSelect(object sender, TreeViewEventArgs e) { @@ -429,8 +465,12 @@ namespace Better_NCP_Editor { if (bool.TryParse(currentVal, out _)) return typeof(bool); - if (int.TryParse(currentVal, out _)) + if (int.TryParse(currentVal, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) return typeof(int); + if (float.TryParse(currentVal, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out _)) + return typeof(float); + if (double.TryParse(currentVal, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out _)) + return typeof(double); return typeof(string); } @@ -589,7 +629,8 @@ namespace Better_NCP_Editor obj["Mods"] = new JsonArray(); // Empty array obj["Ammo"] = ""; newNode = obj; - } else if (arrayName.Equals("List of prefabs", StringComparison.OrdinalIgnoreCase)) + } + else if (arrayName.Equals("List of prefabs", StringComparison.OrdinalIgnoreCase)) { var obj = new JsonObject(); obj["Chance [0.0-100.0]"] = 100.0; @@ -945,5 +986,155 @@ namespace Better_NCP_Editor } } } + + private void btn_Search_Click(object sender, EventArgs e) + { + string searchText = searchTextBox.Text.Trim().ToLowerInvariant(); + + if (string.IsNullOrEmpty(searchText)) + { + // If search text is empty, restore the full tree view + if (currentJson != null) + { + PopulateEntityTree(currentJson); + statusTextbox.Text = "Search cleared. Displaying all nodes."; + } + return; + } + + if (currentJson == null) + { + statusTextbox.Text = "No JSON file loaded to search."; + return; + } + + // Clear the tree view before repopulating with filtered results + entityTreeView.Nodes.Clear(); + + // Get the filename for the root node + string fileName = string.IsNullOrEmpty(currentJsonFilePath) ? "JSON" : Path.GetFileName(currentJsonFilePath); + TreeNode rootNode = new TreeNode(fileName) + { + Tag = currentJson + }; + entityTreeView.Nodes.Add(rootNode); + + // Use a flag to track if any matches were found + bool foundMatches = SearchAndPopulateTree(currentJson, rootNode, searchText); + + if (foundMatches) + { + rootNode.ExpandAll(); + statusTextbox.Text = $"Search complete. Showing results for: '{searchText}'"; + } + else + { + // No matches found + PopulateEntityTree(currentJson); // Restore the full tree + statusTextbox.Text = $"No matches found for: '{searchText}'"; + } + } + + private bool SearchAndPopulateTree(JsonNode node, TreeNode treeNode, string searchText) + { + bool foundMatch = false; + + if (node is JsonObject obj) + { + foreach (var kvp in obj) + { + bool keyMatch = kvp.Key.ToLowerInvariant().Contains(searchText); + bool valueMatch = false; + TreeNode child; + + if (kvp.Value is JsonValue jsonValue) + { + string displayValue = FormatJsonValueForDisplay(jsonValue); + string valueStr = jsonValue.ToString().ToLowerInvariant(); + valueMatch = valueStr.Contains(searchText); + child = new TreeNode($"{kvp.Key}: {displayValue}"); + } + else + { + child = new TreeNode(kvp.Key); + } + + child.ToolTipText = toolTips.tips.ContainsKey(kvp.Key) ? toolTips.tips[kvp.Key] : ""; + child.Tag = kvp.Value; + + if (keyMatch || valueMatch) + { + // Direct match found: + // Add the full subtree without filtering. + PopulateTreeRecursive(kvp.Value, child); + treeNode.Nodes.Add(child); + foundMatch = true; + } + else + { + // No direct match: continue filtering the children. + bool childrenMatch = SearchAndPopulateTree(kvp.Value, child, searchText); + if (childrenMatch) + { + treeNode.Nodes.Add(child); + foundMatch = true; + } + } + } + } + else if (node is JsonArray arr) + { + for (int i = 0; i < arr.Count; i++) + { + JsonNode item = arr[i]; + TreeNode child; + + if (item is JsonValue jsonValue) + { + string displayValue = FormatJsonValueForDisplay(jsonValue); + string valueStr = jsonValue.ToString().ToLowerInvariant(); + bool valueMatch = valueStr.Contains(searchText); + child = new TreeNode($"[{i}]: {displayValue}"); + + if (valueMatch) + { + child.BackColor = Color.LightYellow; + treeNode.Nodes.Add(child); + foundMatch = true; + } + } + else + { + child = new TreeNode($"[{i}]"); + child.Tag = item; + bool childrenMatch = SearchAndPopulateTree(item, child, searchText); + if (childrenMatch) + { + treeNode.Nodes.Add(child); + foundMatch = true; + } + } + } + } + return foundMatch; + } + + private void btn_Search_Clear_Click(object sender, EventArgs e) + { + searchTextBox.Clear(); + btn_Search_Click(sender, e); + } + + private void SearchTextBox_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + // Prevent the beep sound + e.SuppressKeyPress = true; + + // Trigger the search button click + btn_Search_Click(sender, EventArgs.Empty); + } + } } }