diff --git a/src/Persistence/Initialization/Updates/AddCrestOfMonarchDropGroupUpdateSeason6.cs b/src/Persistence/Initialization/Updates/AddCrestOfMonarchDropGroupUpdateSeason6.cs
new file mode 100644
index 000000000..7021f2420
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/AddCrestOfMonarchDropGroupUpdateSeason6.cs
@@ -0,0 +1,81 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.Updates;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.DataModel.Configuration;
+using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// This update adds the Crest of Monarch drop item group for the Icarus map.
+///
+[PlugIn]
+[Display(Name = PlugInName, Description = PlugInDescription)]
+[Guid("FF14A478-3EA8-4C41-A298-8E6698D5973D")]
+public class AddCrestOfMonarchDropGroupUpdateSeason6 : UpdatePlugInBase
+{
+ ///
+ /// The plug in name.
+ ///
+ internal const string PlugInName = "Add Crest of Monarch Drop Group";
+
+ ///
+ /// The plug in description.
+ ///
+ internal const string PlugInDescription = "Adds the Crest of Monarch (Loch's Feather +1) drop item group to Icarus.";
+
+ ///
+ public override UpdateVersion Version => UpdateVersion.AddCrestOfMonarchDropGroupSeason6;
+
+ ///
+ public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
+
+ ///
+ public override string Name => PlugInName;
+
+ ///
+ public override string Description => PlugInDescription;
+
+ ///
+ public override bool IsMandatory => true;
+
+ ///
+ public override DateTime CreatedAt => new(2026, 03, 13, 12, 0, 0, DateTimeKind.Utc);
+
+ ///
+#pragma warning disable CS1998
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+#pragma warning restore CS1998
+ {
+ var map = gameConfiguration.Maps.First(m => m.Number == Icarus.Number && m.Discriminator == 0);
+ var lochsFeather = gameConfiguration.Items.First(item => item.Group == 13 && item.Number == 14);
+ var crestId = GuidHelper.CreateGuid(Icarus.Number, 2);
+
+ var crestGroup = gameConfiguration.DropItemGroups.FirstOrDefault(group => group.GetId() == crestId);
+ if (crestGroup is null)
+ {
+ crestGroup = context.CreateNew();
+ crestGroup.SetGuid(Icarus.Number, 2);
+ gameConfiguration.DropItemGroups.Add(crestGroup);
+ }
+
+ crestGroup.Description = "Crest of Monarch";
+ crestGroup.Chance = 0.001;
+ crestGroup.MinimumMonsterLevel = 82;
+ crestGroup.MaximumMonsterLevel = null;
+ crestGroup.ItemLevel = 1;
+ if (crestGroup.PossibleItems.Count != 1 || crestGroup.PossibleItems.First().GetItemId() != lochsFeather.GetItemId())
+ {
+ crestGroup.PossibleItems.Clear();
+ crestGroup.PossibleItems.Add(lochsFeather);
+ }
+
+ if (!map.DropItemGroups.Any(group => group.GetId() == crestGroup.GetId()))
+ {
+ map.DropItemGroups.Add(crestGroup);
+ }
+ }
+}
diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs
index 77180a772..3af0fbb00 100644
--- a/src/Persistence/Initialization/Updates/UpdateVersion.cs
+++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs
@@ -359,8 +359,12 @@ public enum UpdateVersion
///
RemoveJewelDropLevelGapSeason6 = 70,
- ///
/// The version of the .
///
FixRageFighterMultipleHitSkills = 71,
-}
\ No newline at end of file
+
+ ///
+ /// The version of the .
+ ///
+ AddCrestOfMonarchDropGroupSeason6 = 72,
+}
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/Icarus.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/Icarus.cs
index 607b1e842..9995c8f7b 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/Maps/Icarus.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/Icarus.cs
@@ -25,13 +25,25 @@ public Icarus(IContext context, GameConfiguration gameConfiguration)
protected override void InitializeDropItemGroups()
{
base.InitializeDropItemGroups();
+ var lochsFeather = this.GameConfiguration.Items.First(item => item.Group == 13 && item.Number == 14);
+
var feather = this.Context.CreateNew();
feather.SetGuid(this.MapNumber, 1);
feather.Chance = 0.001;
feather.Description = "Loch's Feather";
feather.MinimumMonsterLevel = 82;
- feather.PossibleItems.Add(this.GameConfiguration.Items.First(item => item.Group == 13 && item.Number == 14));
+ feather.PossibleItems.Add(lochsFeather);
this.MapDefinition!.DropItemGroups.Add(feather);
this.GameConfiguration.DropItemGroups.Add(feather);
+
+ var crest = this.Context.CreateNew();
+ crest.SetGuid(this.MapNumber, 2);
+ crest.Chance = 0.001;
+ crest.Description = "Crest of Monarch";
+ crest.MinimumMonsterLevel = 82;
+ crest.ItemLevel = 1;
+ crest.PossibleItems.Add(lochsFeather);
+ this.MapDefinition.DropItemGroups.Add(crest);
+ this.GameConfiguration.DropItemGroups.Add(crest);
}
-}
\ No newline at end of file
+}
diff --git a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs
index 383982414..b053d2a1d 100644
--- a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs
+++ b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TestInitializationWithEfCore.cs
@@ -5,10 +5,13 @@
namespace MUnique.OpenMU.Persistence.Initialization.Tests;
using Microsoft.Extensions.Logging.Abstractions;
+using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Persistence.EntityFramework;
+using MUnique.OpenMU.Persistence.Initialization.Updates;
+using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
using MUnique.OpenMU.Persistence.InMemory;
///
@@ -47,9 +50,47 @@ public async Task TestSeason6DataAsync()
var contextProvider = new InMemoryPersistenceContextProvider();
var dataInitialization = new VersionSeasonSix.DataInitialization(contextProvider, new NullLoggerFactory());
await dataInitialization.CreateInitialDataAsync(1, true).ConfigureAwait(false);
+ await this.AssertIcarusFeatherAndCrestDropGroupsAsync(contextProvider).ConfigureAwait(false);
await this.TestIfItemsFitIntoInventoriesAsync(contextProvider).ConfigureAwait(false);
}
+ ///
+ /// Tests that applying the update for Crest of Monarch in Season 6 is idempotent.
+ ///
+ [Test]
+ public async Task TestSeason6CrestOfMonarchUpdatePlugInAsync()
+ {
+ var contextProvider = new InMemoryPersistenceContextProvider();
+ var dataInitialization = new VersionSeasonSix.DataInitialization(contextProvider, new NullLoggerFactory());
+ await dataInitialization.CreateInitialDataAsync(1, true).ConfigureAwait(false);
+
+ using var context = contextProvider.CreateNewContext();
+ var gameConfiguration = (await context.GetAsync().ConfigureAwait(false)).First();
+ var map = gameConfiguration.Maps.First(m => m.Number == Icarus.Number && m.Discriminator == 0);
+
+ var crestGroupId = GuidHelper.CreateGuid(Icarus.Number, 2);
+ if (gameConfiguration.DropItemGroups.FirstOrDefault(group => group.GetId() == crestGroupId) is { } existingCrestGroup)
+ {
+ map.DropItemGroups.Remove(existingCrestGroup);
+ gameConfiguration.DropItemGroups.Remove(existingCrestGroup);
+ }
+
+ var update = new AddCrestOfMonarchDropGroupUpdateSeason6();
+ await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false);
+ await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false);
+
+ var groups = gameConfiguration.DropItemGroups.Where(group => group.GetId() == crestGroupId).ToList();
+ Assert.That(groups, Has.Count.EqualTo(1));
+ Assert.That(map.DropItemGroups.Count(group => group.GetId() == crestGroupId), Is.EqualTo(1));
+ Assert.That(groups[0].Description.ToString(), Is.EqualTo("Crest of Monarch"));
+ Assert.That(groups[0].Chance, Is.EqualTo(0.001));
+ Assert.That(groups[0].MinimumMonsterLevel, Is.EqualTo((byte)82));
+ Assert.That(groups[0].ItemLevel, Is.EqualTo((byte)1));
+ Assert.That(groups[0].PossibleItems, Has.Count.EqualTo(1));
+ Assert.That(groups[0].PossibleItems.Single().Group, Is.EqualTo((byte)13));
+ Assert.That(groups[0].PossibleItems.Single().Number, Is.EqualTo((short)14));
+ }
+
///
/// Tests the data initialization using the in-memory persistence.
///
@@ -114,4 +155,35 @@ private async Task TestIfItemsFitIntoInventoriesAsync(IPersistenceContextProvide
}
}
}
-}
\ No newline at end of file
+
+ private async Task AssertIcarusFeatherAndCrestDropGroupsAsync(IPersistenceContextProvider contextProvider)
+ {
+ using var context = contextProvider.CreateNewConfigurationContext();
+ var gameConfiguration = (await context.GetAsync().ConfigureAwait(false)).First();
+ var map = gameConfiguration.Maps.First(m => m.Number == Icarus.Number && m.Discriminator == 0);
+
+ var lochsFeatherGroupId = GuidHelper.CreateGuid(Icarus.Number, 1);
+ var crestGroupId = GuidHelper.CreateGuid(Icarus.Number, 2);
+ var featherGroup = gameConfiguration.DropItemGroups.Single(group => group.GetId() == lochsFeatherGroupId);
+ var crestGroup = gameConfiguration.DropItemGroups.Single(group => group.GetId() == crestGroupId);
+
+ Assert.That(map.DropItemGroups.Any(group => group.GetId() == lochsFeatherGroupId), Is.True);
+ Assert.That(map.DropItemGroups.Any(group => group.GetId() == crestGroupId), Is.True);
+
+ Assert.That(featherGroup.Description.ToString(), Is.EqualTo("Loch's Feather"));
+ Assert.That(featherGroup.Chance, Is.EqualTo(0.001));
+ Assert.That(featherGroup.MinimumMonsterLevel, Is.EqualTo((byte)82));
+ Assert.That(featherGroup.ItemLevel, Is.Null);
+ Assert.That(featherGroup.PossibleItems, Has.Count.EqualTo(1));
+ Assert.That(featherGroup.PossibleItems.Single().Group, Is.EqualTo((byte)13));
+ Assert.That(featherGroup.PossibleItems.Single().Number, Is.EqualTo((short)14));
+
+ Assert.That(crestGroup.Description.ToString(), Is.EqualTo("Crest of Monarch"));
+ Assert.That(crestGroup.Chance, Is.EqualTo(0.001));
+ Assert.That(crestGroup.MinimumMonsterLevel, Is.EqualTo((byte)82));
+ Assert.That(crestGroup.ItemLevel, Is.EqualTo((byte)1));
+ Assert.That(crestGroup.PossibleItems, Has.Count.EqualTo(1));
+ Assert.That(crestGroup.PossibleItems.Single().Group, Is.EqualTo((byte)13));
+ Assert.That(crestGroup.PossibleItems.Single().Number, Is.EqualTo((short)14));
+ }
+}