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); }
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 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 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); } }
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); }
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) { var until = functionCall.Parameters.ElementAt(1); // build the reset next clause var builder = new ScriptInterpreterAchievementBuilder(); ExpressionBase result; if (!TriggerBuilderContext.ProcessAchievementConditions(builder, until, scope, out result)) { return new ParseErrorExpression("until did not evaluate to a valid comparison", until) { InnerError = (ParseErrorExpression)result } } ; var error = builder.CollapseForSubClause(); if (error != null) { return(new ParseErrorExpression(error.Message, until)); } var resetNextClause = new List <Requirement>(); if (builder.CoreRequirements.Count > 0 && builder.CoreRequirements.First().Evaluate() != false) { foreach (var requirement in builder.CoreRequirements) { if (requirement.Type == RequirementType.None) { requirement.Type = RequirementType.AndNext; } resetNextClause.Add(requirement); } resetNextClause.Last().Type = RequirementType.ResetNextIf; } // build the when clause var whenContext = new TriggerBuilderContext { Trigger = new List <Requirement>() }; error = base.BuildTrigger(whenContext, scope, functionCall); if (error != null) { return(error); } // 'reset next' clause first foreach (var resetRequirement in resetNextClause) { context.Trigger.Add(resetRequirement); } // then 'when' clause. make sure to insert the 'reset next' clause after each addhits/subhits // as they break up the 'reset next' scope. foreach (var whenRequirement in whenContext.Trigger) { context.Trigger.Add(whenRequirement); switch (whenRequirement.Type) { case RequirementType.AddHits: case RequirementType.SubHits: foreach (var resetRequirement in resetNextClause) { context.Trigger.Add(resetRequirement); } break; default: break; } } // disable_when is a pause lock - if a hitcount was not specified assume the first hit is enough if (context.LastRequirement.HitCount == 0) { context.LastRequirement.HitCount = 1; } return(null); } }