mirror of
https://github.com/waynebian01/Shigure.git
synced 2026-09-03 07:15:24 +08:00
配置页改为冷却双栏与职业级物品列表
将配置页「法术」改为「冷却」(左技能冷却、右专精物品冷却),「物品」改为职业级 Fuyutsui.itemsList 编辑;保存时缺表则在 spellsList 后插入。 Co-authored-by: Wayne-Arasaka <waynebian01@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,7 @@ public sealed record ClassConfigPostSaveResult(
|
||||
|
||||
/// <summary>
|
||||
/// 图形化编辑 Fuyutsui class/*.lua 的 ClassBlocks(states / auras / spells / items / group),
|
||||
/// 并编辑同文件中的 spellsList。
|
||||
/// 并编辑同文件中的 spellsList 与 itemsList。
|
||||
/// </summary>
|
||||
public sealed class ClassConfigEditorControl : UserControl
|
||||
{
|
||||
@@ -32,6 +32,8 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
private readonly DataGridView _spellsGrid = new();
|
||||
private readonly DataGridView _itemsGrid = new();
|
||||
private readonly TextBox _itemsSearchBox = new();
|
||||
private readonly DataGridView _itemsListGrid = new();
|
||||
private readonly TextBox _itemsListSearchBox = new();
|
||||
private readonly DataGridView _itemDatabaseGrid = new();
|
||||
private readonly TextBox _itemDatabaseFilterBox = new();
|
||||
private readonly Label _itemDatabaseStatusLabel = new();
|
||||
@@ -72,6 +74,7 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
private int? _currentSpecId;
|
||||
private bool _suppressUi;
|
||||
private bool _dirty;
|
||||
private int _editorTabIndex = -1;
|
||||
|
||||
internal event Action<bool>? DirtyStateChanged;
|
||||
internal bool HasUnsavedChanges => _dirty;
|
||||
@@ -382,21 +385,28 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
}
|
||||
|
||||
var tabs = new UiPillTab[6];
|
||||
var selectedIndex = -1;
|
||||
void SelectTab(int index)
|
||||
{
|
||||
if (selectedIndex == index)
|
||||
if (_editorTabIndex == index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_suppressUi && selectedIndex == 3)
|
||||
if (!_suppressUi && _editorTabIndex == 2)
|
||||
{
|
||||
_spellsGrid.EndEdit();
|
||||
_itemsGrid.EndEdit();
|
||||
WriteBackSpells();
|
||||
WriteBackItems();
|
||||
}
|
||||
|
||||
selectedIndex = index;
|
||||
if (!_suppressUi && _editorTabIndex == 3)
|
||||
{
|
||||
_itemsListGrid.EndEdit();
|
||||
WriteBackItemsList();
|
||||
}
|
||||
|
||||
_editorTabIndex = index;
|
||||
for (var i = 0; i < tabs.Length; i++)
|
||||
{
|
||||
var selected = i == index;
|
||||
@@ -409,7 +419,7 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
var titles = new[] { "状态", "光环", "法术", "物品", "队伍", "技能列表" };
|
||||
var titles = new[] { "状态", "光环", "冷却", "物品列表", "队伍", "技能列表" };
|
||||
for (var i = 0; i < titles.Length; i++)
|
||||
{
|
||||
var index = i;
|
||||
@@ -522,14 +532,14 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
Padding = new Padding(8, 0, 0, 0)
|
||||
}, 0, 0);
|
||||
|
||||
UiTheme.StyleTextBox(_itemsSearchBox);
|
||||
_itemsSearchBox.Dock = DockStyle.None;
|
||||
_itemsSearchBox.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
_itemsSearchBox.Margin = new Padding(0);
|
||||
_itemsSearchBox.Height = 30;
|
||||
_itemsSearchBox.PlaceholderText = "itemId 或名称";
|
||||
_itemsSearchBox.TextChanged += (_, _) => ApplyItemsFilter();
|
||||
searchCard.Controls.Add(_itemsSearchBox, 1, 0);
|
||||
UiTheme.StyleTextBox(_itemsListSearchBox);
|
||||
_itemsListSearchBox.Dock = DockStyle.None;
|
||||
_itemsListSearchBox.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
_itemsListSearchBox.Margin = new Padding(0);
|
||||
_itemsListSearchBox.Height = 30;
|
||||
_itemsListSearchBox.PlaceholderText = "itemId 或名称";
|
||||
_itemsListSearchBox.TextChanged += (_, _) => ApplyItemsListFilter();
|
||||
searchCard.Controls.Add(_itemsListSearchBox, 1, 0);
|
||||
leftColumn.Controls.Add(searchCard, 0, 0);
|
||||
|
||||
var currentListCard = new UiCardPanel
|
||||
@@ -555,46 +565,45 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
};
|
||||
currentListHeader.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
currentListHeader.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
var currentListTitle = CreateCardTitle("当前专精物品");
|
||||
var currentListTitle = CreateCardTitle("物品列表");
|
||||
currentListTitle.AutoSize = true;
|
||||
currentListTitle.Dock = DockStyle.None;
|
||||
currentListTitle.Anchor = AnchorStyles.Left;
|
||||
currentListHeader.Controls.Add(currentListTitle, 0, 0);
|
||||
var hint = CreateFieldCaption("名称可改为业务别名;图标始终按 itemId 匹配。");
|
||||
var hint = CreateFieldCaption("来自当前职业 Lua 的 Fuyutsui.itemsList。");
|
||||
hint.TextAlign = ContentAlignment.MiddleRight;
|
||||
currentListHeader.Controls.Add(hint, 1, 0);
|
||||
currentListCard.Controls.Add(currentListHeader, 0, 0);
|
||||
|
||||
ConfigureGrid(_itemsGrid, "class-config-items");
|
||||
_itemsGrid.AllowUserToAddRows = false;
|
||||
_itemsGrid.CellContentClick += HandleDeleteClick;
|
||||
_itemsGrid.CellValueChanged += (_, e) =>
|
||||
ConfigureGrid(_itemsListGrid, "class-config-items-list");
|
||||
_itemsListGrid.AllowUserToAddRows = false;
|
||||
_itemsListGrid.CellContentClick += HandleItemsListDeleteClick;
|
||||
_itemsListGrid.CellValueChanged += (_, e) =>
|
||||
{
|
||||
MarkDirty();
|
||||
if (e.RowIndex >= 0 && e.RowIndex < _itemsGrid.Rows.Count)
|
||||
if (e.RowIndex >= 0 && e.RowIndex < _itemsListGrid.Rows.Count)
|
||||
{
|
||||
UpdateItemGridIcon(_itemsGrid.Rows[e.RowIndex]);
|
||||
UpdateItemGridIcon(_itemsListGrid.Rows[e.RowIndex]);
|
||||
}
|
||||
};
|
||||
_itemsGrid.DataError += (_, e) => e.ThrowException = false;
|
||||
_itemsGrid.Columns.Add(CreateSpellIconColumn());
|
||||
_itemsGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
_itemsListGrid.DataError += (_, e) => e.ThrowException = false;
|
||||
_itemsListGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "ItemId",
|
||||
HeaderText = "itemId",
|
||||
Width = 125,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable
|
||||
});
|
||||
_itemsGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
_itemsListGrid.Columns.Add(CreateSpellIconColumn());
|
||||
_itemsListGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "Name",
|
||||
HeaderText = "名称",
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable
|
||||
});
|
||||
_itemsGrid.Columns.Add(CreateSpellCheckColumn("IsEquipped", "是否装备中", 12, 110));
|
||||
_itemsGrid.Columns.Add(CreateDeleteColumn());
|
||||
currentListCard.Controls.Add(_itemsGrid, 0, 1);
|
||||
_itemsListGrid.Columns.Add(CreateDeleteColumn());
|
||||
currentListCard.Controls.Add(_itemsListGrid, 0, 1);
|
||||
leftColumn.Controls.Add(currentListCard, 0, 1);
|
||||
split.Controls.Add(leftColumn, 0, 0);
|
||||
|
||||
@@ -871,21 +880,63 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
|
||||
private Control BuildSpellsPage()
|
||||
{
|
||||
var panel = new TableLayoutPanel
|
||||
var split = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 1,
|
||||
BackColor = UiTheme.SurfaceRaised,
|
||||
Margin = new Padding(0),
|
||||
Padding = new Padding(0)
|
||||
};
|
||||
split.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
|
||||
split.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
|
||||
split.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
|
||||
var leftColumn = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 3,
|
||||
BackColor = UiTheme.SurfaceRaised
|
||||
RowCount = 2,
|
||||
BackColor = UiTheme.SurfaceRaised,
|
||||
Margin = new Padding(0, 0, 5, 0),
|
||||
Padding = new Padding(0)
|
||||
};
|
||||
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 34));
|
||||
panel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 44));
|
||||
leftColumn.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
leftColumn.RowStyles.Add(new RowStyle(SizeType.Absolute, 44));
|
||||
|
||||
var spellCard = new UiCardPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 2,
|
||||
Margin = new Padding(0),
|
||||
Padding = new Padding(0)
|
||||
};
|
||||
spellCard.RowStyles.Add(new RowStyle(SizeType.Absolute, 50));
|
||||
spellCard.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
|
||||
var spellHeader = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 1,
|
||||
BackColor = Color.Transparent,
|
||||
Margin = new Padding(0),
|
||||
Padding = new Padding(12, 0, 12, 0)
|
||||
};
|
||||
spellHeader.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
spellHeader.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
var spellTitle = CreateCardTitle("技能冷却");
|
||||
spellTitle.AutoSize = true;
|
||||
spellTitle.Dock = DockStyle.None;
|
||||
spellTitle.Anchor = AnchorStyles.Left;
|
||||
spellHeader.Controls.Add(spellTitle, 0, 0);
|
||||
var textureOrderHint = CreateFieldCaption(
|
||||
"纹理按行排列;充能法术连续占 2 格:冷却 → 充能冷却。最大充能、施法次数只生成横向计数条。");
|
||||
textureOrderHint.Padding = new Padding(8, 0, 0, 0);
|
||||
panel.Controls.Add(textureOrderHint, 0, 0);
|
||||
"充能法术连续占 2 格:冷却 → 充能冷却。");
|
||||
textureOrderHint.TextAlign = ContentAlignment.MiddleRight;
|
||||
spellHeader.Controls.Add(textureOrderHint, 1, 0);
|
||||
spellCard.Controls.Add(spellHeader, 0, 0);
|
||||
|
||||
ConfigureGrid(_spellsGrid, "class-config-spells");
|
||||
_spellsGrid.Columns.Add(CreateSpellIconColumn());
|
||||
@@ -908,9 +959,122 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
};
|
||||
_spellsGrid.UserAddedRow += (_, _) => MarkDirty();
|
||||
_spellsGrid.DataError += (_, e) => e.ThrowException = false;
|
||||
panel.Controls.Add(_spellsGrid, 0, 1);
|
||||
panel.Controls.Add(BuildMoveButtons(_spellsGrid), 0, 2);
|
||||
return panel;
|
||||
spellCard.Controls.Add(_spellsGrid, 0, 1);
|
||||
leftColumn.Controls.Add(spellCard, 0, 0);
|
||||
leftColumn.Controls.Add(BuildMoveButtons(_spellsGrid), 0, 1);
|
||||
split.Controls.Add(leftColumn, 0, 0);
|
||||
|
||||
var rightColumn = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 2,
|
||||
BackColor = UiTheme.SurfaceRaised,
|
||||
Margin = new Padding(5, 0, 0, 0),
|
||||
Padding = new Padding(0)
|
||||
};
|
||||
rightColumn.RowStyles.Add(new RowStyle(SizeType.Absolute, 68));
|
||||
rightColumn.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
|
||||
var searchCard = new UiCardPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 1,
|
||||
Margin = new Padding(0, 0, 0, 10),
|
||||
Padding = new Padding(12, 8, 12, 8)
|
||||
};
|
||||
searchCard.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 64));
|
||||
searchCard.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
searchCard.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
searchCard.Controls.Add(new Label
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Text = "搜索",
|
||||
ForeColor = UiTheme.Text,
|
||||
BackColor = Color.Transparent,
|
||||
Font = new Font(Font.FontFamily, 9F, FontStyle.Bold, GraphicsUnit.Point),
|
||||
TextAlign = ContentAlignment.MiddleLeft,
|
||||
Margin = new Padding(0),
|
||||
Padding = new Padding(8, 0, 0, 0)
|
||||
}, 0, 0);
|
||||
|
||||
UiTheme.StyleTextBox(_itemsSearchBox);
|
||||
_itemsSearchBox.Dock = DockStyle.None;
|
||||
_itemsSearchBox.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
_itemsSearchBox.Margin = new Padding(0);
|
||||
_itemsSearchBox.Height = 30;
|
||||
_itemsSearchBox.PlaceholderText = "itemId 或名称";
|
||||
_itemsSearchBox.TextChanged += (_, _) => ApplyItemsFilter();
|
||||
searchCard.Controls.Add(_itemsSearchBox, 1, 0);
|
||||
rightColumn.Controls.Add(searchCard, 0, 0);
|
||||
|
||||
var itemCard = new UiCardPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 1,
|
||||
RowCount = 2,
|
||||
Margin = new Padding(0),
|
||||
Padding = new Padding(0)
|
||||
};
|
||||
itemCard.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
itemCard.RowStyles.Add(new RowStyle(SizeType.Absolute, 50));
|
||||
itemCard.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
||||
|
||||
var itemHeader = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 1,
|
||||
BackColor = Color.Transparent,
|
||||
Margin = new Padding(0),
|
||||
Padding = new Padding(12, 0, 12, 0)
|
||||
};
|
||||
itemHeader.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
itemHeader.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
var itemTitle = CreateCardTitle("物品冷却");
|
||||
itemTitle.AutoSize = true;
|
||||
itemTitle.Dock = DockStyle.None;
|
||||
itemTitle.Anchor = AnchorStyles.Left;
|
||||
itemHeader.Controls.Add(itemTitle, 0, 0);
|
||||
var itemHint = CreateFieldCaption("名称可改为业务别名;图标始终按 itemId 匹配。");
|
||||
itemHint.TextAlign = ContentAlignment.MiddleRight;
|
||||
itemHeader.Controls.Add(itemHint, 1, 0);
|
||||
itemCard.Controls.Add(itemHeader, 0, 0);
|
||||
|
||||
ConfigureGrid(_itemsGrid, "class-config-items");
|
||||
_itemsGrid.CellContentClick += HandleDeleteClick;
|
||||
_itemsGrid.CellValueChanged += (_, e) =>
|
||||
{
|
||||
MarkDirty();
|
||||
if (e.RowIndex >= 0 && e.RowIndex < _itemsGrid.Rows.Count)
|
||||
{
|
||||
UpdateItemGridIcon(_itemsGrid.Rows[e.RowIndex]);
|
||||
}
|
||||
};
|
||||
_itemsGrid.UserAddedRow += (_, _) => MarkDirty();
|
||||
_itemsGrid.DataError += (_, e) => e.ThrowException = false;
|
||||
_itemsGrid.Columns.Add(CreateSpellIconColumn());
|
||||
_itemsGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "ItemId",
|
||||
HeaderText = "itemId",
|
||||
Width = 125,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable
|
||||
});
|
||||
_itemsGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "Name",
|
||||
HeaderText = "名称",
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable
|
||||
});
|
||||
_itemsGrid.Columns.Add(CreateSpellCheckColumn("IsEquipped", "是否装备中", 12, 110));
|
||||
_itemsGrid.Columns.Add(CreateDeleteColumn());
|
||||
itemCard.Controls.Add(_itemsGrid, 0, 1);
|
||||
rightColumn.Controls.Add(itemCard, 0, 1);
|
||||
split.Controls.Add(rightColumn, 1, 0);
|
||||
return split;
|
||||
}
|
||||
|
||||
private Control BuildSpellsListPage()
|
||||
@@ -1647,11 +1811,16 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
// spellsList 是用户实际添加/保存技能时使用的名称,优先级高于专精规则中的同 ID 别名。
|
||||
// spellsList / itemsList 是用户实际添加/保存时使用的名称,优先级高于专精规则中的同 ID 别名。
|
||||
foreach (var spell in document.SpellsList)
|
||||
{
|
||||
SpellIconCatalog.Register(spell.SpellId, spell.Name, overwriteIdName: true);
|
||||
}
|
||||
|
||||
foreach (var item in document.ItemsList)
|
||||
{
|
||||
SpellIconCatalog.RegisterItem(item.ItemId, item.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectClassFromList()
|
||||
@@ -1766,6 +1935,7 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
try
|
||||
{
|
||||
FillSpellsListGrid();
|
||||
FillItemsListGrid();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1924,6 +2094,11 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
|
||||
foreach (DataGridViewRow row in _itemsGrid.Rows)
|
||||
{
|
||||
if (row.IsNewRow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
row.Visible = string.IsNullOrEmpty(query) || ItemsRowMatches(row, query);
|
||||
}
|
||||
}
|
||||
@@ -2235,112 +2410,80 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"当前物品数据库缺少 itemId {suggestion.ItemId} 的名称,请更新技能/物品数据包后再添加。",
|
||||
"物品",
|
||||
"物品列表",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
AddItemFromDatabase(new ItemSuggestion(suggestion.ItemId, name));
|
||||
AddItemsListFromDatabase(new ItemSuggestion(suggestion.ItemId, name));
|
||||
}
|
||||
|
||||
private void AddItemFromDatabase(ItemSuggestion suggestion)
|
||||
private void AddItemsListFromDatabase(ItemSuggestion suggestion)
|
||||
{
|
||||
if (_currentSpec is null)
|
||||
if (_currentDocument is null)
|
||||
{
|
||||
MessageBox.Show("请先选择一个专精。", "物品", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show("请先选择一个职业文件。", "物品列表", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
_itemsGrid.EndEdit();
|
||||
WriteBackItems();
|
||||
if (_currentSpec.Items.Any(item => item.ItemId == suggestion.ItemId))
|
||||
if (!_currentDocument.IsModernFormat)
|
||||
{
|
||||
MessageBox.Show("旧版稀疏索引格式暂不支持添加物品。", "物品列表", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_itemsListGrid.EndEdit();
|
||||
if (!TryValidateItemsList(out var validationError))
|
||||
{
|
||||
MessageBox.Show(validationError, "物品列表", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
WriteBackItemsList();
|
||||
if (_currentDocument.ItemsList.Any(item => item.ItemId == suggestion.ItemId))
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"已有此物品:{suggestion.Name}({suggestion.ItemId})",
|
||||
"物品",
|
||||
"物品列表",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentSpec.Items.Any(item =>
|
||||
if (_currentDocument.ItemsList.Any(item =>
|
||||
string.Equals(item.Name, suggestion.Name, StringComparison.Ordinal)))
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"已有同名物品“{suggestion.Name}”。",
|
||||
"物品",
|
||||
"物品列表",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentSpecReservedItemNames().Contains(suggestion.Name))
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"物品名称“{suggestion.Name}”与状态、能量或配置开关字段重名。",
|
||||
"物品",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = new ClassBlocksStore.ItemEntry
|
||||
var entry = new ClassBlocksStore.ItemsListEntry
|
||||
{
|
||||
ItemId = suggestion.ItemId,
|
||||
Name = suggestion.Name,
|
||||
IsEquipped = false
|
||||
Name = suggestion.Name
|
||||
};
|
||||
_currentSpec.Items.Add(entry);
|
||||
_currentDocument.ItemsList.Add(entry);
|
||||
SpellIconCatalog.RegisterItem(suggestion.ItemId, suggestion.Name);
|
||||
|
||||
var rowIndex = _itemsGrid.Rows.Add(
|
||||
SpellIconCatalog.GetItem(suggestion.ItemId)!,
|
||||
var rowIndex = _itemsListGrid.Rows.Add(
|
||||
suggestion.ItemId.ToString(CultureInfo.InvariantCulture),
|
||||
suggestion.Name,
|
||||
false,
|
||||
"×");
|
||||
var row = _itemsGrid.Rows[rowIndex];
|
||||
SpellIconCatalog.GetItem(suggestion.ItemId)!,
|
||||
suggestion.Name);
|
||||
var row = _itemsListGrid.Rows[rowIndex];
|
||||
row.Tag = entry;
|
||||
_itemsSearchBox.Clear();
|
||||
ApplyItemsFilter();
|
||||
_itemsListSearchBox.Clear();
|
||||
ApplyItemsListFilter();
|
||||
row.Selected = true;
|
||||
_itemsGrid.CurrentCell = row.Cells["ItemId"];
|
||||
_itemsGrid.FirstDisplayedScrollingRowIndex = rowIndex;
|
||||
_itemsListGrid.CurrentCell = row.Cells["ItemId"];
|
||||
_itemsListGrid.FirstDisplayedScrollingRowIndex = rowIndex;
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
private HashSet<string> CurrentSpecReservedItemNames()
|
||||
{
|
||||
var bareNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
if (_currentSpec is null)
|
||||
{
|
||||
return bareNames;
|
||||
}
|
||||
|
||||
if (_currentSpec.NestedStates)
|
||||
{
|
||||
foreach (var category in new[]
|
||||
{
|
||||
ClassStateCatalog.CategoryState,
|
||||
ClassStateCatalog.CategoryResource,
|
||||
ClassStateCatalog.CategoryConfig
|
||||
})
|
||||
{
|
||||
foreach (var name in _currentSpec.CategorizedStates.GetValueOrDefault(category) ?? [])
|
||||
{
|
||||
bareNames.Add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bareNames.UnionWith(_currentSpec.FlatStates);
|
||||
}
|
||||
|
||||
return bareNames;
|
||||
}
|
||||
|
||||
private void ReloadStatesGrid()
|
||||
{
|
||||
CloseStateComboDropDown();
|
||||
@@ -2677,6 +2820,43 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
private void FillItemsListGrid()
|
||||
{
|
||||
_itemsListGrid.Rows.Clear();
|
||||
_itemsListSearchBox.Clear();
|
||||
if (_currentDocument is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in _currentDocument.ItemsList.OrderBy(entry => entry.ItemId))
|
||||
{
|
||||
SpellIconCatalog.RegisterItem(item.ItemId, item.Name);
|
||||
var rowIndex = _itemsListGrid.Rows.Add(
|
||||
item.ItemId.ToString(CultureInfo.InvariantCulture),
|
||||
SpellIconCatalog.GetItem(item.ItemId)!,
|
||||
item.Name);
|
||||
_itemsListGrid.Rows[rowIndex].Tag = item;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyItemsListFilter()
|
||||
{
|
||||
var query = _itemsListSearchBox.Text.Trim();
|
||||
_itemsListGrid.ClearSelection();
|
||||
_itemsListGrid.CurrentCell = null;
|
||||
|
||||
foreach (DataGridViewRow row in _itemsListGrid.Rows)
|
||||
{
|
||||
if (row.IsNewRow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
row.Visible = string.IsNullOrEmpty(query) || ItemsRowMatches(row, query);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySpellsListFilter()
|
||||
{
|
||||
var query = _spellsListSearchBox.Text.Trim();
|
||||
@@ -3087,6 +3267,7 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
}
|
||||
|
||||
_itemsGrid.EndEdit();
|
||||
_spellsGrid.EndEdit();
|
||||
NormalizeFixedStateNames(_currentSpec);
|
||||
WriteBackStatesCategory(_lastStateCategory);
|
||||
|
||||
@@ -3122,6 +3303,34 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteBackItemsList()
|
||||
{
|
||||
if (_currentDocument is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DataGridViewRow row in _itemsListGrid.Rows)
|
||||
{
|
||||
if (row.IsNewRow || row.Tag is not ClassBlocksStore.ItemsListEntry entry)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!long.TryParse(
|
||||
row.Cells["ItemId"].Value?.ToString()?.Trim(),
|
||||
NumberStyles.None,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var itemId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.ItemId = itemId;
|
||||
entry.Name = row.Cells["Name"].Value?.ToString()?.Trim() ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSpellFromDatabase(SpellSuggestion suggestion)
|
||||
{
|
||||
if (_currentDocument is null)
|
||||
@@ -3218,15 +3427,19 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
grid.Invalidate();
|
||||
}
|
||||
|
||||
foreach (DataGridViewRow row in _itemsGrid.Rows)
|
||||
foreach (var grid in new[] { _itemsGrid, _itemsListGrid })
|
||||
{
|
||||
if (!row.IsNewRow)
|
||||
foreach (DataGridViewRow row in grid.Rows)
|
||||
{
|
||||
UpdateItemGridIcon(row);
|
||||
if (!row.IsNewRow)
|
||||
{
|
||||
UpdateItemGridIcon(row);
|
||||
}
|
||||
}
|
||||
|
||||
grid.Invalidate();
|
||||
}
|
||||
|
||||
_itemsGrid.Invalidate();
|
||||
_itemDatabaseGrid.Invalidate();
|
||||
|
||||
foreach (var grid in new[] { _aurasGrid, _groupAurasGrid })
|
||||
@@ -3295,6 +3508,55 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryValidateItemsList(out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
if (_currentDocument is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var itemIds = new HashSet<long>();
|
||||
var itemNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (DataGridViewRow row in _itemsListGrid.Rows)
|
||||
{
|
||||
if (row.IsNewRow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rowNumber = row.Index + 1;
|
||||
if (!long.TryParse(row.Cells["ItemId"].Value?.ToString()?.Trim(), NumberStyles.None,
|
||||
CultureInfo.InvariantCulture, out var itemId)
|
||||
|| itemId <= 0)
|
||||
{
|
||||
error = $"物品列表第 {rowNumber} 行 itemId 必须是正整数。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var name = row.Cells["Name"].Value?.ToString()?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
error = $"物品列表第 {rowNumber} 行名称不能为空。";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!itemIds.Add(itemId))
|
||||
{
|
||||
error = $"物品列表 itemId {itemId} 重复。";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!itemNames.Add(name))
|
||||
{
|
||||
error = $"物品列表名称“{name}”重复。";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryValidateItems(out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
@@ -3619,7 +3881,9 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
try
|
||||
{
|
||||
_spellsListGrid.EndEdit();
|
||||
_itemsListGrid.EndEdit();
|
||||
_statesGrid.EndEdit();
|
||||
_spellsGrid.EndEdit();
|
||||
_itemsGrid.EndEdit();
|
||||
if (!TryValidateSpellsList(out var validationError))
|
||||
{
|
||||
@@ -3628,16 +3892,24 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryValidateItemsList(out validationError))
|
||||
{
|
||||
MessageBox.Show(validationError, "物品列表", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
_statusLabel.Text = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换分类前把当前状态表写回。
|
||||
CommitCurrentSpecFromUi();
|
||||
if (!TryValidateItems(out validationError))
|
||||
{
|
||||
MessageBox.Show(validationError, "物品", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
MessageBox.Show(validationError, "物品冷却", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
_statusLabel.Text = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
WriteBackSpellsList();
|
||||
WriteBackItemsList();
|
||||
ClassBlocksStore.Save(_currentDocument);
|
||||
localSaved = true;
|
||||
SetDirty(false);
|
||||
@@ -3736,6 +4008,8 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
_spellsGrid.Rows.Clear();
|
||||
_itemsGrid.Rows.Clear();
|
||||
_itemsSearchBox.Clear();
|
||||
_itemsListGrid.Rows.Clear();
|
||||
_itemsListSearchBox.Clear();
|
||||
_spellsListGrid.Rows.Clear();
|
||||
_spellsListSearchBox.Clear();
|
||||
_groupAurasGrid.Rows.Clear();
|
||||
@@ -3792,6 +4066,36 @@ public sealed class ClassConfigEditorControl : UserControl
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
private void HandleItemsListDeleteClick(object? sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
if (sender is not DataGridView grid
|
||||
|| e.RowIndex < 0
|
||||
|| e.ColumnIndex < 0
|
||||
|| grid.Columns[e.ColumnIndex].Name != "Delete")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var row = grid.Rows[e.RowIndex];
|
||||
if (row.IsNewRow || row.Tag is not ClassBlocksStore.ItemsListEntry entry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentDocument is not null)
|
||||
{
|
||||
if (entry.OriginalItemId > 0)
|
||||
{
|
||||
_currentDocument.DeletedItemsListOriginalIds.Add(entry.OriginalItemId);
|
||||
}
|
||||
|
||||
_currentDocument.ItemsList.Remove(entry);
|
||||
}
|
||||
|
||||
grid.Rows.RemoveAt(e.RowIndex);
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
private void MoveSelectedRow(DataGridView grid, int delta)
|
||||
{
|
||||
if (grid.CurrentRow is null || grid.CurrentRow.IsNewRow)
|
||||
|
||||
Reference in New Issue
Block a user