mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 13:26:30 +01:00
234 lines
8.9 KiB
Python
234 lines
8.9 KiB
Python
"""
|
|
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] <nr>d<sides> [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 <nr>d<sides> [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 <nr>d<sides>. 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())
|