""" Dice - rolls dice for roleplaying, in-game gambling or GM:ing Evennia contribution - Griatch 2012 This module implements a full-fledged dice-roller and a 'dice' command to go with it. It uses standard RPG 'd'-syntax (e.g. 2d6 to roll two six-sided die) and also supports modifiers such as 3d6 + 5. One can also specify a standard Python operator in order to specify eventual target numbers and get results in a fair and guaranteed unbiased way. For example a GM could (using the dice command) from the start define the roll as 2d6 < 8 to show that a roll below 8 is required to succeed. The command will normally echo this result to all parties (although it also has options for hidden and secret rolls). Installation: To use in your code, just import the roll_dice function from this module. To use the dice/roll command, just import this module in your custom cmdset module and add the following line to the end of DefaultCmdSet's at_cmdset_creation(): self.add(dice.CmdDice()) After a reload the dice (or roll) command will be available in-game. """ import re from random import randint from ev import default_cmds, CmdSet def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False): """ This is a standard dice roller. Input: dicenum - number of dice to roll (the result to be added) dicetype - number of sides of the dice to be rolled modifier - tuple (operator, value), where operator is a character string with one of +,-,/ or *. The entire result of the dice rolls will be modified by this value. conditional - tuple (conditional, value), where conditional is a character string with one of ==,<,>,>=,<= or !=. return_tuple - return result as a tuple containing all relevant info return_tuple - (default False) - return a tuple with all individual roll results All input numbers are converted to integers. Returns: normally returns the result if return_tuple=True, returns a tuple (result, outcome, diff, rolls) In this tuple, outcome and diff will be None if conditional is not set. rolls is itself a tuple holding all the individual rolls in the case of multiple die-rolls. Raises: TypeError if non-supported modifiers or conditionals are given. """ dicelimit = 0 # This is the maximum number of dice that can be used in a single roll. dicenum = int(dicenum) dicetype = int(dicetype) # roll all dice, remembering each roll rolls = tuple([randint(1, dicetype) for roll in range(dicenum)]) result = sum(rolls) if modifier: # make sure to check types well before eval mod, modvalue = modifier if not mod in ('+', '-', '*', '/'): raise TypeError("Non-supported dice modifier: %s" % mod) modvalue = int(modvalue) # for safety result = eval("%s %s %s" % (result, mod, modvalue)) outcome, diff = None, None if conditional: # make sure to check types well before eval cond, condvalue = conditional if not cond in ('>', '<', '>=', '<=', '!=', '=='): raise TypeError("Non-supported dice result conditional: %s" % conditional) condvalue = int(condvalue) # for safety outcome = eval("%s %s %s" % (result, cond, condvalue)) # True/False diff = abs(result - condvalue) if return_tuple: return (result, outcome, diff, rolls) else: return result RE_PARTS = re.compile(r"(d|\+|-|/|\*|<|>|<=|>=|!=|==)") RE_MOD = re.compile(r"(\+|-|/|\*)") RE_COND = re.compile(r"(<|>|<=|>=|!=|==)") class CmdDice(default_cmds.MuxCommand): """ roll dice Usage: dice[/switch] d [modifier] [success condition] Switch: hidden - tell the room the roll is being done, but don't show the result secret - don't inform the room about neither roll nor result Examples: dice 3d6 + 4 dice 1d100 - 2 < 50 This will roll the given number of dice with given sides and modifiers. So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result, then add 3 to the total'. Accepted modifiers are +, -, * and /. A success condition is given as normal Python conditionals (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed only if the final result is above 8. If a success condition is given, the outcome (pass/fail) will be echoed along with how much it succeeded/failed with. The hidden/secret switches will hide all or parts of the roll from everyone but the person rolling. """ key = "dice" aliases = ["roll", "@dice"] locks = "cmd:all()" def func(self): "Mostly parsing for calling the dice roller function" if not self.args: self.caller.msg("Usage: @dice d [modifier] [conditional]") return argstring = "".join(str(arg) for arg in self.args) parts = RE_PARTS.split(self.args) lparts = len(parts) ndice = 0 nsides = 0 modifier = None conditional = None if lparts < 3 or parts[1] != 'd': self.caller.msg("You must specify the die roll(s) as d. So 2d6 means rolling a 6-sided die 2 times.") return # Limit the number of dice and sides a character can roll to prevent server slow down and crashes ndicelimit = 10000 # Maximum number of dice nsidelimit = 10000 # Maximum number of sides if int(parts[0]) > ndicelimit or int(parts[2]) > nsidelimit: self.caller.msg("The maximum roll allowed is %sd%s." % (ndicelimit, nsidelimit)) return ndice, nsides = parts[0], parts[2] if lparts == 3: # just something like 1d6 pass elif lparts == 5: # either e.g. 1d6 + 3 or something like 1d6 > 3 if parts[3] in ('+', '-', '*', '/'): modifier = (parts[3], parts[4]) else: # assume it is a conditional conditional = (parts[3], parts[4]) elif lparts == 7: # the whole sequence, e.g. 1d6 + 3 > 5 modifier = (parts[3], parts[4]) conditional = (parts[5], parts[6]) else: # error self.caller.msg("You must specify a valid die roll") return # do the roll try: result, outcome, diff, rolls = roll_dice(ndice, nsides, modifier=modifier, conditional=conditional, return_tuple=True) except ValueError: self.caller.msg("You need to enter valid integer numbers, modifiers and operators. {w%s{n was not understood." % self.args) return # format output if len(rolls) > 1: rolls = ", ".join(str(roll) for roll in rolls[:-1]) + " and " + str(rolls[-1]) else: rolls = rolls[0] if outcome is None: outcomestring = "" elif outcome: outcomestring = " This is a {gsuccess{n (by %s)." % diff else: outcomestring = " This is a {rfailure{n (by %s)." % diff yourollstring = "You roll %s%s." roomrollstring = "%s rolls %s%s." resultstring = " Roll(s): %s. Total result is {w%s{n." if 'secret' in self.switches: # don't echo to the room at all string = yourollstring % (argstring, " (secret, not echoed)") string += "\n" + resultstring % (rolls, result) string += outcomestring + " (not echoed)" self.caller.msg(string) elif 'hidden' in self.switches: # announce the roll to the room, result only to caller string = yourollstring % (argstring, " (hidden)") self.caller.msg(string) string = roomrollstring % (self.caller.key, argstring, " (hidden)") self.caller.location.msg_contents(string, exclude=self.caller) # handle result string = resultstring % (rolls, result) string += outcomestring + " (not echoed)" self.caller.msg(string) else: # normal roll string = yourollstring % (argstring, "") self.caller.msg(string) string = roomrollstring % (self.caller.key, argstring, "") self.caller.location.msg_contents(string, exclude=self.caller) string = resultstring % (rolls, result) string += outcomestring self.caller.location.msg_contents(string) class DiceCmdSet(CmdSet): """ a small cmdset for testing purposes. Add with @py self.cmdset.add("contrib.dice.DiceCmdSet") """ def at_cmdset_creation(self): "Called when set is created" self.add(CmdDice())