Skip to content
Merged
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,105 @@
// <copyright file="FixBloodCastleMonsterAttributesUpdatePlugIn.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.PlugIns;

/// <summary>
/// Swaps attribute values between BC7 (monsters 138-143) and BC8 (monsters 428-433)
/// so the difficulty progression is correct: BC6 → BC7 → BC8.
/// </summary>
[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");

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

/// <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 => false;

/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 05, 17, 0, 0, 0, DateTimeKind.Utc);

/// <inheritdoc />
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<MonsterDefinition> 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;
}
}
}
5 changes: 5 additions & 0 deletions src/Persistence/Initialization/Updates/UpdateVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,9 @@ public enum UpdateVersion
/// The version of the <see cref="FinishDarkLordMasterTreePlugIn"/>.
/// </summary>
FinishDarkLordMasterTree = 79,

/// <summary>
/// The version of the <see cref="FixBloodCastleMonsterAttributesUpdatePlugIn"/>.
/// </summary>
FixBloodCastleMonsterAttributes = 80,
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 114 },
{ Stats.MaximumHealth, 173500 },
{ Stats.MinimumPhysBaseDmg, 745 },
{ Stats.MaximumPhysBaseDmg, 800 },
{ Stats.DefenseBase, 600 },
{ Stats.AttackRatePvm, 640 },
{ Stats.DefenseRatePvm, 426 },
{ Stats.Level, 105 },
Comment thread
nolt marked this conversation as resolved.
{ 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 },
Expand All @@ -185,13 +185,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -217,13 +217,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -250,13 +250,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.EnergyBall);
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -282,13 +282,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -315,13 +315,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Comment thread
nolt marked this conversation as resolved.
{ Stats.AttackRatePvm, 920 },
{ Stats.DefenseRatePvm, 370 },
{ Stats.PoisonResistance, 10f / 255 },
{ Stats.IceResistance, 10f / 255 },
{ Stats.FireResistance, 10f / 255 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -185,13 +185,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -217,13 +217,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -250,13 +250,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.EnergyBall);
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -282,13 +282,13 @@ protected override void CreateMonsters()
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand All @@ -315,13 +315,13 @@ protected override void CreateMonsters()
monster.AttackSkill = this.GameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
var attributes = new Dictionary<AttributeDefinition, float>
{
{ 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 },
Expand Down