From 52d495b4b2e3adf15d2a56fe479597d723641c63 Mon Sep 17 00:00:00 2001 From: Gh3ttoK!nG Date: Sun, 6 Oct 2024 23:15:03 +0200 Subject: [PATCH] Add and Delete rows in Database Sometimes cells for "DialogResource" (TypeId = 11) have the TypeId = 5 (String) and sometimes the intended TypeId = 11, same goes for Characters (TypeId = 6). This sometimes causes program to crash when entering the cell. This causes the program to crash when entering / editing the cell with wrong TypeId (unless story reloaded) Presumably this is a bug from Larian Studio in BG3. Workarounds: - Use typeId from first row, if database is not empty (implemented) - Create columns with typeId previously copied from another StorySave (implemented) - Save and reload the StorySave (implemented) Might cause problems ingame because it possibly has a different typeId than intended. --- ConverterApp/OsirisPane.Designer.cs | 2 + ConverterApp/OsirisPane.cs | 313 ++++++++++++++++++++++------ 2 files changed, 255 insertions(+), 60 deletions(-) diff --git a/ConverterApp/OsirisPane.Designer.cs b/ConverterApp/OsirisPane.Designer.cs index a6152cf3..45fb82f9 100644 --- a/ConverterApp/OsirisPane.Designer.cs +++ b/ConverterApp/OsirisPane.Designer.cs @@ -121,6 +121,8 @@ private void InitializeComponent() this.databaseGrid.Name = "databaseGrid"; this.databaseGrid.Size = new System.Drawing.Size(829, 380); this.databaseGrid.TabIndex = 2; + this.databaseGrid.CellContextMenuStripNeeded += databaseGrid_CellContextMenuStripNeeded; + this.databaseGrid.MouseClick += databaseGrid_MouseClick; // // databaseSelectorCb // diff --git a/ConverterApp/OsirisPane.cs b/ConverterApp/OsirisPane.cs index 00b0ca33..7d06aac6 100644 --- a/ConverterApp/OsirisPane.cs +++ b/ConverterApp/OsirisPane.cs @@ -16,11 +16,11 @@ public partial class OsirisPane : UserControl { private Story _story; public Game Game; - + private ConverterAppSettings Settings { get; } private readonly List> _databaseItems = new List>(); - + public OsirisPane(ISettingsDataSource settingsDataSource) { InitializeComponent(); @@ -31,9 +31,9 @@ public OsirisPane(ISettingsDataSource settingsDataSource) goalPath.DataBindings.Add("Text", settingsDataSource, "Settings.Story.OutputPath", true, DataSourceUpdateMode.OnPropertyChanged); tbFilter.DataBindings.Add("Text", settingsDataSource, "Settings.Story.FilterText", true, DataSourceUpdateMode.OnPropertyChanged); btnFilterMatchCase.DataBindings.Add("Tag", settingsDataSource, "Settings.Story.FilterMatchCase", true, DataSourceUpdateMode.OnPropertyChanged); - + btnFilterMatchCase.BackColor = Color.FromKnownColor(Settings.Story.FilterMatchCase ? KnownColor.MenuHighlight : KnownColor.Control); - + databaseSelectorCb.DisplayMember = "Value"; databaseSelectorCb.ValueMember = "Key"; } @@ -73,7 +73,7 @@ private void LoadStory() _databaseItems.Add(new KeyValuePair(index, name)); index += 1; } - + databaseSelectorCb_FilterDropdownList(); RefreshDataGrid(); } @@ -82,7 +82,7 @@ public Resource LoadResourceFromSave(string path) { var packageReader = new PackageReader(); using var package = packageReader.Read(path); - + var abstractFileInfo = package.Files.FirstOrDefault(p => p.Name.ToLowerInvariant() == "globals.lsf"); if (abstractFileInfo == null) { @@ -105,33 +105,33 @@ private void loadStoryBtn_Click(object sender, EventArgs e) switch (extension) { case ".lsv": - { - using (var saveHelpers = new SavegameHelpers(storyFilePath.Text)) { - _story = saveHelpers.LoadStory(); - LoadStory(); + using (var saveHelpers = new SavegameHelpers(storyFilePath.Text)) + { + _story = saveHelpers.LoadStory(); + LoadStory(); + } + + MessageBox.Show("Save game database loaded successfully."); + break; } - - MessageBox.Show("Save game database loaded successfully."); - break; - } case ".osi": - { - using (var file = new FileStream(storyFilePath.Text, FileMode.Open, FileAccess.Read, FileShare.Read)) { - var reader = new StoryReader(); - _story = reader.Read(file); - LoadStory(); + using (var file = new FileStream(storyFilePath.Text, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var reader = new StoryReader(); + _story = reader.Read(file); + LoadStory(); + } + + MessageBox.Show("Story file loaded successfully."); + break; } - - MessageBox.Show("Story file loaded successfully."); - break; - } default: - { - MessageBox.Show($"Unsupported file extension: {extension}", "Load Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); - break; - } + { + MessageBox.Show($"Unsupported file extension: {extension}", "Load Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } } } @@ -184,22 +184,22 @@ private void saveStoryBtn_Click(object sender, EventArgs e) switch (extension) { case ".lsv": - { - SaveSavegameDatabase(); - MessageBox.Show("Save game database save successful."); - break; - } + { + SaveSavegameDatabase(); + MessageBox.Show("Save game database save successful."); + break; + } case ".osi": - { - SaveStory(); - MessageBox.Show("Story file save successful."); - break; - } + { + SaveStory(); + MessageBox.Show("Story file save successful."); + break; + } default: - { - MessageBox.Show($"Unsupported file extension: {extension}", "Story save failed", MessageBoxButtons.OK, MessageBoxIcon.Error); - break; - } + { + MessageBox.Show($"Unsupported file extension: {extension}", "Story save failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } } } @@ -264,28 +264,28 @@ private void databaseSelectorCb_FilterDropdownList() return; } - List> queryResults; + List> queryResults; - if (Settings.Story.FilterMatchCase == false) - { - queryResults = _databaseItems + if (Settings.Story.FilterMatchCase == false) + { + queryResults = _databaseItems .Where(s => s.Value.ToLowerInvariant().Contains(tbFilter.Text.ToLowerInvariant())) - .ToList(); - } - else - { - queryResults = _databaseItems + .ToList(); + } + else + { + queryResults = _databaseItems .Where(s => s.Value.Contains(tbFilter.Text)) - .ToList(); - } + .ToList(); + } - if (queryResults.Any()) - { - databaseSelectorCb.DataSource = queryResults; - databaseSelectorCb.SelectedIndex = 0; - } - else - { + if (queryResults.Any()) + { + databaseSelectorCb.DataSource = queryResults; + databaseSelectorCb.SelectedIndex = 0; + } + else + { tbFilter.BackColor = Color.LightCoral; } } @@ -338,5 +338,198 @@ private void btnDatabaseFilterMatchCase_Click(object sender, EventArgs e) if (tbFilter.Text.Trim().Length > 0) databaseSelectorCb_FilterDropdownList(); } + + private void databaseGrid_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e) + { + if (databaseGrid.SelectedRows.Count == 1 && e.RowIndex != -1) + { + databaseGrid.ClearSelection(); + databaseGrid.Rows[e.RowIndex].Selected = true; + } + else if (databaseGrid.SelectedCells.Count == 1 && e.RowIndex != -1) + { + databaseGrid.ClearSelection(); + databaseGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected = true; + } + } + private void databaseGrid_MouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + { + if (databaseGrid.Rows.Count.Equals(0)) + { + ContextMenuStrip m = new ContextMenuStrip(); + ToolStripMenuItem MenuItem; + + MenuItem = new ToolStripMenuItem { Name = "AddRow", Text = "Add Row" }; + MenuItem.Click += new EventHandler(databaseGrid_MenuItemClickHandler); + m.Items.Add(MenuItem); + m.Show(databaseGrid, new Point(e.X, e.Y), ToolStripDropDownDirection.BelowRight); + } + else if (true) + { + ContextMenuStrip m = new ContextMenuStrip(); + ToolStripMenuItem MenuItem; + + MenuItem = new ToolStripMenuItem { Name = "AddRow", Text = "Add Row" }; + MenuItem.Click += new EventHandler(databaseGrid_MenuItemClickHandler); + m.Items.Add(MenuItem); + MenuItem = new ToolStripMenuItem { Name = "CopyTypes", Text = "Copy database column types" }; + MenuItem.Click += new EventHandler(databaseGrid_MenuItemClickHandler); + m.Items.Add(MenuItem); + MenuItem = new ToolStripMenuItem { Name = "DeleteRow", Text = "Delete Row" }; + MenuItem.Click += new EventHandler(databaseGrid_MenuItemClickHandler); + m.Items.Add(MenuItem); + + m.Show(databaseGrid, new Point(e.X, e.Y), ToolStripDropDownDirection.BelowRight); + } + } + } + private void databaseGrid_MenuItemClickHandler(object sender, EventArgs e) + { + ToolStripMenuItem clickedItem = (ToolStripMenuItem)sender; + if (clickedItem.Name == "AddRow") + { + if (databaseSelectorCb.SelectedItem != null) + { + var selectedIndex = ((KeyValuePair)databaseSelectorCb.SelectedItem).Key; + Database database = _story.Databases[selectedIndex + 1]; + + List ColumnTypeCheck = new List(); + List ColumnTypeUInt = new List(); + database.Parameters.Types.ForEach(type => ColumnTypeCheck.Add(false)); + if (database.Facts.Count == 0) + { + string[] ColumnTypes = Clipboard.GetText().Split(";"); + if (ColumnTypes.Length == database.Parameters.Types.Count) + { + for (int i = 0; i < ColumnTypes.Length; i++) + { + string[] ColumnType = ColumnTypes[i].Split(" "); + if (ColumnType.Length == 2) + { + if (uint.TryParse(ColumnType[0], out uint result)) + { + ColumnTypeUInt.Add(Convert.ToUInt32(ColumnType[0])); + string ColumnTypeString; + ColumnTypeString = ColumnType[1]; + if (ColumnTypeString == "{" + _story.Types[database.Parameters.Types[i]].Name + "}") + { + ColumnTypeCheck[i] = true; + } + } + } + } + } + /* + if (ColumnTypeCheck.Contains(false)) + { + MessageBox.Show("Can't get database column type from clipboard." + Environment.NewLine + + "There is a bug the databaset hat the column type of the database is not the specified in the database parameters." + Environment.NewLine + + "This causes the program to crash with the wrong database column type." + Environment.NewLine + + Environment.NewLine + + "Right-Click and use 'Copy database column types' from the same database of another Story/savegame to add the correct database column types!", + "Can't get database column types from clipboard", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + */ + } + DialogResult ConfirmCreateEntry = DialogResult.No; + if (database.Facts.Count == 0 && ColumnTypeCheck.Contains(false)) + { + ConfirmCreateEntry = MessageBox.Show("Story needs to saved and reloaded, otherwise the program might crash due to mismatch of the database column type." + Environment.NewLine + + "Creating a new entry in an empty database is even more dangerous, because for unkown reasons in some databases the columns have a different type than in the properties and this can corrupt your story file." + Environment.NewLine + + Environment.NewLine + + "Alternative: Use the function 'Copy database column types' from another story" + Environment.NewLine + + Environment.NewLine + + Environment.NewLine + + "Story export is an experimental feature and may corrupt your story files." + Environment.NewLine + + "Are you sure you want to do this?", + "Story needs to be saved and reloaded", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning); + } + if (database.Facts.Count > 0 || !ColumnTypeCheck.Contains(false) || ConfirmCreateEntry == DialogResult.Yes) + { + Fact NewRow = new Fact { }; + NewRow.Columns = new System.Collections.Generic.List(); + for (var i = 0; i < database.Parameters.Types.Count; i++) + { + LSLib.LS.Story.Value NewColumn; + NewColumn = new LSLib.LS.Story.Value(); + if (database.Facts.Count > 0) + { + // Adds new column with TypeId from existing row (reason see further below) + NewColumn.TypeId = database.Facts[0].Columns[i].TypeId; + } + else + { + if (!ColumnTypeCheck.Contains(false)) + { + // Add new column with TypeId from a valid clipboard entry (reason see further below) + NewColumn.TypeId = ColumnTypeUInt[i]; + } + else + { + // Add new column with TypeId from intended Parameter.Types + // !!! Only use it in combination with saving and reloading the story (buggy, can cause crash, reason see below) !!! + + // This sometimes causes program to crash because some cells have a different TypeId than in the database.Parameters.Types (in Balgur's Gate 3)! + // Sometimes cells for "DialogResource" (TypeId = 11) have the TypeId = 5 and sometimes the intended TypeId = 11, same goes for Characters (TypeId = 6). + // This causes the program to crash when entering / editing the cell with wrong TypeId (unless story reloaded) + NewColumn.TypeId = database.Parameters.Types[i]; + } + } + NewRow.Columns.Add(NewColumn); + } + database.Facts.Add(NewRow); + RefreshDataGrid(); + if (ConfirmCreateEntry == DialogResult.Yes) + { + SaveSavegameDatabase(); + loadStoryBtn.PerformClick(); + } + } + } + } + else if (clickedItem.Name == "DeleteRow") + { + var selectedIndex = ((KeyValuePair)databaseSelectorCb.SelectedItem).Key; + Database database = _story.Databases[selectedIndex + 1]; + // Creates sortable list of rows / indexes + List SelectedRows = new List(); + for (var i = 0; i < databaseGrid.SelectedCells.Count; i++) + { + SelectedRows.Add(databaseGrid.SelectedCells[i].RowIndex); + } + SelectedRows.Sort(); + SelectedRows = SelectedRows.Distinct().ToList(); // removes duplicates + SelectedRows.Reverse(); // Reverse sorting to delete from bottom to top + + foreach (int index in SelectedRows) + { + if (database.Facts.Count - 1 >= index) + { + Fact RemoveItem = database.Facts[index]; + database.Facts.Remove(RemoveItem); + } + } + RefreshDataGrid(); + } + else if (clickedItem.Name == "CopyTypes") + { + var selectedIndex = ((KeyValuePair)databaseSelectorCb.SelectedItem).Key; + Database database = _story.Databases[selectedIndex + 1]; + List ColumnTypes = new List(); + for (var i = 0; i < database.Parameters.Types.Count; i++) + { + ColumnTypes.Add(database.Facts[0].Columns[i].TypeId + " {" + _story.Types[database.Parameters.Types[i]].Name + "}"); + }; + Clipboard.SetText(string.Join(";", ColumnTypes)); + MessageBox.Show("Database column types copied"); + RefreshDataGrid(); + } + } } -} +} \ No newline at end of file