baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
31
src/AttributeSystem/AggregateType.cs
Normal file
31
src/AttributeSystem/AggregateType.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// <copyright file="AggregateType.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The attribute aggregate type.
|
||||
/// </summary>
|
||||
public enum AggregateType
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the value to the raw base value.
|
||||
/// </summary>
|
||||
AddRaw,
|
||||
|
||||
/// <summary>
|
||||
/// Multiplicates the raw base value.
|
||||
/// </summary>
|
||||
Multiplicate,
|
||||
|
||||
/// <summary>
|
||||
/// Adds the value to the final value.
|
||||
/// </summary>
|
||||
AddFinal,
|
||||
|
||||
/// <summary>
|
||||
/// Adds only the highest available value to the raw base value (jewelry element resistance).
|
||||
/// </summary>
|
||||
Maximum,
|
||||
}
|
||||
128
src/AttributeSystem/AttributeDefinition.cs
Normal file
128
src/AttributeSystem/AttributeDefinition.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
// <copyright file="AttributeDefinition.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Defines and Identifies a Attribute.
|
||||
/// In the future it may also contain additional data, like a maximum limit of the reachable value to do balancing.
|
||||
/// </summary>
|
||||
public class AttributeDefinition : IEquatable<AttributeDefinition>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeDefinition"/> class.
|
||||
/// </summary>
|
||||
public AttributeDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeDefinition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="designation">The designation.</param>
|
||||
/// <param name="description">The description.</param>
|
||||
public AttributeDefinition(Guid id, string designation, string description)
|
||||
{
|
||||
this.Id = id;
|
||||
this.Designation = designation;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the designation.
|
||||
/// </summary>
|
||||
public string? Designation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum value of this attribute, if the value should be capped.
|
||||
/// </summary>
|
||||
public float? MaximumValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ==.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The LHS.</param>
|
||||
/// <param name="rhs">The RHS.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator ==(AttributeDefinition? lhs, AttributeDefinition? rhs)
|
||||
{
|
||||
if (ReferenceEquals(lhs, rhs))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs is null || rhs is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator !=.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The LHS.</param>
|
||||
/// <param name="rhs">The RHS.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator !=(AttributeDefinition? lhs, AttributeDefinition? rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string? ToString()
|
||||
{
|
||||
return this.Designation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return this.Equals(obj as AttributeDefinition);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(AttributeDefinition? other)
|
||||
{
|
||||
return this.Id == other?.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Id.GetHashCode();
|
||||
}
|
||||
}
|
||||
162
src/AttributeSystem/AttributeRelationship.cs
Normal file
162
src/AttributeSystem/AttributeRelationship.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
// <copyright file="AttributeRelationship.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
/// <summary>
|
||||
/// The operator which is applied between the input attribute and the input operand.
|
||||
/// </summary>
|
||||
public enum InputOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="AttributeRelationship.InputAttribute"/> is multiplied with the <see cref="AttributeRelationship.InputOperand"/> before effecting the <see cref="AttributeRelationship.TargetAttribute"/>.
|
||||
/// </summary>
|
||||
Multiply,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AttributeRelationship.InputAttribute"/> is increased by the <see cref="AttributeRelationship.InputOperand"/> before effecting the <see cref="AttributeRelationship.TargetAttribute"/>.
|
||||
/// </summary>
|
||||
Add,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AttributeRelationship.InputAttribute"/> is exponentiated by the <see cref="AttributeRelationship.InputOperand"/> before effecting the <see cref="AttributeRelationship.TargetAttribute"/>.
|
||||
/// </summary>
|
||||
Exponentiate,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AttributeRelationship.InputOperand"/> is exponentiated by the <see cref="AttributeRelationship.InputAttribute"/> before effecting the <see cref="AttributeRelationship.TargetAttribute"/>.
|
||||
/// </summary>
|
||||
ExponentiateByAttribute,
|
||||
|
||||
/// <summary>
|
||||
/// The maximum between <see cref="AttributeRelationship.InputAttribute"/> and <see cref="AttributeRelationship.InputOperand"/> is taken before effecting the <see cref="AttributeRelationship.TargetAttribute"/>.
|
||||
/// </summary>
|
||||
Maximum,
|
||||
|
||||
/// <summary>
|
||||
/// The minimum between <see cref="AttributeRelationship.InputAttribute"/> and <see cref="AttributeRelationship.InputOperand"/> is taken before effecting the <see cref="AttributeRelationship.TargetAttribute"/>.
|
||||
/// </summary>
|
||||
Minimum,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a relationship between two attributes.
|
||||
/// </summary>
|
||||
public class AttributeRelationship
|
||||
{
|
||||
private AttributeDefinition? _targetAttribute;
|
||||
private AttributeDefinition? _inputAttribute;
|
||||
private AttributeDefinition? _operandAttribute;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeRelationship"/> class.
|
||||
/// </summary>
|
||||
public AttributeRelationship()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeRelationship" /> class.
|
||||
/// </summary>
|
||||
/// <param name="targetAttribute">The target attribute.</param>
|
||||
/// <param name="inputOperand">The multiplier.</param>
|
||||
/// <param name="inputAttribute">The input attribute.</param>
|
||||
/// <param name="aggregateType">The type of the aggregate on the <paramref name="targetAttribute"/>.</param>
|
||||
public AttributeRelationship(AttributeDefinition targetAttribute, float inputOperand, AttributeDefinition inputAttribute, AggregateType aggregateType = AggregateType.AddRaw)
|
||||
: this(targetAttribute, inputOperand, inputAttribute, InputOperator.Multiply, default, aggregateType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeRelationship" /> class.
|
||||
/// </summary>
|
||||
/// <param name="targetAttribute">The target attribute.</param>
|
||||
/// <param name="inputOperand">The multiplier.</param>
|
||||
/// <param name="inputAttribute">The input attribute.</param>
|
||||
/// <param name="aggregateType">The type of the aggregate on the <paramref name="targetAttribute"/>.</param>
|
||||
public AttributeRelationship(AttributeDefinition targetAttribute, AttributeDefinition inputOperand, AttributeDefinition inputAttribute, AggregateType aggregateType = AggregateType.AddRaw)
|
||||
: this(targetAttribute, 1, inputAttribute, InputOperator.Multiply, inputOperand, aggregateType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeRelationship" /> class.
|
||||
/// </summary>
|
||||
/// <param name="targetAttribute">The target attribute.</param>
|
||||
/// <param name="inputOperand">The multiplier.</param>
|
||||
/// <param name="inputAttribute">The input attribute.</param>
|
||||
/// <param name="inputOperator">The input operator.</param>
|
||||
/// <param name="operandAttribute">The operand attribute.</param>
|
||||
/// <param name="aggregateType">The type of the aggregate on the <paramref name="targetAttribute"/>.</param>
|
||||
public AttributeRelationship(AttributeDefinition targetAttribute, float inputOperand, AttributeDefinition inputAttribute, InputOperator inputOperator, AttributeDefinition? operandAttribute = null, AggregateType aggregateType = AggregateType.AddRaw)
|
||||
{
|
||||
this.InputOperand = inputOperand;
|
||||
this.InputOperator = inputOperator;
|
||||
this.AggregateType = aggregateType;
|
||||
this._targetAttribute = targetAttribute;
|
||||
this._inputAttribute = inputAttribute;
|
||||
this._operandAttribute = operandAttribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target attribute which will be affected.
|
||||
/// </summary>
|
||||
public virtual AttributeDefinition? TargetAttribute
|
||||
{
|
||||
get => this._targetAttribute;
|
||||
set => this._targetAttribute = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the input attribute which provides the input value.
|
||||
/// </summary>
|
||||
public virtual AttributeDefinition? InputAttribute
|
||||
{
|
||||
get => this._inputAttribute;
|
||||
set => this._inputAttribute = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operand attribute which replaces the <see cref="InputOperand"/>, if set.
|
||||
/// </summary>
|
||||
public virtual AttributeDefinition? OperandAttribute
|
||||
{
|
||||
get => this._operandAttribute;
|
||||
set => this._operandAttribute = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operator which is applied between the input attribute and the input operand.
|
||||
/// </summary>
|
||||
public InputOperator InputOperator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operand which is applied to the input attribute before adding to the target attribute.
|
||||
/// Has only effect, when <see cref="OperandAttribute"/> is <see langword="null"/>.
|
||||
/// </summary>
|
||||
public float InputOperand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the aggregate type with which the relationship should effect the target attribute.
|
||||
/// </summary>
|
||||
public AggregateType AggregateType { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.TargetAttribute is null)
|
||||
{
|
||||
return $"{this.InputAttribute} {this.InputOperator.AsString()} {this.OperandAttribute?.ToString() ?? this.InputOperand.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
if (this.InputOperator == InputOperator.ExponentiateByAttribute)
|
||||
{
|
||||
return $"{this.TargetAttribute} {(this.AggregateType == AggregateType.Multiplicate ? "*" : "+")}= {this.OperandAttribute?.ToString() ?? this.InputOperand.ToString(CultureInfo.InvariantCulture)} {this.InputOperator.AsString()} {this.InputAttribute}";
|
||||
}
|
||||
|
||||
return $"{this.TargetAttribute} {(this.AggregateType == AggregateType.Multiplicate ? "*" : "+")}= {this.InputAttribute} {this.InputOperator.AsString()} {this.OperandAttribute?.ToString() ?? this.InputOperand.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
||||
89
src/AttributeSystem/AttributeRelationshipElement.cs
Normal file
89
src/AttributeSystem/AttributeRelationshipElement.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
// <copyright file="AttributeRelationshipElement.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute relationship element which takes several input elements which are summed up and multiplied.
|
||||
/// Calculated values are cached for a better performance.
|
||||
/// </summary>
|
||||
public class AttributeRelationshipElement : SimpleElement
|
||||
{
|
||||
private float? _cachedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeRelationshipElement" /> class.
|
||||
/// </summary>
|
||||
/// <param name="inputElements">The input elements which are summed up.</param>
|
||||
/// <param name="inputOperand">The operand which is applied to the summed up input elements.</param>
|
||||
/// <param name="inputOperator">The input operator.</param>
|
||||
public AttributeRelationshipElement(IEnumerable<IElement> inputElements, IElement inputOperand, InputOperator inputOperator)
|
||||
{
|
||||
this.InputElements = inputElements;
|
||||
this.InputOperand = inputOperand;
|
||||
this.InputOperator = inputOperator;
|
||||
foreach (var element in this.InputElements)
|
||||
{
|
||||
element.ValueChanged += this.ElementChanged;
|
||||
}
|
||||
|
||||
inputOperand.ValueChanged += this.ElementChanged;
|
||||
|
||||
// TODO: Is Dispose required?
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input elements.
|
||||
/// </summary>
|
||||
public IEnumerable<IElement> InputElements { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the multiplier with which the sum of all input element values are multiplied.
|
||||
/// </summary>
|
||||
public IElement InputOperand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the input operator.
|
||||
/// </summary>
|
||||
public InputOperator InputOperator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the calculated value.
|
||||
/// </summary>
|
||||
public override float Value => this._cachedValue ?? this.GetAndCacheValue();
|
||||
|
||||
private void ElementChanged(object? sender, EventArgs eventArgs)
|
||||
{
|
||||
this._cachedValue = null;
|
||||
this.RaiseValueChanged();
|
||||
}
|
||||
|
||||
private float GetAndCacheValue()
|
||||
{
|
||||
this._cachedValue = this.CalculateValue();
|
||||
return this._cachedValue.Value;
|
||||
}
|
||||
|
||||
private float CalculateValue()
|
||||
{
|
||||
return this.InputOperator switch
|
||||
{
|
||||
InputOperator.Multiply => this.InputElements.Sum(a => a.Value) * this.InputOperand.Value,
|
||||
InputOperator.Add => this.InputElements.Sum(a => a.Value) + this.InputOperand.Value,
|
||||
InputOperator.Exponentiate => (float)Math.Pow(
|
||||
this.InputElements.Sum(a => a.Value),
|
||||
this.InputOperand.Value),
|
||||
InputOperator.ExponentiateByAttribute => (float)Math.Pow(
|
||||
this.InputOperand.Value,
|
||||
this.InputElements.Sum(a => a.Value)),
|
||||
InputOperator.Maximum => Math.Max(
|
||||
this.InputElements.Sum(a => a.Value),
|
||||
this.InputOperand.Value),
|
||||
InputOperator.Minimum => Math.Min(
|
||||
this.InputElements.Sum(a => a.Value),
|
||||
this.InputOperand.Value),
|
||||
_ => throw new InvalidOperationException($"Input operator {this.InputOperator} unknown"),
|
||||
};
|
||||
}
|
||||
}
|
||||
51
src/AttributeSystem/AttributeRelationshipExtensions.cs
Normal file
51
src/AttributeSystem/AttributeRelationshipExtensions.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// <copyright file="AttributeRelationshipExtensions.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for <see cref="AttributeRelationship"/>.
|
||||
/// </summary>
|
||||
public static class AttributeRelationshipExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the target attribute and throws an exception if it's not initialized yet.
|
||||
/// </summary>
|
||||
/// <param name="relationship">The attribute relationship.</param>
|
||||
/// <returns>The target attribute definition.</returns>
|
||||
/// <exception cref="InvalidOperationException">TargetAttribute not initialized.</exception>
|
||||
public static AttributeDefinition GetTargetAttribute(this AttributeRelationship relationship)
|
||||
{
|
||||
return relationship.TargetAttribute ?? throw new InvalidOperationException("TargetAttribute not initialized.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input attribute and throws an exception if it's not initialized yet.
|
||||
/// </summary>
|
||||
/// <param name="relationship">The attribute relationship.</param>
|
||||
/// <returns>The input attribute definition.</returns>
|
||||
/// <exception cref="InvalidOperationException">TargetAttribute not initialized.</exception>
|
||||
public static AttributeDefinition GetInputAttribute(this AttributeRelationship relationship)
|
||||
{
|
||||
return relationship.InputAttribute ?? throw new InvalidOperationException("InputAttribute not initialized.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operand attribute.
|
||||
/// </summary>
|
||||
/// <param name="relationship">The attribute relationship.</param>
|
||||
/// <param name="attributeSystem">The attribute system.</param>
|
||||
/// <returns>
|
||||
/// The input attribute definition.
|
||||
/// </returns>
|
||||
public static IElement GetOperandElement(this AttributeRelationship relationship, IAttributeSystem attributeSystem)
|
||||
{
|
||||
if (relationship.OperandAttribute is { } operandAttribute)
|
||||
{
|
||||
return attributeSystem.GetOrCreateAttribute(operandAttribute);
|
||||
}
|
||||
|
||||
return new ConstantElement(relationship.InputOperand);
|
||||
}
|
||||
}
|
||||
259
src/AttributeSystem/AttributeSystem.cs
Normal file
259
src/AttributeSystem/AttributeSystem.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
// <copyright file="AttributeSystem.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
using System.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// The attribute system which holds all attributes of a character.
|
||||
/// </summary>
|
||||
public class AttributeSystem : IAttributeSystem, IEnumerable<IAttribute>
|
||||
{
|
||||
private readonly IDictionary<AttributeDefinition, IAttribute> _attributes = new Dictionary<AttributeDefinition, IAttribute>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttributeSystem" /> class.
|
||||
/// </summary>
|
||||
/// <param name="statAttributes">The stat attributes. These attributes are added just as-is and are not wrapped by a <see cref="ComposableAttribute"/>.</param>
|
||||
/// <param name="baseAttributes">The initial base attributes. These attributes contain the base values which will be wrapped by a <see cref="ComposableAttribute"/>, so additional elements can contribute to the attributes value. Instead of providing them here, you could also add them to the system by calling <see cref="AddElement"/> later.</param>
|
||||
/// <param name="attributeRelationships">The initial attribute relationships. Instead of providing them here, you could also add them to the system by calling <see cref="AddAttributeRelationship(AttributeRelationship, IAttributeSystem, AggregateType)"/> later.</param>
|
||||
public AttributeSystem(IEnumerable<IAttribute> statAttributes, IEnumerable<IAttribute> baseAttributes, IEnumerable<AttributeRelationship> attributeRelationships)
|
||||
{
|
||||
foreach (var statAttribute in statAttributes)
|
||||
{
|
||||
this._attributes.Add(statAttribute.Definition, statAttribute);
|
||||
}
|
||||
|
||||
foreach (var baseAttribute in baseAttributes)
|
||||
{
|
||||
this.AddElement(baseAttribute, baseAttribute.Definition);
|
||||
}
|
||||
|
||||
foreach (var combination in attributeRelationships)
|
||||
{
|
||||
this.AddAttributeRelationship(combination);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float this[AttributeDefinition? attributeDefinition]
|
||||
{
|
||||
get => this.GetValueOfAttribute(attributeDefinition);
|
||||
|
||||
set => this.SetStatAttribute(attributeDefinition, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddAttributeRelationship(AttributeRelationship relationship, IAttributeSystem sourceAttributeHolder, AggregateType aggregateType)
|
||||
{
|
||||
if (this.GetOrCreateAttribute(relationship.GetTargetAttribute()) is IComposableAttribute targetAttribute)
|
||||
{
|
||||
var relatedElement = this.CreateRelatedAttribute(relationship, sourceAttributeHolder, aggregateType);
|
||||
targetAttribute.AddElement(relatedElement);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the related attribute.
|
||||
/// </summary>
|
||||
/// <param name="relationship">The relationship.</param>
|
||||
/// <param name="sourceAttributeHolder">The source attribute holder. This may be the attribute system of another player.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
/// <returns>
|
||||
/// The newly created relationship element.
|
||||
/// </returns>
|
||||
public IElement CreateRelatedAttribute(AttributeRelationship relationship, IAttributeSystem sourceAttributeHolder, AggregateType aggregateType)
|
||||
{
|
||||
var inputElements = new[] { sourceAttributeHolder.GetOrCreateAttribute(relationship.GetInputAttribute()) };
|
||||
return new AttributeRelationshipElement(inputElements, relationship.GetOperandElement(sourceAttributeHolder), relationship.InputOperator)
|
||||
{
|
||||
AggregateType = aggregateType,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the stat attribute, if the <paramref name="attributeDefinition"/> is a stat attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeDefinition">The attribute definition.</param>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
/// <returns>The success.</returns>
|
||||
public bool SetStatAttribute(AttributeDefinition? attributeDefinition, float newValue)
|
||||
{
|
||||
if (attributeDefinition is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._attributes.TryGetValue(attributeDefinition, out var attribute)
|
||||
&& attribute is StatAttribute statAttribute)
|
||||
{
|
||||
statAttribute.Value = newValue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the composable attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeDefinition">The attribute definition.</param>
|
||||
/// <returns>The composable attribute.</returns>
|
||||
public ComposableAttribute? GetComposableAttribute(AttributeDefinition attributeDefinition)
|
||||
{
|
||||
return this.GetOrCreateAttribute(attributeDefinition) as ComposableAttribute;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float GetValueOfAttribute(AttributeDefinition? attributeDefinition)
|
||||
{
|
||||
var element = this.GetAttribute(attributeDefinition);
|
||||
if (element != null)
|
||||
{
|
||||
var actualDefinition = (element as BaseAttribute)?.Definition ?? attributeDefinition;
|
||||
if (actualDefinition?.MaximumValue is { } maximumValue && element.Value > maximumValue)
|
||||
{
|
||||
return maximumValue;
|
||||
}
|
||||
|
||||
return element.Value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddElement(IElement element, AttributeDefinition targetAttribute)
|
||||
{
|
||||
if (!this._attributes.TryGetValue(targetAttribute, out var attribute))
|
||||
{
|
||||
attribute = new ComposableAttribute(targetAttribute);
|
||||
this._attributes.Add(targetAttribute, attribute);
|
||||
this.OnAttributeAdded(attribute);
|
||||
}
|
||||
|
||||
if (attribute is IComposableAttribute composableAttribute)
|
||||
{
|
||||
composableAttribute.AddElement(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Attribute {targetAttribute} is not a composable attribute.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveElement(IElement element, AttributeDefinition targetAttribute)
|
||||
{
|
||||
if (this._attributes.TryGetValue(targetAttribute, out var attribute))
|
||||
{
|
||||
if (attribute is IComposableAttribute composableAttribute)
|
||||
{
|
||||
composableAttribute.RemoveElement(element);
|
||||
if (!composableAttribute.Elements.Any())
|
||||
{
|
||||
this._attributes.Remove(targetAttribute);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Attribute {targetAttribute} is not a composable attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine("Stat Attributes:");
|
||||
foreach (var statAttribute in this._attributes.Values.OfType<StatAttribute>())
|
||||
{
|
||||
stringBuilder.AppendLine($" {statAttribute.Definition}: {statAttribute.Value}");
|
||||
}
|
||||
|
||||
stringBuilder.AppendLine("Others:");
|
||||
foreach (var attribute in this._attributes.Values.OfType<IComposableAttribute>())
|
||||
{
|
||||
stringBuilder.AppendLine($" {attribute.Definition}: {attribute.Value}");
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<IAttribute> GetEnumerator()
|
||||
{
|
||||
return this._attributes.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates the element with the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeDefinition">The attribute definition.</param>
|
||||
/// <returns>The element of the attribute.</returns>
|
||||
public IElement GetOrCreateAttribute(AttributeDefinition attributeDefinition)
|
||||
{
|
||||
var element = this.GetAttribute(attributeDefinition);
|
||||
if (element is null)
|
||||
{
|
||||
var composableAttribute = new ComposableAttribute(attributeDefinition);
|
||||
element = composableAttribute;
|
||||
this._attributes.Add(attributeDefinition, composableAttribute);
|
||||
this.OnAttributeAdded(composableAttribute);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an attribute was added to the system after the initial construction.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The attribute.</param>
|
||||
protected virtual void OnAttributeAdded(IAttribute attribute)
|
||||
{
|
||||
// can be overwritten.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an attribute was removed from the system.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The attribute.</param>
|
||||
protected virtual void OnAttributeRemoved(IAttribute attribute)
|
||||
{
|
||||
// can be overwritten.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the attribute relationship.
|
||||
/// </summary>
|
||||
/// <param name="combination">The combination.</param>
|
||||
private void AddAttributeRelationship(AttributeRelationship combination)
|
||||
{
|
||||
this.AddAttributeRelationship(combination, this, combination.AggregateType);
|
||||
}
|
||||
|
||||
private IElement? GetAttribute(AttributeDefinition? attributeDefinition)
|
||||
{
|
||||
if (attributeDefinition is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._attributes.TryGetValue(attributeDefinition, out var attribute))
|
||||
{
|
||||
return attribute;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
54
src/AttributeSystem/BaseAttribute.cs
Normal file
54
src/AttributeSystem/BaseAttribute.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// <copyright file="BaseAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The base class for an attribute.
|
||||
/// </summary>
|
||||
public abstract class BaseAttribute : IAttribute
|
||||
{
|
||||
private AttributeDefinition _definition = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="definition">The definition.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
protected BaseAttribute(AttributeDefinition definition, AggregateType aggregateType)
|
||||
{
|
||||
this.Definition = definition;
|
||||
this.AggregateType = aggregateType;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler? ValueChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual AttributeDefinition Definition
|
||||
{
|
||||
get => this._definition;
|
||||
protected set => this._definition = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract float Value { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AggregateType AggregateType { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Definition?.Designation}: {this.Value}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the value changed event.
|
||||
/// </summary>
|
||||
protected void RaiseValueChanged()
|
||||
{
|
||||
this.ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
33
src/AttributeSystem/BaseStatAttribute.cs
Normal file
33
src/AttributeSystem/BaseStatAttribute.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// <copyright file="BaseStatAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute which represents an increasable stat attribute (e.g. by level-up points).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Intermediate class, needed because we want to add a setter.
|
||||
/// We do just override the getter here and have to introduce a new Value get/set-property on a derived type.
|
||||
/// </remarks>
|
||||
public abstract class BaseStatAttribute : BaseAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseStatAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="definition">The definition.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
protected BaseStatAttribute(AttributeDefinition definition, AggregateType aggregateType)
|
||||
: base(definition, aggregateType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float Value => this.ValueGetter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
protected abstract float ValueGetter { get; }
|
||||
}
|
||||
48
src/AttributeSystem/CombinedElement.cs
Normal file
48
src/AttributeSystem/CombinedElement.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
// <copyright file="CombinedElement.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An element which combines two elements into one.
|
||||
/// </summary>
|
||||
public class CombinedElement : IElement
|
||||
{
|
||||
private readonly IElement _element1;
|
||||
private readonly IElement _element2;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CombinedElement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="element1">The first element.</param>
|
||||
/// <param name="element2">The second element.</param>
|
||||
/// <exception cref="ArgumentException">The aggregate type of both elements need to match.</exception>
|
||||
public CombinedElement(IElement element1, IElement element2)
|
||||
{
|
||||
if (element1.AggregateType != element2.AggregateType)
|
||||
{
|
||||
throw new ArgumentException($"The aggregate type of both elements need to match. Element1: {element1.AggregateType}, Element2: {element2.AggregateType}");
|
||||
}
|
||||
|
||||
this._element1 = element1;
|
||||
this._element2 = element2;
|
||||
this._element1.ValueChanged += (_, _) => this.ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
this._element2.ValueChanged += (_, _) => this.ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? ValueChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Value => this._element1.Value + this._element2.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public AggregateType AggregateType => this._element1.AggregateType;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Value} ({this.AggregateType})";
|
||||
}
|
||||
}
|
||||
97
src/AttributeSystem/ComposableAttribute.cs
Normal file
97
src/AttributeSystem/ComposableAttribute.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
// <copyright file="ComposableAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute which is a composition of elements.
|
||||
/// </summary>
|
||||
public class ComposableAttribute : BaseAttribute, IComposableAttribute
|
||||
{
|
||||
private readonly IList<IElement> _elementList;
|
||||
|
||||
private float? _maximumValue;
|
||||
|
||||
private float? _cachedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComposableAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="definition">The definition.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
/// <param name="maximumValue">The inner maximum value.</param>
|
||||
public ComposableAttribute(AttributeDefinition definition, AggregateType aggregateType = AggregateType.AddRaw, float? maximumValue = null)
|
||||
: base(definition, aggregateType)
|
||||
{
|
||||
this._elementList = new List<IElement>();
|
||||
this._maximumValue = maximumValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IElement> Elements => this._elementList;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float Value => this._cachedValue ?? this.GetAndCacheValue();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IComposableAttribute AddElement(IElement element)
|
||||
{
|
||||
this._elementList.Add(element);
|
||||
element.ValueChanged += this.ElementChanged;
|
||||
this.ElementChanged(element, EventArgs.Empty);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveElement(IElement element)
|
||||
{
|
||||
if (this._elementList.Remove(element))
|
||||
{
|
||||
element.ValueChanged -= this.ElementChanged;
|
||||
this.ElementChanged(element, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetAndCacheValue()
|
||||
{
|
||||
if (this._elementList.Count == 0)
|
||||
{
|
||||
this._cachedValue = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var rawValues = this.Elements.Where(e => e.AggregateType == AggregateType.AddRaw).Sum(e => e.Value);
|
||||
var multiValues = this.Elements.Where(e => e.AggregateType == AggregateType.Multiplicate).Select(e => e.Value).Concat(Enumerable.Repeat(1.0F, 1)).Aggregate((a, b) => a * b);
|
||||
var finalValues = this.Elements.Where(e => e.AggregateType == AggregateType.AddFinal).Sum(e => e.Value);
|
||||
var maxValues = this.Elements.Where(e => e.AggregateType == AggregateType.Maximum).MaxBy(e => e.Value)?.Value ?? 0;
|
||||
rawValues += maxValues;
|
||||
|
||||
if (this.Elements.All(e => e.AggregateType == AggregateType.Multiplicate))
|
||||
{
|
||||
rawValues = 1;
|
||||
}
|
||||
|
||||
var newValue = (rawValues * multiValues) + finalValues;
|
||||
if (this._maximumValue.HasValue)
|
||||
{
|
||||
newValue = Math.Min(this._maximumValue.Value, newValue);
|
||||
}
|
||||
|
||||
if (this.Definition.MaximumValue.HasValue)
|
||||
{
|
||||
newValue = Math.Min(this.Definition.MaximumValue.Value, newValue);
|
||||
}
|
||||
|
||||
this._cachedValue = newValue;
|
||||
|
||||
return this._cachedValue.Value;
|
||||
}
|
||||
|
||||
private void ElementChanged(object? sender, EventArgs eventArgs)
|
||||
{
|
||||
this._cachedValue = null;
|
||||
this.RaiseValueChanged();
|
||||
}
|
||||
}
|
||||
67
src/AttributeSystem/ConstValueAttribute.cs
Normal file
67
src/AttributeSystem/ConstValueAttribute.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// <copyright file="ConstValueAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute with a constant value.
|
||||
/// </summary>
|
||||
public class ConstValueAttribute : IAttribute
|
||||
{
|
||||
private AttributeDefinition _definition = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConstValueAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="definition">The definition.</param>
|
||||
public ConstValueAttribute(float value, AttributeDefinition definition)
|
||||
{
|
||||
this.Value = value;
|
||||
this._definition = definition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConstValueAttribute"/> class.
|
||||
/// </summary>
|
||||
protected ConstValueAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Empty implementation, because the value can't change.</remarks>
|
||||
public event EventHandler? ValueChanged
|
||||
{
|
||||
#pragma warning disable S3237 //Empty implementation, because the value can't change.
|
||||
add
|
||||
{
|
||||
// no action required
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
// no action required
|
||||
}
|
||||
#pragma warning restore S3237
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual AttributeDefinition Definition
|
||||
{
|
||||
get => this._definition;
|
||||
protected set => this._definition = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Value { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AggregateType AggregateType => AggregateType.AddRaw;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Definition.Designation}: {this.Value}";
|
||||
}
|
||||
}
|
||||
50
src/AttributeSystem/ConstantElement.cs
Normal file
50
src/AttributeSystem/ConstantElement.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// <copyright file="ConstantElement.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An element with a constant value.
|
||||
/// </summary>
|
||||
public class ConstantElement : IElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConstantElement" /> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The constant value.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
public ConstantElement(float value, AggregateType aggregateType = AggregateType.AddRaw)
|
||||
{
|
||||
this.Value = value;
|
||||
this.AggregateType = aggregateType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Never occurs, so the implementation is empty.
|
||||
/// </summary>
|
||||
public event EventHandler? ValueChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
// do nothing, as the value never changes.
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
// do nothing, as the value never changes.
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Value { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AggregateType AggregateType { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Value} ({this.AggregateType})";
|
||||
}
|
||||
}
|
||||
30
src/AttributeSystem/Extensions.cs
Normal file
30
src/AttributeSystem/Extensions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// <copyright file="Extensions.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Common extension methods.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the input operator as string.
|
||||
/// </summary>
|
||||
/// <param name="inputOperator">The input operator.</param>
|
||||
/// <returns>The textual representation of the input operator.</returns>
|
||||
public static string AsString(this InputOperator inputOperator)
|
||||
{
|
||||
return inputOperator switch
|
||||
{
|
||||
InputOperator.Add => "+",
|
||||
InputOperator.Multiply => "*",
|
||||
InputOperator.Exponentiate => "^",
|
||||
InputOperator.ExponentiateByAttribute => "^",
|
||||
InputOperator.Maximum => "<max>",
|
||||
InputOperator.Minimum => "<min>",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
16
src/AttributeSystem/IAttribute.cs
Normal file
16
src/AttributeSystem/IAttribute.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// <copyright file="IAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The interface of an attribute.
|
||||
/// </summary>
|
||||
public interface IAttribute : IElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the attribute definition.
|
||||
/// </summary>
|
||||
AttributeDefinition Definition { get; }
|
||||
}
|
||||
54
src/AttributeSystem/IAttributeSystem.cs
Normal file
54
src/AttributeSystem/IAttributeSystem.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// <copyright file="IAttributeSystem.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute system which holds all attributes of a game object.
|
||||
/// </summary>
|
||||
public interface IAttributeSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value with the specified attribute definition.
|
||||
/// </summary>
|
||||
/// <param name="attributeDefinition">The attribute definition.</param>
|
||||
/// <returns>The value of the specified attribute.</returns>
|
||||
float this[AttributeDefinition attributeDefinition] { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeDefinition">The attribute definition.</param>
|
||||
/// <returns>The value of the attribute.</returns>
|
||||
float GetValueOfAttribute(AttributeDefinition attributeDefinition);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the element.
|
||||
/// </summary>
|
||||
/// <param name="element">The element.</param>
|
||||
/// <param name="targetAttribute">The target attribute.</param>
|
||||
void AddElement(IElement element, AttributeDefinition targetAttribute);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the element.
|
||||
/// </summary>
|
||||
/// <param name="element">The element.</param>
|
||||
/// <param name="targetAttribute">The target attribute.</param>
|
||||
void RemoveElement(IElement element, AttributeDefinition targetAttribute);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the attribute relationship.
|
||||
/// </summary>
|
||||
/// <param name="relationship">The relationship.</param>
|
||||
/// <param name="sourceAttributeHolder">The source attribute holder. May be the attribute system of another player.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
void AddAttributeRelationship(AttributeRelationship relationship, IAttributeSystem sourceAttributeHolder, AggregateType aggregateType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates the element with the specified attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeDefinition">The attribute definition.</param>
|
||||
/// <returns>The element of the attribute.</returns>
|
||||
IElement GetOrCreateAttribute(AttributeDefinition attributeDefinition);
|
||||
}
|
||||
35
src/AttributeSystem/IComposableAttribute.cs
Normal file
35
src/AttributeSystem/IComposableAttribute.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// <copyright file="IComposableAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The Attribute... Hint: every attribute could be wrapped into an element to chain them.
|
||||
/// Example: We have a base strength attribute which could be wrapped into an element,
|
||||
/// and this element could be added to a total strength attribute, which will
|
||||
/// also contain the elements gotten from master tree or ancient items.
|
||||
/// This way we could identify the base stats, which are needed for the character stats packet.
|
||||
/// Another use could define an aggregate function (either addition or multiplication), and again
|
||||
/// we could chain this calculations together (keeping commutative property).
|
||||
/// </summary>
|
||||
public interface IComposableAttribute : IAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the elements, of which this attribute is calculated.
|
||||
/// </summary>
|
||||
IEnumerable<IElement> Elements { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the element to the composition.
|
||||
/// </summary>
|
||||
/// <param name="element">The element.</param>
|
||||
/// <returns>The composable attribute itself, to be able to chain adding.</returns>
|
||||
IComposableAttribute AddElement(IElement element);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the element from the composition.
|
||||
/// </summary>
|
||||
/// <param name="element">The element.</param>
|
||||
void RemoveElement(IElement element);
|
||||
}
|
||||
26
src/AttributeSystem/IElement.cs
Normal file
26
src/AttributeSystem/IElement.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// <copyright file="IElement.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The interface of an element.
|
||||
/// </summary>
|
||||
public interface IElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the value has been changed.
|
||||
/// </summary>
|
||||
event EventHandler? ValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
float Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the aggregate.
|
||||
/// </summary>
|
||||
AggregateType AggregateType { get; }
|
||||
}
|
||||
20
src/AttributeSystem/MUnique.OpenMU.AttributeSystem.csproj
Normal file
20
src/AttributeSystem/MUnique.OpenMU.AttributeSystem.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable;CS4014;VSTHRD110;VSTHRD100</WarningsAsErrors>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\..\bin\Debug\</OutputPath>
|
||||
<DocumentationFile>..\..\bin\Debug\MUnique.OpenMU.AttributeSystem.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\..\bin\Release\</OutputPath>
|
||||
<DocumentationFile>..\..\bin\Release\MUnique.OpenMU.AttributeSystem.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
12
src/AttributeSystem/Properties/AssemblyInfo.cs
Normal file
12
src/AttributeSystem/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// <copyright file="AssemblyInfo.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MUnique.OpenMU.AttributeSystem")]
|
||||
[assembly: InternalsVisibleTo("MUnique.OpenMU.AttributeSystem.Tests")]
|
||||
56
src/AttributeSystem/Readme.md
Normal file
56
src/AttributeSystem/Readme.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Attribute System
|
||||
|
||||
This project contains all what's required to create a so-called "Attribute System".
|
||||
I'm sure it could be helpful also for other games, even other kind of games,
|
||||
because it's pretty generic and not bound to MU Online or RPGs in general.
|
||||
|
||||
## What is it?
|
||||
|
||||
In OpenMU, every object which attacks and is attackable (interface IAttackable),
|
||||
has an instance of an Attribute System. This Attribute System contains all attributes
|
||||
of this entity.
|
||||
|
||||
## And how is it used?
|
||||
|
||||
For example a attribute system of a player can contain the following attributes:
|
||||
|
||||
* Strength
|
||||
* Agility
|
||||
* Vitality
|
||||
* Health
|
||||
* Level
|
||||
* Physical Damage (min)
|
||||
* Physical Damage (max)
|
||||
|
||||
Attributes can depend on each other and attributes values are calculated automatically.
|
||||
For example we can define that Health is calculated by multiplying Vitality by
|
||||
2 and adding the Level. Or that the minimum physical damage is Strength divided
|
||||
by 6.
|
||||
We can define such relationships by AttributeRelationship objects.
|
||||
|
||||
To make this work there need to be attributes which don't depend on other ones
|
||||
and can be influenced by the player. These are called "StatAttributes".
|
||||
StatAttributes are for example:
|
||||
|
||||
* Level (increasable by gaining experience)
|
||||
* Base Strength (increasable by distributing free level up points)
|
||||
|
||||
Attribute values can get increased by equipping items. For example, there are
|
||||
ancient items which increase Strength when they are equipped. Or more classical,
|
||||
Swords which would increase the minimum and maximum physical damage attributes.
|
||||
|
||||
E.g. for Strength we usually have another attribute which is called "Total Strength",
|
||||
which is basically the "Base Strength", but it can be a target attribute of an
|
||||
AttributeRelationship. This "Total Strength" is then used for item requirements
|
||||
or the further damage calculation. The "Base Strength" is the value which is
|
||||
usually persisted to the database for a specific player.
|
||||
|
||||
In case of this server project, these relationships are all defined the
|
||||
configuration for each character class. For some examples you can have a look
|
||||
at the CharacterClassInitialization of the MUnique.OpenMU.Persistence.Initialization
|
||||
project.
|
||||
|
||||
## Tests
|
||||
|
||||
This project also includes some unit tests for AttributeSystem, so check it out
|
||||
if you want to find out how that works as a whole.
|
||||
79
src/AttributeSystem/SimpleElement.cs
Normal file
79
src/AttributeSystem/SimpleElement.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// <copyright file="SimpleElement.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// A simple element with a variable value.
|
||||
/// </summary>
|
||||
public class SimpleElement : IElement
|
||||
{
|
||||
private float _value;
|
||||
private AggregateType _aggregateType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimpleElement"/> class.
|
||||
/// </summary>
|
||||
public SimpleElement()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimpleElement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="aggregateType">Type of the aggregate.</param>
|
||||
public SimpleElement(float value, AggregateType aggregateType)
|
||||
{
|
||||
this._value = value;
|
||||
this._aggregateType = aggregateType;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler? ValueChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float Value
|
||||
{
|
||||
get => this._value;
|
||||
|
||||
set
|
||||
{
|
||||
if (Math.Abs(this._value - value) > 0.00001f)
|
||||
{
|
||||
this._value = value;
|
||||
this.RaiseValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AggregateType AggregateType
|
||||
{
|
||||
get => this._aggregateType;
|
||||
|
||||
set
|
||||
{
|
||||
if (this._aggregateType != value)
|
||||
{
|
||||
this._aggregateType = value;
|
||||
this.RaiseValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Value} ({this.AggregateType})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the value changed event.
|
||||
/// </summary>
|
||||
protected void RaiseValueChanged()
|
||||
{
|
||||
this.ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
63
src/AttributeSystem/StatAttribute.cs
Normal file
63
src/AttributeSystem/StatAttribute.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// <copyright file="StatAttribute.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.AttributeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute which represents an increasable stat attribute (e.g. by level-up points).
|
||||
/// </summary>
|
||||
public class StatAttribute : BaseStatAttribute
|
||||
{
|
||||
private float _statValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatAttribute"/> class.
|
||||
/// </summary>
|
||||
public StatAttribute()
|
||||
: base(null!, AggregateType.AddRaw)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="definition">The definition.</param>
|
||||
/// <param name="baseValue">The base value.</param>
|
||||
public StatAttribute(AttributeDefinition definition, float baseValue)
|
||||
: base(definition, AggregateType.AddRaw)
|
||||
{
|
||||
this._statValue = baseValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
public new virtual float Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Definition?.MaximumValue is { } maxValue)
|
||||
{
|
||||
return Math.Min(maxValue, this._statValue);
|
||||
}
|
||||
|
||||
return this._statValue;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (Math.Abs(this._statValue - value) > 0.01f)
|
||||
{
|
||||
this._statValue = value;
|
||||
this.RaiseValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override float ValueGetter => this._statValue;
|
||||
}
|
||||
Reference in New Issue
Block a user