diff --git a/Source/Data/Requirement.cs b/Source/Data/Requirement.cs index 6206b095..b4ff2424 100644 --- a/Source/Data/Requirement.cs +++ b/Source/Data/Requirement.cs @@ -65,6 +65,18 @@ public bool IsMeasured } } + /// + /// Gets whether or not the requirement references a remembered value. + /// + public bool HasRecall + { + get + { + return Left.Type == FieldType.Recall || Right.Type == FieldType.Recall; + } + } + + /// /// Gets or sets the requirement operator. /// diff --git a/Source/Data/RequirementEx.cs b/Source/Data/RequirementEx.cs index de197b92..2968829d 100644 --- a/Source/Data/RequirementEx.cs +++ b/Source/Data/RequirementEx.cs @@ -83,6 +83,20 @@ public bool HasHitCount } } + public bool HasRecall + { + get + { + foreach (var requirement in Requirements) + { + if (requirement.HasRecall) + return true; + } + + return false; + } + } + public override string ToString() { if (Requirements.Count == 1) diff --git a/Source/Parser/Internal/RequirementsOptimizer.cs b/Source/Parser/Internal/RequirementsOptimizer.cs index f7e9f15f..b9d73cbc 100644 --- a/Source/Parser/Internal/RequirementsOptimizer.cs +++ b/Source/Parser/Internal/RequirementsOptimizer.cs @@ -1,6 +1,7 @@ using RATools.Data; using RATools.Parser.Expressions; using RATools.Parser.Functions; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -569,6 +570,34 @@ private static void PromoteCommonAltsToCore(List> groups, Li } } + // Remember and Recall cannot be separated. If any Recall items are being promoted, make sure + // the Remember and all other Recalls are also being promoted. + Predicate hasRememberOrRecall = r => r.Requirements.Any(r2 => r2.Type == RequirementType.Remember || r2.HasRecall); + if (requirementsFoundInAll.Any(r => hasRememberOrRecall(r))) + { + for (int i = 1; i < groups.Count; i++) + { + bool allPromoted = true; + foreach (var requirementEx in groups[i]) + { + if (hasRememberOrRecall(requirementEx)) + { + if (!requirementsFoundInAll.Contains(requirementEx)) + { + allPromoted = false; + break; + } + } + } + + if (!allPromoted) + { + requirementsFoundInAll.RemoveAll(r => hasRememberOrRecall(r)); + break; + } + } + } + // remove the redundant requirements from each alt group if (requirementsFoundInAll.Any()) { @@ -1395,7 +1424,7 @@ private static RequirementEx GetRemembered(List requirements) break; j--; - if (requirements[j].Left.Type == FieldType.Recall || requirements[j].Right.Type == FieldType.Recall) + if (requirements[j].HasRecall) hasRecall = true; } @@ -1458,11 +1487,8 @@ internal static int FindResetNextIfStart(IList requirements, int re // these have higher precedence than ResetNextIf, drag them with i--; - if (requirements[i].Left.Type == FieldType.Recall || - requirements[i].Right.Type == FieldType.Recall) - { + if (requirements[i].HasRecall) hasRecall = true; - } continue; diff --git a/Source/Parser/ScriptBuilderContext.cs b/Source/Parser/ScriptBuilderContext.cs index 9bf3d4ec..dcc13583 100644 --- a/Source/Parser/ScriptBuilderContext.cs +++ b/Source/Parser/ScriptBuilderContext.cs @@ -1,6 +1,7 @@ using RATools.Data; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; @@ -52,6 +53,7 @@ public ScriptBuilderContext Clone() private Requirement _lastAndNext; private int _remainingWidth; + [DebuggerDisplay("{Requirement}")] class MemoryAccessorAliasChain { public MemoryAccessorAlias Alias { get; set; } @@ -154,7 +156,7 @@ public void AppendRequirements(StringBuilder builder, IEnumerable r if (group.Requirements.Any(r => r.Type == RequirementType.Remember)) break; - if (group.Requirements.Any(r => r.Left.Type == FieldType.Recall || r.Right.Type == FieldType.Recall)) + if (group.HasRecall) { // found a Recall without a Remember, it will use the last non-pause Remember hasNonPauseRecallBeforeLastPauseRemember = true; @@ -253,7 +255,6 @@ private void AppendRequirementEx(StringBuilder builder, RequirementEx requiremen Next = null, Requirement = requirement, }; - continue; } else if (requirement.Left.IsMemoryReference) { @@ -850,7 +851,17 @@ private void AppendFieldAlias(StringBuilder builder, Field field, MemoryAccessor builder.Append('~'); break; case FieldType.Recall: - builder.Append(_remember); + if (parentRequirement.Operator != RequirementOperator.None) + { + builder.Append('('); + builder.Append(_remember); + builder.Append(')'); + AppendFieldModifier(builder, parentRequirement); + } + else + { + builder.Append(_remember); + } return; } diff --git a/Tests/Parser/Internal/RequirementsOptimizerTests.cs b/Tests/Parser/Internal/RequirementsOptimizerTests.cs index 2abd3ba8..f015742f 100644 --- a/Tests/Parser/Internal/RequirementsOptimizerTests.cs +++ b/Tests/Parser/Internal/RequirementsOptimizerTests.cs @@ -225,6 +225,21 @@ public void TestOptimizeNormalizeResetIfsAndPauseIfs(string input, string expect "once(byte(0x001234) == 1) && ((once(byte(0x002345) == 2) && unless(byte(0x002345) == 1)))")] [TestCase("once(byte(0x001234) == 1) && unless(once(byte(0x001234) == 1)) && (always_false() || never(byte(0x002345) == 1))", // never should not be promoted to core containing unless "once(byte(0x001234) == 1) && disable_when(byte(0x001234) == 1) && (never(byte(0x002345) == 1))")] + [TestCase("1 == 1 && (byte((word(0x001234) & 0x3FF) * 4 + 8) == 6 || byte((word(0x001234) & 0x3FF) * 4 + 16) == 8)", // common remember should not be promoted to core without all recall references. + "(byte((word(0x001234) & 0x000003FF) * 4 + 0x08) == 6) || (byte((word(0x001234) & 0x000003FF) * 4 + 0x10) == 8)")] + [TestCase("1 == 1 && (byte((word(0x001234) & 0x3FF) * 4 + 8) == 6 || byte((word(0x002345) & 0x3FF) * 4 + 8) == 6)", // common remember should not be promoted to core without all recall references. + "(byte((word(0x001234) & 0x000003FF) * 4 + 0x08) == 6) || (byte((word(0x002345) & 0x000003FF) * 4 + 0x08) == 6)")] + [TestCase("1 == 1 && ((byte((word(0x001234) & 0x3FF) * 4 + 8) == 6 && prev(byte((word(0x001234) & 0x3FF) * 4 + 8)) == 5) || " + + "(byte((word(0x002345) & 0x3FF) * 4 + 8) == 6 && prev(byte((word(0x002345) & 0x3FF) * 4 + 8)) == 5))", // common remember should not be promoted to core without all recall references. + "(byte((word(0x001234) & 0x000003FF) * 4 + 0x08) == 6 && prev(byte((word(0x001234) & 0x000003FF) * 4 + 0x08)) == 5) || " + + "(byte((word(0x002345) & 0x000003FF) * 4 + 0x08) == 6 && prev(byte((word(0x002345) & 0x000003FF) * 4 + 0x08)) == 5)")] + [TestCase("1 == 1 && ((byte(remembered((word(0x001234) & 0x3FF) * 4) + 8) == 6 && prev(byte(remembered((word(0x001234) & 0x3FF) * 4) + 8)) == 5) || " + + "(byte(remembered((word(0x002345) & 0x3FF) * 4) + 8) == 6 && prev(byte(remembered((word(0x002345) & 0x3FF) * 4) + 8)) == 5))", // common remember should not be promoted to core without all recall references. + "(byte((word(0x001234) & 0x000003FF) * 4 + 0x08) == 6 && prev(byte((word(0x001234) & 0x000003FF) * 4 + 0x08)) == 5) || " + + "(byte((word(0x002345) & 0x000003FF) * 4 + 0x08) == 6 && prev(byte((word(0x002345) & 0x000003FF) * 4 + 0x08)) == 5)")] + [TestCase("1 == 1 && ((byte(remembered((word(0x001234) & 0x3FF) * 4) + 8) == 6 && prev(byte(remembered((word(0x001234) & 0x3FF) * 4) + 8)) == 5) || " + + "(byte(remembered((word(0x001234) & 0x3FF) * 4) + 8) == 6 && prev(byte(remembered((word(0x001234) & 0x3FF) * 4) + 8)) == 5))", // common remember can be promoted to core with all recall references. + "byte((word(0x001234) & 0x000003FF) * 4 + 0x08) == 6 && prev(byte((word(0x001234) & 0x000003FF) * 4 + 0x08)) == 5")] public void TestOptimizePromoteCommonAltsToCore(string input, string expected) { var achievement = CreateAchievement(input); diff --git a/Tests/Parser/Internal/ScriptBuilderContextTests.cs b/Tests/Parser/Internal/ScriptBuilderContextTests.cs index a77685fb..05c09eb4 100644 --- a/Tests/Parser/Internal/ScriptBuilderContextTests.cs +++ b/Tests/Parser/Internal/ScriptBuilderContextTests.cs @@ -69,6 +69,7 @@ class ScriptBuilderContextTests "((once(word(0x000002) == 2) && word(0x000003) == 3) || word(0x000004) == 4) && never(word(0x000001) == 1)")] [TestCase("I:0xX001234_A:0xH000001/0xH000001_0=1", "(byte(dword(0x001234) + 0x01) / byte(dword(0x001234) + 0x01)) == 1")] + [TestCase("K:0x 001234&1023_I:{recall}*4_0xH000008=6", "byte((word(0x001234) & 0x3FF) * 4 + 0x08) == 6")] public void TestAppendRequirements(string input, string expected) { var trigger = Trigger.Deserialize(input);