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,102 @@
// <copyright file="AttributeSystemExtensions.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.Persistence;
/// <summary>
/// Extensions for <see cref="IAttributeSystem"/>s.
/// </summary>
public static class AttributeSystemExtensions
{
/// <summary>
/// Gets the attribute for a dummy attribute to be used internally for durations.
/// </summary>
private static AttributeDefinition DurationDummy { get; } = new(new Guid("23D069C3-24D8-4277-8FDC-D82F0AF64037"), "Duration Dummy", "A dummy attribute to be used internally for durations.");
/// <summary>
/// Gets the attribute for a dummy attribute to be used internally for chances.
/// </summary>
private static AttributeDefinition ChanceDummy { get; } = new(new Guid("E6B9E6A5-5800-40EA-80B7-14C2C06392A6"), "Chance Dummy", "A dummy attribute to be used internally for chances.");
/// <summary>
/// Creates a new element on this attribute system with the specified power up value.
/// </summary>
/// <param name="attributeSystem">The attribute system.</param>
/// <param name="powerUpDefinition">The power up definition.</param>
/// <returns>The added element.</returns>
public static IElement CreateDurationElement(this IAttributeSystem attributeSystem, PowerUpDefinitionValue powerUpDefinition)
{
return attributeSystem.CreateElement(powerUpDefinition, DurationDummy);
}
/// <summary>
/// Creates a new element on this attribute system with the specified power up value.
/// </summary>
/// <param name="attributeSystem">The attribute system.</param>
/// <param name="powerUpDefinition">The power up definition.</param>
/// <returns>The added element.</returns>
public static IElement CreateChanceElement(this IAttributeSystem attributeSystem, PowerUpDefinitionValue powerUpDefinition)
{
return attributeSystem.CreateElement(powerUpDefinition, ChanceDummy);
}
/// <summary>
/// Creates a new element on this attribute system with the specified power up value.
/// </summary>
/// <param name="attributeSystem">The attribute system.</param>
/// <param name="powerUpDefinition">The power up definition.</param>
/// <returns>The added element.</returns>
public static IElement CreateElement(this IAttributeSystem attributeSystem, PowerUpDefinition powerUpDefinition)
{
var value = powerUpDefinition.Boost ?? throw Error.NotInitializedProperty(powerUpDefinition, nameof(powerUpDefinition.Boost));
var targetDefinition = powerUpDefinition.TargetAttribute ?? throw Error.NotInitializedProperty(powerUpDefinition, nameof(powerUpDefinition.TargetAttribute));
return attributeSystem.CreateElement(value, targetDefinition);
}
/// <summary>
/// Creates a new element on this attribute system with the specified power up value.
/// </summary>
/// <param name="attributeSystem">The attribute system.</param>
/// <param name="value">The value.</param>
/// <param name="targetDefinition">The attribute.</param>
/// <returns>The added element.</returns>
public static IElement CreateElement(this IAttributeSystem attributeSystem, PowerUpDefinitionValue value, AttributeDefinition targetDefinition)
{
var relations = value.RelatedValues;
var result = value.ConstantValue;
if (relations?.Any() ?? false)
{
var elements = relations
.Select(r => new AttributeRelationshipElement(
new[] { attributeSystem.GetOrCreateAttribute(r.InputAttribute ?? throw new InvalidOperationException($"InputAttribute value not set for AttributeRelationship {r.GetId()}.")) },
r.GetOperandElement(attributeSystem),
r.InputOperator)
{
AggregateType = result.AggregateType,
})
.Cast<IElement>();
if (value.ConstantValue is not null)
{
elements = elements.Concat(value.ConstantValue.GetAsEnumerable());
}
var composableResult = new ComposableAttribute(targetDefinition, result.AggregateType, value.MaximumValue);
elements.ForEach(element => composableResult.AddElement(element));
return composableResult;
}
if (result is null)
{
throw new ArgumentException($"The passed {nameof(PowerUpDefinitionValue)} doesn't have a constant value or related values.", nameof(value));
}
return result;
}
}

View File

@@ -0,0 +1,129 @@
// <copyright file="ItemAwareAttributeSystem.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Configuration;
/// <summary>
/// An attribute system which considers items of a character.
/// </summary>
public sealed class ItemAwareAttributeSystem : AttributeSystem, IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemAwareAttributeSystem" /> class.
/// </summary>
/// <param name="account">The account.</param>
/// <param name="character">The character.</param>
/// <param name="gameConfiguration">The game configuration with global attributes.</param>
public ItemAwareAttributeSystem(Account account, Character character, GameConfiguration gameConfiguration)
: base(
character.Attributes.Concat(account.Attributes),
character.CharacterClass!.BaseAttributeValues.Concat(gameConfiguration.GlobalBaseAttributeValues),
character.CharacterClass.AttributeCombinations.Concat(gameConfiguration.GlobalAttributeCombinations))
{
this.ItemPowerUps = new Dictionary<Item, IReadOnlyList<PowerUpWrapper>>();
foreach (var attribute in this)
{
this.OnAttributeAdded(attribute);
}
}
/// <summary>
/// Occurs when an attribute value changed.
/// </summary>
public event EventHandler<IAttribute>? AttributeValueChanged;
/// <summary>
/// Gets the item power ups.
/// </summary>
public IDictionary<Item, IReadOnlyList<PowerUpWrapper>> ItemPowerUps { get; }
/// <summary>
/// Gets or sets the item set power ups.
/// </summary>
public IReadOnlyList<PowerUpWrapper>? ItemSetPowerUps { get; set; }
/// <inheritdoc />
public void Dispose()
{
this.AttributeValueChanged = null;
foreach (var attribute in this)
{
attribute.ValueChanged -= this.OnAttributeValueChanged;
}
foreach (var powerUpWrapper in this.ItemPowerUps.SelectMany(p => p.Value))
{
powerUpWrapper.Dispose();
}
this.ItemPowerUps.Clear();
if (this.ItemSetPowerUps is { } itemSetPowerUps)
{
foreach (var powerUp in itemSetPowerUps)
{
powerUp.Dispose();
}
this.ItemSetPowerUps = null;
}
}
/// <inheritdoc />
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(base.ToString());
stringBuilder.AppendLine("Item Power Ups:");
foreach (var (item, powerUps) in this.ItemPowerUps)
{
stringBuilder.Append(" ").AppendLine(item.ToString());
foreach (var powerUp in powerUps)
{
stringBuilder.Append(" ").AppendLine(powerUp.ToString());
}
}
if (this.ItemSetPowerUps != null)
{
stringBuilder.AppendLine("Item Set Power Ups:");
foreach (var attribute in this.ItemSetPowerUps)
{
stringBuilder.Append(" ").AppendLine(attribute.ToString());
}
}
return stringBuilder.ToString();
}
/// <inheritdoc />
protected override void OnAttributeAdded(IAttribute attribute)
{
base.OnAttributeAdded(attribute);
attribute.ValueChanged += this.OnAttributeValueChanged;
}
/// <inheritdoc />
protected override void OnAttributeRemoved(IAttribute attribute)
{
attribute.ValueChanged -= this.OnAttributeValueChanged;
base.OnAttributeRemoved(attribute);
}
/// <summary>
/// Called when an attribute value changed. Forwards the event to <see cref="AttributeValueChanged"/>.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnAttributeValueChanged(object? sender, EventArgs e)
{
if (sender is IAttribute attribute)
{
this.AttributeValueChanged?.Invoke(this, attribute);
}
}
}

View File

@@ -0,0 +1,208 @@
// <copyright file="MonsterAttributeHolder.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.Attributes;
using System.Collections.Concurrent;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.NPC;
/// <summary>
/// The attribute system for monsters, which is considering monster definitions.
/// </summary>
public class MonsterAttributeHolder : IAttributeSystem
{
private static readonly IDictionary<AttributeDefinition, Func<AttackableNpcBase, float>> StatMapping =
new Dictionary<AttributeDefinition, Func<AttackableNpcBase, float>>
{
{ Stats.CurrentHealth, m => m.Health },
{ Stats.DefensePvm, m => m.Attributes.GetValueOfAttribute(Stats.DefenseBase) + ((m as Monster)?.SummonedBy?.Attributes?[Stats.SummonedMonsterDefenseIncrease] ?? 0) },
{ Stats.DefensePvp, m => m.Attributes.GetValueOfAttribute(Stats.DefenseBase) + ((m as Monster)?.SummonedBy?.Attributes?[Stats.SummonedMonsterDefenseIncrease] ?? 0) },
{ Stats.DamageReceiveDecrement, m => 1.0f },
{ Stats.AttackDamageIncrease, m => 1.0f },
{ Stats.MovementSpeedFactor, m => 1.0f },
{ Stats.ShieldBypassChance, m => 1.0f },
{ Stats.DefenseDecrement, m => 1.0f - m.Attributes.GetValueOfAttribute(Stats.InnovationDefDecrement) },
};
private static readonly IDictionary<AttributeDefinition, Action<AttackableNpcBase, float>> SetterMapping =
new Dictionary<AttributeDefinition, Action<AttackableNpcBase, float>>
{
{ Stats.CurrentHealth, (m, v) => m.Health = (int)v },
};
private static readonly ConcurrentDictionary<MonsterDefinition, IDictionary<AttributeDefinition, float>> MonsterStatAttributesCache = new();
private readonly AttackableNpcBase _monster;
private readonly object _attributesLock = new();
private IDictionary<AttributeDefinition, float> _statAttributes;
/// <summary>
/// Attribute dictionary of a monster instance.
/// Most monster instances don't have additional attributes, so we just instantiate one if needed.
/// </summary>
private IDictionary<AttributeDefinition, IComposableAttribute>? _attributes;
/// <summary>
/// Initializes a new instance of the <see cref="MonsterAttributeHolder"/> class.
/// </summary>
/// <param name="monster">The monster.</param>
public MonsterAttributeHolder(AttackableNpcBase monster)
{
this._monster = monster;
this._statAttributes = GetStatAttributeOfMonster(monster.Definition);
}
/// <inheritdoc/>
public float this[AttributeDefinition attributeDefinition]
{
get => this.GetValueOfAttribute(attributeDefinition);
set
{
if (SetterMapping.TryGetValue(attributeDefinition, out var setAction))
{
setAction(this._monster, value);
}
}
}
/// <inheritdoc/>
public float GetValueOfAttribute(AttributeDefinition attributeDefinition)
{
IDictionary<AttributeDefinition, IComposableAttribute>? attributes;
lock (this._attributesLock)
{
attributes = this._attributes;
}
if (attributes is not null
&& attributes.TryGetValue(attributeDefinition, out var attribute))
{
return attribute.Value;
}
if (this._statAttributes.TryGetValue(attributeDefinition, out float value))
{
return value;
}
if (StatMapping.TryGetValue(attributeDefinition, out var mappingFunction))
{
return mappingFunction(this._monster);
}
return 0;
}
/// <inheritdoc/>
public void AddElement(IElement element, AttributeDefinition targetAttribute)
{
var attributeDictionary = this.GetAttributeDictionary();
if (!attributeDictionary.TryGetValue(targetAttribute, out var attribute))
{
attribute = new ComposableAttribute(targetAttribute);
var attrValue = this.GetValueOfAttribute(targetAttribute);
var nullValue = element.AggregateType == AggregateType.Multiplicate ? 1 : 0;
attrValue = Math.Abs(attrValue) < 0.01f ? nullValue : attrValue;
attribute.AddElement(new SimpleElement { Value = attrValue });
attributeDictionary.Add(targetAttribute, attribute);
}
attribute.AddElement(element);
}
/// <inheritdoc/>
public void RemoveElement(IElement element, AttributeDefinition targetAttribute)
{
IDictionary<AttributeDefinition, IComposableAttribute>? attributes;
lock (this._attributesLock)
{
attributes = this._attributes;
}
if (attributes is null)
{
return;
}
if (attributes.TryGetValue(targetAttribute, out var attribute))
{
attribute.RemoveElement(element);
if (!attribute.Elements.Skip(1).Any())
{
attributes.Remove(targetAttribute);
}
}
if (attributes.Count == 0)
{
lock (this._attributesLock)
{
this._attributes = null;
}
}
}
/// <inheritdoc/>
public void AddAttributeRelationship(AttributeRelationship combination, IAttributeSystem sourceAttributeHolder, AggregateType aggregateType)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public IElement GetOrCreateAttribute(AttributeDefinition attributeDefinition)
{
throw new NotImplementedException();
}
/// <summary>
/// Reloads the stat attributes from the (possibly changed) <see cref="MonsterDefinition"/>,
/// so that configuration changes take effect on the running game server.
/// </summary>
public void ApplyChanges()
{
// When many instances of the same monster are spawned, all of them get notified about the
// same change. The first one rebuilds the shared cache; the others just adopt the new one.
if (MonsterStatAttributesCache.TryGetValue(this._monster.Definition, out var cached)
&& !ReferenceEquals(cached, this._statAttributes))
{
this._statAttributes = cached;
return;
}
var statAttributes = BuildStatAttributes(this._monster.Definition);
MonsterStatAttributesCache[this._monster.Definition] = statAttributes;
this._statAttributes = statAttributes;
}
private static IDictionary<AttributeDefinition, float> GetStatAttributeOfMonster(MonsterDefinition monsterDefinition)
{
return MonsterStatAttributesCache.GetOrAdd(monsterDefinition, BuildStatAttributes);
}
private static IDictionary<AttributeDefinition, float> BuildStatAttributes(MonsterDefinition monsterDefinition)
{
return monsterDefinition.Attributes.ToDictionary(
m => m.AttributeDefinition ?? throw Error.NotInitializedProperty(m, nameof(m.AttributeDefinition)),
m => m.Value);
}
private IDictionary<AttributeDefinition, IComposableAttribute> GetAttributeDictionary()
{
lock (this._attributesLock)
{
var attributes = this._attributes;
if (attributes is null)
{
attributes = new Dictionary<AttributeDefinition, IComposableAttribute>();
this._attributes = attributes;
}
return attributes;
}
}
}

View File

@@ -0,0 +1,105 @@
// <copyright file="PowerUpWrapper.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
/// <summary>
/// A wrapper class which adapts power ups to <see cref="IElement"/> instances.
/// </summary>
public sealed class PowerUpWrapper : IElement, IDisposable
{
private readonly IElement _element;
private readonly AggregateType? _aggregateType;
private ComposableAttribute? _parentAttribute;
/// <summary>
/// Initializes a new instance of the <see cref="PowerUpWrapper"/> class.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="targetAttribute">The target attribute.</param>
/// <param name="attributeHolder">The attribute holder.</param>
/// <param name="aggregateType">The aggregate type that should override the <paramref name="element"/>'s.</param>
public PowerUpWrapper(IElement element, AttributeDefinition targetAttribute, AttributeSystem attributeHolder, AggregateType? aggregateType = null)
{
this._parentAttribute = attributeHolder.GetComposableAttribute(targetAttribute);
if (this._parentAttribute is null)
{
throw new ArgumentException($"Target attribute [{targetAttribute}] is not composable", nameof(targetAttribute));
}
this._element = element;
this._aggregateType = aggregateType;
this._parentAttribute.AddElement(this);
this._element.ValueChanged += this.OnValueChanged;
}
/// <inheritdoc/>
public event EventHandler? ValueChanged;
/// <inheritdoc/>
public float Value => this._element.Value;
/// <inheritdoc/>
public AggregateType AggregateType => this._aggregateType ?? this._element.AggregateType;
/// <summary>
/// Creates elements by a <see cref="PowerUpDefinition"/>.
/// </summary>
/// <param name="powerUpDef">The power up definition.</param>
/// <param name="attributeHolder">The attribute holder.</param>
/// <param name="aggregateType">The specific aggregate type. If not specified, the aggregate type of the <paramref name="powerUpDef"/>'s constant value will be used.</param>
/// <returns>The elements which represent the power-up.</returns>
public static IEnumerable<PowerUpWrapper> CreateByPowerUpDefinition(PowerUpDefinition powerUpDef, AttributeSystem attributeHolder, AggregateType? aggregateType = null)
{
if (powerUpDef.Boost?.ConstantValue != null)
{
yield return new PowerUpWrapper(
powerUpDef.Boost.ConstantValue,
powerUpDef.TargetAttribute ?? throw Error.NotInitializedProperty(powerUpDef, nameof(PowerUpDefinition.TargetAttribute)),
attributeHolder,
aggregateType);
}
if (powerUpDef.Boost?.RelatedValues != null)
{
foreach (var relationship in powerUpDef.Boost.RelatedValues)
{
var aggregType = powerUpDef.Boost?.ConstantValue.AggregateType ?? AggregateType.AddRaw;
yield return new PowerUpWrapper(
attributeHolder.CreateRelatedAttribute(relationship, attributeHolder, aggregType),
powerUpDef.TargetAttribute ?? throw Error.NotInitializedProperty(powerUpDef, nameof(PowerUpDefinition.TargetAttribute)),
attributeHolder,
aggregateType);
}
}
}
/// <inheritdoc/>
public void Dispose()
{
if (this._parentAttribute != null)
{
this._parentAttribute.RemoveElement(this);
this.ValueChanged = null;
this._parentAttribute = null;
this._element.ValueChanged -= this.OnValueChanged;
}
}
/// <inheritdoc/>
public override string? ToString()
{
return $"{this._parentAttribute?.Definition.Designation}: {this._element}{(this._aggregateType is { } aggreg ? $"->({aggreg})" : string.Empty)}";
}
private void OnValueChanged(object? sender, EventArgs eventArgs)
{
this.ValueChanged?.Invoke(sender, eventArgs);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
// <copyright file="TrapAttributeHolder.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.DataModel.Attributes;
using MUnique.OpenMU.GameLogic.NPC;
/// <summary>
/// The attribute system for traps, which is considering monster definitions.
/// </summary>
public class TrapAttributeHolder : IAttributeSystem
{
private static readonly IDictionary<AttributeDefinition, Func<Trap, float>> StatMapping =
new Dictionary<AttributeDefinition, Func<Trap, float>>
{
{ Stats.AttackDamageIncrease, _ => 1.0f },
{ Stats.ShieldBypassChance, _ => 1.0f },
};
private static readonly IDictionary<MonsterDefinition, IDictionary<AttributeDefinition, float>> MonsterStatAttributesCache = new Dictionary<MonsterDefinition, IDictionary<AttributeDefinition, float>>();
private readonly Trap _trap;
private readonly IDictionary<AttributeDefinition, float> _statAttributes;
/// <summary>
/// Initializes a new instance of the <see cref="TrapAttributeHolder"/> class.
/// </summary>
/// <param name="trap">The trap.</param>
public TrapAttributeHolder(Trap trap)
{
this._trap = trap;
this._statAttributes = GetStatAttributeOfMonster(trap.Definition);
}
/// <inheritdoc/>
public float this[AttributeDefinition attributeDefinition]
{
get => this.GetValueOfAttribute(attributeDefinition);
set
{
// can't set anything for Traps.
}
}
/// <inheritdoc/>
public float GetValueOfAttribute(AttributeDefinition attributeDefinition)
{
if (this._statAttributes.TryGetValue(attributeDefinition, out float value))
{
return value;
}
if (StatMapping.TryGetValue(attributeDefinition, out var mappingFunction))
{
return mappingFunction(this._trap);
}
return 0;
}
/// <inheritdoc/>
public void AddElement(IElement element, AttributeDefinition targetAttribute)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
public void RemoveElement(IElement element, AttributeDefinition targetAttribute)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
public void AddAttributeRelationship(AttributeRelationship combination, IAttributeSystem sourceAttributeHolder, AggregateType aggregateType)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public IElement GetOrCreateAttribute(AttributeDefinition attributeDefinition)
{
throw new NotImplementedException();
}
private static IDictionary<AttributeDefinition, float> GetStatAttributeOfMonster(MonsterDefinition monsterDef)
{
if (!MonsterStatAttributesCache.TryGetValue(monsterDef, out var result))
{
result = monsterDef.Attributes.ToDictionary(
m => m.AttributeDefinition ?? throw Error.NotInitializedProperty(m, nameof(PowerUpDefinition.TargetAttribute)),
m => m.Value);
MonsterStatAttributesCache.Add(monsterDef, result);
}
return result;
}
}