Merge pull request #825 from eduardosmaniotto/feature/configurable-npc-buffs
feature: configurable npc buffs (cherry picked from commit 6a061fcad756e644dadf3721e84e3cce2a25bceb)
This commit is contained in:
30
src/DataModel/Configuration/Buff.cs
Normal file
30
src/DataModel/Configuration/Buff.cs
Normal 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; }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/GameLogic/PlayerActions/Quests/NpcBuffRequestAction.cs
Normal file
90
src/GameLogic/PlayerActions/Quests/NpcBuffRequestAction.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/GameLogic/Properties/PlayerMessage.Designer.cs
generated
13
src/GameLogic/Properties/PlayerMessage.Designer.cs
generated
@@ -405,9 +405,9 @@ namespace MUnique.OpenMU.GameLogic.Properties {
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You'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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
81
src/Persistence/BasicModel/Buff.Generated.cs
Normal file
81
src/Persistence/BasicModel/Buff.Generated.cs
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
5288
src/Persistence/EntityFramework/Migrations/20260712014203_AddBuff.Designer.cs
generated
Normal file
5288
src/Persistence/EntityFramework/Migrations/20260712014203_AddBuff.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
91
src/Persistence/EntityFramework/Model/Buff.Generated.cs
Normal file
91
src/Persistence/EntityFramework/Model/Buff.Generated.cs
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -98,6 +98,44 @@ public class PartyTest
|
||||
Assert.That(party.PartyList, Does.Not.Contain(partyMaster));
|
||||
Assert.That(partyMember.Party, Is.SameAs(party));
|
||||
Assert.That(party.PartyList, Has.Count.EqualTo(2));
|
||||
|
||||
// A new party master should have been assigned.
|
||||
Assert.That(party.PartyMaster, Is.Not.Null);
|
||||
Assert.That(party.PartyMaster, Is.SameAs(partyMember));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the party master role stays the same when a non-master is kicked.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask PartyMemberKickByMasterMasterRemainsMasterAsync()
|
||||
{
|
||||
var party = await this.CreatePartyWithMembersAsync(3).ConfigureAwait(false);
|
||||
var partyMaster = party.PartyList[0];
|
||||
var partyMember = party.PartyList[1];
|
||||
|
||||
await this._kickAction.KickPlayerAsync((Player)partyMaster, GetPartyMemberIndex(party, partyMember)).ConfigureAwait(false);
|
||||
Assert.That(party.PartyMaster, Is.SameAs(partyMaster));
|
||||
Assert.That(party.PartyList, Has.Count.EqualTo(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the first remaining member becomes the new party master when the master leaves.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask PartyMasterLeavesAndFirstMemberBecomesNewMasterAsync()
|
||||
{
|
||||
var party = await this.CreatePartyWithMembersAsync(4).ConfigureAwait(false);
|
||||
var partyMaster = party.PartyList[0];
|
||||
var firstRemainingMember = party.PartyList[1];
|
||||
|
||||
await this._kickAction.KickPlayerAsync((Player)partyMaster, GetPartyMemberIndex(party, partyMaster)).ConfigureAwait(false);
|
||||
|
||||
Assert.That(partyMaster.Party, Is.Null);
|
||||
Assert.That(party.PartyList, Does.Not.Contain(partyMaster));
|
||||
Assert.That(party.PartyList, Has.Count.EqualTo(3));
|
||||
Assert.That(party.PartyList[0], Is.SameAs(firstRemainingMember));
|
||||
Assert.That(party.PartyMaster, Is.SameAs(firstRemainingMember));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user