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); }
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); }