internal static DieResult DoRoll(RollData data, RollType rollType, int numSides, DieFlags flags = 0) { if (numSides < 1 || numSides > data.Config.MaxSides) { throw new DiceException(DiceErrorCode.BadSides, data.Config.MaxSides); } if (data.Config.NormalSidesOnly && !_normalSides.Contains(numSides)) { throw new DiceException(DiceErrorCode.WrongSides); } byte[] roll = new byte[4]; uint sides = (uint)numSides; int min, max; uint rollValue; int rollAmt; DieType dt; switch (rollType) { case RollType.Normal: dt = DieType.Normal; min = 1; max = numSides; break; case RollType.Fudge: dt = DieType.Fudge; // fudge dice go from -sides to sides, so we need to double // numSides and include an extra side for a "0" value as well. sides = (sides * 2) + 1; min = -numSides; max = numSides; break; default: throw new InvalidOperationException("Unknown RollType"); } if (data.Config.RollDie != null) { rollAmt = data.Config.RollDie(min, max); if (rollAmt < min || rollAmt > max) { throw new InvalidOperationException("RollerConfig.RollDie returned a value not within the expected range."); } // convert rollValue into the 0-based number for serialization switch (rollType) { case RollType.Normal: rollValue = (uint)(rollAmt - 1); break; case RollType.Fudge: rollValue = (uint)(rollAmt + numSides); break; default: throw new InvalidOperationException("Unknown RollType"); } } else { do { if (data.Config.GetRandomBytes != null) { data.Config.GetRandomBytes(roll); } else { _rand.GetBytes(roll); } } while (!IsFairRoll(roll, sides)); // rollAmt is a number from 0 to sides-1, need to convert to a proper number rollValue = BitConverter.ToUInt32(roll, 0) % sides; rollAmt = (int)rollValue; switch (rollType) { case RollType.Normal: // change from 0 to sides-1 into 1 to sides rollAmt++; break; case RollType.Fudge: // normalize back into -numSides to +numSides rollAmt -= ((int)sides - 1) / 2; break; default: throw new InvalidOperationException("Unknown RollType"); } } data.InternalContext.AllRolls.Add(rollValue); // finally, mark if this was a critical or fumble. This may be overridden later by a CritNode. if (rollAmt == min) { flags |= DieFlags.Fumble; } if (rollAmt == max) { flags |= DieFlags.Critical; } return(new DieResult() { DieType = dt, NumSides = numSides, Value = rollAmt, Flags = flags }); }
private void MarkCrits() { Value = Expression.Value; ValueType = Expression.ValueType; _values.Clear(); DieFlags mask = 0; if (Critical != null) { mask |= DieFlags.Critical; } if (Fumble != null) { mask |= DieFlags.Fumble; } foreach (var die in Expression.Values) { DieFlags flags = 0; if (die.DieType == DieType.Special || die.DieType == DieType.Group) { // we don't skip over dropped dice here since we DO still want to // mark them as criticals/fumbles as needed. _values.Add(die); continue; } if (Critical?.Compare(die.Value) == true) { flags |= DieFlags.Critical; // if tracking successes; a critical success is worth 2 successes if (ValueType == ResultType.Successes) { // just in case the die wasn't already marked as a success if ((die.Flags & DieFlags.Success) == 0) { flags |= DieFlags.Success; Value++; } Value++; } } if (Fumble?.Compare(die.Value) == true) { flags |= DieFlags.Fumble; // if tracking failures; a critical failure is worth -2 successes if (ValueType == ResultType.Successes) { // just in case the die wasn't already marked as a failure if ((die.Flags & DieFlags.Failure) == 0) { flags |= DieFlags.Failure; Value--; } Value--; } } _values.Add(new DieResult() { DieType = die.DieType, NumSides = die.NumSides, Value = die.Value, // strip any existing crit/fumble flag off and use ours, // assuming a comparison was defined for it. // (we may have an existing flag if the die rolled min or max value) Flags = (die.Flags & ~mask) | flags }); } }