Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// <copyright file="AddCrestOfMonarchDropGroupUpdateSeason6.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

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;

/// <summary>
/// This update adds the Crest of Monarch drop item group for the Icarus map.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("FF14A478-3EA8-4C41-A298-8E6698D5973D")]
public class AddCrestOfMonarchDropGroupUpdateSeason6 : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Crest of Monarch Drop Group";

/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Adds the Crest of Monarch (Loch's Feather +1) drop item group to Icarus.";

/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddCrestOfMonarchDropGroupSeason6;

/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;

/// <inheritdoc />
public override string Name => PlugInName;

/// <inheritdoc />
public override string Description => PlugInDescription;

/// <inheritdoc />
public override bool IsMandatory => true;

/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 03, 13, 12, 0, 0, DateTimeKind.Utc);

/// <inheritdoc />
#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<DropItemGroup>(Icarus.Number, 2);

var crestGroup = gameConfiguration.DropItemGroups.FirstOrDefault(group => group.GetId() == crestId);
if (crestGroup is null)
{
crestGroup = context.CreateNew<DropItemGroup>();
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);
}
}
Comment on lines +52 to +80

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This method contains several magic numbers (e.g., 13, 14, 2, 0.001, 82, 1). To improve readability and maintainability, consider defining these as named constants. This would also allow them to be shared with Icarus.cs and the test files, ensuring consistency.

For example, you could define constants at the class level:

private const byte LochsFeatherItemGroup = 13;
private const short LochsFeatherItemNumber = 14;
private const short CrestOfMonarchDropItemGroupId = 2;
private const double ItemDropChance = 0.001;
private const byte MinimumMonsterLevel = 82;
private const byte CrestOfMonarchItemLevel = 1;

}
8 changes: 6 additions & 2 deletions src/Persistence/Initialization/Updates/UpdateVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,12 @@ public enum UpdateVersion
/// </summary>
RemoveJewelDropLevelGapSeason6 = 70,

/// <summary>
/// The version of the <see cref="FixRageFighterMultipleHitSkillsPlugIn"/>.
/// </summary>
FixRageFighterMultipleHitSkills = 71,
}

/// <summary>
/// The version of the <see cref="AddCrestOfMonarchDropGroupUpdateSeason6"/>.
/// </summary>
AddCrestOfMonarchDropGroupSeason6 = 72,
}
16 changes: 14 additions & 2 deletions src/Persistence/Initialization/VersionSeasonSix/Maps/Icarus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DropItemGroup>();
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<DropItemGroup>();
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
Expand Down Expand Up @@ -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);
}

/// <summary>
/// Tests that applying the update for Crest of Monarch in Season 6 is idempotent.
/// </summary>
[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<GameConfiguration>().ConfigureAwait(false)).First();
var map = gameConfiguration.Maps.First(m => m.Number == Icarus.Number && m.Discriminator == 0);

var crestGroupId = GuidHelper.CreateGuid<DropItemGroup>(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));
Comment on lines +85 to +91

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These assertions use hardcoded values (e.g., 82, 1, 13, 14). If you introduce constants for these magic numbers in the production code as suggested in other comments, please consider using those constants here as well. This makes the tests more robust to changes in these values and ensures consistency.

}

/// <summary>
/// Tests the data initialization using the in-memory persistence.
/// </summary>
Expand Down Expand Up @@ -114,4 +155,35 @@ private async Task TestIfItemsFitIntoInventoriesAsync(IPersistenceContextProvide
}
}
}
}

private async Task AssertIcarusFeatherAndCrestDropGroupsAsync(IPersistenceContextProvider contextProvider)
{
using var context = contextProvider.CreateNewConfigurationContext();
var gameConfiguration = (await context.GetAsync<GameConfiguration>().ConfigureAwait(false)).First();
var map = gameConfiguration.Maps.First(m => m.Number == Icarus.Number && m.Discriminator == 0);

var lochsFeatherGroupId = GuidHelper.CreateGuid<DropItemGroup>(Icarus.Number, 1);
var crestGroupId = GuidHelper.CreateGuid<DropItemGroup>(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));
}
}
Loading