internal RollPartialNode(RollNode roll) { Roll = roll; Keep = new List <KeepNode>(); Sort = null; RerollNode = null; Explode = null; Critical = null; Success = null; Functions = new List <FunctionNode>(); }
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); }
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); }