1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Merge pull request #97 from Steffo99/dndproficiency

Use proficiencies for dnd
This commit is contained in:
Steffo 2019-10-24 13:19:46 +02:00 committed by GitHub
commit 1174502290
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 447 additions and 256 deletions

View file

@ -4,4 +4,3 @@ from .commands import available_commands
from .tables import available_tables
__all__ = ["commands", "tables", "available_commands", "available_tables"]

View file

@ -6,8 +6,6 @@ from .dndinfo import DndinfoCommand
from .dndnew import DndnewCommand
from .dndedit import DndeditCommand
from .dndroll import DndrollCommand
from .dndrolladv import DndrolladvCommand
from .dndrolldis import DndrolldisCommand
# Enter the commands of your Pack here!
available_commands = [
@ -18,8 +16,6 @@ available_commands = [
DndnewCommand,
DndeditCommand,
DndrollCommand,
DndrolladvCommand,
DndrolldisCommand,
]
# Don't change this, it should automatically generate __all__

View file

@ -1,60 +1,35 @@
import re
from royalnet.commands import *
from .dndnew import DndnewCommand
from ..tables import DndCharacter, DndActiveCharacter
class DndeditCommand(Command):
class DndeditCommand(DndnewCommand):
name: str = "dndedit"
description: str = "Edit the active DnD character."
aliases = ["de", "dnde", "edit", "dedit"]
syntax = "{name}\n" \
"LV {level}\n" \
"\n" \
"STR {strength}\n" \
"DEX {dexterity}\n" \
"CON {constitution}\n" \
"INT {intelligence}\n" \
"WIS {wisdom}\n" \
"CHA {charisma}\n" \
"\n" \
"MAXHP {max_hp}\n" \
"AC {armor_class}"
tables = {DndCharacter, DndActiveCharacter}
async def run(self, args: CommandArgs, data: CommandData) -> None:
name, level, strength, dexterity, constitution, intelligence, wisdom, charisma, max_hp, armor_class = \
args.match(r"([\w ]+\w)\s*"
r"LV\s+(\d+)\s+"
r"STR\s+(\d+)\s+"
r"DEX\s+(\d+)\s+"
r"CON\s+(\d+)\s+"
r"INT\s+(\d+)\s+"
r"WIS\s+(\d+)\s+"
r"CHA\s+(\d+)\s+"
r"MAXHP\s+(\d+)\s+"
r"AC\s+(\d+)", re.IGNORECASE)
try:
int(name)
except ValueError:
pass
else:
raise CommandError("Character names cannot be composed of only a number.")
character_sheet = args.joined()
if character_sheet == "":
await data.reply(self._syntax())
return
author = await data.get_author(error_if_none=True)
char = author.dnd_active_character.character
char.name = name
char.level = level
char.strength = strength
char.dexterity = dexterity
char.constitution = constitution
char.intelligence = intelligence
char.wisdom = wisdom
char.charisma = charisma
char.max_hp = max_hp
char.armor_class = armor_class
data.session.add(char)
if author.dnd_active_character is None:
raise CommandError("You don't have an active character.")
char: DndCharacter = author.dnd_active_character.character
arguments = self._parse(character_sheet)
for key in arguments:
char.__setattr__(key, arguments[key])
await data.session_commit()
await data.reply(f"✅ Edit successful!")

View file

@ -1,6 +1,8 @@
import re
# noinspection PyUnresolvedReferences
from royalnet.commands import *
from ..tables import DndCharacter
from ..utils import DndProficiencyType
class DndnewCommand(Command):
@ -10,51 +12,60 @@ class DndnewCommand(Command):
aliases = ["dn", "dndn", "new", "dnew"]
syntax = "{name}\n" \
"LV {level}\n" \
"\n" \
"STR {strength}\n" \
"DEX {dexterity}\n" \
"CON {constitution}\n" \
"INT {intelligence}\n" \
"WIS {wisdom}\n" \
"CHA {charisma}\n" \
"\n" \
"MAXHP {max_hp}\n" \
"AC {armor_class}"
syntax = "{name}\n{character_sheet}"
tables = {DndCharacter}
def _search_value(self, name: str, string: str):
return re.search(r"\s*" + name + r"\s*([0-9]+)\s*", string, re.IGNORECASE)
def _parse(self, character_sheet: str) -> dict:
columns = list(self.alchemy.DndCharacter.__table__.columns)
column_names = [column.name for column in columns if (not column.primary_key and
not column.foreign_keys and
column.name != "name")]
arguments = {}
for column_name in column_names:
match = self._search_value(column_name, character_sheet)
if match:
if column_name.endswith("_proficiency"):
arguments[column_name] = DndProficiencyType(float(match.group(1)))
else:
arguments[column_name] = match.group(1)
return arguments
def _syntax(self) -> str:
columns = list(self.alchemy.DndCharacter.__table__.columns)
column_names = [column.name for column in columns if (not column.primary_key and
not column.foreign_keys and
column.name != "name")]
message = " Character Sheet syntax:\n[p]\nName\n"
for column_name in column_names:
message += f"{column_name} _\n"
message += "[/p]"
return message
async def run(self, args: CommandArgs, data: CommandData) -> None:
name, level, strength, dexterity, constitution, intelligence, wisdom, charisma, max_hp, armor_class = \
args.match(r"([\w ]+\w)\s*"
r"LV\s+(\d+)\s+"
r"STR\s+(\d+)\s+"
r"DEX\s+(\d+)\s+"
r"COS\s+(\d+)\s+"
r"INT\s+(\d+)\s+"
r"WIS\s+(\d+)\s+"
r"CHA\s+(\d+)\s+"
r"MAXHP\s+(\d+)\s+"
r"AC\s+(\d+)", re.IGNORECASE)
character_sheet = args.joined()
if character_sheet == "":
await data.reply(self._syntax())
return
creator = await data.get_author()
name, rest = character_sheet.split("\n", 1)
character = self.alchemy.DndCharacter(name=name, creator=creator, **self._parse(rest))
data.session.add(character)
try:
int(name)
except ValueError:
pass
else:
raise CommandError("Character names cannot be composed of only a number.")
author = await data.get_author(error_if_none=True)
char = self.alchemy.DndCharacter(name=name,
level=level,
strength=strength,
dexterity=dexterity,
constitution=constitution,
intelligence=intelligence,
wisdom=wisdom,
charisma=charisma,
max_hp=max_hp,
armor_class=armor_class,
creator=author)
data.session.add(char)
await data.session_commit()
await data.reply(f"✅ Character [b]{char.name}[/b] ([c]{char.character_id}[/c]) was created!")
await data.session_commit()
except Exception as err:
# THIS IS INTENDED
if err.__class__.__name__ == "IntegrityError":
param_name = re.search(r'in column "(\S+)"', err.args[0]).group(1)
raise CommandError(f"Mandatory parameter '{param_name}' is missing.")
raise
await data.reply(f"✅ Character [b]{character.name}[/b] created!")

View file

@ -1,94 +1,146 @@
import typing
import re
import random
from royalnet.commands import *
from royalnet.utils import plusformat
from ..tables import DndCharacter, DndActiveCharacter
from royalnet.utils import plusformat
class DndrollCommand(Command):
name: str = "dndroll"
description: str = "Roll as the active DnD character."
description: str = "Roll dice as the active DnD character."
aliases = ["dr", "dndr", "droll"]
syntax = "{stat} [proficiency] [modifier]"
aliases = ["dr", "dndr", "roll", "droll"]
tables = {DndCharacter, DndActiveCharacter}
@staticmethod
def _roll():
return random.randrange(1, 21)
_skill_names = {
"str": "strength",
"for": "strength",
"dex": "dexterity",
"des": "dexterity",
"con": "constitution",
"cos": "constitution",
"inte": "intelligence",
"wis": "wisdom",
"sag": "wisdom",
"cha": "charisma",
"car": "charisma",
_roll_string = "1d20"
"ststr": "strength_save",
"stfor": "strength_save",
"stdex": "dexterity_save",
"stdes": "dexterity_save",
"stcon": "constitution_save",
"stcos": "constitution_save",
"stint": "intelligence_save",
"stwis": "wisdom_save",
"stsag": "wisdom_save",
"stcha": "charisma_save",
"stcar": "charisma_save",
"tsstr": "strength_save",
"tsfor": "strength_save",
"tsdex": "dexterity_save",
"tsdes": "dexterity_save",
"tscon": "constitution_save",
"tscos": "constitution_save",
"tsint": "intelligence_save",
"tswis": "wisdom_save",
"tssag": "wisdom_save",
"tscha": "charisma_save",
"tscar": "charisma_save",
"acr": "acrobatics",
"add": "animal_handling",
"ani": "animal_handling",
"arc": "arcana",
"ath": "athletics",
"dec": "deception",
"ing": "deception",
"his": "history",
"sto": "history",
"ins": "insight",
"intu": "insight",
"inti": "intimidation",
"inv": "investigation",
"med": "medicine",
"nat": "nature",
"perc": "perception",
"perf": "performance",
"pers": "persuasion",
"rel": "religion",
"sle": "sleight_of_hand",
"soh": "sleight_of_hand",
"rap": "sleight_of_hand",
"ste": "stealth",
"nas": "stealth",
"sur": "survival",
"sop": "sopravvivenza",
}
async def run(self, args: CommandArgs, data: CommandData) -> None:
author = await data.get_author(error_if_none=True)
if author.dnd_active_character is None:
raise CommandError("You don't have an active character.")
char: DndCharacter = author.dnd_active_character.character
stat: str = args[0]
second: typing.Optional[str] = args.optional(1)
third: typing.Optional[str] = args.optional(2)
first = args[0]
second = args.optional(1)
third = args.optional(2)
advantage = False
disadvantage = False
extra_modifier = 0
if third:
extra_mod: int = int(third)
else:
extra_mod: int = 0
if second:
if second.startswith("e") or second.startswith("x"):
proficiency_mul: float = 2.0
proficiency_name: str = " with Expertise"
elif second.startswith("f") or second.startswith("n") or second.startswith("c"):
proficiency_mul: float = 1.0
proficiency_name: str = " with Proficiency"
elif second.startswith("h") or second == "/" or second.startswith("m"):
proficiency_mul: float = 0.5
proficiency_name: str = " with Half Proficiency"
elif second.startswith("h") or second == "/" or second.startswith("m"):
proficiency_mul: float = 0.0
proficiency_name: str = " [i]without Proficiency[/i]"
try:
extra_modifier = int(third)
except ValueError:
raise InvalidInputError("Invalid modifier value (third parameter).")
if second.startswith("a") or second.startswith("v"):
advantage = True
elif second.startswith("d") or second.startswith("d"):
disadvantage = True
else:
raise CommandError(f"Unknown proficiency type '{second}'")
proficiency_mod: int = int(char.proficiency_bonus * proficiency_mul)
raise InvalidInputError("Invalid advantage string (second parameter).")
elif second:
try:
extra_modifier = int(second)
except ValueError:
if second.startswith("a") or second.startswith("v"):
advantage = True
elif second.startswith("d") or second.startswith("d"):
disadvantage = True
else:
raise InvalidInputError("Invalid modifier value or advantage string (second parameter).")
skill_short_name = first
for root in self._skill_names:
if skill_short_name.startswith(root):
skill_name = self._skill_names[root]
break
else:
proficiency_name: str = ""
proficiency_mod: int = 0
raise CommandError("Invalid skill name (first parameter).")
if stat.startswith("st") or stat.startswith("fo"):
stat_mod: int = char.strength_mod
stat_name: str = "[i]STR[/i]"
elif stat.startswith("de"):
stat_mod: int = char.dexterity_mod
stat_name: str = "[i]DEX[/i]"
elif stat.startswith("co"):
stat_mod: int = char.constitution_mod
stat_name: str = "[i]CON[/i]"
elif stat.startswith("in"):
stat_mod: int = char.intelligence_mod
stat_name: str = "[i]INT[/i]"
elif stat.startswith("wi") or stat.startswith("sa"):
stat_mod: int = char.wisdom_mod
stat_name: str = "[i]WIS[/i]"
elif stat.startswith("ch") or stat.startswith("ca"):
stat_mod: int = char.charisma_mod
stat_name: str = "[i]CHA[/i]"
skill_modifier = char.__getattribute__(skill_name)
modifier = skill_modifier + extra_modifier
modifier_str = plusformat(modifier, empty_if_zero=True)
if advantage:
roll_a = random.randrange(1, 21)
roll_b = random.randrange(1, 21)
roll = max([roll_a, roll_b])
total = roll + modifier
await data.reply(f"🎲 2d20h1{modifier_str} = ({roll_a}|{roll_b}){modifier_str} = [b]{total}[/b]")
elif disadvantage:
roll_a = random.randrange(1, 21)
roll_b = random.randrange(1, 21)
roll = min([roll_a, roll_b])
total = roll + modifier
await data.reply(f"🎲 2d20l1{modifier_str} = ({roll_a}|{roll_b}){modifier_str} = [b]{total}[/b]")
else:
raise CommandError(f"Unknown stat '{stat}'")
total_mod = stat_mod + proficiency_mod + extra_mod
roll = self._roll()
result = roll + total_mod
await data.reply(f"🎲 Rolling {stat_name}{proficiency_name}{plusformat(extra_mod, empty_if_zero=True)}:\n"
f"{self._roll_string}"
f"{plusformat(stat_mod, empty_if_zero=True)}"
f"{plusformat(proficiency_mod, empty_if_zero=True)}"
f"{plusformat(extra_mod, empty_if_zero=True)}"
f" = "
f"{roll}{plusformat(total_mod, empty_if_zero=True)}"
f" = "
f"[b]{result}[/b]")
roll = random.randrange(1, 21)
total = roll + modifier
await data.reply(f"🎲 1d20{modifier_str} = {roll}{modifier_str} = [b]{total}[/b]")

View file

@ -1,26 +0,0 @@
import typing
import random
from royalnet.commands import *
from royalnet.utils import plusformat
from .dndroll import DndrollCommand
from ..tables import DndCharacter, DndActiveCharacter
class DndrolladvCommand(DndrollCommand):
name: str = "dndrolladv"
description: str = "Roll with advantage as the active DnD character."
aliases = ["dra", "dndra", "drolladv"]
syntax = "{stat} [proficiency] [modifier]"
tables = {DndCharacter, DndActiveCharacter}
@staticmethod
def _roll():
first = random.randrange(1, 21)
second = random.randrange(1, 21)
return max(first, second)
_roll_string = "2d20h1"

View file

@ -1,26 +0,0 @@
import typing
import random
from royalnet.commands import *
from royalnet.utils import plusformat
from .dndroll import DndrollCommand
from ..tables import DndCharacter, DndActiveCharacter
class DndrolldisCommand(DndrollCommand):
name: str = "dndrolldis"
description: str = "Roll with disadvantage as the active DnD character."
aliases = ["drd", "dndrd", "drolldis"]
syntax = "{stat} [proficiency] [modifier]"
tables = {DndCharacter, DndActiveCharacter}
@staticmethod
def _roll():
first = random.randrange(1, 21)
second = random.randrange(1, 21)
return min(first, second)
_roll_string = "2d20l1"

View file

@ -1,6 +1,7 @@
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import *
from ..utils import DndProficiencyType
class DndCharacter:
@ -20,71 +21,267 @@ class DndCharacter:
@declared_attr
def name(self):
return Column(String)
return Column(String, nullable=False)
@declared_attr
def strength_score(self):
return Column(Integer, nullable=False)
@property
def strength(self):
return Column(Integer)
@property
def strength_mod(self):
return (self.strength - 10) // 2
return (self.strength_score - 10) // 2
@declared_attr
def dexterity_score(self):
return Column(Integer, nullable=False)
@property
def dexterity(self):
return Column(Integer)
@property
def dexterity_mod(self):
return (self.dexterity - 10) // 2
return (self.dexterity_score - 10) // 2
@declared_attr
def constitution_score(self):
return Column(Integer, nullable=False)
@property
def constitution(self):
return Column(Integer)
@property
def constitution_mod(self):
return (self.constitution - 10) // 2
return (self.constitution_score - 10) // 2
@declared_attr
def intelligence_score(self):
return Column(Integer, nullable=False)
@property
def intelligence(self):
return Column(Integer)
@property
def intelligence_mod(self):
return (self.intelligence - 10) // 2
return (self.intelligence_score - 10) // 2
@declared_attr
def wisdom_score(self):
return Column(Integer, nullable=False)
@property
def wisdom(self):
return Column(Integer)
@property
def wisdom_mod(self):
return (self.wisdom - 10) // 2
return (self.wisdom_score - 10) // 2
@declared_attr
def charisma(self):
return Column(Integer)
def charisma_score(self):
return Column(Integer, nullable=False)
@property
def charisma_mod(self):
return (self.charisma - 10) // 2
def charisma(self):
return (self.charisma_score - 10) // 2
@declared_attr
def level(self):
return Column(Integer)
return Column(Integer, nullable=False)
@property
def proficiency_bonus(self):
return ((self.level - 1) // 4) + 2
@declared_attr
def current_hp(self):
return Column(Integer, nullable=False)
@declared_attr
def max_hp(self):
return Column(Integer)
return Column(Integer, nullable=False)
@declared_attr
def armor_class(self):
return Column(Integer)
return Column(Integer, nullable=False)
@declared_attr
def strength_save_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def dexterity_save_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def constitution_save_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def intelligence_save_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def wisdom_save_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def charisma_save_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def acrobatics_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def animal_handling_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def arcana_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def athletics_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def deception_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def history_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def insight_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def intimidation_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def investigation_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def medicine_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def nature_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def perception_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def performance_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def persuasion_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def religion_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def sleight_of_hand_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def stealth_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@declared_attr
def survival_proficiency(self):
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
@property
def strength_save(self):
return self.strength + self.proficiency_bonus * self.strength_save_proficiency.value
@property
def dexterity_save(self):
return self.dexterity + self.proficiency_bonus * self.dexterity_save_proficiency.value
@property
def constitution_save(self):
return self.constitution + self.proficiency_bonus * self.constitution_save_proficiency.value
@property
def intelligence_save(self):
return self.intelligence + self.proficiency_bonus * self.intelligence_save_proficiency.value
@property
def wisdom_save(self):
return self.wisdom + self.proficiency_bonus * self.wisdom_save_proficiency.value
@property
def charisma_save(self):
return self.charisma + self.proficiency_bonus * self.charisma_save_proficiency.value
@property
def acrobatics(self):
return self.dexterity + self.proficiency_bonus * self.acrobatics_proficiency.value
@property
def animal_handling(self):
return self.wisdom + self.proficiency_bonus * self.animal_handling_proficiency.value
@property
def arcana(self):
return self.intelligence + self.proficiency_bonus * self.arcana_proficiency.value
@property
def athletics(self):
return self.strength + self.proficiency_bonus * self.athletics_proficiency.value
@property
def deception(self):
return self.charisma + self.proficiency_bonus * self.deception_proficiency.value
@property
def history(self):
return self.intelligence + self.proficiency_bonus * self.history_proficiency.value
@property
def insight(self):
return self.wisdom + self.proficiency_bonus * self.insight_proficiency.value
@property
def intimidation(self):
return self.charisma + self.proficiency_bonus * self.intimidation_proficiency.value
@property
def investigation(self):
return self.intelligence + self.proficiency_bonus * self.investigation_proficiency.value
@property
def medicine(self):
return self.wisdom + self.proficiency_bonus * self.medicine_proficiency.value
@property
def nature(self):
return self.intelligence + self.proficiency_bonus * self.nature_proficiency.value
@property
def perception(self):
return self.wisdom + self.proficiency_bonus * self.perception_proficiency.value
@property
def performance(self):
return self.charisma + self.proficiency_bonus * self.performance_proficiency.value
@property
def persuasion(self):
return self.charisma + self.proficiency_bonus * self.persuasion_proficiency.value
@property
def religion(self):
return self.intelligence + self.proficiency_bonus * self.religion_proficiency.value
@property
def sleight_of_hand(self):
return self.dexterity + self.proficiency_bonus * self.sleight_of_hand_proficiency.value
@property
def stealth(self):
return self.dexterity + self.proficiency_bonus * self.stealth_proficiency.value
@property
def survival(self):
return self.wisdom + self.proficiency_bonus * self.survival_proficiency.value
def __repr__(self):
return f"<{self.__class__.__qualname__} {self.name}>"
@ -92,14 +289,13 @@ class DndCharacter:
def __str__(self):
return f"{self.name}"
def character_sheet(self):
return f"{self.name}\n" \
f"LV {self.level}\n\n" \
f"STR {self.strength}\n" \
f"DEX {self.dexterity}\n" \
f"CON {self.constitution}\n" \
f"INT {self.intelligence}\n" \
f"WIS {self.wisdom}\n" \
f"CHA {self.charisma}\n\n" \
f"MAXHP {self.max_hp}\n" \
f"AC {self.armor_class}\n" \
def character_sheet(self) -> str:
columns = list(self.__class__.__table__.columns)
column_names = [column.name for column in columns if (not column.primary_key and
not column.foreign_keys and
column.name != "name")]
message = f"[b]{self.name}[/b]\n"
for column_name in column_names:
value = self.__getattribute__(column_name)
message += f"{column_name} {value}\n"
return message

View file

@ -0,0 +1,3 @@
from .dndproficiencytype import DndProficiencyType
__all__ = ["DndProficiencyType"]

View file

@ -0,0 +1,11 @@
import enum
class DndProficiencyType(enum.Enum):
NONE = 0
HALF_PROFICIENCY = 0.5
FULL_PROFICIENCY = 1
EXPERTISE = 2
def __str__(self):
return str(self.value)