baseline: OpenMU upstream b5a0961 (fresh source)

This commit is contained in:
Acentech Dev
2026-07-14 19:00:35 +03:00
parent 450402e47d
commit 36fc125d5c
11968 changed files with 748705 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
// <copyright file="AddAreaSkillSettingsUpdatePlugIn.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.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This adds the items required to enter the kalima map.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("D01DA745-BF72-40C4-BD90-D2D637AEDF99")]
public class AddAreaSkillSettingsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Area Skill Settings";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Adds the new area skill settings for skills like evil spirit, etc. to make them work properly again.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddAreaSkillSettings;
/// <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(2024, 10, 25, 19, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Flame, false, default, default, default, true, TimeSpan.Zero, TimeSpan.FromMilliseconds(500), 0, 2, default, 0.5f, targetAreaDiameter: 2, useTargetAreaFilter: true);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Twister, true, 1.5f, 1.5f, 4f, true, TimeSpan.FromMilliseconds(300), TimeSpan.FromMilliseconds(1000), 0, 2, default, 0.7f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.EvilSpirit, false, default, default, default, true, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(1000), 0, 2, default, 0.7f, newRange: 7);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.AquaBeam, true, 1.5f, 1.5f, 8f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Cometfall, false, default, default, default, targetAreaDiameter: 2, useTargetAreaFilter: true);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.TripleShot, true, 1f, 4.5f, 7f, true, TimeSpan.FromMilliseconds(50), maximumHitsPerTarget: 3, maximumHitsPerAttack: 3);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.IceStorm, false, default, default, default, true, TimeSpan.Zero, TimeSpan.FromMilliseconds(200), targetAreaDiameter: 3, useTargetAreaFilter: true);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Penetration, true, 1.1f, 1.2f, 8f, useDeferredHits: true, delayPerOneDistance: TimeSpan.FromMilliseconds(50));
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.FireSlash, true, 1.5f, 2, 2);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.PowerSlash, true, 1.0f, 6.0f, 6.0f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.ElectricSpike, true, 1.5f, 1.5f, 12f, useDeferredHits: true, delayPerOneDistance: TimeSpan.FromMilliseconds(10));
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.ForceWave, true, 1f, 1f, 4f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Stun, true, 1.5f, 1.5f, 3f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.FireScream, true, 2f, 3f, 6f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.MultiShot, true, 1f, 6f, 7f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.FlameStrike, true, 5f, 2f, 4f);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.ChaoticDiseier, true, 1.5f, 1.5f, 6f);
// Fix master skills as well:
foreach (var skill in gameConfiguration.Skills.OrderBy(s => s.Number))
{
var replacedSkill = skill.MasterDefinition?.ReplacedSkill;
if (replacedSkill?.AreaSkillSettings is not { } areaSkillSettings)
{
continue;
}
skill.AreaSkillSettings = context.CreateNew<AreaSkillSettings>();
var id = skill.AreaSkillSettings.GetId();
skill.AreaSkillSettings.AssignValuesOf(areaSkillSettings, gameConfiguration);
skill.AreaSkillSettings.SetGuid(id);
}
}
private void AddAreaSkillSettings(
GameConfiguration gameConfiguration,
IContext context,
SkillNumber skillNumber,
bool useFrustumFilter,
float frustumStartWidth,
float frustumEndWidth,
float frustumDistance,
bool useDeferredHits = false,
TimeSpan delayPerOneDistance = default,
TimeSpan delayBetweenHits = default,
int minimumHitsPerTarget = 1,
int maximumHitsPerTarget = 1,
int maximumHitsPerAttack = default,
float hitChancePerDistanceMultiplier = 1.0f,
bool useTargetAreaFilter = false,
float targetAreaDiameter = default,
short? newRange = null)
{
var skill = gameConfiguration.Skills.First(s => s.Number == (short)skillNumber);
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
skill.AreaSkillSettings = areaSkillSettings;
skill.SkillType = SkillType.AreaSkillAutomaticHits;
if (newRange.HasValue)
{
skill.Range = newRange.Value;
}
areaSkillSettings.UseFrustumFilter = useFrustumFilter;
areaSkillSettings.FrustumStartWidth = frustumStartWidth;
areaSkillSettings.FrustumEndWidth = frustumEndWidth;
areaSkillSettings.FrustumDistance = frustumDistance;
areaSkillSettings.UseTargetAreaFilter = useTargetAreaFilter;
areaSkillSettings.TargetAreaDiameter = targetAreaDiameter;
areaSkillSettings.UseDeferredHits = useDeferredHits;
areaSkillSettings.DelayPerOneDistance = delayPerOneDistance;
areaSkillSettings.DelayBetweenHits = delayBetweenHits;
areaSkillSettings.MinimumNumberOfHitsPerTarget = minimumHitsPerTarget;
areaSkillSettings.MaximumNumberOfHitsPerTarget = maximumHitsPerTarget;
areaSkillSettings.MaximumNumberOfHitsPerAttack = maximumHitsPerAttack;
areaSkillSettings.HitChancePerDistanceMultiplier = hitChancePerDistanceMultiplier;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,115 @@
// <copyright file="AddDuelConfigurationPlugIn.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;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This updates adds the data for the duel system.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("5DC5638E-581E-4ACC-81E4-D565C625649B")]
public class AddDuelConfigurationPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add duel configuration";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds data for the duel configuration";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddDuelConfiguration;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 07, 11, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
if (gameConfiguration.DuelConfiguration is not null)
{
return;
}
var invisibleEffect = new InvisibleEffectInitializer(context, gameConfiguration);
invisibleEffect.Initialize();
var duelMap = gameConfiguration.Maps.First(m => m.Number == 64);
var mapGates = duelMap.ExitGates.ToDictionary(g => (g.X1, g.Y1), g => g);
var targetGates = new Dictionary<short, ExitGate>
{
{ 294, gameConfiguration.Maps.First(m => m.Number == 63).ExitGates.First() },
{ 295, mapGates[(101, 64)] },
{ 296, mapGates[(101, 75)] },
{ 297, mapGates[(101, 113)] },
{ 298, mapGates[(101, 124)] },
{ 299, mapGates[(154, 64)] },
{ 300, mapGates[(154, 75)] },
{ 301, mapGates[(154, 113)] },
{ 302, mapGates[(154, 124)] },
{ 303, mapGates[(100, 70)] },
{ 304, mapGates[(100, 120)] },
{ 305, mapGates[(150, 70)] },
{ 306, mapGates[(150, 120)] },
};
gameConfiguration.DuelConfiguration = this.CreateDuelConfiguration(context, targetGates);
var doorkeeper = gameConfiguration.Monsters.First(m => m.Number == 479);
doorkeeper.NpcWindow = NpcWindow.DoorkeeperTitusDuelWatch;
}
private DuelConfiguration CreateDuelConfiguration(IContext context, IDictionary<short, ExitGate> targetGates)
{
var duelConfig = context.CreateNew<DuelConfiguration>();
duelConfig.MaximumScore = 10;
duelConfig.MinimumCharacterLevel = 30;
duelConfig.EntranceFee = 30000;
duelConfig.Exit = targetGates[294]; // Vulcanus, see above
List<(short FirstPlayerGate, short SecondPlayerGate, short SpectatorGate)> duelGateNumbers =
[
(295, 296, 303),
(297, 298, 304),
(299, 300, 305),
(301, 302, 306),
];
for (short i = 0; i < duelGateNumbers.Count; i++)
{
var indices = duelGateNumbers[i];
var duelArea = context.CreateNew<DuelArea>();
duelArea.Index = i;
duelArea.FirstPlayerGate = targetGates[indices.FirstPlayerGate];
duelArea.SecondPlayerGate = targetGates[indices.SecondPlayerGate];
duelArea.SpectatorsGate = targetGates[indices.SpectatorGate];
duelConfig.DuelAreas.Add(duelArea);
}
return duelConfig;
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="AddGlobalMoneyAmountRateAttributePlugIn075.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.PlugIns;
/// <summary>
/// This update moves the MoneyAmountRate attribute to global base attributes for version 0.75.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A1B2C3D4-E5F6-7890-ABCD-EF1234567890")]
public class AddGlobalMoneyAmountRateAttributePlugIn075 : AddGlobalMoneyAmountRateAttributePlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddGlobalMoneyAmountRateAttribute075;
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
}

View File

@@ -0,0 +1,23 @@
// <copyright file="AddGlobalMoneyAmountRateAttributePlugIn095d.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.PlugIns;
/// <summary>
/// This update moves the MoneyAmountRate attribute to global base attributes for version 0.95d.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("B2C3D4E5-F6A7-8901-BCDE-F12345678901")]
public class AddGlobalMoneyAmountRateAttributePlugIn095d : AddGlobalMoneyAmountRateAttributePlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddGlobalMoneyAmountRateAttribute095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
}

View File

@@ -0,0 +1,64 @@
// <copyright file="AddGlobalMoneyAmountRateAttributePlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// This update moves the <see cref="Stats.MoneyAmountRate"/> attribute from individual character class
/// base attribute values to the global base attribute values of the game configuration.
/// </summary>
public abstract class AddGlobalMoneyAmountRateAttributePlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Global Money Amount Rate Attribute";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Moves the MoneyAmountRate attribute from character class base attributes to global base attributes.";
/// <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, 20, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var moneyAmountRateDef = gameConfiguration.Attributes.FirstOrDefault(a => a.Id == Stats.MoneyAmountRate.Id);
if (moneyAmountRateDef is null)
{
return;
}
if (!gameConfiguration.GlobalBaseAttributeValues.Any(a => a.Definition?.Id == Stats.MoneyAmountRate.Id))
{
var globalMoneyRate = context.CreateNew<ConstValueAttribute>(1f, moneyAmountRateDef);
gameConfiguration.GlobalBaseAttributeValues.Add(globalMoneyRate);
}
foreach (var characterClass in gameConfiguration.CharacterClasses)
{
var classMoneyRate = characterClass.BaseAttributeValues
.FirstOrDefault(a => a.Definition?.Id == Stats.MoneyAmountRate.Id);
if (classMoneyRate is not null)
{
characterClass.BaseAttributeValues.Remove(classMoneyRate);
}
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="AddGlobalMoneyAmountRateAttributePlugInSeason6.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.PlugIns;
/// <summary>
/// This update moves the MoneyAmountRate attribute to global base attributes for Season 6.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("C3D4E5F6-A7B8-9012-CDEF-123456789012")]
public class AddGlobalMoneyAmountRateAttributePlugInSeason6 : AddGlobalMoneyAmountRateAttributePlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddGlobalMoneyAmountRateAttributeSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
}

View File

@@ -0,0 +1,133 @@
// <copyright file="AddGuardsDataPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This updates adds the data for Guard NPCs.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("1EF50759-0A5F-4301-A5E9-B68A8B7D29F9")]
public class AddGuardsDataPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add guard npc data";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds data for guard npcs, so they can attack and move around.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddGuardsData;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2023, 06, 01, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var guard = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == 220);
if (guard is { AttackRange: 0 })
{
guard.Designation = "Guard";
guard.ObjectKind = NpcObjectKind.Guard;
guard.MoveRange = 3;
guard.AttackRange = 2;
guard.ViewRange = 8;
guard.IntelligenceTypeName = typeof(GuardIntelligence).FullName;
guard.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond);
guard.AttackDelay = new TimeSpan(1500 * TimeSpan.TicksPerMillisecond);
guard.RespawnDelay = new TimeSpan(3 * TimeSpan.TicksPerSecond);
guard.NumberOfMaximumItemDrops = 0;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 2 },
{ Stats.MaximumHealth, 500 },
{ Stats.MinimumPhysBaseDmg, 15 },
{ Stats.MaximumPhysBaseDmg, 30 },
{ Stats.AttackRatePvm, 30 },
{ Stats.DefenseRatePvm, 20 },
{ Stats.DefenseBase, 70 },
};
guard.AddAttributes(attributes, context, gameConfiguration);
}
var crossbowGuard = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == 247);
if (crossbowGuard is { AttackRange: 0 })
{
crossbowGuard.Designation = "Crossbow Guard";
crossbowGuard.ObjectKind = NpcObjectKind.Guard;
crossbowGuard.MoveRange = 3;
crossbowGuard.AttackRange = 5;
crossbowGuard.ViewRange = 7;
crossbowGuard.IntelligenceTypeName = typeof(GuardIntelligence).FullName;
crossbowGuard.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond);
crossbowGuard.AttackDelay = new TimeSpan(1500 * TimeSpan.TicksPerMillisecond);
crossbowGuard.RespawnDelay = new TimeSpan(3 * TimeSpan.TicksPerSecond);
crossbowGuard.NumberOfMaximumItemDrops = 0;
crossbowGuard.Attribute = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 90 },
{ Stats.MaximumHealth, 10000 },
{ Stats.MinimumPhysBaseDmg, 180 },
{ Stats.MaximumPhysBaseDmg, 195 },
{ Stats.AttackRatePvm, 300 },
{ Stats.DefenseRatePvm, 100 },
{ Stats.DefenseBase, 70 },
};
crossbowGuard.AddAttributes(attributes, context, gameConfiguration);
}
var berdyshGuard = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == 249);
if (berdyshGuard is { AttackRange: 0 })
{
berdyshGuard.Designation = "Berdysh Guard";
berdyshGuard.ObjectKind = NpcObjectKind.Guard;
berdyshGuard.MoveRange = 3;
berdyshGuard.AttackRange = 2;
berdyshGuard.ViewRange = 7;
berdyshGuard.IntelligenceTypeName = typeof(GuardIntelligence).FullName;
berdyshGuard.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond);
berdyshGuard.AttackDelay = new TimeSpan(1500 * TimeSpan.TicksPerMillisecond);
berdyshGuard.RespawnDelay = new TimeSpan(3 * TimeSpan.TicksPerSecond);
berdyshGuard.NumberOfMaximumItemDrops = 0;
berdyshGuard.Attribute = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 90 },
{ Stats.MaximumHealth, 10000 },
{ Stats.MinimumPhysBaseDmg, 180 },
{ Stats.MaximumPhysBaseDmg, 195 },
{ Stats.AttackRatePvm, 300 },
{ Stats.DefenseRatePvm, 100 },
{ Stats.DefenseBase, 70 },
};
berdyshGuard.AddAttributes(attributes, context, gameConfiguration);
}
}
}

View File

@@ -0,0 +1,154 @@
// <copyright file="AddHarmonyOptionWeightsUpdateSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds Jewel of Harmony option weights used for option assignment, fixes some options, and fixes item restore mix.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("E94DE59E-5B3A-4498-A4AF-E7F4F173B754")]
public class AddHarmonyOptionWeightsUpdateSeason6 : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add harmony option weights";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds Jewel of Harmony option weights used for option assignment, fixes some options, and fixes item restore mix";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddHarmonyOptionWeightsSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 10, 24, 15, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Fix Restore Item Mix
var requiredItem = gameConfiguration.Monsters
.Single(m => m.NpcWindow == NpcWindow.RemoveJohOption).ItemCraftings
.Single(ic => ic.Number == 35).SimpleCraftingSettings?.RequiredItems
.Single();
if (requiredItem is { } item)
{
item.MaximumAmount = 1;
item.MaximumItemLevel = 15;
}
// Add JoH option weights
byte[] defOptWeights = [50, 40, 40, 30, 20, 20, 20, 10];
byte[] physAttackOptWeights = [40, 40, 40, 40, 30, 30, 20, 20, 20, 10];
byte[] magicOptWeights = [40, 40, 40, 30, 30, 20, 20, 10];
var defOptions = gameConfiguration.ItemOptions.Where(io => io.Name == HarmonyOptions.DefenseOptionsName)
.FirstOrDefault()?.PossibleOptions.OrderBy(o => o.Number);
var physAttackOptions = gameConfiguration.ItemOptions.Where(io => io.Name == HarmonyOptions.PhysicalAttackOptionsName)
.FirstOrDefault()?.PossibleOptions.OrderBy(o => o.Number);
var wizAttackOptions = gameConfiguration.ItemOptions.Where(io => io.Name == HarmonyOptions.WizardryAttackOptionsName)
.FirstOrDefault()?.PossibleOptions.OrderBy(o => o.Number);
var curseAttackOptions = gameConfiguration.ItemOptions.Where(io => io.Name == HarmonyOptions.CurseAttackOptionsName)
.FirstOrDefault()?.PossibleOptions.OrderBy(o => o.Number);
if (defOptions?.Count() == defOptWeights.Length)
{
for (int i = 0; i < defOptWeights.Length; i++)
{
defOptions.ElementAt(i).Weight = defOptWeights[i];
}
}
if (physAttackOptions?.Count() == physAttackOptWeights.Length)
{
for (int i = 0; i < physAttackOptWeights.Length; i++)
{
physAttackOptions.ElementAt(i).Weight = physAttackOptWeights[i];
}
}
if (wizAttackOptions?.Count() == magicOptWeights.Length)
{
for (int i = 0; i < magicOptWeights.Length; i++)
{
wizAttackOptions.ElementAt(i).Weight = magicOptWeights[i];
}
}
if (curseAttackOptions?.Count() == magicOptWeights.Length)
{
for (int i = 0; i < magicOptWeights.Length; i++)
{
curseAttackOptions.ElementAt(i).Weight = magicOptWeights[i];
}
}
// Fix physical base dmg attribute
var baseDmgBonusOpt = physAttackOptions?.Single(o => o.Number == 5);
var physBaseDmgAttr = gameConfiguration.Attributes.Single(a => a.Id == new Guid("DD1E13E4-BFFD-45B5-9B91-9080710324B2"));
if (baseDmgBonusOpt?.LevelDependentOptions is ICollection<ItemOptionOfLevel> baseDmgOptLvls)
{
foreach (var level in baseDmgOptLvls)
{
if (level.PowerUpDefinition is PowerUpDefinition pud)
{
pud.TargetAttribute = physBaseDmgAttr;
}
}
}
// Fix wiz/curse dmg increase option values and attribute
var wizAtkDmgIncOpt = wizAttackOptions?.Single(o => o.Number == 1);
var curseAtkDmgIncOpt = curseAttackOptions?.Single(o => o.Number == 1);
List<IncreasableItemOption?> magicAtkDmgIncOpts = [wizAtkDmgIncOpt, curseAtkDmgIncOpt];
var wizBaseDmgAttr = gameConfiguration.Attributes.Single(a => a.Id == new Guid("7F4F3646-33A6-40AC-8DA6-29A0A0F46016"));
float[] magicAtkDmgIncValues = [6, 8, 10, 12, 14, 16, 17, 18, 19, 21, 23, 25, 27, 31];
foreach (var opt in magicAtkDmgIncOpts)
{
var optLvls = opt?.LevelDependentOptions.OrderBy(ldo => ldo.Level);
if (optLvls?.Count() == magicAtkDmgIncValues.Length)
{
for (int i = 0; i < magicAtkDmgIncValues.Length; i++)
{
if (optLvls.ElementAt(i).PowerUpDefinition is PowerUpDefinition pud)
{
pud.TargetAttribute = wizBaseDmgAttr;
if (pud.Boost?.ConstantValue is SimpleElement element)
{
element.Value = magicAtkDmgIncValues[i];
}
}
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
// <copyright file="AddIsQuestItemFlagPlugIn.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.Items;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update sets the new <see cref="ItemDefinition.IsQuestItem"/> flag on the existing quest items,
/// so that pick-up eligibility can be checked against a character's active quests instead of relying on
/// <see cref="ItemDefinition.IsBoundToCharacter"/> alone, which is also used by non-quest items.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("9374E428-CF5C-44B1-AAB9-0369C77AF7C6")]
public class AddIsQuestItemFlagPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add IsQuestItem flag";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update flags the existing quest items as such, so that they can only be picked up by characters with a matching active quest.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddIsQuestItemFlag;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 07, 10, 20, 50, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var hashSet = new HashSet<short>
{
Quest.BrokenSwordNumber,
Quest.EyeOfAbyssalNumber,
Quest.FeatherOfDarkPhoenixNumber,
Quest.FlameOfDeathBeamKnightNumber,
Quest.HornOfHellMaineNumber,
Quest.ScrollOfEmperorNumber,
Quest.SoulShardOfWizardNumber,
Quest.TearOfElfNumber,
};
var questItems = gameConfiguration.Items.Where(item => item.Group == 14 && hashSet.Contains(item.Number));
foreach (var item in questItems)
{
item.IsQuestItem = true;
}
}
}

View File

@@ -0,0 +1,147 @@
// <copyright file="AddItemDropGroupForJewelsUpdate075.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;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update creates a specific item drop group for jewels with a default chance of 5%.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("DCF14924-BB19-4CA2-93EC-397A89AA3EB3")]
public class AddItemDropGroupForJewelsUpdate075 : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Create item drop group for jewels";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update creates a specific item drop group for jewels with a default chance of 5%.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddItemDropGroupForJewels075;
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => false;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 26, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
this.CreateDropItemGroupForJewels(context, gameConfiguration, 4, null, "The jewels drop item group (0.1 % drop chance)");
this.AddJewelToItemDrop(gameConfiguration, 4, null, "Jewel of Bless");
this.AddJewelToItemDrop(gameConfiguration, 4, null, "Jewel of Soul");
this.AddJewelToItemDrop(gameConfiguration, 4, null, "Jewel of Chaos");
}
/// <summary>
/// Add jewel to specific drop item group.
/// </summary>
/// <param name="gameConfiguration">Game configuration context to update.</param>
/// <param name="dropId">Drop to which the jewel will be associated.</param>
/// <param name="mapNumber">Optionally the map number to asociate drop group item.</param>
/// <param name="jewelName">Jewel name to add into specific drop item group.</param>
protected void AddJewelToItemDrop(GameConfiguration gameConfiguration, short dropId, short? mapNumber, string jewelName)
{
var jewelsItemDrop = GetJewelsDropItemGroup(gameConfiguration, dropId, mapNumber);
if (jewelsItemDrop == null)
{
return;
}
var item = gameConfiguration.Items.FirstOrDefault(x => x.Name == jewelName);
if (item == null)
{
return;
}
if (item.DropsFromMonsters == true)
{
item.DropsFromMonsters = false;
}
var jewelId = item.GetItemId();
if (!jewelsItemDrop.PossibleItems.Any(x => x.GetItemId() == jewelId))
{
jewelsItemDrop.PossibleItems.Add(item);
}
}
/// <summary>
/// Create drop item group for jewels.
/// </summary>
/// <param name="context">The persistence context.</param>
/// <param name="gameConfiguration">Game configuration context to update.</param>
/// <param name="dropId">New drop id.</param>
/// <param name="mapNumber">Optionally the map number to associate drop group item.</param>
/// <param name="name">Description of drop.</param>
/// <returns>Returns new drop item group.</returns>
protected DropItemGroup CreateDropItemGroupForJewels(IContext context, GameConfiguration gameConfiguration, short dropId, short? mapNumber, string name)
{
var jewelsItemDrop = GetJewelsDropItemGroup(gameConfiguration, dropId, mapNumber);
if (jewelsItemDrop != null)
{
return jewelsItemDrop;
}
var jewelsDropItemGroup = context.CreateNew<DropItemGroup>();
if (mapNumber != null)
{
jewelsDropItemGroup.SetGuid(mapNumber.Value, dropId);
}
else
{
jewelsDropItemGroup.SetGuid(dropId);
}
jewelsDropItemGroup.Chance = 0.001;
jewelsDropItemGroup.ItemType = SpecialItemType.RandomItem;
jewelsDropItemGroup.Description = name;
gameConfiguration.DropItemGroups.Add(jewelsDropItemGroup);
if (mapNumber != null)
{
var map = gameConfiguration.Maps.First(x => x.Number == mapNumber);
map.DropItemGroups.Add(jewelsDropItemGroup);
}
return jewelsDropItemGroup;
}
private static DropItemGroup? GetJewelsDropItemGroup(GameConfiguration gameConfiguration, short dropId, short? mapNumber)
{
var id = mapNumber != null
? GuidHelper.CreateGuid<DropItemGroup>(mapNumber ?? 0, dropId)
: GuidHelper.CreateGuid<DropItemGroup>(dropId);
var jewelsItemDrop = gameConfiguration.DropItemGroups.FirstOrDefault(x => x.GetId() == id);
return jewelsItemDrop;
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="AddItemDropGroupForJewelsUpdate095d.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 System.Threading.Tasks;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update creates a specific item drop group for jewels with a default chance of 5%.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("D21056E6-E912-416B-A076-3C2D17DA517B")]
public class AddItemDropGroupForJewelsUpdate095D : AddItemDropGroupForJewelsUpdate075
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddItemDropGroupForJewels095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.AddJewelToItemDrop(gameConfiguration, 4, null, "Jewel of Life");
}
}

View File

@@ -0,0 +1,35 @@
// <copyright file="AddItemDropGroupForJewelsUpdateSeason6.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 System.Threading.Tasks;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update creates a specific item drop group for jewels with a default chance of 5%.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F958CC5B-C1E6-4F67-B48D-4BF75EC5CAA8")]
public class AddItemDropGroupForJewelsUpdateSeason6 : AddItemDropGroupForJewelsUpdate075
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddItemDropGroupForJewelsSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
this.CreateDropItemGroupForJewels(context, gameConfiguration, 1, VersionSeasonSix.Maps.LandOfTrials.Number, "Jewel of Guardian");
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.AddJewelToItemDrop(gameConfiguration, 4, null, "Jewel of Creation");
this.AddJewelToItemDrop(gameConfiguration, 1, VersionSeasonSix.Maps.LandOfTrials.Number, "Jewel of Guardian");
}
}

View File

@@ -0,0 +1,113 @@
// <copyright file="AddKalimaPlugIn.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.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This adds the items required to enter the kalima map.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("0C99155F-1289-4E73-97F0-47CB67C3716F")]
public class AddKalimaPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Kalima";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This adds the items required to enter the kalima map.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddKalima;
/// <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(2024, 06, 09, 18, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
#pragma warning disable CS1998
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
#pragma warning restore CS1998
{
this.CreateLostMap(context, gameConfiguration);
this.CreateSymbolOfKundun(context, gameConfiguration);
// copy potion girl items to oracle layla:
var potionGirl = gameConfiguration.Monsters.First(m => m.Number == 253);
var oracleLayla = gameConfiguration.Monsters.First(m => m.Number == 259);
if (oracleLayla.NpcWindow is not NpcWindow.Merchant && oracleLayla.MerchantStore is null)
{
oracleLayla.NpcWindow = NpcWindow.Merchant;
oracleLayla.MerchantStore = potionGirl.MerchantStore!.Clone(gameConfiguration);
oracleLayla.MerchantStore.SetGuid(oracleLayla.Number);
}
}
private void CreateLostMap(IContext context, GameConfiguration gameConfiguration)
{
var itemDefinition = context.CreateNew<ItemDefinition>();
itemDefinition.Name = "Lost Map";
itemDefinition.Number = 28;
itemDefinition.Group = 14;
itemDefinition.DropsFromMonsters = false;
itemDefinition.Durability = 1;
itemDefinition.Width = 1;
itemDefinition.Height = 1;
itemDefinition.MaximumItemLevel = 7;
itemDefinition.SetGuid(itemDefinition.Group, itemDefinition.Number);
gameConfiguration.Items.Add(itemDefinition);
}
private void CreateSymbolOfKundun(IContext context, GameConfiguration gameConfiguration)
{
var itemDefinition = context.CreateNew<ItemDefinition>();
itemDefinition.Name = "Symbol of Kundun";
itemDefinition.Number = 29;
itemDefinition.Group = 14;
itemDefinition.DropLevel = 0;
itemDefinition.DropsFromMonsters = true;
itemDefinition.Durability = 5;
itemDefinition.Width = 1;
itemDefinition.Height = 1;
itemDefinition.MaximumItemLevel = 7;
itemDefinition.SetGuid(itemDefinition.Group, itemDefinition.Number);
gameConfiguration.Items.Add(itemDefinition);
(byte, byte)[] dropLevels = [(25, 46), (47, 65), (66, 77), (78, 84), (85, 91), (92, 107), (108, 255)];
for (byte level = 1; level <= dropLevels.Length; level++)
{
var dropItemGroup = context.CreateNew<DropItemGroup>();
dropItemGroup.SetGuid(14, 29, level);
dropItemGroup.ItemLevel = level;
dropItemGroup.PossibleItems.Add(itemDefinition);
dropItemGroup.Chance = 0.003; // 0.3 Percent
dropItemGroup.Description = $"The drop item group for Symbol of Kundun (Level {level})";
(dropItemGroup.MinimumMonsterLevel, dropItemGroup.MaximumMonsterLevel) = dropLevels[level - 1];
gameConfiguration.DropItemGroups.Add(dropItemGroup);
gameConfiguration.Maps.ForEach(map => map.DropItemGroups.Add(dropItemGroup));
}
}
}

View File

@@ -0,0 +1,90 @@
// <copyright file="AddLorenMarketJuliaWarpPlugIn.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 turns Market Union Member Julia (547) into a working warp NPC for the Loren Market,
/// on both sides. It changes her NPC window so that talking to her opens the warp dialog instead of
/// an empty merchant shop, and spawns a second instance of her in Lorencia, which is the entrance.
/// </summary>
/// <remarks>
/// The actual warp is performed server-side by the <c>EnterMarketPlaceHandlerPlugIn</c> when the
/// player uses the 'Warp' button of the (client-side) Julia window.
/// </remarks>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("ed2f1728-b35c-4a3d-810e-eab5b6e12a82")]
public class AddLorenMarketJuliaWarpPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Loren Market Julia warp";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Makes Market Union Member Julia (547) a working warp NPC between Lorencia and the Loren Market, on both sides, instead of an empty merchant.";
private const short JuliaNpcNumber = 547;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddLorenMarketJuliaWarp;
/// <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, 06, 24, 0, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var julia = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == JuliaNpcNumber);
if (julia is null)
{
return default;
}
julia.NpcWindow = NpcWindow.JuliaWarpMarketServer;
julia.MerchantStore = null;
var lorencia = gameConfiguration.Maps.FirstOrDefault(m => m.Number == Lorencia.Number);
if (lorencia is null
|| lorencia.MonsterSpawns.Any(s => s.MonsterDefinition?.Number == JuliaNpcNumber))
{
return default;
}
var juliaSpawn = context.CreateNew<MonsterSpawnArea>();
lorencia.MonsterSpawns.Add(juliaSpawn);
juliaSpawn.SetGuid(JuliaNpcNumber);
juliaSpawn.GameMap = lorencia;
juliaSpawn.MonsterDefinition = julia;
juliaSpawn.Quantity = 1;
juliaSpawn.SpawnTrigger = SpawnTrigger.Automatic;
juliaSpawn.Direction = Direction.SouthEast;
juliaSpawn.X1 = 139;
juliaSpawn.X2 = 139;
juliaSpawn.Y1 = 138;
juliaSpawn.Y2 = 138;
return default;
}
}

View File

@@ -0,0 +1,71 @@
// <copyright file="AddMaximumAllianceSizeUpdatePlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds the <see cref="Stats.MaximumAllianceSize"/> global base attribute
/// (default value 5) to Season 6 game configurations.
/// Older game versions (below season 1) do not support alliances and therefore do not
/// receive this attribute.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("2C2743B0-1305-47BF-85D9-09F6CA64AD54")]
public class AddMaximumAllianceSizeUpdatePlugInSeason6 : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Maximum Alliance Size Attribute (Season 6)";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Adds the MaximumAllianceSize global base attribute with a default value of 5 to the Season 6 game configuration.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddMaximumAllianceSizeSeason6;
/// <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, 04, 03, 18, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
#pragma warning disable CS1998
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
#pragma warning restore CS1998
{
this.AddStatIfNotExists(context, gameConfiguration, Stats.MaximumAllianceSize);
var maximumAllianceSizeDef = gameConfiguration.Attributes.FirstOrDefault(a => a.Id == Stats.MaximumAllianceSize.Id);
if (maximumAllianceSizeDef is null)
{
return;
}
if (!gameConfiguration.GlobalBaseAttributeValues.Any(a => a.Definition?.Id == Stats.MaximumAllianceSize.Id))
{
var maximumAllianceSizeValue = context.CreateNew<ConstValueAttribute>(5f, maximumAllianceSizeDef);
gameConfiguration.GlobalBaseAttributeValues.Add(maximumAllianceSizeValue);
}
}
}

View File

@@ -0,0 +1,88 @@
// <copyright file="AddMissingMerchantStoresPlugIn.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>
/// This update gives a merchant store to a merchant NPC which has a merchant window
/// but no items assigned, so talking to it opens an empty (non-working) shop.
/// </summary>
/// <remarks>
/// Affected NPC: Christine the General Goods Merchant (545) in the Loren Market. She is given a
/// clone of the general-goods (potion girl) store carried by Thompson the Merchant (231).
/// Two other NPCs in that window state are intentionally left out: Moss The Merchant (492) is the
/// gambler and Market Union Member Julia (547) is a warp NPC, so a general-goods store would not
/// fit either of them.
/// </remarks>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("f78d6e1d-1cb5-45f7-912d-54b2cb1220eb")]
public class AddMissingMerchantStoresPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add missing merchant stores";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Gives a general-goods store to Christine the General Goods Merchant (545), which had a shop window but no items, cloned from Thompson the Merchant (231).";
/// <summary>
/// The number of the NPC whose store is cloned for the empty merchants.
/// </summary>
private const short StoreSourceNpcNumber = 231; // Thompson the Merchant - carries the general-goods (potion girl) store
/// <summary>
/// The numbers of the NPCs which have a merchant window but no store.
/// </summary>
private static readonly short[] EmptyMerchantNpcNumbers = [545];
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddMissingMerchantStores;
/// <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, 06, 17, 0, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var storeSource = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == StoreSourceNpcNumber);
if (storeSource?.MerchantStore is null)
{
return default;
}
foreach (var number in EmptyMerchantNpcNumbers)
{
var npc = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == number);
if (npc is null || npc.MerchantStore is not null)
{
continue;
}
npc.MerchantStore = storeSource.MerchantStore.Clone(gameConfiguration);
npc.MerchantStore.SetGuid(npc.Number);
}
return default;
}
}

View File

@@ -0,0 +1,26 @@
// <copyright file="AddMovementSpeedAttributesPlugIn075.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.PlugIns;
/// <summary>
/// Adds movement speed attributes to 0.75 game configurations.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("890E2FCB-EC93-4CC1-84FC-67A1B398D5C8")]
public class AddMovementSpeedAttributesPlugIn075 : AddMovementSpeedAttributesPlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddMovementSpeedAttributes075;
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
protected override int MaximumItemLevel => Version075.Items.Constants.MaximumItemLevel;
}

View File

@@ -0,0 +1,26 @@
// <copyright file="AddMovementSpeedAttributesPlugIn095D.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.PlugIns;
/// <summary>
/// Adds movement speed attributes to 0.95d game configurations.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("7C38C30F-163B-4625-A82D-5C3A0A9ED883")]
public class AddMovementSpeedAttributesPlugIn095D : AddMovementSpeedAttributesPlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddMovementSpeedAttributes095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
protected override int MaximumItemLevel => Version095d.Items.Constants.MaximumItemLevel;
}

View File

@@ -0,0 +1,369 @@
// <copyright file="AddMovementSpeedAttributesPlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using AtlansMap = MUnique.OpenMU.Persistence.Initialization.Version075.Maps.Atlans;
using Doppelgaenger3Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Doppelgaenger3;
using Kalima1Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima1;
using Kalima2Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima2;
using Kalima3Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima3;
using Kalima4Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima4;
using Kalima5Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima5;
using Kalima6Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima6;
using Kalima7Map = MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps.Kalima7;
/// <summary>
/// Adds movement speed attributes and configuration values.
/// </summary>
public abstract class AddMovementSpeedAttributesPlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug-in name.
/// </summary>
internal const string PlugInName = "Add Movement Speed Attributes";
/// <summary>
/// The plug-in description.
/// </summary>
internal const string PlugInDescription = "Adds attribute-based movement speed configuration for players, monsters, items, effects, and underwater maps.";
private const byte PetItemGroup = (byte)ItemGroups.Misc1;
private const byte UniriaNumber = 2;
private const byte DinorantNumber = 3;
private const byte DarkHorseNumber = 4;
private const byte WingsOfDragonNumber = 5;
private const byte WingOfStormNumber = 36;
private const byte FenrirNumber = 37;
private const string RunningMovementSpeedTableName = "Running Movement Speed";
private const int BlackFenrirMovementSpeedCombinationBonusNumber = 101;
private const int BlackFenrirUnderwaterMovementSpeedCombinationBonusNumber = 102;
private const int BlueFenrirMovementSpeedCombinationBonusNumber = 103;
private const int BlueFenrirUnderwaterMovementSpeedCombinationBonusNumber = 104;
private const int GoldFenrirMovementSpeedCombinationBonusNumber = 105;
private const int GoldFenrirUnderwaterMovementSpeedCombinationBonusNumber = 106;
private static readonly short[] UnderwaterMapNumbers =
[
AtlansMap.Number,
Kalima1Map.Number,
Kalima2Map.Number,
Kalima3Map.Number,
Kalima4Map.Number,
Kalima5Map.Number,
Kalima6Map.Number,
Kalima7Map.Number,
Doppelgaenger3Map.Number,
];
/// <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, 05, 15, 20, 0, 0, DateTimeKind.Utc);
/// <summary>
/// Gets the maximum item level of the target game version.
/// </summary>
protected abstract int MaximumItemLevel { get; }
/// <inheritdoc />
protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
this.AddStatIfNotExists(context, gameConfiguration, Stats.MovementSpeed);
this.AddStatIfNotExists(context, gameConfiguration, Stats.MovementSpeedUnderwater);
this.AddStatIfNotExists(context, gameConfiguration, Stats.MovementSpeedFactor);
this.AddStatIfNotExists(context, gameConfiguration, Stats.IsUnderwater);
this.AddGlobalMovementSpeedFactor(context, gameConfiguration);
this.AddEffectMovementSpeedFactors(context, gameConfiguration);
this.AddItemMovementSpeeds(context, gameConfiguration);
this.AddUnderwaterMapPowerUps(context, gameConfiguration);
return ValueTask.CompletedTask;
}
private static bool IsWingSlotItem(ItemDefinition item)
{
return item.ItemSlot?.ItemSlots.Contains(InventoryConstants.WingsSlot) ?? false;
}
private static float GetWingMovementSpeed(ItemDefinition wing)
{
return wing.Number is WingsOfDragonNumber or WingOfStormNumber
? MovementSpeedConstants.FastWingMovementSpeed
: MovementSpeedConstants.DefaultWingMovementSpeed;
}
private static float GetPetMovementSpeed(ItemDefinition pet)
{
return pet.Number switch
{
UniriaNumber or DinorantNumber => MovementSpeedConstants.BasicMountMovementSpeed,
DarkHorseNumber or FenrirNumber => MovementSpeedConstants.HorseOrFenrirMovementSpeed,
_ => 0f,
};
}
private void AddGlobalMovementSpeedFactor(IContext context, GameConfiguration gameConfiguration)
{
if (gameConfiguration.GlobalBaseAttributeValues.Any(a => a.Definition?.Id == Stats.MovementSpeedFactor.Id))
{
return;
}
gameConfiguration.GlobalBaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(1f, Stats.MovementSpeedFactor.GetPersistent(gameConfiguration)));
}
private void AddEffectMovementSpeedFactors(IContext context, GameConfiguration gameConfiguration)
{
foreach (var icedEffect in gameConfiguration.MagicEffects.Where(e => e.Number == (short)MagicEffectNumber.Iced))
{
this.AddMovementSpeedFactorPowerUp(context, gameConfiguration, icedEffect, MovementSpeedConstants.IcedMovementSpeedFactor);
}
var coldEffect = gameConfiguration.MagicEffects.FirstOrDefault(e => e.Number == (short)MagicEffectNumber.Cold);
if (coldEffect is null
&& gameConfiguration.Skills.Any(s => s.Number == (short)SkillNumber.StrikeofDestruction))
{
coldEffect = this.CreateEffect(context, gameConfiguration, ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10);
}
if (coldEffect is not null)
{
this.AddMovementSpeedFactorPowerUp(context, gameConfiguration, coldEffect, MovementSpeedConstants.ColdMovementSpeedFactor);
foreach (var skill in gameConfiguration.Skills.Where(s => s.Number == (short)SkillNumber.StrikeofDestruction || s.Number == (short)SkillNumber.StrikeofDestrStr))
{
skill.MagicEffectDef = coldEffect;
}
}
}
private void AddMovementSpeedFactorPowerUp(IContext context, GameConfiguration gameConfiguration, MagicEffectDefinition magicEffect, float value)
{
if (magicEffect.PowerUpDefinitions.Any(p => p.TargetAttribute?.Id == Stats.MovementSpeedFactor.Id))
{
return;
}
magicEffect.PowerUpDefinitions.Add(this.CreatePowerUpDefinition(context, gameConfiguration, Stats.MovementSpeedFactor, value, AggregateType.Multiplicate));
}
private void AddItemMovementSpeeds(IContext context, GameConfiguration gameConfiguration)
{
var runningMovementSpeedTable = this.GetOrCreateRunningMovementSpeedTable(context, gameConfiguration);
foreach (var boots in gameConfiguration.Items.Where(item => item.Group == (byte)ItemGroups.Boots))
{
this.AddItemBasePowerUp(context, gameConfiguration, boots, Stats.MovementSpeed, 0, AggregateType.Maximum, runningMovementSpeedTable);
}
foreach (var gloves in gameConfiguration.Items.Where(item => item.Group == (byte)ItemGroups.Gloves))
{
this.AddItemBasePowerUp(context, gameConfiguration, gloves, Stats.MovementSpeedUnderwater, 0, AggregateType.Maximum, runningMovementSpeedTable);
}
foreach (var wing in gameConfiguration.Items.Where(IsWingSlotItem))
{
this.AddMovementSpeedPowerUps(context, gameConfiguration, wing, GetWingMovementSpeed(wing));
}
foreach (var pet in gameConfiguration.Items.Where(item => item.Group == PetItemGroup))
{
var speed = GetPetMovementSpeed(pet);
if (speed > 0)
{
this.AddMovementSpeedPowerUps(context, gameConfiguration, pet, speed);
}
}
this.AddFenrirMovementSpeedCombinationBonuses(context, gameConfiguration);
}
private ItemLevelBonusTable GetOrCreateRunningMovementSpeedTable(IContext context, GameConfiguration gameConfiguration)
{
if (gameConfiguration.ItemLevelBonusTables.FirstOrDefault(t => t.Name == RunningMovementSpeedTableName) is { } existingTable)
{
return existingTable;
}
var table = context.CreateNew<ItemLevelBonusTable>();
gameConfiguration.ItemLevelBonusTables.Add(table);
table.Name = RunningMovementSpeedTableName;
table.Description = "Defines the running movement speed for boots and underwater gloves from item level 5.";
for (int level = MovementSpeedConstants.RunningGearMinimumLevel; level <= this.MaximumItemLevel; level++)
{
var levelBonus = context.CreateNew<LevelBonus>();
levelBonus.Level = level;
levelBonus.AdditionalValue = MovementSpeedConstants.RunningGearMovementSpeed;
table.BonusPerLevel.Add(levelBonus);
}
return table;
}
private void AddMovementSpeedPowerUps(IContext context, GameConfiguration gameConfiguration, ItemDefinition item, float speed)
{
this.AddItemBasePowerUp(context, gameConfiguration, item, Stats.MovementSpeed, speed, AggregateType.Maximum);
this.AddItemBasePowerUp(context, gameConfiguration, item, Stats.MovementSpeedUnderwater, speed, AggregateType.Maximum);
}
private void AddItemBasePowerUp(
IContext context,
GameConfiguration gameConfiguration,
ItemDefinition item,
AttributeDefinition targetAttribute,
float value,
AggregateType aggregateType,
ItemLevelBonusTable? bonusPerLevelTable = null)
{
if (item.BasePowerUpAttributes.Any(p => p.TargetAttribute?.Id == targetAttribute.Id))
{
return;
}
var powerUp = context.CreateNew<ItemBasePowerUpDefinition>();
powerUp.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration);
powerUp.BaseValue = value;
powerUp.AggregateType = aggregateType;
powerUp.BonusPerLevelTable = bonusPerLevelTable;
item.BasePowerUpAttributes.Add(powerUp);
}
private void AddFenrirMovementSpeedCombinationBonuses(IContext context, GameConfiguration gameConfiguration)
{
this.AddFenrirMovementSpeedCombinationBonus(context, gameConfiguration, ItemOptionTypes.BlackFenrir, BlackFenrirMovementSpeedCombinationBonusNumber, Stats.MovementSpeed);
this.AddFenrirMovementSpeedCombinationBonus(context, gameConfiguration, ItemOptionTypes.BlackFenrir, BlackFenrirUnderwaterMovementSpeedCombinationBonusNumber, Stats.MovementSpeedUnderwater);
this.AddFenrirMovementSpeedCombinationBonus(context, gameConfiguration, ItemOptionTypes.BlueFenrir, BlueFenrirMovementSpeedCombinationBonusNumber, Stats.MovementSpeed);
this.AddFenrirMovementSpeedCombinationBonus(context, gameConfiguration, ItemOptionTypes.BlueFenrir, BlueFenrirUnderwaterMovementSpeedCombinationBonusNumber, Stats.MovementSpeedUnderwater);
this.AddFenrirMovementSpeedCombinationBonus(context, gameConfiguration, ItemOptionTypes.GoldFenrir, GoldFenrirMovementSpeedCombinationBonusNumber, Stats.MovementSpeed);
this.AddFenrirMovementSpeedCombinationBonus(context, gameConfiguration, ItemOptionTypes.GoldFenrir, GoldFenrirUnderwaterMovementSpeedCombinationBonusNumber, Stats.MovementSpeedUnderwater);
}
private void AddFenrirMovementSpeedCombinationBonus(
IContext context,
GameConfiguration gameConfiguration,
ItemOptionType optionType,
int number,
AttributeDefinition targetAttribute)
{
if (gameConfiguration.ItemOptionTypes.FirstOrDefault(t => t == optionType) is not { } persistentOptionType)
{
return;
}
if (gameConfiguration.ItemOptionCombinationBonuses.Any(b =>
b.Bonus?.TargetAttribute?.Id == targetAttribute.Id
&& b.Requirements.Any(r => r.OptionType == persistentOptionType)))
{
return;
}
var combinationBonus = context.CreateNew<ItemOptionCombinationBonus>();
combinationBonus.Number = number;
combinationBonus.Description = $"{persistentOptionType.Name}: {targetAttribute.Designation}";
combinationBonus.AppliesMultipleTimes = false;
combinationBonus.Requirements.Add(this.CreateFenrirMovementSpeedRequirement(context, persistentOptionType));
combinationBonus.Bonus = this.CreatePowerUpDefinition(context, gameConfiguration, targetAttribute, MovementSpeedConstants.UpgradedFenrirMovementSpeed, AggregateType.Maximum);
gameConfiguration.ItemOptionCombinationBonuses.Add(combinationBonus);
}
private CombinationBonusRequirement CreateFenrirMovementSpeedRequirement(IContext context, ItemOptionType optionType)
{
var requirement = context.CreateNew<CombinationBonusRequirement>();
requirement.OptionType = optionType;
requirement.MinimumCount = 1;
return requirement;
}
private void AddUnderwaterMapPowerUps(IContext context, GameConfiguration gameConfiguration)
{
foreach (var map in gameConfiguration.Maps.Where(m => UnderwaterMapNumbers.Contains(m.Number)))
{
if (map.CharacterPowerUpDefinitions.Any(p => p.TargetAttribute?.Id == Stats.IsUnderwater.Id))
{
continue;
}
map.CharacterPowerUpDefinitions.Add(this.CreatePowerUpDefinition(context, gameConfiguration, Stats.IsUnderwater, 1, AggregateType.AddRaw));
}
}
private PowerUpDefinition CreatePowerUpDefinition(IContext context, GameConfiguration gameConfiguration, AttributeDefinition targetAttribute, float value, AggregateType aggregateType)
{
var powerUp = context.CreateNew<PowerUpDefinition>();
powerUp.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration);
powerUp.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUp.Boost.ConstantValue.Value = value;
powerUp.Boost.ConstantValue.AggregateType = aggregateType;
return powerUp;
}
private MagicEffectDefinition CreateEffect(IContext context, GameConfiguration gameConfiguration, ElementalType type, MagicEffectNumber effectNumber, AttributeDefinition targetAttribute, float durationInSeconds, float chance = 0)
{
if (gameConfiguration.MagicEffects.FirstOrDefault(
e => e.Number == (short)effectNumber
&& e.SubType == (byte)(0xFF - type)
&& Equals(e.Duration?.ConstantValue.Value, durationInSeconds)
&& Equals(e.Chance?.ConstantValue.Value, chance)
&& e.PowerUpDefinitions.FirstOrDefault()?.TargetAttribute == targetAttribute) is { } existingEffect)
{
return existingEffect;
}
var effect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(effect);
effect.Name = Enum.GetName(effectNumber) ?? string.Empty;
effect.InformObservers = true;
effect.Number = (short)effectNumber;
effect.StopByDeath = true;
effect.SubType = (byte)(0xFF - type);
effect.Duration = context.CreateNew<PowerUpDefinitionValue>();
effect.Duration.ConstantValue.Value = durationInSeconds;
var powerUpDefinition = context.CreateNew<PowerUpDefinition>();
effect.PowerUpDefinitions.Add(powerUpDefinition);
powerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = 1;
powerUpDefinition.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration);
if (targetAttribute == Stats.IsIced)
{
var movementSpeedFactorPowerUp = context.CreateNew<PowerUpDefinition>();
effect.PowerUpDefinitions.Add(movementSpeedFactorPowerUp);
movementSpeedFactorPowerUp.Boost = context.CreateNew<PowerUpDefinitionValue>();
movementSpeedFactorPowerUp.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
movementSpeedFactorPowerUp.TargetAttribute = Stats.MovementSpeedFactor.GetPersistent(gameConfiguration);
if (effectNumber == MagicEffectNumber.Cold)
{
movementSpeedFactorPowerUp.Boost.ConstantValue.Value = MovementSpeedConstants.ColdMovementSpeedFactor;
}
else
{
movementSpeedFactorPowerUp.Boost.ConstantValue.Value = MovementSpeedConstants.IcedMovementSpeedFactor;
}
}
if (chance > 0)
{
effect.Chance = context.CreateNew<PowerUpDefinitionValue>();
effect.Chance.ConstantValue.Value = chance;
}
return effect;
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="AddMovementSpeedAttributesPlugInSeason6.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.PlugIns;
/// <summary>
/// Adds movement speed attributes to season 6 game configurations.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("1D4968DA-9C9C-42A7-AF80-D4811535EC63")]
public class AddMovementSpeedAttributesPlugInSeason6 : AddMovementSpeedAttributesPlugInBase
{
private const int SeasonSixMaximumItemLevel = 15;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddMovementSpeedAttributesSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
protected override int MaximumItemLevel => SeasonSixMaximumItemLevel;
}

View File

@@ -0,0 +1,63 @@
// <copyright file="AddPointsPerResetAttributePlugIn.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;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds the <see cref="Stats.PointsPerReset"/>.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("6011A1B8-7FA5-48EB-935D-EEAF83017799")]
public class AddPointsPerResetAttributePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Adds attribute PointsPerReset";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds the attribute PointsPerReset.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddPointsPerResetByClassAttribute;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 06, 09, 13, 30, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
if (gameConfiguration.Attributes.Contains(Stats.PointsPerReset))
{
return;
}
var attribute = Stats.PointsPerReset;
var persistentAttribute = context.CreateNew<AttributeDefinition>(attribute.Id, attribute.Designation, attribute.Description);
gameConfiguration.Attributes.Add(persistentAttribute);
}
}

View File

@@ -0,0 +1,59 @@
// <copyright file="AddProjectileCountToTripleShotUpdatePlugIn.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.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Updates the Triple Shot skill to use 3 projectiles for proper arrow direction handling.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("E3A8F7C9-2D4B-4A1E-9F3C-8B5D7A6C1E4F")]
public class AddProjectileCountToTripleShotUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Projectile Count to Triple Shot";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Adds the projectile count of 3 to the Triple Shot skill to properly handle arrow directions.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddProjectileCountToTripleShot;
/// <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, 01, 04, 11, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var tripleShotSkill = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.TripleShot);
if (tripleShotSkill?.AreaSkillSettings is { } areaSkillSettings)
{
areaSkillSettings.ProjectileCount = 3;
}
await Task.CompletedTask.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,69 @@
// <copyright file="AddQuestItemLimitPlugIn.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.DataModel.Configuration.Items;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This updates adds the new <see cref="ItemDefinition.StorageLimitPerCharacter"/> for quest items.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("48D40F2E-2844-4058-B1FA-710EEE55157B")]
public class AddQuestItemLimitPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add quest item limits";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds limits to quest items, so that only one item can be picked up.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddQuestItemLimit;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2023, 05, 04, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var hashSet = new HashSet<short>
{
Quest.BrokenSwordNumber,
Quest.EyeOfAbyssalNumber,
Quest.FeatherOfDarkPhoenixNumber,
Quest.FlameOfDeathBeamKnightNumber,
Quest.HornOfHellMaineNumber,
Quest.ScrollOfEmperorNumber,
Quest.SoulShardOfWizardNumber,
Quest.TearOfElfNumber,
};
var questItems = gameConfiguration.Items.Where(item => item.Group == 14 && hashSet.Contains(item.Number));
foreach (var item in questItems)
{
item.StorageLimitPerCharacter = 1;
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="AddRandomExperienceConfigAttributesPlugIn075.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.PlugIns;
/// <summary>
/// This update adds the random experience config attributes for version 0.75.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("5F412933-CC0F-483B-B6AE-7B358A6257FD")]
public class AddRandomExperienceConfigAttributesPlugIn075 : AddRandomExperienceConfigAttributesPlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddRandomExperienceConfigAttributes075;
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
}

View File

@@ -0,0 +1,23 @@
// <copyright file="AddRandomExperienceConfigAttributesPlugIn095d.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.PlugIns;
/// <summary>
/// This update adds the random experience config attributes for version 0.95d.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("9A166583-C3E7-4E04-924C-F01FF9840974")]
public class AddRandomExperienceConfigAttributesPlugIn095d : AddRandomExperienceConfigAttributesPlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddRandomExperienceConfigAttributes095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
}

View File

@@ -0,0 +1,71 @@
// <copyright file="AddRandomExperienceConfigAttributesPlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// This update adds the random experience config attributes to the database
/// (RandomExperienceMinMultiplier, RandomExperienceMaxMultiplier)
/// and assigns their default values to global base attributes.
/// </summary>
public abstract class AddRandomExperienceConfigAttributesPlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plugin name.
/// </summary>
internal const string PlugInName = "Add Random Experience Configuration Attributes";
/// <summary>
/// The plugin description.
/// </summary>
internal const string PlugInDescription = "Adds new random experience Configuration attributes to the game configuration.";
/// <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, 04, 27, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var attributesToAdd = new[]
{
Stats.RandomExperienceMinMultiplier,
Stats.RandomExperienceMaxMultiplier,
};
foreach (var attr in attributesToAdd)
{
if (!gameConfiguration.Attributes.Any(a => a.Id == attr.Id))
{
var newAttr = context.CreateNew<AttributeDefinition>(attr.Id, attr.Designation, attr.Description);
gameConfiguration.Attributes.Add(newAttr);
}
}
var randMin = gameConfiguration.Attributes.First(a => a.Id == Stats.RandomExperienceMinMultiplier.Id);
var randMax = gameConfiguration.Attributes.First(a => a.Id == Stats.RandomExperienceMaxMultiplier.Id);
if (!gameConfiguration.GlobalBaseAttributeValues.Any(a => a.Definition?.Id == randMin.Id))
{
gameConfiguration.GlobalBaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(0.8f, randMin));
}
if (!gameConfiguration.GlobalBaseAttributeValues.Any(a => a.Definition?.Id == randMax.Id))
{
gameConfiguration.GlobalBaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(1.2f, randMax));
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="AddRandomExperienceConfigAttributesPlugInSeason6.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.PlugIns;
/// <summary>
/// This update adds the random experience config attributes for season 6.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("D1DC70A2-2614-4CC0-81C0-6C8253781019")]
public class AddRandomExperienceConfigAttributesPlugInSeason6 : AddRandomExperienceConfigAttributesPlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddRandomExperienceConfigAttributesSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
}

View File

@@ -0,0 +1,485 @@
// <copyright file="AddSummonerBuffSkillsPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds the Sleep, Innovation, Damage Reflection and Weakness Summoner buff skills. It also fixes the 3rd wing full reflect option.
/// </summary>
[Display(Name = PlugInName, Description = PlugInDescription)]
[PlugIn]
[Guid("B1E2D6C3-1F4A-4D7C-8C2E-3F6D9A7B8E2F")]
public class AddSummonerBuffSkillsPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Summoner Buff Skills";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds the Sleep, Innovation, Damage Reflection and Weakness Summoner buff skills. It also fixes the 3rd wing full reflect option.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddSummonerBuffSkills;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 12, 29, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Add new attributes
var innovationDefDecrement = context.CreateNew<AttributeDefinition>(Stats.InnovationDefDecrement.Id, Stats.InnovationDefDecrement.Designation, Stats.InnovationDefDecrement.Description);
gameConfiguration.Attributes.Add(innovationDefDecrement);
var isAsleep = context.CreateNew<AttributeDefinition>(Stats.IsAsleep.Id, Stats.IsAsleep.Designation, Stats.IsAsleep.Description);
gameConfiguration.Attributes.Add(isAsleep);
var fullyReflectDamageAfterHitChance = context.CreateNew<AttributeDefinition>(Stats.FullyReflectDamageAfterHitChance.Id, Stats.FullyReflectDamageAfterHitChance.Designation, Stats.FullyReflectDamageAfterHitChance.Description);
gameConfiguration.Attributes.Add(fullyReflectDamageAfterHitChance);
// Fix reflect excellent option
var excDefenseOptionsId = new Guid("00000083-0012-0000-0000-000000000000");
if (gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == excDefenseOptionsId) is { } excDefenseOptions)
{
if (excDefenseOptions.PossibleOptions.FirstOrDefault(p => p.PowerUpDefinition?.TargetAttribute == Stats.DamageReflection) is { } dmgReflection)
{
dmgReflection.PowerUpDefinition!.Boost!.ConstantValue.Value = 0.05f;
}
}
// Update Weakness magic effect
if (gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.Weakness) is { } weaknessEffect)
{
weaknessEffect.Chance = context.CreateNew<PowerUpDefinitionValue>();
weaknessEffect.Chance.ConstantValue.Value = 0.1f; // 10%
}
var innovationEffect = this.CreateInnovationMagicEffect(context, gameConfiguration);
var reflectionEffect = this.CreateReflectionMagicEffect(context, gameConfiguration);
var sleepEffect = this.CreateSleepMagicEffect(context, gameConfiguration);
var weaknessSummonerEffect = this.CreateWeaknessSummonerMagicEffect(context, gameConfiguration);
// Update 3rd wing reflect option
var thirWingOptionDefId = new Guid("00000083-0067-0000-0000-000000000000");
if (gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == thirWingOptionDefId) is { } thirWingOptionDef
&& thirWingOptionDef.PossibleOptions.FirstOrDefault(po => po.PowerUpDefinition?.TargetAttribute == Stats.DamageReflection) is { } reflectOpt
&& reflectOpt.PowerUpDefinition is not null)
{
reflectOpt.PowerUpDefinition.TargetAttribute = fullyReflectDamageAfterHitChance;
}
// Update existing skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Innovation) is { } innovation)
{
innovation.MagicEffectDef = innovationEffect;
innovation.SkillType = SkillType.Buff;
innovation.AreaSkillSettings =
this.AddAreaSkillSettings(context, false, 0, 0, 0, maximumHitsPerAttack: 5, useTargetAreaFilter: true, targetAreaDiameter: 10);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DamageReflection) is { } damageReflection)
{
damageReflection.MagicEffectDef = reflectionEffect;
damageReflection.SkillType = SkillType.Buff;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Sleep) is { } sleep)
{
sleep.MagicEffectDef = sleepEffect;
sleep.SkillType = SkillType.Buff;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Weakness) is { } weakness)
{
weakness.MagicEffectDef = weaknessSummonerEffect;
weakness.SkillType = SkillType.Buff;
weakness.AreaSkillSettings =
this.AddAreaSkillSettings(context, false, 0, 0, 0, maximumHitsPerAttack: 5, useTargetAreaFilter: true, targetAreaDiameter: 10);
}
}
private MagicEffectDefinition CreateInnovationMagicEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.Innovation;
magicEffect.Name = "Innovation Effect";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
magicEffect.DurationDependsOnTargetLevel = true;
magicEffect.MonsterTargetLevelDivisor = 20;
magicEffect.PlayerTargetLevelDivisor = 150;
// Chance % = 32 + (Energy / 50) + (Book Rise / 6)
magicEffect.Chance = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Chance.ConstantValue.Value = 0.32f; // 32%
var chancePerEnergy = context.CreateNew<AttributeRelationship>();
chancePerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
chancePerEnergy.InputOperator = InputOperator.Multiply;
chancePerEnergy.InputOperand = 1f / 5000f; // 50 energy adds 1% chance
magicEffect.Chance.RelatedValues.Add(chancePerEnergy);
var chancePerBookRise = context.CreateNew<AttributeRelationship>();
chancePerBookRise.InputAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
chancePerBookRise.InputOperator = InputOperator.Multiply;
chancePerBookRise.InputOperand = 1f / 600f; // 6 book rise adds 1% chance
magicEffect.Chance.RelatedValues.Add(chancePerBookRise);
// Chance PvP % = 17 + (Energy / 50) + (Book Rise / 6)
magicEffect.ChancePvp = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.ChancePvp.ConstantValue.Value = 0.17f; // 17%
var chancePerEnergyPvp = context.CreateNew<AttributeRelationship>();
chancePerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
chancePerEnergyPvp.InputOperator = InputOperator.Multiply;
chancePerEnergyPvp.InputOperand = 1f / 5000f; // 50 energy adds 1% chance
magicEffect.ChancePvp.RelatedValues.Add(chancePerEnergyPvp);
var chancePerBookRisePvp = context.CreateNew<AttributeRelationship>();
chancePerBookRisePvp.InputAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
chancePerBookRisePvp.InputOperator = InputOperator.Multiply;
chancePerBookRisePvp.InputOperand = 1f / 600f; // 6 book rise adds 1% chance
magicEffect.ChancePvp.RelatedValues.Add(chancePerBookRisePvp);
// Duration = 4 + (Energy / 100)
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 4; // 4 Seconds
magicEffect.Duration.MaximumValue = 44; // 44 Seconds (based on 4k total energy cap)
var durationPerEnergy = context.CreateNew<AttributeRelationship>();
durationPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergy.InputOperator = InputOperator.Multiply;
durationPerEnergy.InputOperand = 1f / 100f; // 100 energy adds 1s
magicEffect.Duration.RelatedValues.Add(durationPerEnergy);
magicEffect.DurationPvp = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.DurationPvp.ConstantValue.Value = 5; // 5 Seconds
magicEffect.DurationPvp.MaximumValue = 18; // 18 Seconds (based on 4k total energy cap)
// Duration PvP = 5 + (Energy / 300) + ((Level - Target's Level) / 150)
var durationPerEnergyPvp = context.CreateNew<AttributeRelationship>();
durationPerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergyPvp.InputOperator = InputOperator.Multiply;
durationPerEnergyPvp.InputOperand = 1f / 300f; // 300 energy adds 1s
magicEffect.DurationPvp.RelatedValues.Add(durationPerEnergyPvp);
var durationPerLevelPvp = context.CreateNew<AttributeRelationship>();
durationPerLevelPvp.InputAttribute = Stats.Level.GetPersistent(gameConfiguration);
durationPerLevelPvp.InputOperator = InputOperator.Multiply;
durationPerLevelPvp.InputOperand = 1f / 150f; // 150 levels adds 1s
magicEffect.DurationPvp.RelatedValues.Add(durationPerLevelPvp);
// Defense decrease % (applies last) = 20 + (Energy / 90)
var decDefPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(decDefPowerUpDefinition);
decDefPowerUpDefinition.TargetAttribute = Stats.InnovationDefDecrement.GetPersistent(gameConfiguration);
decDefPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDefPowerUpDefinition.Boost.ConstantValue.Value = 0.20f; // 20% decrease
decDefPowerUpDefinition.Boost.MaximumValue = 0.64f; // 64% decrease (based on 4k total energy cap)
var decDefPerEnergy = context.CreateNew<AttributeRelationship>();
decDefPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
decDefPerEnergy.InputOperator = InputOperator.Multiply;
decDefPerEnergy.InputOperand = 1f / 9000f; // 90 energy further decreases 0.01
decDefPowerUpDefinition.Boost.RelatedValues.Add(decDefPerEnergy);
// Defense decrease PvP % (applies last) = 12 + (Energy / 110)
var decDefPowerUpDefinitionPvp = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitionsPvp.Add(decDefPowerUpDefinitionPvp);
decDefPowerUpDefinitionPvp.TargetAttribute = Stats.InnovationDefDecrement.GetPersistent(gameConfiguration);
decDefPowerUpDefinitionPvp.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDefPowerUpDefinitionPvp.Boost.ConstantValue.Value = 0.12f; // 12% decrease
decDefPowerUpDefinitionPvp.Boost.MaximumValue = 0.48f; // 48% decrease (based on 4k total energy cap)
var decDefPerEnergyPvp = context.CreateNew<AttributeRelationship>();
decDefPerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
decDefPerEnergyPvp.InputOperator = InputOperator.Multiply;
decDefPerEnergyPvp.InputOperand = 1f / 11000f; // 110 energy further decreases 0.01
decDefPowerUpDefinitionPvp.Boost.RelatedValues.Add(decDefPerEnergyPvp);
return magicEffect;
}
private MagicEffectDefinition CreateReflectionMagicEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.Reflection;
magicEffect.Name = "Reflection Effect";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
// Duration = 30 + (Energy / 24)
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 30; // 30 Seconds
magicEffect.Duration.MaximumValue = 180;
var durationPerEnergy = context.CreateNew<AttributeRelationship>();
durationPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergy.InputOperator = InputOperator.Multiply;
durationPerEnergy.InputOperand = 1f / 24; // 24 energy adds 1s
magicEffect.Duration.RelatedValues.Add(durationPerEnergy);
// Reflection % = 30 + (Energy / 42)
var incReflectPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(incReflectPowerUpDefinition);
incReflectPowerUpDefinition.TargetAttribute = Stats.DamageReflection.GetPersistent(gameConfiguration);
incReflectPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
incReflectPowerUpDefinition.Boost.ConstantValue.Value = 0.3f; // 30% increase
incReflectPowerUpDefinition.Boost.MaximumValue = 0.6f;
var incReflectPerEnergy = context.CreateNew<AttributeRelationship>();
incReflectPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
incReflectPerEnergy.InputOperator = InputOperator.Multiply;
incReflectPerEnergy.InputOperand = 1f / 4200f; // 42 energy further increases 0.01
incReflectPowerUpDefinition.Boost.RelatedValues.Add(incReflectPerEnergy);
return magicEffect;
}
private MagicEffectDefinition CreateSleepMagicEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.Sleep;
magicEffect.Name = "Sleep Effect";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
magicEffect.DurationDependsOnTargetLevel = true;
magicEffect.MonsterTargetLevelDivisor = 20;
magicEffect.PlayerTargetLevelDivisor = 100;
// Chance % = 20 + (Energy / 30) + (Book Rise / 6)
magicEffect.Chance = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Chance.ConstantValue.Value = 0.2f; // 20%
var chancePerEnergy = context.CreateNew<AttributeRelationship>();
chancePerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
chancePerEnergy.InputOperator = InputOperator.Multiply;
chancePerEnergy.InputOperand = 1f / 3000f; // 30 energy adds 1% chance
magicEffect.Chance.RelatedValues.Add(chancePerEnergy);
var chancePerBookRise = context.CreateNew<AttributeRelationship>();
chancePerBookRise.InputAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
chancePerBookRise.InputOperator = InputOperator.Multiply;
chancePerBookRise.InputOperand = 1f / 600f; // 6 book rise adds 1% chance
magicEffect.Chance.RelatedValues.Add(chancePerBookRise);
// Chance PvP % = 15 + (Energy / 37) + (Book Rise / 6)
magicEffect.ChancePvp = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.ChancePvp.ConstantValue.Value = 0.15f; // 15%
var chancePerEnergyPvp = context.CreateNew<AttributeRelationship>();
chancePerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
chancePerEnergyPvp.InputOperator = InputOperator.Multiply;
chancePerEnergyPvp.InputOperand = 1f / 3700f; // 37 energy adds 1% chance
magicEffect.ChancePvp.RelatedValues.Add(chancePerEnergyPvp);
var chancePerBookRisePvp = context.CreateNew<AttributeRelationship>();
chancePerBookRisePvp.InputAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
chancePerBookRisePvp.InputOperator = InputOperator.Multiply;
chancePerBookRisePvp.InputOperand = 1f / 600f; // 6 book rise adds 1% chance
magicEffect.ChancePvp.RelatedValues.Add(chancePerBookRisePvp);
// Duration = 5 + (Energy / 100)
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 5; // 5 Seconds
magicEffect.Duration.MaximumValue = 20; // 20 Seconds
var durationPerEnergy = context.CreateNew<AttributeRelationship>();
durationPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergy.InputOperator = InputOperator.Multiply;
durationPerEnergy.InputOperand = 1f / 100f; // 100 energy adds 1s
magicEffect.Duration.RelatedValues.Add(durationPerEnergy);
// Duration = 4 + (Energy / 250) + ((Level - Target's Level) / 100)
magicEffect.DurationPvp = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.DurationPvp.ConstantValue.Value = 4; // 4 Seconds
magicEffect.DurationPvp.MaximumValue = 10; // 10 Seconds
var durationPerEnergyPvp = context.CreateNew<AttributeRelationship>();
durationPerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergyPvp.InputOperator = InputOperator.Multiply;
durationPerEnergyPvp.InputOperand = 1f / 250f; // 250 energy adds 1s
magicEffect.DurationPvp.RelatedValues.Add(durationPerEnergyPvp);
var durationPerLevelPvp = context.CreateNew<AttributeRelationship>();
durationPerLevelPvp.InputAttribute = Stats.Level.GetPersistent(gameConfiguration);
durationPerLevelPvp.InputOperator = InputOperator.Multiply;
durationPerLevelPvp.InputOperand = 1f / 100f; // 100 levels adds 1s
magicEffect.DurationPvp.RelatedValues.Add(durationPerLevelPvp);
var isAsleep = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(isAsleep);
isAsleep.TargetAttribute = Stats.IsAsleep.GetPersistent(gameConfiguration);
isAsleep.Boost = context.CreateNew<PowerUpDefinitionValue>();
isAsleep.Boost.ConstantValue.Value = 1;
return magicEffect;
}
private MagicEffectDefinition CreateWeaknessSummonerMagicEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.Weakness; // We will map skill to effect by hand in this update, so we use this number instead of WeaknessSummoner
magicEffect.Name = "Weakness Effect (Summoner)";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
magicEffect.DurationDependsOnTargetLevel = true;
magicEffect.MonsterTargetLevelDivisor = 20;
magicEffect.PlayerTargetLevelDivisor = 150;
// Chance % = 32 + (Energy / 50) + (Book Rise / 6)
magicEffect.Chance = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Chance.ConstantValue.Value = 0.32f; // 32%
var chancePerEnergy = context.CreateNew<AttributeRelationship>();
chancePerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
chancePerEnergy.InputOperator = InputOperator.Multiply;
chancePerEnergy.InputOperand = 1f / 5000f; // 50 energy adds 1% chance
magicEffect.Chance.RelatedValues.Add(chancePerEnergy);
var chancePerBookRise = context.CreateNew<AttributeRelationship>();
chancePerBookRise.InputAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
chancePerBookRise.InputOperator = InputOperator.Multiply;
chancePerBookRise.InputOperand = 1f / 600f; // 6 book rise adds 1% chance
magicEffect.Chance.RelatedValues.Add(chancePerBookRise);
// Chance % = 17 + (Energy / 50) + (Book Rise / 6)
magicEffect.ChancePvp = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.ChancePvp.ConstantValue.Value = 0.17f; // 17%
var chancePerEnergyPvp = context.CreateNew<AttributeRelationship>();
chancePerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
chancePerEnergyPvp.InputOperator = InputOperator.Multiply;
chancePerEnergyPvp.InputOperand = 1f / 5000f; // 50 energy adds 1% chance
magicEffect.ChancePvp.RelatedValues.Add(chancePerEnergyPvp);
var chancePerBookRisePvp = context.CreateNew<AttributeRelationship>();
chancePerBookRisePvp.InputAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
chancePerBookRisePvp.InputOperator = InputOperator.Multiply;
chancePerBookRisePvp.InputOperand = 1f / 600f; // 6 book rise adds 1% chance
magicEffect.ChancePvp.RelatedValues.Add(chancePerBookRisePvp);
// Duration = 4 + (Energy / 100)
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 4; // 4 Seconds
magicEffect.Duration.MaximumValue = 44; // 44 Seconds (based on 4k total energy cap)
var durationPerEnergy = context.CreateNew<AttributeRelationship>();
durationPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergy.InputOperator = InputOperator.Multiply;
durationPerEnergy.InputOperand = 1f / 100f; // 100 energy adds 1s
magicEffect.Duration.RelatedValues.Add(durationPerEnergy);
// Duration = 5 + (Energy / 300) + ((Level - Target's Level) / 150)
magicEffect.DurationPvp = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.DurationPvp.ConstantValue.Value = 5; // 5 Seconds
magicEffect.DurationPvp.MaximumValue = 18; // 18 Seconds (based on 4k total energy cap)
var durationPerEnergyPvp = context.CreateNew<AttributeRelationship>();
durationPerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergyPvp.InputOperator = InputOperator.Multiply;
durationPerEnergyPvp.InputOperand = 1f / 300f; // 300 energy adds 1s
magicEffect.DurationPvp.RelatedValues.Add(durationPerEnergyPvp);
var durationPerLevelPvp = context.CreateNew<AttributeRelationship>();
durationPerLevelPvp.InputAttribute = Stats.Level.GetPersistent(gameConfiguration);
durationPerLevelPvp.InputOperator = InputOperator.Multiply;
durationPerLevelPvp.InputOperand = 1f / 150f; // 150 levels adds 1s
magicEffect.DurationPvp.RelatedValues.Add(durationPerLevelPvp);
// Phys damage decrease % = 4 + (Energy / 58)
var decDmgPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(decDmgPowerUpDefinition);
decDmgPowerUpDefinition.TargetAttribute = Stats.WeaknessPhysDmgDecrement.GetPersistent(gameConfiguration);
decDmgPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDmgPowerUpDefinition.Boost.ConstantValue.Value = 0.04f; // 4% decrease
decDmgPowerUpDefinition.Boost.MaximumValue = 0.73f; // 73% decrease (based on 4k total energy cap)
var decDmgPerEnergy = context.CreateNew<AttributeRelationship>();
decDmgPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
decDmgPerEnergy.InputOperator = InputOperator.Multiply;
decDmgPerEnergy.InputOperand = 1f / 5800f; // 58 energy further decreases 0.01
decDmgPowerUpDefinition.Boost.RelatedValues.Add(decDmgPerEnergy);
// Phys damage decrease PvP % = 3 + (Energy / 93)
var decDmgPowerUpDefinitionPvp = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitionsPvp.Add(decDmgPowerUpDefinitionPvp);
decDmgPowerUpDefinitionPvp.TargetAttribute = Stats.WeaknessPhysDmgDecrement.GetPersistent(gameConfiguration);
decDmgPowerUpDefinitionPvp.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDmgPowerUpDefinitionPvp.Boost.ConstantValue.Value = 0.03f; // 3% decrease
decDmgPowerUpDefinitionPvp.Boost.MaximumValue = 0.46f; // 46% decrease (based on 4k total energy cap)
var decDmgPerEnergyPvp = context.CreateNew<AttributeRelationship>();
decDmgPerEnergyPvp.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
decDmgPerEnergyPvp.InputOperator = InputOperator.Multiply;
decDmgPerEnergyPvp.InputOperand = 1f / 9300f; // 93 energy further decreases 0.01
decDmgPowerUpDefinitionPvp.Boost.RelatedValues.Add(decDmgPerEnergyPvp);
return magicEffect;
}
private AreaSkillSettings AddAreaSkillSettings(
IContext context,
bool useFrustumFilter,
float frustumStartWidth,
float frustumEndWidth,
float frustumDistance,
bool useDeferredHits = false,
TimeSpan delayPerOneDistance = default,
TimeSpan delayBetweenHits = default,
int minimumHitsPerTarget = 1,
int maximumHitsPerTarget = 1,
int maximumHitsPerAttack = default,
float hitChancePerDistanceMultiplier = 1.0f,
bool useTargetAreaFilter = false,
float targetAreaDiameter = default)
{
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
areaSkillSettings.UseFrustumFilter = useFrustumFilter;
areaSkillSettings.FrustumStartWidth = frustumStartWidth;
areaSkillSettings.FrustumEndWidth = frustumEndWidth;
areaSkillSettings.FrustumDistance = frustumDistance;
areaSkillSettings.UseTargetAreaFilter = useTargetAreaFilter;
areaSkillSettings.TargetAreaDiameter = targetAreaDiameter;
areaSkillSettings.UseDeferredHits = useDeferredHits;
areaSkillSettings.DelayPerOneDistance = delayPerOneDistance;
areaSkillSettings.DelayBetweenHits = delayBetweenHits;
areaSkillSettings.MinimumNumberOfHitsPerTarget = minimumHitsPerTarget;
areaSkillSettings.MaximumNumberOfHitsPerTarget = maximumHitsPerTarget;
areaSkillSettings.MaximumNumberOfHitsPerAttack = maximumHitsPerAttack;
areaSkillSettings.HitChancePerDistanceMultiplier = hitChancePerDistanceMultiplier;
return areaSkillSettings;
}
}

View File

@@ -0,0 +1,187 @@
// <copyright file="AddWhiteWizardInvasionMobsUpdatePlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Adds the White Wizard invasion monsters (135-137) and their drop groups
/// to existing databases where <see cref="VersionSeasonSix.InvasionMobsInitialization"/>
/// has already run but before White Wizard monsters were defined.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("D8F4E2C0-5A6B-4C3D-9E7F-1B2A4C6D8E0F")]
public class AddWhiteWizardInvasionMobsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// Gets the plugin name.
/// </summary>
internal const string PlugInName = "Add White Wizard Invasion Monsters";
/// <summary>
/// Gets the plugin description.
/// </summary>
internal const string PlugInDescription = "Adds White Wizard (135), Destructive Ogre Soldier (136), and Destructive Ogre Archer (137) and drop groups for existing databases.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddWhiteWizardInvasionMobs;
/// <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, 07, 05, 0, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
if (gameConfiguration.Monsters.Any(m => m.Number == 135))
{
return ValueTask.CompletedTask;
}
this.AddWhiteWizard(context, gameConfiguration);
this.AddDestructiveOgreSoldier(context, gameConfiguration);
this.AddDestructiveOgreArcher(context, gameConfiguration);
return ValueTask.CompletedTask;
}
private void AddWhiteWizard(IContext context, GameConfiguration gameConfiguration)
{
var monster = context.CreateNew<MonsterDefinition>();
gameConfiguration.Monsters.Add(monster);
monster.Number = 135;
monster.Designation = "White Wizard";
monster.MoveRange = 4;
monster.AttackRange = 5;
monster.AttackSkill = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
monster.ViewRange = 6;
monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond);
monster.AttackDelay = new TimeSpan(1400 * TimeSpan.TicksPerMillisecond);
monster.RespawnDelay = new TimeSpan(10 * TimeSpan.TicksPerSecond);
monster.Attribute = 2;
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 87 },
{ Stats.MaximumHealth, 26000 },
{ Stats.MinimumPhysBaseDmg, 370 },
{ Stats.MaximumPhysBaseDmg, 410 },
{ Stats.DefenseBase, 400 },
{ Stats.AttackRatePvm, 550 },
{ Stats.DefenseRatePvm, 200 },
{ Stats.IceResistance, 15f / 255 },
{ Stats.PoisonResistance, 15f / 255 },
{ Stats.LightningResistance, 20f / 255 },
{ Stats.FireResistance, 15f / 255 },
};
monster.AddAttributes(attributes, context, gameConfiguration);
var itemDrop = context.CreateNew<DropItemGroup>();
itemDrop.Chance = 1.0;
itemDrop.Description = "Jewel of Bless from White Wizard";
itemDrop.Monster = monster;
itemDrop.PossibleItems.Add(gameConfiguration.Items.First(item => item.Number == ItemConstants.JewelOfBless.Number && item.Group == ItemConstants.JewelOfBless.Group));
monster.DropItemGroups.Add(itemDrop);
gameConfiguration.DropItemGroups.Add(itemDrop);
}
private void AddDestructiveOgreSoldier(IContext context, GameConfiguration gameConfiguration)
{
var monster = context.CreateNew<MonsterDefinition>();
gameConfiguration.Monsters.Add(monster);
monster.Number = 136;
monster.Designation = "Destructive Ogre Soldier";
monster.MoveRange = 3;
monster.AttackRange = 1;
monster.AttackSkill = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
monster.ViewRange = 3;
monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond);
monster.AttackDelay = new TimeSpan(1400 * TimeSpan.TicksPerMillisecond);
monster.RespawnDelay = new TimeSpan(10 * TimeSpan.TicksPerSecond);
monster.Attribute = 2;
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 70 },
{ Stats.MaximumHealth, 9500 },
{ Stats.MinimumPhysBaseDmg, 210 },
{ Stats.MaximumPhysBaseDmg, 240 },
{ Stats.DefenseBase, 180 },
{ Stats.AttackRatePvm, 400 },
{ Stats.DefenseRatePvm, 125 },
{ Stats.IceResistance, 7f / 255 },
{ Stats.PoisonResistance, 7f / 255 },
{ Stats.LightningResistance, 7f / 255 },
{ Stats.FireResistance, 7f / 255 },
};
monster.AddAttributes(attributes, context, gameConfiguration);
var itemDrop = context.CreateNew<DropItemGroup>();
itemDrop.Chance = 0.8;
itemDrop.Description = "Wizard's Ring from Destructive Ogre Soldier";
itemDrop.Monster = monster;
itemDrop.PossibleItems.Add(gameConfiguration.Items.First(item => item.Number == ItemConstants.WizardsRing.Number && item.Group == ItemConstants.WizardsRing.Group));
monster.DropItemGroups.Add(itemDrop);
gameConfiguration.DropItemGroups.Add(itemDrop);
}
private void AddDestructiveOgreArcher(IContext context, GameConfiguration gameConfiguration)
{
var monster = context.CreateNew<MonsterDefinition>();
gameConfiguration.Monsters.Add(monster);
monster.Number = 137;
monster.Designation = "Destructive Ogre Archer";
monster.MoveRange = 3;
monster.AttackRange = 5;
monster.AttackSkill = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MonsterSkill);
monster.ViewRange = 5;
monster.MoveDelay = new TimeSpan(400 * TimeSpan.TicksPerMillisecond);
monster.AttackDelay = new TimeSpan(1600 * TimeSpan.TicksPerMillisecond);
monster.RespawnDelay = new TimeSpan(10 * TimeSpan.TicksPerSecond);
monster.Attribute = 2;
monster.NumberOfMaximumItemDrops = 1;
var attributes = new Dictionary<AttributeDefinition, float>
{
{ Stats.Level, 74 },
{ Stats.MaximumHealth, 12000 },
{ Stats.MinimumPhysBaseDmg, 220 },
{ Stats.MaximumPhysBaseDmg, 260 },
{ Stats.DefenseBase, 190 },
{ Stats.AttackRatePvm, 440 },
{ Stats.DefenseRatePvm, 130 },
{ Stats.IceResistance, 8f / 255 },
{ Stats.PoisonResistance, 8f / 255 },
{ Stats.LightningResistance, 8f / 255 },
{ Stats.FireResistance, 8f / 255 },
};
monster.AddAttributes(attributes, context, gameConfiguration);
var itemDrop = context.CreateNew<DropItemGroup>();
itemDrop.Chance = 0.8;
itemDrop.Description = "Wizard's Ring from Destructive Ogre Archer";
itemDrop.Monster = monster;
itemDrop.PossibleItems.Add(gameConfiguration.Items.First(item => item.Number == ItemConstants.WizardsRing.Number && item.Group == ItemConstants.WizardsRing.Group));
monster.DropItemGroups.Add(itemDrop);
gameConfiguration.DropItemGroups.Add(itemDrop);
}
}

View File

@@ -0,0 +1,59 @@
// <copyright file="ChainLightningUpdatePlugIn.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.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update sets the right settings for the chain lightning skill.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("039D09CB-283C-4CBD-ABBC-FFD3F7D5C62F")]
public class ChainLightningUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Chain Lightning skill";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update sets the right settings for the chain lightning skill.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.ChainLightningUpdate;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 07, 15, 18, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var chainLightning = gameConfiguration.Skills.First(s => s.Number == (int)SkillNumber.ChainLightning);
chainLightning.SkillType = SkillType.AreaSkillExplicitTarget;
chainLightning.Target = SkillTarget.Explicit;
var chainLightningStr = gameConfiguration.Skills.First(s => s.Number == (int)SkillNumber.ChainLightningStr);
chainLightningStr.SkillType = SkillType.AreaSkillExplicitTarget;
chainLightningStr.Target = SkillTarget.Explicit;
}
}

View File

@@ -0,0 +1,69 @@
// <copyright file="ChaosCastleDataUpdatePlugIn.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.Events;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The chaos castle update plugin.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("13059991-F3C8-4050-A201-6D6A67E57541")]
public class ChaosCastleDataUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Chaos Castle Data";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update creates the configuration data for the chaos castle event.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.ChaosCastleDataUpdate;
/// <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(2023, 03, 05, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
#pragma warning disable CS1998
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
#pragma warning restore CS1998
{
// First check if an update is required.
if (gameConfiguration.MiniGameDefinitions.Any(def => def.Type == MiniGameType.ChaosCastle))
{
// There is already a chaos castle definition, so we can skip this update
return;
}
var initializer = new ChaosCastleInitializer(context, gameConfiguration);
initializer.Initialize();
var chaosCastleMaps = gameConfiguration.Maps.Where(map => map.Number is >= 17 and <= 23 or 53);
foreach (var chaosCastleMap in chaosCastleMaps)
{
chaosCastleMap.UpdateTerrainFromResources();
}
}
}

View File

@@ -0,0 +1,116 @@
// <copyright file="DataUpdateService.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 MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Service which applies updates of previously initialized data by a <see cref="IDataInitializationPlugIn"/>.
/// </summary>
public class DataUpdateService
{
private readonly IPersistenceContextProvider _contextProvider;
private readonly PlugInManager _plugInManager;
/// <summary>
/// Initializes a new instance of the <see cref="DataUpdateService"/> class.
/// </summary>
/// <param name="contextProvider">The context provider.</param>
/// <param name="plugInManager">The plug in manager.</param>
public DataUpdateService(IPersistenceContextProvider contextProvider, PlugInManager plugInManager)
{
this._contextProvider = contextProvider;
this._plugInManager = plugInManager;
}
/// <summary>
/// Occurs when updates have been installed.
/// </summary>
public event AsyncEventHandler? UpdatesInstalled;
/// <summary>
/// Determines the available updates which are not installed yet.
/// </summary>
/// <returns>The available plugins.</returns>
/// <exception cref="System.InvalidOperationException">The plugin manager is not initialized.</exception>
public async ValueTask<IReadOnlyCollection<IConfigurationUpdatePlugIn>> DetermineAvailableUpdatesAsync()
{
using var context = this._contextProvider.CreateNewContext();
var updates = (await context.GetAsync<ConfigurationUpdate>().ConfigureAwait(false)).ToList();
var initializationKey = await this.DetermineInitializationKeyAsync(context).ConfigureAwait(false);
var installedUpdates = updates
.Where(up => up.InstalledAt is not null)
.Select(up => (UpdateVersion)up.Version)
.ToHashSet();
var updateStrategyProvider = this._plugInManager.GetStrategyProvider<int, IConfigurationUpdatePlugIn>();
if (updateStrategyProvider is null)
{
// it's null when there are no plugins yet ...
return [];
}
var availableUpdates = updateStrategyProvider.AvailableStrategies
.Where(up => up.DataInitializationKey == initializationKey)
.Where(up => !installedUpdates.Contains(up.Version))
.OrderBy(up => up.Version)
.ToList();
return availableUpdates;
}
/// <summary>
/// Applies the updates asynchronous.
/// </summary>
/// <param name="updates">The updates.</param>
/// <param name="progress">The progress provider. Reports the progress back to the caller.</param>
public async ValueTask ApplyUpdatesAsync(IReadOnlyList<IConfigurationUpdatePlugIn> updates, IProgress<(UpdateVersion CurrentUpdatingVersion, bool IsCompleted)> progress)
{
using var context = this._contextProvider.CreateNewContext();
var updateStates = await context.GetAsync<ConfigurationUpdateState>().ConfigureAwait(false);
var updateState = updateStates.FirstOrDefault() ?? context.CreateNew<ConfigurationUpdateState>();
var gameConfiguration = (await context.GetAsync<GameConfiguration>().ConfigureAwait(false)).First();
foreach (var update in updates)
{
progress.Report((update.Version, false));
await update.ApplyUpdateAsync(context, gameConfiguration).ConfigureAwait(false);
updateState.CurrentInstalledVersion = Math.Max((int)update.Version, updateState.CurrentInstalledVersion);
updateState.InitializationKey = update.DataInitializationKey;
await context.SaveChangesAsync().ConfigureAwait(false);
progress.Report((update.Version, true));
}
progress.Report((UpdateVersion.Undefined, true));
this.UpdatesInstalled?.SafeInvokeAsync();
}
private async ValueTask<string> DetermineInitializationKeyAsync(IContext context)
{
var updateStates = await context.GetAsync<ConfigurationUpdateState>().ConfigureAwait(false);
if (updateStates.FirstOrDefault() is { InitializationKey: not null } updateState)
{
return updateState.InitializationKey;
}
// Now it's getting tricky ...
var clientDefinitions = await context.GetAsync<GameClientDefinition>().ConfigureAwait(false);
if (clientDefinitions.FirstOrDefault() is not { } clientDefinition)
{
throw new InvalidOperationException("No data installed");
}
return (clientDefinition.Season, clientDefinition.Episode) switch
{
(6, 3) => VersionSeasonSix.DataInitialization.Id,
(0, 75) => Version075.DataInitialization.Id,
(0, 95) => Version095d.DataInitialization.Id,
_ => throw new InvalidOperationException($"Unknown client version: {clientDefinition}."),
};
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="FinishDarkKnightMasterTreePlugIn075.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>
/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("B8F3E2C1-4D5A-6F78-9B0C-2E7D1A3F5B6C")]
public class FinishDarkKnightMasterTreePlugIn075 : FinishDarkKnightMasterTreePlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FinishDarkKnightMasterTree075;
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,24 @@
// <copyright file="FinishDarkKnightMasterTreePlugIn095d.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>
/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("D4F7A9C2-1B3E-56D8-9F0A-7C2E4B1D5A8F")]
public class FinishDarkKnightMasterTreePlugIn095D : FinishDarkKnightMasterTreePlugInBase
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FinishDarkKnightMasterTree095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
}

View File

@@ -0,0 +1,204 @@
// <copyright file="FinishDarkKnightMasterTreePlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations.
/// </summary>
public abstract class FinishDarkKnightMasterTreePlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Double Wield Damage Calculations";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the double wield damage calculations.";
/// <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, 6, 22, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Create new Stats
var maceMasteryStunChance = context.CreateNew<AttributeDefinition>(Stats.MaceMasteryStunChance.Id, Stats.MaceMasteryStunChance.Designation, Stats.MaceMasteryStunChance.Description);
gameConfiguration.Attributes.Add(maceMasteryStunChance);
var ragefulBlowMasteryDurabilityDecChance = context.CreateNew<AttributeDefinition>(Stats.RagefulBlowMasteryDurabilityDecChance.Id, Stats.RagefulBlowMasteryDurabilityDecChance.Designation, Stats.RagefulBlowMasteryDurabilityDecChance.Description);
gameConfiguration.Attributes.Add(ragefulBlowMasteryDurabilityDecChance);
var durabilityReductionFactor = context.CreateNew<AttributeDefinition>(Stats.DurabilityReductionFactor.Id, Stats.DurabilityReductionFactor.Designation, Stats.DurabilityReductionFactor.Description);
gameConfiguration.Attributes.Add(durabilityReductionFactor);
var spearMasteryDoubleDamageChance = context.CreateNew<AttributeDefinition>(Stats.SpearMasteryDoubleDamageChance.Id, Stats.SpearMasteryDoubleDamageChance.Designation, Stats.SpearMasteryDoubleDamageChance.Description);
gameConfiguration.Attributes.Add(spearMasteryDoubleDamageChance);
var swellLifeHealthIncrease = context.CreateNew<AttributeDefinition>(Stats.SwellLifeHealthIncrease.Id, Stats.SwellLifeHealthIncrease.Designation, Stats.SwellLifeHealthIncrease.Description);
gameConfiguration.Attributes.Add(swellLifeHealthIncrease);
var swellLifeManaIncrease = context.CreateNew<AttributeDefinition>(Stats.SwellLifeManaIncrease.Id, Stats.SwellLifeManaIncrease.Designation, Stats.SwellLifeManaIncrease.Description);
gameConfiguration.Attributes.Add(swellLifeManaIncrease);
// Update attribute combinations
var maximumHealth = Stats.MaximumHealth.GetPersistent(gameConfiguration);
var maximumMana = Stats.MaximumMana.GetPersistent(gameConfiguration);
var obsoleteTempDoubleWieldMultipliers = gameConfiguration.Attributes.Where(a => a.Designation == "Temp Double Wield multiplier"); // we remove this later
var hasDoubleWield = Stats.HasDoubleWield.GetPersistent(gameConfiguration);
var minimumPhysBaseDmgByWeapon = Stats.MinimumPhysBaseDmgByWeapon.GetPersistent(gameConfiguration);
var maximumPhysBaseDmgByWeapon = Stats.MaximumPhysBaseDmgByWeapon.GetPersistent(gameConfiguration);
var physicalBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration);
var physicalBaseDmgIncrease = Stats.PhysicalBaseDmgIncrease.GetPersistent(gameConfiguration);
var masteryStunChance = Stats.MasteryStunChance.GetPersistent(gameConfiguration);
var isMaceEquipped = Stats.IsMaceEquipped.GetPersistent(gameConfiguration);
var doubleDamageChance = Stats.DoubleDamageChance.GetPersistent(gameConfiguration);
var isSpearEquipped = Stats.IsSpearEquipped.GetPersistent(gameConfiguration);
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
// Update common combination attribute
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.PhysicalBaseDmg && attrCombo.InputAttribute == Stats.BaseDamageBonus) is { } baseDmgBonusToPhysicalBaseDmg)
{
baseDmgBonusToPhysicalBaseDmg.AggregateType = AggregateType.AddFinal;
}
// Add new attribute combinations
var swellLifeHealthIncreaseToMaxHealth = context.CreateNew<AttributeRelationship>(
maximumHealth,
1,
swellLifeHealthIncrease,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.Multiplicate);
var swellLifeManaIncreaseToMaxMana = context.CreateNew<AttributeRelationship>(
maximumMana,
1,
swellLifeManaIncrease,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.Multiplicate);
charClass.AttributeCombinations.Add(swellLifeHealthIncreaseToMaxHealth);
charClass.AttributeCombinations.Add(swellLifeManaIncreaseToMaxMana);
charClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(1, swellLifeHealthIncrease));
charClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(1, swellLifeManaIncrease));
charClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(0.1f, durabilityReductionFactor));
// Update/add double wield attribute combinations
if (charClass.Number == 4 || charClass.Number == 6 || charClass.Number == 7 // DK classes
|| charClass.Number == 12 || charClass.Number == 13 // MG classes
|| charClass.Number == 24 || charClass.Number == 25) // RF classes
{
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.InputOperand == -0.45f) is { } hasDoubleWieldToTempDoubleWieldMultiplier)
{
charClass.AttributeCombinations.Remove(hasDoubleWieldToTempDoubleWieldMultiplier);
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.PhysicalBaseDmgIncrease
&& obsoleteTempDoubleWieldMultipliers.Contains(attrCombo.InputAttribute)) is { } tempDoubleWieldToPhysicalBaseDmgIncrease)
{
tempDoubleWieldToPhysicalBaseDmgIncrease.InputAttribute = hasDoubleWield;
tempDoubleWieldToPhysicalBaseDmgIncrease.InputOperand = 0.55f;
tempDoubleWieldToPhysicalBaseDmgIncrease.InputOperator = InputOperator.ExponentiateByAttribute;
}
var hasDoubleWieldToMinimumPhysBaseDmgByWeapon = context.CreateNew<AttributeRelationship>(
minimumPhysBaseDmgByWeapon,
0.5f,
hasDoubleWield,
InputOperator.ExponentiateByAttribute,
default(AttributeDefinition?),
AggregateType.Multiplicate);
var hasDoubleWieldToMaximumPhysBaseDmgByWeapon = context.CreateNew<AttributeRelationship>(
maximumPhysBaseDmgByWeapon,
0.5f,
hasDoubleWield,
InputOperator.ExponentiateByAttribute,
default(AttributeDefinition?),
AggregateType.Multiplicate);
var hasDoubleWieldToPhysicalBaseDmg = context.CreateNew<AttributeRelationship>(
physicalBaseDmg,
0.5f,
hasDoubleWield,
InputOperator.ExponentiateByAttribute,
default(AttributeDefinition?),
AggregateType.Multiplicate);
var hasDoubleWieldToPhysicalBaseDmgIncrease = context.CreateNew<AttributeRelationship>(
physicalBaseDmgIncrease,
0.5f,
hasDoubleWield,
InputOperator.ExponentiateByAttribute,
default(AttributeDefinition?),
AggregateType.Multiplicate);
var hasDoubleWieldToPhysicalBaseDmgIncreaseRaw = context.CreateNew<AttributeRelationship>(
physicalBaseDmgIncrease,
1,
hasDoubleWield,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
charClass.AttributeCombinations.Add(hasDoubleWieldToMinimumPhysBaseDmgByWeapon);
charClass.AttributeCombinations.Add(hasDoubleWieldToMaximumPhysBaseDmgByWeapon);
charClass.AttributeCombinations.Add(hasDoubleWieldToPhysicalBaseDmg);
charClass.AttributeCombinations.Add(hasDoubleWieldToPhysicalBaseDmgIncrease);
charClass.AttributeCombinations.Add(hasDoubleWieldToPhysicalBaseDmgIncreaseRaw);
if (charClass.Number == 4 || charClass.Number == 6 || charClass.Number == 7)
{
var masteryStunChanceToMaceMasteryStunChance = context.CreateNew<AttributeRelationship>(
maceMasteryStunChance,
isMaceEquipped,
masteryStunChance,
AggregateType.AddRaw);
var spearMasteryDoubleDamageChanceToDoubleDamageChance = context.CreateNew<AttributeRelationship>(
doubleDamageChance,
isSpearEquipped,
spearMasteryDoubleDamageChance,
AggregateType.AddRaw);
charClass.AttributeCombinations.Add(masteryStunChanceToMaceMasteryStunChance);
charClass.AttributeCombinations.Add(spearMasteryDoubleDamageChanceToDoubleDamageChance);
}
}
});
// Removed obsolete attributes
foreach (var obsoleteTempDoubleWield in obsoleteTempDoubleWieldMultipliers.ToList())
{
gameConfiguration.Attributes.Remove(obsoleteTempDoubleWield);
}
// Update wings physical base dmg option
var wingsSlot = gameConfiguration.ItemSlotTypes.First(st => st.ItemSlots.Contains(InventoryConstants.WingsSlot));
foreach (var wing in gameConfiguration.Items.Where(i => i.ItemSlot == wingsSlot))
{
if (wing.PossibleItemOptions.SelectMany(pio => pio.PossibleOptions).FirstOrDefault(po => po.PowerUpDefinition?.TargetAttribute == physicalBaseDmg) is { } pio)
{
foreach (var levelOption in pio.LevelDependentOptions)
{
levelOption.PowerUpDefinition!.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal;
}
}
}
}
}

View File

@@ -0,0 +1,325 @@
// <copyright file="FinishDarkKnightMasterTreePlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F7B2C9E4-1A3D-56F8-9B0C-4E2D7A1F8B3C")]
public class FinishDarkKnightMasterTreePlugInSeason6 : FinishDarkKnightMasterTreePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal new const string PlugInName = "Finish Dark Knight Master Tree PlugIn";
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInDescription = "This update completes the dark knight master tree skills and effects. It also fixes the double wield damage calculations.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FinishDarkKnightMasterTreeSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
var masteryStunChance = Stats.MasteryStunChance.GetPersistent(gameConfiguration);
var ragefulBlowMasteryDurabilityDecChance = Stats.RagefulBlowMasteryDurabilityDecChance.GetPersistent(gameConfiguration);
var spearMasteryDoubleDamageChance = Stats.SpearMasteryDoubleDamageChance.GetPersistent(gameConfiguration);
var swellLifeHealthIncrease = Stats.SwellLifeHealthIncrease.GetPersistent(gameConfiguration);
var swellLifeManaIncrease = Stats.SwellLifeManaIncrease.GetPersistent(gameConfiguration);
var physicalBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration);
// Update Life Swell effect
var lifeSwellEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.GreaterFortitude);
lifeSwellEffect.SubType = 4;
lifeSwellEffect.Duration!.MaximumValue = 180;
if (lifeSwellEffect.Duration.RelatedValues.FirstOrDefault() is { } durationPerEnergy)
{
durationPerEnergy.InputOperand = 1f / 180f;
}
if (lifeSwellEffect.PowerUpDefinitions.FirstOrDefault() is { } maxHealth)
{
maxHealth.TargetAttribute = swellLifeHealthIncrease;
maxHealth.Boost!.ConstantValue.Value = 0.12f;
maxHealth.Boost.ConstantValue.AggregateType = AggregateType.AddRaw;
maxHealth.Boost.MaximumValue = 2f;
foreach (var boostRelatedValue in maxHealth.Boost.RelatedValues)
{
if (boostRelatedValue.InputAttribute == Stats.TotalEnergy)
{
boostRelatedValue.InputOperator = InputOperator.Multiply;
boostRelatedValue.InputOperand = 1f / 2000;
continue;
}
if (boostRelatedValue.InputAttribute == Stats.TotalVitality)
{
boostRelatedValue.InputOperator = InputOperator.Multiply;
boostRelatedValue.InputOperand = 1f / 10000;
}
}
var boostPerPartyMember = context.CreateNew<AttributeRelationship>();
boostPerPartyMember.InputAttribute = Stats.NearbyPartyMemberCount.GetPersistent(gameConfiguration);
boostPerPartyMember.InputOperator = InputOperator.Multiply;
boostPerPartyMember.InputOperand = 1f / 100;
maxHealth.Boost.RelatedValues.Add(boostPerPartyMember);
}
// Create Life Swell Proficiency Skill Effect
var lifeSwellProficiencyEffect = this.CreateLifeSwellProficiencyEffect(context, gameConfiguration);
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SwellLifeProficiency) is { } skill)
{
skill.MagicEffectDef = lifeSwellProficiencyEffect;
}
// Restore iced effect (revert bug)
if (gameConfiguration.MagicEffects.FirstOrDefault(e => e.Number == (short)MagicEffectNumber.Iced && e.Chance is { }) is { } originalIced)
{
originalIced.Chance = null;
}
// Remove existing cold effect
var existingColdEffect = gameConfiguration.MagicEffects.FirstOrDefault(e => e.Number == (short)MagicEffectNumber.Cold);
if (existingColdEffect is not null)
{
gameConfiguration.MagicEffects.Remove(existingColdEffect);
}
// Create chain drive cold effect
var chainDriveCold = this.CreateEffect(context, gameConfiguration, ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10, 0.4f);
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainDrive) is { } chainDrive)
{
chainDrive.MagicEffectDef = chainDriveCold;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainDriveStrengthener) is { } chainDriveStrengthener)
{
chainDriveStrengthener.MagicEffectDef = chainDriveCold;
}
// Create strike of destruction cold effect
var strikeOfDestructCold = this.CreateEffect(context, gameConfiguration, ElementalType.Ice, MagicEffectNumber.Cold, Stats.IsIced, 10);
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.StrikeofDestruction) is { } strikeOfDestruction)
{
strikeOfDestruction.SkipElementalModifier = true;
strikeOfDestruction.MagicEffectDef = strikeOfDestructCold;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.StrikeofDestrStr) is { } strikeOfDestrStr)
{
strikeOfDestrStr.SkipElementalModifier = true;
strikeOfDestrStr.MagicEffectDef = strikeOfDestructCold;
}
// Update harmony option
if (gameConfiguration.ItemOptions.FirstOrDefault(o => o.Name == "Harmony Physical Attack Options") is { } harmonyPhysAttackOptions
&& harmonyPhysAttackOptions.PossibleOptions.FirstOrDefault(o => o.Number == 5) is { } physBaseDmgOpt)
{
foreach (var level in physBaseDmgOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal;
}
}
// Update gold fenrir option
var goldFenrirOptionId = new Guid("00000083-0081-0000-0000-000000000000");
if (gameConfiguration.ItemOptions.FirstOrDefault(o => o.GetId() == goldFenrirOptionId) is { } goldFenrirOption)
{
foreach (var option in goldFenrirOption.PossibleOptions)
{
if (option.PowerUpDefinition?.TargetAttribute == physicalBaseDmg)
{
option.PowerUpDefinition.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal;
}
}
}
// Update master skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.TwistingSlashMastery)?.MasterDefinition is { } twistingSlashMastery)
{
twistingSlashMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.TwistingSlashStreng);
twistingSlashMastery.TargetAttribute = Stats.MasteryMoveTargetChance.GetPersistent(gameConfiguration);
twistingSlashMastery.Aggregation = AggregateType.AddRaw;
twistingSlashMastery.ValueFormula = $"{twistingSlashMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.RagefulBlowMastery) is { } ragefulBlowMastery)
{
ragefulBlowMastery.AttributeRelationships.Add(context.CreateNew<AttributeRelationship>(
ragefulBlowMasteryDurabilityDecChance,
1,
ragefulBlowMasteryDurabilityDecChance,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw));
if (ragefulBlowMastery.MasterDefinition is { } masterDefinition)
{
masterDefinition.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.RagefulBlowStreng);
masterDefinition.TargetAttribute = ragefulBlowMasteryDurabilityDecChance;
masterDefinition.Aggregation = AggregateType.AddRaw;
masterDefinition.ValueFormula = $"{masterDefinition.ValueFormula} / 100";
}
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MaceMastery)?.MasterDefinition is { } maceMastery)
{
maceMastery.TargetAttribute = masteryStunChance;
maceMastery.Aggregation = AggregateType.AddRaw;
maceMastery.ValueFormula = $"{maceMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SpearMastery)?.MasterDefinition is { } spearMastery)
{
spearMastery.TargetAttribute = spearMasteryDoubleDamageChance;
spearMastery.Aggregation = AggregateType.AddRaw;
spearMastery.ValueFormula = $"{spearMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SwellLifeStrengt)?.MasterDefinition is { } swellLifeStrengt)
{
swellLifeStrengt.TargetAttribute = swellLifeHealthIncrease;
swellLifeStrengt.Aggregation = AggregateType.AddRaw;
swellLifeStrengt.ValueFormula = $"{swellLifeStrengt.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.SwellLifeProficiency)?.MasterDefinition is { } swellLifeProficiency)
{
swellLifeProficiency.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.SwellLifeStrengt);
swellLifeProficiency.TargetAttribute = swellLifeManaIncrease;
swellLifeProficiency.Aggregation = AggregateType.AddRaw;
swellLifeProficiency.ValueFormula = $"{swellLifeProficiency.ValueFormula} / 100";
}
}
private MagicEffectDefinition CreateLifeSwellProficiencyEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (byte)MagicEffectNumber.GreaterFortitudeProficiency;
magicEffect.Name = "Life Swell Proficiency Skill Effect";
var lifeSwellEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.GreaterFortitude);
magicEffect.InformObservers = lifeSwellEffect.InformObservers;
magicEffect.SubType = lifeSwellEffect.SubType;
magicEffect.SendDuration = lifeSwellEffect.SendDuration;
magicEffect.StopByDeath = lifeSwellEffect.StopByDeath;
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = lifeSwellEffect.Duration!.ConstantValue.Value;
magicEffect.Duration.MaximumValue = lifeSwellEffect.Duration.MaximumValue;
foreach (var durationRelatedValue in lifeSwellEffect.Duration.RelatedValues)
{
var durationRelatedValueCopy = context.CreateNew<AttributeRelationship>();
durationRelatedValueCopy.InputAttribute = durationRelatedValue.InputAttribute!.GetPersistent(gameConfiguration);
durationRelatedValueCopy.InputOperator = durationRelatedValue.InputOperator;
durationRelatedValueCopy.InputOperand = durationRelatedValue.InputOperand;
magicEffect.Duration.RelatedValues.Add(durationRelatedValueCopy);
}
foreach (var powerUp in lifeSwellEffect.PowerUpDefinitions)
{
var powerUpCopy = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpCopy);
powerUpCopy.TargetAttribute = powerUp.TargetAttribute!.GetPersistent(gameConfiguration);
powerUpCopy.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpCopy.Boost.ConstantValue.Value = powerUp.Boost!.ConstantValue.Value;
powerUpCopy.Boost.MaximumValue = powerUp.Boost.MaximumValue;
foreach (var boostRelatedValue in powerUp.Boost.RelatedValues)
{
var boostRelatedValueCopy = context.CreateNew<AttributeRelationship>();
boostRelatedValueCopy.InputAttribute = boostRelatedValue.InputAttribute!.GetPersistent(gameConfiguration);
boostRelatedValueCopy.InputOperator = boostRelatedValue.InputOperator;
boostRelatedValueCopy.InputOperand = boostRelatedValue.InputOperand;
powerUpCopy.Boost.RelatedValues.Add(boostRelatedValueCopy);
}
}
// one percent per party member in view
var boostPerPartyMember = context.CreateNew<AttributeRelationship>();
boostPerPartyMember.InputAttribute = Stats.NearbyPartyMemberCount.GetPersistent(gameConfiguration);
boostPerPartyMember.InputOperator = InputOperator.Multiply;
boostPerPartyMember.InputOperand = 1f / 100;
var manaPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(manaPowerUpDefinition);
manaPowerUpDefinition.TargetAttribute = Stats.SwellLifeManaIncrease.GetPersistent(gameConfiguration);
manaPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
manaPowerUpDefinition.Boost.RelatedValues.Add(boostPerPartyMember);
return magicEffect;
}
private MagicEffectDefinition CreateEffect(IContext context, GameConfiguration gameConfiguration, ElementalType type, MagicEffectNumber effectNumber, AttributeDefinition targetAttribute, float durationInSeconds, float chance = 0)
{
var effect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(effect);
effect.Name = Enum.GetName(effectNumber) ?? string.Empty;
effect.InformObservers = true;
effect.Number = (short)effectNumber;
effect.StopByDeath = true;
effect.SubType = (byte)(0xFF - type);
effect.Duration = context.CreateNew<PowerUpDefinitionValue>();
effect.Duration.ConstantValue.Value = durationInSeconds;
var powerUpDefinition = context.CreateNew<PowerUpDefinition>();
effect.PowerUpDefinitions.Add(powerUpDefinition);
powerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = 1;
powerUpDefinition.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration);
if (targetAttribute == Stats.IsIced)
{
var movementSpeedFactorPowerUp = context.CreateNew<PowerUpDefinition>();
effect.PowerUpDefinitions.Add(movementSpeedFactorPowerUp);
movementSpeedFactorPowerUp.Boost = context.CreateNew<PowerUpDefinitionValue>();
movementSpeedFactorPowerUp.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
movementSpeedFactorPowerUp.TargetAttribute = Stats.MovementSpeedFactor.GetPersistent(gameConfiguration);
if (effectNumber == MagicEffectNumber.Cold)
{
movementSpeedFactorPowerUp.Boost.ConstantValue.Value = MovementSpeedConstants.ColdMovementSpeedFactor;
}
else
{
movementSpeedFactorPowerUp.Boost.ConstantValue.Value = MovementSpeedConstants.IcedMovementSpeedFactor;
}
}
if (chance > 0)
{
effect.Chance = context.CreateNew<PowerUpDefinitionValue>();
effect.Chance.ConstantValue.Value = chance;
}
return effect;
}
}

View File

@@ -0,0 +1,230 @@
// <copyright file="FinishDarkLordMasterTreePlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update completes the dark lord master tree skills and effects.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("1A2B3C4D-5E6F-7890-ABCD-EF1234567890")]
public class FinishDarkLordMasterTreePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Finish Dark Lord Master Tree PlugIn";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update completes the dark lord master tree skills and effects.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FinishDarkLordMasterTree;
/// <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, 4, 14, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Create Critical Damage Increase Mastery Skill Effect
var critDmgIncMasteryEffect = this.CreateCritDmgIncMasteryEffect(context, gameConfiguration);
// Update Stunned effect
var stunnedEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.Stunned);
stunnedEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
stunnedEffect.Duration.ConstantValue.Value = 2;
var stunChancePowerUpDefinition = context.CreateNew<PowerUpDefinition>();
stunnedEffect.PowerUpDefinitions.Add(stunChancePowerUpDefinition);
stunChancePowerUpDefinition.TargetAttribute = Stats.MasteryStunChance.GetPersistent(gameConfiguration);
stunChancePowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
stunChancePowerUpDefinition.Boost.ConstantValue.Value = 0;
// Map skills to effects
this.MapSkillToEffect(gameConfiguration, SkillNumber.FireBurstMastery, stunnedEffect);
this.MapSkillToEffect(gameConfiguration, SkillNumber.EarthshakeMastery, stunnedEffect);
this.MapSkillToEffect(gameConfiguration, SkillNumber.CritDmgIncPowUp3, critDmgIncMasteryEffect);
// Update AreaSkillSettings
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Earthshake, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.EarthshakeStreng, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.EarthshakeMastery, false, 0, 0, 0, useTargetAreaFilter: true, targetAreaDiameter: 10, minimumHitsPerAttack: 9, maximumHitsPerAttack: 15);
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChaoticDiseier) is { } chaoticDiseier)
{
chaoticDiseier.AreaSkillSettings!.MinimumNumberOfHitsPerAttack = 7;
}
// Update master skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CriticalDmgIncPowUp)?.MasterDefinition is { } fireTomeMastery)
{
fireTomeMastery.TargetAttribute = Stats.CriticalDamageBonus.GetPersistent(gameConfiguration);
fireTomeMastery.Aggregation = AggregateType.AddRaw;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.FireBurstMastery)?.MasterDefinition is { } fireBurstMastery)
{
fireBurstMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.FireBurstStreng);
fireBurstMastery.TargetAttribute = Stats.MasteryStunChance.GetPersistent(gameConfiguration);
fireBurstMastery.Aggregation = AggregateType.AddRaw;
fireBurstMastery.ValueFormula = $"{fireBurstMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CritDmgIncPowUp2)?.MasterDefinition is { } critDmgIncPowUp2)
{
critDmgIncPowUp2.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.CriticalDmgIncPowUp);
critDmgIncPowUp2.ExtendsDuration = true;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.EarthshakeMastery)?.MasterDefinition is { } earthshakeMastery)
{
earthshakeMastery.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.EarthshakeStreng);
earthshakeMastery.TargetAttribute = Stats.MasteryStunChance.GetPersistent(gameConfiguration);
earthshakeMastery.Aggregation = AggregateType.AddRaw;
earthshakeMastery.ValueFormula = $"{earthshakeMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CritDmgIncPowUp3)?.MasterDefinition is { } critDmgIncPowUp3)
{
critDmgIncPowUp3.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.CritDmgIncPowUp2);
critDmgIncPowUp3.TargetAttribute = Stats.CriticalDamageChance.GetPersistent(gameConfiguration);
critDmgIncPowUp3.Aggregation = AggregateType.AddRaw;
critDmgIncPowUp3.ValueFormula = $"{critDmgIncPowUp3.ValueFormula} / 100";
}
}
private MagicEffectDefinition CreateCritDmgIncMasteryEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (byte)MagicEffectNumber.CriticalDamageIncreaseMastery;
magicEffect.Name = "Critical Damage Increase Mastery Skill Effect";
var critDmgIncEffect = gameConfiguration.MagicEffects.First(e => e.Number == (short)MagicEffectNumber.CriticalDamageIncrease);
critDmgIncEffect.Duration?.MaximumValue = 180;
critDmgIncEffect.SubType = 17;
magicEffect.InformObservers = critDmgIncEffect.InformObservers;
magicEffect.SubType = critDmgIncEffect.SubType;
magicEffect.SendDuration = critDmgIncEffect.SendDuration;
magicEffect.StopByDeath = critDmgIncEffect.StopByDeath;
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = critDmgIncEffect.Duration!.ConstantValue.Value;
magicEffect.Duration.MaximumValue = critDmgIncEffect.Duration.MaximumValue;
foreach (var durationRelatedValue in critDmgIncEffect.Duration.RelatedValues)
{
var durationRelatedValueCopy = context.CreateNew<AttributeRelationship>();
durationRelatedValueCopy.InputAttribute = durationRelatedValue.InputAttribute!.GetPersistent(gameConfiguration);
durationRelatedValueCopy.InputOperator = durationRelatedValue.InputOperator;
durationRelatedValueCopy.InputOperand = durationRelatedValue.InputOperand;
magicEffect.Duration.RelatedValues.Add(durationRelatedValueCopy);
}
foreach (var powerUp in critDmgIncEffect.PowerUpDefinitions)
{
var powerUpCopy = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpCopy);
powerUpCopy.TargetAttribute = powerUp.TargetAttribute!.GetPersistent(gameConfiguration);
powerUpCopy.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpCopy.Boost.ConstantValue.Value = powerUp.Boost!.ConstantValue.Value;
foreach (var boostRelatedValue in powerUp.Boost.RelatedValues)
{
var boostRelatedValueCopy = context.CreateNew<AttributeRelationship>();
boostRelatedValueCopy.InputAttribute = boostRelatedValue.InputAttribute!.GetPersistent(gameConfiguration);
boostRelatedValueCopy.InputOperator = boostRelatedValue.InputOperator;
boostRelatedValueCopy.InputOperand = boostRelatedValue.InputOperand;
powerUpCopy.Boost.RelatedValues.Add(boostRelatedValueCopy);
}
}
var critChancePowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(critChancePowerUpDefinition);
critChancePowerUpDefinition.TargetAttribute = Stats.CriticalDamageChance.GetPersistent(gameConfiguration);
critChancePowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
critChancePowerUpDefinition.Boost.ConstantValue.Value = 0;
return magicEffect;
}
private void MapSkillToEffect(GameConfiguration gameConfiguration, SkillNumber skillNumber, MagicEffectDefinition magicEffect)
{
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is { } skill)
{
skill.MagicEffectDef = magicEffect;
}
}
private void AddAreaSkillSettings(
GameConfiguration gameConfiguration,
IContext context,
SkillNumber skillNumber,
bool useFrustumFilter,
float frustumStartWidth,
float frustumEndWidth,
float frustumDistance,
bool useDeferredHits = false,
TimeSpan delayPerOneDistance = default,
TimeSpan delayBetweenHits = default,
int minimumHitsPerTarget = 1,
int maximumHitsPerTarget = 1,
int minimumHitsPerAttack = default,
int maximumHitsPerAttack = default,
float hitChancePerDistanceMultiplier = 1.0f,
bool useTargetAreaFilter = false,
float targetAreaDiameter = default,
int projectileCount = 1,
int effectRange = default)
{
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is not { } skill)
{
return;
}
skill.SkillType = SkillType.AreaSkillAutomaticHits;
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
skill.AreaSkillSettings = areaSkillSettings;
areaSkillSettings.UseFrustumFilter = useFrustumFilter;
areaSkillSettings.FrustumStartWidth = frustumStartWidth;
areaSkillSettings.FrustumEndWidth = frustumEndWidth;
areaSkillSettings.FrustumDistance = frustumDistance;
areaSkillSettings.UseTargetAreaFilter = useTargetAreaFilter;
areaSkillSettings.TargetAreaDiameter = targetAreaDiameter;
areaSkillSettings.UseDeferredHits = useDeferredHits;
areaSkillSettings.DelayPerOneDistance = delayPerOneDistance;
areaSkillSettings.DelayBetweenHits = delayBetweenHits;
areaSkillSettings.MinimumNumberOfHitsPerTarget = minimumHitsPerTarget;
areaSkillSettings.MaximumNumberOfHitsPerTarget = maximumHitsPerTarget;
areaSkillSettings.MinimumNumberOfHitsPerAttack = minimumHitsPerAttack;
areaSkillSettings.MaximumNumberOfHitsPerAttack = maximumHitsPerAttack;
areaSkillSettings.HitChancePerDistanceMultiplier = hitChancePerDistanceMultiplier;
areaSkillSettings.ProjectileCount = projectileCount;
areaSkillSettings.EffectRange = effectRange;
}
}

View File

@@ -0,0 +1,83 @@
// <copyright file="FixAncientDiscriminatorsUpdatePlugIn.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.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the discriminators of some ancient items.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("AB664421-1CA6-4FCE-A150-0007971017E1")]
public class FixAncientDiscriminatorsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Warrior Morning Star";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the discriminators of some ancient items.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixAncientDiscriminators;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 25, 17, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
ChangeDiscriminator(gameConfiguration, "Anonymous", ItemGroups.Shields, 0, 1);
ChangeDiscriminator(gameConfiguration, "Mist", ItemGroups.Gloves, 0, 1);
ChangeDiscriminator(gameConfiguration, "Mist", ItemGroups.Helm, 0, 1);
ChangeDiscriminator(gameConfiguration, "Berserker", ItemGroups.Gloves, 6, 1);
ChangeDiscriminator(gameConfiguration, "Berserker", ItemGroups.Boots, 6, 1);
ChangeDiscriminator(gameConfiguration, "Cloud", ItemGroups.Helm, 8, 1);
ChangeDiscriminator(gameConfiguration, "Rave", ItemGroups.Helm, 9, 1);
ChangeDiscriminator(gameConfiguration, "Rave", ItemGroups.Pants, 9, 1);
ChangeDiscriminator(gameConfiguration, "Barnake", ItemGroups.Boots, 2, 1);
ChangeDiscriminator(gameConfiguration, "Sylion", ItemGroups.Gloves, 4, 1);
ChangeDiscriminator(gameConfiguration, "Sylion", ItemGroups.Helm, 4, 1);
ChangeDiscriminator(gameConfiguration, "Drake", ItemGroups.Armor, 10, 1);
ChangeDiscriminator(gameConfiguration, "Fase", ItemGroups.Boots, 11, 1);
}
private static void ChangeDiscriminator(GameConfiguration gameConfiguration, string setName, ItemGroups itemGroup, byte itemNumber, byte discriminator)
{
var itemSetGroup = gameConfiguration.ItemSetGroups.First(set => set.Name == setName);
var itemOfItemSet = itemSetGroup.Items.FirstOrDefault(item => item.ItemDefinition?.Group == (byte)itemGroup && item.ItemDefinition?.Number == itemNumber);
if (itemOfItemSet != null)
{
itemOfItemSet.AncientSetDiscriminator = discriminator;
}
}
}

View File

@@ -0,0 +1,101 @@
// <copyright file="FixAreaSkillsUpdatePlugIn.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.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes area skills' range, effect radius, and delay issues.
/// Hellfire, Decay, and Ice Storm had missing area skill settings causing incorrect hit radius.
/// Ice Storm had an incorrect 200ms delay between hits.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("B9938E4D-8F63-48DF-AE45-6739D1E2A8C7")]
public class FixAreaSkillsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Area Skills";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Fixes Hellfire, Decay, and Ice Storm skills' range, effect radius, and delay.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixAreaSkills;
/// <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, 04, 09, 12, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Fix Hellfire Strengthener
var hellfireStrengthener = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.HellfireStrengthener);
if (hellfireStrengthener != null)
{
hellfireStrengthener.Range = 4;
hellfireStrengthener.AttackDamage = 3;
if (hellfireStrengthener.AreaSkillSettings == null)
{
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
hellfireStrengthener.AreaSkillSettings = areaSkillSettings;
areaSkillSettings.EffectRange = 2;
}
}
// Fix Decay Strengthener
var decayStrengthener = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DecayStrengthener);
if (decayStrengthener != null)
{
decayStrengthener.Range = 6;
if (decayStrengthener.AreaSkillSettings == null)
{
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
decayStrengthener.AreaSkillSettings = areaSkillSettings;
areaSkillSettings.EffectRange = 2;
}
}
// Fix base Decay skill
var decay = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Decay);
if (decay != null)
{
if (decay.AreaSkillSettings == null)
{
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
decay.AreaSkillSettings = areaSkillSettings;
areaSkillSettings.EffectRange = 2;
}
}
// Fix Ice Storm - remove incorrect 200ms delay
var iceStorm = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.IceStorm);
if (iceStorm?.AreaSkillSettings != null)
{
iceStorm.AreaSkillSettings.DelayBetweenHits = TimeSpan.Zero;
}
}
}

View File

@@ -0,0 +1,250 @@
// <copyright file="FixAttackSpeedCalculationUpdate.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Network.Packets;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
using static MUnique.OpenMU.Persistence.Initialization.CharacterClasses.CharacterClassHelper;
/// <summary>
/// This adds attributes and relations for attack speed. Adds effects for Ale and Potion of Soul.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F9977AA7-F52A-4F42-BD6C-98DE700B5980")]
public class FixAttackSpeedCalculationUpdate : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Attack Speed Calculation";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Adds attributes and relations for attack speed. Adds effects for Ale and Potion of Soul.";
private static readonly Dictionary<int, int> AttackSpeedByGloveNumber = new()
{
{ 0, 4 }, // Bronze Gloves
{ 1, 6 }, // Dragon Gloves
{ 5, 8 }, // Leather Gloves
{ 6, 10 }, // Scale Gloves
{ 8, 8 }, // Brass Gloves
{ 9, 4 }, // Plate Gloves
{ 10, 4 }, // Vine Gloves
{ 11, 8 }, // Silk Gloves
{ 12, 10 }, // Wind Gloves
{ 13, 4 }, // Spirit Gloves
{ 14, 6 }, // Guardian Gloves
{ 15, 6 }, // Storm Crow Gloves
{ 16, 6 }, // Black Dragon Gloves
{ 17, 6 }, // Dark Phoenix Gloves
{ 18, 5 }, // Grand Soul Gloves
{ 19, 6 }, // Divine Gloves
{ 20, 7 }, // Thunder Hawk Gloves
{ 21, 6 }, // Great Dragon Gloves
{ 22, 6 }, // Dark Soul Gloves
{ 23, 7 }, // Hurricane Gloves
{ 24, 6 }, // Red Spirit Gloves
{ 25, 7 }, // Light Plate Gloves
{ 26, 6 }, // Adamantine Gloves
{ 27, 5 }, // Dark Steel Gloves
{ 28, 4 }, // Dark Master Gloves
{ 29, 7 }, // Dragon Knight Gloves
{ 30, 7 }, // Venom Mist Gloves
{ 31, 7 }, // Sylphid Ray Gloves
{ 32, 7 }, // Volcano Gloves
{ 33, 5 }, // Sunlight Gloves
{ 34, 6 }, // Ashcrow Gloves
{ 35, 6 }, // Eclipse Gloves
{ 36, 6 }, // Iris Gloves
{ 37, 7 }, // Valiant Gloves
{ 38, 5 }, // Glorious Gloves
{ 39, 6 }, // Violent Wind Gloves
{ 40, 8 }, // Red Wing Gloves
{ 41, 7 }, // Ancient Gloves
{ 42, 6 }, // Demonic Gloves
{ 43, 6 }, // Storm Blitz Gloves
{ 45, 7 }, // Titan Gloves
{ 46, 7 }, // Brave Gloves
{ 47, 7 }, // Phantom Gloves
{ 48, 7 }, // Destroy Gloves
{ 49, 7 }, // Seraphim Gloves
{ 50, 7 }, // Divine Gloves
{ 51, 7 }, // Royal Gloves
{ 52, 7 }, // Hades Gloves
};
private static readonly Dictionary<int, int> WalkSpeedByBootNumber = new()
{
{ 0, 10 }, // Bronze Boots
{ 1, 2 }, // Dragon Boots
{ 2, 10 }, // Pad Boots
{ 3, 0 }, // Legendary Boots
{ 4, 6 }, // Bone Boots
{ 5, 12 }, // Leather Boots
{ 6, 8 }, // Scale Boots
{ 7, 8 }, // Sphinx Boots
{ 8, 6 }, // Brass Boots
{ 9, 4 }, // Plate Boots
{ 15, 2 }, // Storm Crow Boots
{ 16, 2 }, // Black Dragon Boots
{ 17, 2 }, // Dark Phoenix Boots
{ 20, 2 }, // Thunder Hawk Boots
};
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixAttackSpeedCalculation;
/// <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(2024, 10, 19, 14, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
if (gameConfiguration.Attributes.Contains(Stats.MagicSpeed))
{
return;
}
this.AddStatIfNotExists(context, gameConfiguration, Stats.MagicSpeed);
this.AddStatIfNotExists(context, gameConfiguration, Stats.AttackSpeedByWeapon);
this.AddStatIfNotExists(context, gameConfiguration, Stats.AreTwoWeaponsEquipped);
this.AddStatIfNotExists(context, gameConfiguration, Stats.EquippedWeaponCount);
this.AddStatIfNotExists(context, gameConfiguration, Stats.WalkSpeed);
Stats.AttackSpeed.GetPersistent(gameConfiguration).MaximumValue = Stats.AttackSpeed.MaximumValue;
Stats.MagicSpeed.GetPersistent(gameConfiguration).MaximumValue = Stats.MagicSpeed.MaximumValue;
foreach (var characterClass in gameConfiguration.CharacterClasses)
{
var attributeRelationships = characterClass.AttributeCombinations;
attributeRelationships.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.AttackSpeed, 1, Stats.AttackSpeedByWeapon));
attributeRelationships.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1, Stats.AttackSpeedByWeapon));
// If two weapons are equipped we subtract the half of the sum of the speeds again from the attack speed
attributeRelationships.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.AreTwoWeaponsEquipped, 1, Stats.EquippedWeaponCount));
var tempSpeed = context.CreateNew<AttributeDefinition>(Guid.NewGuid(), "Temp Half weapon attack speed", string.Empty);
gameConfiguration.Attributes.Add(tempSpeed);
attributeRelationships.Add(CreateAttributeRelationship(context, gameConfiguration, tempSpeed, -0.5f, Stats.AttackSpeedByWeapon));
attributeRelationships.Add(CreateConditionalRelationship(context, gameConfiguration, Stats.AttackSpeed, Stats.AreTwoWeaponsEquipped, tempSpeed));
attributeRelationships.Add(CreateConditionalRelationship(context, gameConfiguration, Stats.MagicSpeed, Stats.AreTwoWeaponsEquipped, tempSpeed));
characterClass.BaseAttributeValues.Add(CreateConstValueAttribute(context, gameConfiguration, -1, Stats.AreTwoWeaponsEquipped));
}
foreach (var darkKnight in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.BladeKnight or CharacterClassNumber.BladeMaster or CharacterClassNumber.DarkKnight))
{
darkKnight.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 20, Stats.TotalAgility));
}
foreach (var darkLord in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.DarkLord or CharacterClassNumber.LordEmperor))
{
darkLord.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.AttackSpeed, 1.0f / 10, Stats.TotalAgility));
darkLord.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 10, Stats.TotalAgility));
}
foreach (var darkWizard in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.DarkWizard or CharacterClassNumber.SoulMaster or CharacterClassNumber.GrandMaster))
{
darkWizard.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.AttackSpeed, 1.0f / 20, Stats.TotalAgility));
darkWizard.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 10, Stats.TotalAgility));
}
foreach (var elf in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.FairyElf or CharacterClassNumber.MuseElf or CharacterClassNumber.HighElf))
{
elf.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.AttackSpeed, 1.0f / 50, Stats.TotalAgility));
elf.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 50, Stats.TotalAgility));
}
foreach (var magicGladiator in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.MagicGladiator or CharacterClassNumber.DuelMaster))
{
magicGladiator.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 20, Stats.TotalAgility));
}
foreach (var rageFighter in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.RageFighter or CharacterClassNumber.FistMaster))
{
rageFighter.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.AttackSpeed, 1.0f / 9, Stats.TotalAgility));
rageFighter.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 9, Stats.TotalAgility));
}
foreach (var summoner in gameConfiguration.CharacterClasses.Where(c => ((CharacterClassNumber)c.Number) is CharacterClassNumber.Summoner or CharacterClassNumber.BloodySummoner or CharacterClassNumber.DimensionMaster))
{
summoner.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.MagicSpeed, 1.0f / 20, Stats.TotalAgility));
}
// attack speed from gloves:
var glovesWithSpeed = gameConfiguration.Items.Where(item => item.Group == (byte)ItemGroups.Gloves)
.Select(item => (item, AttackSpeedByGloveNumber.GetValueOrDefault(item.Number)))
.Where(pair => pair.Item2 > 0);
foreach (var (gloves, attackSpeed) in glovesWithSpeed)
{
gloves.BasePowerUpAttributes.Add(this.CreateItemBasePowerUpDefinition(context, gameConfiguration, Stats.AttackSpeed, attackSpeed, AggregateType.AddRaw));
gloves.BasePowerUpAttributes.Add(this.CreateItemBasePowerUpDefinition(context, gameConfiguration, Stats.MagicSpeed, attackSpeed, AggregateType.AddRaw));
}
// walk speed from boots:
var bootsWithSpeed = gameConfiguration.Items.Where(item => item.Group == (byte)ItemGroups.Boots)
.Select(item => (item, WalkSpeedByBootNumber.GetValueOrDefault(item.Number)))
.Where(pair => pair.Item2 > 0);
foreach (var (boots, walkSpeed) in bootsWithSpeed)
{
boots.BasePowerUpAttributes.Add(this.CreateItemBasePowerUpDefinition(context, gameConfiguration, Stats.WalkSpeed, walkSpeed, AggregateType.AddRaw));
}
new AlcoholEffectInitializer(context, gameConfiguration).Initialize();
new BlessPotionEffectInitializer(context, gameConfiguration).Initialize();
new SoulPotionEffectInitializer(context, gameConfiguration).Initialize();
this.SetItemEffect(gameConfiguration, ItemConstants.Alcohol, MagicEffectNumber.Alcohol);
var siegePotion = gameConfiguration.Items.First(item => item.Number == ItemConstants.SiegePotion.Number && item.Group == ItemConstants.SiegePotion.Group);
siegePotion.Name = "Potion of Bless;Potion of Soul";
siegePotion.Durability = 10;
siegePotion.MaximumItemLevel = 1;
var jackOlanternBlessingEffect = gameConfiguration.MagicEffects.First(item => item.Number == (int)MagicEffectNumber.JackOlanternBlessing);
var powerUpDefinition = context.CreateNew<PowerUpDefinition>();
jackOlanternBlessingEffect.PowerUpDefinitions.Add(powerUpDefinition);
powerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = 10;
powerUpDefinition.TargetAttribute = Stats.MagicSpeed.GetPersistent(gameConfiguration);
}
private void SetItemEffect(GameConfiguration gameConfiguration, ItemIdentifier itemIdentifier, MagicEffectNumber effectNumber)
{
var item = gameConfiguration.Items.First(i => i.Number == itemIdentifier.Number && i.Group == itemIdentifier.Group);
item.ConsumeEffect = gameConfiguration.MagicEffects.First(effect => effect.Number == (int)effectNumber);
}
private ItemBasePowerUpDefinition CreateItemBasePowerUpDefinition(IContext context, GameConfiguration gameConfiguration, AttributeDefinition attributeDefinition, float value, AggregateType aggregateType)
{
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = attributeDefinition.GetPersistent(gameConfiguration);
powerUpDefinition.BaseValue = value;
powerUpDefinition.AggregateType = aggregateType;
return powerUpDefinition;
}
}

View File

@@ -0,0 +1,111 @@
// <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
{
/// <summary>
/// Gets the plugin name.
/// </summary>
internal const string PlugInName = "Fix Blood Castle 7/8 Monster Attributes";
/// <summary>
/// Gets the plugin description.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,69 @@
// <copyright file="FixChaosMixesPlugIn095D.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.DataModel.Configuration.ItemCrafting;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the Chaos Weapon, First Wings, Dinorant, and Item Level Upgrade craftings' settings.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("68BC1F35-FC9A-468F-89FB-0940485AC107")]
public class FixChaosMixesPlugIn095D : FixChaosMixesPlugInBase
{
/// <summary>
/// The plug in description.
/// </summary>
private new const string PlugInDescription = "This update fixes the Chaos Weapon, First Wings, Dinorant, and Item Level Upgrade crafting settings.";
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixChaosMixes095d;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
var craftings = gameConfiguration.Monsters.First(m => m.NpcWindow == NpcWindow.ChaosMachine).ItemCraftings;
this.ApplyFirstWingsCraftingUpdate(craftings);
this.ApplyDinorantCraftingUpdate(craftings);
this.ApplyDinorantOptionsUpdate(gameConfiguration);
// Item Level Upgrade craftings
int[] itemLevelUpgradeCraftingNos = [3, 4];
for (int i = 0; i < itemLevelUpgradeCraftingNos.Length; i++)
{
if (craftings.Single(c => c.Number == itemLevelUpgradeCraftingNos[i])?.SimpleCraftingSettings is { } craftingSettings)
{
craftingSettings.Money = 2_000_000 * (10 + i - 9);
craftingSettings.SuccessPercent = (byte)(10 + i == 10 ? 50 : 45);
craftingSettings.SuccessPercentageAdditionForLuck = 25;
craftingSettings.SuccessPercentageAdditionForExcellentItem = 0;
craftingSettings.SuccessPercentageAdditionForAncientItem = 0;
craftingSettings.SuccessPercentageAdditionForSocketItem = 0;
foreach (var item in craftingSettings.RequiredItems)
{
item.FailResult = MixResult.Disappear;
if (item.MaximumAmount == 0)
{
item.MaximumAmount = item.MinimumAmount;
}
}
}
}
}
}

View File

@@ -0,0 +1,159 @@
// <copyright file="FixChaosMixesPlugInBase.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 MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.PlayerActions.Craftings;
/// <summary>
/// This update fixes the Chaos Weapon crafting settings.
/// </summary>
public abstract class FixChaosMixesPlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Chaos Mixes Settings";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes Chaos Weapon crafting settings.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 12, 10, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Misc item fixes
Guid crystalSwordId = new("00000080-0002-0005-0000-000000000000");
if (gameConfiguration.Items.FirstOrDefault(id => id.GetId() == crystalSwordId) is { } crystalSword)
{
crystalSword.Width = 2;
}
Guid powerWaveScrollId = new("00000080-000f-000a-0000-000000000000");
if (gameConfiguration.Items.FirstOrDefault(id => id.GetId() == powerWaveScrollId) is { } powerWaveScroll)
{
powerWaveScroll.Value = 1100;
}
// Fix Chaos Weapon crafting
var craftings = gameConfiguration.Monsters.First(m => m.NpcWindow == NpcWindow.ChaosMachine).ItemCraftings;
if (craftings.Single(c => c.Number == 1) is { } chaosWeaponCrafting)
{
chaosWeaponCrafting.ItemCraftingHandlerClassName = typeof(ChaosWeaponAndFirstWingsCrafting).FullName!;
if (chaosWeaponCrafting.SimpleCraftingSettings is { } settings)
{
settings.SuccessPercent = 0;
settings.NpcPriceDivisor = 20_000;
foreach (var requiredItem in settings.RequiredItems)
{
requiredItem.AddPercentage = 0;
if (requiredItem.FailResult != MixResult.Disappear)
{
requiredItem.NpcPriceDivisor = 0;
requiredItem.FailResult = MixResult.ChaosWeaponAndFirstWingsDowngradedRandom;
}
}
settings.ResultItemLuckOptionChance = 0;
settings.ResultItemSkillChance = 0;
foreach (var resultItem in settings.ResultItems)
{
resultItem.RandomMinimumLevel = 0;
resultItem.RandomMaximumLevel = 4;
}
}
}
}
/// <summary>
/// Applies First Wings crafting settings update.
/// </summary>
/// <param name="craftings">The craftings collection.</param>
protected void ApplyFirstWingsCraftingUpdate(ICollection<ItemCrafting> craftings)
{
if (craftings.Single(c => c.Number == 11) is { } firstWingsCrafting)
{
firstWingsCrafting.ItemCraftingHandlerClassName = typeof(ChaosWeaponAndFirstWingsCrafting).FullName!;
if (firstWingsCrafting.SimpleCraftingSettings is { } settings)
{
settings.SuccessPercent = 0;
settings.NpcPriceDivisor = 20_000;
foreach (var requiredItem in settings.RequiredItems)
{
requiredItem.AddPercentage = 0;
requiredItem.NpcPriceDivisor = 0;
if (requiredItem.MaximumAmount == 1)
{
// Chaos weapon
requiredItem.FailResult = MixResult.ChaosWeaponAndFirstWingsDowngradedRandom;
}
else
{
requiredItem.FailResult = MixResult.Disappear;
}
}
settings.ResultItemLuckOptionChance = 0;
}
}
}
/// <summary>
/// Applies Dinorant crafting settings update.
/// </summary>
/// <param name="craftings">The craftings collection.</param>
protected void ApplyDinorantCraftingUpdate(ICollection<ItemCrafting> craftings)
{
if (craftings.Single(c => c.Number == 5) is { } dinorantCrafting)
{
dinorantCrafting.ItemCraftingHandlerClassName = typeof(DinorantCrafting).FullName!;
if (dinorantCrafting.SimpleCraftingSettings is { } settings)
{
settings.ResultItemExcellentOptionChance = 0;
}
}
}
/// <summary>
/// Applies Dinorant options update.
/// </summary>
/// <param name="gameConfiguration">The game configuration.</param>
protected void ApplyDinorantOptionsUpdate(GameConfiguration gameConfiguration)
{
if (gameConfiguration.ItemOptions.Single(iod => iod.Name == "Dinorant Options") is { } dinoOpts
&& gameConfiguration.ItemOptionTypes.Single(iot => iot == ItemOptionTypes.Option) is { } itemOption)
{
dinoOpts.AddChance = 0.3f;
foreach (var opt in dinoOpts.PossibleOptions)
{
opt.OptionType = itemOption;
opt.Number = 4;
}
}
}
}

View File

@@ -0,0 +1,423 @@
// <copyright file="FixChaosMixesPlugInSeason6.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.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlayerActions.Craftings;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the Chaos Weapon, First Wings, Dinorant, Item Level Upgrade, Second Wings, Third Wings, Cape, SD Potions, Guardian Option, and Secromicon crafting settings; Blue Fenrir (Protect) damage decrease option value; Wizard's Ring wizardry option; lvl 380 item guardian options for Summoner and Rage Fighter.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("EFD7EA69-56AE-48A3-ACE2-1C3B5B87780A")]
public class FixChaosMixesPlugInSeason6 : FixChaosMixesPlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
private new const string PlugInName = "Fix Chaos Mixes Settings And Several Options";
/// <summary>
/// The plug in description.
/// </summary>
private new const string PlugInDescription = "This update fixes the Chaos Weapon, First Wings, Dinorant, Item Level Upgrade, Second Wings, Third Wings, Cape, SD Potions, Guardian Option, and Secromicon crafting settings; Blue Fenrir (Protect) damage decrease option value; Wizard's Ring wizardry option; lvl 380 item guardian options for Summoner and Rage Fighter.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixChaosMixesSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Dark horse spirit and raven spirit drop item groups id fix (do this first because it persists changes to DB)
if (gameConfiguration.Items.Single(id => id.Name == "Spirit") is { } spirit)
{
spirit.MaximumItemLevel = 1;
var maps = gameConfiguration.Maps;
if (gameConfiguration.DropItemGroups.Single(dig => dig.Description == "Dark Horse Spirit") is { } oldHorseGroup
&& gameConfiguration.DropItemGroups.Single(dig => dig.Description == "Dark Raven Spirit") is { } oldRavenGroup)
{
await DeleteDropItemGroupAsync(oldHorseGroup).ConfigureAwait(false);
await DeleteDropItemGroupAsync(oldRavenGroup).ConfigureAwait(false);
CreateDropItemGroup(0, "Dark Horse Spirit", 102);
CreateDropItemGroup(1, "Dark Raven Spirit", 96);
}
async ValueTask DeleteDropItemGroupAsync(DropItemGroup group)
{
group.PossibleItems.Clear();
foreach (var map in maps)
{
if (map.DropItemGroups.FirstOrDefault(dig => dig.GetId() == group.GetId()) is { } mapDropItemGroup)
{
map.DropItemGroups.Remove(mapDropItemGroup);
}
}
gameConfiguration.DropItemGroups.Remove(group);
await context.DeleteAsync(group).ConfigureAwait(false);
}
void CreateDropItemGroup(int itemLevel, string description, short minimumMonsterLevel)
{
var group = context.CreateNew<DropItemGroup>();
group.SetGuid(NumberConversionExtensions.MakeWord(13, 31).ToSigned(), (short)itemLevel, 1);
group.ItemLevel = (byte)itemLevel;
group.Chance = 0.001;
group.Description = description;
group.PossibleItems.Add(spirit);
group.MinimumMonsterLevel = (byte)minimumMonsterLevel;
gameConfiguration.DropItemGroups.Add(group);
foreach (var map in maps)
{
map.DropItemGroups.Add(group);
}
}
}
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
var goblinCraftings = gameConfiguration.Monsters.Single(m => m.NpcWindow == NpcWindow.ChaosMachine).ItemCraftings;
var petTrainercraftings = gameConfiguration.Monsters.Single(m => m.NpcWindow == NpcWindow.PetTrainer).ItemCraftings;
this.ApplyFirstWingsCraftingUpdate(goblinCraftings);
this.ApplyDinorantCraftingUpdate(goblinCraftings);
this.ApplyDinorantOptionsUpdate(gameConfiguration);
// Fenrir dmg decrease fix
Guid fenrirOptionsId = new("00000083-0081-0000-0000-000000000000");
if (gameConfiguration.ItemOptions.Single(iod => iod.GetId() == fenrirOptionsId) is { } fenrirOpts
&& fenrirOpts.PossibleOptions.Single(iio => iio.PowerUpDefinition?.TargetAttribute == Stats.DamageReceiveDecrement) is { } dmgDecreaseOpt
&& dmgDecreaseOpt.PowerUpDefinition?.Boost is { } boost)
{
boost.ConstantValue.Value = 0.90f;
}
// Wizard's Ring wizardry option fix
Guid wizardsRingId = new("00000080-000d-0014-0000-000000000000");
if (gameConfiguration.Items.Single(id => id.GetId() == wizardsRingId) is { } wizardsRing)
{
wizardsRing.Durability = 30;
if (wizardsRing.PossibleItemOptions.FirstOrDefault() is { } wizardsRingOpts)
{
wizardsRingOpts.MaximumOptionsPerItem = 3;
var increaseWizardryDamage = context.CreateNew<IncreasableItemOption>();
increaseWizardryDamage.SetGuid(ItemOptionDefinitionNumbers.WizardRing, 3);
increaseWizardryDamage.PowerUpDefinition = context.CreateNew<PowerUpDefinition>();
increaseWizardryDamage.PowerUpDefinition.TargetAttribute = Stats.WizardryAttackDamageIncrease.GetPersistent(gameConfiguration);
increaseWizardryDamage.PowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
increaseWizardryDamage.PowerUpDefinition.Boost.ConstantValue.Value = 0.1f;
wizardsRingOpts.PossibleOptions.Add(increaseWizardryDamage);
}
}
// Remove Raven's lvl requirement
Guid darkRavenId = new("00000080-000d-0005-0000-000000000000");
if (gameConfiguration.Items.Single(id => id.GetId() == darkRavenId) is { } darkRaven)
{
darkRaven.Requirements.Clear();
}
// Aura/Storm Blitz Set definitions fix
for (int i = 7; i <= 11; i++)
{
if (gameConfiguration.Items.FirstOrDefault(id => id.Group == i && id.Number == 43) is { } auraItem)
{
if (gameConfiguration.ItemOptions.FirstOrDefault(io => io.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.GuardianOption && po.Number == i)) is { } guardOpt)
{
auraItem.PossibleItemOptions.Add(guardOpt);
}
if (auraItem.Requirements.FirstOrDefault(r => r.Attribute == Stats.Level) is { } auraLvlRequirement)
{
auraLvlRequirement.MinimumValue = 380;
}
}
}
// Phoenix Soul Set definitions fix
for (int i = 7; i <= 11; i++)
{
if (gameConfiguration.Items.FirstOrDefault(id => id.Group == i && id.Number == 73) is { } phoenixSoulItem)
{
if (gameConfiguration.ItemOptions.FirstOrDefault(io => io.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.GuardianOption && po.Number == i)) is { } guardOpt)
{
phoenixSoulItem.PossibleItemOptions.Add(guardOpt);
}
if (phoenixSoulItem.Requirements.FirstOrDefault(r => r.Attribute == Stats.Level) is { } psLvlRequirement)
{
psLvlRequirement.MinimumValue = 380;
}
}
}
// Phoenix Soul Star definition fix
Guid phoenixSoulStarId = new("00000080-0000-0023-0000-000000000000");
if (gameConfiguration.Items.FirstOrDefault(id => id.GetId() == phoenixSoulStarId) is { } phoenixSoulStar)
{
if (gameConfiguration.ItemOptions.FirstOrDefault(io => io.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.GuardianOption && po.Number == (int)ItemGroups.Weapon)) is { } weapGuardOpt)
{
phoenixSoulStar.PossibleItemOptions.Add(weapGuardOpt);
}
if (phoenixSoulStar.Requirements.FirstOrDefault(r => r.Attribute == Stats.Level) is { } pssLvlRequirement)
{
pssLvlRequirement.MinimumValue = 380;
}
}
// ---> Fix chaos mixes settings
// Item Level Upgrade craftings
int[] itemLevelUpgradeCraftingNos = [3, 4, 22, 23, 49, 50];
for (int i = 0; i < itemLevelUpgradeCraftingNos.Length; i++)
{
if (goblinCraftings.Single(c => c.Number == itemLevelUpgradeCraftingNos[i])?.SimpleCraftingSettings is { } craftingSettings)
{
craftingSettings.Money = 2_000_000 * (10 + i - 9);
craftingSettings.SuccessPercentageAdditionForLuck = 25;
craftingSettings.SuccessPercentageAdditionForAncientItem = -10;
craftingSettings.SuccessPercentageAdditionForGuardianItem = -10;
foreach (var item in craftingSettings.RequiredItems)
{
item.FailResult = MixResult.Disappear;
if (item.MaximumAmount == 0)
{
item.MaximumAmount = item.MinimumAmount;
}
}
}
}
// Second Wings crafting
if (goblinCraftings.Single(c => c.Number == 7) is { } secondWingsCrafting)
{
secondWingsCrafting.ItemCraftingHandlerClassName = typeof(SecondWingsCrafting).FullName!;
if (secondWingsCrafting.SimpleCraftingSettings is { } settings)
{
settings.MoneyPerFinalSuccessPercentage = 0;
foreach (var requiredItem in settings.RequiredItems)
{
if (requiredItem.FailResult != MixResult.Disappear)
{
// 1st level wings or exc item
requiredItem.FailResult = MixResult.Disappear;
}
else
{
// chaos or feather
requiredItem.AddPercentage = 0;
requiredItem.MaximumAmount = 1;
}
}
settings.ResultItemLuckOptionChance = 20;
}
}
// Thirds Wings, Stage 1 crafting
if (goblinCraftings.Single(c => c.Number == 38) is { } thirdWingsS1Crafting)
{
if (thirdWingsS1Crafting.SimpleCraftingSettings is { } settings)
{
settings.SuccessPercent = 1;
foreach (var requiredItem in settings.RequiredItems)
{
if (requiredItem.FailResult != MixResult.Disappear)
{
// 2nd level wings or anc item
requiredItem.FailResult = MixResult.ThirdWingsDowngradedRandom;
if (requiredItem.MinimumItemLevel == 9)
{
// 2nd lvl wings
requiredItem.NpcPriceDivisor = 0;
}
}
}
}
}
// Thirds Wings, Stage 2 crafting
if (goblinCraftings.Single(c => c.Number == 39) is { } thirdWingsS2Crafting)
{
thirdWingsS2Crafting.ItemCraftingHandlerClassName = typeof(ThirdWingsCrafting).FullName!;
if (thirdWingsS2Crafting.SimpleCraftingSettings is { } settings)
{
settings.Money = 0;
settings.MoneyPerFinalSuccessPercentage = 200_000;
settings.SuccessPercent = 1;
foreach (var requiredItem in settings.RequiredItems)
{
if (requiredItem.FailResult != MixResult.Disappear)
{
// exc item
requiredItem.MinimumAmount = 1;
requiredItem.FailResult = MixResult.ThirdWingsDowngradedRandom;
}
else
{
requiredItem.MaximumAmount = 1;
}
}
settings.ResultItemLuckOptionChance = 5;
settings.ResultItemExcellentOptionChance = 0;
settings.ResultItemMaxExcOptionCount = 0;
}
}
// Cape crafting
if (goblinCraftings.Single(c => c.Number == 24) is { } capeCrafting)
{
capeCrafting.ItemCraftingHandlerClassName = typeof(SecondWingsCrafting).FullName!;
if (capeCrafting.SimpleCraftingSettings is { } settings)
{
settings.MoneyPerFinalSuccessPercentage = 0;
foreach (var requiredItem in settings.RequiredItems)
{
if (requiredItem.FailResult != MixResult.Disappear)
{
// 1st level wings or exc item
requiredItem.FailResult = MixResult.Disappear;
if (requiredItem.NpcPriceDivisor == 4_000_000)
{
// 1st level wings
requiredItem.MinimumItemLevel = 0;
}
}
else
{
// chaos or crest
requiredItem.AddPercentage = 0;
requiredItem.MaximumAmount = 1;
}
}
settings.ResultItemLuckOptionChance = 20;
settings.ResultItemExcellentOptionChance = 20;
settings.ResultItemMaxExcOptionCount = 1;
}
}
// Fruit crafting
if (goblinCraftings.Single(c => c.Number == 6) is { } fruitCrafting)
{
fruitCrafting.ItemCraftingHandlerClassName = typeof(SecondWingsCrafting).FullName!;
if (fruitCrafting.SimpleCraftingSettings is { } settings)
{
settings.MoneyPerFinalSuccessPercentage = 0;
foreach (var resultItem in settings.ResultItems)
{
resultItem.RandomMaximumLevel = 0;
}
}
}
// Small, Medium, and Large Shield Potion craftings
for (int i = 30; i <= 32; i++)
{
if (goblinCraftings.Single(c => c.Number == i) is { } smallShieldPotCrafting)
{
if (smallShieldPotCrafting.SimpleCraftingSettings is { } settings)
{
foreach (var resultItem in settings.ResultItems)
{
resultItem.Durability = 1;
}
}
}
}
// Guardian Option crafting
if (goblinCraftings.Single(c => c.Number == 36) is { } guardianOptionCrafting)
{
if (guardianOptionCrafting.SimpleCraftingSettings is { } settings)
{
foreach (var requiredItem in settings.RequiredItems.OrderBy(i => i.MinimumItemLevel))
{
if (requiredItem.MinimumItemLevel < 10)
{
continue;
}
else if (requiredItem.MinimumItemLevel == 10)
{
requiredItem.MaximumItemLevel = 15;
}
else
{
settings.RequiredItems.Remove(requiredItem);
break;
}
}
}
}
// Secromicon crafting
if (goblinCraftings.Single(c => c.Number == 46) is { } secromiconCrafting)
{
if (secromiconCrafting.SimpleCraftingSettings is { } settings)
{
settings.Money = 1_000_000;
}
}
// Dark Horse crafting
if (petTrainercraftings.Single(c => c.Number == 13) is { } darkHorseCrafting)
{
if (darkHorseCrafting.SimpleCraftingSettings is { } settings)
{
if (settings.ResultItems.First() is { } darkHorseResult)
{
darkHorseResult.RandomMinimumLevel = 1;
darkHorseResult.RandomMaximumLevel = 1;
}
}
}
// Dark Raven crafting
if (petTrainercraftings.Single(c => c.Number == 14) is { } darkRavenCrafting)
{
if (darkRavenCrafting.SimpleCraftingSettings is { } settings)
{
settings.ResultItemSkillChance = 0;
if (settings.ResultItems.First() is { } darkRavenResult)
{
darkRavenResult.RandomMinimumLevel = 1;
darkRavenResult.RandomMaximumLevel = 1;
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="FixChaosMixesUpdatePlugIn075.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.PlugIns;
/// <summary>
/// This update fixes the Chaos Weapon crafting settings.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("04A5F236-117F-422A-8C38-28D09DE911D7")]
public class FixChaosMixesUpdatePlugIn075 : FixChaosMixesPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixChaosMixes075;
}

View File

@@ -0,0 +1,23 @@
// <copyright file="FixCharStatsForceWavePlugIn075.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.PlugIns;
/// <summary>
/// This update fixes DW agility to defense multiplier stat.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("D7CD05B7-06EE-4D9F-BAD0-65267F3A9FE8")]
public class FixCharStatsForceWavePlugIn075 : FixCharStatsForceWavePlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixCharStatsForceWave075;
}

View File

@@ -0,0 +1,47 @@
// <copyright file="FixCharStatsForceWavePlugIn095d.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>
/// This update fixes agility to defense multiplier (DW) and base energy (MG) stats.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("14DFF317-B4E6-424A-A8D1-6D1D5195E970")]
public class FixCharStatsForceWavePlugIn095D : FixCharStatsForceWavePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal new const string PlugInName = "Fix DW and MG Char Stats";
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInDescription = "This update fixes agility to defense multiplier (DW) and base energy (MG) stats.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixCharStatsForceWave095d;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.UpdateMagicGladiatorClassesStats(gameConfiguration);
}
}

View File

@@ -0,0 +1,60 @@
// <copyright file="FixCharStatsForceWavePlugInBase.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 MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// This update fixes DW agility to defense multiplier stat.
/// </summary>
public abstract class FixCharStatsForceWavePlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix DW Defense Multiplier";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes DW agility to defense multiplier stat.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 03, 24, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var wizardClasses = gameConfiguration.CharacterClasses.Where(charClass => charClass.Number == 0 || charClass.Number == 2 || charClass.Number == 3);
foreach (var wizardClass in wizardClasses)
{
if (wizardClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.DefenseBase && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToDefenseBase)
{
totalAgilityToDefenseBase.InputOperand = 0.25f;
}
}
}
#pragma warning disable SA1600, CS1591 // Elements should be documented.
protected void UpdateMagicGladiatorClassesStats(GameConfiguration gameConfiguration)
{
var magicGladiatorClasses = gameConfiguration.CharacterClasses.Where(charClass => charClass.Number == 12 || charClass.Number == 13);
foreach (var magicGladiatorClass in magicGladiatorClasses)
{
magicGladiatorClass.StatAttributes.First(attr => attr.Attribute == Stats.BaseEnergy).BaseValue = 26;
}
}
#pragma warning restore SA1600, CS1591 // Elements should be documented.
}

View File

@@ -0,0 +1,207 @@
// <copyright file="FixCharStatsForceWavePlugInSeason6.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.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes several character stats values and DL Force Wave Strengthener master skill.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("0C1995AB-A1CC-42A8-9EFC-E5FE8F360C53")]
public class FixCharStatsForceWavePlugInSeason6 : FixCharStatsForceWavePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal new const string PlugInName = "Fix Char Stats and DL Force Wave Str skill";
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInDescription = "This update fixes several character stats values and DL Force Wave Strengthener master skill.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixCharStatsForceWaveSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.UpdateMagicGladiatorClassesStats(gameConfiguration);
var totalLevel = Stats.TotalLevel.GetPersistent(gameConfiguration);
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
foreach (var attrCombo in charClass.AttributeCombinations)
{
if (attrCombo.TargetAttribute == Stats.AttackRatePvm && attrCombo.InputAttribute == Stats.Level)
{
attrCombo.InputAttribute = totalLevel;
}
else if (attrCombo.TargetAttribute == Stats.AttackRatePvp && attrCombo.InputAttribute == Stats.Level)
{
attrCombo.InputAttribute = totalLevel;
}
else if (attrCombo.TargetAttribute == Stats.DefenseRatePvp && attrCombo.InputAttribute == Stats.Level)
{
attrCombo.InputAttribute = totalLevel;
}
else if (attrCombo.TargetAttribute == Stats.MaximumMana && attrCombo.InputAttribute == Stats.Level)
{
attrCombo.InputAttribute = totalLevel;
}
else if (attrCombo.TargetAttribute == Stats.MaximumHealth && attrCombo.InputAttribute == Stats.Level)
{
attrCombo.InputAttribute = totalLevel;
}
else if (attrCombo.TargetAttribute == Stats.MaximumShieldTemp && attrCombo.InputAttribute == Stats.Level)
{
attrCombo.InputAttribute = totalLevel;
}
else
{
// nothing to do
}
}
// Lord classes.
if (charClass.Number == 16 || charClass.Number == 17)
{
charClass.StatAttributes.First(attr => attr.Attribute == Stats.CurrentHealth).BaseValue = 90;
charClass.StatAttributes.First(attr => attr.Attribute == Stats.CurrentMana).BaseValue = 40;
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.MaximumMana && attrCombo.InputAttribute == Stats.TotalLevel) is { } totalLevelToMaximumMana)
{
totalLevelToMaximumMana.InputOperand = 1;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.MaximumHealth && attrCombo.InputAttribute == Stats.TotalLevel) is { } totalLevelToMaximumHealth)
{
totalLevelToMaximumHealth.InputOperand = 1.5f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.MaximumHealth && attrCombo.InputAttribute == Stats.TotalVitality) is { } totalVitalityoMaximumHealth)
{
totalVitalityoMaximumHealth.InputOperand = 2;
}
}
// RF classes.
else if (charClass.Number == 24 || charClass.Number == 25)
{
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.DefenseRatePvp && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToDefenseRatePvp)
{
totalAgilityToDefenseRatePvp.InputOperand = 0.2f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.DefenseRatePvp && attrCombo.InputAttribute == Stats.TotalLevel) is { } totalLevelToDefenseRatePvp)
{
totalLevelToDefenseRatePvp.InputOperand = 1.5f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.AttackRatePvm && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToAttackRatePvm)
{
totalAgilityToAttackRatePvm.InputOperand = 1.25f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.AttackRatePvm && attrCombo.InputAttribute == Stats.TotalStrength) is { } totalStrengthToAttackRatePvm)
{
totalStrengthToAttackRatePvm.InputOperand = 1.0f / 6;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.AttackRatePvp && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToAttackRatePvp)
{
totalAgilityToAttackRatePvp.InputOperand = 3.6f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.AttackRatePvp && attrCombo.InputAttribute == Stats.TotalLevel) is { } totalLevelToAttackRatePvp)
{
totalLevelToAttackRatePvp.InputOperand = 2.6f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.AttackRatePvp && attrCombo.InputAttribute == Stats.TotalEnergy) is { } totalEnergyToAttackRatePvp)
{
charClass.AttributeCombinations.Remove(totalEnergyToAttackRatePvp);
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.FenrirBaseDmg && attrCombo.InputAttribute == Stats.TotalStrength) is { } totalStrengthToFenrirBaseDmg)
{
totalStrengthToFenrirBaseDmg.InputOperand = 1.0f / 5;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.FenrirBaseDmg && attrCombo.InputAttribute == Stats.TotalVitality) is { } totalVitalityToFenrirBaseDmg)
{
totalVitalityToFenrirBaseDmg.InputOperand = 1.0f / 3;
}
}
// Summoner classes.
else if (charClass.Number == 20 || charClass.Number == 22 || charClass.Number == 23)
{
charClass.StatAttributes.First(attr => attr.Attribute == Stats.CurrentMana).BaseValue = 40;
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.DefenseBase && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToDefenseBase)
{
totalAgilityToDefenseBase.InputOperand = 1.0f / 3;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.DefenseRatePvp && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToDefenseRatePvp)
{
totalAgilityToDefenseRatePvp.InputOperand = 0.5f;
}
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.AttackRatePvp && attrCombo.InputAttribute == Stats.TotalAgility) is { } totalAgilityToAttackRatePvp)
{
totalAgilityToAttackRatePvp.InputOperand = 3.5f;
}
}
else
{
// nothing to do
}
});
// Update Force Wave Strengthener skill
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ForceWave) is { } forceWave
&& gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ForceWaveStreng) is { } forceWaveStr)
{
forceWaveStr.AttackDamage = forceWave.AttackDamage;
forceWaveStr.DamageType = forceWave.DamageType;
forceWaveStr.ElementalModifierTarget = forceWave.ElementalModifierTarget;
forceWaveStr.ImplicitTargetRange = forceWave.ImplicitTargetRange;
forceWaveStr.MovesTarget = forceWave.MovesTarget;
forceWaveStr.MovesToTarget = forceWave.MovesToTarget;
forceWaveStr.SkillType = forceWave.SkillType;
forceWaveStr.Target = forceWave.Target;
forceWaveStr.TargetRestriction = forceWave.TargetRestriction;
forceWaveStr.MagicEffectDef = forceWave.MagicEffectDef;
if (forceWave.AreaSkillSettings is { } areaSkillSettings)
{
forceWaveStr.AreaSkillSettings = context.CreateNew<AreaSkillSettings>();
var id = forceWaveStr.AreaSkillSettings.GetId();
forceWaveStr.AreaSkillSettings.AssignValuesOf(areaSkillSettings, gameConfiguration);
forceWaveStr.AreaSkillSettings.SetGuid(id);
}
}
}
}

View File

@@ -0,0 +1,392 @@
// <copyright file="FixDamageAbsorbItemsUpdatePlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the damage absorption settings for items and skills.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("280ACE93-2B96-476C-A4AF-4FDA7611D5D5")]
public class FixDamageAbsorbItemsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fixed damage absorb items/skills";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the damage absorbtion settings for items and skills.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDamageAbsorbItems;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 16, 14, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
FixSoulBarrierEffect(gameConfiguration);
FixGuardianAngel(gameConfiguration);
FixImp(gameConfiguration);
FixDinorant(gameConfiguration);
FixDemon(gameConfiguration);
FixSpiritOfGuardian(gameConfiguration);
FixPanda(gameConfiguration);
FixUnicorn(gameConfiguration);
FixSkeleton(gameConfiguration, context);
FixHarmonyDefenseOption(gameConfiguration);
FixWings(gameConfiguration, 12, 0);
FixWings(gameConfiguration, 12, 1);
FixWings(gameConfiguration, 12, 2);
FixWings(gameConfiguration, 12, 41);
FixWings(gameConfiguration, 12, 3);
FixWings(gameConfiguration, 12, 4);
FixWings(gameConfiguration, 12, 5);
FixWings(gameConfiguration, 12, 6);
FixWings(gameConfiguration, 12, 42);
FixWings(gameConfiguration, 12, 49);
FixWings(gameConfiguration, 13, 30);
FixWings(gameConfiguration, 12, 36);
FixWings(gameConfiguration, 12, 37);
FixWings(gameConfiguration, 12, 38);
FixWings(gameConfiguration, 12, 39);
FixWings(gameConfiguration, 12, 40);
FixWings(gameConfiguration, 12, 43);
FixWings(gameConfiguration, 12, 50);
FixDamageIncreaseTable(gameConfiguration, 12, 0); // First and Third Wings
FixDamageIncreaseTable(gameConfiguration, 12, 3); // Second Wings
FixDamageAbsorbTable(gameConfiguration); // One for all
}
private static void FixDamageIncreaseTable(GameConfiguration gameConfiguration, short group, short number)
{
var wings = gameConfiguration.Items.FirstOrDefault(item => item.Group == group && item.Number == number);
if (wings is null)
{
return;
}
var damageIncreaseTable = wings.BasePowerUpAttributes
.FirstOrDefault(a => a.TargetAttribute == Stats.AttackDamageIncrease)
?.BonusPerLevelTable;
if (damageIncreaseTable is null)
{
return;
}
foreach (var row in damageIncreaseTable.BonusPerLevel)
{
if (row.AdditionalValue < 1)
{
row.AdditionalValue += 1f;
}
}
}
private static void FixDamageAbsorbTable(GameConfiguration gameConfiguration)
{
var wings = gameConfiguration.Items.FirstOrDefault(item => item.Group == 12 && item.Number == 0);
if (wings is null)
{
return;
}
var damageDecreaseTable = wings.BasePowerUpAttributes
.FirstOrDefault(a => a.TargetAttribute == Stats.DamageReceiveDecrement)
?.BonusPerLevelTable;
if (damageDecreaseTable is null)
{
return;
}
foreach (var row in damageDecreaseTable.BonusPerLevel)
{
if (row.AdditionalValue <= 0)
{
row.AdditionalValue += 1f;
}
}
}
private static void FixHarmonyDefenseOption(GameConfiguration gameConfiguration)
{
var harmonyOption = gameConfiguration.ItemOptions
.FirstOrDefault(io => io.Name == HarmonyOptions.DefenseOptionsName)
?.PossibleOptions.FirstOrDefault(o => o.Number == 7);
if (harmonyOption is null)
{
return;
}
var options = harmonyOption.LevelDependentOptions.OrderBy(o => o.RequiredItemLevel).ToList();
float[] values = [0.97f, 0.96f, 0.95f, 0.94f, 0.93f];
for (int i = 0; i < values.Length; i++)
{
var option = options[i];
var boost = option.PowerUpDefinition?.Boost;
if (boost is null)
{
continue;
}
boost.ConstantValue.Value = values[i];
boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixWings(GameConfiguration gameConfiguration, short group, short number)
{
var wings = gameConfiguration.Items.FirstOrDefault(item => item.Group == group && item.Number == number);
if (wings is null)
{
return;
}
var damageDecrease = wings.BasePowerUpAttributes
.FirstOrDefault(a => a.TargetAttribute == Stats.DamageReceiveDecrement && a.AggregateType != AggregateType.Multiplicate);
if (damageDecrease is not null)
{
damageDecrease.BaseValue += 1f;
damageDecrease.AggregateType = AggregateType.Multiplicate;
}
var damageIncrease = wings.BasePowerUpAttributes
.FirstOrDefault(a => a.TargetAttribute == Stats.AttackDamageIncrease && a.AggregateType != AggregateType.Multiplicate);
if (damageIncrease is not null)
{
damageIncrease.BaseValue += 1f;
damageIncrease.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixGuardianAngel(GameConfiguration gameConfiguration)
{
var angel = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 0 });
if (angel is null)
{
return;
}
var damageDecrease = angel.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.DamageReceiveDecrement);
if (damageDecrease is not null)
{
damageDecrease.BaseValue = 0.8f;
damageDecrease.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixImp(GameConfiguration gameConfiguration)
{
var imp = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 1 });
if (imp is null)
{
return;
}
var damageIncrease = imp.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.AttackDamageIncrease);
if (damageIncrease is not null)
{
damageIncrease.BaseValue = 1.3f;
damageIncrease.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixDemon(GameConfiguration gameConfiguration)
{
var demon = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 64 });
if (demon is null)
{
return;
}
var damageIncrease = demon.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.AttackDamageIncrease);
if (damageIncrease is not null)
{
damageIncrease.BaseValue = 1.4f;
damageIncrease.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixSpiritOfGuardian(GameConfiguration gameConfiguration)
{
var spiritOfGuardian = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 65 });
if (spiritOfGuardian is null)
{
return;
}
var damageDecrease = spiritOfGuardian.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.DamageReceiveDecrement);
if (damageDecrease is not null)
{
damageDecrease.BaseValue = 0.7f;
damageDecrease.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixDinorant(GameConfiguration gameConfiguration)
{
var dinorant = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 2 });
if (dinorant is null)
{
return;
}
var damageDecrease = dinorant.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.DamageReceiveDecrement);
if (damageDecrease is not null)
{
damageDecrease.BaseValue = 0.9f;
damageDecrease.AggregateType = AggregateType.Multiplicate;
}
var damageIncrease = dinorant.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.AttackDamageIncrease);
if (damageIncrease is not null)
{
damageIncrease.BaseValue = 1.15f;
damageIncrease.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixPanda(GameConfiguration gameConfiguration)
{
var panda = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 80 });
if (panda is null)
{
return;
}
var exp = panda.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.ExperienceRate);
if (exp is not null)
{
exp.BaseValue = 1.5f;
exp.AggregateType = AggregateType.Multiplicate;
}
var masterExp = panda.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.MasterExperienceRate);
if (masterExp is not null)
{
masterExp.BaseValue = 1.5f;
masterExp.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixUnicorn(GameConfiguration gameConfiguration)
{
var panda = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 106 });
if (panda is null)
{
return;
}
var moneyRate = panda.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.MoneyAmountRate);
if (moneyRate is not null)
{
moneyRate.BaseValue = 1.5f;
moneyRate.AggregateType = AggregateType.Multiplicate;
}
}
private static void FixSkeleton(GameConfiguration gameConfiguration, IContext context)
{
var skeleton = gameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 123 });
if (skeleton is null)
{
return;
}
var damageIncrease = skeleton.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.AttackDamageIncrease);
if (damageIncrease is not null)
{
damageIncrease.BaseValue = 1.2f;
damageIncrease.AggregateType = AggregateType.Multiplicate;
}
var exp = skeleton.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.ExperienceRate);
if (exp is not null)
{
exp.BaseValue = 1.3f;
exp.AggregateType = AggregateType.Multiplicate;
}
var masterExp = skeleton.BasePowerUpAttributes.FirstOrDefault(a => a.TargetAttribute == Stats.MasterExperienceRate);
if (masterExp is null)
{
masterExp = context.CreateNew<ItemBasePowerUpDefinition>();
masterExp.TargetAttribute = Stats.MasterExperienceRate.GetPersistent(gameConfiguration);
skeleton.BasePowerUpAttributes.Add(masterExp);
}
masterExp.BaseValue = 1.3f;
masterExp.AggregateType = AggregateType.Multiplicate;
}
private static void FixSoulBarrierEffect(GameConfiguration gameConfiguration)
{
var soulBarrierEffect = gameConfiguration.MagicEffects.FirstOrDefault(effect => effect.Number == (short)MagicEffectNumber.SoulBarrier);
if (soulBarrierEffect is null)
{
return;
}
var powerUp = soulBarrierEffect.PowerUpDefinitions.FirstOrDefault();
if (powerUp is null)
{
return;
}
var boost = powerUp.Boost;
if (boost is null)
{
return;
}
boost.ConstantValue.Value = 0.90f;
boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
var boostPerEnergy = boost.RelatedValues.FirstOrDefault(value => value.InputAttribute == Stats.TotalEnergy);
if (boostPerEnergy is not null)
{
boostPerEnergy.InputOperand = 1 - (0.01f / 200f);
boostPerEnergy.InputOperator = InputOperator.ExponentiateByAttribute;
}
var boostPerAgility = boost.RelatedValues.FirstOrDefault(value => value.InputAttribute == Stats.TotalAgility);
if (boostPerAgility is not null)
{
boostPerAgility.InputOperand = 1 - (0.01f / 50f);
boostPerAgility.InputOperator = InputOperator.ExponentiateByAttribute;
}
}
}

View File

@@ -0,0 +1,31 @@
// <copyright file="FixDamageCalcsPlugIn075.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>
/// This update fixes character stats, magic effects, items, and options related to damage.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("42B1582B-667F-4098-A339-DDA8560157E3")]
public class FixDamageCalcsPlugIn075 : FixDamageCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDamageCalcs075;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.UpdateWeaponItems(context, gameConfiguration);
}
}

View File

@@ -0,0 +1,35 @@
// <copyright file="FixDamageCalcsPlugIn095d.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>
/// This update fixes character stats, skills, magic effects, items, and options related to damage.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A4410B7B-7E5F-409C-9F6F-4E216208829A")]
public class FixDamageCalcsPlugIn095D : FixDamageCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDamageCalcs095d;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.UpdateExcellentOptions(gameConfiguration);
this.UpdateAmmoItems(context, gameConfiguration, [0, 0.03f, 0.05f]);
this.UpdateWeaponItems(context, gameConfiguration);
this.AddDinorantBasePowerUp(context, gameConfiguration);
this.UpdateMGStaffsItemSlot(gameConfiguration);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,450 @@
// <copyright file="FixDamageCalcsPlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.CharacterClasses;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes character stats, skills, magic effects, items, and options related to damage. It also adds the Berserker magic effect.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("077BA63D-F201-41BE-8A65-CFB859482A1B")]
public class FixDamageCalcsPlugInSeason6 : FixDamageCalcsPlugInBase
{
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInDescription = "This update fixes character stats, skills, magic effects, items, and options related to damage. It also adds the Berserker magic effect.";
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDamageCalcsSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.UpdateExcellentOptions(gameConfiguration);
this.UpdateAmmoItems(context, gameConfiguration, [0, 0.03f, 0.05f, 0.07f], true);
this.AddDinorantBasePowerUp(context, gameConfiguration);
this.UpdateMGStaffsItemSlot(gameConfiguration);
// Create Berserker magic effect
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.Berserker;
magicEffect.Name = "Berserker Buff Skill Effect";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 30; // 30 Seconds
var durationPerEnergy = context.CreateNew<AttributeRelationship>();
durationPerEnergy.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
durationPerEnergy.InputOperator = InputOperator.Multiply;
durationPerEnergy.InputOperand = 1f / 20f; // 20 energy adds 1 second duration
magicEffect.Duration.RelatedValues.Add(durationPerEnergy);
// Mana (and damage) multiplier (buff)
var manaPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(manaPowerUpDefinition);
manaPowerUpDefinition.TargetAttribute = Stats.BerserkerManaMultiplier.GetPersistent(gameConfiguration);
manaPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
var manaMultiplier = context.CreateNew<AttributeRelationship>();
manaMultiplier.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
manaMultiplier.InputOperator = InputOperator.Multiply;
manaMultiplier.InputOperand = 1f / 3000f;
manaPowerUpDefinition.Boost.RelatedValues.Add(manaMultiplier);
// Health (and defense) multiplier factor (debuff)
var healthPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(healthPowerUpDefinition);
healthPowerUpDefinition.TargetAttribute = Stats.BerserkerHealthDecrement.GetPersistent(gameConfiguration);
healthPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
healthPowerUpDefinition.Boost.ConstantValue.Value = -0.4f;
var healthMultiplier = context.CreateNew<AttributeRelationship>();
healthMultiplier.InputAttribute = Stats.TotalEnergy.GetPersistent(gameConfiguration);
healthMultiplier.InputOperator = InputOperator.Multiply;
healthMultiplier.InputOperand = 1f / 6000f;
healthPowerUpDefinition.Boost.RelatedValues.Add(healthMultiplier);
// Min physical damage bonus (later gets multiplied by the mana multiplier)
var minPhysPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(minPhysPowerUpDefinition);
minPhysPowerUpDefinition.TargetAttribute = Stats.BerserkerMinPhysDmgBonus.GetPersistent(gameConfiguration);
minPhysPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
minPhysPowerUpDefinition.Boost.ConstantValue.Value = 140f;
var minDmgPerStrengthAndAgility = context.CreateNew<AttributeRelationship>();
minDmgPerStrengthAndAgility.InputAttribute = Stats.TotalStrengthAndAgility.GetPersistent(gameConfiguration);
minDmgPerStrengthAndAgility.InputOperator = InputOperator.Multiply;
minDmgPerStrengthAndAgility.InputOperand = 1.0f / 50;
minPhysPowerUpDefinition.Boost.RelatedValues.Add(minDmgPerStrengthAndAgility);
// Max physical damage bonus (later gets multiplied by the mana multiplier)
var maxPhysPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(maxPhysPowerUpDefinition);
maxPhysPowerUpDefinition.TargetAttribute = Stats.BerserkerMaxPhysDmgBonus.GetPersistent(gameConfiguration);
maxPhysPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
maxPhysPowerUpDefinition.Boost.ConstantValue.Value = 160f;
var maxDmgPerStrengthAndAgility = context.CreateNew<AttributeRelationship>();
maxDmgPerStrengthAndAgility.InputAttribute = Stats.TotalStrengthAndAgility.GetPersistent(gameConfiguration);
maxDmgPerStrengthAndAgility.InputOperator = InputOperator.Multiply;
maxDmgPerStrengthAndAgility.InputOperand = 1.0f / 30;
maxPhysPowerUpDefinition.Boost.RelatedValues.Add(maxDmgPerStrengthAndAgility);
// Placeholder for the Berserker Strengthener master skill
var strengthenerCurseDmgMultiplier = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(strengthenerCurseDmgMultiplier);
strengthenerCurseDmgMultiplier.TargetAttribute = Stats.BerserkerCurseMultiplier.GetPersistent(gameConfiguration);
strengthenerCurseDmgMultiplier.Boost = context.CreateNew<PowerUpDefinitionValue>();
// Placeholder for the Berserker Proficiency master skill
var proficiencyDmgMultiplier = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(proficiencyDmgMultiplier);
proficiencyDmgMultiplier.TargetAttribute = Stats.BerserkerProficiencyMultiplier.GetPersistent(gameConfiguration);
proficiencyDmgMultiplier.Boost = context.CreateNew<PowerUpDefinitionValue>();
var berserkers = gameConfiguration.Skills.Where(s =>
s.Number == (short)SkillNumber.Berserker ||
s.Number == (short)SkillNumber.BerserkerStrengthener ||
s.Number == (short)SkillNumber.BerserkerProficiency);
foreach (var berserker_ in berserkers)
{
berserker_.SkillType = SkillType.Buff;
berserker_.TargetRestriction = SkillTargetRestriction.Self;
berserker_.MagicEffectDef = magicEffect;
}
// Update Infinite Arrow magic effect
if (gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.InfiniteArrow) is { } infiniteArrowEffect)
{
infiniteArrowEffect.Duration!.ConstantValue.Value = 600;
var powerUps = infiniteArrowEffect.PowerUpDefinitions.ToList();
foreach (var powerUp in powerUps)
{
// SkillExtraManaCost is the old ManaLossAfterHit.
if (powerUp.TargetAttribute == Stats.SkillExtraManaCost || powerUp.TargetAttribute == Stats.BaseDamageBonus)
{
infiniteArrowEffect.PowerUpDefinitions.Remove(powerUp);
}
}
}
// Update options
var ancientSetsOpts = gameConfiguration.ItemOptions.Where(io => io.Name.ValueInNeutralLanguageAsSpan.EndsWith("(Ancient Set)"));
var finalDamageBonus = Stats.FinalDamageBonus.GetPersistent(gameConfiguration);
var wizardryBaseDmgIncrease = Stats.WizardryBaseDmgIncrease.GetPersistent(gameConfiguration);
foreach (var ancientSetOpts in ancientSetsOpts)
{
if (ancientSetOpts.PossibleOptions.FirstOrDefault(o => o.PowerUpDefinition?.TargetAttribute == Stats.BaseDamageBonus) is { } dmgBonusAncOpt)
{
dmgBonusAncOpt.PowerUpDefinition!.TargetAttribute = finalDamageBonus;
}
if (ancientSetOpts.PossibleOptions.FirstOrDefault(o => o.PowerUpDefinition?.TargetAttribute == Stats.WizardryAttackDamageIncrease) is { } wizDmgIncAncOpt)
{
wizDmgIncAncOpt.PowerUpDefinition!.TargetAttribute = wizardryBaseDmgIncrease;
wizDmgIncAncOpt.PowerUpDefinition.Boost!.ConstantValue.Value += 1f;
wizDmgIncAncOpt.PowerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
}
}
var armorDamageDecrease = Stats.ArmorDamageDecrease.GetPersistent(gameConfiguration);
var harmonyDefOptions = gameConfiguration.ItemOptions.FirstOrDefault(o => o.Name == "Harmony Defense Options");
if (harmonyDefOptions is not null
&& harmonyDefOptions.PossibleOptions.FirstOrDefault(o => o.Number == 7) is { } dmgDecOpt)
{
foreach (var level in dmgDecOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.TargetAttribute = armorDamageDecrease;
level.PowerUpDefinition.Boost!.ConstantValue.Value = 1f - level.PowerUpDefinition!.Boost!.ConstantValue.Value;
level.PowerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddRaw;
}
}
var socketOptionsFireId = new Guid("00000083-0032-0000-0000-000000000000");
if (gameConfiguration.ItemOptions.FirstOrDefault(o => o.GetId() == socketOptionsFireId) is { } fireSocketOptions
&& fireSocketOptions.PossibleOptions.FirstOrDefault(o => o.Number == 0) is { } lvlDmgSockOpt)
{
var totalLevel = Stats.TotalLevel.GetPersistent(gameConfiguration);
var baseDamageBonus = Stats.BaseDamageBonus.GetPersistent(gameConfiguration);
foreach (var level in lvlDmgSockOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.TargetAttribute = baseDamageBonus;
if (level.PowerUpDefinition!.Boost!.RelatedValues.FirstOrDefault() is { } relatedValue)
{
relatedValue.InputAttribute = totalLevel;
}
}
}
var socketOptionsWaterId = new Guid("00000083-0033-0000-0000-000000000000");
if (gameConfiguration.ItemOptions.FirstOrDefault(o => o.GetId() == socketOptionsWaterId) is { } waterSocketOptions
&& waterSocketOptions.PossibleOptions.FirstOrDefault(o => o.Number == 3) is { } dmgDecSockOpt)
{
foreach (var level in dmgDecSockOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.TargetAttribute = armorDamageDecrease;
level.PowerUpDefinition.Boost!.ConstantValue.Value = 1f - level.PowerUpDefinition.Boost.ConstantValue.Value;
level.PowerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddRaw;
}
}
// Update RF glove weapons item slot
var leftOrRightHandSlot = gameConfiguration.ItemSlotTypes.First(t => t.ItemSlots.Contains(0) && t.ItemSlots.Contains(1));
if (gameConfiguration.Items.Where(i => i.Group == (byte)ItemGroups.Swords && i.Width == 1 && i.Number >= 32) is { } gloveWeapons)
{
foreach (var gloveWeapon in gloveWeapons)
{
gloveWeapon.ItemSlot = leftOrRightHandSlot;
}
}
// Add Stats.IsBookEquipped attribute to books
if (gameConfiguration.Items.Where(i => i.Group == (byte)ItemGroups.Staff && i.Height == 2) is { } bookItems)
{
var isBookEquipped = Stats.IsBookEquipped.GetPersistent(gameConfiguration);
foreach (var bookItem in bookItems)
{
if (bookItem.BasePowerUpAttributes.FirstOrDefault(p => p.TargetAttribute == isBookEquipped) is null)
{
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = isBookEquipped;
powerUpDefinition.BaseValue = 1;
powerUpDefinition.AggregateType = AggregateType.AddRaw;
bookItem.BasePowerUpAttributes.Add(powerUpDefinition);
}
}
}
// Update skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Lance) is { } lance)
{
lance.DamageType = DamageType.Wizardry;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.PowerSlash) is { } powerSlash
&& powerSlash.Requirements.FirstOrDefault(req => req.Attribute == Stats.TotalEnergy) is { } energyRequirement)
{
powerSlash.Requirements.Remove(energyRequirement);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DrainLife) is { } drainLife)
{
drainLife.DamageType = DamageType.Wizardry;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainLightning) is { } chainLightning)
{
chainLightning.DamageType = DamageType.Wizardry;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Berserker) is { } berserker)
{
berserker.DamageType = DamageType.None;
berserker.SkillType = SkillType.Buff;
berserker.TargetRestriction = SkillTargetRestriction.Self;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.LightningShock) is { } lightningShock)
{
lightningShock.DamageType = DamageType.Wizardry;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainLightningStr) is { } chainLightningStr)
{
chainLightningStr.DamageType = DamageType.Wizardry;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.LightningShockStr) is { } lightningShockStr)
{
lightningShockStr.DamageType = DamageType.Wizardry;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DrainLifeStrengthener) is { } drainLifeStr)
{
drainLifeStr.DamageType = DamageType.Wizardry;
}
// Create Skill attribute relationships
AddAttributeRelationship(SkillNumber.Nova, Stats.SkillDamageBonus, 1.0f / 2, Stats.TotalStrength);
AddAttributeRelationship(SkillNumber.Nova, Stats.SkillDamageBonus, 1, Stats.NovaStageDamage);
AddAttributeRelationship(SkillNumber.Earthshake, Stats.SkillDamageBonus, 1.0f / 10, Stats.TotalStrength);
AddAttributeRelationship(SkillNumber.Earthshake, Stats.SkillDamageBonus, 1.0f / 5, Stats.TotalLeadership);
AddAttributeRelationship(SkillNumber.Earthshake, Stats.SkillDamageBonus, 10, Stats.HorseLevel);
AddAttributeRelationship(SkillNumber.ElectricSpike, Stats.SkillDamageBonus, 50, Stats.NearbyPartyMemberCount);
AddAttributeRelationship(SkillNumber.ElectricSpike, Stats.SkillDamageBonus, 1.0f / 10, Stats.TotalLeadership);
AddAttributeRelationship(SkillNumber.ChaoticDiseier, Stats.SkillDamageBonus, 1.0f / 30, Stats.TotalStrength);
AddAttributeRelationship(SkillNumber.ChaoticDiseier, Stats.SkillDamageBonus, 1.0f / 55, Stats.TotalEnergy);
SkillNumber[] lordSkills = [SkillNumber.Force, SkillNumber.FireBlast, SkillNumber.FireBurst, SkillNumber.ForceWave, SkillNumber.FireScream];
foreach (var lordSkillNumber in lordSkills)
{
AddAttributeRelationship(lordSkillNumber, Stats.SkillDamageBonus, 1.0f / 25, Stats.TotalStrength);
AddAttributeRelationship(lordSkillNumber, Stats.SkillDamageBonus, 1.0f / 50, Stats.TotalEnergy);
}
AddAttributeRelationship(SkillNumber.MultiShot, Stats.SkillMultiplier, 0.8f, Stats.SkillMultiplier, AggregateType.Multiplicate);
void AddAttributeRelationship(SkillNumber skillNumber, AttributeDefinition targetAttribute, float multiplier, AttributeDefinition sourceAttribute, AggregateType aggregateType = AggregateType.AddRaw)
{
var skill = gameConfiguration.Skills.First(s => s.Number == (int)skillNumber);
var relationship = CharacterClassHelper.CreateAttributeRelationship(context, gameConfiguration, targetAttribute, multiplier, sourceAttribute, aggregateType: aggregateType);
skill.AttributeRelationships.Add(relationship);
}
// Update master skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WeaponMasteryBladeMaster)?.MasterDefinition is { } weaponMasteryBladeMaster)
{
weaponMasteryBladeMaster.TargetAttribute = Stats.MasterSkillPhysBonusDmg.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.TwoHandedSwordMaster)?.MasterDefinition is { } twoHandedSwordMaster)
{
twoHandedSwordMaster.TargetAttribute = Stats.TwoHandedSwordMasteryBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.OneHandedSwordMaster)?.MasterDefinition is { } oneHandedSwordMaster)
{
oneHandedSwordMaster.TargetAttribute = Stats.WeaponMasteryAttackSpeed.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.OneHandedStaffStrengthener)?.MasterDefinition is { } oneHandedtaffStr)
{
oneHandedtaffStr.TargetAttribute = Stats.OneHandedStaffBonusBaseDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.TwoHandedStaffStrengthener)?.MasterDefinition is { } twoHandedStaffStr)
{
twoHandedStaffStr.TargetAttribute = Stats.TwoHandedStaffBonusBaseDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.OneHandedStaffMaster)?.MasterDefinition is { } oneHandedStaffMaster)
{
oneHandedStaffMaster.TargetAttribute = Stats.WeaponMasteryAttackSpeed.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.TwoHandedStaffMaster)?.MasterDefinition is { } twoHandedStaffMaster)
{
twoHandedStaffMaster.TargetAttribute = Stats.TwoHandedStaffMasteryBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WeaponMasteryHighElf)?.MasterDefinition is { } weaponMasteryHighElf)
{
weaponMasteryHighElf.TargetAttribute = Stats.MasterSkillPhysBonusDmg.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BowStrengthener)?.MasterDefinition is { } bowStr)
{
bowStr.TargetAttribute = Stats.BowStrBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CrossbowStrengthener)?.MasterDefinition is { } crossbowStr)
{
crossbowStr.TargetAttribute = Stats.CrossBowStrBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BowMastery)?.MasterDefinition is { } bowMastery)
{
bowMastery.TargetAttribute = Stats.WeaponMasteryAttackSpeed.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.CrossbowMastery)?.MasterDefinition is { } crossbowMastery)
{
crossbowMastery.TargetAttribute = Stats.CrossBowMasteryBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.InfinityArrowStr)?.MasterDefinition is { } infinityArrowStr)
{
infinityArrowStr.ValueFormula = $"1 + {infinityArrowStr.ValueFormula}";
infinityArrowStr.Aggregation = AggregateType.Multiplicate;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MagicMasterySummoner)?.MasterDefinition is { } magicMasterySummoner)
{
magicMasterySummoner.TargetAttribute = Stats.WizardryAndCurseBaseDmgBonus.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.OtherWorldTomeStreng)?.MasterDefinition is { } otherWorldTomeStr)
{
otherWorldTomeStr.TargetAttribute = Stats.BookBonusBaseDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.StickMastery)?.MasterDefinition is { } stickMastery)
{
stickMastery.TargetAttribute = Stats.StickMasteryBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.OtherWorldTomeMastery)?.MasterDefinition is { } otherWorldTomeMastery)
{
otherWorldTomeMastery.TargetAttribute = Stats.WeaponMasteryAttackSpeed.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BerserkerStrengthener)?.MasterDefinition is { } berserkerStr)
{
berserkerStr.TargetAttribute = Stats.BerserkerCurseMultiplier.GetPersistent(gameConfiguration);
berserkerStr.ValueFormula += " / 100";
berserkerStr.Aggregation = AggregateType.AddRaw;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BerserkerProficiency)?.MasterDefinition is { } berserkerProf)
{
berserkerProf.ReplacedSkill = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.BerserkerStrengthener);
berserkerProf.TargetAttribute = Stats.BerserkerProficiencyMultiplier.GetPersistent(gameConfiguration);
berserkerProf.ValueFormula += " / 100";
berserkerProf.Aggregation = AggregateType.AddRaw;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.MinimumWizCurseInc)?.MasterDefinition is { } minimumWizCurseInc)
{
minimumWizCurseInc.TargetAttribute = Stats.MinWizardryAndCurseDmgBonus.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WeaponMasteryDuelMaster)?.MasterDefinition is { } weaponMasteryDuelMaster)
{
weaponMasteryDuelMaster.TargetAttribute = Stats.MasterSkillPhysBonusDmg.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WeaponMasteryLordEmperor)?.MasterDefinition is { } weaponMasteryLordEmperor)
{
weaponMasteryLordEmperor.TargetAttribute = Stats.MasterSkillPhysBonusDmg.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ScepterMastery)?.MasterDefinition is { } scepterMastery)
{
scepterMastery.TargetAttribute = Stats.ScepterMasteryBonusDamage.GetPersistent(gameConfiguration);
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WeaponMasteryFistMaster)?.MasterDefinition is { } weaponMasteryFistMaster)
{
weaponMasteryFistMaster.TargetAttribute = Stats.MasterSkillPhysBonusDmg.GetPersistent(gameConfiguration);
}
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="FixDefenseCalcsPlugIn075.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>
/// This update fixes character stats, magic effects, and options related to defense.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("683A8F8F-EFE9-4EF4-B536-21048E195A87")]
public class FixDefenseCalcsPlugIn075 : FixDefenseCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDefenseCalcs075;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="FixDefenseCalcsPlugIn095d.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>
/// This update fixes character stats, magic effects, and options related to defense.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("C6945ADC-0313-47AC-AFAE-61D0544C8935")]
public class FixDefenseCalcsPlugIn095D : FixDefenseCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDefenseCalcs095d;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,199 @@
// <copyright file="FixDefenseCalcsPlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
/// <summary>
/// This update fixes character stats, magic effects, and options related to defense.
/// </summary>
public abstract class FixDefenseCalcsPlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Defense Calculations";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes character stats, magic effects, and options related to defense.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 05, 23, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Update attributes
var defenseFinal = context.CreateNew<AttributeDefinition>(Stats.DefenseFinal.Id, Stats.DefenseFinal.Designation, Stats.DefenseFinal.Description);
gameConfiguration.Attributes.Add(defenseFinal);
var bonusDefenseRateWithShield = context.CreateNew<AttributeDefinition>(Stats.BonusDefenseRateWithShield.Id, Stats.BonusDefenseRateWithShield.Designation, Stats.BonusDefenseRateWithShield.Description);
gameConfiguration.Attributes.Add(bonusDefenseRateWithShield);
var defenseShield = context.CreateNew<AttributeDefinition>(Stats.DefenseShield.Id, Stats.DefenseShield.Designation, Stats.DefenseShield.Description);
gameConfiguration.Attributes.Add(defenseShield);
var shieldItemDefenseIncrease = context.CreateNew<AttributeDefinition>(Stats.ShieldItemDefenseIncrease.Id, Stats.ShieldItemDefenseIncrease.Designation, Stats.ShieldItemDefenseIncrease.Description);
gameConfiguration.Attributes.Add(shieldItemDefenseIncrease);
var shieldBlockDamageDecrementId = new Guid("DAC6690B-5922-4446-BCE5-5E701BE62EC1");
if (gameConfiguration.Attributes.FirstOrDefault(a => a.Id == shieldBlockDamageDecrementId) is { } shieldBlockDamageDecrement)
{
gameConfiguration.Attributes.Remove(shieldBlockDamageDecrement);
}
// Update attribute combinations
var defenseBase = Stats.DefenseBase.GetPersistent(gameConfiguration);
var defensePvm = Stats.DefensePvm.GetPersistent(gameConfiguration);
var defensePvp = Stats.DefensePvp.GetPersistent(gameConfiguration);
var bonusDefenseWithShield = Stats.BonusDefenseWithShield.GetPersistent(gameConfiguration);
var isShieldEquipped = Stats.IsShieldEquipped.GetPersistent(gameConfiguration);
var defenseRatePvm = Stats.DefenseRatePvm.GetPersistent(gameConfiguration);
var isHorseEquipped = Stats.IsHorseEquipped.GetPersistent(gameConfiguration);
var bonusDefenseWithHorse = Stats.BonusDefenseWithHorse.GetPersistent(gameConfiguration);
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
var attrCombos = charClass.AttributeCombinations.ToList();
foreach (var attrCombo in attrCombos)
{
if ((attrCombo.TargetAttribute == Stats.DefensePvm && attrCombo.InputAttribute == Stats.DefenseBase)
|| (attrCombo.TargetAttribute == Stats.DefensePvp && attrCombo.InputAttribute == Stats.DefenseBase))
{
charClass.AttributeCombinations.Remove(attrCombo);
}
}
if (attrCombos.FirstOrDefault(attrCombo =>
attrCombo.TargetAttribute == Stats.DefenseBase
&& attrCombo.InputAttribute == Stats.BonusDefenseWithShield
&& attrCombo.OperandAttribute == Stats.IsShieldEquipped) is { } bonusDefenseWithShieldToDefenseBase)
{
charClass.AttributeCombinations.Remove(bonusDefenseWithShieldToDefenseBase);
}
var shieldDefenseToDefenseBase = context.CreateNew<AttributeRelationship>(
defenseBase,
1,
defenseShield,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
var defenseBaseToDefenseFinal = context.CreateNew<AttributeRelationship>(
defenseFinal,
0.5f,
defenseBase,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
var defenseFinalToDefensePvm = context.CreateNew<AttributeRelationship>(
defensePvm,
1,
defenseFinal,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
var defenseFinalToDefensePvp = context.CreateNew<AttributeRelationship>(
defensePvp,
1,
defenseFinal,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
if (attrCombos.FirstOrDefault(attrCombo => attrCombo.InputAttribute == Stats.DefenseIncreaseWithEquippedShield
&& attrCombo.OperandAttribute == Stats.IsShieldEquipped) is { } defenseIncWithEquippedShieldToTempDefense)
{
var tempDefenseToDefenseFinal = context.CreateNew<AttributeRelationship>(
defenseFinal,
1,
defenseIncWithEquippedShieldToTempDefense.TargetAttribute,
InputOperator.Add,
default(AttributeDefinition?),
AggregateType.Multiplicate);
charClass.AttributeCombinations.Add(tempDefenseToDefenseFinal);
}
var shieldItemDefenseIncreaseToDefenseFinal = context.CreateNew<AttributeRelationship>(
defenseFinal,
defenseShield,
shieldItemDefenseIncrease,
AggregateType.AddRaw);
var bonusDefenseWithShieldToDefenseFinal = context.CreateNew<AttributeRelationship>(
defenseFinal,
isShieldEquipped,
bonusDefenseWithShield,
AggregateType.AddFinal);
var bonusDefenseRateWithShieldToDefenseRatePvm = context.CreateNew<AttributeRelationship>(
defenseRatePvm,
isShieldEquipped,
bonusDefenseRateWithShield,
AggregateType.AddFinal);
charClass.AttributeCombinations.Add(shieldDefenseToDefenseBase);
charClass.AttributeCombinations.Add(defenseBaseToDefenseFinal);
charClass.AttributeCombinations.Add(defenseFinalToDefensePvm);
charClass.AttributeCombinations.Add(defenseFinalToDefensePvp);
charClass.AttributeCombinations.Add(shieldItemDefenseIncreaseToDefenseFinal);
charClass.AttributeCombinations.Add(bonusDefenseWithShieldToDefenseFinal);
charClass.AttributeCombinations.Add(bonusDefenseRateWithShieldToDefenseRatePvm);
// Lord classes.
if (charClass.Number == 16 || charClass.Number == 17)
{
var bonusDefenseWithHorseToDefenseFinal = context.CreateNew<AttributeRelationship>(
defenseFinal,
isHorseEquipped,
bonusDefenseWithHorse,
AggregateType.AddFinal);
charClass.AttributeCombinations.Add(bonusDefenseWithHorseToDefenseFinal);
}
});
// Update magic effects
var defenseEffect = gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.ShieldSkill);
if (defenseEffect?.Duration is not null)
{
defenseEffect.Duration.ConstantValue.Value = 4;
}
var greaterDefenseEffect = gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.GreaterDefense);
if (greaterDefenseEffect?.PowerUpDefinitions.FirstOrDefault(p => p.TargetAttribute == defenseBase) is { } greaterDefenseEffectPowerUp)
{
greaterDefenseEffectPowerUp.TargetAttribute = defenseFinal;
greaterDefenseEffectPowerUp.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal;
}
// Update shields
var shields = gameConfiguration.Items.Where(i => i.Group == (byte)ItemGroups.Shields);
foreach (var shield in shields)
{
if (shield.BasePowerUpAttributes.FirstOrDefault(bpua => bpua.TargetAttribute == Stats.DefenseBase) is { } defensePowerUp)
{
defensePowerUp.TargetAttribute = defenseShield;
}
}
}
}

View File

@@ -0,0 +1,195 @@
// <copyright file="FixDefenseCalcsPlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes character stats, magic effects, and options related to defense.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("447FA95B-091B-4950-B1F3-F4EB6D20DE19")]
public class FixDefenseCalcsPlugInSeason6 : FixDefenseCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDefenseCalcsSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Update Anonymous Leather ancient set obsolete option prior to its removal
var shieldBlockDamageDecrementId = new Guid("DAC6690B-5922-4446-BCE5-5E701BE62EC1");
var anonymousLeatherOptsId = new Guid("00000083-0029-0002-0100-000000000000");
var anonymousLeatherOpts = gameConfiguration.ItemOptions.FirstOrDefault(i => i.GetId() == anonymousLeatherOptsId);
if (gameConfiguration.Attributes.FirstOrDefault(a => a.Id == shieldBlockDamageDecrementId) is { } shieldBlockDamageDecrement
&& anonymousLeatherOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == shieldBlockDamageDecrement) is { } shieldBlockDamageDecrementOpt)
{
shieldBlockDamageDecrementOpt.PowerUpDefinition!.TargetAttribute = Stats.DefenseIncreaseWithEquippedShield.GetPersistent(gameConfiguration);
}
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
var defenseBase = Stats.DefenseBase.GetPersistent(gameConfiguration);
var defenseFinal = Stats.DefenseFinal.GetPersistent(gameConfiguration);
var defensePvm = Stats.DefensePvm.GetPersistent(gameConfiguration);
var defensePvp = Stats.DefensePvp.GetPersistent(gameConfiguration);
var shieldItemDefenseIncrease = Stats.ShieldItemDefenseIncrease.GetPersistent(gameConfiguration);
var bonusDefenseRateWithShield = Stats.BonusDefenseRateWithShield.GetPersistent(gameConfiguration);
var excellentDamageChance = Stats.ExcellentDamageChance.GetPersistent(gameConfiguration);
var excellentDamageBonus = Stats.ExcellentDamageBonus.GetPersistent(gameConfiguration);
// Update attribute combinations
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
if (charClass.AttributeCombinations.FirstOrDefault(attrCombo => attrCombo.TargetAttribute == Stats.MaximumShield && attrCombo.InputAttribute == Stats.DefenseBase) is { } defenseBaseToMaximumShield)
{
defenseBaseToMaximumShield.InputOperand = 1;
defenseBaseToMaximumShield.InputAttribute = defenseFinal;
}
});
// Update magic effects
var defenseReductionEffect = gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.DefenseReduction);
if (defenseReductionEffect is not null)
{
defenseReductionEffect.PowerUpDefinitions.Clear();
var reducePvmDefenseEffect = context.CreateNew<PowerUpDefinition>();
defenseReductionEffect.PowerUpDefinitions.Add(reducePvmDefenseEffect);
reducePvmDefenseEffect.TargetAttribute = defensePvm;
reducePvmDefenseEffect.Boost = context.CreateNew<PowerUpDefinitionValue>();
reducePvmDefenseEffect.Boost.ConstantValue.Value = 0.9f;
reducePvmDefenseEffect.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
var reducePvpDefenseEffect = context.CreateNew<PowerUpDefinition>();
defenseReductionEffect.PowerUpDefinitions.Add(reducePvpDefenseEffect);
reducePvpDefenseEffect.TargetAttribute = defensePvp;
reducePvpDefenseEffect.Boost = context.CreateNew<PowerUpDefinitionValue>();
reducePvpDefenseEffect.Boost.ConstantValue.Value = 0.9f;
reducePvpDefenseEffect.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
}
var jackOlanternCry = gameConfiguration.Items.FirstOrDefault(m => m.Group == 14 && m.Number == 48);
if (jackOlanternCry?.ConsumeEffect is { } effect)
{
foreach (var powerUp in effect.PowerUpDefinitions)
{
powerUp.TargetAttribute = defenseFinal;
powerUp.Boost!.ConstantValue.Value = 100 / 2;
powerUp.Boost.ConstantValue.AggregateType = AggregateType.AddFinal;
}
}
// Update options
var ancientSetsOpts = gameConfiguration.ItemOptions.Where(io => io.Name.ValueInNeutralLanguageAsSpan.EndsWith("(Ancient Set)"));
foreach (var ancientSetOpts in ancientSetsOpts)
{
if (ancientSetOpts.PossibleOptions.FirstOrDefault(o => o.PowerUpDefinition?.TargetAttribute == defenseBase) is { } defenseBaseAncOpt)
{
defenseBaseAncOpt.PowerUpDefinition!.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal;
}
}
var anubisLegendaryOptsId = new Guid("00000083-0029-0013-0100-000000000000");
var anubisLegendaryOpts = gameConfiguration.ItemOptions.FirstOrDefault(i => i.GetId() == anubisLegendaryOptsId);
if (anubisLegendaryOpts?.PossibleOptions.FirstOrDefault(opt =>
opt.PowerUpDefinition?.TargetAttribute == excellentDamageChance
&& opt.PowerUpDefinition.Boost?.ConstantValue.Value == 20.0f) is { } excellentDamageChanceOpt)
{
excellentDamageChanceOpt.PowerUpDefinition!.TargetAttribute = excellentDamageBonus;
}
var pantsGuardianOptions = gameConfiguration.ItemOptions.FirstOrDefault(o => o.Name == "Guardian Option (Pants)");
if (pantsGuardianOptions is not null
&& pantsGuardianOptions.PossibleOptions.FirstOrDefault(o => o.PowerUpDefinition?.TargetAttribute == defenseBase) is { } defenseOpt)
{
defenseOpt.PowerUpDefinition!.TargetAttribute = defensePvp;
defenseOpt.PowerUpDefinition.Boost!.ConstantValue.Value = 200 / 2;
}
var harmonyDefOptions = gameConfiguration.ItemOptions.FirstOrDefault(o => o.Name == "Harmony Defense Options");
if (harmonyDefOptions is not null
&& harmonyDefOptions.PossibleOptions.FirstOrDefault(o => o.Number == 1) is { } defenseBaseOpt)
{
foreach (var level in defenseBaseOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.Boost!.ConstantValue.AggregateType = AggregateType.AddFinal;
}
}
var socketOptionsWaterId = new Guid("00000083-0033-0000-0000-000000000000");
var waterSocketOptions = gameConfiguration.ItemOptions.FirstOrDefault(o => o.GetId() == socketOptionsWaterId);
if (waterSocketOptions is not null)
{
if (waterSocketOptions.PossibleOptions.FirstOrDefault(o => o.Number == 1) is { } defenseBaseSockOpt)
{
foreach (var level in defenseBaseSockOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.TargetAttribute = defenseFinal;
}
}
if (waterSocketOptions.PossibleOptions.FirstOrDefault(o => o.Number == 2) is { } shieldItemdefenseSockOpt)
{
foreach (var level in shieldItemdefenseSockOpt.LevelDependentOptions)
{
level.PowerUpDefinition!.TargetAttribute = shieldItemDefenseIncrease;
}
}
}
var socketBonusOptionsArmorsId = new Guid("00000083-0031-0003-0000-000000000000");
var socketArmorsBonusOptions = gameConfiguration.ItemOptions.FirstOrDefault(o => o.GetId() == socketBonusOptionsArmorsId);
if (socketArmorsBonusOptions is not null
&& socketArmorsBonusOptions.PossibleOptions.FirstOrDefault(o => o.Number == 4) is { } defenseBaseSockBonusOpt)
{
defenseBaseSockBonusOpt.PowerUpDefinition!.TargetAttribute = defenseFinal;
}
// Update master skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DefenseIncrease)?.MasterDefinition is { } defenseIncrease)
{
defenseIncrease.Aggregation = AggregateType.AddFinal;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.IncreasesDefense)?.MasterDefinition is { } increasesDefense)
{
increasesDefense.Aggregation = AggregateType.AddFinal;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DefenseSuccessRateInc)?.MasterDefinition is { } defenseSuccessRateInc)
{
string formula120 = "(1 + (((((((level - 30) ^ 3) + 25000) / 499) / 50) * 100) / 12))";
defenseSuccessRateInc.ValueFormula = $"1 + {formula120} / 100";
defenseSuccessRateInc.Aggregation = AggregateType.Multiplicate;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ShieldMasteryGrandMaster)?.MasterDefinition is { } shieldMasteryGrandMaster)
{
shieldMasteryGrandMaster.TargetAttribute = bonusDefenseRateWithShield;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ShieldMasteryHighElf)?.MasterDefinition is { } shieldMasteryHighElf)
{
shieldMasteryHighElf.TargetAttribute = bonusDefenseRateWithShield;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ShieldMastery)?.MasterDefinition is { } shieldMastery)
{
shieldMastery.TargetAttribute = bonusDefenseRateWithShield;
}
}
}

View File

@@ -0,0 +1,56 @@
// <copyright file="FixDrainLifeSkillUpdate.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.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This adds the items required to enter the kalima map.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A8827A3C-7F52-47CF-9EA5-562A9C06B986")]
public class FixDrainLifeSkillUpdate : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Drain Life Skill";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Updates the attributes of the summoner's Drain Life skill to make it work properly.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDrainLifeSkill;
/// <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(2024, 08, 29, 18, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var drainLife = gameConfiguration.Skills.First(x => x.Number == (short)SkillNumber.DrainLife);
drainLife.SkillType = SkillType.AreaSkillExplicitTarget;
}
}

View File

@@ -0,0 +1,54 @@
// <copyright file="FixDuelArenaSafezoneMapUpdate.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 Sets the safezone of duel arena to lorencia.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("27714BB3-43F9-4D90-920F-98EF0EC20232")]
public class FixDuelArenaSafezoneMapUpdate : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Duel Arena Safezone Map";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Sets the safezone of duel arena to lorencia.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixDuelArenaSafezoneMap;
/// <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(2024, 10, 28, 18, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var duelArena = gameConfiguration.Maps.First(x => x.Number == DuelArena.Number);
duelArena.SafezoneMap = gameConfiguration.Maps.First(m => m.Number == Lorencia.Number);
}
}

View File

@@ -0,0 +1,40 @@
// <copyright file="FixEventItemsDropFromMonstersUpdatePlugIn095d.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.PlugIns;
/// <summary>
/// This update fixes event items that have DropsFromMonsters set to true,
/// but should use dedicated DropItemGroups instead.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("B9C6D3E2-4F5A-6B7C-8D9E-0F1A2B3C4D5E")]
public class FixEventItemsDropFromMonstersUpdatePlugIn095d : FixEventItemsDropFromMonstersUpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Event Items DropsFromMonsters 0.95d";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes event items that have DropsFromMonsters set to true, causing them to drop at level 0 instead of using their dedicated DropItemGroups.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixEventItemsDropFromMonsters095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
}

View File

@@ -0,0 +1,55 @@
// <copyright file="FixEventItemsDropFromMonstersUpdatePlugInBase.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 MUnique.OpenMU.DataModel.Configuration;
/// <summary>
/// This update fixes event items that have DropsFromMonsters set to true,
/// but should use dedicated DropItemGroups instead.
/// </summary>
public abstract class FixEventItemsDropFromMonstersUpdatePlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Event Items DropsFromMonsters";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes event items that have DropsFromMonsters set to true, causing them to drop at level 0 instead of using their dedicated DropItemGroups.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 09, 01, 10, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
#pragma warning disable CS1998
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
#pragma warning restore CS1998
{
// Find event items that have DropsFromMonsters = true but also have dedicated DropItemGroups
var eventItemsWithDropGroups = gameConfiguration.DropItemGroups
.Where(dig => dig.PossibleItems.Any())
.SelectMany(dig => dig.PossibleItems)
.Where(item => item.Group is 13 or 14 && item.DropsFromMonsters)
.Distinct()
.ToList();
foreach (var item in eventItemsWithDropGroups)
{
item.DropsFromMonsters = false;
}
}
}

View File

@@ -0,0 +1,40 @@
// <copyright file="FixEventItemsDropFromMonstersUpdatePlugInSeason6.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.PlugIns;
/// <summary>
/// This update fixes event items that have DropsFromMonsters set to true,
/// but should use dedicated DropItemGroups instead.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A8B5C2D1-3E4F-5A6B-7C8D-9E0F1A2B3C4D")]
public class FixEventItemsDropFromMonstersUpdatePlugInSeason6 : FixEventItemsDropFromMonstersUpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Event Items DropsFromMonsters Season 6";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes event items that have DropsFromMonsters set to true, causing them to drop at level 0 instead of using their dedicated DropItemGroups.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixEventItemsDropFromMonstersSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
}

View File

@@ -0,0 +1,254 @@
// <copyright file="FixHorseFenrirOptionsSoulBarrierPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlayerActions.Craftings;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds Dark Horse options, fixes Gold Fenrir options and Soul Barrier effects.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("3E362629-AAF3-40E0-BC6D-32230285FB03")]
public class FixHorseFenrirOptionsSoulBarrierPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Dark Horse and Gold Fenrir Options, and Soul Barrier Effects";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds Dark Horse options, fixes Gold Fenrir options and Soul Barrier effects.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 03, 12, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixHorseFenrirOptionsSoulBarrierPlugIn;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Update dark horse item crafting
var petTrainerCraftings = gameConfiguration.Monsters.Single(m => m.NpcWindow == NpcWindow.PetTrainer).ItemCraftings;
if (petTrainerCraftings.Single(c => c.Number == 13) is { } darkHorseCrafting)
{
darkHorseCrafting.ItemCraftingHandlerClassName = typeof(DarkHorseCrafting).FullName!;
}
// Add new Stats
var totalLevel = context.CreateNew<AttributeDefinition>(Stats.TotalLevel.Id, Stats.TotalLevel.Designation, Stats.TotalLevel.Description);
gameConfiguration.Attributes.Add(totalLevel);
var dmgReceiveHorseDec = context.CreateNew<AttributeDefinition>(Stats.DamageReceiveHorseDecrement.Id, Stats.DamageReceiveHorseDecrement.Designation, Stats.DamageReceiveHorseDecrement.Description);
gameConfiguration.Attributes.Add(dmgReceiveHorseDec);
var soulBarrierReceiveDec = context.CreateNew<AttributeDefinition>(Stats.SoulBarrierReceiveDecrement.Id, Stats.SoulBarrierReceiveDecrement.Designation, Stats.SoulBarrierReceiveDecrement.Description);
gameConfiguration.Attributes.Add(soulBarrierReceiveDec);
var soulBarrierManaTollPerHit = context.CreateNew<AttributeDefinition>(Stats.SoulBarrierManaTollPerHit.Id, Stats.SoulBarrierManaTollPerHit.Designation, Stats.SoulBarrierManaTollPerHit.Description);
gameConfiguration.Attributes.Add(soulBarrierManaTollPerHit);
// Add total level relationships class attributes
var level = Stats.Level.GetPersistent(gameConfiguration);
var masterLevel = Stats.MasterLevel.GetPersistent(gameConfiguration);
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
charClass.AttributeCombinations.Add(context.CreateNew<AttributeRelationship>(
totalLevel,
1,
level,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw));
charClass.AttributeCombinations.Add(context.CreateNew<AttributeRelationship>(
totalLevel,
1,
masterLevel,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw));
});
// Add DL horse dmg receive base value and relationship to dmg receive
var lordClasses = gameConfiguration.CharacterClasses.Where(c => c.Number == 16 || c.Number == 17);
var damageReceiveDec = Stats.DamageReceiveDecrement.GetPersistent(gameConfiguration);
foreach (var lordClass in lordClasses)
{
lordClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(
1,
dmgReceiveHorseDec));
lordClass.AttributeCombinations.Add(context.CreateNew<AttributeRelationship>(
damageReceiveDec,
1,
dmgReceiveHorseDec,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.Multiplicate));
}
// Add new dark horse option type
var darkHorseOptionType = context.CreateNew<ItemOptionType>();
darkHorseOptionType.Description = ItemOptionTypes.DarkHorse.Description;
darkHorseOptionType.Id = ItemOptionTypes.DarkHorse.Id;
darkHorseOptionType.Name = ItemOptionTypes.DarkHorse.Name;
darkHorseOptionType.IsVisible = ItemOptionTypes.DarkHorse.IsVisible;
gameConfiguration.ItemOptionTypes.Add(darkHorseOptionType);
// Add dark horse options
var horse = gameConfiguration.Items.Single(i => i.Group == 13 && i.Number == 4);
var horseOptionDefinition = context.CreateNew<ItemOptionDefinition>();
horseOptionDefinition.SetGuid(ItemOptionDefinitionNumbers.Horse);
gameConfiguration.ItemOptions.Add(horseOptionDefinition);
horseOptionDefinition.Name = "Dark Horse Options";
horseOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(context, gameConfiguration, ItemOptionTypes.DarkHorse, 1, Stats.DamageReceiveHorseDecrement, AggregateType.AddRaw, ItemOptionDefinitionNumbers.Horse, -0.15f, (Stats.HorseLevel, -0.005f)));
horseOptionDefinition.PossibleOptions.Add(this.CreateRelatedPetOption(context, gameConfiguration, ItemOptionTypes.DarkHorse, 2, Stats.DefenseBase, AggregateType.AddRaw, ItemOptionDefinitionNumbers.Horse, 5, (Stats.HorseLevel, 2), (Stats.TotalAgility, 1f / 20)));
horse.PossibleItemOptions.Add(horseOptionDefinition);
// Update gold fenrir options
var fenrirOptionDef = gameConfiguration.ItemOptions.First(o => o.PossibleOptions.Any(opt => opt.OptionType == ItemOptionTypes.GoldFenrir));
var goldFenrirOptions = fenrirOptionDef.PossibleOptions.Where(opt => opt.OptionType == ItemOptionTypes.GoldFenrir).ToList();
foreach (var goldFenrirOpt in goldFenrirOptions)
{
AttributeDefinition? targetAttribute = null;
float multiplier = 0;
if (goldFenrirOpt.PowerUpDefinition!.TargetAttribute == Stats.MaximumHealth)
{
targetAttribute = Stats.MaximumHealth;
multiplier = 0.5f;
}
else if (goldFenrirOpt.PowerUpDefinition.TargetAttribute == Stats.MaximumMana)
{
targetAttribute = Stats.MaximumMana;
multiplier = 0.5f;
}
else if (goldFenrirOpt.PowerUpDefinition.TargetAttribute == Stats.MaximumPhysBaseDmg)
{
targetAttribute = Stats.PhysicalBaseDmg;
multiplier = 1f / 12f;
}
else
{
targetAttribute = Stats.WizardryBaseDmg;
multiplier = 1f / 25f;
}
goldFenrirOpt.PowerUpDefinition.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration);
goldFenrirOpt.PowerUpDefinition.Boost!.ConstantValue.Value = 0;
goldFenrirOpt.PowerUpDefinition.Boost.RelatedValues.Add(
this.CreateAttributeRelationship(context, gameConfiguration, targetAttribute, ItemOptionDefinitionNumbers.Fenrir, (Stats.TotalLevel, multiplier)));
}
// Update soul barrier magic effect. Soul barrier dmg decrease % = 10 + (Agility/50) + (Energy/200)
var soulBarrierMagicEffect = gameConfiguration.MagicEffects.FirstOrDefault(me => me.Number == (short)MagicEffectNumber.SoulBarrier);
if (soulBarrierMagicEffect is not null)
{
if (soulBarrierMagicEffect.Duration is { } duration)
{
duration.RelatedValues.First().InputOperand = 1f / 40f;
}
if (soulBarrierMagicEffect.PowerUpDefinitions.FirstOrDefault() is { } powerUp)
{
powerUp.TargetAttribute = soulBarrierReceiveDec;
powerUp.Boost!.ConstantValue.Value = 0.1f;
powerUp.Boost!.ConstantValue.AggregateType = AggregateType.AddRaw;
var boostPerEnergy = powerUp.Boost.RelatedValues.First(v => v.InputOperand == 1 - (0.01f / 200f));
boostPerEnergy.InputOperator = InputOperator.Multiply;
boostPerEnergy.InputOperand = 1f / 20000f;
var boostPerAgility = powerUp.Boost.RelatedValues.First(v => v.InputOperand == 1 - (0.01f / 50f));
boostPerAgility.InputOperator = InputOperator.Multiply;
boostPerAgility.InputOperand = 1f / 5000f;
}
var manaTollPerHit = context.CreateNew<PowerUpDefinition>();
soulBarrierMagicEffect.PowerUpDefinitions.Add(manaTollPerHit);
manaTollPerHit.TargetAttribute = soulBarrierManaTollPerHit;
manaTollPerHit.Boost = context.CreateNew<PowerUpDefinitionValue>();
var manaToll = context.CreateNew<AttributeRelationship>();
manaToll.InputAttribute = Stats.MaximumMana.GetPersistent(gameConfiguration);
manaToll.InputOperator = InputOperator.Multiply;
manaToll.InputOperand = 0.02f; // two percent of total mana
manaTollPerHit.Boost.RelatedValues.Add(manaToll);
}
// Update Soul Barrier Streng master skill definition
var soulBarrierStreng = gameConfiguration.Skills.First(s => s.GetId() == new Guid("00000400-0193-0000-0000-000000000000"));
soulBarrierStreng.MasterDefinition!.ValueFormula = $"{soulBarrierStreng.MasterDefinition.ValueFormula} / 100";
soulBarrierStreng.MasterDefinition.TargetAttribute = soulBarrierReceiveDec;
soulBarrierStreng.MasterDefinition.Aggregation = AggregateType.AddRaw;
// Update Soul Barrier Profic master skill definition
var soulBarrierProfic = gameConfiguration.Skills.First(s => s.GetId() == new Guid("00000400-0194-0000-0000-000000000000"));
soulBarrierProfic.MasterDefinition!.ExtendsDuration = true;
soulBarrierProfic.MasterDefinition.ReplacedSkill = soulBarrierStreng;
soulBarrierProfic.AttackDamage = soulBarrierStreng.AttackDamage;
soulBarrierProfic.DamageType = soulBarrierStreng.DamageType;
soulBarrierProfic.ElementalModifierTarget = soulBarrierStreng.ElementalModifierTarget;
soulBarrierProfic.ImplicitTargetRange = soulBarrierStreng.ImplicitTargetRange;
soulBarrierProfic.MovesTarget = soulBarrierStreng.MovesTarget;
soulBarrierProfic.MovesToTarget = soulBarrierStreng.MovesToTarget;
soulBarrierProfic.SkillType = soulBarrierStreng.SkillType;
soulBarrierProfic.Target = soulBarrierStreng.Target;
soulBarrierProfic.TargetRestriction = soulBarrierStreng.TargetRestriction;
soulBarrierProfic.MagicEffectDef = soulBarrierStreng.MagicEffectDef;
}
private IncreasableItemOption CreateRelatedPetOption(IContext context, GameConfiguration gameConfiguration, ItemOptionType optionType, int number, AttributeDefinition targetAttribute, AggregateType aggregateType, short optionNumber, float baseValue = 0, params (AttributeDefinition SourceAttribute, float Multiplier)[] relatedAttributes)
{
var itemOption = context.CreateNew<IncreasableItemOption>();
itemOption.SetGuid(optionNumber, targetAttribute.Id.ExtractFirstTwoBytes());
itemOption.OptionType = gameConfiguration.ItemOptionTypes.First(t => t == optionType);
itemOption.Number = number;
itemOption.PowerUpDefinition = context.CreateNew<PowerUpDefinition>();
itemOption.PowerUpDefinition.TargetAttribute = targetAttribute.GetPersistent(gameConfiguration);
itemOption.PowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
itemOption.PowerUpDefinition.Boost.ConstantValue.Value = baseValue;
itemOption.PowerUpDefinition.Boost.ConstantValue.AggregateType = aggregateType;
for (int i = 0; i < relatedAttributes.Length; i++)
{
itemOption.PowerUpDefinition.Boost.RelatedValues.Add(this.CreateAttributeRelationship(context, gameConfiguration, targetAttribute, optionNumber, relatedAttributes[i], i));
}
return itemOption;
}
private AttributeRelationship CreateAttributeRelationship(IContext context, GameConfiguration gameConfiguration, AttributeDefinition targetAttribute, short optionNumber, (AttributeDefinition SourceAttribute, float Multiplier) relatedAttribute, int i = 0)
{
var attributeRelationship = context.CreateNew<AttributeRelationship>();
attributeRelationship.SetGuid(optionNumber, targetAttribute.Id.ExtractFirstTwoBytes(), (byte)i);
attributeRelationship.InputAttribute = relatedAttribute.SourceAttribute.GetPersistent(gameConfiguration);
attributeRelationship.InputOperator = InputOperator.Multiply;
attributeRelationship.InputOperand = relatedAttribute.Multiplier;
return attributeRelationship;
}
}

View File

@@ -0,0 +1,56 @@
// <copyright file="FixIgnoreDefenseSkillUpdatePlugIn.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.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update sets the right settings for the ignore defense skill.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("8DEC7BC2-E6A0-4E46-B123-C92CB43B9ED5")]
public class FixIgnoreDefenseSkillUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fixed ignore defense skill";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds the magic effect definition for the Ignore Defense skill.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixIgnoreDefenseSkill;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 08, 20, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var skill = gameConfiguration.Skills.First(s => s.Number == (int)SkillNumber.IgnoreDefense);
skill.SkillType = SkillType.Buff;
skill.Target = SkillTarget.ImplicitPlayer;
skill.MagicEffectDef = gameConfiguration.MagicEffects.First(m => m.Number == (int)MagicEffectNumber.IgnoreDefense);
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="FixItemOptionsAndAttackSpeedPlugIn075.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>
/// This update fixes some item options (damage, defense rate) and weapons attack speed.
/// It also refactors attack speed attributes for simplification.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("7733CDA9-6F4B-48D2-94F1-796C937F032A")]
public class FixItemOptionsAndAttackSpeedPlugIn075 : FixItemOptionsAndAttackSpeedPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixItemOptionsAndAttackSpeed075;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.FixWeaponsAttackSpeedStat(gameConfiguration);
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="FixItemOptionsAndAttackSpeedPlugIn095d.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>
/// This update fixes some item options (damage, defense rate) and weapons attack speed.
/// It also refactors attack speed attributes for simplification.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("C7F90EDB-EC00-467D-826F-9DEFFEA1206A")]
public class FixItemOptionsAndAttackSpeedPlugIn095D : FixItemOptionsAndAttackSpeedPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixItemOptionsAndAttackSpeed095d;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.FixWeaponsAttackSpeedStat(gameConfiguration);
this.ChangeDinorantAttackSpeedOption(gameConfiguration);
this.UpdateExcellentAttackSpeedAndBaseDmgOptions(gameConfiguration);
}
}

View File

@@ -0,0 +1,259 @@
// <copyright file="FixItemOptionsAndAttackSpeedPlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
/// <summary>
/// This update fixes some item options (damage, defense rate) and weapons attack speed.
/// It also refactors attack speed attributes for simplification.
/// </summary>
public abstract class FixItemOptionsAndAttackSpeedPlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Item Options And Attack Speed";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes some item options (damage, defense rate) and weapons attack speed.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 03, 05, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var attackSpeedByWeapon = Stats.AttackSpeedByWeapon.GetPersistent(gameConfiguration);
var physicalBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration);
var wizardryBaseDmg = Stats.WizardryBaseDmg.GetPersistent(gameConfiguration);
// Add AttackSpeedAny stat
var attackSpeedAny = context.CreateNew<AttributeDefinition>(Stats.AttackSpeedAny.Id, Stats.AttackSpeedAny.Designation, Stats.AttackSpeedAny.Description);
gameConfiguration.Attributes.Add(attackSpeedAny);
// Change attack speed-related combination class attributes
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
if (charClass.AttributeCombinations.FirstOrDefault(rel => rel.TargetAttribute == Stats.AttackSpeed && rel.InputAttribute == Stats.AttackSpeedByWeapon) is { } attackSpeedByWeaponToAttackSpeed
&& charClass.AttributeCombinations.FirstOrDefault(rel => rel.TargetAttribute == Stats.MagicSpeed && rel.InputAttribute == Stats.AttackSpeedByWeapon) is { } attackSpeedByWeaponToMagicSpeed
&& charClass.AttributeCombinations.FirstOrDefault(rel => rel.TargetAttribute == Stats.AttackSpeed && rel.OperandAttribute == Stats.AreTwoWeaponsEquipped) is { } areTwoWeaponsEquippedToAttackSpeedConditional
&& charClass.AttributeCombinations.FirstOrDefault(rel => rel.TargetAttribute == Stats.MagicSpeed && rel.OperandAttribute == Stats.AreTwoWeaponsEquipped) is { } areTwoWeaponsEquippedToMagicSpeedConditional)
{
attackSpeedByWeaponToAttackSpeed.InputAttribute = attackSpeedAny;
attackSpeedByWeaponToMagicSpeed.InputAttribute = attackSpeedAny;
charClass.AttributeCombinations.Add(context.CreateNew<AttributeRelationship>(
attackSpeedAny,
1,
attackSpeedByWeapon,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw));
areTwoWeaponsEquippedToAttackSpeedConditional.TargetAttribute = attackSpeedAny;
charClass.AttributeCombinations.Remove(areTwoWeaponsEquippedToMagicSpeedConditional);
}
});
// Change gloves attack speed stats
var gloves = gameConfiguration.Items.Where(i => i.Group == (int)ItemGroups.Gloves);
foreach (var glovesItem in gloves)
{
if (glovesItem.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.AttackSpeed) is { } glovesAttackSpeed
&& glovesItem.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.MagicSpeed) is { } glovesMagicSpeed)
{
glovesAttackSpeed.TargetAttribute = attackSpeedAny;
glovesItem.BasePowerUpAttributes.Remove(glovesMagicSpeed);
}
}
// Change alcohol attack speed magic effect
if (gameConfiguration.MagicEffects.FirstOrDefault(me => me.Number == (short)MagicEffectNumber.Alcohol) is { } alcoholMagicEffect
&& alcoholMagicEffect.PowerUpDefinitions.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeed) is { } alcoholAttackSpeed
&& alcoholMagicEffect.PowerUpDefinitions.FirstOrDefault(pu => pu.TargetAttribute == Stats.MagicSpeed) is { } alcoholMagicSpeed)
{
alcoholAttackSpeed.TargetAttribute = attackSpeedAny;
alcoholMagicEffect.PowerUpDefinitions.Remove(alcoholMagicSpeed);
}
// Fix Item Options (all weapon and wing damage options)
var itemOptions = gameConfiguration.ItemOptions.Where(io => io.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.Option));
foreach (var itemOption in itemOptions)
{
foreach (var opt in itemOption.PossibleOptions)
{
if (opt.PowerUpDefinition?.TargetAttribute == Stats.MaximumPhysBaseDmg)
{
opt.PowerUpDefinition.TargetAttribute = physicalBaseDmg;
}
else if (opt.PowerUpDefinition?.TargetAttribute == Stats.MaximumWizBaseDmg)
{
opt.PowerUpDefinition.TargetAttribute = wizardryBaseDmg;
}
else
{
continue;
}
}
}
gameConfiguration.PhysicalDamageOption().Name = Stats.PhysicalBaseDmg.Designation + " Option";
gameConfiguration.WizardryDamageOption().Name = Stats.WizardryBaseDmg.Designation + " Option";
// Add shield defense rate item option
gameConfiguration.ItemOptions.Add(this.CreateOptionDefinition(context, gameConfiguration, Stats.DefenseRatePvm, ItemOptionDefinitionNumbers.DefenseRateOption, 5));
// Fix all shields item option
var shields = gameConfiguration.Items.Where(i => i.Group == (int)ItemGroups.Shields);
var defenseOption = gameConfiguration.GetDefenseOption();
var defenseRateOption = gameConfiguration.GetDefenseRateOption();
foreach (var shield in shields)
{
if (shield.PossibleItemOptions.FirstOrDefault(io => io == defenseOption) is { } defOpt)
{
shield.PossibleItemOptions.Remove(defOpt);
}
shield.PossibleItemOptions.Add(defenseRateOption);
}
// Fix 3rd wings defense bonus per level table
var thirdWingsDefenseTable = gameConfiguration.ItemLevelBonusTables.FirstOrDefault(t => t.Name == "Defense Bonus (3rd Wings)");
if (thirdWingsDefenseTable is not null)
{
var thirdWings = gameConfiguration.Items.Where(i =>
i.Group == (int)ItemGroups.Orbs
&& i.BasePowerUpAttributes.Any(pua => pua.TargetAttribute == Stats.CanFly)
&& i.Requirements.Any(r => r.Attribute == Stats.Level && r.MinimumValue == 400));
foreach (var wing in thirdWings)
{
if (wing.BasePowerUpAttributes.First(pua => pua.TargetAttribute == Stats.DefenseBase) is { } defensePowerUp)
{
defensePowerUp.BonusPerLevelTable = thirdWingsDefenseTable;
}
}
}
}
#pragma warning disable SA1600, CS1591 // Elements should be documented.
protected ItemOptionDefinition CreateOptionDefinition(IContext context, GameConfiguration gameConfiguration, AttributeDefinition attributeDefinition, short number, byte baseValue)
{
var definition = context.CreateNew<ItemOptionDefinition>();
definition.SetGuid(number);
definition.Name = attributeDefinition.Designation + " Option";
definition.AddChance = 0.25f;
definition.AddsRandomly = true;
definition.MaximumOptionsPerItem = 1;
var itemOption = context.CreateNew<IncreasableItemOption>();
itemOption.SetGuid(number);
itemOption.OptionType = gameConfiguration.ItemOptionTypes.FirstOrDefault(o => o == ItemOptionTypes.Option);
itemOption.PowerUpDefinition = context.CreateNew<PowerUpDefinition>();
itemOption.PowerUpDefinition.TargetAttribute = gameConfiguration.Attributes.First(a => a == attributeDefinition);
itemOption.PowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
itemOption.PowerUpDefinition.Boost.ConstantValue!.Value = baseValue;
for (short level = 2; level <= 4; level++)
{
var levelDependentOption = context.CreateNew<ItemOptionOfLevel>();
levelDependentOption.Level = level;
var powerUpDefinition = context.CreateNew<PowerUpDefinition>();
powerUpDefinition.TargetAttribute = itemOption.PowerUpDefinition.TargetAttribute;
powerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue!.Value = level * baseValue;
levelDependentOption.PowerUpDefinition = powerUpDefinition;
itemOption.LevelDependentOptions.Add(levelDependentOption);
}
definition.PossibleOptions.Add(itemOption);
return definition;
}
protected void FixWeaponsAttackSpeedStat(GameConfiguration gameConfiguration)
{
var attackSpeedByWeapon = Stats.AttackSpeedByWeapon.GetPersistent(gameConfiguration);
var weapons = gameConfiguration.Items.Where(i => i.Group >= (int)ItemGroups.Swords && i.Group <= (int)ItemGroups.Staff);
foreach (var weapon in weapons)
{
if (weapon.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.AttackSpeed) is { } weaponAttackSpeed)
{
weaponAttackSpeed.TargetAttribute = attackSpeedByWeapon;
}
}
}
protected void ChangeDinorantAttackSpeedOption(GameConfiguration gameConfiguration)
{
var dinorantOption = gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == new Guid("00000083-0080-0000-0000-000000000000"));
dinorantOption ??= gameConfiguration.ItemOptions.FirstOrDefault(io => io.Name == "Dinorant Options"); // 0.95d
if (dinorantOption is not null
&& dinorantOption.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.AttackSpeed) is { } dinoAttackSpeed)
{
dinoAttackSpeed.PowerUpDefinition!.TargetAttribute = Stats.AttackSpeedAny.GetPersistent(gameConfiguration);
}
}
protected void UpdateExcellentAttackSpeedAndBaseDmgOptions(GameConfiguration gameConfiguration)
{
var excPhysAttackOpts = gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == new Guid("00000083-0013-0000-0000-000000000000"));
var excWizAttackOpts = gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == new Guid("00000083-0014-0000-0000-000000000000"));
var excCurseAttackOpts = gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == new Guid("00000083-0015-0000-0000-000000000000"));
var attackSpeedAny = Stats.AttackSpeedAny.GetPersistent(gameConfiguration);
var physicalBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration);
var wizardryBaseDmg = Stats.PhysicalBaseDmg.GetPersistent(gameConfiguration);
if (excPhysAttackOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.AttackSpeed) is { } physOptAttackSpeed)
{
physOptAttackSpeed.PowerUpDefinition!.TargetAttribute = attackSpeedAny;
}
if (excPhysAttackOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.MaximumPhysBaseDmg && opt.Number == 5) is { } maxPhysBaseDmg)
{
maxPhysBaseDmg.PowerUpDefinition!.TargetAttribute = physicalBaseDmg;
}
if (excWizAttackOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.AttackSpeed) is { } wizOptAttackSpeed)
{
wizOptAttackSpeed.PowerUpDefinition!.TargetAttribute = attackSpeedAny;
}
if (excWizAttackOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.MaximumWizBaseDmg && opt.Number == 5) is { } maxWizBaseDmg)
{
maxWizBaseDmg.PowerUpDefinition!.TargetAttribute = wizardryBaseDmg;
}
if (excCurseAttackOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.AttackSpeed) is { } curseOptAttackSpeed)
{
curseOptAttackSpeed.PowerUpDefinition!.TargetAttribute = attackSpeedAny;
}
if (excCurseAttackOpts?.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.MaximumCurseBaseDmg && opt.Number == 5) is { } maxCurseBaseDmg)
{
maxCurseBaseDmg.PowerUpDefinition!.TargetAttribute = wizardryBaseDmg; // Yes, wizardry. This should not be in use, but just in case.
}
}
#pragma warning restore SA1600, CS1591 // Elements should be documented.
}

View File

@@ -0,0 +1,225 @@
// <copyright file="FixItemOptionsAndAttackSpeedPlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes some item options (damage, defense rate), weapons attack speed, third wings defense, and some AA weapon values.
/// It also refactors attack speed attributes for simplification.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("EEEAA884-4704-48DE-825A-8E588A47E2CC")]
public class FixItemOptionsAndAttackSpeedPlugInSeason6 : FixItemOptionsAndAttackSpeedPlugInBase
{
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInDescription = "This update fixes some item options (damage, defense rate), weapons attack speed, third wings defense, and some AA weapon values.";
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixItemOptionsAndAttackSpeedSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
this.ChangeDinorantAttackSpeedOption(gameConfiguration);
this.UpdateExcellentAttackSpeedAndBaseDmgOptions(gameConfiguration);
var attackSpeedAny = Stats.AttackSpeedAny.GetPersistent(gameConfiguration);
// Add CurseBaseDmg stat
var curseBaseDmg = context.CreateNew<AttributeDefinition>(Stats.CurseBaseDmg.Id, Stats.CurseBaseDmg.Designation, Stats.CurseBaseDmg.Description);
gameConfiguration.Attributes.Add(curseBaseDmg);
// Fix Curse Dmg Item Options (all weapon and wing damage options)
var itemOptions = gameConfiguration.ItemOptions.Where(io =>
io.PossibleOptions.Any(po =>
po.OptionType == ItemOptionTypes.Option
&& po.PowerUpDefinition?.TargetAttribute == Stats.MaximumCurseBaseDmg));
foreach (var itemOption in itemOptions)
{
if (itemOption.PossibleOptions.First().PowerUpDefinition is { } curseDmgOpt)
{
curseDmgOpt.TargetAttribute = curseBaseDmg;
}
}
gameConfiguration.ItemOptions
.First(iod => iod.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Option && o.PowerUpDefinition?.TargetAttribute == curseBaseDmg))
.Name = curseBaseDmg.Designation + " Option";
// Change attack speed magic effects
if (gameConfiguration.MagicEffects.FirstOrDefault(me => me.Number == (short)MagicEffectNumber.PotionOfSoul) is { } soulPotionMagicEffect
&& soulPotionMagicEffect.PowerUpDefinitions.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeed) is { } soulPotionAttackSpeed
&& soulPotionMagicEffect.PowerUpDefinitions.FirstOrDefault(pu => pu.TargetAttribute == Stats.MagicSpeed) is { } soulPotionMagicSpeed)
{
soulPotionAttackSpeed.TargetAttribute = attackSpeedAny;
soulPotionMagicEffect.PowerUpDefinitions.Remove(soulPotionMagicSpeed);
}
if (gameConfiguration.MagicEffects.FirstOrDefault(me => me.Number == (short)MagicEffectNumber.JackOlanternBlessing) is { } jackMagicEffect
&& jackMagicEffect.PowerUpDefinitions.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeed) is { } jackAttackSpeed
&& jackMagicEffect.PowerUpDefinitions.FirstOrDefault(pu => pu.TargetAttribute == Stats.MagicSpeed) is { } jackMagicSpeed)
{
jackAttackSpeed.TargetAttribute = attackSpeedAny;
jackMagicEffect.PowerUpDefinitions.Remove(jackMagicSpeed);
}
// Change wizard's ring, and fire socket attack speed option
var wizardsRingOption = gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == new Guid("00000083-0073-0000-0000-000000000000"));
if (wizardsRingOption is not null
&& wizardsRingOption.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.AttackSpeed) is { } wizardsRingAttackSpeed)
{
wizardsRingAttackSpeed.PowerUpDefinition!.TargetAttribute = attackSpeedAny;
}
var fireSocketOption = gameConfiguration.ItemOptions.FirstOrDefault(io => io.GetId() == new Guid("00000083-0032-0000-0000-000000000000"));
if (fireSocketOption is not null
&& fireSocketOption.PossibleOptions.FirstOrDefault(opt => opt.PowerUpDefinition?.TargetAttribute == Stats.AttackSpeed) is { } fireSocketAttackSpeed)
{
fireSocketAttackSpeed.PowerUpDefinition!.TargetAttribute = attackSpeedAny;
}
// Add physical and attack damage item option
gameConfiguration.ItemOptions.Add(this.CreateOptionDefinition(context, gameConfiguration, Stats.BaseDamageBonus, ItemOptionDefinitionNumbers.PhysicalAndWizardryAttack, 4));
// Fix magic swords options
var magicSwords = gameConfiguration.Items.Where(i => i.Group == (int)ItemGroups.Swords
&& (i.Number == 21 // Dark Reign Blade
|| i.Number == 23 // Explosion Blade
|| i.Number == 25 // Sword Dancer
|| i.Number == 28 // Imperial Sword
|| i.Number == 31)); // Rune Blade
var wizItemOption = gameConfiguration.WizardryDamageOption();
var physAndWizItemOption = gameConfiguration.PhysicalAndWizardryDamageOption();
var excWizAttackOption = gameConfiguration.ExcellentWizardryAttackOptions();
var excPhysAttackOption = gameConfiguration.ExcellentPhysicalAttackOptions();
var harmonyWizAttackOption = gameConfiguration.ItemOptions.First(o => o.Name == HarmonyOptions.WizardryAttackOptionsName);
var harmonyPhysAttackOption = gameConfiguration.ItemOptions.First(o => o.Name == HarmonyOptions.PhysicalAttackOptionsName);
foreach (var magicSword in magicSwords)
{
magicSword.PossibleItemOptions.Remove(wizItemOption);
magicSword.PossibleItemOptions.Remove(excWizAttackOption);
magicSword.PossibleItemOptions.Remove(harmonyWizAttackOption);
magicSword.PossibleItemOptions.Add(physAndWizItemOption);
magicSword.PossibleItemOptions.Add(excPhysAttackOption);
magicSword.PossibleItemOptions.Add(harmonyPhysAttackOption);
}
// Remove staffs two handed weapon powerup & add missing powerup for soul master/mg staffs
var staffs = gameConfiguration.Items.Where(i => i.Group == (int)ItemGroups.Staff);
foreach (var staff in staffs)
{
if (staff.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.IsTwoHandedWeaponEquipped) is { } twoHandedWeapon)
{
staff.BasePowerUpAttributes.Remove(twoHandedWeapon);
}
// Dragon Soul Staff, Kundun Staff, Grand Viper Staff, Platina Staff, Deadly Staff, Imperial Staff, Chromatic Staff.
if (staff.Number == 9
|| staff.Number == 11
|| staff.Number == 12
|| staff.Number == 13
|| staff.Number == 30
|| staff.Number == 31
|| staff.Number == 33)
{
staff.BasePowerUpAttributes.Add(CreateNewBasePowerUpDefinition(Stats.IsOneHandedStaffEquipped));
}
}
ItemBasePowerUpDefinition CreateNewBasePowerUpDefinition(AttributeDefinition attribute)
{
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = attribute.GetPersistent(gameConfiguration);
powerUpDefinition.BaseValue = 1;
powerUpDefinition.AggregateType = AggregateType.AddRaw;
return powerUpDefinition;
}
// Add crystal sword two handed sword powerup
var crystalSword = gameConfiguration.Items.FirstOrDefault(i => i.Group == (int)ItemGroups.Scepters && i.Number == 5);
if (crystalSword is not null)
{
crystalSword.BasePowerUpAttributes.Add(CreateNewBasePowerUpDefinition(Stats.IsTwoHandedSwordEquipped));
}
// Fix AA weapons values
var archangelSword = gameConfiguration.Items.FirstOrDefault(i => i.Group == (int)ItemGroups.Swords && i.Number == 19);
var archangelScepter = gameConfiguration.Items.FirstOrDefault(i => i.Group == (int)ItemGroups.Scepters && i.Number == 13);
var archangelCrossbow = gameConfiguration.Items.FirstOrDefault(i => i.Group == (int)ItemGroups.Bows && i.Number == 18);
if (archangelSword is not null)
{
if (archangelSword.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.MinimumPhysBaseDmgByWeapon) is { } minPhysDmg)
{
minPhysDmg.BaseValue = 220;
}
if (archangelSword.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon) is { } maxPhysDmg)
{
maxPhysDmg.BaseValue = 230;
}
if (archangelSword.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeedByWeapon) is { } attackSpeed)
{
attackSpeed.BaseValue = 45;
}
}
if (archangelScepter is not null)
{
if (archangelScepter.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.MinimumPhysBaseDmgByWeapon) is { } minPhysDmg)
{
minPhysDmg.BaseValue = 200;
}
if (archangelScepter.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon) is { } maxPhysDmg)
{
maxPhysDmg.BaseValue = 223;
}
if (archangelScepter.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.ScepterRise) is { } rise)
{
rise.BaseValue = 138 / 2.0f;
}
}
if (archangelCrossbow is not null)
{
if (archangelCrossbow.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.MinimumPhysBaseDmgByWeapon) is { } minPhysDmg)
{
minPhysDmg.BaseValue = 224;
}
if (archangelCrossbow.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon) is { } maxPhysDmg)
{
maxPhysDmg.BaseValue = 246;
}
if (archangelCrossbow.BasePowerUpAttributes.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeedByWeapon) is { } attackSpeed)
{
attackSpeed.BaseValue = 45;
}
}
}
}

View File

@@ -0,0 +1,129 @@
// <copyright file="FixItemRequirementsPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Updates some item requirements for elf bows that were initialized wrongly.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("9E8DB2CB-1972-40D3-9129-6964ABFEB4DC")]
public class FixItemRequirementsPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plugin name.
/// </summary>
internal const string PlugInName = "Fix Item Requirements (Elf Bows)";
/// <summary>
/// The plugin description.
/// </summary>
internal const string PlugInDescription = "Updates some item requirements for elf bows which were initialized wrongly.";
private static readonly List<(int Group, int Number, int StrengthRequirement, int AgilityRequirement, int EnergyRequirement, int VitalityRequirement)> RequirementCorrections =
[
(4, 0, 20, 80, 0, 0), // Short Bow
(4, 1, 30, 90, 0, 0), // Bow
(4, 2, 30, 90, 0, 0), // Elven Bow
(4, 3, 30, 90, 0, 0), // Battle Bow
(4, 4, 30, 100, 0, 0), // Tiger Bow
(4, 5, 30, 100, 0, 0), // Silver Bow
(4, 6, 40, 150, 0, 0), // Chaos Nature Bow
/*
These are handled different, so they don't need to be corrected.
See ItemExtensions.GetRequirement.
(15, 0, 0, 0, 100, 0), // Scroll of Poison
(15, 1, 0, 0, 100, 0), // Scroll of Meteorite
(15, 2, 0, 0, 100, 0), // Scroll of Lighting
(15, 3, 0, 0, 100, 0), // Scroll of Fire Ball
(15, 4, 0, 0, 100, 0), // Scroll of Flame
(15, 5, 0, 0, 100, 0), // Scroll of Teleport
(15, 6, 0, 0, 100, 0), // Scroll of Ice
(15, 7, 0, 0, 100, 0), // Scroll of Twister
(15, 8, 0, 0, 100, 0), // Scroll of Evil Spirit
(15, 9, 0, 0, 100, 0), // Scroll of Hellfire
(15, 10, 0, 0, 100, 0), // Scroll of Power Wave
(15, 11, 0, 0, 110, 0), // Scroll of Aqua Beam
(15, 12, 0, 0, 150, 0), // Scroll of Cometfall
(15, 13, 0, 0, 200, 0), // Scroll of Inferno
(15, 14, 0, 0, 188, 0), // Scroll of Teleport Ally
(15, 15, 0, 0, 126, 0), // Scroll of Soul Barrier
(15, 16, 0, 0, 243, 0), // Scroll of Decay
(15, 17, 0, 0, 223, 0), // Scroll of Ice Storm
(15, 18, 0, 0, 258, 0), // Scroll of Nova
(15, 19, 0, 0, 75, 0), // Chain Lightning Parchment
(15, 20, 0, 0, 93, 0), // Drain Life Parchment
(15, 21, 0, 0, 216, 0), // Lightning Shock Parchment
(15, 22, 0, 0, 111, 0), // Damage Reflection Parchment
(15, 23, 0, 0, 181, 0), // Berserker Parchment
(15, 24, 0, 0, 100, 0), // Sleep Parchment
(15, 26, 0, 0, 173, 0), // Weakness Parchment
(15, 27, 0, 0, 201, 0), // Innovation Parchment
(15, 28, 0, 0, 118, 0), // Scroll of Wizardry Enhance
(15, 29, 0, 0, 118, 0), // Scroll of Gigantic Storm
(15, 34, 0, 0, ?, 0), // Ignore Defense Parchment
(15, 35, 0, 0, ?, 0), // Increase Health Parchment
(15, 36, 0, 0, ?, 0), // Increase Block Parchment
*/
];
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixItemRequirements;
/// <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(2024, 11, 03, 18, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
foreach (var reqUpdate in RequirementCorrections)
{
var item = gameConfiguration.Items.First(x => x.Number == reqUpdate.Number && x.Group == reqUpdate.Group);
UpdateRequirement(Stats.TotalStrengthRequirementValue, reqUpdate.StrengthRequirement);
UpdateRequirement(Stats.TotalAgilityRequirementValue, reqUpdate.AgilityRequirement);
UpdateRequirement(Stats.TotalEnergyRequirementValue, reqUpdate.EnergyRequirement);
UpdateRequirement(Stats.TotalVitalityRequirementValue, reqUpdate.VitalityRequirement);
void UpdateRequirement(AttributeDefinition stat, int newValue)
{
var requirement = item.Requirements.FirstOrDefault(r => r.Attribute == stat);
if (requirement is null && newValue == 0)
{
return;
}
if (requirement is null)
{
requirement = context.CreateNew<AttributeRequirement>();
requirement.Attribute = stat.GetPersistent(gameConfiguration);
item.Requirements.Add(requirement);
}
requirement.MinimumValue = newValue;
}
}
}
}

View File

@@ -0,0 +1,97 @@
// <copyright file="FixItemRequirementsPlugIn2.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Updates some item requirements for elf bows which were initialized wrongly.
/// This plugin fixes configurations that were created after the initial fix but before the base data was corrected.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A7C9D4E1-8F2B-4A3C-9E6D-7B8F9A0E1C2D")]
public class FixItemRequirementsPlugIn2 : UpdatePlugInBase
{
/// <summary>
/// The plugin name.
/// </summary>
internal const string PlugInName = "Fix Item Requirements (Elf Bows) v2";
/// <summary>
/// The plugin description.
/// </summary>
internal const string PlugInDescription = "Updates some item requirements for elf bows which were initialized wrongly. This fixes configurations created after the initial fix but before the base data was corrected.";
private static readonly List<(int Group, int Number, int StrengthRequirement, int AgilityRequirement, int EnergyRequirement, int VitalityRequirement)> RequirementCorrections =
[
(4, 0, 20, 80, 0, 0), // Short Bow
(4, 1, 30, 90, 0, 0), // Bow
(4, 2, 30, 90, 0, 0), // Elven Bow
(4, 3, 30, 90, 0, 0), // Battle Bow
(4, 4, 30, 100, 0, 0), // Tiger Bow
(4, 5, 30, 100, 0, 0), // Silver Bow
(4, 6, 40, 150, 0, 0), // Chaos Nature Bow
];
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixItemRequirements2;
/// <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(2025, 09, 16, 19, 44, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
foreach (var reqUpdate in RequirementCorrections)
{
var item = gameConfiguration.Items.FirstOrDefault(x => x.Number == reqUpdate.Number && x.Group == reqUpdate.Group);
if (item == null)
{
continue;
}
UpdateRequirement(Stats.TotalStrengthRequirementValue, reqUpdate.StrengthRequirement);
UpdateRequirement(Stats.TotalAgilityRequirementValue, reqUpdate.AgilityRequirement);
UpdateRequirement(Stats.TotalEnergyRequirementValue, reqUpdate.EnergyRequirement);
UpdateRequirement(Stats.TotalVitalityRequirementValue, reqUpdate.VitalityRequirement);
void UpdateRequirement(AttributeDefinition stat, int newValue)
{
var requirement = item.Requirements.FirstOrDefault(r => r.Attribute == stat);
if (requirement is null && newValue == 0)
{
return;
}
if (requirement is null)
{
requirement = context.CreateNew<AttributeRequirement>();
requirement.Attribute = stat.GetPersistent(gameConfiguration);
item.Requirements.Add(requirement);
}
requirement.MinimumValue = newValue;
}
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="FixJeweleryPetsDamageCalcsPlugIn075.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.PlugIns;
/// <summary>
/// This update fixes jewelery items and pet options related to damage.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("46F50226-B0A2-4FE7-B708-AEB3F306A7C0")]
public class FixJeweleryPetsDamageCalcsPlugIn075 : FixJeweleryPetsDamageCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixJeweleryPetsDamageCalcs075;
}

View File

@@ -0,0 +1,23 @@
// <copyright file="FixJeweleryPetsDamageCalcsPlugIn095d.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.PlugIns;
/// <summary>
/// This update fixes jewelery items and pet options related to damage.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("BF56A0E2-D7B3-4456-81F7-249440489607")]
public class FixJeweleryPetsDamageCalcsPlugIn095D : FixJeweleryPetsDamageCalcsPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixJeweleryPetsDamageCalcs095d;
}

View File

@@ -0,0 +1,90 @@
// <copyright file="FixJeweleryPetsDamageCalcsPlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
/// <summary>
/// This update fixes jewelery items resistance values.
/// </summary>
public abstract class FixJeweleryPetsDamageCalcsPlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Jewelery Resistance Calculations";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes jewelery items resistance values.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 09, 30, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Add new attributes
var bonusExperienceRate = context.CreateNew<AttributeDefinition>(Stats.BonusExperienceRate.Id, Stats.BonusExperienceRate.Designation, Stats.BonusExperienceRate.Description);
gameConfiguration.Attributes.Add(bonusExperienceRate);
var isPetSkeletionEquipped = context.CreateNew<AttributeDefinition>(Stats.IsPetSkeletonEquipped.Id, Stats.IsPetSkeletonEquipped.Designation, Stats.IsPetSkeletonEquipped.Description);
gameConfiguration.Attributes.Add(isPetSkeletionEquipped);
var resistancesTable = gameConfiguration.ItemLevelBonusTables.First(t => t.Name == "Elemental resistances (Jewelery)");
resistancesTable.Description = "Defines the elemental resistances for jewelery. It's 1 per item level.";
float[] resistanceIncreaseByLevel = [0, 1, 2, 3, 4];
foreach (var levelBonus in resistancesTable.BonusPerLevel)
{
levelBonus.AdditionalValue = resistanceIncreaseByLevel[levelBonus.Level];
}
// Update jewelery
var jeweleryItemSlots = gameConfiguration.ItemSlotTypes.Where(st => st.ItemSlots.Contains(9) || st.ItemSlots.Contains(10));
var jewelery = gameConfiguration.Items.Where(i => i.Group == (byte)ItemGroups.Misc1 && jeweleryItemSlots.Contains(i.ItemSlot));
var xfmRing = jewelery.First(i => i.Number == 10); // Transformation Ring
this.AddLevelRequirement(context, gameConfiguration, xfmRing, 20);
foreach (var jeweleryItem in jewelery)
{
if (jeweleryItem.BasePowerUpAttributes.FirstOrDefault(p =>
p.TargetAttribute == Stats.IceResistance ||
p.TargetAttribute == Stats.PoisonResistance ||
p.TargetAttribute == Stats.LightningResistance ||
p.TargetAttribute == Stats.FireResistance ||
p.TargetAttribute == Stats.EarthResistance ||
p.TargetAttribute == Stats.WindResistance ||
p.TargetAttribute == Stats.WaterResistance) is { } resistancePowerUp)
{
resistancePowerUp.BaseValue = 1;
}
}
}
#pragma warning disable SA1600, CS1591
protected void AddLevelRequirement(IContext context, GameConfiguration gameConfiguration, ItemDefinition item, int level)
{
var requirement = context.CreateNew<AttributeRequirement>();
requirement.Attribute = Stats.Level.GetPersistent(gameConfiguration);
requirement.MinimumValue = level;
item.Requirements.Add(requirement);
}
#pragma warning restore SA1600, CS1591
}

View File

@@ -0,0 +1,260 @@
// <copyright file="FixJeweleryPetsDamageCalcsPlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes jewelery items and pet options related to damage, and jewelery resistance values.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("DD5B0424-89DB-4DE4-A1BB-B294F2C1FCE6")]
public class FixJeweleryPetsDamageCalcsPlugInSeason6 : FixJeweleryPetsDamageCalcsPlugInBase
{
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInName = "Fix Jewelery and Pets Damage Calculations";
/// <summary>
/// The plug in description.
/// </summary>
internal new const string PlugInDescription = "This update fixes jewelery items and pet options related to damage, and jewelery resistance values.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixJeweleryPetsDamageCalcsSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
var jeweleryItemSlots = gameConfiguration.ItemSlotTypes.Where(st => st.ItemSlots.Contains(9) || st.ItemSlots.Contains(10));
var petItemSlot = gameConfiguration.ItemSlotTypes.First(st => st.ItemSlots.Contains(8));
var miscItems = gameConfiguration.Items.Where(i => i.Group == (byte)ItemGroups.Misc1);
var jewelery = miscItems.Where(i => jeweleryItemSlots.Contains(i.ItemSlot));
var pets = miscItems.Where(i => petItemSlot.Equals(i.ItemSlot));
// Remove next season rings
foreach (var jeweleryItem in jewelery.ToList())
{
if (jeweleryItem.Number is 163 or 164 or 165)
{
gameConfiguration.Items.Remove(jeweleryItem);
}
}
// Update jewelery
#pragma warning disable SA1116, SA1117 // Parameters must be on same line or separate lines
var eliteSkeletonRing = jewelery.First(i => i.Number == 39);
this.AddLevelRequirement(context, gameConfiguration, eliteSkeletonRing, 10);
if (eliteSkeletonRing.PossibleItemOptions.FirstOrDefault() is { } eliteSkeletonRingItemOptDef)
{
CreateItemBasePowerUps(eliteSkeletonRing,
(Stats.DefenseBase, 1.1f, AggregateType.Multiplicate));
CreateItemOptionDefinition(eliteSkeletonRingItemOptDef, "Elite Skeleton Transformation Ring", ItemOptionDefinitionNumbers.EliteSkeletonTransformationRing,
(Stats.MaximumHealth, 0, AggregateType.AddRaw, (Stats.Level, 1)));
}
var jackOlanternRing = jewelery.First(i => i.Number == 40);
this.AddLevelRequirement(context, gameConfiguration, jackOlanternRing, 10);
var christmasRing = jewelery.First(i => i.Number == 41);
CreateItemBasePowerUps(christmasRing,
(Stats.BaseDamageBonus, 20, AggregateType.AddRaw));
var gameMasterRing = jewelery.First(i => i.Number == 42);
CreateItemBasePowerUps(gameMasterRing,
(Stats.IceDamageBonus, 255, AggregateType.AddRaw),
(Stats.PoisonDamageBonus, 255, AggregateType.AddRaw),
(Stats.LightningDamageBonus, 255, AggregateType.AddRaw),
(Stats.FireDamageBonus, 255, AggregateType.AddRaw),
(Stats.EarthDamageBonus, 255, AggregateType.AddRaw),
(Stats.WindDamageBonus, 255, AggregateType.AddRaw),
(Stats.WaterDamageBonus, 255, AggregateType.AddRaw),
(Stats.IceResistance, 255, AggregateType.Maximum),
(Stats.PoisonResistance, 255, AggregateType.Maximum),
(Stats.LightningResistance, 255, AggregateType.Maximum),
(Stats.FireResistance, 255, AggregateType.Maximum),
(Stats.EarthResistance, 255, AggregateType.Maximum),
(Stats.WindResistance, 255, AggregateType.Maximum),
(Stats.WaterResistance, 255, AggregateType.Maximum));
var snowmanRing = jewelery.First(i => i.Number == 68);
this.AddLevelRequirement(context, gameConfiguration, snowmanRing, 10);
var pandaRing = jewelery.First(i => i.Number == 76);
pandaRing.PossibleItemOptions.Clear();
CreateItemBasePowerUps(pandaRing,
(Stats.BaseDamageBonus, 30, AggregateType.AddRaw),
(Stats.CurseBaseDmg, 30, AggregateType.AddRaw),
(Stats.MoneyAmountRate, 1.5f, AggregateType.Multiplicate),
(Stats.FinalDamageBonus, 30, AggregateType.AddRaw));
var skeletonRing = jewelery.First(i => i.Number == 122);
if (skeletonRing.PossibleItemOptions.FirstOrDefault() is { } skeletonRingItemOptDef)
{
CreateItemBasePowerUps(skeletonRing,
(Stats.BaseDamageBonus, 40, AggregateType.AddRaw),
(Stats.CurseBaseDmg, 40, AggregateType.AddRaw));
CreateItemOptionDefinition(skeletonRingItemOptDef, "Skeleton Transformation Ring", ItemOptionDefinitionNumbers.SkeletonTransformationRing,
(Stats.BonusExperienceRate, 0, AggregateType.AddRaw, (Stats.IsPetSkeletonEquipped, 0.3f)));
}
var wizardsRing = jewelery.First(i => i.Number == 20);
wizardsRing.PossibleItemOptions.Clear();
CreateItemBasePowerUps(wizardsRing,
(Stats.MinimumPhysBaseDmg, 1.1f, AggregateType.Multiplicate),
(Stats.MaximumPhysBaseDmg, 1.1f, AggregateType.Multiplicate),
(Stats.AttackSpeedAny, 10f, AggregateType.AddRaw),
(Stats.MinimumWizBaseDmg, 1.1f, AggregateType.Multiplicate),
(Stats.MaximumWizBaseDmg, 1.1f, AggregateType.Multiplicate));
// Update pets
var demon = pets.First(i => i.Number == 64);
demon.BasePowerUpAttributes.Clear();
CreateItemBasePowerUps(demon,
(Stats.MinimumPhysBaseDmg, 1.4f, AggregateType.Multiplicate),
(Stats.MaximumPhysBaseDmg, 1.4f, AggregateType.Multiplicate),
(Stats.MinimumWizBaseDmg, 1.4f, AggregateType.Multiplicate),
(Stats.MaximumWizBaseDmg, 1.4f, AggregateType.Multiplicate),
(Stats.MinimumCurseBaseDmg, 1.4f, AggregateType.Multiplicate),
(Stats.MaximumCurseBaseDmg, 1.4f, AggregateType.Multiplicate),
(Stats.AttackSpeedAny, 10f, AggregateType.AddRaw));
var panda = pets.First(i => i.Number == 80);
panda.BasePowerUpAttributes.Clear();
CreateItemBasePowerUps(panda,
(Stats.BonusExperienceRate, 0.5f, AggregateType.AddRaw),
(Stats.DefenseFinal, 50f, AggregateType.AddRaw));
var unicorn = pets.First(i => i.Number == 106);
unicorn.BasePowerUpAttributes.Clear();
CreateItemBasePowerUps(unicorn,
(Stats.MoneyAmountRate, 1.5f, AggregateType.Multiplicate),
(Stats.DefenseFinal, 50f, AggregateType.AddRaw));
var skeleton = pets.First(i => i.Number == 123);
skeleton.BasePowerUpAttributes.Clear();
CreateItemBasePowerUps(skeleton,
(Stats.MinimumPhysBaseDmg, 1.2f, AggregateType.Multiplicate),
(Stats.MaximumPhysBaseDmg, 1.2f, AggregateType.Multiplicate),
(Stats.MinimumWizBaseDmg, 1.2f, AggregateType.Multiplicate),
(Stats.MaximumWizBaseDmg, 1.2f, AggregateType.Multiplicate),
(Stats.MinimumCurseBaseDmg, 1.2f, AggregateType.Multiplicate),
(Stats.MaximumCurseBaseDmg, 1.2f, AggregateType.Multiplicate),
(Stats.AttackSpeedAny, 10f, AggregateType.AddRaw),
(Stats.BonusExperienceRate, 0.3f, AggregateType.AddRaw),
(Stats.IsPetSkeletonEquipped, 1, AggregateType.AddRaw));
#pragma warning restore SA1116, SA1117
void CreateItemBasePowerUps(ItemDefinition item, params (AttributeDefinition AttributeDefinition, float Value, AggregateType AggregateType)[] powerUps)
{
foreach (var (attributeDefinition, value, aggregateType) in powerUps)
{
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = attributeDefinition.GetPersistent(gameConfiguration);
powerUpDefinition.BaseValue = value;
powerUpDefinition.AggregateType = aggregateType;
item.BasePowerUpAttributes.Add(powerUpDefinition);
}
}
void CreateItemOptionDefinition(ItemOptionDefinition itemOptionDef, string name, short number, (AttributeDefinition TargetOption, float Value, AggregateType AggregateType, (AttributeDefinition SourceAttribute, float Multiplier)) option)
{
itemOptionDef.Name = name;
foreach (var possibleOption in itemOptionDef.PossibleOptions.ToList())
{
if (possibleOption.PowerUpDefinition?.TargetAttribute != option.TargetOption)
{
itemOptionDef.PossibleOptions.Remove(possibleOption);
}
}
if (itemOptionDef.PossibleOptions.Count == 0)
{
itemOptionDef.PossibleOptions.Add(CreateItemOption(option.TargetOption, option.Value, option.AggregateType, number, (option.Item4.SourceAttribute, option.Item4.Multiplier)));
}
else
{
// Update existing option
var possibleOption = itemOptionDef.PossibleOptions.First();
possibleOption.PowerUpDefinition!.Boost!.ConstantValue.Value = option.Value;
possibleOption.PowerUpDefinition.Boost.ConstantValue.AggregateType = option.AggregateType;
var attributeRelationship = context.CreateNew<AttributeRelationship>();
attributeRelationship.SetGuid(number, option.TargetOption.Id.ExtractFirstTwoBytes(), 0);
attributeRelationship.InputAttribute = option.Item4.SourceAttribute.GetPersistent(gameConfiguration);
attributeRelationship.InputOperator = InputOperator.Multiply;
attributeRelationship.InputOperand = option.Item4.Multiplier;
possibleOption.PowerUpDefinition.Boost.RelatedValues.Add(attributeRelationship);
}
// Always add all options "randomly" when it drops ;)
itemOptionDef.AddChance = 1.0f;
itemOptionDef.AddsRandomly = true;
itemOptionDef.MaximumOptionsPerItem = 1;
}
IncreasableItemOption CreateItemOption(AttributeDefinition targetOption, float value, AggregateType aggregateType, short number, (AttributeDefinition? SourceAttribute, float Multiplier) relatedAttribute)
{
var itemOption = context.CreateNew<IncreasableItemOption>();
itemOption.SetGuid(number, targetOption.Id.ExtractFirstTwoBytes());
itemOption.PowerUpDefinition = context.CreateNew<PowerUpDefinition>();
itemOption.PowerUpDefinition.TargetAttribute = targetOption.GetPersistent(gameConfiguration);
itemOption.PowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
itemOption.PowerUpDefinition.Boost.ConstantValue.Value = value;
itemOption.PowerUpDefinition.Boost.ConstantValue.AggregateType = aggregateType;
if (relatedAttribute.SourceAttribute is not null)
{
var attributeRelationship = context.CreateNew<AttributeRelationship>();
attributeRelationship.SetGuid(number, targetOption.Id.ExtractFirstTwoBytes(), 0);
attributeRelationship.InputAttribute = relatedAttribute.SourceAttribute.GetPersistent(gameConfiguration);
attributeRelationship.InputOperator = InputOperator.Multiply;
attributeRelationship.InputOperand = relatedAttribute.Multiplier;
itemOption.PowerUpDefinition.Boost.RelatedValues.Add(attributeRelationship);
}
return itemOption;
}
// Update skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.PoisonResistanceInc)?.MasterDefinition is { } poisonResistanceInc)
{
poisonResistanceInc.ValueFormula = poisonResistanceInc.DisplayValueFormula;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.LightningResistanceInc)?.MasterDefinition is { } lightningResistanceInc)
{
lightningResistanceInc.ValueFormula = lightningResistanceInc.DisplayValueFormula;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.IceResistanceIncrease)?.MasterDefinition is { } iceResistanceIncrease)
{
iceResistanceIncrease.ValueFormula = iceResistanceIncrease.DisplayValueFormula;
}
}
}

View File

@@ -0,0 +1,64 @@
// <copyright file="FixLevelDiv20ExcOptionUpdatePlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The chaos castle update plugin.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("B0F275DC-B3C2-4826-8263-FFDC8A8AFAEA")]
public class FixLevelDiv20ExcOptionUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Excellent Option Level/20 damage fix";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the excellent option which adds level / 20 as wizardry damage";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixLevelDiv20ExcOptionUpdate;
/// <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(2023, 04, 03, 20, 5, 0, DateTimeKind.Utc);
/// <inheritdoc />
#pragma warning disable CS1998
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
#pragma warning restore CS1998
{
var level20Relationships = gameConfiguration.ItemOptions
.SelectMany(io => io.PossibleOptions.Where(p => p.OptionType == ItemOptionTypes.Excellent))
.SelectMany(o => o.PowerUpDefinition?.Boost?.RelatedValues.Where(s => s.InputAttribute == Stats.Level && Math.Abs(s.InputOperand - 20f) < 0.01) ?? Enumerable.Empty<AttributeRelationship>())
.ToList();
foreach (var relationship in level20Relationships)
{
relationship.InputOperand = 1f / 20f;
}
}
}

View File

@@ -0,0 +1,69 @@
// <copyright file="FixLifeSwellEffectUpdatePlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update sets the right settings for the life swell effect.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("FD521A61-D5B4-4CF2-B203-6FFF12C80E51")]
public class FixLifeSwellEffectUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fixed life swell effect";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update sets the right settings for the life swell effect.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixLifeSwellEffect;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 25, 15, 05, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var effect = gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.GreaterFortitude);
if (effect is null)
{
return;
}
var boost = effect.PowerUpDefinitions.FirstOrDefault()?.Boost;
if (boost is null)
{
return;
}
foreach (var relatedValue in boost.RelatedValues)
{
relatedValue.InputOperator = InputOperator.ExponentiateByAttribute;
}
}
}

View File

@@ -0,0 +1,73 @@
// <copyright file="FixMaxManaAndAbilityJewelryOptionsUpdateSeason6.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 System.Threading.Tasks;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the calculation for max mana/AG % increase provided by Ring of Magic/Pendant of Ability options.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("EAC7C809-D4B8-443F-BE52-E56560003483")]
public class FixMaxManaAndAbilityJewelryOptionsUpdateSeason6 : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix max mana/AG % increase jewelry options";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the calculation for max mana/AG % increase provided by Ring of Magic/Pendant of Ability options";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixMaxManaAndAbilityJewelryOptionsSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 09, 26, 10, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var itemOptionGuids = new List<Guid> { new("00000083-d018-8826-0000-000000000000"), new("00000083-d01c-bbba-0000-000000000000") };
var itemOptions = gameConfiguration.ItemOptions.Where(io => itemOptionGuids.Contains(io.GetId()));
foreach (var itemOption in itemOptions)
{
var optionsOfLevel = itemOption?.PossibleOptions.FirstOrDefault()?.LevelDependentOptions
.Where(opt => opt.Level > 1)
.OrderBy(opt => opt.Level);
if (optionsOfLevel is not null)
{
for (int i = 0; i < optionsOfLevel.Count(); i++)
{
if (optionsOfLevel.ElementAt(i).PowerUpDefinition?.Boost?.ConstantValue is SimpleElement elmt && elmt.Value > i + 2)
{
elmt.Value -= i + 1;
}
}
}
}
}
}

View File

@@ -0,0 +1,260 @@
// <copyright file="FixRageFighterMultipleHitSkillsPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds the missing multiple hits to the Killing Blow, Beast Uppercut, Chain Drive, Dragon Roar and Phoenix Shot Rage Fighter skills, as well as their magic effects.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("EDDD17F9-BEA5-40F0-A653-8567566C40E7")]
public class FixRageFighterMultipleHitSkillsPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Rage Fighter Multiple Hit Skills";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds the missing multiple hits to the Killing Blow, Beast Uppercut, Chain Drive, Dragon Roar and Phoenix Shot Rage Fighter skills, as well as their magic effects.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixRageFighterMultipleHitSkills;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 1, 12, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Update attributes
var defenseDecrement = context.CreateNew<AttributeDefinition>(Stats.DefenseDecrement.Id, Stats.DefenseDecrement.Designation, Stats.DefenseDecrement.Description);
gameConfiguration.Attributes.Add(defenseDecrement);
var innovationDefDecrement = Stats.InnovationDefDecrement.GetPersistent(gameConfiguration);
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
// Update summoner berserker defense reduction attributes
// Summoner classes.
if (charClass.Number == 20 || charClass.Number == 22 || charClass.Number == 23)
{
if (charClass.AttributeCombinations.FirstOrDefault(attr => attr.TargetAttribute == Stats.DefensePvm && attr.AggregateType == AggregateType.AddFinal) is { } finalBerserkerHealthDecrementToDefensePvm)
{
finalBerserkerHealthDecrementToDefensePvm.TargetAttribute = Stats.DefenseFinal.GetPersistent(gameConfiguration);
}
if (charClass.AttributeCombinations.FirstOrDefault(attr => attr.TargetAttribute == Stats.DefensePvp && attr.AggregateType == AggregateType.AddFinal) is { } finalBerserkerHealthDecrementToDefensePvp)
{
charClass.AttributeCombinations.Remove(finalBerserkerHealthDecrementToDefensePvp);
}
}
// Add defense decrement relationships
var tempInnovDefDec = context.CreateNew<AttributeDefinition>(Guid.NewGuid(), "Temp Innovation defense decrement", string.Empty);
gameConfiguration.Attributes.Add(tempInnovDefDec);
var innovationDefDecrementToTempInnovDefDec = context.CreateNew<AttributeRelationship>(
tempInnovDefDec,
-1,
innovationDefDecrement,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
var tempInnovDefDecToDefenseDecrement = context.CreateNew<AttributeRelationship>(
defenseDecrement,
1,
tempInnovDefDec,
InputOperator.Add,
default(AttributeDefinition?),
AggregateType.Multiplicate);
charClass.AttributeCombinations.Add(innovationDefDecrementToTempInnovDefDec);
charClass.AttributeCombinations.Add(tempInnovDefDecToDefenseDecrement);
charClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(1, defenseDecrement));
});
// Update Increase Health (Vitality) magic effect
if (gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.IncreaseHealth) is { } increaseHealthEffect
&& increaseHealthEffect.PowerUpDefinitions.FirstOrDefault() is { } increaseHealthPowerUp)
{
increaseHealthPowerUp.Boost!.MaximumValue = 200f;
}
// Update Defense Reduction (Fire Slash) magic effect
if (gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.DefenseReduction) is { } defenseReductionEffect
&& defenseReductionEffect.PowerUpDefinitions.FirstOrDefault() is { } powerUp)
{
powerUp.TargetAttribute = defenseDecrement;
defenseReductionEffect.PowerUpDefinitions.Clear();
defenseReductionEffect.PowerUpDefinitions.Add(powerUp);
}
// Add new magic effects
var defensereductionBeastUppercut = this.CreateDefenseReductionBeastUppercutMagicEffect(context, gameConfiguration);
var decreaseBlockEffect = this.CreateDecreaseBlockMagicEffect(context, gameConfiguration);
// Apply default value of NumberOfHitsPerAttack to all skills
foreach (var skill in gameConfiguration.Skills)
{
skill.NumberOfHitsPerAttack = 1;
}
// Update existing skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.KillingBlow) is { } killingBlow)
{
killingBlow.NumberOfHitsPerAttack = 4;
}
var killingBlowStrengthener = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.KillingBlowStrengthener);
if (killingBlowStrengthener is not null)
{
killingBlowStrengthener.NumberOfHitsPerAttack = 4;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.KillingBlowMastery) is { } killingBlowMastery)
{
killingBlowMastery.NumberOfHitsPerAttack = 4;
killingBlowMastery.MasterDefinition!.ReplacedSkill = killingBlowStrengthener;
killingBlowMastery.MasterDefinition.ValueFormula = $"{killingBlowMastery.MasterDefinition.ValueFormula} / 100";
killingBlowMastery.MasterDefinition.TargetAttribute = Stats.WeaknessPhysDmgDecrement.GetPersistent(gameConfiguration);
killingBlowMastery.MasterDefinition.Aggregation = AggregateType.AddRaw;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BeastUppercut) is { } beastUppercut)
{
beastUppercut.MagicEffectDef = defensereductionBeastUppercut;
beastUppercut.NumberOfHitsPerAttack = 2;
}
var beastUppercutStrengthener = gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BeastUppercutStrengthener);
if (beastUppercutStrengthener is not null)
{
beastUppercutStrengthener.MagicEffectDef = defensereductionBeastUppercut;
beastUppercutStrengthener.NumberOfHitsPerAttack = 2;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.BeastUppercutMastery) is { } beastUppercutMastery)
{
beastUppercutMastery.MagicEffectDef = defensereductionBeastUppercut;
beastUppercutMastery.NumberOfHitsPerAttack = 2;
beastUppercutMastery.MasterDefinition!.ReplacedSkill = beastUppercutStrengthener;
beastUppercutMastery.MasterDefinition.ValueFormula = $"{beastUppercutMastery.MasterDefinition.ValueFormula} / -100";
beastUppercutMastery.MasterDefinition.TargetAttribute = defenseDecrement;
beastUppercutMastery.MasterDefinition.Aggregation = AggregateType.Multiplicate;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainDrive) is { } chainDrive)
{
chainDrive.MagicEffectDef!.Chance = context.CreateNew<PowerUpDefinitionValue>();
chainDrive.MagicEffectDef.Chance.ConstantValue.Value = 0.4f;
chainDrive.NumberOfHitsPerAttack = 4;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.ChainDriveStrengthener) is { } chainDriveStrengthener)
{
chainDriveStrengthener.MagicEffectDef!.Chance = context.CreateNew<PowerUpDefinitionValue>();
chainDriveStrengthener.MagicEffectDef.Chance.ConstantValue.Value = 0.4f;
chainDriveStrengthener.NumberOfHitsPerAttack = 4;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DragonRoar) is { } dragonRoar)
{
dragonRoar.SkillType = SkillType.AreaSkillExplicitTarget;
dragonRoar.NumberOfHitsPerAttack = 4;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.DragonRoarStrengthener) is { } dragonRoarStrengthener)
{
dragonRoarStrengthener.SkillType = SkillType.AreaSkillExplicitTarget;
dragonRoarStrengthener.NumberOfHitsPerAttack = 4;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.PhoenixShot) is { } phoenixShot)
{
phoenixShot.MagicEffectDef = decreaseBlockEffect;
phoenixShot.NumberOfHitsPerAttack = 4;
}
}
private MagicEffectDefinition CreateDecreaseBlockMagicEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.DecreaseBlock;
magicEffect.Name = "Decrease Block Effect (Phoenix Shot)";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 10; // 10 Seconds
magicEffect.Chance = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Chance.ConstantValue.Value = 0.1f; // 10%
var decDefRatePowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(decDefRatePowerUpDefinition);
decDefRatePowerUpDefinition.TargetAttribute = Stats.DefenseRatePvm.GetPersistent(gameConfiguration);
decDefRatePowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDefRatePowerUpDefinition.Boost.ConstantValue.Value = -50f;
decDefRatePowerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddFinal;
var decDefRatePowerUpDefinitionPvp = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitionsPvp.Add(decDefRatePowerUpDefinitionPvp);
decDefRatePowerUpDefinitionPvp.TargetAttribute = Stats.DefenseRatePvm.GetPersistent(gameConfiguration);
decDefRatePowerUpDefinitionPvp.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDefRatePowerUpDefinitionPvp.Boost.ConstantValue.Value = -20f;
decDefRatePowerUpDefinitionPvp.Boost.ConstantValue.AggregateType = AggregateType.AddFinal;
return magicEffect;
}
private MagicEffectDefinition CreateDefenseReductionBeastUppercutMagicEffect(IContext context, GameConfiguration gameConfiguration)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.DefenseReduction; // We will map skill to effect by hand in this update, so we use this number instead of DefenseReductionBeastUppercut
magicEffect.Name = "Defense Reduction Effect (Beast Uppercut)";
magicEffect.InformObservers = true;
magicEffect.SendDuration = true;
magicEffect.StopByDeath = true;
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = (float)TimeSpan.FromSeconds(10).TotalSeconds;
magicEffect.Chance = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Chance.ConstantValue.Value = 0.1f; // 10%
var reduceDefenseEffect = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(reduceDefenseEffect);
reduceDefenseEffect.TargetAttribute = Stats.DefenseDecrement.GetPersistent(gameConfiguration);
reduceDefenseEffect.Boost = context.CreateNew<PowerUpDefinitionValue>();
reduceDefenseEffect.Boost.ConstantValue.Value = 0.9f; // 10% decrease
reduceDefenseEffect.Boost.ConstantValue.AggregateType = AggregateType.Multiplicate;
return magicEffect;
}
}

View File

@@ -0,0 +1,94 @@
// <copyright file="FixSetBonusesPlugIn.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.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This updates adds the new <see cref="SystemConfiguration"/> with default settings.
/// </summary>
public abstract class FixSetBonusesPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Item Set Bonuses";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the set bonuses for additional defense of +10~15 sets and the defense rate.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2023, 04, 22, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var itemSetGroups = await context.GetAsync<ItemSetGroup>().ConfigureAwait(false);
var unassignedSets = itemSetGroups.Where(s => !gameConfiguration.ItemSetGroups.Contains(s));
foreach (var set in unassignedSets)
{
gameConfiguration.ItemSetGroups.Add(set);
}
}
/// <summary>
/// The <see cref="FixSetBonusesPlugIn"/> for season 6.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F858E471-B76D-4AAF-8886-8DEB45BC1AB8")]
public class Season6 : FixSetBonusesPlugIn
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSetBonusesSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
}
/// <summary>
/// The <see cref="FixSetBonusesPlugIn"/> for version 0.95d.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("3D0201C3-D956-4BDD-9D57-3F6FD921EDF7")]
public class V095d : FixSetBonusesPlugIn
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSetBonuses095d;
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
}
/// <summary>
/// The <see cref="FixSetBonusesPlugIn"/> for version 0.75.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("2E009ADF-1580-4E03-BA59-C9C51DC109BA")]
public class V075 : FixSetBonusesPlugIn
{
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSetBonuses075;
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
}
}

View File

@@ -0,0 +1,243 @@
// <copyright file="FixSkillMultipliersPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.CharacterClasses;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes RF skill multipliers and adds several skill-specific multipliers.
/// </summary>
[Display(Name = PlugInName, Description = PlugInDescription)]
[PlugIn]
[Guid("753F01BA-5FCA-42FA-9587-7055631C27B7")]
public class FixSkillMultipliersPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Skill Multipliers";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes RF skill multipliers and adds several skill-specific multipliers.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSkillMultipliers;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2025, 11, 28, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Add SBRD max value
Stats.SoulBarrierReceiveDecrement.GetPersistent(gameConfiguration).MaximumValue = 0.7f;
// Add new attributes
var vitalitySkillMultiplier = context.CreateNew<AttributeDefinition>(Stats.VitalitySkillMultiplier.Id, Stats.VitalitySkillMultiplier.Designation, Stats.VitalitySkillMultiplier.Description);
gameConfiguration.Attributes.Add(vitalitySkillMultiplier);
var explosionBonusDmg = context.CreateNew<AttributeDefinition>(Stats.ExplosionBonusDmg.Id, Stats.ExplosionBonusDmg.Designation, Stats.ExplosionBonusDmg.Description);
gameConfiguration.Attributes.Add(explosionBonusDmg);
var requiemBonusDmg = context.CreateNew<AttributeDefinition>(Stats.RequiemBonusDmg.Id, Stats.RequiemBonusDmg.Designation, Stats.RequiemBonusDmg.Description);
gameConfiguration.Attributes.Add(requiemBonusDmg);
var pollutionBonusDmg = context.CreateNew<AttributeDefinition>(Stats.PollutionBonusDmg.Id, Stats.PollutionBonusDmg.Designation, Stats.PollutionBonusDmg.Description);
gameConfiguration.Attributes.Add(pollutionBonusDmg);
var skillBaseMultiplier = context.CreateNew<AttributeDefinition>(Stats.SkillBaseMultiplier.Id, Stats.SkillBaseMultiplier.Designation, Stats.SkillBaseMultiplier.Description);
gameConfiguration.Attributes.Add(skillBaseMultiplier);
var skillBaseDamageBonus = context.CreateNew<AttributeDefinition>(Stats.SkillBaseDamageBonus.Id, Stats.SkillBaseDamageBonus.Designation, Stats.SkillBaseDamageBonus.Description);
gameConfiguration.Attributes.Add(skillBaseDamageBonus);
var skillFinalMultiplier = context.CreateNew<AttributeDefinition>(Stats.SkillFinalMultiplier.Id, Stats.SkillFinalMultiplier.Designation, Stats.SkillFinalMultiplier.Description);
gameConfiguration.Attributes.Add(skillFinalMultiplier);
var skillFinalDamageBonus = context.CreateNew<AttributeDefinition>(Stats.SkillFinalDamageBonus.Id, Stats.SkillFinalDamageBonus.Designation, Stats.SkillFinalDamageBonus.Description);
gameConfiguration.Attributes.Add(skillFinalDamageBonus);
var totalEnergy = Stats.TotalEnergy.GetPersistent(gameConfiguration);
var totalVitality = Stats.TotalVitality.GetPersistent(gameConfiguration);
var skillMultiplier = Stats.SkillMultiplier.GetPersistent(gameConfiguration);
// Fix RF classes skill multiplier
gameConfiguration.CharacterClasses.ForEach(charClass =>
{
// RF classes.
if (charClass.Number == 24 || charClass.Number == 25)
{
if (charClass.BaseAttributeValues.FirstOrDefault(a => a.Definition == Stats.SkillMultiplier) is { } skillMult)
{
charClass.BaseAttributeValues.Remove(skillMult);
charClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(0.5f, skillMultiplier));
}
var totalEnergyToSkillMultiplier = context.CreateNew<AttributeRelationship>(
skillMultiplier,
0.001f,
totalEnergy,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
var totalVitalityToVitalitySkillMultiplier = context.CreateNew<AttributeRelationship>(
vitalitySkillMultiplier,
0.001f,
totalVitality,
InputOperator.Multiply,
default(AttributeDefinition?),
AggregateType.AddRaw);
charClass.AttributeCombinations.Add(totalEnergyToSkillMultiplier);
charClass.AttributeCombinations.Add(totalVitalityToVitalitySkillMultiplier);
charClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(0.5f, vitalitySkillMultiplier));
}
});
// Update Increase Health magic effect
if (gameConfiguration.MagicEffects.FirstOrDefault(m => m.Number == (short)MagicEffectNumber.IncreaseHealth) is { } increaseHealthEffect)
{
if (increaseHealthEffect.PowerUpDefinitions.FirstOrDefault() is { } powerUp)
{
powerUp.TargetAttribute = totalVitality;
}
}
// Create Weakness magic effect
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)MagicEffectNumber.Weakness;
magicEffect.Name = "Weakness Effect";
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = 10; // 10 seconds
var decDmgPowerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(decDmgPowerUpDefinition);
decDmgPowerUpDefinition.TargetAttribute = Stats.WeaknessPhysDmgDecrement.GetPersistent(gameConfiguration);
decDmgPowerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
decDmgPowerUpDefinition.Boost.ConstantValue.Value = 0.05f;
decDmgPowerUpDefinition.Boost.ConstantValue.AggregateType = AggregateType.AddRaw;
// Add Wakness magic effect to Killing Blow skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.KillingBlow) is { } killingBlow)
{
killingBlow.MagicEffectDef = magicEffect;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.KillingBlowStrengthener) is { } killingBlowStr)
{
killingBlowStr.MagicEffectDef = magicEffect;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.KillingBlowMastery) is { } killingBlowMastery)
{
killingBlowMastery.MagicEffectDef = magicEffect;
}
// Add Selupan's skill multiplier
if (gameConfiguration.Monsters.FirstOrDefault(m => m.Number == 459) is { } selupan)
{
selupan.AddAttributes(new Dictionary<AttributeDefinition, float> { { skillMultiplier, 2 } }, context, gameConfiguration);
}
// Update skill attribute relationships
foreach (var skill in gameConfiguration.Skills)
{
if (skill.Number == (short)SkillNumber.Nova
|| skill.Number == (short)SkillNumber.Earthshake
|| skill.Number == (short)SkillNumber.ElectricSpike
|| skill.Number == (short)SkillNumber.ChaoticDiseier
|| skill.Number == (short)SkillNumber.Force
|| skill.Number == (short)SkillNumber.FireBlast
|| skill.Number == (short)SkillNumber.FireBurst
|| skill.Number == (short)SkillNumber.ForceWave
|| skill.Number == (short)SkillNumber.FireScream)
{
skill.AttributeRelationships.ForEach(rel => { rel.TargetAttribute = skillBaseDamageBonus; });
continue;
}
// Not really a fix, more to standardize
if (skill.Number == (short)SkillNumber.MultiShot)
{
skill.AttributeRelationships.ForEach(rel =>
{
rel.AggregateType = AggregateType.AddRaw;
});
}
}
// Add new skill attribute relationships
AddAttributeRelationship(SkillNumber.FallingSlash, Stats.SkillFinalMultiplier, 2.0f, Stats.SkillMultiplier, InputOperator.Maximum);
AddAttributeRelationship(SkillNumber.IceArrow, Stats.SkillFinalMultiplier, 2.0f, Stats.SkillMultiplier);
AddAttributeRelationship(SkillNumber.Penetration, Stats.SkillFinalMultiplier, 2.0f, Stats.SkillMultiplier);
AddAttributeRelationship(SkillNumber.Starfall, Stats.SkillFinalMultiplier, 2.0f, Stats.SkillMultiplier);
AddAttributeRelationship(SkillNumber.Explosion223, Stats.SkillFinalDamageBonus, 1.0f, Stats.ExplosionBonusDmg);
AddAttributeRelationship(SkillNumber.Requiem, Stats.SkillFinalDamageBonus, 1.0f, Stats.RequiemBonusDmg);
AddAttributeRelationship(SkillNumber.Pollution, Stats.SkillFinalDamageBonus, 1.0f, Stats.PollutionBonusDmg);
AddAttributeRelationship(SkillNumber.PlasmaStorm, Stats.SkillFinalMultiplier, 0.002f, Stats.TotalLevel);
AddAttributeRelationship(SkillNumber.PlasmaStorm, Stats.SkillFinalMultiplier, -0.6f, Stats.MaximumHealth, InputOperator.Minimum); // 0.002 * 300(min lvl)
AddAttributeRelationship(SkillNumber.PlasmaStorm, Stats.SkillFinalMultiplier, 2.0f, Stats.MaximumHealth, InputOperator.Minimum);
AddAttributeRelationship(SkillNumber.ChaoticDiseier, Stats.SkillFinalMultiplier, 0.8f, Stats.SkillMultiplier);
AddAttributeRelationship(SkillNumber.KillingBlow, Stats.SkillFinalMultiplier, 1.0f, Stats.VitalitySkillMultiplier);
AddAttributeRelationship(SkillNumber.BeastUppercut, Stats.SkillFinalMultiplier, 1.0f, Stats.VitalitySkillMultiplier);
AddAttributeRelationship(SkillNumber.ChainDrive, Stats.SkillFinalMultiplier, 1.0f, Stats.VitalitySkillMultiplier);
AddAttributeRelationship(SkillNumber.Charge, Stats.SkillFinalMultiplier, 1.0f, Stats.VitalitySkillMultiplier);
AddAttributeRelationship(SkillNumber.PhoenixShot, Stats.SkillFinalMultiplier, 1.0f, Stats.VitalitySkillMultiplier);
AddAttributeRelationship(SkillNumber.DarkSide, Stats.SkillFinalMultiplier, 0.5f, Stats.SkillMultiplier, InputOperator.Add);
AddAttributeRelationship(SkillNumber.DarkSide, Stats.SkillFinalMultiplier, 1.0f / 800, Stats.TotalAgility);
AddAttributeRelationship(SkillNumber.DragonRoar, Stats.SkillFinalMultiplier, 1.0f, Stats.SkillMultiplier);
AddAttributeRelationship(SkillNumber.DragonSlasher, Stats.SkillFinalMultiplier, 1.0f, Stats.SkillMultiplier);
void AddAttributeRelationship(SkillNumber skillNumber, AttributeDefinition targetAttribute, float multiplier, AttributeDefinition sourceAttribute, InputOperator inputOperator = InputOperator.Multiply, AggregateType aggregateType = AggregateType.AddRaw)
{
var skill = gameConfiguration.Skills.First(s => s.Number == (int)skillNumber);
var relationship = CharacterClassHelper.CreateAttributeRelationship(context, gameConfiguration, targetAttribute, multiplier, sourceAttribute, inputOperator, aggregateType);
skill.AttributeRelationships.Add(relationship);
}
// Update sum master skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.FireTomeStrengthener)?.MasterDefinition is { } fireTomeStr)
{
fireTomeStr.TargetAttribute = explosionBonusDmg;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WindTomeStrengthener)?.MasterDefinition is { } windTomeStr)
{
windTomeStr.TargetAttribute = requiemBonusDmg;
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.LightningTomeStren)?.MasterDefinition is { } lightningTomeStr)
{
lightningTomeStr.TargetAttribute = pollutionBonusDmg;
}
}
}

View File

@@ -0,0 +1,69 @@
// <copyright file="FixSocketSeedCraftingUpdatePlugIn.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.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update sets the right settings for the socket seed crafting.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("C802EFC2-1D42-4218-871E-8886D115F3ED")]
public class FixSocketSeedCraftingUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fixed socket seed crafting";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update sets the right settings for the socket seed crafting.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSocketSeedCrafting;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 25, 15, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var seedMaster = gameConfiguration.Monsters.FirstOrDefault(m => m.NpcWindow == NpcWindow.SeedMaster);
if (seedMaster is null)
{
return;
}
var seedCrafting = seedMaster.ItemCraftings.FirstOrDefault(c => c.Number == 42);
if (seedCrafting?.SimpleCraftingSettings is not { } craftingSettings)
{
return;
}
var option = gameConfiguration.ItemOptionTypes.First(o => object.Equals(o, ItemOptionTypes.Option));
foreach (var requirement in craftingSettings.RequiredItems)
{
requirement.RequiredItemOptions.Remove(option);
}
}
}

View File

@@ -0,0 +1,211 @@
// <copyright file="FixSummonerCurseSkillsPlugIn.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.CharacterClasses;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds missing area skill settings for summoner curse (book) and lightning shock skills.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A3B4C8DB-2F39-4C81-A2D9-5E4FA5B9E004")]
public class FixSummonerCurseSkillsPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Summoner Curse Skills";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds missing area skill settings for summoner curse (book) and lightning shock skills.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixSummonerCurseSkills;
/// <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, 3, 30, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// Add new attributes
this.AddStatIfNotExists(context, gameConfiguration, Stats.BleedingDamageMultiplier);
var bleedingDamageMultiplier = Stats.BleedingDamageMultiplier.GetPersistent(gameConfiguration);
this.AddStatIfNotExists(context, gameConfiguration, Stats.IsBleeding);
var isBleeding = Stats.IsBleeding.GetPersistent(gameConfiguration);
this.AddStatIfNotExists(context, gameConfiguration, Stats.MasteryStunChance);
var stunChance = Stats.MasteryStunChance.GetPersistent(gameConfiguration);
this.AddStatIfNotExists(context, gameConfiguration, Stats.MasteryMoveTargetChance);
var masteryMoveTargetChance = Stats.MasteryMoveTargetChance.GetPersistent(gameConfiguration);
// Add new base attribute to summoner classes
var summonerClassNumbers = new[] { (int)CharacterClassNumber.Summoner, (int)CharacterClassNumber.BloodySummoner, (int)CharacterClassNumber.DimensionMaster };
foreach (var characterClass in gameConfiguration.CharacterClasses.Where(c => summonerClassNumbers.Contains(c.Number)))
{
characterClass.BaseAttributeValues.Add(context.CreateNew<ConstValueAttribute>(0.6f, bleedingDamageMultiplier));
}
// Create new magic effects
var explosionEffect = this.CreateMagicEffect(context, gameConfiguration, MagicEffectNumber.Explosion, "Explosion Effect", isBleeding, duration: 5);
var requiemEffect = this.CreateMagicEffect(context, gameConfiguration, MagicEffectNumber.Requiem, "Requiem Effect", isBleeding, duration: 5);
this.CreateMagicEffect(context, gameConfiguration, MagicEffectNumber.Stunned, "Stun Effect", Stats.IsStunned.GetPersistent(gameConfiguration));
this.MapSkillToEffect(gameConfiguration, SkillNumber.Explosion223, explosionEffect);
this.MapSkillToEffect(gameConfiguration, SkillNumber.Requiem, requiemEffect);
// Set elemental modifiers
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.Pollution) is { } pollution)
{
pollution.SkipElementalModifier = true;
pollution.MagicEffectDef = this.CreateMagicEffect(context, gameConfiguration, MagicEffectNumber.Iced, "Iced", Stats.IsIced.GetPersistent(gameConfiguration), duration: 2);
}
foreach (var skillNumber in new[]
{
SkillNumber.ChainDrive, SkillNumber.ChainDriveStrengthener,
SkillNumber.LightningShock, SkillNumber.LightningShockStr,
SkillNumber.Earthshake, SkillNumber.EarthshakeStreng, SkillNumber.EarthshakeMastery,
SkillNumber.Explosion223, SkillNumber.Requiem,
})
{
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is { } skill)
{
skill.SkipElementalModifier = true;
}
}
// Update AreaSkillSettings
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Explosion223, false, 0, 0, 0, effectRange: 2);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Requiem, false, 0, 0, 0, effectRange: 2);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.Pollution, false, 0, 0, 0, minimumHitsPerAttack: 4, maximumHitsPerAttack: 8, effectRange: 3);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.LightningShock, false, 0, 0, 0, minimumHitsPerAttack: 5, maximumHitsPerAttack: 12, useTargetAreaFilter: true, targetAreaDiameter: 14);
this.AddAreaSkillSettings(gameConfiguration, context, SkillNumber.LightningShockStr, false, 0, 0, 0, minimumHitsPerAttack: 5, maximumHitsPerAttack: 12, useTargetAreaFilter: true, targetAreaDiameter: 14);
// Update master skills
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.FireTomeMastery)?.MasterDefinition is { } fireTomeMastery)
{
fireTomeMastery.TargetAttribute = bleedingDamageMultiplier;
fireTomeMastery.Aggregation = AggregateType.AddRaw;
fireTomeMastery.ValueFormula = $"{fireTomeMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.WindTomeMastery)?.MasterDefinition is { } windTomeMastery)
{
windTomeMastery.TargetAttribute = stunChance;
windTomeMastery.Aggregation = AggregateType.AddRaw;
windTomeMastery.ValueFormula = $"{windTomeMastery.ValueFormula} / 100";
}
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)SkillNumber.LightningTomeMastery)?.MasterDefinition is { } lightningTomeMastery)
{
lightningTomeMastery.TargetAttribute = masteryMoveTargetChance;
lightningTomeMastery.Aggregation = AggregateType.AddRaw;
lightningTomeMastery.ValueFormula = $"{lightningTomeMastery.ValueFormula} / 100";
}
}
private MagicEffectDefinition CreateMagicEffect(IContext context, GameConfiguration gameConfiguration, MagicEffectNumber effectNumber, string name, AttributeDefinition targetAttribute, int? duration = null)
{
var magicEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(magicEffect);
magicEffect.Number = (short)effectNumber;
magicEffect.Name = name;
magicEffect.InformObservers = true;
magicEffect.SendDuration = false;
magicEffect.StopByDeath = true;
if (duration is not null)
{
magicEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
magicEffect.Duration.ConstantValue.Value = duration.Value;
}
var powerUpDefinition = context.CreateNew<PowerUpDefinition>();
magicEffect.PowerUpDefinitions.Add(powerUpDefinition);
powerUpDefinition.TargetAttribute = targetAttribute;
powerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = 1;
return magicEffect;
}
private void MapSkillToEffect(GameConfiguration gameConfiguration, SkillNumber skillNumber, MagicEffectDefinition magicEffect)
{
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is { } skill)
{
skill.MagicEffectDef = magicEffect;
}
}
private void AddAreaSkillSettings(
GameConfiguration gameConfiguration,
IContext context,
SkillNumber skillNumber,
bool useFrustumFilter,
float frustumStartWidth,
float frustumEndWidth,
float frustumDistance,
bool useDeferredHits = false,
TimeSpan delayPerOneDistance = default,
TimeSpan delayBetweenHits = default,
int minimumHitsPerTarget = 1,
int maximumHitsPerTarget = 1,
int minimumHitsPerAttack = default,
int maximumHitsPerAttack = default,
float hitChancePerDistanceMultiplier = 1.0f,
bool useTargetAreaFilter = false,
float targetAreaDiameter = default,
int projectileCount = 1,
int effectRange = default)
{
if (gameConfiguration.Skills.FirstOrDefault(s => s.Number == (short)skillNumber) is not { } skill)
{
return;
}
skill.SkillType = SkillType.AreaSkillAutomaticHits;
var areaSkillSettings = context.CreateNew<AreaSkillSettings>();
skill.AreaSkillSettings = areaSkillSettings;
areaSkillSettings.UseFrustumFilter = useFrustumFilter;
areaSkillSettings.FrustumStartWidth = frustumStartWidth;
areaSkillSettings.FrustumEndWidth = frustumEndWidth;
areaSkillSettings.FrustumDistance = frustumDistance;
areaSkillSettings.UseTargetAreaFilter = useTargetAreaFilter;
areaSkillSettings.TargetAreaDiameter = targetAreaDiameter;
areaSkillSettings.UseDeferredHits = useDeferredHits;
areaSkillSettings.DelayPerOneDistance = delayPerOneDistance;
areaSkillSettings.DelayBetweenHits = delayBetweenHits;
areaSkillSettings.MinimumNumberOfHitsPerTarget = minimumHitsPerTarget;
areaSkillSettings.MaximumNumberOfHitsPerTarget = maximumHitsPerTarget;
areaSkillSettings.MinimumNumberOfHitsPerAttack = minimumHitsPerAttack;
areaSkillSettings.MaximumNumberOfHitsPerAttack = maximumHitsPerAttack;
areaSkillSettings.HitChancePerDistanceMultiplier = hitChancePerDistanceMultiplier;
areaSkillSettings.ProjectileCount = projectileCount;
areaSkillSettings.EffectRange = effectRange;
}
}

View File

@@ -0,0 +1,108 @@
// <copyright file="FixWarpLevelUpdatePlugIn.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>
/// updating LevelWarpRequirementReductionPercent plugin.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F4342D86-7042-477A-BC3B-475C1F2A79FF")]
public class FixWarpLevelUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Warp Level Reduction";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This plugin updates the LevelWarpRequirementReductionPercent for MG, DL, and RF.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWarpLevelUpdate;
/// <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(2023, 04, 24, 02, 05, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
// warp requirement reduction
var characterClasses = gameConfiguration.CharacterClasses
.Where(cc => cc.LevelWarpRequirementReductionPercent == 33)
.ToList();
foreach (var cc in characterClasses)
{
cc.LevelWarpRequirementReductionPercent = 34;
}
// warp list
(string Name, string? NewName, int Costs, int Level)[] warps =
{
("KanturuRuins", "KanturuRuins1", -1, 160),
("KanturuRelics", null, 12000, -1),
("Elbeland", "Elveland", -1, -1),
("Elbeland2", "Elveland2", -1, -1),
("Elbeland3", "Elveland3", -1, -1),
("Vulcan", "Vulcanus", -1, -1),
("KanturuRuins3", null, 15000, -1),
("Karutan2", null, -1, 170),
};
foreach (var (name, newName, costs, level) in warps)
{
var warpInfo = gameConfiguration.WarpList.FirstOrDefault(w => w.Name == name);
if (warpInfo is not null)
{
if (newName is not null)
{
warpInfo.Name = newName;
}
if (costs > 0)
{
warpInfo.Costs = costs;
}
if (level > 0)
{
warpInfo.LevelRequirement = level;
}
}
}
// add LaCleon
var laCleon = gameConfiguration.WarpList.FirstOrDefault(w => w.Name == "LaCleon");
if (laCleon is null)
{
laCleon = context.CreateNew<WarpInfo>();
laCleon.Index = 48;
laCleon.Name = "LaCleon";
laCleon.Costs = 15000;
laCleon.LevelRequirement = 280;
laCleon.Gate = gameConfiguration.Maps
.FirstOrDefault(m => m.Name == "LaCleon")
?.ExitGates.FirstOrDefault(g => g.X1 == 222);
gameConfiguration.WarpList.Add(laCleon);
}
}
}

View File

@@ -0,0 +1,58 @@
// <copyright file="FixWarriorMorningStarPlugIn.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.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the weapon of the warrior ancient set. The Hand Axe is replaced by the Morning Star.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("65BA79B5-1DBF-4C97-9628-0D8A429A8C88")]
public class FixWarriorMorningStarPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Warrior Morning Star";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the weapon of the warrior ancient set. The Hand Axe is replaced by the Morning Star.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWarriorMorningStar;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2023, 08, 28, 20, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var warriorSet = gameConfiguration.ItemSetGroups.First(set => set.Name == "Warrior");
var itemSet = warriorSet.Items.FirstOrDefault(item => item.ItemDefinition?.Group == (byte)ItemGroups.Axes);
if (itemSet != null)
{
itemSet.ItemDefinition = gameConfiguration.Items.First(item => item is { Group: (byte)ItemGroups.Scepters, Number: 1 });
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="FixWeaponRisePercentagePlugIn075.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.PlugIns;
/// <summary>
/// This update fixes weapons (staff) rise percentage.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("5B63534D-E5DF-46B1-992D-C1637B197EE1")]
public class FixWeaponRisePercentagePlugIn075 : FixWeaponRisePercentagePlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWeaponRisePercentage075;
}

View File

@@ -0,0 +1,23 @@
// <copyright file="FixWeaponRisePercentagePlugIn095d.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.PlugIns;
/// <summary>
/// This update fixes weapons (staff) rise percentage.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("33259706-F3DF-4F4D-9935-3DEF7E53BF81")]
public class FixWeaponRisePercentagePlugIn095D : FixWeaponRisePercentagePlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWeaponRisePercentage095d;
}

View File

@@ -0,0 +1,82 @@
// <copyright file="FixWeaponRisePercentagePlugInBase.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 MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence.Initialization.Items;
/// <summary>
/// This update fixes weapons (staff) rise percentage.
/// </summary>
public abstract class FixWeaponRisePercentagePlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Weapon Rise Percentage";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes weapons (staff) rise percentage";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 11, 11, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var staffRiseBonusTable = gameConfiguration.ItemLevelBonusTables.Single(bt => bt.Name == "Staff Rise");
// Modify existing table name and description
staffRiseBonusTable.Name = "Staff Rise (even)";
staffRiseBonusTable.Description = "The staff rise bonus per item level for even magic power staves.";
// Add new staff odd increase table
float[] staffRiseIncreaseByLevelOdd = { 0, 4, 7, 11, 14, 18, 21, 25, 28, 32, 36, 40, 45, 51, 57, 63 };
var staffOddTable = context.CreateNew<ItemLevelBonusTable>();
gameConfiguration.ItemLevelBonusTables.Add(staffOddTable);
staffOddTable.Name = "Staff Rise (odd)";
staffOddTable.Description = "The staff rise bonus per item level for odd magic power staves.";
for (int level = 0; level < staffRiseIncreaseByLevelOdd.Length; level++)
{
var value = staffRiseIncreaseByLevelOdd[level];
if (value != 0)
{
var levelBonus = context.CreateNew<LevelBonus>();
levelBonus.Level = level;
levelBonus.AdditionalValue = staffRiseIncreaseByLevelOdd[level];
staffOddTable.BonusPerLevel.Add(levelBonus);
}
}
// Fix Group 5 weapons (staves)
var staves = gameConfiguration.Items.Where(i => i.Group == (int)ItemGroups.Staff && i.BasePowerUpAttributes.Any(pua => pua.TargetAttribute == Stats.StaffRise));
foreach (var staff in staves)
{
if (staff.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.StaffRise) is { } staffRiseAttr)
{
if ((int)staffRiseAttr.BaseValue % 2 != 0)
{
staffRiseAttr.BonusPerLevelTable = staffOddTable;
}
staffRiseAttr.BaseValue /= 2.0f;
}
}
}
}

View File

@@ -0,0 +1,381 @@
// <copyright file="FixWeaponRisePercentagePlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Network.Packets;
using MUnique.OpenMU.Persistence.Initialization.Items;
using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Items;
using MUnique.OpenMU.PlugIns;
using static MUnique.OpenMU.Persistence.Initialization.CharacterClasses.CharacterClassHelper;
/// <summary>
/// This update fixes weapons (staff, stick, book, scepter) rise percentage increase; Summoner weapons and wings wizardry/curse options; and Wing of Dimension (inc/dec), Cape of Overrule (inc/dec), Cape of Emperor (dec) damage rates..
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("58740F26-6496-4CCA-8C90-C4749E09DDB2")]
public class FixWeaponRisePercentagePlugInSeason6 : FixWeaponRisePercentagePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
private new const string PlugInName = "Fix Weapon Rise Percentage, Summoner Items Wizardry/Curse Options, and Several 3rd Level Wing Damage Rates";
/// <summary>
/// The plug in description.
/// </summary>
private new const string PlugInDescription = "This update fixes weapons (staff, stick, book, scepter) rise percentage; Summoner weapons and wings wizardry/curse options; and Wing of Dimension (inc/dec), Cape of Overrule (inc/dec), Cape of Emperor (dec) damage rates. Also includes fixes for Divine Staff of Archangel and Eternal Wing Stick.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWeaponRisePercentageSeason6;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
var scepterRiseBonusTable = gameConfiguration.ItemLevelBonusTables.Single(bt => bt.Name == "Scepter Rise");
// Modify existing table name and description
scepterRiseBonusTable.Name = "Scepter Rise (even)";
scepterRiseBonusTable.Description = "The scepter rise bonus per item level for even magic power scepters.";
// Add new scepter odd increase table
float[] scepterRiseIncreaseByLevelOdd = { 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 18, 21, 25, 29, 33 };
var scepterOddTable = context.CreateNew<ItemLevelBonusTable>();
gameConfiguration.ItemLevelBonusTables.Add(scepterOddTable);
scepterOddTable.Name = "Scepter Rise (odd)";
scepterOddTable.Description = "The scepter rise bonus per item level for odd magic power scepters.";
for (int level = 0; level < scepterRiseIncreaseByLevelOdd.Length; level++)
{
var value = scepterRiseIncreaseByLevelOdd[level];
if (value != 0)
{
var levelBonus = context.CreateNew<LevelBonus>();
levelBonus.Level = level;
levelBonus.AdditionalValue = scepterRiseIncreaseByLevelOdd[level];
scepterOddTable.BonusPerLevel.Add(levelBonus);
}
}
// Fix scepters
var scepters = gameConfiguration.Items.Where(i => i.Group == (int)ItemGroups.Scepters && i.BasePowerUpAttributes.Any(pua => pua.TargetAttribute == Stats.ScepterRise));
foreach (var scepter in scepters)
{
if (scepter.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.ScepterRise) is { } scepterRiseAttr)
{
if ((int)scepterRiseAttr.BaseValue % 2 != 0)
{
scepterRiseAttr.BonusPerLevelTable = scepterOddTable;
}
scepterRiseAttr.BaseValue /= 2.0f;
}
}
// Fix Group 5 weapons (Skull & Divine Staves, sticks, and books)
var weaponsG5 = gameConfiguration.Items.Where(i => i.Group == 5);
var summonerWeapons = weaponsG5.Where(i => i.PossibleItemOptions.Contains(gameConfiguration.ItemOptions.First(io => io.Name == ExcellentOptions.CurseAttackOptionsName))); // Skull Staff included at this point
var staffEvenTable = gameConfiguration.ItemLevelBonusTables.Single(bt => bt.Name == "Staff Rise (even)");
var staffOddTable = gameConfiguration.ItemLevelBonusTables.Single(bt => bt.Name == "Staff Rise (odd)");
// -> fix Skull Staff
if (weaponsG5.FirstOrDefault(e => e.Number == 0) is { } skullStaff)
{
skullStaff.PossibleItemOptions.Clear();
skullStaff.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.Name == ExcellentOptions.WizardryAttackOptionsName));
skullStaff.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.Name == HarmonyOptions.WizardryAttackOptionsName));
skullStaff.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Luck)));
skullStaff.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Option && o.PowerUpDefinition?.TargetAttribute == Stats.MaximumWizBaseDmg)));
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = Stats.StaffRise.GetPersistent(gameConfiguration);
powerUpDefinition.BaseValue = 6 / 2.0f;
powerUpDefinition.AggregateType = AggregateType.AddRaw;
powerUpDefinition.BonusPerLevelTable = staffEvenTable;
skullStaff.BasePowerUpAttributes.Add(powerUpDefinition);
}
// -> fix Divine Staff of Archangel
if (weaponsG5.FirstOrDefault(e => e.Number == 10) is { } divineStaff)
{
var basePowerUps = divineStaff.BasePowerUpAttributes;
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.MinimumPhysBaseDmgByWeapon) is { } minPhysDmgAttr)
{
minPhysDmgAttr.BaseValue = 153;
}
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon) is { } maxPhysDmgAttr)
{
maxPhysDmgAttr.BaseValue = 165;
}
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeedByWeapon) is { } attackSpeedAttr)
{
attackSpeedAttr.BaseValue = 30;
}
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.StaffRise) is { } staffRiseAttr)
{
staffRiseAttr.BaseValue = 156 / 2.0f;
}
}
var rhOnlySlotType = gameConfiguration.ItemSlotTypes.First(t => !t.ItemSlots.Contains(0) && t.ItemSlots.Contains(1));
var books = summonerWeapons.Where(e => e.ItemSlot == rhOnlySlotType);
var sticks = summonerWeapons.Except(books);
// -> fix sticks
Dictionary<int, int> sticksMagicPower = new()
{
[14] = 34,
[15] = 46,
[16] = 59,
[17] = 76,
[18] = 92,
[19] = 110,
[20] = 106,
[34] = 130,
[36] = 146,
};
foreach (var stick in sticks)
{
stick.PossibleItemOptions.Clear();
stick.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.Name == ExcellentOptions.WizardryAttackOptionsName));
stick.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.Name == HarmonyOptions.WizardryAttackOptionsName));
stick.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Luck)));
stick.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Option && o.PowerUpDefinition?.TargetAttribute == Stats.MaximumWizBaseDmg)));
if (sticksMagicPower.ContainsKey(stick.Number))
{
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = Stats.StaffRise.GetPersistent(gameConfiguration);
powerUpDefinition.BaseValue = sticksMagicPower[stick.Number] / 2.0f;
powerUpDefinition.AggregateType = AggregateType.AddRaw;
powerUpDefinition.BonusPerLevelTable = sticksMagicPower[stick.Number] % 2 == 0 ? staffEvenTable : staffOddTable;
stick.BasePowerUpAttributes.Add(powerUpDefinition);
}
}
// -> fix Eternal Wing Stick
if (weaponsG5.FirstOrDefault(e => e.Number == 20) is { } eternalStick)
{
eternalStick.DropLevel = 147;
var basePowerUps = eternalStick.BasePowerUpAttributes;
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.MinimumPhysBaseDmgByWeapon) is { } minPhysDmgAttr)
{
minPhysDmgAttr.BaseValue = 66;
}
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon) is { } maxPhysDmgAttr)
{
maxPhysDmgAttr.BaseValue = 74;
}
if (basePowerUps.FirstOrDefault(pu => pu.TargetAttribute == Stats.AttackSpeedByWeapon) is { } attackSpeedAttr)
{
attackSpeedAttr.BaseValue = 30;
}
}
// -> fix books
if (gameConfiguration.CharacterClasses.FirstOrDefault(cc => cc.Number == (int)CharacterClassNumber.Summoner) is { } summoner)
{
var bookRiseAttr = context.CreateNew<AttributeDefinition>(Stats.BookRise.Id, Stats.BookRise.Designation, Stats.BookRise.Description);
gameConfiguration.Attributes.Add(bookRiseAttr);
summoner.AttributeCombinations.Add(CreateAttributeRelationship(context, gameConfiguration, Stats.CurseAttackDamageIncrease, 1.0f / 100, Stats.BookRise));
}
Dictionary<int, int> booksMagicPower = new() { [21] = 46, [22] = 59, [23] = 72 };
foreach (var book in books)
{
book.PossibleItemOptions.Clear();
book.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.Name == ExcellentOptions.WizardryAttackOptionsName));
book.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.Name == HarmonyOptions.WizardryAttackOptionsName));
book.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Luck)));
book.PossibleItemOptions.Add(gameConfiguration.ItemOptions.First(io => io.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Option && o.PowerUpDefinition?.TargetAttribute == Stats.MaximumCurseBaseDmg)));
if (booksMagicPower.ContainsKey(book.Number))
{
var powerUpDefinition = context.CreateNew<ItemBasePowerUpDefinition>();
powerUpDefinition.TargetAttribute = Stats.BookRise.GetPersistent(gameConfiguration);
powerUpDefinition.BaseValue = booksMagicPower[book.Number] / 2.0f;
powerUpDefinition.AggregateType = AggregateType.AddRaw;
powerUpDefinition.BonusPerLevelTable = booksMagicPower[book.Number] % 2 == 0 ? staffEvenTable : staffOddTable;
book.BasePowerUpAttributes.Add(powerUpDefinition);
}
if (book.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.IsStickEquipped) is { } stickEquipAttr)
{
book.BasePowerUpAttributes.Remove(stickEquipAttr);
}
}
// Fix Wings of Curse options
var wingsOfCurse = gameConfiguration.Items.FirstOrDefault(i => i.GetId() == new Guid("00000080-000c-0029-0000-000000000000"));
if (wingsOfCurse is not null)
{
wingsOfCurse.Name = "Wings of Curse";
var wingOpts = wingsOfCurse.PossibleItemOptions.First(o => o.Name == "Wing of Curse Options");
var wizOption = wingOpts.PossibleOptions.First();
wizOption.PowerUpDefinition = this.CreatePowerUpDefinition(Stats.MaximumWizBaseDmg, 0, AggregateType.AddRaw, context, gameConfiguration);
wizOption.LevelDependentOptions.Clear();
for (int level = 1; level <= 4; level++)
{
var optionOfLevel = context.CreateNew<ItemOptionOfLevel>();
optionOfLevel.Level = level;
optionOfLevel.PowerUpDefinition = this.CreatePowerUpDefinition(
wizOption.PowerUpDefinition.TargetAttribute!,
level * 4f,
AggregateType.AddRaw,
context,
gameConfiguration);
wizOption.LevelDependentOptions.Add(optionOfLevel);
}
}
// Fix Wings of Despair options
var wingsOfDespair = gameConfiguration.Items.FirstOrDefault(i => i.GetId() == new Guid("00000080-000c-002a-0000-000000000000"));
if (wingsOfDespair is not null)
{
var wingOpts = wingsOfDespair.PossibleItemOptions.First(o => o.Name == "Wings of Despair Options");
var curseOption = wingOpts.PossibleOptions.First(o => o.Number == 0);
curseOption.PowerUpDefinition = this.CreatePowerUpDefinition(Stats.MaximumCurseBaseDmg, 0, AggregateType.AddRaw, context, gameConfiguration);
curseOption.LevelDependentOptions.Clear();
for (int level = 1; level <= 4; level++)
{
var optionOfLevel = context.CreateNew<ItemOptionOfLevel>();
optionOfLevel.Level = level;
optionOfLevel.PowerUpDefinition = this.CreatePowerUpDefinition(
curseOption.PowerUpDefinition.TargetAttribute!,
level * 4f,
AggregateType.AddRaw,
context,
gameConfiguration);
curseOption.LevelDependentOptions.Add(optionOfLevel);
}
var wizOption = wingOpts.PossibleOptions.First(o => o.Number == 2);
wizOption.PowerUpDefinition = this.CreatePowerUpDefinition(Stats.MaximumWizBaseDmg, 0, AggregateType.AddRaw, context, gameConfiguration);
wizOption.LevelDependentOptions.Clear();
for (int level = 1; level <= 4; level++)
{
var optionOfLevel = context.CreateNew<ItemOptionOfLevel>();
optionOfLevel.Level = level;
optionOfLevel.PowerUpDefinition = this.CreatePowerUpDefinition(
wizOption.PowerUpDefinition.TargetAttribute!,
level * 4f,
AggregateType.AddRaw,
context,
gameConfiguration);
wizOption.LevelDependentOptions.Add(optionOfLevel);
}
}
// Fix Wing of Dimension options
var wingsOfDimension = gameConfiguration.Items.FirstOrDefault(i => i.GetId() == new Guid("00000080-000c-002b-0000-000000000000"));
if (wingsOfDimension is not null)
{
var wingOpts = wingsOfDimension.PossibleItemOptions.First(o => o.Name == "Wing of Dimension Options");
var wizOption = wingOpts.PossibleOptions.First(o => o.Number == 3);
wizOption.PowerUpDefinition = this.CreatePowerUpDefinition(Stats.MaximumWizBaseDmg, 0, AggregateType.AddRaw, context, gameConfiguration);
wizOption.LevelDependentOptions.Clear();
for (int level = 1; level <= 4; level++)
{
var optionOfLevel = context.CreateNew<ItemOptionOfLevel>();
optionOfLevel.Level = level;
optionOfLevel.PowerUpDefinition = this.CreatePowerUpDefinition(
wizOption.PowerUpDefinition.TargetAttribute!,
level * 4f,
AggregateType.AddRaw,
context,
gameConfiguration);
wizOption.LevelDependentOptions.Add(optionOfLevel);
}
var curseOption = wingOpts.PossibleOptions.First(o => o.Number == 2);
curseOption.PowerUpDefinition = this.CreatePowerUpDefinition(Stats.MaximumCurseBaseDmg, 0, AggregateType.AddRaw, context, gameConfiguration);
curseOption.LevelDependentOptions.Clear();
for (int level = 1; level <= 4; level++)
{
var optionOfLevel = context.CreateNew<ItemOptionOfLevel>();
optionOfLevel.Level = level;
optionOfLevel.PowerUpDefinition = this.CreatePowerUpDefinition(
curseOption.PowerUpDefinition.TargetAttribute!,
level * 4f,
AggregateType.AddRaw,
context,
gameConfiguration);
curseOption.LevelDependentOptions.Add(optionOfLevel);
}
if (wingsOfDimension.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.AttackDamageIncrease) is { } dmgInc)
{
dmgInc.BaseValue = 1f + (39 / 100f);
}
if (wingsOfDimension.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.DamageReceiveDecrement) is { } dmgDec)
{
dmgDec.BaseValue = 1f - (39 / 100f);
}
}
// Fix Cape of Overrule dmg increase/decrease rate
var capeOfOverrule = gameConfiguration.Items.FirstOrDefault(i => i.GetId() == new Guid("00000080-000c-0032-0000-000000000000"));
if (capeOfOverrule is not null)
{
if (capeOfOverrule.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.AttackDamageIncrease) is { } dmgInc)
{
dmgInc.BaseValue = 1f + (39 / 100f);
}
if (capeOfOverrule.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.DamageReceiveDecrement) is { } dmgDec)
{
dmgDec.BaseValue = 1f - (39 / 100f);
}
}
// Fix Cape of Emperor dmg decrease rate
var capeOfEmperor = gameConfiguration.Items.FirstOrDefault(i => i.GetId() == new Guid("00000080-000c-0028-0000-000000000000"));
if (capeOfEmperor is not null)
{
if (capeOfEmperor.BasePowerUpAttributes.FirstOrDefault(pua => pua.TargetAttribute == Stats.DamageReceiveDecrement) is { } dmgDec)
{
dmgDec.BaseValue = 1f - (24 / 100f);
}
}
}
private PowerUpDefinition CreatePowerUpDefinition(AttributeDefinition attributeDefinition, float value, AggregateType aggregateType, IContext context, GameConfiguration gameConfiguration)
{
var powerUpDefinition = context.CreateNew<PowerUpDefinition>();
powerUpDefinition.TargetAttribute = attributeDefinition.GetPersistent(gameConfiguration);
if (value != 0)
{
powerUpDefinition.Boost = context.CreateNew<PowerUpDefinitionValue>();
powerUpDefinition.Boost.ConstantValue.Value = value;
powerUpDefinition.Boost.ConstantValue.AggregateType = aggregateType;
}
return powerUpDefinition;
}
}

View File

@@ -0,0 +1,132 @@
// <copyright file="FixWingsAndCapesCraftingsUpdatePlugIn.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>
/// This update sets the right settings for the wings and capes craftings.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("618A53AF-ED2A-4C78-A103-BAD061FFB0D2")]
public class FixWingsAndCapesCraftingsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fixed wings and capes craftings";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update sets the right settings for the wings and capes craftings.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWingsAndCapesCraftings;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2024, 08, 14, 8, 00, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var chaosGoblin = gameConfiguration.Monsters.FirstOrDefault(m => m.NpcWindow == NpcWindow.ChaosMachine);
if (chaosGoblin is null)
{
return;
}
FixSecondWingCrafting(chaosGoblin);
FixFlameOfCondorCrafting(chaosGoblin, gameConfiguration);
FixCapeOfFighterCrafting(chaosGoblin, gameConfiguration);
}
private static void FixCapeOfFighterCrafting(MonsterDefinition chaosGoblin, GameConfiguration gameConfiguration)
{
var capeCrafting = chaosGoblin.ItemCraftings.FirstOrDefault(c => c.Number == 24);
if (capeCrafting is null)
{
return;
}
if (gameConfiguration.Items.FirstOrDefault(item => item.Group == 13 && item.Number == 49) is not { } oldScroll)
{
return;
}
if (gameConfiguration.Items.FirstOrDefault(item => item.Group == 12 && item.Number == 49) is not { } capeOfFighter)
{
return;
}
if (capeCrafting.SimpleCraftingSettings?.ResultItems.FirstOrDefault(it => object.Equals(it.ItemDefinition, oldScroll)) is { } resultItem)
{
resultItem.ItemDefinition = capeOfFighter;
}
}
private static void FixFlameOfCondorCrafting(MonsterDefinition chaosGoblin, GameConfiguration gameConfiguration)
{
var flameOfCondorCrafting = chaosGoblin.ItemCraftings.FirstOrDefault(c => c.Number == 38);
if (flameOfCondorCrafting is null)
{
return;
}
var requiredItems = flameOfCondorCrafting.SimpleCraftingSettings?.RequiredItems
.FirstOrDefault(it => it.PossibleItems.Any(item => item.Group == 12 && item.Number == 3));
if (requiredItems is null)
{
return;
}
if (gameConfiguration.Items.FirstOrDefault(item => item.Group == 12 && item.Number == 49) is { } capeOfFighter
&& !requiredItems.PossibleItems.Any(it => object.Equals(it, capeOfFighter)))
{
requiredItems.PossibleItems.Add(capeOfFighter);
}
if (gameConfiguration.Items.FirstOrDefault(item => item.Group == 13 && item.Number == 30) is { } capeOfLord
&& !requiredItems.PossibleItems.Any(it => object.Equals(it, capeOfLord)))
{
requiredItems.PossibleItems.Add(capeOfLord);
}
}
private static void FixSecondWingCrafting(MonsterDefinition chaosGoblin)
{
var secondWingsCrafting = chaosGoblin.ItemCraftings.FirstOrDefault(c => c.Number == 7);
if (secondWingsCrafting is null)
{
return;
}
var requiredItems = secondWingsCrafting.SimpleCraftingSettings?.RequiredItems
.FirstOrDefault(it => it.PossibleItems.Any(item => item.Group == 12 && item.Number == 0));
if (requiredItems is null)
{
return;
}
requiredItems.MinimumItemLevel = 0;
}
}

View File

@@ -0,0 +1,25 @@
// <copyright file="FixWingsDmgRatesUpdatePlugIn075.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.AttributeSystem;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the wings damage absorption and increase bonus level tables values for a <see cref="CombinedElement"/> (sum) calculation, instead of a compound calculation
/// for version 075.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("3821267A-9C37-40E5-B023-BAB1A8E4DAB7")]
public class FixWingsDmgRatesUpdatePlugIn075 : FixWingsDmgRatesUpdatePlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWingsDmgRatesPlugIn075;
}

View File

@@ -0,0 +1,25 @@
// <copyright file="FixWingsDmgRatesUpdatePlugIn095d.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.AttributeSystem;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the wings damage absorption and increase bonus level tables values for a <see cref="CombinedElement"/> (sum) calculation, instead of a compound calculation
/// for version 095d.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("F45FA4D0-B19B-48E2-9592-A37F3B36348A")]
public class FixWingsDmgRatesUpdatePlugIn095D : FixWingsDmgRatesUpdatePlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWingsDmgRatesPlugIn095d;
}

View File

@@ -0,0 +1,59 @@
// <copyright file="FixWingsDmgRatesUpdatePlugInBase.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 MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
/// <summary>
/// This update fixes the wings damage absorption and increase bonus level tables values for a <see cref="CombinedElement"/> (sum) calculation, instead of a compound calculation.
/// </summary>
public abstract class FixWingsDmgRatesUpdatePlugInBase : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Fix Wings Damage Rates";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update fixes the wings damage absorption and increase bonus level tables values.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2023, 10, 8, 16, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
string dmgAbsorbCommonName = "Wing absorb";
string dmgIncreaseCommonName = "Damage Increase (1st and 3rd Wings)";
string dmgIncrease2ndWingsName = "Damage Increase (2nd Wings)";
string[] wingDmgTableNames = [dmgAbsorbCommonName, dmgIncrease2ndWingsName, dmgIncreaseCommonName];
foreach (var tableName in wingDmgTableNames)
{
var bonusEntries = gameConfiguration.ItemLevelBonusTables.FirstOrDefault(ilbt => ilbt.Name == tableName)?.BonusPerLevel;
if (bonusEntries is not null)
{
foreach (var bonusEntry in bonusEntries)
{
bonusEntry.AdditionalValue -= 1.0f;
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
// <copyright file="FixWingsDmgRatesUpdatePlugInSeason6.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.AttributeSystem;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update fixes the wings damage absorption and increase bonus level tables values for a <see cref="CombinedElement"/> (sum) calculation, instead of a compound calculation
/// for season 6.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("03F49890-CB0E-40B7-A590-174BBA1962F4")]
public class FixWingsDmgRatesUpdatePlugInSeason6 : FixWingsDmgRatesUpdatePlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.FixWingsDmgRatesPlugInSeason6;
}

View File

@@ -0,0 +1,59 @@
// <copyright file="IConfigurationUpdatePlugIn.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>
/// An interface for a plug in which provides data updates for a <see cref="IDataInitializationPlugIn"/>.
/// </summary>
[Guid("C4DB0C18-84DE-40DE-BD4C-22D884FB3790")]
[PlugInPoint("Configuration update", "Provides updates for initialized data.")]
public interface IConfigurationUpdatePlugIn : IStrategyPlugIn<int>
{
/// <summary>
/// Gets the version number of the update. This must be unique over all <see cref="DataInitializationKey"/>s.
/// </summary>
UpdateVersion Version { get; }
/// <summary>
/// Gets the data initialization key to which this update belongs.
/// </summary>
string DataInitializationKey { get; }
/// <summary>
/// Gets the name of the update.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description about the update.
/// </summary>
string Description { get; }
/// <summary>
/// Gets a value indicating whether this update is mandatory and will be
/// installed automatically without asking the user.
/// </summary>
bool IsMandatory { get; }
/// <summary>
/// Gets the creation date of the update (at development).
/// </summary>
DateTime CreatedAt { get; }
/// <summary>
/// Applies this update on the given persistence context.
/// </summary>
/// <param name="context">The persistence context.</param>
/// <param name="gameConfiguration">The game configuration which can be updated.</param>
/// <remarks>
/// Calling <see cref="IContext.SaveChangesAsync"/> is not required in this implementation.
/// It will be called by <see cref="DataUpdateService"/>.
/// </remarks>
ValueTask ApplyUpdateAsync(IContext context, GameConfiguration gameConfiguration);
}

View File

@@ -0,0 +1,68 @@
// <copyright file="InfinityArrowSkillOnQuestCompletionPlugIn.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.DataModel.Configuration.Quests;
using MUnique.OpenMU.GameServer.MessageHandler.Quests;
using MUnique.OpenMU.Persistence.Initialization.CharacterClasses;
using MUnique.OpenMU.Persistence.Initialization.Skills;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update adds the infinity arrow skill as quest reward for 'Gain Hero Status (Muse Elf)'.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A78B7540-75AC-494C-9AEC-BC943D929C98")]
public class InfinityArrowSkillOnQuestCompletionPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Adds infinity arrow skill with quest";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds the infinity arrow skill as quest reward for 'Gain Hero Status (Muse Elf)'.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.InfinityArrowSkillOnQuestCompletion;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2023, 08, 28, 20, 30, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var marlon = gameConfiguration.Monsters.First(m => m.Number == 229);
var quest = marlon.Quests.First(q => q.Number == 2 && q.Group == QuestConstants.LegacyQuestGroup && q.QualifiedCharacter?.Number == (byte)CharacterClassNumber.MuseElf);
if (quest.Rewards.Any(r => r.RewardType == QuestRewardType.Skill))
{
return;
}
var skillReward = context.CreateNew<QuestReward>();
skillReward.Value = 1;
skillReward.SkillReward = gameConfiguration.Skills.First(s => s.Number == (short)SkillNumber.InfinityArrow);
skillReward.RewardType = QuestRewardType.Skill;
quest.Rewards.Add(skillReward);
}
}

View File

@@ -0,0 +1,60 @@
// <copyright file="LimitWhiteWizardDropsUpdatePlugIn.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.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This update limits the Wizard's Ring to one per character for existing databases.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("A7B3E5F1-8C2D-4E6F-9A1B-3D5C7E8F2A4B")]
public class LimitWhiteWizardDropsUpdatePlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Limit White Wizard drops";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "Limits the Wizard's Ring to one per character.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.LimitWhiteWizardDrops;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 07, 05, 2, 20, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var wizardsRing = gameConfiguration.Items.FirstOrDefault(item =>
item.Number == ItemConstants.WizardsRing.Number && item.Group == ItemConstants.WizardsRing.Group);
if (wizardsRing is not null)
{
wizardsRing.StorageLimitPerCharacter = 1;
}
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="RemoveJewelDropLevelGapPlugIn075.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>
/// This update removes the existing drop level gap condition for jewels and similar items that should always drop.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("CD958BC1-F17A-4C60-B66D-BD29D49B6ADA")]
public class RemoveJewelDropLevelGapPlugIn075 : RemoveJewelDropLevelGapPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version075.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.RemoveJewelDropLevelGap075;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="RemoveJewelDropLevelGapPlugIn095d.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>
/// This update removes the existing drop level gap condition for jewels and similar items that should always drop.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("6614E91E-5749-478A-96A4-3240E7C1280E")]
public class RemoveJewelDropLevelGapPlugIn095D : RemoveJewelDropLevelGapPlugInBase
{
/// <inheritdoc />
public override string DataInitializationKey => Version095d.DataInitialization.Id;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.RemoveJewelDropLevelGap095d;
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
}
}

Some files were not shown because too many files have changed in this diff Show More