public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var count = (IntegerConstantExpression)functionCall.Parameters.First(); var comparison = functionCall.Parameters.ElementAt(1); return(BuildTriggerConditions(context, scope, comparison, count.Value)); }
public override bool BuildMacro(RichPresenceDisplayFunction.RichPresenceDisplayContext context, InterpreterScope scope, out ExpressionBase result) { var macro = GetStringParameter(scope, "macro", out result); if (macro == null) { return(false); } var expression = GetParameter(scope, "expression", out result); if (expression == null) { return(false); } var value = TriggerBuilderContext.GetValueString(expression, scope, out result); if (value == null) { return(false); } var functionCall = scope.GetContext <FunctionCallExpression>(); var valueFormat = GetValueFormat(macro.Value); context.RichPresence.AddValueField(functionCall, macro.Value, valueFormat); result = new StringConstantExpression(String.Format("@{0}({1})", macro.Value, value)); return(true); }
public void TestGetConditionString(string input, string expected) { ExpressionBase error; InterpreterScope scope = new InterpreterScope(AchievementScriptInterpreter.GetGlobalScope()); scope.Context = new TriggerBuilderContext(); var expression = Parse(input); ExpressionBase processed; Assert.That(expression.ReplaceVariables(scope, out processed), Is.True); var result = TriggerBuilderContext.GetConditionString(processed, scope, out error); if (error != null) { Assert.That(((ParseErrorExpression)error).InnermostError.Message, Is.EqualTo(expected)); } else { Assert.That(error, Is.Null); Assert.That(result, Is.EqualTo(expected)); } }
private List <Requirement> Evaluate(string input, string expectedError = null) { var requirements = new List <Requirement>(); var funcDef = new RepeatedFunction(); var expression = ExpressionBase.Parse(new PositionalTokenizer(Tokenizer.CreateTokenizer(input))); Assert.That(expression, Is.InstanceOf <FunctionCallExpression>()); var funcCall = (FunctionCallExpression)expression; ExpressionBase error; var scope = funcCall.GetParameters(funcDef, AchievementScriptInterpreter.GetGlobalScope(), out error); var context = new TriggerBuilderContext { Trigger = requirements }; scope.Context = context; ExpressionBase evaluated; Assert.That(funcDef.ReplaceVariables(scope, out evaluated), Is.True); if (expectedError == null) { Assert.That(funcDef.BuildTrigger(context, scope, funcCall), Is.Null); } else { var parseError = funcDef.BuildTrigger(context, scope, funcCall); Assert.That(parseError, Is.Not.Null); Assert.That(parseError.Message, Is.EqualTo(expectedError)); } return(requirements); }
public void TestFunctionReference() { string input = "once(f)"; var requirements = new List <Requirement>(); var funcDef = new OnceFunction(); var expression = ExpressionBase.Parse(new PositionalTokenizer(Tokenizer.CreateTokenizer(input))); Assert.That(expression, Is.InstanceOf <FunctionCallExpression>()); var funcCall = (FunctionCallExpression)expression; var scope = new InterpreterScope(AchievementScriptInterpreter.GetGlobalScope()); scope.AssignVariable(new VariableExpression("f"), new FunctionReferenceExpression("f2")); ExpressionBase error; scope = funcCall.GetParameters(funcDef, scope, out error); var context = new TriggerBuilderContext { Trigger = requirements }; scope.Context = context; ExpressionBase evaluated; Assert.That(funcDef.ReplaceVariables(scope, out evaluated), Is.True); funcCall = evaluated as FunctionCallExpression; var parseError = funcDef.BuildTrigger(context, scope, funcCall); Assert.That(parseError, Is.Not.Null); Assert.That(parseError.InnermostError.Message, Is.EqualTo("Function used like a variable")); }
private string ProcessValue(InterpreterScope scope, string parameter, out ExpressionBase result) { var expression = GetParameter(scope, parameter, out result); if (expression == null) { return(null); } var functionCallExpression = expression as FunctionCallExpression; if (functionCallExpression != null) { var functionDefinition = scope.GetFunction(functionCallExpression.FunctionName.Name); if (functionDefinition is MaxOfFunction) { var builder = new StringBuilder(); foreach (var value in functionCallExpression.Parameters) { if (builder.Length > 0) { builder.Append('$'); } builder.Append(TriggerBuilderContext.GetValueString(value, scope, out result)); } return(builder.ToString()); } } return(TriggerBuilderContext.GetValueString(expression, scope, out result)); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { // add another TriggerBuilderContext scope to prevent optimizing the expression at this time var nestedScope = new InterpreterScope(scope) { Context = new TriggerBuilderContext() }; return(base.BuildTrigger(context, nestedScope, functionCall)); }
private string ProcessTrigger(InterpreterScope scope, string parameter, out ExpressionBase result) { var expression = GetParameter(scope, parameter, out result); if (expression == null) { return(null); } return(TriggerBuilderContext.GetConditionString(expression, scope, out result)); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var requirement = new Requirement(); var address = (IntegerConstantExpression)functionCall.Parameters.First(); requirement.Left = new Field { Size = _size, Type = FieldType.MemoryAddress, Value = (uint)address.Value }; context.Trigger.Add(requirement); return(null); }
public void TestGetValueString(string input, string expected) { ExpressionBase error; InterpreterScope scope = new InterpreterScope(AchievementScriptInterpreter.GetGlobalScope()); scope.Context = new TriggerBuilderContext(); var expression = Parse(input); var result = TriggerBuilderContext.GetValueString(expression, scope, out error); Assert.That(error, Is.Null); Assert.That(result, Is.EqualTo(expected)); }
public override bool BuildMacro(RichPresenceDisplayFunction.RichPresenceDisplayContext context, InterpreterScope scope, out ExpressionBase result) { var name = GetStringParameter(scope, "name", out result); if (name == null) { return(false); } var expression = GetParameter(scope, "expression", out result); if (expression == null) { return(false); } var dictionary = GetDictionaryParameter(scope, "dictionary", out result); if (dictionary == null) { return(false); } var fallback = GetStringParameter(scope, "fallback", out result); if (fallback == null) { return(false); } var value = TriggerBuilderContext.GetValueString(expression, scope, out result); if (value == null) { return(false); } var functionCall = scope.GetContext <FunctionCallExpression>(); var error = context.RichPresence.AddLookupField(functionCall, name.Value, dictionary, fallback); if (error != null) { result = error; return(false); } result = new StringConstantExpression(String.Format("@{0}({1})", name.Value, value)); return(true); }
protected ParseErrorExpression BuildTriggerCondition(TriggerBuilderContext context, InterpreterScope scope, ExpressionBase condition) { var builder = new ScriptInterpreterAchievementBuilder(); ExpressionBase result; if (!TriggerBuilderContext.ProcessAchievementConditions(builder, condition, scope, out result)) { return((ParseErrorExpression)result); } if (builder.AlternateRequirements.Count > 0) { return(new ParseErrorExpression(Name.Name + " does not support ||'d conditions", condition)); } if (builder.CoreRequirements.Count > 1) { var last = builder.CoreRequirements.Last(); foreach (var requirement in builder.CoreRequirements) { if (requirement.Type == RequirementType.None && !ReferenceEquals(requirement, last)) { requirement.Type = RequirementType.AndNext; } } } ParseErrorExpression error = ValidateSingleCondition(builder.CoreRequirements); if (error != null) { return(new ParseErrorExpression(error, condition)); } error = ModifyRequirements(builder); if (error != null) { return(error); } foreach (var requirement in builder.CoreRequirements) { context.Trigger.Add(requirement); } return(null); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var accessor = (FunctionCallExpression)functionCall.Parameters.First(); var error = context.CallFunction(accessor, scope); if (error != null) { return(error); } var left = context.LastRequirement.Left; context.LastRequirement.Left = new Field { Size = left.Size, Type = _fieldType, Value = left.Value }; return(null); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var address = ((IntegerConstantExpression)functionCall.Parameters.ElementAt(1)).Value; var index = ((IntegerConstantExpression)functionCall.Parameters.First()).Value; if (index < 0 || index > 31) { return(new ParseErrorExpression("index must be between 0 and 31", functionCall.Parameters.First())); } address += index / 8; index %= 8; FieldSize size; switch (index) { default: case 0: size = FieldSize.Bit0; break; case 1: size = FieldSize.Bit1; break; case 2: size = FieldSize.Bit2; break; case 3: size = FieldSize.Bit3; break; case 4: size = FieldSize.Bit4; break; case 5: size = FieldSize.Bit5; break; case 6: size = FieldSize.Bit6; break; case 7: size = FieldSize.Bit7; break; } var requirement = new Requirement(); requirement.Left = new Field { Size = size, Type = FieldType.MemoryAddress, Value = (uint)address }; context.Trigger.Add(requirement); return(null); }
protected override bool SetDisplayString(RichPresenceBuilder richPresence, string displayString, InterpreterScope scope, out ExpressionBase result) { var expression = GetParameter(scope, "condition", out result); if (expression == null) { return(false); } var condition = TriggerBuilderContext.GetConditionString(expression, scope, out result); if (condition == null) { return(false); } richPresence.AddConditionalDisplayString(condition, displayString); return(true); }
protected ParseErrorExpression BuildTriggerCondition(TriggerBuilderContext context, InterpreterScope scope, ExpressionBase condition) { var builder = new ScriptInterpreterAchievementBuilder(); ExpressionBase result; if (!TriggerBuilderContext.ProcessAchievementConditions(builder, condition, scope, out result)) { switch (condition.Type) { case ExpressionType.Conditional: case ExpressionType.Comparison: // allowed constructs should only report the inner error return((ParseErrorExpression)result); default: // non-allowed construct return(new ParseErrorExpression("comparison did not evaluate to a valid comparison", condition) { InnerError = (ParseErrorExpression)result }); } } var error = builder.CollapseForSubClause(); if (error != null) { return(new ParseErrorExpression(error.Message, condition)); } error = ModifyRequirements(builder); if (error != null) { return(new ParseErrorExpression(error.Message, condition)); } foreach (var requirement in builder.CoreRequirements) { context.Trigger.Add(requirement); } return(null); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var accessor = (FunctionCallExpression)functionCall.Parameters.First(); var error = context.CallFunction(accessor, scope); if (error != null) { return(error); } var left = context.LastRequirement.Left; if (left.Type != FieldType.MemoryAddress) { return(new ParseErrorExpression("cannot apply multiple modifiers to memory accessor", functionCall)); } context.LastRequirement.Left = new Field { Size = left.Size, Type = _fieldType, Value = left.Value }; return(null); }
public override ParseErrorExpression BuildMacro(RichPresenceDisplayFunction.RichPresenceDisplayContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var name = (StringConstantExpression)functionCall.Parameters.First(); var expression = functionCall.Parameters.ElementAt(1); var dictionary = (DictionaryExpression)functionCall.Parameters.ElementAt(2); ExpressionBase result; var value = TriggerBuilderContext.GetValueString(expression, scope, out result); if (value == null) { return((ParseErrorExpression)result); } context.RichPresence.AddLookupField(name.Value, dictionary); context.DisplayString.Append('@'); context.DisplayString.Append(name.Value); context.DisplayString.Append('('); context.DisplayString.Append(value); context.DisplayString.Append(')'); return(null); }
protected ParseErrorExpression BuildTriggerConditions(TriggerBuilderContext context, InterpreterScope scope, ExpressionBase comparison, int count) { ParseErrorExpression error; var logicalComparison = comparison as ConditionalExpression; if (logicalComparison != null && logicalComparison.Operation == ConditionalOperation.Or) { error = EvaluateAddHits(context, scope, logicalComparison); } else { error = BuildTriggerCondition(context, scope, comparison); } if (error != null) { return(error); } context.LastRequirement.HitCount = (ushort)count; return(null); }
private ParseErrorExpression EvaluateAddHits(TriggerBuilderContext context, InterpreterScope scope, ConditionalExpression condition) { ExpressionBase result; var builder = new ScriptInterpreterAchievementBuilder(); builder.CoreRequirements.Add(new Requirement()); // empty core requirement required for optimize call, we'll ignore it if (!TriggerBuilderContext.ProcessAchievementConditions(builder, condition, scope, out result)) { return((ParseErrorExpression)result); } var requirements = new List <Requirement>(); foreach (var altGroup in builder.AlternateRequirements) { var error = ValidateSingleCondition(altGroup); if (error != null) { return(error); } var requirement = altGroup.First(); if (requirement.Type != RequirementType.None) { return(new ParseErrorExpression("modifier not allowed in multi-condition repeated clause")); } requirements.Add(requirement); } // the last item cannot have its own HitCount as it will hold the HitCount for the group. // if necessary, find one without a HitCount and make it the last. int index = requirements.Count - 1; if (requirements[index].HitCount > 0) { do { index--; } while (index >= 0 && requirements[index].HitCount > 0); if (index == -1) { // all requirements had HitCount limits, add a dummy item that's never true for the total HitCount requirements.Add(new Requirement { Left = new Field { Type = FieldType.Value, Value = 1 }, Operator = RequirementOperator.Equal, Right = new Field { Type = FieldType.Value, Value = 0 } }); } else { // found a requirement without a HitCount limit, move it to the last spot for the total HitCount var requirement = requirements[index]; requirements.RemoveAt(index); requirements.Add(requirement); } } // everything but the last becomes an AddHits for (int i = 0; i < requirements.Count - 1; i++) { requirements[i].Type = RequirementType.AddHits; } // load the requirements into the trigger foreach (var requirement in requirements) { context.Trigger.Add(requirement); } return(null); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var address = functionCall.Parameters.ElementAt(1); var result = BuildTrigger(context, scope, functionCall, address); if (result != null) { return(result); } var index = ((IntegerConstantExpression)functionCall.Parameters.First()).Value; if (index < 0 || index > 31) { return(new ParseErrorExpression("index must be between 0 and 31", functionCall.Parameters.First())); } var offset = (uint)index / 8; index %= 8; FieldSize size; switch (index) { default: case 0: size = FieldSize.Bit0; break; case 1: size = FieldSize.Bit1; break; case 2: size = FieldSize.Bit2; break; case 3: size = FieldSize.Bit3; break; case 4: size = FieldSize.Bit4; break; case 5: size = FieldSize.Bit5; break; case 6: size = FieldSize.Bit6; break; case 7: size = FieldSize.Bit7; break; } var lastRequirement = context.LastRequirement; if (lastRequirement.Left.IsMemoryReference) { lastRequirement.Left = new Field { Size = size, Type = lastRequirement.Left.Type, Value = lastRequirement.Left.Value + offset } } ; if (lastRequirement.Right.IsMemoryReference) { lastRequirement.Right = new Field { Size = size, Type = lastRequirement.Right.Type, Value = lastRequirement.Right.Value + offset } } ; return(null); } } }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var address = functionCall.Parameters.First(); return(BuildTrigger(context, scope, functionCall, address)); }
public override bool Evaluate(InterpreterScope scope, out ExpressionBase result) { var achievement = new ScriptInterpreterAchievementBuilder(); var stringExpression = GetStringParameter(scope, "title", out result); if (stringExpression == null) { return(false); } achievement.Title = stringExpression.Value; stringExpression = GetStringParameter(scope, "description", out result); if (stringExpression == null) { return(false); } achievement.Description = stringExpression.Value; stringExpression = GetStringParameter(scope, "badge", out result); if (stringExpression == null) { return(false); } achievement.BadgeName = stringExpression.Value; var integerExpression = GetIntegerParameter(scope, "points", out result); if (integerExpression == null) { return(false); } achievement.Points = integerExpression.Value; integerExpression = GetIntegerParameter(scope, "id", out result); if (integerExpression == null) { return(false); } achievement.Id = integerExpression.Value; var trigger = GetParameter(scope, "trigger", out result); if (trigger == null) { return(false); } if (!TriggerBuilderContext.ProcessAchievementConditions(achievement, trigger, scope, out result)) { if (result.Location.Start != trigger.Location.Start || result.Location.End != trigger.Location.End) { var error = (ParseErrorExpression)result; result = new ParseErrorExpression(error.Message, trigger) { InnerError = error }; } return(false); } var newAchievement = achievement.ToAchievement(); var functionCall = scope.GetOutermostContext <FunctionCallExpression>(); if (functionCall != null) { newAchievement.SourceLine = functionCall.Location.Start.Line; } var context = scope.GetContext <AchievementScriptContext>(); Debug.Assert(context != null); context.Achievements.Add(newAchievement); return(true); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var comparison = functionCall.Parameters.First(); return(BuildTriggerConditions(context, scope, comparison, 1)); }
protected ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall, ExpressionBase address) { var requirement = new Requirement(); var integerConstant = address as IntegerConstantExpression; if (integerConstant != null) { requirement.Left = new Field { Size = this.Size, Type = FieldType.MemoryAddress, Value = (uint)integerConstant.Value }; context.Trigger.Add(requirement); return(null); } IntegerConstantExpression offsetConstant = null; IntegerConstantExpression scalarConstant = null; RequirementOperator scalarOperation = RequirementOperator.None; var originalAddress = address; var funcCall = address as FunctionCallExpression; if (funcCall == null) { var mathematic = address as MathematicExpression; if (mathematic != null && (mathematic.Operation == MathematicOperation.Add || mathematic.Operation == MathematicOperation.Subtract)) { if (CountMathematicMemoryAccessors(mathematic, 2) >= 2) { return(new ParseErrorExpression("Cannot construct single address lookup from multiple memory references", address)); } offsetConstant = mathematic.Right as IntegerConstantExpression; if (offsetConstant != null) { address = mathematic.Left; } else { offsetConstant = mathematic.Left as IntegerConstantExpression; if (offsetConstant != null) { address = mathematic.Right; } } if (offsetConstant != null) { if (mathematic.Operation == MathematicOperation.Subtract) { offsetConstant = new IntegerConstantExpression(-offsetConstant.Value); } } mathematic = address as MathematicExpression; } if (mathematic != null) { switch (mathematic.Operation) { case MathematicOperation.Multiply: scalarConstant = mathematic.Right as IntegerConstantExpression; if (scalarConstant != null) { address = mathematic.Left; scalarOperation = RequirementOperator.Multiply; } else { scalarConstant = mathematic.Left as IntegerConstantExpression; if (scalarConstant != null) { scalarOperation = RequirementOperator.Multiply; address = mathematic.Right; } } break; case MathematicOperation.Divide: scalarConstant = mathematic.Right as IntegerConstantExpression; if (scalarConstant != null) { address = mathematic.Left; scalarOperation = RequirementOperator.Divide; } break; case MathematicOperation.BitwiseAnd: scalarConstant = mathematic.Right as IntegerConstantExpression; if (scalarConstant != null) { address = mathematic.Left; scalarOperation = RequirementOperator.BitwiseAnd; } break; } } funcCall = address as FunctionCallExpression; } if (funcCall != null) { var funcDef = scope.GetFunction(funcCall.FunctionName.Name) as TriggerBuilderContext.FunctionDefinition; if (funcDef != null) { if (funcDef is MemoryAccessorFunction || funcDef is PrevPriorFunction) { var error = funcDef.BuildTrigger(context, scope, funcCall); if (error != null) { return(error); } var lastRequirement = context.LastRequirement; lastRequirement.Type = RequirementType.AddAddress; if (scalarConstant != null && scalarConstant.Value != 1) { lastRequirement.Operator = scalarOperation; lastRequirement.Right = new Field { Size = FieldSize.DWord, Type = FieldType.Value, Value = (uint)scalarConstant.Value }; } // a memory reference without an offset has to be generated with a 0 offset. uint offset = (offsetConstant != null) ? (uint)offsetConstant.Value : 0; requirement.Left = new Field { Size = this.Size, Type = FieldType.MemoryAddress, Value = offset }; context.Trigger.Add(requirement); return(null); } } } var builder = new StringBuilder(); builder.Append("Cannot convert to an address: "); originalAddress.AppendString(builder); return(new ParseErrorExpression(builder.ToString(), address)); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { var error = base.BuildTrigger(context, scope, functionCall); if (error != null) { return(error); } ExpressionBase result; var format = functionCall.Parameters.ElementAt(2); if (!format.ReplaceVariables(scope, out result)) { return((ParseErrorExpression)result); } StringConstantExpression formatStr = result as StringConstantExpression; if (formatStr == null) { return(new ParseErrorExpression("format is not a string", format)); } if (formatStr.Value != "raw") { if (scope.GetContext <ValueBuilderContext>() != null) { return(new ParseErrorExpression("Value fields only support raw measured values", format)); } if (formatStr.Value == "percent") { context.LastRequirement.Type = RequirementType.MeasuredPercent; } else { return(new ParseErrorExpression("Unknown format: " + formatStr.Value, format)); } } var when = functionCall.Parameters.ElementAt(1); var builder = new ScriptInterpreterAchievementBuilder(); if (!TriggerBuilderContext.ProcessAchievementConditions(builder, when, scope, out result)) { return new ParseErrorExpression("when did not evaluate to a valid comparison", when) { InnerError = (ParseErrorExpression)result } } ; error = builder.CollapseForSubClause(); if (error != null) { return(error); } if (builder.CoreRequirements.Count != 1 || builder.CoreRequirements.First().Evaluate() != true) { foreach (var requirement in builder.CoreRequirements) { if (requirement.Type == RequirementType.None || requirement.Type == RequirementType.AndNext) { requirement.Type = RequirementType.MeasuredIf; } context.Trigger.Add(requirement); } } return(null); } }
protected ParseErrorExpression BuildTriggerConditions(TriggerBuilderContext context, InterpreterScope scope, ExpressionBase comparison, int count) { ParseErrorExpression error; var condition = comparison as ConditionalExpression; if (condition != null && condition.Operation == ConditionalOperation.And) { // extract never() conditions from And sequence and build a ResetNextIf clause var nonNeverExpressions = new List <ExpressionBase>(); ExpressionBase neverExpression = null; foreach (var clause in condition.Conditions) { var functionCall = clause as FunctionCallExpression; if (functionCall != null && functionCall.FunctionName.Name == "never") { if (neverExpression != null) { return(new ParseErrorExpression("Only one never() clause allowed inside " + Name.Name + "()", clause)); } neverExpression = clause; } else { nonNeverExpressions.Add(clause); } } if (neverExpression != null && nonNeverExpressions.Count > 0) { // define a new scope with a nested context to prevent TriggerBuilderContext.ProcessAchievementConditions // from optimizing out the ResetIf var nestedContext = new TriggerBuilderContext(); nestedContext.Trigger = new List <Requirement>(); var innerScope = new InterpreterScope(scope); innerScope.Context = nestedContext; error = BuildTriggerCondition(nestedContext, innerScope, neverExpression); if (error != null) { return(error); } nestedContext.LastRequirement.Type = RequirementType.ResetNextIf; foreach (var requirement in nestedContext.Trigger) { context.Trigger.Add(requirement); } comparison = new ConditionalExpression(ConditionalOperation.And, nonNeverExpressions); } } error = BuildTriggerCondition(context, scope, comparison); if (error != null) { return(error); } context.LastRequirement.HitCount = (uint)count; return(null); }
protected ParseErrorExpression EvaluateCondition(TriggerBuilderContext context, InterpreterScope scope, ConditionalExpression condition) { ExpressionBase result; var builder = new ScriptInterpreterAchievementBuilder(); if (!TriggerBuilderContext.ProcessAchievementConditions(builder, condition, scope, out result)) { return((ParseErrorExpression)result); } if (builder.CoreRequirements.Any()) { var error = ProcessOrNextSubClause(builder.CoreRequirements); if (error != null) { return(error); } if (builder.AlternateRequirements.Count == 0) { // everything was converted to an OrNext. convert the last back builder.CoreRequirements.Last().Type = RequirementType.None; // one of the alts was entirely promoted to Core. We only need to check for that. foreach (var clause in builder.CoreRequirements) { context.Trigger.Add(clause); } return(null); } // core requirements have to be injected into each subclause as a series of AndNext's builder.CoreRequirements.Last().Type = RequirementType.AndNext; } var requirements = new List <ICollection <Requirement> >(); foreach (var altGroup in builder.AlternateRequirements) { var error = ProcessOrNextSubClause(altGroup); if (error != null) { return(error); } if (builder.CoreRequirements.Any()) { var merged = new List <Requirement>(builder.CoreRequirements); merged.AddRange(altGroup); requirements.Add(merged); } else { requirements.Add(altGroup); } } // the last item cannot have its own HitCount as it will hold the HitCount for the group. // if necessary, find one without a HitCount and make it the last. int index = requirements.Count - 1; if (requirements[index].Last().HitCount > 0) { do { index--; } while (index >= 0 && requirements[index].Last().HitCount > 0); if (index == -1) { // all requirements had HitCount limits, add a dummy item that's never true for the total HitCount requirements.Add(new Requirement[] { AlwaysFalseFunction.CreateAlwaysFalseRequirement() }); } else { // found a requirement without a HitCount limit, move it to the last spot for the total HitCount var requirement = requirements[index]; requirements.RemoveAt(index); requirements.Add(requirement); } } // if we can guarantee the individual requirements won't be true in the same frame, we can use AddHits // instead of OrNext to improve compatibility with older versions of RetroArch if (CanUseAddHits(requirements)) { foreach (var requirement in requirements) { foreach (var cond in requirement) { if (cond.Type == RequirementType.OrNext) { cond.Type = RequirementType.AddHits; } } } } else { // an AndNext in the first clause is acceptable, but once we see the first // OrNext, each clause must be a single logical condition as AndNext has the // same priority as OrNext and will not be processed first. for (int i = 1; i < requirements.Count; ++i) { if (requirements[i].Any(r => r.Type == RequirementType.AndNext)) { return(new ParseErrorExpression("Cannot join multiple AndNext chains with OrNext")); } } } // everything was converted to an OrNext. convert the last back requirements.Last().Last().Type = RequirementType.None; // load the requirements into the trigger foreach (var requirement in requirements) { foreach (var clause in requirement) { context.Trigger.Add(clause); } } return(null); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { ExpressionBase result; int addHitsClauses = 0; int subHitsClauses = 0; var requirements = new List <ICollection <Requirement> >(); for (int i = 1; i < functionCall.Parameters.Count; ++i) { var condition = functionCall.Parameters.ElementAt(i); var conditionRequirements = new List <Requirement>(); var nestedContext = new TriggerBuilderContext() { Trigger = conditionRequirements }; var modifier = RequirementType.AddHits; var funcCall = condition as FunctionCallExpression; if (funcCall != null && funcCall.FunctionName.Name == "deduct") { var deductScope = funcCall.GetParameters(scope.GetFunction(funcCall.FunctionName.Name), scope, out result); if (deductScope == null) { return((ParseErrorExpression)result); } condition = deductScope.GetVariable("comparison"); modifier = RequirementType.SubHits; ++subHitsClauses; } else { ++addHitsClauses; } var error = BuildTriggerCondition(nestedContext, scope, condition); if (error != null) { return(error); } conditionRequirements.Last().Type = modifier; requirements.Add(conditionRequirements); } // if no requirements were generated, we're done if (requirements.Count == 0) { return(null); } // if there's any SubHits clauses, add a dummy clause for the final count, regardless of whether // the AddHits clauses have hit targets. if (subHitsClauses > 0) { if (addHitsClauses == 0) { return(new ParseErrorExpression("tally requires at least one non-deducted item")); } requirements.Add(new Requirement[] { AlwaysFalseFunction.CreateAlwaysFalseRequirement() }); } // the last item cannot have its own HitCount as it will hold the HitCount for the group. // if necessary, find one without a HitCount and make it the last. AchievementBuilder.EnsureLastGroupHasNoHitCount(requirements); // load the requirements into the trigger foreach (var requirement in requirements) { foreach (var clause in requirement) { context.Trigger.Add(clause); } } // the last item of each clause was set to AddHits, change the absolute last back to None context.LastRequirement.Type = RequirementType.None; // set the target hitcount var count = (IntegerConstantExpression)functionCall.Parameters.First(); context.LastRequirement.HitCount = (uint)count.Value; return(null); }
public override ParseErrorExpression BuildTrigger(TriggerBuilderContext context, InterpreterScope scope, FunctionCallExpression functionCall) { context.Trigger.Add(CreateAlwaysTrueRequirement()); return(null); }