Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        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);
        }