Modify expression syntax

Hi!

You can use the Modify expression syntax to get to the attributes of the nested ItemDefinition. We want to try to make weapons modular, I make Attribute<ItemDefenition> and replace it at runtime. Do you think it will be possible to get to the attributes of this item in order to write an auto calculation of the parameters of the main item?

1647352205123.png

As an example, there is the weight of the main item, the module has been invested, it also has a weight. Automatically calculated weights based on Modify Syntax

[Weight] + StockEquippedModule[Weight] + MagazineEquippedModule[Weight]
 
Last edited:
Unfortunatly that is not possible.
The modify syntax is very limited. It only works for int, float and strings. And it can only reference other attributes from items in the same hierarchy. It cannot fetch attribute values from Attribute<itemDefinitions>

A work around would be to use a custom type for your attributes. This way you can write the custom function to compute the value you want.

So instead of TotalWeight being a float make it a custom type called ItemWeight. Which has a GetWeight(item) function that computes the weigth however you want. You can even make it into a scriptable object if you have multiple items that share similar functionality.
I hope that helps.
 
I'm not sure about the purity of this solution, but I got it:

1647435406047.png

1. Added the following pattern:
C#:
public const string ItemDefinitionWithAttributePattern = @"\((.*?)\)\[(.*?)\]";

2. Find mathces

1647436564196.png

2.1 changed ParametersInString to pass any pattern,
C#:
public static IReadOnlyList<GroupCollection> ParametersInString(string expression, string pattern)
{
    var parameters = new List<GroupCollection>();
    var matches = Regex.Matches(expression, pattern);

    foreach (Match match in matches)
        parameters.Add(match.Groups);

    return parameters;
}

2.2 moved the attribute check to a local function to make it easier to read
C#:
(bool, string) ValidateParameters(IReadOnlyList<GroupCollection> groupCollections)
{
    for (var i = 0; i < groupCollections.Count; i++)
    {
        var @group = groupCollections[i];
        for (var j = 1; j < @group.Count; j++)
        {
            string parameter = @group[j].Value;
            if (parameter != Name)
                continue;
         
            var errorMessage =
                $"Expression Error: The attribute '{Name}' on the object '{GetAttachedObject()}' cannot reference itself because it will cause an infinit loop";
            Debug.LogWarning(errorMessage);

            return (false, errorMessage);
        }
    }

    return (true, null);
}

3. First of all, I search and replace in the string those found according to my pattern (ItemDefenitionAttributeName)[AttributeName], so that further search for attributes [AttributeName] works correctly

4. The function of replacing the attributes of the found items definitions, this is a slightly modified RegexReplaceAttributesFromString

C#:
 public (bool, string) RegexReplaceAttributesOtherItemDefinitionsFromString(string expression,
            IReadOnlyList<GroupCollection> matchedGroups, AttributeCollection attributeCollection,
            IReadOnlyDictionary<string, AttributeBase> otherAttributes, out string newStringExpression)
        {
            newStringExpression = expression;
            var message = "";

            if (attributeCollection == null)
            {
                var errorMessage = "Expression Error: Missing attribute Collection.";
                Debug.LogWarning(errorMessage);
                return (false, errorMessage);
            }

            for (var i = 0; i < matchedGroups.Count; i++)
            {
                var matchedGroup = matchedGroups[i];
                string itemDefinitionAttributeName = matchedGroup[1].Value;
                string parameter = matchedGroup[2].Value;

                AttributeBase itemDefinitionAttribute =
                    GetAttributeBase(attributeCollection, otherAttributes, itemDefinitionAttributeName);
                if (itemDefinitionAttribute == null)
                    continue;

                var attachedItemDefinition = itemDefinitionAttribute.GetValueAsObject() as ItemDefinition;
                if (attachedItemDefinition == null)
                    continue;

                AttributeBase parameterAttribute =
                    itemDefinitionAttribute.GetAttributeBase(attachedItemDefinition.ItemDefinitionAttributeCollection,
                        otherAttributes, parameter);
                if (parameterAttribute == null)
                    continue;

                var pattern = $@"\({itemDefinitionAttributeName}\)\[{parameter}\]";
                var attributeValue = parameterAttribute.GetValueAsObject();
                string attributeValueText = attributeValue != null ? ModifyToString(attributeValue) : "";

                newStringExpression = Regex.Replace(newStringExpression, pattern, attributeValueText);
            }

            return string.IsNullOrWhiteSpace(message) == false
                ? (false, message)
                : (true, null);
        }

5. The search for AttributeBase has been moved to the public GetAttributeBase method. Thus, you can get the value on the found ItemDefinition of the value of the searched parameter.

C#:
public AttributeBase GetAttributeBase(AttributeCollection attributeCollection,
            IReadOnlyDictionary<string, AttributeBase> otherAttributes, string parameter)
        {
            AttributeBase attribute = null;
            if (attributeCollection.AttachedItem != null &&
                attributeCollection.AttachedItem.TryGetAttribute(parameter, out attribute))
            {
            }
            else if (
                attributeCollection.AttachedItemDefinition != null &&
                attributeCollection.AttachedItemDefinition.TryGetAttribute(parameter, out attribute))
            {
            }
            else if (
                attributeCollection.AttachedItemCategory != null)
            {
                if (attributeCollection.AttachedItemCategory.TryGetCategoryAttribute(parameter, out attribute))
                {
                }
                else if
                (attributeCollection.AttachedItemCategory.TryGetDefinitionAttribute(parameter,
                    out attribute))
                {
                }
                else if (attributeCollection.AttachedItemCategory
                    .TryGetItemAttribute(parameter, out attribute))
                {
                }
            }
            else if (otherAttributes != null && otherAttributes.TryGetValue(parameter, out attribute))
            {
            }

            return attribute;
        }
 
Last edited:
6. Yes, and add a check, if the pattern is found, but there is no item definition there, then overwrite "0"


C#:
var valueAsObject = itemDefinitionAttribute?.GetValueAsObject();
if (valueAsObject == null)
{
    newStringExpression = Regex.Replace(newStringExpression, pattern, "0");
    continue;
}

1647451230948.png
1647451248813.png
 
Last edited:
Looks like a good solution. Thank you for sharing.

I'm not sure I will add it in the source code though, I'm slightly concerned about infinite loops with modify attributes which can corrupt database if you are not careful.

In the future I would like to implement a solution for modify experssions that's a lot more flexible than the current one. Perhaps by adding an option to specify a script that runs instead of the default modify expression parser such that you can define your own custom functions in code giving you more control on how to parse the expression without having to change the source code.

But that would probably require a big breaking refactor so it would need to wait for a version 2.0... and for now this isn't something we have planned.
 
Thanks. I understand that this is not task number 1, but what you wrote about flexibility sounds cool. At this stage, I think that such a solution is still suitable for us, you can always write a certain handler and create your own methods for calculating attributes in it without changing AttibuteBase.

I noticed that with Modify Syntax you have to be careful. If something is wrong, then the final result will not count.
 
@@Sangemdoko It turns out that when adding a copy of a rifle to the inventory, the Modify Value field is not transferred, the value is calculated and entered into Inherit Value?
Yes that's how it works.
Don't worry the inheriting is smart. It should know that it is inheriting the modify experssion not the value. So if you change the StickEquippedModule it should work as expected.

Do let me know if that's not the case
 
Yes that's how it works.
Don't worry the inheriting is smart. It should know that it is inheriting the modify experssion not the value. So if you change the StickEquippedModule it should work as expected.

Do let me know if that's not the case
Everything is right, it works, right. I just need a syntax line for a new item in the Modify field in order to change its attributes
 
Top