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,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;