diff --git a/src/Persistence/Initialization/Updates/FixBloodCastleMonsterAttributesUpdatePlugIn.cs b/src/Persistence/Initialization/Updates/FixBloodCastleMonsterAttributesUpdatePlugIn.cs
new file mode 100644
index 000000000..3b7934710
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/FixBloodCastleMonsterAttributesUpdatePlugIn.cs
@@ -0,0 +1,105 @@
+//
+// 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.PlugIns;
+
+///
+/// Swaps attribute values between BC7 (monsters 138-143) and BC8 (monsters 428-433)
+/// so the difficulty progression is correct: BC6 → BC7 → BC8.
+///
+[PlugIn]
+[Display(Name = PlugInName, Description = PlugInDescription)]
+[Guid("D4E5F6A0-1B2C-3D4E-5F6A-7B8C9D0E1F2A")]
+public class FixBloodCastleMonsterAttributesUpdatePlugIn : UpdatePlugInBase
+{
+ internal const string PlugInName = "Fix Blood Castle 7/8 Monster Attributes";
+
+ internal const string PlugInDescription = "Swaps attribute values between BC7 (monsters 138-143) and BC8 (monsters 428-433) so progression is correct: BC6 → BC7 → BC8.";
+
+ private static readonly Guid LevelId = new("560931AD-0901-4342-B7F4-FD2E2FCC0563");
+ private static readonly Guid MaximumHealthId = new("A6C39A5C-295F-415E-A314-5E9F9A748D27");
+ private static readonly Guid MinimumPhysBaseDmgId = new("3E8D6A02-E973-4AE4-9DF3-CDDC3D3183B3");
+ private static readonly Guid MaximumPhysBaseDmgId = new("8A918EA2-893A-48B2-A684-3E71526CA71F");
+ private static readonly Guid DefenseBaseId = new("EB098C46-60D4-4CA6-BBD4-5B6270A1407B");
+ private static readonly Guid AttackRatePvmId = new("1129442A-E1C7-4240-8866-B781C2838C25");
+ private static readonly Guid DefenseRatePvmId = new("C520DD2D-1B06-4392-95EE-3C41F33E68DA");
+ private static readonly Guid PoisonResistanceId = new("3D50D0B7-63A2-4DA9-8855-12173EAE6B39");
+ private static readonly Guid IceResistanceId = new("47235C36-41BB-44B4-8823-6FC415709F59");
+ private static readonly Guid FireResistanceId = new("9AE4D80D-5706-48B9-AD11-EAC4FE088A81");
+ private static readonly Guid LightningResistanceId = new("3E339393-2D17-452E-81D9-3987947A407F");
+
+ ///
+ public override UpdateVersion Version => UpdateVersion.FixBloodCastleMonsterAttributes;
+
+ ///
+ public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
+
+ ///
+ public override string Name => PlugInName;
+
+ ///
+ public override string Description => PlugInDescription;
+
+ ///
+ public override bool IsMandatory => false;
+
+ ///
+ public override DateTime CreatedAt => new(2026, 05, 17, 0, 0, 0, DateTimeKind.Utc);
+
+ ///
+ protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ {
+ var monsters = gameConfiguration.Monsters;
+
+ UpdateMonster(monsters, 138, 105, 29000, 475, 510, 440, 570, 250, 7f / 255, 7f / 255, 7f / 255, 7f / 255);
+ UpdateMonster(monsters, 139, 106, 32000, 510, 555, 480, 640, 260, 7f / 255, 7f / 255, 7f / 255, 7f / 255);
+ UpdateMonster(monsters, 140, 110, 37000, 600, 650, 500, 710, 300, 7f / 255, 7f / 255, 7f / 255, 7f / 255);
+ UpdateMonster(monsters, 141, 112, 45000, 645, 690, 540, 780, 310, 7f / 255, 7f / 255, 7f / 255, 7f / 255);
+ UpdateMonster(monsters, 142, 119, 55000, 780, 820, 600, 850, 360, 7f / 255, 7f / 255, 7f / 255, 7f / 255);
+ UpdateMonster(monsters, 143, 125, 60000, 830, 865, 680, 920, 370, 10f / 255, 10f / 255, 10f / 255, 10f / 255);
+
+ UpdateMonster(monsters, 428, 114, 173500, 745, 800, 600, 640, 426, 8f / 255, 8f / 255, 8f / 255, 8f / 255);
+ UpdateMonster(monsters, 429, 117, 175000, 825, 872, 615, 690, 440, 8f / 255, 8f / 255, 8f / 255, 8f / 255);
+ UpdateMonster(monsters, 430, 125, 184000, 890, 915, 622, 760, 465, 8f / 255, 8f / 255, 8f / 255, 8f / 255);
+ UpdateMonster(monsters, 431, 129, 208000, 920, 946, 635, 830, 510, 8f / 255, 8f / 255, 8f / 255, 8f / 255);
+ UpdateMonster(monsters, 432, 132, 208700, 995, 1120, 648, 900, 585, 8f / 255, 8f / 255, 8f / 255, 8f / 255);
+ UpdateMonster(monsters, 433, 140, 215000, 1500, 1780, 690, 950, 750, 11f / 255, 11f / 255, 11f / 255, 11f / 255);
+
+ return ValueTask.CompletedTask;
+ }
+
+ private static void UpdateMonster(ICollection monsters, short number, int level, int maxHealth, int minDmg, int maxDmg, int defense, int attackRate, int defenseRate, float poisonRes, float iceRes, float fireRes, float lightningRes)
+ {
+ var monster = monsters.FirstOrDefault(m => m.Number == number);
+ if (monster is null)
+ {
+ return;
+ }
+
+ SetAttribute(monster, LevelId, level);
+ SetAttribute(monster, MaximumHealthId, maxHealth);
+ SetAttribute(monster, MinimumPhysBaseDmgId, minDmg);
+ SetAttribute(monster, MaximumPhysBaseDmgId, maxDmg);
+ SetAttribute(monster, DefenseBaseId, defense);
+ SetAttribute(monster, AttackRatePvmId, attackRate);
+ SetAttribute(monster, DefenseRatePvmId, defenseRate);
+ SetAttribute(monster, PoisonResistanceId, poisonRes);
+ SetAttribute(monster, IceResistanceId, iceRes);
+ SetAttribute(monster, FireResistanceId, fireRes);
+ SetAttribute(monster, LightningResistanceId, lightningRes);
+ }
+
+ private static void SetAttribute(MonsterDefinition monster, Guid attributeId, float value)
+ {
+ var attribute = monster.Attributes.FirstOrDefault(a => a.AttributeDefinition?.Id == attributeId);
+ if (attribute is not null)
+ {
+ attribute.Value = value;
+ }
+ }
+}
diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs
index 88de6ca39..0962b5719 100644
--- a/src/Persistence/Initialization/Updates/UpdateVersion.cs
+++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs
@@ -402,4 +402,9 @@ public enum UpdateVersion
/// The version of the .
///
FinishDarkLordMasterTree = 79,
+
+ ///
+ /// The version of the .
+ ///
+ FixBloodCastleMonsterAttributes = 80,
}
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle7.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle7.cs
index a34ca6aaa..6310a8d57 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle7.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle7.cs
@@ -153,13 +153,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 114 },
- { Stats.MaximumHealth, 173500 },
- { Stats.MinimumPhysBaseDmg, 745 },
- { Stats.MaximumPhysBaseDmg, 800 },
- { Stats.DefenseBase, 600 },
- { Stats.AttackRatePvm, 640 },
- { Stats.DefenseRatePvm, 426 },
+ { Stats.Level, 105 },
+ { Stats.MaximumHealth, 29000 },
+ { Stats.MinimumPhysBaseDmg, 475 },
+ { Stats.MaximumPhysBaseDmg, 510 },
+ { Stats.DefenseBase, 440 },
+ { Stats.AttackRatePvm, 570 },
+ { Stats.DefenseRatePvm, 250 },
{ Stats.PoisonResistance, 7f / 255 },
{ Stats.IceResistance, 7f / 255 },
{ Stats.FireResistance, 7f / 255 },
@@ -185,13 +185,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 117 },
- { Stats.MaximumHealth, 175000 },
- { Stats.MinimumPhysBaseDmg, 825 },
- { Stats.MaximumPhysBaseDmg, 872 },
- { Stats.DefenseBase, 615 },
- { Stats.AttackRatePvm, 690 },
- { Stats.DefenseRatePvm, 440 },
+ { Stats.Level, 106 },
+ { Stats.MaximumHealth, 32000 },
+ { Stats.MinimumPhysBaseDmg, 510 },
+ { Stats.MaximumPhysBaseDmg, 555 },
+ { Stats.DefenseBase, 480 },
+ { Stats.AttackRatePvm, 640 },
+ { Stats.DefenseRatePvm, 260 },
{ Stats.PoisonResistance, 7f / 255 },
{ Stats.IceResistance, 7f / 255 },
{ Stats.FireResistance, 7f / 255 },
@@ -217,13 +217,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 125 },
- { Stats.MaximumHealth, 184000 },
- { Stats.MinimumPhysBaseDmg, 890 },
- { Stats.MaximumPhysBaseDmg, 915 },
- { Stats.DefenseBase, 622 },
- { Stats.AttackRatePvm, 760 },
- { Stats.DefenseRatePvm, 465 },
+ { Stats.Level, 110 },
+ { Stats.MaximumHealth, 37000 },
+ { Stats.MinimumPhysBaseDmg, 600 },
+ { Stats.MaximumPhysBaseDmg, 650 },
+ { Stats.DefenseBase, 500 },
+ { Stats.AttackRatePvm, 710 },
+ { Stats.DefenseRatePvm, 300 },
{ Stats.PoisonResistance, 7f / 255 },
{ Stats.IceResistance, 7f / 255 },
{ Stats.FireResistance, 7f / 255 },
@@ -250,13 +250,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.EnergyBall);
var attributes = new Dictionary
{
- { Stats.Level, 129 },
- { Stats.MaximumHealth, 208000 },
- { Stats.MinimumPhysBaseDmg, 920 },
- { Stats.MaximumPhysBaseDmg, 946 },
- { Stats.DefenseBase, 635 },
- { Stats.AttackRatePvm, 830 },
- { Stats.DefenseRatePvm, 510 },
+ { Stats.Level, 112 },
+ { Stats.MaximumHealth, 45000 },
+ { Stats.MinimumPhysBaseDmg, 645 },
+ { Stats.MaximumPhysBaseDmg, 690 },
+ { Stats.DefenseBase, 540 },
+ { Stats.AttackRatePvm, 780 },
+ { Stats.DefenseRatePvm, 310 },
{ Stats.PoisonResistance, 7f / 255 },
{ Stats.IceResistance, 7f / 255 },
{ Stats.FireResistance, 7f / 255 },
@@ -282,13 +282,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 132 },
- { Stats.MaximumHealth, 208700 },
- { Stats.MinimumPhysBaseDmg, 995 },
- { Stats.MaximumPhysBaseDmg, 1120 },
- { Stats.DefenseBase, 648 },
- { Stats.AttackRatePvm, 900 },
- { Stats.DefenseRatePvm, 585 },
+ { Stats.Level, 119 },
+ { Stats.MaximumHealth, 55000 },
+ { Stats.MinimumPhysBaseDmg, 780 },
+ { Stats.MaximumPhysBaseDmg, 820 },
+ { Stats.DefenseBase, 600 },
+ { Stats.AttackRatePvm, 850 },
+ { Stats.DefenseRatePvm, 360 },
{ Stats.PoisonResistance, 7f / 255 },
{ Stats.IceResistance, 7f / 255 },
{ Stats.FireResistance, 7f / 255 },
@@ -315,13 +315,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
var attributes = new Dictionary
{
- { Stats.Level, 140 },
- { Stats.MaximumHealth, 215000 },
- { Stats.MinimumPhysBaseDmg, 1500 },
- { Stats.MaximumPhysBaseDmg, 1780 },
- { Stats.DefenseBase, 670 },
- { Stats.AttackRatePvm, 950 },
- { Stats.DefenseRatePvm, 750 },
+ { Stats.Level, 125 },
+ { Stats.MaximumHealth, 60000 },
+ { Stats.MinimumPhysBaseDmg, 830 },
+ { Stats.MaximumPhysBaseDmg, 865 },
+ { Stats.DefenseBase, 680 },
+ { Stats.AttackRatePvm, 920 },
+ { Stats.DefenseRatePvm, 370 },
{ Stats.PoisonResistance, 10f / 255 },
{ Stats.IceResistance, 10f / 255 },
{ Stats.FireResistance, 10f / 255 },
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle8.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle8.cs
index 33d1cd4d9..a63e8ae95 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle8.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/BloodCastle8.cs
@@ -153,13 +153,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 105 },
- { Stats.MaximumHealth, 29000 },
- { Stats.MinimumPhysBaseDmg, 475 },
- { Stats.MaximumPhysBaseDmg, 510 },
- { Stats.DefenseBase, 440 },
- { Stats.AttackRatePvm, 570 },
- { Stats.DefenseRatePvm, 250 },
+ { Stats.Level, 114 },
+ { Stats.MaximumHealth, 173500 },
+ { Stats.MinimumPhysBaseDmg, 745 },
+ { Stats.MaximumPhysBaseDmg, 800 },
+ { Stats.DefenseBase, 600 },
+ { Stats.AttackRatePvm, 640 },
+ { Stats.DefenseRatePvm, 426 },
{ Stats.PoisonResistance, 8f / 255 },
{ Stats.IceResistance, 8f / 255 },
{ Stats.FireResistance, 8f / 255 },
@@ -185,13 +185,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 106 },
- { Stats.MaximumHealth, 32000 },
- { Stats.MinimumPhysBaseDmg, 510 },
- { Stats.MaximumPhysBaseDmg, 555 },
- { Stats.DefenseBase, 480 },
- { Stats.AttackRatePvm, 640 },
- { Stats.DefenseRatePvm, 260 },
+ { Stats.Level, 117 },
+ { Stats.MaximumHealth, 175000 },
+ { Stats.MinimumPhysBaseDmg, 825 },
+ { Stats.MaximumPhysBaseDmg, 872 },
+ { Stats.DefenseBase, 615 },
+ { Stats.AttackRatePvm, 690 },
+ { Stats.DefenseRatePvm, 440 },
{ Stats.PoisonResistance, 8f / 255 },
{ Stats.IceResistance, 8f / 255 },
{ Stats.FireResistance, 8f / 255 },
@@ -217,13 +217,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 110 },
- { Stats.MaximumHealth, 37000 },
- { Stats.MinimumPhysBaseDmg, 600 },
- { Stats.MaximumPhysBaseDmg, 650 },
- { Stats.DefenseBase, 500 },
- { Stats.AttackRatePvm, 710 },
- { Stats.DefenseRatePvm, 300 },
+ { Stats.Level, 125 },
+ { Stats.MaximumHealth, 184000 },
+ { Stats.MinimumPhysBaseDmg, 890 },
+ { Stats.MaximumPhysBaseDmg, 915 },
+ { Stats.DefenseBase, 622 },
+ { Stats.AttackRatePvm, 760 },
+ { Stats.DefenseRatePvm, 465 },
{ Stats.PoisonResistance, 8f / 255 },
{ Stats.IceResistance, 8f / 255 },
{ Stats.FireResistance, 8f / 255 },
@@ -250,13 +250,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.EnergyBall);
var attributes = new Dictionary
{
- { Stats.Level, 112 },
- { Stats.MaximumHealth, 45000 },
- { Stats.MinimumPhysBaseDmg, 645 },
- { Stats.MaximumPhysBaseDmg, 690 },
- { Stats.DefenseBase, 540 },
- { Stats.AttackRatePvm, 780 },
- { Stats.DefenseRatePvm, 310 },
+ { Stats.Level, 129 },
+ { Stats.MaximumHealth, 208000 },
+ { Stats.MinimumPhysBaseDmg, 920 },
+ { Stats.MaximumPhysBaseDmg, 946 },
+ { Stats.DefenseBase, 635 },
+ { Stats.AttackRatePvm, 830 },
+ { Stats.DefenseRatePvm, 510 },
{ Stats.PoisonResistance, 8f / 255 },
{ Stats.IceResistance, 8f / 255 },
{ Stats.FireResistance, 8f / 255 },
@@ -282,13 +282,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary
{
- { Stats.Level, 119 },
- { Stats.MaximumHealth, 55000 },
- { Stats.MinimumPhysBaseDmg, 780 },
- { Stats.MaximumPhysBaseDmg, 820 },
- { Stats.DefenseBase, 600 },
- { Stats.AttackRatePvm, 850 },
- { Stats.DefenseRatePvm, 360 },
+ { Stats.Level, 132 },
+ { Stats.MaximumHealth, 208700 },
+ { Stats.MinimumPhysBaseDmg, 995 },
+ { Stats.MaximumPhysBaseDmg, 1120 },
+ { Stats.DefenseBase, 648 },
+ { Stats.AttackRatePvm, 900 },
+ { Stats.DefenseRatePvm, 585 },
{ Stats.PoisonResistance, 8f / 255 },
{ Stats.IceResistance, 8f / 255 },
{ Stats.FireResistance, 8f / 255 },
@@ -315,13 +315,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
var attributes = new Dictionary
{
- { Stats.Level, 125 },
- { Stats.MaximumHealth, 60000 },
- { Stats.MinimumPhysBaseDmg, 830 },
- { Stats.MaximumPhysBaseDmg, 865 },
- { Stats.DefenseBase, 680 },
- { Stats.AttackRatePvm, 920 },
- { Stats.DefenseRatePvm, 370 },
+ { Stats.Level, 140 },
+ { Stats.MaximumHealth, 215000 },
+ { Stats.MinimumPhysBaseDmg, 1500 },
+ { Stats.MaximumPhysBaseDmg, 1780 },
+ { Stats.DefenseBase, 690 },
+ { Stats.AttackRatePvm, 950 },
+ { Stats.DefenseRatePvm, 750 },
{ Stats.PoisonResistance, 11f / 255 },
{ Stats.IceResistance, 11f / 255 },
{ Stats.FireResistance, 11f / 255 },