def getMeleeAtks(self, item_name=None): items = [] if item_name is None: items = [w for w, _ in self.equipment.getWieldedItems()] else: itemno = self.equipment.find(item_name) if itemno >= 0: items = [self.equipment.equipment[itemno]] weapons = [] for weapon in items: if not weapon.isWeapon(): continue if not weapon.isWielding(): continue if not weapon.hasTag("melee"): continue weapons.append(weapon) if len(weapons) == 0: raise NotWieldingItems out = [] str = self.stats.getMod("str") for idx, weapon in enumerate(weapons): desc = f"{weapon.description}" atk = f"1d20 +{self.getBtH()} [BtH] +{weapon.bth} [w BtH]" atk += f" +{str} [str]" if len(weapons) > 1: dex = self.stats.getMod("dex") dw_malus = 3 * (1 + idx) atk += f" -{dw_malus} [dual w] +{dex} [dex]" dmg = f"{weapon.damage} [w dmg]" dmg += f" +{str} [str]" if (self.god == "Thor" and self.xclass == "Cleric" and weapon.description.lower().find("hammer") != -1): dmg += f" +2 [god]" out.append(desc) out.append("- atk:") out.append(roll(atk).__str__()) out.append("- dmg:") out.append(roll(dmg).__str__()) return "\n".join(out)
async def rrr(self, ctx, iterations: int, rollStr, dc: int = 0, *, args=""): """Rolls dice in xdy format, given a set dc. Usage: !rrr <iterations> <xdy> <DC> [args]""" if iterations < 1 or iterations > 100: return await ctx.send("Too many or too few iterations.") adv = 0 out = [] successes = 0 if re.search("(^|\s+)(adv|dis)(\s+|$)", args) is not None: adv = 1 if re.search("(^|\s+)adv(\s+|$)", args) is not None else -1 args = re.sub("(adv|dis)(\s+|$)", "", args) for r in range(iterations): res = roll(rollStr, adv=adv, rollFor=args, inline=True) if res.plain >= dc: successes += 1 out.append(res) outStr = "Rolling {} iterations, DC {}...\n".format(iterations, dc) outStr += "\n".join([o.skeleton for o in out]) if len(outStr) < 1500: outStr += "\n{} successes.".format(str(successes)) else: outStr = "Rolling {} iterations, DC {}...\n[Output truncated due to length]\n".format( iterations, dc) + "{} successes.".format(str(successes)) await try_delete(ctx.message) await ctx.send(ctx.author.mention + "\n" + outStr) await Stats.increase_stat(ctx, "dice_rolled_life")
async def rr(self, ctx, iterations: int, rollStr, *, args=""): """Rolls dice in xdy format a given number of times. Usage: !rr <iterations> <xdy> [args]""" if iterations < 1 or iterations > 100: return await ctx.send("Too many or too few iterations.") adv = 0 out = [] if re.search("(^|\s+)(adv|dis)(\s+|$)", args) is not None: adv = 1 if re.search("(^|\s+)adv(\s+|$)", args) is not None else -1 args = re.sub("(adv|dis)(\s+|$)", "", args) for _ in range(iterations): res = roll(rollStr, adv=adv, rollFor=args, inline=True) out.append(res) outStr = "Rolling {} iterations...\n".format(iterations) outStr += "\n".join([o.skeleton for o in out]) if len(outStr) < 1500: outStr += "\n{} total.".format(sum(o.total for o in out)) else: outStr = ( "Rolling {} iterations...\n[Output truncated due to length]\n". format(iterations) + "{} total.".format(sum(o.total for o in out))) await try_delete(ctx.message) await ctx.send(ctx.author.mention + "\n" + outStr) await Stats.increase_stat(ctx, "dice_rolled_life")
def levelUp(self): """Level up and roll new hit points.""" hd: int if self.xclass == "Barbarian" or self.xclass == "Monk": hd = 12 elif (self.xclass == "Fighter" or self.xclass == "Ranger" or self.xclass == "Knight" or self.xclass == "Paladin" or self.xclass == "Bard"): hd = 10 elif self.xclass == "Cleric" or self.xclass == "Druid": hd = 8 elif self.xclass == "Rogue" or self.xclass == "Assassin": hd = 6 elif self.xclass == "Wizard" or self.xclass == "Illusionist": hd = 4 else: raise InvalidArgument(f"{self.xclass} is not a valid class.") con_mod = self.stats.getMod("con") result = [roll(f"1d{hd}{con_mod:+}", inline=True) for _ in range(1)] total = result[0].total if total < 1: total = 1 self.hp.max += total self.hp.current += total self.level += 1 hptxt = "hit points" if total == 1: hptxt = "hit point" return ( f"{self.name} levels up! :partying_face:\n" + f":game_die: {result[0].skeleton}\n" + f"{self.name} advances to level {self.level} and gains {total} {hptxt} (total hp: {self.hp.max})." )
def siegeCheck(self, name: str, level: int, stat: str, bonus: int, cl: int): cb = 18 mod = self.getMod(stat) prime = self.getPrime(stat) all_mods = level + mod + prime + bonus result = [roll(f"1d20{all_mods:+}", inline=True) for _ in range(1)] total = result[0].total if total > cb and cl == 0: success = f"Success against CL{total-cb}!" elif total >= cb + cl: success = f"Success (CL{cl})! :grinning:" else: success = (f"Failure ({total-cb})" if name == "secret_check" else "Failure! :scream:") # Long-form result for normal checks known_cl = f"against challenge level {cl}" if cl > 0 else "" bonuses = f"{level:+} for level" if mod != 0: bonuses = bonuses + f", {mod:+} modifier" if prime != 0: bonuses = bonuses + f", {prime:+} for prime attribute" if bonus != 0: bonuses = bonuses + f", {bonus:+} bonus" if name == "secret_check": return f"{success} \nBonuses are {bonuses} = {all_mods:+}\n:game_die: {result[0].skeleton}" else: return f"{name} makes a {stat.upper()} check {known_cl}\nBonuses are {bonuses} = {all_mods:+}\n:game_die: {result[0].skeleton}\n{success}"
def rest(self, name: str, conMod: int, duration: int): if self.wound == Wound.DEAD: return f"{name} gets rigor mortis." result = "" if not self.conscious: self.conscious = True hours = [roll("1d6", inline=True) for _ in range(1)] result = f"{name} recovers consciousness after {hours[0].total} hours." if self.wound == Wound.MORTAL: self.wound = Wound.GRIEVOUS result += f"\n{name} is no longer mortally wounded." duration -= 1 if duration > 0 and self.wound == Wound.GRIEVOUS: self.wound = Wound.NORMAL result += f"\n{name} is no longer greviously wounded." duration -= 1 if duration > 0 and duration < 7: result += self.recover(name, duration) duration = 0 elif duration > 0: result += self.recover(name, 7) duration -= 7 if duration > 0: heal_rate = 1 if conMod > 0: heal_rate += conMod result += self.recover(name, duration * heal_rate) return result
def first_aid(self): all_mods = (self.getLevel() + self.stats.getMod("con") + self.stats.getPrime("con")) result = [roll(f"1d20{all_mods:+}", inline=True) for _ in range(1)] total = result[0].total success = total > 18 check = f"{self.getName()} makes a Constitution check.\n:game_die: {result[0].skeleton}" return self.getHp().first_aid(self.getName(), check, success)
def rollHp(self, already_rolled: int = 0): result = [ roll(f"{self.hd}", inline=True) for _ in range(self.count - already_rolled) ] for hp in result: print(f"{hp.skeleton}") return [hp.total for hp in result]
async def genStats(self, ctx): """Randomly generate the six base stats for a new character.""" rolls = [roll("4d6kh3", inline=True) for _ in range(6)] # self.stats.set(rolls[0].total, rolls[1].total, rolls[2].total, rolls[3].total, rolls[4].total, rolls[5].total) stat_summary = "\n:game_die: ".join(r.skeleton for r in rolls) total = sum([r.total for r in rolls]) await ctx.send( f"{ctx.message.author.mention}\nGenerated random stats:\n:game_die: {stat_summary}\nTotal = `{total}`" )
def getShootAtk(self, ammo_name): itemno = self.equipment.find(ammo_name) if itemno >= 0: ammo = self.equipment.equipment[itemno] else: raise ItemNotFound weapon = self.equipment.getWieldedItems() if not weapon: raise NotWieldingItems weapon = weapon[0][0] if not weapon.hasTag("shoot"): raise NotWieldingItems elif not ammo.isAmmo(): raise NotWieldingItems( f"The item {ammo.description} is not an ammo.") elif not ammo.hasAnyTag(list(weapon.tags)): raise NotWieldingItems( f"The wielded weapon and the ammo are not compatible (they do not share tags)." ) out = [] str = self.stats.getMod("dex") desc = f"{weapon.description}" atk = ( f"1d20 +{self.getBtH()} [BtH] +{weapon.bth} [w BtH] +{ammo.bth} [ammo BtH]" ) atk += f" +{str} [dex]" dmg = f"{weapon.damage} [w dmg] +{ammo.damage} [ammo dmg]" dmg += f" +{str} [str]" out.append(desc) out.append("- atk:") out.append(roll(atk).__str__()) out.append("- dmg:") out.append(roll(dmg).__str__()) self.equipment.drop(ammo.description, 1) return "\n".join(out)
def getThrowAtk(self, ammo_or_weapon_name): itemno = self.equipment.find(ammo_or_weapon_name) if itemno >= 0: weapon = self.equipment.equipment[itemno] else: raise ItemNotFound if not weapon.hasTag("throw"): raise NotWieldingItems elif not weapon.isAmmo() and not weapon.isWeapon(): raise NotWieldingItems out = [] str = self.stats.getMod("str") dex = self.stats.getMod("dex") desc = f"{weapon.description}" atk = f"1d20 +{self.getBtH()} [BtH] +{weapon.bth} [w BtH]" atk += f" +{dex} [dex]" if (self.god == "Thor" and self.xclass == "Cleric" and weapon.description.lower().find("hammer") != -1): atk += " +3 [god]" dmg = f"{weapon.damage} [w dmg]" dmg += f" +{str} [str]" if (self.god == "Thor" and self.xclass == "Cleric" and weapon.description.lower().find("hammer") != -1): dmg += f" +2 [god]" out.append(desc) out.append("- atk:") out.append(roll(atk).__str__()) out.append("- dmg:") out.append(roll(dmg).__str__()) if weapon.isAmmo(): self.equipment.drop(weapon.description, 1) elif weapon.isWeapon(): self.equipment.markAsDropped(weapon.description) return "\n".join(out)
def heal(self, heal: str): heal_roll = "" heal_amt: int try: int(heal) heal_amt = int(heal) except ValueError: result = [roll(f"{heal}", inline=True) for _ in range(1)] heal_amt = result[0].total heal_roll = f":game_die: {result[0].skeleton}\n" return f"{heal_roll}{self.getHp().heal(self.getName(), heal_amt)}"
def damage(self, dmg: str): dmg_roll = "" dmg_amt: int try: int(dmg) dmg_amt = int(dmg) except ValueError: result = [roll(f"{dmg}", inline=True) for _ in range(1)] dmg_amt = result[0].total dmg_roll = f":game_die: {result[0].skeleton}\n" return f"{dmg_roll}{self.getHp().lose(self.getName(), dmg_amt)}"
async def rollCmd(self, ctx, *, rollStr: str = "1d20"): """Rolls dice in xdy format. __Examples__ !r xdy Attack! !r xdy+z adv Attack with Advantage! !r xdy-z dis Hide with Heavy Armor! !r xdy+xdy*z !r XdYkhZ !r 4d6mi2[fire] Elemental Adept, Fire !r 2d6e6 Explode on 6 !r 10d6ra6 Spell Bombardment !r 4d6ro<3 Great Weapon Master __Supported Operators__ k (keep) p (drop) ro (reroll once) rr (reroll infinitely) mi/ma (min/max result) e (explode dice of value) ra (reroll and add) __Supported Selectors__ lX (lowest X) hX (highest X) >X/<X (greater than or less than X)""" if rollStr == "0/0": # easter eggs return await ctx.send( "What do you expect me to do, destroy the universe?") adv = 0 if re.search("(^|\s+)(adv|dis)(\s+|$)", rollStr) is not None: adv = 1 if re.search("(^|\s+)adv(\s+|$)", rollStr) is not None else -1 rollStr = re.sub("(adv|dis)(\s+|$)", "", rollStr) res = roll(rollStr, adv=adv) out = res.result await try_delete(ctx.message) outStr = ctx.author.mention + " :game_die:\n" + out if len(outStr) > 1999: await ctx.send( ctx.author.mention + " :game_die:\n[Output truncated due to length]\n**Result:** " + str(res.plain)) else: await ctx.send(outStr) await Stats.increase_stat(ctx, "dice_rolled_life")
def rollForInitiative(self): dex_mod = self.stats.getMod("dex") result = [roll(f"2d6{dex_mod:+}", inline=True) for _ in range(1)] init = result[0].total return (init, f":game_die: {self.getName()} rolls 2d6{dex_mod:+} = {init}\n")