diff --git a/src/AcDream.Plugins.MossTank/MossTankLootProfileStore.cs b/src/AcDream.Plugins.MossTank/MossTankLootProfileStore.cs
index 4c675120..2444c9cf 100644
--- a/src/AcDream.Plugins.MossTank/MossTankLootProfileStore.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankLootProfileStore.cs
@@ -238,6 +238,29 @@ internal sealed class MossTankLootProfileStore
WriteUtl(fileName, profile);
}
+ ///
+ /// Round 3 item 10: deletes the currently selected named loot profile's
+ /// real .utl file, then falls back to .
+ /// Refuses for itself — there is no file to
+ /// delete, only a reset (see ), matching
+ /// 's own contract.
+ ///
+ public bool Delete(out string notice)
+ {
+ if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
+ {
+ notice = "'By char' is the built-in loot profile and cannot be deleted.";
+ return false;
+ }
+ string fileName = _selected;
+ if (VtankStorage.IsAvailable)
+ VtankStorage.Delete(fileName);
+ _selected = ByCharacter;
+ WriteBinding();
+ notice = $"Deleted loot profile {StripUtl(fileName)}.";
+ return true;
+ }
+
public void ClearCurrent(List target, LootSettings? settings = null)
{
target.Clear();
diff --git a/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs b/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs
index 97511797..719e6c0f 100644
--- a/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs
@@ -261,6 +261,30 @@ internal sealed class MossTankMetaProfileStore
return true;
}
+ ///
+ /// Round 3 item 10: deletes the currently selected named Meta profile's
+ /// real .af file, then falls back to .
+ /// Refuses for itself — there is no file to
+ /// delete, only a reset (see ), matching
+ /// 's own contract.
+ ///
+ public bool Delete(out string notice)
+ {
+ if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
+ {
+ notice = "'By char' is the built-in Meta profile and cannot be deleted.";
+ return false;
+ }
+ string fileName = _selected;
+ if (VtankStorage.IsAvailable)
+ VtankStorage.Delete(fileName);
+ _selected = ByCharacter;
+ _pendingLegacyBareName = null;
+ WriteBinding();
+ notice = $"Deleted Meta profile {fileName}.";
+ return true;
+ }
+
public MetaProfile ClearCurrent()
{
var empty = new MetaProfile();
diff --git a/src/AcDream.Plugins.MossTank/MossTankPanel.cs b/src/AcDream.Plugins.MossTank/MossTankPanel.cs
index cb6983af..a487505b 100644
--- a/src/AcDream.Plugins.MossTank/MossTankPanel.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankPanel.cs
@@ -594,6 +594,7 @@ internal sealed partial class MossTankPanel
public Action CopyLootProfile => () =>
CreateLootProfileCore(copyCurrent: true);
public Action ClearLootProfile => ClearLootProfileCore;
+ public Action DeleteLootProfile => DeleteLootProfileCore;
public Action CloseLootEditor => () => _lootEditorVisible = false;
public Action SelectLootRule => SelectLootRuleCore;
public Action SetLootExpressionDraft => value =>
@@ -756,6 +757,7 @@ internal sealed partial class MossTankPanel
public Action CopyRouteProfile => () =>
CreateRouteProfileCore(copyCurrent: true);
public Action ClearRouteProfile => ClearRouteProfileCore;
+ public Action DeleteRouteProfile => DeleteRouteProfileCore;
public Action SetFollowTarget => CaptureFollowTarget;
// ── Meta profile / editor ────────────────────────────────────────────
@@ -826,6 +828,7 @@ internal sealed partial class MossTankPanel
public Action CreateMetaProfile => () => CreateMetaProfileCore(copyCurrent: false);
public Action CopyMetaProfile => () => CreateMetaProfileCore(copyCurrent: true);
public Action ClearMetaProfile => ClearMetaProfileCore;
+ public Action DeleteMetaProfile => DeleteMetaProfileCore;
// ── Profiles tab ─────────────────────────────────────────────────────
public IReadOnlyList MacroProfileNames => _profiles.AvailableNames;
@@ -1425,6 +1428,17 @@ internal sealed partial class MossTankPanel
_lootEditorNotice = $"Cleared {_lootProfiles.Selected}.";
}
+ private void DeleteLootProfileCore()
+ {
+ if (!_lootProfiles.Delete(out string notice))
+ {
+ _lootEditorNotice = notice;
+ return;
+ }
+ LoadLootProfile();
+ _lootEditorNotice = notice;
+ }
+
private void LoadLootProfile()
{
if (!_lootProfiles.LoadCurrent(
@@ -1784,6 +1798,17 @@ internal sealed partial class MossTankPanel
_routeNotice = $"Cleared {_routeProfiles.Selected}.";
}
+ private void DeleteRouteProfileCore()
+ {
+ if (!_routeProfiles.Delete(out string notice))
+ {
+ _routeNotice = notice;
+ return;
+ }
+ LoadRouteProfile();
+ _routeNotice = notice;
+ }
+
private void LoadRouteProfile()
{
if (!_routeProfiles.LoadCurrent(_navigationSettings, _host.Automation.Spells))
@@ -2380,6 +2405,17 @@ internal sealed partial class MossTankPanel
_metaNotice = $"Cleared Meta profile {_metaProfiles.Selected}.";
}
+ private void DeleteMetaProfileCore()
+ {
+ if (!_metaProfiles.Delete(out string notice))
+ {
+ _metaNotice = notice;
+ return;
+ }
+ LoadMetaProfile();
+ _metaNotice = notice;
+ }
+
private void LoadMetaProfile()
{
_metaProfile = _metaProfiles.LoadCurrent();
diff --git a/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs b/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs
index 13121ceb..5035eb04 100644
--- a/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs
@@ -228,6 +228,30 @@ internal sealed class MossTankRouteProfileStore
return true;
}
+ ///
+ /// Round 3 item 10: deletes the currently selected named route's real
+ /// .af file, then falls back to .
+ /// Refuses for itself — there is no file to
+ /// delete, only a reset (see ), matching
+ /// 's own contract.
+ ///
+ public bool Delete(out string notice)
+ {
+ if (_selected.Equals(ByCharacter, StringComparison.OrdinalIgnoreCase))
+ {
+ notice = "'By char' is the built-in route profile and cannot be deleted.";
+ return false;
+ }
+ string fileName = _selected;
+ if (VtankStorage.IsAvailable)
+ VtankStorage.Delete(fileName);
+ _selected = ByCharacter;
+ _pendingLegacyBareName = null;
+ WriteBinding();
+ notice = $"Deleted route profile {Strip(fileName)}.";
+ return true;
+ }
+
///
/// Resets only the fields this store owns (Mode/Waypoints/FollowTarget)
/// — Enabled/Priority/MinimumDistanceMeters/FollowAroundCorners/
diff --git a/src/AcDream.Plugins.MossTank/mosstank.xml b/src/AcDream.Plugins.MossTank/mosstank.xml
index f89fcff1..6c697b2d 100644
--- a/src/AcDream.Plugins.MossTank/mosstank.xml
+++ b/src/AcDream.Plugins.MossTank/mosstank.xml
@@ -158,6 +158,9 @@
onclick="{CreateRouteProfile}" />
+
-
+
-
+
+
interactive.Contains(element.Name.LocalName))
.ToArray();
- Assert.Equal(191, controls.Length);
+ // Round 3 item 10: +3 for the Route/Loot/Meta Delete buttons
+ // (Settings already had one from round 2 step 5).
+ Assert.Equal(194, controls.Length);
foreach (XElement control in controls)
{
diff --git a/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs b/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs
index 857723fd..11cb6a3f 100644
--- a/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs
+++ b/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs
@@ -1142,6 +1142,98 @@ public sealed class MossTankPanelTests
Assert.Contains("cannot be deleted", panel.ProfileLifecycleNotice, StringComparison.Ordinal);
}
+ // Round 3 item 10: Meta/Route/Loot get the same Delete action Settings
+ // already had from round 2 step 5.
+ [Fact]
+ public void DeleteRouteProfileRemovesTheRealFileAndFallsBackToByCharacter()
+ {
+ var storage = new MemoryStorage();
+ var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
+ panel.SetRouteProfileNameDraft("Fellowship");
+ panel.CopyRouteProfile();
+ Assert.Equal("Fellowship", panel.SelectedRouteProfile);
+ Assert.Contains("Fellowship", panel.RouteProfileNames);
+
+ panel.DeleteRouteProfile();
+
+ Assert.Equal(MossTankRouteProfileStore.ByCharacter, panel.SelectedRouteProfile);
+ Assert.DoesNotContain("Fellowship", panel.RouteProfileNames);
+ Assert.False(storage.Text.ContainsKey("nav_Fellowship.af"));
+ Assert.Contains("Deleted", panel.RouteNotice, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void DeleteRouteProfileRefusesToRemoveByCharacter()
+ {
+ var storage = new MemoryStorage();
+ var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
+
+ panel.DeleteRouteProfile();
+
+ Assert.Equal(MossTankRouteProfileStore.ByCharacter, panel.SelectedRouteProfile);
+ Assert.Contains("cannot be deleted", panel.RouteNotice, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void DeleteMetaProfileRemovesTheRealFileAndFallsBackToByCharacter()
+ {
+ var storage = new MemoryStorage();
+ var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
+ panel.SetMetaProfileNameDraft("Fellowship");
+ panel.CopyMetaProfile();
+ Assert.Equal("Fellowship", panel.SelectedMetaProfile);
+ Assert.Contains("Fellowship", panel.MetaProfileNames);
+
+ panel.DeleteMetaProfile();
+
+ Assert.Equal(MossTankMetaProfileStore.ByCharacter, panel.SelectedMetaProfile);
+ Assert.DoesNotContain("Fellowship", panel.MetaProfileNames);
+ Assert.False(storage.Text.ContainsKey("Fellowship.af"));
+ Assert.Contains("Deleted", panel.MetaNotice, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void DeleteMetaProfileRefusesToRemoveByCharacter()
+ {
+ var storage = new MemoryStorage();
+ var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
+
+ panel.DeleteMetaProfile();
+
+ Assert.Equal(MossTankMetaProfileStore.ByCharacter, panel.SelectedMetaProfile);
+ Assert.Contains("cannot be deleted", panel.MetaNotice, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void DeleteLootProfileRemovesTheRealFileAndFallsBackToByCharacter()
+ {
+ var storage = new MemoryStorage();
+ var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
+ panel.SetLootProfileNameDraft("Fellowship");
+ panel.CopyLootProfile();
+ Assert.Equal("Fellowship", panel.LootProfileName);
+ Assert.Contains("Fellowship", panel.LootProfileNames);
+
+ panel.DeleteLootProfile();
+
+ Assert.Equal(MossTankLootProfileStore.ByCharacter, panel.LootProfileName);
+ Assert.DoesNotContain("Fellowship", panel.LootProfileNames);
+ Assert.False(storage.Text.ContainsKey("Fellowship.utl"));
+ Assert.Contains("Deleted", panel.LootEditorNotice, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void DeleteLootProfileRefusesToRemoveByCharacter()
+ {
+ var storage = new MemoryStorage();
+ var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage));
+
+ panel.DeleteLootProfile();
+
+ Assert.Equal(MossTankLootProfileStore.ByCharacter, panel.LootProfileName);
+ Assert.Contains("cannot be deleted", panel.LootEditorNotice, StringComparison.Ordinal);
+ }
+
// Item J (slice-1 fix round): renamed from
// NavCommandsImportAndExportExactVtankNavFiles — .af is a real writer
// output now (item E's header/fold-marker emission), not a byte-exact