// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.AttributeSystem.Tests; /// /// Tests for the . /// [TestFixture] public class AttributeSystemTests { private readonly AttributeDefinition _attributeA = new(Guid.NewGuid(), "A", "The A attribute"); private readonly AttributeDefinition _attributeB = new(Guid.NewGuid(), "B", "The B attribute"); private readonly AttributeDefinition _attributeC = new(Guid.NewGuid(), "C", "The C attribute"); private readonly AttributeDefinition _attributeD = new(Guid.NewGuid(), "D", "The D attribute"); private readonly AttributeDefinition _attributeAplusB = new(Guid.NewGuid(), "A+B", "The A+B attribute"); private readonly AttributeDefinition _attributeAtimesB = new(Guid.NewGuid(), "A*B", "The A*B attribute"); private readonly AttributeDefinition _attributeAchained = new(Guid.NewGuid(), "A'", "The chained A attribute"); private List _statAttributes = null!; private List _baseAttributes = null!; private List _relationShips = null!; /// /// Setups each test case. /// [SetUp] public void Setup() { this._statAttributes = new List(); this._baseAttributes = new List(); this._relationShips = new List(); } /// /// Tests if a is added correctly and it's value is returned. /// [Test] public void StatAttributeValue() { const int attributeAValue = 1234; this._statAttributes.Add(new StatAttribute(this._attributeA, attributeAValue)); var system = this.CreateAttributeSystem(); var value = system[this._attributeA]; Assert.That(value, Is.EqualTo(attributeAValue)); } /// /// Tests if a attribute is added correctly and it's value is returned. /// [Test] public void BaseAttributeValue() { const int attributeAValue = 9999; this._baseAttributes.Add(new ConstValueAttribute(attributeAValue, this._attributeA)); var system = this.CreateAttributeSystem(); var value = system[this._attributeA]; Assert.That(value, Is.EqualTo(attributeAValue)); } /// /// Tests if a is added correctly and the combined (in this case a chained) attribute does return the right value. /// [Test] public void ChainedAttribute() { const int attributeAValue = 1234; this._statAttributes.Add(new StatAttribute(this._attributeA, attributeAValue)); this._relationShips.Add(new AttributeRelationship(this._attributeAchained, 1, this._attributeA)); var system = this.CreateAttributeSystem(); var value = system[this._attributeAchained]; Assert.That(value, Is.EqualTo(attributeAValue)); } /// /// Tests if a is added correctly and the multiplied attribute does return the right multiplied value. /// [Test] public void MultipliedAttribute() { const int attributeAValue = 1234; const int multiplier = 3; this._statAttributes.Add(new StatAttribute(this._attributeA, attributeAValue)); this._relationShips.Add(new AttributeRelationship(this._attributeAchained, multiplier, this._attributeA)); var system = this.CreateAttributeSystem(); var value = system[this._attributeAchained]; Assert.That(value, Is.EqualTo(attributeAValue * multiplier)); } /// /// Tests if a combination of s is added and handled correctly. /// Two attributes are combined to one target attribute. /// It tests if the resulting value is calculated correctly. /// [Test] public void CombinedRelationship() { const int attributeAValue = 1234; const int attributeBValue = 4938; const int attributeBMultiplier = 2; this._statAttributes.Add(new StatAttribute(this._attributeA, attributeAValue)); this._statAttributes.Add(new StatAttribute(this._attributeB, attributeBValue)); this._relationShips.Add(new AttributeRelationship(this._attributeAplusB, 1, this._attributeA)); this._relationShips.Add(new AttributeRelationship(this._attributeAplusB, attributeBMultiplier, this._attributeB)); var system = this.CreateAttributeSystem(); var value = system[this._attributeAplusB]; Assert.That(value, Is.EqualTo(attributeAValue + (attributeBValue * attributeBMultiplier))); } /// /// Tests if a combination of s is added and handled correctly. /// Two attributes are combined to one target attribute. /// It tests if the resulting value is calculated correctly after one of the depending attributes changed their value. /// [Test] public void CombinedRelationshipChangedValue() { const int attributeAValue = 1234; const int attributeBValue = 4938; const int attributeBMultiplier = 2; var statAttributeA = new StatAttribute(this._attributeA, attributeAValue); this._statAttributes.Add(statAttributeA); this._statAttributes.Add(new StatAttribute(this._attributeB, attributeBValue)); this._relationShips.Add(new AttributeRelationship(this._attributeAplusB, 1, this._attributeA)); this._relationShips.Add(new AttributeRelationship(this._attributeAplusB, attributeBMultiplier, this._attributeB)); var system = this.CreateAttributeSystem(); const int attributeAnewValue = 1000; statAttributeA.Value = attributeAnewValue; var value = system[this._attributeAplusB]; Assert.That(value, Is.EqualTo(attributeAnewValue + (attributeBValue * attributeBMultiplier))); } /// /// Tests if adding additional elements results in a updated correct value. /// [Test] public void CombinedRelationshipAddedElement() { const int attributeAValue = 1234; const int attributeBValue = 4938; const int attributeBMultiplier = 2; var statAttributeA = new StatAttribute(this._attributeA, attributeAValue); this._statAttributes.Add(statAttributeA); this._statAttributes.Add(new StatAttribute(this._attributeB, attributeBValue)); this._relationShips.Add(new AttributeRelationship(this._attributeAplusB, 1, this._attributeA)); this._relationShips.Add(new AttributeRelationship(this._attributeAplusB, attributeBMultiplier, this._attributeB)); var system = this.CreateAttributeSystem(); const int addedAttributeValue = 10; system.AddElement(new ConstValueAttribute(addedAttributeValue, this._attributeAplusB), this._attributeAplusB); var value = system[this._attributeAplusB]; Assert.That(value, Is.EqualTo(attributeAValue + (attributeBValue * attributeBMultiplier) + addedAttributeValue)); } /// /// Tests if using another attribute as operand results in a correct value. /// [Test] public void MultipliedAttributes() { const int attributeAValue = 1234; const int attributeBValue = 2; var statAttributeA = new StatAttribute(this._attributeA, attributeAValue); var statAttributeB = new StatAttribute(this._attributeB, attributeBValue); this._statAttributes.Add(statAttributeA); this._statAttributes.Add(statAttributeB); this._relationShips.Add(new AttributeRelationship(this._attributeAtimesB, this._attributeB, this._attributeA)); var system = this.CreateAttributeSystem(); var value = system[this._attributeAtimesB]; Assert.That(value, Is.EqualTo(attributeAValue * attributeBValue)); } /// /// Tests if using another attribute as operand results in a correct value. /// [TestCase(true, 2)] [TestCase(false, 0)] public void ConditionalAttributes(bool conditionMet, float expected) { const int bonusValue = 2; var targetAttribute = this._attributeAplusB; var bonusIfConditionMet = new StatAttribute(this._attributeB, bonusValue); var conditionalAttribute = new StatAttribute(this._attributeC, conditionMet ? 1 : 0); this._statAttributes.Add(bonusIfConditionMet); this._statAttributes.Add(conditionalAttribute); this._relationShips.Add(new AttributeRelationship(targetAttribute, conditionalAttribute.Definition, bonusIfConditionMet.Definition)); var system = this.CreateAttributeSystem(); var value = system[targetAttribute]; Assert.That(value, Is.EqualTo(expected)); } /// /// Tests the use case of multiplying a base value with a conditional bonus multiplier. /// This is how Stats.DefenseIncreaseWithEquippedShield is intended to work. /// [TestCase(true, 105)] [TestCase(false, 100)] public void ConditionalAttributes_Multiply(bool conditionMet, float expected) { const float bonusMultiplier = 0.05f; const float baseValue = 100f; var targetAttribute = this._attributeAplusB; var baseAttribute = new StatAttribute(this._attributeA, baseValue); var bonusIfConditionMet = new StatAttribute(this._attributeB, bonusMultiplier); var conditionalAttribute = new StatAttribute(this._attributeC, conditionMet ? 1 : 0); var tempAttribute = this._attributeD; this._statAttributes.Add(baseAttribute); this._statAttributes.Add(bonusIfConditionMet); this._statAttributes.Add(conditionalAttribute); // First, we copy our base value to the target this._relationShips.Add(new AttributeRelationship(targetAttribute, 1, baseAttribute.Definition)); // Then, we calculate the bonus into a temporary attribute, depending on the condition this._relationShips.Add(new AttributeRelationship(tempAttribute, conditionalAttribute.Definition, bonusIfConditionMet.Definition)); // Finally, we apply the temporary attribute as multiplier for the base value and add it to the target this._relationShips.Add(new AttributeRelationship(targetAttribute, tempAttribute, baseAttribute.Definition)); var system = this.CreateAttributeSystem(); var value = system[targetAttribute]; Assert.That(value, Is.EqualTo(expected)); } /// /// Tests if a changed maximum value of an attribute is considered when requesting it from the AttributeSystem. /// [Test] public void MaximumValueIsRespectedFromStoredDefinition() { // Create an attribute definition with a maximum value var attackSpeedDefinition = new AttributeDefinition(Guid.NewGuid(), "AttackSpeed", "Attack speed attribute") { MaximumValue = 300, }; // Add a stat attribute with a very high value (exceeding the maximum) const int highValue = 5000; this._statAttributes.Add(new StatAttribute(attackSpeedDefinition, highValue)); var system = this.CreateAttributeSystem(); // Create a different instance of the attribute definition (simulating what happens when // the definition is retrieved from a different source, like a database) var differentInstanceOfDefinition = new AttributeDefinition(attackSpeedDefinition.Id, "AttackSpeed", "Attack speed attribute"); // The system should return the maximum value from the stored definition, not exceed it var value = system[differentInstanceOfDefinition]; Assert.That(value, Is.EqualTo(300)); } /// /// Tests if a changed maximum value is respected for a ComposableAttribute when requesting it from the AttributeSystem. /// [Test] public void MaximumValueIsRespectedFromStoredDefinitionForComposableAttribute() { // Create an attribute definition with a maximum value var attackSpeedDefinition = new AttributeDefinition(Guid.NewGuid(), "AttackSpeed", "Attack speed attribute") { MaximumValue = 300, }; // Add a base attribute which will create a ComposableAttribute const int highValue = 5000; this._baseAttributes.Add(new ConstValueAttribute(highValue, attackSpeedDefinition)); var system = this.CreateAttributeSystem(); // Create a different instance of the attribute definition var differentInstanceOfDefinition = new AttributeDefinition(attackSpeedDefinition.Id, "AttackSpeed", "Attack speed attribute"); // The system should return the maximum value from the stored definition, not exceed it var value = system[differentInstanceOfDefinition]; Assert.That(value, Is.EqualTo(300)); } /// /// Creates the attribute system for testing, initialized with , and . /// /// The created attribute system, initialized with , and . private AttributeSystem CreateAttributeSystem() { return new(this._statAttributes, this._baseAttributes, this._relationShips); } }