Merge pull request #825 from eduardosmaniotto/feature/configurable-npc-buffs

feature: configurable npc buffs
(cherry picked from commit 6a061fcad756e644dadf3721e84e3cce2a25bceb)
This commit is contained in:
sven-n
2026-07-15 22:39:52 +02:00
committed by Acentech Dev
parent ed32ebdfd3
commit d22ec20a04
22 changed files with 5953 additions and 71 deletions

View File

@@ -0,0 +1,30 @@
// <copyright file="Buff.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.Annotations;
/// <summary>
/// A buff which can be granted by an NPC.
/// </summary>
[Cloneable]
public partial class Buff
{
/// <summary>
/// Gets or sets the magic effect definition which defines the buff and its duration.
/// </summary>
[MemberOfAggregate]
public virtual MagicEffectDefinition? MagicEffectDefinition { get; set; }
/// <summary>
/// Gets or sets the minimum character level to be allowed to receive this buff. Optional.
/// </summary>
public int? MinimumLevel { get; set; }
/// <summary>
/// Gets or sets the maximum character level to be allowed to receive this buff. Optional.
/// </summary>
public int? MaximumLevel { get; set; }
}

View File

@@ -328,6 +328,12 @@ public partial class MonsterDefinition
[MemberOfAggregate]
public virtual ICollection<QuestDefinition> Quests { get; protected set; } = null!;
/// <summary>
/// Gets or sets the buffs which can be granted by this npc.
/// </summary>
[MemberOfAggregate]
public virtual ICollection<Buff> Buffs { get; protected set; } = null!;
/// <summary>
/// Attribute default accessor.
/// </summary>

View File

@@ -423,6 +423,12 @@ public sealed class Party : AsyncDisposable
if (!shouldDispose)
{
this._partyMembers = this._partyMembers.Where(m => m != member).ToArray();
// If the party master is leaving, assign the new master to the first remaining member.
if (this.PartyMaster == member && this._partyMembers.Length > 0)
{
this.PartyMaster = this._partyMembers[0];
}
}
}

View File

@@ -1,64 +0,0 @@
// <copyright file="ElfSoldierBuffRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Quests;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action of requesting the elf soldier buff.
/// </summary>
/// <remarks>
/// Instead of hard-coding all this stuff, we could define something like a 'RequestableBuff' in the MonsterDefinition.
/// </remarks>
public class ElfSoldierBuffRequestAction
{
private static readonly short ElfSoldierNumber = 257;
private static readonly MagicEffectDefinition BuffEffect = new SoldierBuffMagicEffectDefinition
{
InformObservers = true,
Name = "Elf Soldier Buff",
Number = 3,
StopByDeath = true,
};
/// <summary>
/// Requests the buff and adds it to the <see cref="Player.MagicEffectList"/> when the player is allowed to get it.
/// </summary>
/// <param name="player">The player.</param>
public async ValueTask RequestBuffAsync(Player player)
{
if (player.OpenedNpc is null
|| player.OpenedNpc.Definition.NpcWindow != NpcWindow.NpcDialog
|| player.OpenedNpc.Definition.Number != ElfSoldierNumber)
{
return;
}
if (player.Level > 220)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ElfSoldierStrongEnoughMessage)).ConfigureAwait(false);
return;
}
await player.MagicEffectList.AddEffectAsync(new MagicEffect(
TimeSpan.FromMinutes(60),
BuffEffect,
new MagicEffect.ElementWithTarget(new ConstantElement(50 + (player.Level / 5), AggregateType.AddFinal), Stats.DefenseFinal),
new MagicEffect.ElementWithTarget(new ConstantElement(45 + (player.Level / 3)), Stats.GreaterDamageBonus))).ConfigureAwait(false);
}
private sealed class SoldierBuffMagicEffectDefinition : MagicEffectDefinition
{
public SoldierBuffMagicEffectDefinition()
{
this.PowerUpDefinitions = new List<PowerUpDefinition>(2);
}
}
}

View File

@@ -0,0 +1,90 @@
// <copyright file="NpcBuffRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Quests;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action which applies the <see cref="Buff"/>s of the currently opened NPC.
/// </summary>
public class NpcBuffRequestAction
{
/// <summary>
/// Requests the buffs from the opened NPC and adds them to the <see cref="Player.MagicEffectList"/>.
/// </summary>
/// <param name="player">The player.</param>
public async ValueTask RequestBuffAsync(Player player)
{
if (player.OpenedNpc?.Definition is not { NpcWindow: NpcWindow.NpcDialog, Buffs: { } buffs } || !buffs.Any())
{
return;
}
var anyApplied = false;
var anyTooLow = false;
var anyTooStrong = false;
var anyValidEffect = false;
foreach (var buff in buffs)
{
if (buff.MagicEffectDefinition is not { } effectDef)
{
continue;
}
anyValidEffect = true;
if (buff.MinimumLevel.HasValue && player.Level < buff.MinimumLevel.Value)
{
anyTooLow = true;
continue;
}
if (buff.MaximumLevel.HasValue && player.Level > buff.MaximumLevel.Value)
{
anyTooStrong = true;
continue;
}
var duration = TimeSpan.FromSeconds(effectDef.Duration?.ConstantValue?.Value ?? 0);
if (duration.TotalSeconds == 0)
{
continue;
}
var boosts = effectDef.PowerUpDefinitions
.Where(def => def.Boost is not null && def.TargetAttribute is not null)
.Select(def => new MagicEffect.ElementWithTarget(player.Attributes!.CreateElement(def), def.TargetAttribute!))
.ToArray();
if (boosts.Length == 0)
{
continue;
}
var effect = new MagicEffect(duration, effectDef, boosts);
await player.MagicEffectList.AddEffectAsync(effect).ConfigureAwait(false);
anyApplied = true;
}
if (anyApplied)
{
return;
}
if (anyValidEffect && anyTooLow)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotStrongEnoughMessage)).ConfigureAwait(false);
return;
}
if (anyValidEffect && anyTooStrong)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterTooStrongMessage)).ConfigureAwait(false);
}
}
}

View File

@@ -405,9 +405,9 @@ namespace MUnique.OpenMU.GameLogic.Properties {
/// <summary>
/// Looks up a localized string similar to You&apos;re strong enough on your own..
/// </summary>
public static string ElfSoldierStrongEnoughMessage {
public static string CharacterTooStrongMessage {
get {
return ResourceManager.GetString("ElfSoldierStrongEnoughMessage", resourceCulture);
return ResourceManager.GetString("CharacterTooStrongMessage", resourceCulture);
}
}
@@ -1814,5 +1814,14 @@ namespace MUnique.OpenMU.GameLogic.Properties {
return ResourceManager.GetString("StatsResetSuccessfully", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You are not strong enough for this buff yet..
/// </summary>
public static string CharacterNotStrongEnoughMessage {
get {
return ResourceManager.GetString("CharacterNotStrongEnoughMessage", resourceCulture);
}
}
}
}

View File

@@ -522,7 +522,7 @@
<data name="PlayerStoreNotOpen" xml:space="preserve">
<value>Player's Store not open.</value>
</data>
<data name="ElfSoldierStrongEnoughMessage" xml:space="preserve">
<data name="CharacterTooStrongMessage" xml:space="preserve">
<value>You're strong enough on your own.</value>
</data>
<data name="NotEnoughMoneyToProceed" xml:space="preserve">
@@ -702,4 +702,7 @@
<data name="ItemDoesNotBelongToYou" xml:space="preserve">
<value>This item doesn't belong to you.</value>
</data>
<data name="CharacterNotStrongEnoughMessage" xml:space="preserve">
<value>You are not strong enough for this yet.</value>
</data>
</root>

View File

@@ -11,7 +11,7 @@ using MUnique.OpenMU.Network.Packets.ClientToServer;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Packet handler for (elf soldier) buff request packets (0xF6, 0x31 identifier).
/// Packet handler for NPC buff request packets (0xF6, 0x31 identifier).
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.BuffRequestHandlerPlugIn_Name), Description = nameof(PlugInResources.BuffRequestHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
@@ -19,7 +19,7 @@ using MUnique.OpenMU.PlugIns;
[BelongsToGroup(QuestGroupHandlerPlugIn.GroupKey)]
public class BuffRequestHandlerPlugIn : ISubPacketHandlerPlugIn
{
private readonly ElfSoldierBuffRequestAction _buffRequestAction = new();
private readonly NpcBuffRequestAction _buffRequestAction = new();
/// <inheritdoc/>
public bool IsEncryptionExpected => false;

View File

@@ -55,7 +55,7 @@ public class ShowSkillAnimationPlugIn : IShowSkillAnimationPlugIn
}
var playerId = attacker.GetId(this._player);
var targetId = target.GetId(this._player);
var targetId = target is { } t ? (effectApplied ? (ushort)(t.GetId(this._player) | 0x8000) : t.GetId(this._player)) : (ushort)0;
var skillId = NumberConversionExtensions.ToUnsigned(skillNumber);
await this._player.Connection.SendSkillAnimationAsync(skillId, playerId, targetId).ConfigureAwait(false);
}

View File

@@ -0,0 +1,81 @@
// <copyright file="Buff.Generated.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
//------------------------------------------------------------------------------
// <auto-generated>
// This source code was auto-generated by a roslyn code generator.
// </auto-generated>
//------------------------------------------------------------------------------
// ReSharper disable All
namespace MUnique.OpenMU.Persistence.BasicModel;
using MUnique.OpenMU.Persistence.Json;
/// <summary>
/// A plain implementation of <see cref="Buff"/>.
/// </summary>
public partial class Buff : MUnique.OpenMU.DataModel.Configuration.Buff, IIdentifiable, IConvertibleTo<Buff>
{
/// <summary>
/// Gets or sets the identifier of this instance.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Gets the raw object of <see cref="MagicEffectDefinition" />.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("magicEffectDefinition")]
public MagicEffectDefinition RawMagicEffectDefinition
{
get => base.MagicEffectDefinition as MagicEffectDefinition;
set => base.MagicEffectDefinition = value;
}
/// <inheritdoc/>
[System.Text.Json.Serialization.JsonIgnore]
public override MUnique.OpenMU.DataModel.Configuration.MagicEffectDefinition MagicEffectDefinition
{
get => base.MagicEffectDefinition;
set => base.MagicEffectDefinition = value;
}
/// <inheritdoc />
public override MUnique.OpenMU.DataModel.Configuration.Buff Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
{
var clone = new Buff();
clone.AssignValuesOf(this, gameConfiguration);
return clone;
}
/// <inheritdoc />
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.Buff other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
{
base.AssignValuesOf(other, gameConfiguration);
this.Id = other.GetId();
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
var baseObject = obj as IIdentifiable;
if (baseObject != null)
{
return baseObject.Id == this.Id;
}
return base.Equals(obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
/// <inheritdoc/>
public Buff Convert() => this;
}

View File

@@ -109,6 +109,27 @@ public partial class MonsterDefinition : MUnique.OpenMU.DataModel.Configuration.
}
}
/// <summary>
/// Gets the raw collection of <see cref="Buffs" />.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("buffs")]
public ICollection<Buff> RawBuffs { get; } = new List<Buff>();
/// <inheritdoc/>
[System.Text.Json.Serialization.JsonIgnore]
public override ICollection<MUnique.OpenMU.DataModel.Configuration.Buff> Buffs
{
get => base.Buffs ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.Buff, Buff>(this.RawBuffs);
protected set
{
this.Buffs.Clear();
foreach (var item in value)
{
this.Buffs.Add(item);
}
}
}
/// <summary>
/// Gets the raw object of <see cref="AttackSkill" />.
/// </summary>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
{
/// <inheritdoc />
public partial class AddBuff : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Buff",
schema: "config",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
MagicEffectDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
MonsterDefinitionId = table.Column<Guid>(type: "uuid", nullable: true),
MinimumLevel = table.Column<int>(type: "integer", nullable: true),
MaximumLevel = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Buff", x => x.Id);
table.ForeignKey(
name: "FK_Buff_MagicEffectDefinition_MagicEffectDefinitionId",
column: x => x.MagicEffectDefinitionId,
principalSchema: "config",
principalTable: "MagicEffectDefinition",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Buff_MonsterDefinition_MonsterDefinitionId",
column: x => x.MonsterDefinitionId,
principalSchema: "config",
principalTable: "MonsterDefinition",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Buff_MagicEffectDefinitionId",
schema: "config",
table: "Buff",
column: "MagicEffectDefinitionId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Buff_MonsterDefinitionId",
schema: "config",
table: "Buff",
column: "MonsterDefinitionId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Buff",
schema: "config");
}
}
}

View File

@@ -347,6 +347,34 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
b.ToTable("BattleZoneDefinition", "config");
});
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("MagicEffectDefinitionId")
.HasColumnType("uuid");
b.Property<int?>("MaximumLevel")
.HasColumnType("integer");
b.Property<int?>("MinimumLevel")
.HasColumnType("integer");
b.Property<Guid?>("MonsterDefinitionId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("MagicEffectDefinitionId")
.IsUnique();
b.HasIndex("MonsterDefinitionId");
b.ToTable("Buff", "config");
});
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
{
b.Property<Guid>("Id")
@@ -3592,6 +3620,21 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
b.Navigation("RawRightGoal");
});
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", b =>
{
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MagicEffectDefinition", "RawMagicEffectDefinition")
.WithOne()
.HasForeignKey("MUnique.OpenMU.Persistence.EntityFramework.Model.Buff", "MagicEffectDefinitionId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.MonsterDefinition", null)
.WithMany("RawBuffs")
.HasForeignKey("MonsterDefinitionId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("RawMagicEffectDefinition");
});
modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
{
b.HasOne("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", null)
@@ -5193,6 +5236,8 @@ namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
b.Navigation("RawAttributes");
b.Navigation("RawBuffs");
b.Navigation("RawItemCraftings");
b.Navigation("RawQuests");

View File

@@ -0,0 +1,91 @@
// <copyright file="Buff.Generated.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
//------------------------------------------------------------------------------
// <auto-generated>
// This source code was auto-generated by a roslyn code generator.
// </auto-generated>
//------------------------------------------------------------------------------
// ReSharper disable All
namespace MUnique.OpenMU.Persistence.EntityFramework.Model;
using System.ComponentModel.DataAnnotations.Schema;
using MUnique.OpenMU.Persistence;
/// <summary>
/// The Entity Framework Core implementation of <see cref="MUnique.OpenMU.DataModel.Configuration.Buff"/>.
/// </summary>
[Table(nameof(Buff), Schema = SchemaNames.Configuration)]
internal partial class Buff : MUnique.OpenMU.DataModel.Configuration.Buff, IIdentifiable
{
/// <summary>
/// Gets or sets the identifier of this instance.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the identifier of <see cref="MagicEffectDefinition"/>.
/// </summary>
public Guid? MagicEffectDefinitionId { get; set; }
/// <summary>
/// Gets the raw object of <see cref="MagicEffectDefinition" />.
/// </summary>
[ForeignKey(nameof(MagicEffectDefinitionId))]
public MagicEffectDefinition RawMagicEffectDefinition
{
get => base.MagicEffectDefinition as MagicEffectDefinition;
set => base.MagicEffectDefinition = value;
}
/// <inheritdoc/>
[NotMapped]
public override MUnique.OpenMU.DataModel.Configuration.MagicEffectDefinition MagicEffectDefinition
{
get => base.MagicEffectDefinition;set
{
base.MagicEffectDefinition = value;
this.MagicEffectDefinitionId = this.RawMagicEffectDefinition?.Id;
}
}
/// <inheritdoc />
public override MUnique.OpenMU.DataModel.Configuration.Buff Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
{
var clone = new Buff();
clone.AssignValuesOf(this, gameConfiguration);
return clone;
}
/// <inheritdoc />
public override void AssignValuesOf(MUnique.OpenMU.DataModel.Configuration.Buff other, MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration)
{
base.AssignValuesOf(other, gameConfiguration);
this.Id = other.GetId();
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
var baseObject = obj as IIdentifiable;
if (baseObject != null)
{
return baseObject.Id == this.Id;
}
return base.Equals(obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
}

View File

@@ -40,6 +40,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Entities.SkillEntry>();
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.AreaSkillSettings>();
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.BattleZoneDefinition>();
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.Buff>();
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.CharacterClass>();
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.ChatServerDefinition>();
modelBuilder.Ignore<MUnique.OpenMU.DataModel.Configuration.ChatServerEndpoint>();
@@ -129,6 +130,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawGround).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawLeftGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<BattleZoneDefinition>().HasOne(entity => entity.RawRightGoal).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Buff>().HasOne(entity => entity.RawMagicEffectDefinition).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawStatAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawAttributeCombinations).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CharacterClass>().HasMany(entity => entity.RawBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade);
@@ -179,6 +181,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext
modelBuilder.Entity<MonsterDefinition>().HasMany(entity => entity.RawItemCraftings).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MonsterDefinition>().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MonsterDefinition>().HasMany(entity => entity.RawQuests).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MonsterDefinition>().HasMany(entity => entity.RawBuffs).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Skill>().HasMany(entity => entity.RawRequirements).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Skill>().HasMany(entity => entity.RawConsumeRequirements).WithOne().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Skill>().HasMany(entity => entity.RawAttributeRelationships).WithOne().OnDelete(DeleteBehavior.Cascade);

View File

@@ -83,6 +83,9 @@ public static class MapsterConfigurator
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.BattleZoneDefinition, MUnique.OpenMU.DataModel.Configuration.BattleZoneDefinition>()
.Include<BattleZoneDefinition, BasicModel.BattleZoneDefinition>();
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.Buff, MUnique.OpenMU.DataModel.Configuration.Buff>()
.Include<Buff, BasicModel.Buff>();
Mapster.TypeAdapterConfig.GlobalSettings.NewConfig<MUnique.OpenMU.DataModel.Configuration.CharacterClass, MUnique.OpenMU.DataModel.Configuration.CharacterClass>()
.Include<CharacterClass, BasicModel.CharacterClass>();

View File

@@ -60,6 +60,15 @@ internal partial class MonsterDefinition : MUnique.OpenMU.DataModel.Configuratio
[NotMapped]
public override ICollection<MUnique.OpenMU.DataModel.Configuration.Quests.QuestDefinition> Quests => base.Quests ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.Quests.QuestDefinition, QuestDefinition>(this.RawQuests);
/// <summary>
/// Gets the raw collection of <see cref="Buffs" />.
/// </summary>
public ICollection<Buff> RawBuffs { get; } = new EntityFramework.List<Buff>();
/// <inheritdoc/>
[NotMapped]
public override ICollection<MUnique.OpenMU.DataModel.Configuration.Buff> Buffs => base.Buffs ??= new CollectionAdapter<MUnique.OpenMU.DataModel.Configuration.Buff, Buff>(this.RawBuffs);
/// <summary>
/// Gets or sets the identifier of <see cref="AttackSkill"/>.
/// </summary>

View File

@@ -0,0 +1,106 @@
// <copyright file="AddElfSoldierBuffPlugIn.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 Elf Soldier buff to the existing Elf Soldier NPC.
/// </summary>
[PlugIn]
[Display(Name = PlugInName, Description = PlugInDescription)]
[Guid("9BCFC8B1-6A6E-48F9-AE7C-0D34FA6D706B")]
public class AddElfSoldierBuffPlugIn : UpdatePlugInBase
{
/// <summary>
/// The plug in name.
/// </summary>
internal const string PlugInName = "Add Elf Soldier Buff";
/// <summary>
/// The plug in description.
/// </summary>
internal const string PlugInDescription = "This update adds the Elf Soldier buff (defense and damage boost) as a configurable Buff.";
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddElfSoldierBuff;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 07, 11, 17, 10, 0, DateTimeKind.Utc);
/// <inheritdoc />
protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
{
var elfSoldier = gameConfiguration.Monsters.FirstOrDefault(m => m.Number == 257);
if (elfSoldier is null)
{
return;
}
if (elfSoldier.Buffs.Any())
{
return;
}
var buffEffect = context.CreateNew<MagicEffectDefinition>();
gameConfiguration.MagicEffects.Add(buffEffect);
buffEffect.Number = (short)MagicEffectNumber.ElfSoldierBuff;
buffEffect.Name = "Elf Soldier Buff";
buffEffect.InformObservers = true;
buffEffect.StopByDeath = true;
// Duration: 60 minutes
buffEffect.Duration = context.CreateNew<PowerUpDefinitionValue>();
buffEffect.Duration.ConstantValue.Value = 3600;
// Defense boost: 50 + (Level / 5)
var defensePowerUp = context.CreateNew<PowerUpDefinition>();
defensePowerUp.TargetAttribute = Stats.DefenseFinal.GetPersistent(gameConfiguration);
defensePowerUp.Boost = context.CreateNew<PowerUpDefinitionValue>();
defensePowerUp.Boost.ConstantValue.Value = 50;
defensePowerUp.Boost.ConstantValue.AggregateType = AggregateType.AddFinal;
var defensePerLevel = context.CreateNew<AttributeRelationship>();
defensePerLevel.InputAttribute = Stats.Level.GetPersistent(gameConfiguration);
defensePerLevel.InputOperand = 1f / 5;
defensePerLevel.InputOperator = InputOperator.Multiply;
defensePowerUp.Boost.RelatedValues.Add(defensePerLevel);
buffEffect.PowerUpDefinitions.Add(defensePowerUp);
// Damage boost: 45 + (Level / 3)
var damagePowerUp = context.CreateNew<PowerUpDefinition>();
damagePowerUp.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(gameConfiguration);
damagePowerUp.Boost = context.CreateNew<PowerUpDefinitionValue>();
damagePowerUp.Boost.ConstantValue.Value = 45;
var damagePerLevel = context.CreateNew<AttributeRelationship>();
damagePerLevel.InputAttribute = Stats.Level.GetPersistent(gameConfiguration);
damagePerLevel.InputOperand = 1f / 3;
damagePerLevel.InputOperator = InputOperator.Multiply;
damagePowerUp.Boost.RelatedValues.Add(damagePerLevel);
buffEffect.PowerUpDefinitions.Add(damagePowerUp);
var buff = context.CreateNew<Buff>();
buff.MagicEffectDefinition = buffEffect;
buff.MaximumLevel = 220;
elfSoldier.Buffs.Add(buff);
}
}

View File

@@ -509,4 +509,9 @@ public enum UpdateVersion
/// The version of the <see cref="AddHeykelSavasiEventUpdateSeason6"/> (Heykel Savasi team event: map 92, NPC 560, mini game definition).
/// </summary>
AddHeykelSavasiEventSeason6 = 100,
}
/// <summary>
/// The version of the <see cref="AddElfSoldierBuffPlugIn"/>.
/// </summary>
AddElfSoldierBuff = 101,
}

View File

@@ -5,6 +5,7 @@
namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.MiniGames;
@@ -119,6 +120,51 @@ internal partial class NpcInitialization : Version095d.NpcInitialization
def.SetGuid(def.Number);
}
// Elf Soldier Buff
{
var buffEffect = this.Context.CreateNew<MagicEffectDefinition>();
this.GameConfiguration.MagicEffects.Add(buffEffect);
buffEffect.Number = (short)MagicEffectNumber.ElfSoldierBuff;
buffEffect.Name = "Elf Soldier Buff";
buffEffect.InformObservers = true;
buffEffect.StopByDeath = true;
// Duration: 60 minutes
buffEffect.Duration = this.Context.CreateNew<PowerUpDefinitionValue>();
buffEffect.Duration.ConstantValue.Value = 3600;
// Defense boost: 50 + (Level / 5)
var defensePowerUp = this.Context.CreateNew<PowerUpDefinition>();
defensePowerUp.TargetAttribute = Stats.DefenseFinal.GetPersistent(this.GameConfiguration);
defensePowerUp.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
defensePowerUp.Boost.ConstantValue.Value = 50;
defensePowerUp.Boost.ConstantValue.AggregateType = AggregateType.AddFinal;
var defensePerLevel = this.Context.CreateNew<AttributeRelationship>();
defensePerLevel.InputAttribute = Stats.Level.GetPersistent(this.GameConfiguration);
defensePerLevel.InputOperand = 1f / 5;
defensePerLevel.InputOperator = InputOperator.Multiply;
defensePowerUp.Boost.RelatedValues.Add(defensePerLevel);
buffEffect.PowerUpDefinitions.Add(defensePowerUp);
// Damage boost: 45 + (Level / 3)
var damagePowerUp = this.Context.CreateNew<PowerUpDefinition>();
damagePowerUp.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(this.GameConfiguration);
damagePowerUp.Boost = this.Context.CreateNew<PowerUpDefinitionValue>();
damagePowerUp.Boost.ConstantValue.Value = 45;
var damagePerLevel = this.Context.CreateNew<AttributeRelationship>();
damagePerLevel.InputAttribute = Stats.Level.GetPersistent(this.GameConfiguration);
damagePerLevel.InputOperand = 1f / 3;
damagePerLevel.InputOperator = InputOperator.Multiply;
damagePowerUp.Boost.RelatedValues.Add(damagePerLevel);
buffEffect.PowerUpDefinitions.Add(damagePowerUp);
var elfSoldier = this.GameConfiguration.Monsters.First(m => m.Number == 257);
var buff = this.Context.CreateNew<Buff>();
buff.MagicEffectDefinition = buffEffect;
buff.MaximumLevel = 220;
elfSoldier.Buffs.Add(buff);
}
{
var def = this.Context.CreateNew<MonsterDefinition>();
def.Number = 259;