baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
102
src/GameLogic/Attributes/AttributeSystemExtensions.cs
Normal file
102
src/GameLogic/Attributes/AttributeSystemExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
129
src/GameLogic/Attributes/ItemAwareAttributeSystem.cs
Normal file
129
src/GameLogic/Attributes/ItemAwareAttributeSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
208
src/GameLogic/Attributes/MonsterAttributeHolder.cs
Normal file
208
src/GameLogic/Attributes/MonsterAttributeHolder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/GameLogic/Attributes/PowerUpWrapper.cs
Normal file
105
src/GameLogic/Attributes/PowerUpWrapper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
1608
src/GameLogic/Attributes/Stats.cs
Normal file
1608
src/GameLogic/Attributes/Stats.cs
Normal file
File diff suppressed because it is too large
Load Diff
102
src/GameLogic/Attributes/TrapAttributeHolder.cs
Normal file
102
src/GameLogic/Attributes/TrapAttributeHolder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user