private long ApplyAdvantage(RollData data, DiceAST root, int depth) { Value = Expression.Value; ValueType = Expression.ValueType; _values = Expression.Values.ToList(); var rolls = Expression.Reroll(data, root, depth + 1); if ((KeepType == KeepType.Advantage && Expression.Value > Value) || (KeepType == KeepType.Disadvantage && Expression.Value < Value)) { for (int i = 0; i < _values.Count; i++) { _values[i] = _values[i].Drop(); } Value = Expression.Value; _values.Add(new DieResult(SpecialDie.Add)); _values.AddRange(Expression.Values); } else { _values.Add(new DieResult(SpecialDie.Add)); _values.AddRange(Expression.Values.Select(d => d.Drop())); } return(rolls); }
private void AddFunctionNodes(FunctionTiming timing, ref DiceAST node) { foreach (var fn in Functions.Where(f => f.Timing == timing)) { fn.Context.Expression = node; node = fn; } }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { long rolls = NumTimes?.Evaluate(data, root, depth + 1) ?? 0; rolls += Roll(data, root, depth); return(rolls); }
protected override long RerollInternal(RollData data, DiceAST root, int depth) { var rolls = Expression.Reroll(data, root, depth + 1); DoSort(); return(rolls); }
protected override long RerollInternal(RollData data, DiceAST root, int depth) { long rolls = Expression.Reroll(data, root, depth + 1); MarkCrits(); return(rolls); }
internal RerollNode(int maxRerolls, ComparisonNode comparison, DiceAST maxRerollsExpr = null) { Comparison = comparison; Expression = null; MaxRerolls = maxRerolls; MaxRerollsExpr = maxRerollsExpr; _values = new List <DieResult>(); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { long rolls = Comparison?.Evaluate(data, root, depth + 1) ?? 0; rolls += Expression.Evaluate(data, root, depth + 1); rolls += DoExplode(data); return(rolls); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { var rolls = Amount?.Evaluate(data, root, depth + 1) ?? 0; rolls += Expression.Evaluate(data, root, depth + 1); rolls += ApplyKeep(data, root, depth); return(rolls); }
/// <summary> /// Creates the RollNode subtree /// </summary> /// <returns></returns> internal DiceAST CreateRollNode() { DiceAST roll = Roll; AddFunctionNodes(FunctionTiming.First, ref roll); AddFunctionNodes(FunctionTiming.BeforeExplode, ref roll); if (Explode != null) { Explode.Expression = roll; roll = Explode; } AddFunctionNodes(FunctionTiming.AfterExplode, ref roll); AddFunctionNodes(FunctionTiming.BeforeReroll, ref roll); if (RerollNode != null) { RerollNode.Expression = roll; roll = RerollNode; } AddFunctionNodes(FunctionTiming.AfterReroll, ref roll); AddFunctionNodes(FunctionTiming.BeforeKeep, ref roll); foreach (var k in Keep) { k.Expression = roll; roll = k; } AddFunctionNodes(FunctionTiming.AfterKeep, ref roll); AddFunctionNodes(FunctionTiming.BeforeSuccess, ref roll); if (Success != null) { if (Success.Success.Comparisons.Count() == 0 && Success.Failure.Comparisons.Count() > 0) { throw new DiceException(DiceErrorCode.InvalidSuccess); } Success.Expression = roll; roll = Success; } AddFunctionNodes(FunctionTiming.AfterSuccess, ref roll); AddFunctionNodes(FunctionTiming.BeforeCrit, ref roll); if (Critical != null) { Critical.Expression = roll; roll = Critical; } AddFunctionNodes(FunctionTiming.AfterCrit, ref roll); AddFunctionNodes(FunctionTiming.BeforeSort, ref roll); if (Sort != null) { Sort.Expression = roll; roll = Sort; } AddFunctionNodes(FunctionTiming.AfterSort, ref roll); AddFunctionNodes(FunctionTiming.Last, ref roll); return(roll); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { long rolls = Expression.Evaluate(data, root, depth + 1); rolls += Critical?.Evaluate(data, root, depth + 1) ?? 0; rolls += Fumble?.Evaluate(data, root, depth + 1) ?? 0; MarkCrits(); return(rolls); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { var rolls = MaxRerollsExpr?.Evaluate(data, root, depth + 1) ?? 0; rolls += Comparison.Evaluate(data, root, depth + 1); rolls += Expression.Evaluate(data, root, depth + 1); rolls += MaybeReroll(data, root, depth); return(rolls); }
protected override long RerollInternal(RollData data, DiceAST root, int depth) { long rolls = 0; foreach (var c in Comparisons) { rolls += c.expr.Reroll(data, root, depth); } return(rolls); }
internal GroupNode(DiceAST numTimes, List <DiceAST> exprs) { NumTimes = numTimes; _expressions = exprs ?? throw new ArgumentNullException(nameof(exprs)); _values = new List <DieResult>(); if (exprs.Count == 0) { throw new ArgumentException("A dice group must contain at least one expression", nameof(exprs)); } }
internal RollNode(RollType rollType, DiceAST numDice, DiceAST numSides) { RollType = rollType; _values = new List <DieResult>(); NumDice = numDice ?? throw new ArgumentNullException(nameof(numDice)); NumSides = numSides; if (numSides == null && rollType != RollType.Fudge) { throw new ArgumentNullException(nameof(numSides)); } }
internal ComparisonNode(CompareOp operation, DiceAST expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } _comparisons = new List <Comparison>() { new Comparison(operation, expression) }; }
protected override long RerollInternal(RollData data, DiceAST root, int depth) { if (Success == null) { throw new DiceException(DiceErrorCode.InvalidSuccess); } long rolls = Expression.Reroll(data, root, depth + 1); CountSuccesses(); return(rolls); }
internal MathNode(MathOp operation, DiceAST left, DiceAST right) { Operation = operation; Left = left; Right = right ?? throw new ArgumentNullException(nameof(right)); if (!operation.IsUnary() && left == null) { throw new ArgumentNullException(nameof(left)); } _values = new List <DieResult>(); }
protected override long RerollInternal(RollData data, DiceAST root, int depth) { long rolls = Context.Expression?.Reroll(data, root, depth + 1) ?? 0; foreach (var arg in Context.Arguments) { rolls += arg.Reroll(data, root, depth + 1); } CallFunction(); return(rolls); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { // this doesn't increase depth as there is no actual logic that a ComparisonNode itself performs // (in other words, the Expression can be viewed as the ComparisonNode's evaluation) long rolls = 0; Value = 0; foreach (var c in Comparisons) { rolls += c.expr.Evaluate(data, root, depth); } return(rolls); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { if (Success == null) { throw new DiceException(DiceErrorCode.InvalidSuccess); } long rolls = Expression.Evaluate(data, root, depth + 1); rolls += Success.Evaluate(data, root, depth + 1); rolls += Failure?.Evaluate(data, root, depth + 1) ?? 0; CountSuccesses(); return(rolls); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { if (data.MacroRegistry.Contains(Context.Name)) { data.MacroRegistry.Get(Context.Name).callback(Context); } else if (data.Config.MacroRegistry.Contains(Context.Name)) { data.Config.MacroRegistry.Get(Context.Name).callback(Context); } data.MacroRegistry.GlobalCallbacks?.Invoke(Context); data.Config.MacroRegistry.GlobalCallbacks?.Invoke(Context); Value = Context.Value; ValueType = Context.ValueType; data.InternalContext.AllMacros.Add(Value); if (Context.Value == Decimal.MinValue) { throw new DiceException(DiceErrorCode.InvalidMacro); } _values.Clear(); if (Context.Values != null) { _values.AddRange(Context.Values); } if (_values.Count == 0) { _values.Add(new DieResult() { DieType = DieType.Literal, NumSides = 0, Value = Context.Value, Flags = DieFlags.Macro }); } return(0); }
internal KeepNode(KeepType keepType, DiceAST amount) { KeepType = keepType; Expression = null; _values = new List <DieResult>(); if (keepType == KeepType.Advantage || keepType == KeepType.Disadvantage) { if (amount != null) { throw new ArgumentException("amount must be null if keep type is advantage or disadvantage"); } Amount = null; } else { Amount = amount ?? throw new ArgumentNullException(nameof(amount)); } }
/// <summary> /// Re-do the roll without re-evaluating the entire subtree again /// </summary> /// <param name="data">Roller config</param> /// <param name="root">AST root</param> /// <param name="depth">Recursion depth</param> /// <returns>Number of dice rolls performed</returns> protected internal long Reroll(RollData data, DiceAST root, int depth) { if (!Evaluated) { return(Evaluate(data, root, depth)); } if (depth > data.Config.MaxRecursionDepth) { throw new DiceException(DiceErrorCode.RecursionDepthExceeded, data.Config.MaxRecursionDepth); } long rolls = RerollInternal(data, root, depth); if (rolls > data.Config.MaxDice) { throw new DiceException(DiceErrorCode.TooManyDice, data.Config.MaxDice); } return(rolls); }
/// <summary> /// Evaluates the node, causing it to store its result in Value. /// </summary> /// <param name="data">Configuration of the roller</param> /// <param name="root">Root of the AST</param> /// <param name="depth">Current recursion depth</param> /// <returns>Total number of rolls taken to evaluate this subtree</returns> protected internal long Evaluate(RollData data, DiceAST root, int depth) { if (this == root) { data.InternalContext = new InternalContext(); } if (depth > data.Config.MaxRecursionDepth) { throw new DiceException(DiceErrorCode.RecursionDepthExceeded, data.Config.MaxRecursionDepth); } long rolls = EvaluateInternal(data, root, depth); if (rolls > data.Config.MaxDice) { throw new DiceException(DiceErrorCode.TooManyDice, data.Config.MaxDice); } Evaluated = true; return(rolls); }
protected override long EvaluateInternal(RollData data, DiceAST root, int depth) { return(0); }
protected override long RerollInternal(RollData data, DiceAST root, int depth) { return(Roll(data, root, depth)); }
protected abstract long RerollInternal(RollData data, DiceAST root, int depth);
protected override long RerollInternal(RollData data, DiceAST root, int depth) { throw new InvalidOperationException("This node should not exist in an AST"); }
internal long Roll(RollData data, DiceAST root, int depth) { long rolls = 0; ushort numTimes = (ushort)(NumTimes?.Value ?? 1); bool haveTotal = false; bool haveRoll = false; Value = 0; _values.Clear(); if (numTimes == 0) { _values.Add(new DieResult() { DieType = DieType.Literal, NumSides = 0, Value = 0, Flags = DieFlags.Extra }); return(0); } for (ushort run = 0; run < numTimes; run++) { _values.MaybeAddPlus(); _values.Add(new DieResult(SpecialDie.OpenParen)); foreach (var ast in Expressions) { // we want certain variables "fixed" throughout each iteration // for example, in the group 2{(1d6)d8}, we want to roll 1d6 once and then treat it as if the // group was 2{3d8} (or whatever the 1d6 rolled); in other words, we don't re-evaluate the 1d6 // every iteration. Note that Reroll() calls Evaluate() if the expr wasn't already evaluated. rolls += ast.Reroll(data, root, depth + 1); _values.MaybeAddPlus(); // Add a node containing the aggregate result of this expression _values.Add(new DieResult() { DieType = DieType.Group, NumSides = 0, Value = ast.Value, // maintain any crit/fumble flags from the underlying dice, combining them together Flags = ast.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), Data = data.InternalContext.AddGroupExpression(ast) }); Value += ast.Value; // we make our final ValueType successes if *all* underlying expressions in this group which actually contain rolls are success types // (and if there are actually rolls) if (ast.Values.Any(d => d.DieType.IsRoll())) { haveRoll = true; if (ast.ValueType == ResultType.Total) { haveTotal = true; } } } _values.Add(new DieResult(SpecialDie.CloseParen)); } if (!haveRoll || haveTotal) { ValueType = ResultType.Total; } else { ValueType = ResultType.Successes; } return(rolls); }
protected abstract long EvaluateInternal(RollData data, DiceAST root, int depth);