Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source/Parser/AchievementScriptInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ internal static InterpreterScope GetGlobalScope()
_globalScope.AddFunction(new FlagConditionFunction("trigger_when", RequirementType.Trigger));
_globalScope.AddFunction(new MeasuredFunction());
_globalScope.AddFunction(new DisableWhenFunction());
_globalScope.AddFunction(new RememberedFunction());

_globalScope.AddFunction(new AchievementSetFunction());
_globalScope.AddFunction(new AchievementFunction());
Expand Down
18 changes: 18 additions & 0 deletions Source/Parser/Expressions/Trigger/MemoryValueExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,18 @@ public ErrorExpression BuildTrigger(TriggerBuilderContext context, ExpressionBas
memoryAccessors.Add(last);
}

// if there's a constant modifier that can be moved to the final clause, do so.
appendConstantAccessor = false;
if (constantAccessor != null && context.CanModifyOperator && context.MinimumVersion >= Data.Version._1_3_1)
{
var last = memoryAccessors.Last();
if (last.ModifyingOperator == RequirementOperator.None)
{
memoryAccessors.Remove(constantAccessor);
appendConstantAccessor = true;
}
}

// output the accessor chain
var constantAccessorIndex = -1;
foreach (var accessor in memoryAccessors)
Expand Down Expand Up @@ -1651,6 +1663,12 @@ public ErrorExpression BuildTrigger(TriggerBuilderContext context, ExpressionBas
}
}

if (appendConstantAccessor)
{
context.LastRequirement.Operator = (constantAccessor.CombiningOperator == RequirementType.SubSource) ? RequirementOperator.Subtract : RequirementOperator.Add;
context.LastRequirement.Right = constantAccessor.MemoryAccessor.Field;
}

// the last item will be flagged as an AddSource (or None if 0 was appended)
// make sure it's None before leaving.
context.LastRequirement.Type = RequirementType.None;
Expand Down
12 changes: 11 additions & 1 deletion Source/Parser/Expressions/Trigger/RememberRecallExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public static RememberRecallExpression WrapInRemember(ExpressionBase expression)
if (modifiedMemoryAccessor != null)
return new RememberRecallExpression(modifiedMemoryAccessor);

var memoryAccessor = expression as MemoryAccessorExpression;
if (memoryAccessor != null && memoryAccessor.HasPointerChain)
{
memoryValue = new MemoryValueExpression(new ModifiedMemoryAccessorExpression(memoryAccessor));
return new RememberRecallExpression(memoryValue);
}

return null;
}

Expand Down Expand Up @@ -114,7 +121,10 @@ public override ErrorExpression BuildTrigger(TriggerBuilderContext context)

private ErrorExpression AppendRemember(TriggerBuilderContext context)
{
RememberedValue.BuildTrigger(context);
var rememberContext = context.Clone();
rememberContext.CanModifyOperator = true;

RememberedValue.BuildTrigger(rememberContext);
context.RememberedValue = RememberedValue;

// if the RememberedValue only contains ModifiedMemoryReferences, BuildTrigger
Expand Down
25 changes: 15 additions & 10 deletions Source/Parser/Functions/MemoryAccessorFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,23 @@ private static MemoryAccessorExpression CreateMemoryAccessorExpression(ModifiedM
return null;
}

var result = new MemoryAccessorExpression();
var result = new MemoryAccessorExpression
{
Field = new Field { Type = FieldType.MemoryAddress, Size = size, Value = offset },
};

if (modifiedMemoryAccessor.ModifyingOperator == RequirementOperator.None)
{
var rememberedPointer = modifiedMemoryAccessor.MemoryAccessor as RememberRecallExpression;
if (rememberedPointer != null)
{
result.RememberPointer = rememberedPointer;
return result;
}
}

var requirements = new List<Requirement>();
var context = new TriggerBuilderContext();
context.Trigger = requirements;
var context = new TriggerBuilderContext { Trigger = requirements };
modifiedMemoryAccessor.BuildTrigger(context);
requirements.Last().Type = RequirementType.AddAddress;

Expand Down Expand Up @@ -207,13 +219,6 @@ private static MemoryAccessorExpression CreateMemoryAccessorExpression(ModifiedM
foreach (var requirement in requirements)
result.AddPointer(requirement);

result.Field = new Field
{
Type = FieldType.MemoryAddress,
Size = size,
Value = offset
};

return result;
}
}
Expand Down
33 changes: 33 additions & 0 deletions Source/Parser/Functions/RememberedFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using RATools.Parser.Expressions;
using RATools.Parser.Expressions.Trigger;
using RATools.Parser.Internal;

namespace RATools.Parser.Functions
{
internal class RememberedFunction : FunctionDefinitionExpression
{
public RememberedFunction()
: base("remembered")
{
Parameters.Add(new VariableDefinitionExpression("accessor"));
}

public override bool Evaluate(InterpreterScope scope, out ExpressionBase result)
{
var accessor = GetParameter(scope, "accessor", out result);
if (accessor == null)
return false;

if (!accessor.ReplaceVariables(scope, out result))
return false;

var remembered = RememberRecallExpression.WrapInRemember(result);
if (remembered != null)
result = remembered;

CopyLocation(result, scope);
result.MakeReadOnly();
return true;
}
}
}
6 changes: 6 additions & 0 deletions Source/Parser/Internal/TriggerBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public Requirement LastRequirement
/// </summary>
public bool CanModifyComparison { get; set; }

/// <summary>
/// If set to <c>true</c>, the operator and right operand can be set.
/// Otherwise, they should be left alone so the caller can set them.
/// </summary>
public bool CanModifyOperator { get; set; }

public virtual TriggerBuilderContext Clone()
{
var clone = new TriggerBuilderContext();
Expand Down
9 changes: 5 additions & 4 deletions Tests/Parser/Expressions/Trigger/TriggerExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ public static void AssertBuildValueError(string input, string message)
ExpressionTests.AssertError(error, message);
}

public static string Serialize(ITriggerExpression expression)
public static string Serialize(ITriggerExpression expression, TriggerBuilderContext context = null)
{
var requirements = new List<Requirement>();
var context = new TriggerBuilderContext() { Trigger = requirements };
context ??= new TriggerBuilderContext();
context.Trigger = requirements;

var result = expression.BuildTrigger(context);
Assert.That(result, Is.Null);
Expand All @@ -110,9 +111,9 @@ public static string Serialize(ITriggerExpression expression)
return builder.SerializeRequirements(new SerializationContext());
}

public static void AssertSerialize(ITriggerExpression expression, string expected)
public static void AssertSerialize(ITriggerExpression expression, string expected, TriggerBuilderContext context = null)
{
Assert.That(Serialize(expression), Is.EqualTo(expected));
Assert.That(Serialize(expression, context), Is.EqualTo(expected));
}

public static string SerializeValue(ITriggerExpression expression)
Expand Down
107 changes: 107 additions & 0 deletions Tests/Parser/Functions/RememberedFunctionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using NUnit.Framework;
using RATools.Data;
using RATools.Parser.Expressions;
using RATools.Parser.Functions;
using RATools.Parser.Internal;
using RATools.Parser.Tests.Expressions;
using RATools.Parser.Tests.Expressions.Trigger;
using System.Collections.Generic;
using System.Linq;

namespace RATools.Parser.Tests.Functions
{
[TestFixture]
class RememberedFunctionTests
{
[Test]
public void TestDefinition()
{
var def = new RememberedFunction();
Assert.That(def.Name.Name, Is.EqualTo("remembered"));
Assert.That(def.Parameters.Count, Is.EqualTo(1));
Assert.That(def.Parameters.ElementAt(0).Name, Is.EqualTo("accessor"));
}

[Test]
// Constants and comparisons can't be remembered
[TestCase("1", "1")]
[TestCase("always_false()", "0=1")]
[TestCase("byte(0x1234) == 6", "0xH001234=6")]
// Unmodified memory accessors doesn't need to be remembered
[TestCase("byte(0x1234)", "0xH001234")]
// Modified memory accessors and pointed-at values can be remembered.
// NOTE: The output of the serializer is the {recall} field, but the remember chain
// will also be output if it hasn't already.
[TestCase("byte(0x1234) * 2", "K:0xH001234*2_{recall}")]
[TestCase("word(0x1234) + 2", "A:2_K:0x 001234_{recall}")]
[TestCase("word(0x1234) - 2", "B:2_K:0x 001234_{recall}")]
[TestCase("dword(dword(0x1234))", "I:0xX001234_K:0xX000000_{recall}")]
[TestCase("remembered(dword(0x1234)+8)*2", "A:8_K:0xX001234_K:{recall}*2_{recall}")]
public void TestRemember(string input, string expected)
{
var expr = TriggerExpressionTests.Parse("remembered(" + input + ")");

var error = expr as ErrorExpression;
if (error != null)
{
ExpressionTests.AssertError(error, expected);
}
else
{
var triggerExpr = expr as ITriggerExpression;
if (triggerExpr != null)
TriggerExpressionTests.AssertSerialize(triggerExpr, expected);
else
ExpressionTests.AssertAppendString(expr, expected);
}
}

[Test]
[TestCase("word(0x1234) + 2", "K:0x 001234+2_{recall}")]
[TestCase("word(0x1234) - 2", "K:0x 001234-2_{recall}")]
public void TestRememberSimplifiedAddition(string input, string expected)
{
var expr = TriggerExpressionTests.Parse("remembered(" + input + ")");

var error = expr as ErrorExpression;
if (error != null)
{
ExpressionTests.AssertError(error, expected);
}
else
{
var triggerExpr = expr as ITriggerExpression;
if (triggerExpr != null)
TriggerExpressionTests.AssertSerialize(triggerExpr, expected, new TriggerBuilderContext { MinimumVersion = Data.Version._1_3_1 });
else
ExpressionTests.AssertAppendString(expr, expected);
}
}

[Test]
public void TestScriptInline()
{
var parser = AchievementScriptTests.Parse(
"achievement(\"t\", \"d\", 5, byte(remembered(dword(dword(0x1234) + 8)) + 4) > 5 && " +
"word(remembered(dword(dword(0x1234) + 8)) + 8) == 0)");
Assert.That(parser.Achievements.Count(), Is.EqualTo(1));

var achievement = parser.Achievements.First();
var serialized = achievement.Trigger.Serialize(new SerializationContext());
Assert.That(serialized, Is.EqualTo("I:0xX001234_K:0xX000008_I:{recall}_0xH000004>5_I:{recall}_0x 000008=0"));
}

[Test]
public void TestScriptVariable()
{
var parser = AchievementScriptTests.Parse(
"ptr = remembered(dword(dword(0x1234) + 8))\n" +
"achievement(\"t\", \"d\", 5, byte(ptr + 4) > 5 && word(ptr + 8) == 0)");
Assert.That(parser.Achievements.Count(), Is.EqualTo(1));

var achievement = parser.Achievements.First();
var serialized = achievement.Trigger.Serialize(new SerializationContext());
Assert.That(serialized, Is.EqualTo("I:0xX001234_K:0xX000008_I:{recall}_0xH000004>5_I:{recall}_0x 000008=0"));
}
}
}
Loading