async def roll(ctx, formula=None): """Rolls dice Note: formula cannot include spaces, unless wrapped in double-quotes""" if not formula: await ctx.send(d20.roll('1d20')) else: await ctx.send(d20.roll(formula))
def generate_characteristic(ctx, characteristic): if characteristic in ["STR", "CON", "DEX", "APP", "POW"]: return dtwenty.roll("3d6*5") elif characteristic in ["SIZ", "INT", "EDU"]: return dtwenty.roll("(2d6+6)*5") else: return None
def test_bounding_operators(): # mi, ma r = d20.roll("10d2mi2") assert r.total == 20 r = d20.roll("10d2ma1") assert r.total == 10
def save(self, ability, advdis=''): #advdis = "adv" or "dis" additional = {'adv': 'kh1', 'dis': 'kl1', '': ''}[advdis] if self.abilities[ability]['save'] >= 0: return d20.roll('d20' + additional + '+' + str(self.abilities[ability]['save'])) else: return d20.roll('d20' + additional + str(self.abilities[ability]['save']))
def test_randomness(): rolls = [d20.roll("1d1000").total for _ in range(100)] # the chance of all of them being equal is 1/1000^100, so this should be safe # unless, of course, I broke something horribly assert len(set(rolls)) > 1 # just roll 500 d20s to make codecov happy about branches for _ in range(500): d20.roll("1d20")
def test_h_l_selectors(): r = d20.roll("10d6kl1") assert 1 <= r.total <= 6 assert len(r.expr.roll.keptset) == 1 assert len([p for p in r.expr.roll.set if not p.kept]) == 9 r = d20.roll("10d6kh2") assert 2 <= r.total <= 12 assert len(r.expr.roll.keptset) == 2 assert len([p for p in r.expr.roll.set if not p.kept]) == 8
async def game_deathsave(self, ctx, *args): """Commands to manage character death saves. __Valid Arguments__ See `!help save`.""" character: Character = await Character.from_ctx(ctx) args = argparse(args) adv = args.adv() b = args.join('b', '+') phrase = args.join('phrase', '\n') dc = args.last('dc', 10, int) if b: save_roll = d20.roll(f"{d20_with_adv(adv)}+{b}") else: save_roll = d20.roll(d20_with_adv(adv)) embed = discord.Embed() embed.title = args.last('title', '') \ .replace('[name]', character.name) \ .replace('[sname]', 'Death') \ or f'{character.name} makes a Death Save!' embed.colour = character.get_color() death_phrase = '' if save_roll.crit == d20.CritType.CRIT: character.hp = 1 elif save_roll.crit == d20.CritType.FAIL: character.death_saves.fail(2) elif save_roll.total >= dc: character.death_saves.succeed() else: character.death_saves.fail() if save_roll.crit == d20.CritType.CRIT: death_phrase = f"{character.name} is UP with 1 HP!" elif character.death_saves.is_dead(): death_phrase = f"{character.name} is DEAD!" elif character.death_saves.is_stable(): death_phrase = f"{character.name} is STABLE!" await character.commit(ctx) embed.description = save_roll.result + (f'\n*{phrase}*' if phrase else '') if death_phrase: embed.set_footer(text=death_phrase) if dc != 10: embed.description = f"**DC {dc}**\n" + embed.description embed.add_field(name="Death Saves", value=str(character.death_saves)) if args.last('image') is not None: embed.set_thumbnail(url=args.last('image')) await ctx.send(embed=embed)
def test_keeping_operators(): # k, p r = d20.roll("10d6k1") assert len( r.expr.roll.keptset) == r.total # the total is the number of 1s kept r = d20.roll("10d6k<11") assert len(r.expr.roll.keptset) == 10 r = d20.roll("10d6p<11") assert len(r.expr.roll.keptset) == 0
def weapon_attack(attacker=None, target=None, weapon=None, adv=False, dis=False, condition=None): # Attack Roll = Ability Modifier + Proficiency + Enchantment/Item Bonus + Class Features output = ( f"{'' if attacker is None else attacker.name + ' is '} attacking " f"{'' if target is None else target} with the " f"{'magical +' + str(weapon.bonus_magic) if weapon.magical else ''} " f"{weapon.name}") output += f", and has {Color.BLUE}Advantage{Color.ENDC}." if adv else "" print(f"{' '.join(output.split())}") # clean any repeated spaces if condition: # ToDo: placeholder to handle influence of condition, e.g., blinded pass # If Rogue and has Advantage, then add sneak attack damage to weapon if "Rogue" in attacker.cclass and adv: regexp = re.compile("(\\d+).*Rogue") rogue_level = int(regexp.search( attacker.cclass).group(1)) # get Rogue level weapon.damage += [[(rogue_level + 1) // 2, 6, "sneak"]] # append sneak damage to Weapon attack_roll_str = attacker.attack_roll(adv, dis) d20roll = d20.roll(attack_roll_str) # d20roll = d20.roll('20') # Critical 20, for testing logging.debug(d20roll.crit) print(f'\tYour Attack roll is ', end='') if d20roll.crit == d20.CritType.CRIT or d20roll.total >= attacker.crit: print(f"a Critical HIT: {d20roll.result}") criticalhit(attacker, weapon, target) elif d20roll.crit == d20.CritType.FAIL: # total == 1: print(f"a Critical Fail: {d20roll.result}") critical_miss(weapon) else: attackbonus = attacker.attack_bonus(weapon) attack_result_str = " + ".join([str(d20roll.total), attackbonus]) attack_result = d20.roll(f"{attack_result_str}") print( f"{attack_roll_str} + {attackbonus} = {attack_result_str.replace(' ', '')} = {Color.YELLOW}{attack_result.total}{Color.ENDC}." ) hitanddamage(attacker, weapon, target)
def test_rerolling_operators(): # rr, ro, ra, e r = d20.roll("4d6rr1") assert 8 <= r.total <= 24 assert len([p for p in r.expr.roll.set if p.number == 1 and p.kept]) == 0 r = d20.roll("4d6rr<3") assert 12 <= r.total <= 24 assert len([p for p in r.expr.roll.set if p.number < 3 and p.kept]) == 0 r = d20.roll("10d2ra1") assert 11 <= r.total <= 21 r = d20.roll("10d2e1") assert 20 <= r.total
async def _attack(ctx, dice: str): #TODO: debug this command. Sometimes it raises an error """Tira un dado usando un'espressione (vedi `help r`) e calcola il risultato dell'attacco secondo il regolamento di Crossdoom. Restituisce: singoli valori e risultato dell'attacco Per maggiori informazioni sulla sintassi utilizzata: https://d20.readthedocs.io/en/latest/start.html#dice-syntax Per il regolamento di Crossdoom: https://www.crossdoom.it/ """ try: res = d20.roll(dice) dices = diceIterClass(res.expr.roll) # Iterate and get dice values message = ['Attacco:'] for key in dices.initial_rolls.keys(): if isinstance(key, int) and len(dices.initial_rolls[key]) > 0: message.append('{}d{}{} -> {}'.format(len(dices.initial_rolls[key]), key, dices.initial_rolls[key], dices.crossdoom_rolls[key])) total = [sum(dices.crossdoom_rolls[x]) for x in dices.crossdoom_rolls if isinstance(x,int)] if sum(total) > 0: message.append('Totale: {}'.format(sum(total))) else: message.append('Attacco fallito.') dice_message = '\n'.join(message) await ctx.send(dice_message) except d20.errors.RollSyntaxError: await ctx.send('Espressione non valida, consulta `g.help roll`')
def test_complex_rolls(): r = d20.roll("10d6rol5mi6ma1k1[annotation] some comments", allow_comments=True) assert r.total == 10 assert r.crit == 0 assert "some comments" == r.comment r = d20.roll("5d6kh4e0kl3") assert 3 <= r.total <= 18 assert len(r.expr.roll.keptset) == 3 assert len([p for p in r.expr.roll.set if not p.kept]) == 2 r = d20.roll("10d6kh4kl3") assert 7 <= r.total <= 42 assert len(r.expr.roll.keptset) == 7 assert len([p for p in r.expr.roll.set if not p.kept]) == 3
def hitanddamage(attacker, weapon, target=None): rollplus = "" # print(attacker.feature) if attacker.feature == "Great Weapon Fighting": rollplus = "ro<3" damagebonus = attacker.damage_bonus(weapon) # print(damagebonus) rollplus += f"{damagebonus}" if weapon.magical > 0: rollplus += f"{weapon.bonus_magic:+}" new_dmg = [] for d in weapon.damage: n, s, t = d t += dmg_icon(t) new_dmg.append([n, s, t.title()]) # print(new_dmg) weapon.damage = new_dmg damage_roll_str = " + ".join([weapon.dice_roll_str, rollplus]) # print(damage_roll_str) # dice_roll_str = damage_roll_str damage_result = d20.roll(damage_roll_str) damage_result_str = f"{damage_result.total}{rollplus}" damage_total = damage_result.total output = f"Your {weapon.name} " output += "caused " if target is None else f"targets {target} for " output += f" {damage_result.result} points of {'magical ' if weapon.magical else ''}damage." print(f"\t{' '.join(output.split())}")
def run(self, autoctx): super(TempHP, self).run(autoctx) args = autoctx.args amount = self.amount maxdmg = args.last('max', None, bool, ephem=True) # check if we actually need to run this damage roll (not in combat and roll is redundant) if autoctx.target.is_simple and self.is_meta(autoctx, True): return amount = autoctx.parse_annostr(amount) dice_ast = copy.copy(d20.parse(amount)) dice_ast = _upcast_scaled_dice(self, autoctx, dice_ast) if maxdmg: dice_ast = d20.utils.tree_map(_max_mapper, dice_ast) dmgroll = roll(dice_ast) autoctx.queue(f"**THP**: {dmgroll.result}") if autoctx.target.combatant: autoctx.target.combatant.temp_hp = max(dmgroll.total, 0) autoctx.footer_queue("{}: {}".format( autoctx.target.combatant.name, autoctx.target.combatant.hp_str())) elif autoctx.target.character: autoctx.target.character.temp_hp = max(dmgroll.total, 0) autoctx.footer_queue("{}: {}".format( autoctx.target.character.name, autoctx.target.character.hp_str()))
async def _roll(self, ctx: Context, *, expr: str = '1d20'): """Rolls dice! This library has very similar syntax: https://d20.readthedocs.io/en/latest/start.html#dice-syntax""" msg: discord.Message = await ctx.send(self._d20, 'Rolling...') expr = await self._replace_macros(ctx, expr) result: d20.RollResult = d20.roll(expr) await msg.edit(content=f'{self._d20} {str(result)}')
def reroll_dynamic(self): """ Rerolls all combatant initiatives. Returns a string representing the new init order. """ rolls = {} for c in self._combatants: init_roll = roll(c.init_skill.d20()) c.init = init_roll.total rolls[c] = init_roll self.sort_combatants() # reset current turn self._turn = 0 self._current_index = None order = [] for combatant, init_roll in sorted(rolls.items(), key=lambda r: (r[1].total, int(r[0].init_skill)), reverse=True): order.append(f"{init_roll.result}: {combatant.name}") order = "\n".join(order) return order
def setup(self): global SKILLS, DAMAGETYPES if self.options['rollhp']: self.hp = d20.roll(self.data['hit_dice']).total else: self.hp = int(self.data['hit_points']) self.forms = ['material'] self.form = 0 self.challenge = float(eval(self.data['challenge_rating'])) self.challenge_display = self.data['challenge_rating'] self.proficiency_bonus = self._get_proficiency() self.skills = {} for s in SKILLS.keys(): if s in self.data['skills'].keys(): self.skills[s] = self.data['skills'][s] + 0 else: self.skills[s] = getmod(self.data[SKILLS[s]]) self.ac = self.data['armor_class'] self.vuln = self._parse_dmg_string(self.data['damage_vulnerabilities']) self.resist = self._parse_dmg_string(self.data['damage_resistances']) self.immune = self._parse_dmg_string(self.data['damage_immunities']) self.condition_immunities = self.data['condition_immunities'].split( ', ') self.actions = [self._parse_atk(atk) for atk in self.data['actions']] self.spellcasting = {} self.special_abilities = self.data['special_abilities'][:] for ability in self.data['special_abilities']: if 'spellcasting' in ability['name'].lower(): self.spellcasting[ability['name']] = self._parse_spellcasting( ability['desc']) self.abilities = {} for ability in ABILITIES: if self.data[ability + '_save'] == None: save = getmod(self.data[ability]) else: save = self.data[ability + '_save'] self.abilities[ability] = { 'score': self.data[ability], 'modifier': getmod(self.data[ability]), 'save': save } self.reactions = self.data['reactions'][:] self.legendary_actions = self.data['legendary_actions'] self.slug = self.data['slug'] self.name = self.data['name'] self.img = self.data['img_main'] self.size = self.data['size'] self.type = self.data['type'] self.alignment = self.data['alignment']
def chatbot_interpret(content, fingerprint, campaign, map): raw_split = content.split(' ') cmd = raw_split[0] args = [{'name': '__main__', 'value': []}] if len(raw_split) > 1: _args = raw_split[1:] current = 0 for a in _args: if a.startswith('-') and len(a) > 1: args.append({'name': a[1:], 'value': []}) current += 1 else: args[current]['value'].append(a) args = [{'name': i['name'], 'value': ' '.join(i['value'])} for i in args] args[0]['value'] = args[0]['value'].split(' ') logger.info( f'User {fingerprint} sent command {cmd} with args {str(args)} to map {map} in campaign {campaign}' ) if cmd in ['r', 'roll']: roll_args = [] jnr = '' for i in args[0]['value'][0]: jnr += i if i in ['+', '-', '*', '/']: roll_args.append(jnr) jnr = '' if len(jnr) > 0: roll_args.append(jnr) if 'adv' in args[0]['value']: c = 0 for arg in roll_args: if 'd' in arg: roll_args[c] = '2d' + roll_args[c].split( 'd')[1][:len(roll_args[c].split('d')[1]) - 1] + 'kh1' + cond( roll_args[c].split('d')[1] [len(roll_args[c].split('d')[1]) - 1] in [ '+', '-', '*', '/' ], roll_args[c].split('d')[1] [len(roll_args[c].split('d')[1]) - 1], '') c += 1 elif 'dis' in args[0]['value']: c = 0 for arg in roll_args: if 'd' in arg: roll_args[c] = '2d' + roll_args[c].split( 'd')[1][:len(roll_args[c].split('d')[1]) - 1] + 'kl1' + cond( roll_args[c].split('d')[1] [len(roll_args[c].split('d')[1]) - 1] in [ '+', '-', '*', '/' ], roll_args[c].split('d')[1] [len(roll_args[c].split('d')[1]) - 1], '') c += 1 roll_args = ''.join(roll_args) return str(d20.roll(roll_args))
async def roll(self, ctx: commands.Context, *, expr: str = "1d6"): """Rolls dice based on the expression given.""" result = d20.roll(expr) await HanalonEmbed( title="Dice 🎲", description=str(result), context=ctx, ).respond(True)
async def game_hp_set(self, ctx, *, hp): """Sets the character's HP to a certain value.""" character: Character = await Character.from_ctx(ctx) before = character.hp hp_roll = d20.roll(hp) character.hp = hp_roll.total await character.commit(ctx) await ctx.send(f"{character.name}: {character.hp_str()} ({character.hp - before:+})")
def critical_miss(weapon=None): print(f"{Color.RED}Critical Fail!{Color.ENDC}") def indent(my_paragraph) -> str: return f"\t{my_paragraph}" print(f"Critical fail roll for d100 is {roll(100)}" ) # for David Gavrin's table mymiss = roll(20) if mymiss == 1: if weapon: if weapon.magical: print(f"\tMagic backfires!") magic_damage = d20.roll('+'.join( [die_roll_str(weapon.damage[0]), str(weapon.bonus_magic)])) print( f"\tYou take {magic_damage.result} points of {weapon.damage[0][2]} damage." ) elif weapon.list == "ranged": print( f"\tThe bowstring on {weapon.name} broke!\n" f"\tOn your next turn you lose your Action and half your movement.\n" f"\tEither use your next turn to replace the string, or switch weapons." ) else: print( f"\tDangerous Design-Flaw! {weapon.name} broke, and injured you.\n" f"\tTake {d20.roll(die_roll_str(weapon.damage[0])).result} points of {weapon.damage[0][2]} damage." ) print(f"") # print("Weapon breaks!") elif mymiss < 4: print( "\tPoor attack: Has 50% chance to hit any character in the line of attack or within 5' of the target." ) if random.randint(0, 1): print(f"\tWhuh? Looks like you missed everyone.") else: print( f"\tYour victim takes {d20.roll(weapon.dice_roll_str).result} points of damage." ) print( f"\tAlso, due to dismay, lose your next Bonus Action (either this turn or the next turn)." ) elif mymiss < 6: print( "\tDropped weapon! Disadvantage on next attack, and lose 10ft of motion next turn to pick up weapon." ) elif mymiss < 11: print( "\tFumbled weapon! Lose half your motion until the end of your next turn as you regain your composure." ) else: print( "\tYou missed spectacularly, and your opponent snickered at your failure." )
def check(self, skill_or_ability, advdis=''): #advdis = "adv" or "dis" additional = {'adv': 'kh1', 'dis': 'kl1', '': ''}[advdis] if skill_or_ability in SKILLS.keys(): if self.skills[skill_or_ability] >= 0: return d20.roll('d20' + additional + '+' + str(self.skills[skill_or_ability])) else: return d20.roll('d20' + additional + str(self.skills[skill_or_ability])) elif skill_or_ability in ABILITIES: if self.abilities[skill_or_ability]['modifier'] >= 0: return d20.roll( 'd20' + additional + '+' + str(self.abilities[skill_or_ability]['modifier'])) else: return d20.roll( 'd20' + additional + str(self.abilities[skill_or_ability]['modifier']))
async def game_hp_set(self, ctx, *, hp): """Sets the character's HP to a certain value.""" character: Character = await Character.from_ctx(ctx) caster = await targetutils.maybe_combat_caster(ctx, character) before = caster.hp hp_roll = d20.roll(hp) caster.hp = hp_roll.total await character.commit(ctx) await gameutils.send_hp_result(ctx, caster, f"{caster.hp - before:+}")
def saving_throw(self, ability='Constitution', *, adv=False, dis=False, bless=False): import d20 if adv: roll_str = "2d20kh1" elif dis: roll_str = "2d20kl1" else: roll_str = f"1d20" my_d20 = d20.roll(roll_str).total my_d4 = d20.roll('1d4').total my_total = my_d20 + my_d4 if bless else my_d20 if 'death' in ability.lower(): print(f"{self.name} rolls a Death Saving Throw, and ", end='') if my_d20 == 1: print(f"Failed TWICE with a roll of 1.") return elif my_d20 == 20: print(f"Succeeded TWICE with a roll of 20.") return if my_total < 10: print(f"Failed with a roll of {my_total}.") return else: print(f"Succeeded with a roll of {my_total}.") return else: mymod = ability_score_mod(getattr(self, f"c{ability.lower()[0:3]}")) roll_str += f"{mymod:+}" # print(self.proficiency['save']) roll_str += f"{level_proficiency(self.level):+}" if ability in self.proficiency[ 'save'] else "" roll_str += f"+1d4" if bless else "" print( f"{self.name} performs a {ability} check : {d20.roll(roll_str)}" )
def test_gt_lt_selectors(): r = d20.roll("10d6k>6") assert r.total == 0 assert len(r.expr.roll.keptset) == 0 assert len([p for p in r.expr.roll.set if not p.kept]) == 10 r = d20.roll("10d6k<1") assert r.total == 0 assert len(r.expr.roll.keptset) == 0 assert len([p for p in r.expr.roll.set if not p.kept]) == 10 r = d20.roll("10d6rr<6") assert r.total == 60 r = d20.roll("10d6rr>1") assert r.total == 10 r = d20.roll("10d6k<6") assert 10 <= r.total <= 50 assert len(r.expr.roll.keptset) <= 10 assert all(p.number < 6 for p in r.expr.roll.keptset)
async def roll(ctx): message = ctx.message.content[2:].strip() await delete_message(ctx) if len(message) == 0: percentile = d10p.roll() unit = d10.roll() await ctx.send(f"{ctx.author.mention} rolled `{percentile + unit}`") else: result = dtwenty.roll(message.lower()) result_string = str(result).replace("**", "") await ctx.send(f"{ctx.author.mention} rolled {result_string}")
def roll(self, fp, args): # [Roll string] self.logger.debug('User ' + fp + ' is rolling ' + args[0]) try: roll = d20.roll(args[0]) elements = str(roll) print(elements) self.logger.debug('User ' + fp + ' rolled ' + args[0] + ' and got ' + str(roll.total)) return {'code': 200, 'roll': roll.total, 'elements': elements} except d20.errors.RollSyntaxError as e: self.logger.exception('Dice error: ') return {'code': 400, 'reason': 'Dice error: ' + str(e)}
def attack_roll(self, type='damage'): import d20 if type in 'attack': # self.rollstr += f"{self.bonus_attack:+}" elif type in 'damage': self.rollstr += f"{self.bonus_damage:+}" if self.rollstrappend: self.rollstr += f"{self.rollstrappend:+}" # rolls = [random.randint(1, self.sides) for _ in range(self.dice)] rolls = d20.roll(self.rollstr).total # print(rolls) return rolls
def weapon_roll(self, type='attack'): # todo: unify as 'weapon_roll('attack'|'damage) import d20 if type in 'attack': self.rollstr += f"{self.bonus_attack:+}" elif type in 'damage': self.rollstr += f"{self.bonus_damage:+}" if self.rollstrappend: self.rollstr += f"{self.rollstrappend:+}" # rolls = [random.randint(1, self.sides) for _ in range(self.dice)] rolls = d20.roll(self.rollstr).total # print(rolls) return rolls
def AddCharacter(command): try: character = command.characters[command.base[1]] characterinit = d20.roll('1d20+{}'.format( character['initiative'])).total returnMessage = "{} has rolled a {} and will be added to initiative!".format( character['charactername'], str(characterinit)) initiatebattle.add_to_initiative(command.room, character, characterinit) except: returnMessage = "This character was not found" return returnMessage