Exemplo n.º 1
0
 internal RollPartialNode(RollNode roll)
 {
     Roll       = roll;
     Keep       = new List <KeepNode>();
     Sort       = null;
     RerollNode = null;
     Explode    = null;
     Critical   = null;
     Success    = null;
     Functions  = new List <FunctionNode>();
 }
Exemplo n.º 2
0
        private long MaybeReroll(RollData data, DiceAST root, int depth)
        {
            long rolls      = 0;
            int  rerolls    = 0;
            int  maxRerolls = MaxRerolls;

            if (MaxRerolls < 0)
            {
                if (MaxRerollsExpr.Value < 0 || Math.Floor(MaxRerollsExpr.Value) != MaxRerollsExpr.Value || MaxRerollsExpr.Value > Int32.MaxValue)
                {
                    throw new DiceException(DiceErrorCode.BadRerollCount);
                }

                maxRerolls = (int)MaxRerollsExpr.Value;
            }
            maxRerolls = maxRerolls == 0 ? data.Config.MaxRerolls : Math.Min(maxRerolls, data.Config.MaxRerolls);
            _values.Clear();

            void DoReroll(DieResult die, out DieResult reroll)
            {
                rerolls++;

                if (die.DieType == DieType.Group)
                {
                    var group = data.InternalContext.GetGroupExpression(die.Data);
                    rolls += group.Reroll(data, root, depth + 1);

                    reroll = new DieResult()
                    {
                        DieType  = DieType.Group,
                        NumSides = 0,
                        Value    = group.Value,
                        // maintain any crit/fumble flags from the underlying dice, combining them together
                        Flags = group.Values
                                .Where(d => d.DieType != DieType.Special && !d.Flags.HasFlag(DieFlags.Dropped))
                                .Select(d => d.Flags & (DieFlags.Critical | DieFlags.Fumble))
                                .Aggregate((d1, d2) => d1 | d2) | DieFlags.Extra,
                        Data = die.Data
                    };
                }
                else
                {
                    rolls++;
                    RollType rt = RollType.Normal;
                    switch (die.DieType)
                    {
                    case DieType.Normal:
                        rt = RollType.Normal;
                        break;

                    case DieType.Fudge:
                        rt = RollType.Fudge;
                        break;

                    default:
                        throw new InvalidOperationException("Unsupported die type in reroll");
                    }

                    reroll = RollNode.DoRoll(data, rt, die.NumSides, DieFlags.Extra);
                }
            }

            foreach (var die in Expression.Values)
            {
                if (die.DieType == DieType.Special || die.Flags.HasFlag(DieFlags.Dropped) || !Comparison.Compare(die.Value))
                {
                    _values.Add(die);
                    continue;
                }

                _values.Add(die.Drop());
                DoReroll(die, out DieResult rr);

                while (rerolls < maxRerolls && Comparison.Compare(rr.Value))
                {
                    _values.Add(new DieResult(SpecialDie.Add));
                    _values.Add(rr.Drop()); // mark the overall result as dropped
                    DoReroll(die, out rr);
                }

                _values.Add(new DieResult(SpecialDie.Add));
                _values.Add(rr);
            }

            var dice = _values.Where(d => d.DieType != DieType.Special && !d.Flags.HasFlag(DieFlags.Dropped));

            if (Expression.ValueType == ResultType.Total)
            {
                Value     = dice.Sum(d => d.Value);
                ValueType = ResultType.Total;
            }
            else
            {
                Value     = dice.Sum(d => d.SuccessCount);
                ValueType = ResultType.Successes;
            }

            return(rolls);
        }
Exemplo n.º 3
0
        private long DoExplode(RollData data)
        {
            long rolls = 0;
            Func <DieResult, decimal, bool> shouldExplode;
            decimal addToValue = ExplodeType == ExplodeType.Penetrate ? 1 : 0;

            Value     = 0;
            ValueType = ResultType.Total;
            _values.Clear();

            if (Comparison != null)
            {
                shouldExplode = (d, x) => Comparison.Compare(d.Value + x);
            }
            else
            {
                shouldExplode = (d, x) => d.Value + x == d.NumSides;
            }


            foreach (var die in Expression.Values)
            {
                var accum = die;

                if (!die.IsLiveDie())
                {
                    // special die results can't explode as they aren't actually dice
                    // dropped dice are no longer part of the resultant expression so should not explode
                    _values.Add(die);

                    continue;
                }

                RollType rt;
                switch (die.DieType)
                {
                case DieType.Normal:
                    rt = RollType.Normal;
                    break;

                case DieType.Fudge:
                    rt = RollType.Fudge;
                    break;

                case DieType.Group:     // we can't explode on groups, so throw an exception
                default:
                    throw new InvalidOperationException("Unsupported die type for explosion");
                }

                Value += die.Value;
                if (shouldExplode(die, 0))
                {
                    if (!Compound)
                    {
                        _values.Add(die);
                    }

                    DieResult result;

                    do
                    {
                        rolls++;

                        if (rolls > data.Config.MaxDice)
                        {
                            throw new DiceException(DiceErrorCode.TooManyDice, data.Config.MaxDice);
                        }

                        if (rolls > data.Config.MaxRerolls)
                        {
                            break;
                        }

                        var numSides = die.NumSides;
                        if (ExplodeType == ExplodeType.Penetrate && Comparison == null)
                        {
                            // if penetrating dice are used, d100p penetrates to d20p,
                            // and d20p penetrates to d6p (however, the d20p from
                            // the d100p does not further drop to d6p).
                            // only do this if a custom comparison expression was not used.
                            if (numSides == 100)
                            {
                                numSides = 20;
                            }
                            else if (numSides == 20)
                            {
                                numSides = 6;
                            }
                        }

                        result = RollNode.DoRoll(data, rt, numSides, DieFlags.Extra);
                        switch (ExplodeType)
                        {
                        case ExplodeType.Explode:
                            break;

                        case ExplodeType.Penetrate:
                            result.Value -= 1;
                            break;

                        default:
                            throw new InvalidOperationException("Unknown explosion type");
                        }

                        Value += result.Value;
                        if (Compound)
                        {
                            accum.Value += result.Value;
                        }
                        else
                        {
                            _values.Add(new DieResult(SpecialDie.Add));
                            _values.Add(result);
                        }
                    } while (shouldExplode(result, addToValue));

                    if (Compound)
                    {
                        _values.Add(accum);
                    }
                }
                else
                {
                    _values.Add(die);
                }
            }

            return(rolls);
        }