mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
Second commit
This commit is contained in:
parent
15a3cb84eb
commit
1bd40ef9cd
22 changed files with 987 additions and 23 deletions
|
@ -0,0 +1,4 @@
|
|||
royalnet>=5.0a1
|
||||
aiohttp>=3.6.2
|
||||
dice>=2.4.2
|
||||
sortedcontainers>=2.1.0
|
|
@ -1,6 +1,6 @@
|
|||
# This is a template Pack __init__. You can use this without changing anything in other packages too!
|
||||
|
||||
from . import commands, tables, stars
|
||||
from . import commands, tables, stars, version
|
||||
from .commands import available_commands
|
||||
from .tables import available_tables
|
||||
from .stars import available_page_stars, available_exception_stars
|
||||
|
@ -9,9 +9,9 @@ __all__ = [
|
|||
"commands",
|
||||
"tables",
|
||||
"stars",
|
||||
"version",
|
||||
"available_commands",
|
||||
"available_tables",
|
||||
"available_page_stars",
|
||||
"available_exception_stars",
|
||||
]
|
||||
|
26
rpgpack/commands/__init__.py
Normal file
26
rpgpack/commands/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Imports go here!
|
||||
from .roll import RollCommand
|
||||
from .dice import DiceCommand
|
||||
from .dndactive import DndactiveCommand
|
||||
from .dndinfo import DndinfoCommand
|
||||
from .dndnew import DndnewCommand
|
||||
from .dndedit import DndeditCommand
|
||||
from .dndroll import DndrollCommand
|
||||
from .dnditem import DnditemCommand
|
||||
from .dndspell import DndspellCommand
|
||||
|
||||
# Enter the commands of your Pack here!
|
||||
available_commands = [
|
||||
RollCommand,
|
||||
DiceCommand,
|
||||
DndactiveCommand,
|
||||
DndinfoCommand,
|
||||
DndnewCommand,
|
||||
DndeditCommand,
|
||||
DndrollCommand,
|
||||
DnditemCommand,
|
||||
DndspellCommand,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
__all__ = [command.__name__ for command in available_commands]
|
40
rpgpack/commands/dice.py
Normal file
40
rpgpack/commands/dice.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
import dice
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
class DiceCommand(Command):
|
||||
name: str = "dice"
|
||||
|
||||
description: str = "Roll a dice, using 'dice'."
|
||||
|
||||
syntax = "{dice}"
|
||||
|
||||
aliases = ["d"]
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
dice_str = args.joined(require_at_least=1)
|
||||
try:
|
||||
roll = dice.roll(dice_str)
|
||||
except dice.DiceFatalException as e:
|
||||
raise CommandError(e.msg)
|
||||
except dice.DiceException as e:
|
||||
raise CommandError(e.msg)
|
||||
except dice.DiceBaseException as e:
|
||||
raise CommandError(str(e))
|
||||
try:
|
||||
result = list(roll)
|
||||
except TypeError:
|
||||
result = [roll]
|
||||
message = f"🎲 {dice_str}"
|
||||
total = 0
|
||||
if len(result) > 1:
|
||||
message += f" = "
|
||||
for index, die in enumerate(result):
|
||||
message += f"{die}"
|
||||
total += int(die)
|
||||
if (index + 1) < len(result):
|
||||
message += "+"
|
||||
else:
|
||||
total += int(result[0])
|
||||
message += f" = [b]{total}[/b]"
|
||||
await data.reply(message)
|
56
rpgpack/commands/dndactive.py
Normal file
56
rpgpack/commands/dndactive.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
from royalnet.commands import *
|
||||
from royalnet.utils import asyncify
|
||||
from ..tables import DndCharacter, DndActiveCharacter
|
||||
|
||||
|
||||
class DndactiveCommand(Command):
|
||||
name: str = "dndactive"
|
||||
|
||||
description: str = "Set a DnD character as active."
|
||||
|
||||
aliases = ["da", "dnda", "active", "dactive"]
|
||||
|
||||
syntax = "{name|id}"
|
||||
|
||||
tables = {DndCharacter, DndActiveCharacter}
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
identifier = args.optional(0)
|
||||
author = await data.get_author(error_if_none=True)
|
||||
if identifier is None:
|
||||
# Display the active character
|
||||
if author.dnd_active_character is None:
|
||||
await data.reply("ℹ️ You have no active characters.")
|
||||
else:
|
||||
await data.reply(f"ℹ️ You currently active character is [b]{author.dnd_active_character}[/b].")
|
||||
return
|
||||
try:
|
||||
identifier = int(identifier)
|
||||
except ValueError:
|
||||
# Find the character by name
|
||||
chars = await asyncify(data.session.query(self.alchemy.DndCharacter).filter_by(name=identifier).all)
|
||||
if len(chars) >= 2:
|
||||
char_string = "\n".join([f"[c]{char.character_id}[/c] (LV {char.level}) by {char.creator})" for char in chars])
|
||||
raise CommandError(f"Multiple characters share the name {identifier}, "
|
||||
f"please activate them using their id:\n{char_string}")
|
||||
elif len(chars) == 1:
|
||||
char = chars[0]
|
||||
else:
|
||||
char = None
|
||||
else:
|
||||
# Find the character by id
|
||||
char = await asyncify(data.session.query(self.alchemy.DndCharacter)
|
||||
.filter_by(character_id=identifier)
|
||||
.one_or_none)
|
||||
if char is None:
|
||||
raise CommandError("No character found.")
|
||||
# Check if the player already has an active character
|
||||
if author.dnd_active_character is None:
|
||||
# Create a new active character
|
||||
achar = self.alchemy.DndActiveCharacter(character=char, user=author)
|
||||
data.session.add(achar)
|
||||
else:
|
||||
# Change the active character
|
||||
author.dnd_active_character.character = char
|
||||
await data.session_commit()
|
||||
await data.reply(f"✅ Active character set to [b]{char}[/b]!")
|
35
rpgpack/commands/dndedit.py
Normal file
35
rpgpack/commands/dndedit.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import re
|
||||
from royalnet.commands import *
|
||||
from .dndnew import DndnewCommand
|
||||
from ..tables import DndCharacter, DndActiveCharacter
|
||||
|
||||
|
||||
class DndeditCommand(DndnewCommand):
|
||||
name: str = "dndedit"
|
||||
|
||||
description: str = "Edit the active DnD character."
|
||||
|
||||
aliases = ["de", "dnde", "edit", "dedit"]
|
||||
|
||||
tables = {DndCharacter, DndActiveCharacter}
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
character_sheet = args.joined()
|
||||
|
||||
if character_sheet == "":
|
||||
await data.reply(self._syntax())
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
arguments = self._parse(character_sheet)
|
||||
for key in arguments:
|
||||
char.__setattr__(key, arguments[key])
|
||||
|
||||
await data.session_commit()
|
||||
|
||||
await data.reply(f"✅ Edit successful!")
|
19
rpgpack/commands/dndinfo.py
Normal file
19
rpgpack/commands/dndinfo.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from royalnet.commands import *
|
||||
from royalnet.utils import asyncify
|
||||
from ..tables import DndCharacter, DndActiveCharacter
|
||||
|
||||
|
||||
class DndinfoCommand(Command):
|
||||
name: str = "dndinfo"
|
||||
|
||||
description: str = "Display the character sheet of the active DnD character."
|
||||
|
||||
aliases = ["di", "dndi", "info", "dinfo"]
|
||||
|
||||
tables = {DndCharacter, DndActiveCharacter}
|
||||
|
||||
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.")
|
||||
await data.reply(author.dnd_active_character.character.character_sheet())
|
56
rpgpack/commands/dnditem.py
Normal file
56
rpgpack/commands/dnditem.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import aiohttp
|
||||
import sortedcontainers
|
||||
from royalnet.commands import *
|
||||
from ..utils import parse_5etools_entry
|
||||
|
||||
|
||||
class DnditemCommand(Command):
|
||||
name: str = "dnditem"
|
||||
|
||||
aliases = ["item"]
|
||||
|
||||
description: str = "Ottieni informazioni su un oggetto di D&D5e."
|
||||
|
||||
syntax = "{nomeoggetto}"
|
||||
|
||||
_dnddata: sortedcontainers.SortedKeyList = None
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
super().__init__(interface)
|
||||
interface.loop.create_task(self._fetch_dnddata())
|
||||
|
||||
async def _fetch_dnddata(self):
|
||||
self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower())
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get("https://5e.tools/data/items.json") as response:
|
||||
j = await response.json()
|
||||
for item in j["item"]:
|
||||
self._dnddata.add(item)
|
||||
async with session.get("https://5e.tools/data/fluff-items.json") as response:
|
||||
j = await response.json()
|
||||
for item in j["item"]:
|
||||
self._dnddata.add(item)
|
||||
async with session.get("https://5e.tools/data/items-base.json") as response:
|
||||
j = await response.json()
|
||||
for item in j["baseitem"]:
|
||||
self._dnddata.add(item)
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
if self._dnddata is None:
|
||||
await data.reply("⚠️ Il database degli oggetti di D&D non è ancora stato scaricato.")
|
||||
return
|
||||
search = args.joined().lower()
|
||||
result = self._dnddata[self._dnddata.bisect_key_left(search)]
|
||||
string = f'📦 [b]{result["name"]}[/b]\n'
|
||||
if "source" in result:
|
||||
string += f'[i]{result["source"]}, page {result["page"]}[/i]\n'
|
||||
string += f'\n' \
|
||||
f'Type: [b]{result.get("type", "None")}[/b]\n' \
|
||||
f'Value: [b]{result.get("value", "-")}[/b]\n' \
|
||||
f'Weight: [b]{result.get("weight", "0")} lb[/b]\n' \
|
||||
f'Rarity: [b]{result["rarity"] if result.get("rarity", "None") != "None" else "Mundane"}[/b]\n' \
|
||||
f'\n'
|
||||
for entry in result.get("entries", []):
|
||||
string += parse_5etools_entry(entry)
|
||||
string += "\n\n"
|
||||
await data.reply(string)
|
71
rpgpack/commands/dndnew.py
Normal file
71
rpgpack/commands/dndnew.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
import re
|
||||
# noinspection PyUnresolvedReferences
|
||||
from royalnet.commands import *
|
||||
from ..tables import DndCharacter
|
||||
from ..utils import DndProficiencyType
|
||||
|
||||
|
||||
class DndnewCommand(Command):
|
||||
name: str = "dndnew"
|
||||
|
||||
description: str = "Create a new DnD character."
|
||||
|
||||
aliases = ["dn", "dndn", "new", "dnew"]
|
||||
|
||||
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 = "ℹ️ How to create a new character:\n[p]/dndnew YOUR_CHARACTER_NAME\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:
|
||||
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:
|
||||
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!")
|
146
rpgpack/commands/dndroll.py
Normal file
146
rpgpack/commands/dndroll.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
import re
|
||||
import random
|
||||
from royalnet.commands import *
|
||||
from ..tables import DndCharacter, DndActiveCharacter
|
||||
from royalnet.utils import plusformat
|
||||
|
||||
|
||||
class DndrollCommand(Command):
|
||||
name: str = "dndroll"
|
||||
|
||||
description: str = "Roll dice as the active DnD character."
|
||||
|
||||
aliases = ["dr", "dndr", "roll", "droll"]
|
||||
|
||||
tables = {DndCharacter, DndActiveCharacter}
|
||||
|
||||
_skill_names = {
|
||||
"str": "strength",
|
||||
"for": "strength",
|
||||
"dex": "dexterity",
|
||||
"des": "dexterity",
|
||||
"con": "constitution",
|
||||
"cos": "constitution",
|
||||
"inte": "intelligence",
|
||||
"wis": "wisdom",
|
||||
"sag": "wisdom",
|
||||
"cha": "charisma",
|
||||
"car": "charisma",
|
||||
|
||||
"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
|
||||
|
||||
first = args[0]
|
||||
second = args.optional(1)
|
||||
third = args.optional(2)
|
||||
|
||||
advantage = False
|
||||
disadvantage = False
|
||||
extra_modifier = 0
|
||||
|
||||
if third:
|
||||
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 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.lower()
|
||||
for root in self._skill_names:
|
||||
if skill_short_name.startswith(root):
|
||||
skill_name = self._skill_names[root]
|
||||
break
|
||||
else:
|
||||
raise CommandError("Invalid skill name (first parameter).")
|
||||
|
||||
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:
|
||||
roll = random.randrange(1, 21)
|
||||
total = roll + modifier
|
||||
await data.reply(f"🎲 1d20{modifier_str} = {roll}{modifier_str} = [b]{total}[/b]")
|
114
rpgpack/commands/dndspell.py
Normal file
114
rpgpack/commands/dndspell.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
import aiohttp
|
||||
import sortedcontainers
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import ordinalformat, andformat
|
||||
from ..utils import parse_5etools_entry
|
||||
|
||||
|
||||
class DndspellCommand(Command):
|
||||
name: str = "dndspell"
|
||||
|
||||
aliases = ["spell"]
|
||||
|
||||
description: str = "Ottieni informazioni su una magia di D&D5e."
|
||||
|
||||
syntax = "{nomemagia}"
|
||||
|
||||
_dnddata: sortedcontainers.SortedKeyList = None
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
super().__init__(interface)
|
||||
interface.loop.create_task(self._fetch_dnddata())
|
||||
|
||||
async def _fetch_dnddata(self):
|
||||
self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower())
|
||||
async with aiohttp.ClientSession() as session:
|
||||
for url in [
|
||||
"https://5e.tools/data/spells/spells-ai.json",
|
||||
"https://5e.tools/data/spells/spells-ggr.json",
|
||||
"https://5e.tools/data/spells/spells-llk.json",
|
||||
"https://5e.tools/data/spells/spells-phb.json",
|
||||
"https://5e.tools/data/spells/spells-scag.json",
|
||||
"https://5e.tools/data/spells/spells-stream.json",
|
||||
"https://5e.tools/data/spells/spells-ua-ar.json",
|
||||
"https://5e.tools/data/spells/spells-ua-mm.json",
|
||||
"https://5e.tools/data/spells/spells-ua-ss.json",
|
||||
"https://5e.tools/data/spells/spells-ua-tobm.json",
|
||||
"https://5e.tools/data/spells/spells-xge.json"
|
||||
]:
|
||||
async with session.get(url) as response:
|
||||
j = await response.json()
|
||||
for spell in j["spell"]:
|
||||
self._dnddata.add(spell)
|
||||
|
||||
@staticmethod
|
||||
def _parse_spell(spell: dict) -> str:
|
||||
string = f'✨ [b]{spell["name"]}[/b]\n'
|
||||
if "source" in spell:
|
||||
string += f'[i]{spell["source"]}, page {spell["page"]}[/i]\n'
|
||||
string += "\n"
|
||||
if spell["level"] == 0:
|
||||
string += f'[b]Cantrip[/b] {spell["school"]}\n'
|
||||
else:
|
||||
string += f'[b]{ordinalformat(spell["level"])}[/b] level {spell["school"]}\n'
|
||||
if "time" in spell:
|
||||
for time in spell["time"]:
|
||||
string += f'Cast time: ⌛️ [b]{time["number"]} {time["unit"]}[/b]\n'
|
||||
if "range" in spell:
|
||||
if spell["range"]["distance"]["type"] == "touch":
|
||||
string += "Range: 👉 [b]Touch[/b]\n"
|
||||
elif spell["range"]["distance"]["type"] == "self":
|
||||
string += "Range: 👤 [b]Self[/b]\n"
|
||||
else:
|
||||
string += f'Range: 🏹 [b]{spell["range"]["distance"]["amount"]} {spell["range"]["distance"]["type"]}[/b] ({spell["range"]["type"]})\n'
|
||||
if "components" in spell:
|
||||
string += f'Components: '
|
||||
if spell["components"].get("v", False):
|
||||
string += "👄 [b]Verbal[/b] | "
|
||||
if spell["components"].get("s", False):
|
||||
string += "🤙 [b]Somatic[/b] | "
|
||||
if spell["components"].get("r", False):
|
||||
# TODO: wtf is this
|
||||
string += "❓ [b]R...?[/b] | "
|
||||
if spell["components"].get("m", False):
|
||||
if "text" in spell["components"]["m"]:
|
||||
string += f'💎 [b]Material[/b] ([i]{spell["components"]["m"]["text"]}[/i]) | '
|
||||
else:
|
||||
string += f'💎 [b]Material[/b] ([i]{spell["components"]["m"]}[/i]) | '
|
||||
string = string.rstrip(" ").rstrip("|")
|
||||
string += "\n"
|
||||
string += "\n"
|
||||
if "duration" in spell:
|
||||
for duration in spell["duration"]:
|
||||
if duration["type"] == "timed":
|
||||
string += f'Duration: 🕒 [b]{duration["duration"]["amount"]} {duration["duration"]["type"]}[/b]'
|
||||
elif duration["type"] == "instant":
|
||||
string += 'Duration: ☁️ [b]Instantaneous[/b]'
|
||||
elif duration["type"] == "special":
|
||||
string += 'Duration: ⭐️ [b]Special[/b]'
|
||||
elif duration["type"] == "permanent":
|
||||
string += f"Duration: ♾ [b]Permanent[/b] (ends on {andformat(duration['ends'], final=' or ')})"
|
||||
else:
|
||||
string += f'Duration: ⚠️[b]UNKNOWN[/b]'
|
||||
if duration.get("concentration", False):
|
||||
string += " (requires concentration)"
|
||||
string += "\n"
|
||||
if "meta" in spell:
|
||||
if spell["meta"].get("ritual", False):
|
||||
string += "🔮 Can be casted as ritual\n"
|
||||
string += "\n"
|
||||
for entry in spell.get("entries", []):
|
||||
string += parse_5etools_entry(entry)
|
||||
string += "\n\n"
|
||||
for entry in spell.get("entriesHigherLevel", []):
|
||||
string += parse_5etools_entry(entry)
|
||||
string += "\n\n"
|
||||
return string
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
if self._dnddata is None:
|
||||
await data.reply("⚠️ Il database degli oggetti di D&D non è ancora stato scaricato.")
|
||||
return
|
||||
search = args.joined().lower()
|
||||
result = self._dnddata[self._dnddata.bisect_key_left(search)]
|
||||
await data.reply(self._parse_spell(result))
|
28
rpgpack/commands/roll.py
Normal file
28
rpgpack/commands/roll.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import typing
|
||||
import random
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
class RollCommand(Command):
|
||||
name: str = "roll"
|
||||
|
||||
description: str = "Roll a dice, from N to M (defaults to 1-100)."
|
||||
|
||||
syntax = "[min] [max]"
|
||||
|
||||
aliases = ["r", "random"]
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
first: typing.Optional[str] = args.optional(0)
|
||||
second: typing.Optional[str] = args.optional(1)
|
||||
if second:
|
||||
minimum = int(first)
|
||||
maximum = int(second)
|
||||
elif first:
|
||||
minimum = 1
|
||||
maximum = int(first)
|
||||
else:
|
||||
minimum = 1
|
||||
maximum = 100
|
||||
result = random.randrange(minimum, maximum+1)
|
||||
await data.reply(f"🎲 Dice roll [{minimum}-{maximum}]: [b]{result}[/b]")
|
|
@ -12,5 +12,4 @@ available_exception_stars = [
|
|||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
__all__ = [command.__name__ for command in [*available_page_stars, *available_exception_stars]]
|
||||
|
||||
__all__ = [star.__name__ for star in [*available_page_stars, *available_exception_stars]]
|
|
@ -1,9 +1,11 @@
|
|||
# Imports go here!
|
||||
|
||||
from .dndactivecharacters import DndActiveCharacter
|
||||
from .dndcharacters import DndCharacter
|
||||
|
||||
# Enter the tables of your Pack here!
|
||||
available_tables = [
|
||||
|
||||
DndActiveCharacter,
|
||||
DndCharacter,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
26
rpgpack/tables/dndactivecharacters.py
Normal file
26
rpgpack/tables/dndactivecharacters.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import *
|
||||
|
||||
|
||||
class DndActiveCharacter:
|
||||
__tablename__ = "dndactivecharacters"
|
||||
|
||||
@declared_attr
|
||||
def character_id(self):
|
||||
return Column(Integer, ForeignKey("dndcharacters.character_id"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def user_id(self):
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def character(self):
|
||||
return relationship("DndCharacter", foreign_keys=self.character_id, backref="activated_by")
|
||||
|
||||
@declared_attr
|
||||
def user(self):
|
||||
return relationship("User", foreign_keys=self.user_id, backref=backref("dnd_active_character", uselist=False))
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__} for {self.user_id}: {self.character_id}>"
|
301
rpgpack/tables/dndcharacters.py
Normal file
301
rpgpack/tables/dndcharacters.py
Normal file
|
@ -0,0 +1,301 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import *
|
||||
from ..utils import DndProficiencyType
|
||||
|
||||
|
||||
class DndCharacter:
|
||||
__tablename__ = "dndcharacters"
|
||||
|
||||
@declared_attr
|
||||
def character_id(self):
|
||||
return Column(Integer, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def creator_id(self):
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def creator(self):
|
||||
return relationship("User", foreign_keys=self.creator_id, backref="dndcharacters_created")
|
||||
|
||||
@declared_attr
|
||||
def name(self):
|
||||
return Column(String, nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def strength_score(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@property
|
||||
def strength(self):
|
||||
return (self.strength_score - 10) // 2
|
||||
|
||||
@declared_attr
|
||||
def dexterity_score(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@property
|
||||
def dexterity(self):
|
||||
return (self.dexterity_score - 10) // 2
|
||||
|
||||
@declared_attr
|
||||
def constitution_score(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@property
|
||||
def constitution(self):
|
||||
return (self.constitution_score - 10) // 2
|
||||
|
||||
@declared_attr
|
||||
def intelligence_score(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@property
|
||||
def intelligence(self):
|
||||
return (self.intelligence_score - 10) // 2
|
||||
|
||||
@declared_attr
|
||||
def wisdom_score(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@property
|
||||
def wisdom(self):
|
||||
return (self.wisdom_score - 10) // 2
|
||||
|
||||
@declared_attr
|
||||
def charisma_score(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@property
|
||||
def charisma(self):
|
||||
return (self.charisma_score - 10) // 2
|
||||
|
||||
@declared_attr
|
||||
def level(self):
|
||||
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, nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def armor_class(self):
|
||||
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}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
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
|
4
rpgpack/utils/__init__.py
Normal file
4
rpgpack/utils/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .dndproficiencytype import DndProficiencyType
|
||||
from .parse5etoolsentry import parse_5etools_entry
|
||||
|
||||
__all__ = ["DndProficiencyType", "parse_5etools_entry"]
|
11
rpgpack/utils/dndproficiencytype.py
Normal file
11
rpgpack/utils/dndproficiencytype.py
Normal 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)
|
31
rpgpack/utils/parse5etoolsentry.py
Normal file
31
rpgpack/utils/parse5etoolsentry.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
def parse_5etools_entry(entry) -> str:
|
||||
if isinstance(entry, str):
|
||||
return entry
|
||||
elif isinstance(entry, dict):
|
||||
string = ""
|
||||
if entry["type"] == "entries":
|
||||
string += f'[b]{entry.get("name", "")}[/b]\n'
|
||||
for subentry in entry["entries"]:
|
||||
string += parse_5etools_entry(subentry)
|
||||
string += "\n\n"
|
||||
elif entry["type"] == "table":
|
||||
string += "[i][table hidden][/i]"
|
||||
# for label in entry["colLabels"]:
|
||||
# string += f"| {label} "
|
||||
# string += "|"
|
||||
# for row in entry["rows"]:
|
||||
# for column in row:
|
||||
# string += f"| {self._parse_entry(column)} "
|
||||
# string += "|\n"
|
||||
elif entry["type"] == "cell":
|
||||
return parse_5etools_entry(entry["entry"])
|
||||
elif entry["type"] == "list":
|
||||
string = ""
|
||||
for item in entry["items"]:
|
||||
string += f"- {parse_5etools_entry(item)}\n"
|
||||
string.rstrip("\n")
|
||||
else:
|
||||
string += "[i]⚠️ [unknown type][/i]"
|
||||
else:
|
||||
return "[/i]⚠️ [unknown data][/i]"
|
||||
return string
|
4
rpgpack/version.py
Normal file
4
rpgpack/version.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
semantic = "5.0a93"
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(semantic)
|
13
setup.py
13
setup.py
|
@ -1,4 +1,5 @@
|
|||
import setuptools
|
||||
import rpgpack.version
|
||||
|
||||
with open("README.md", "r") as f:
|
||||
long_description = f.read()
|
||||
|
@ -7,14 +8,14 @@ with open("requirements.txt", "r") as f:
|
|||
install_requires = f.readlines()
|
||||
|
||||
setuptools.setup(
|
||||
name="{packname}",
|
||||
version="0.1",
|
||||
author="{packauthorname}",
|
||||
author_email="{packauthoremail}",
|
||||
description="{packdescription}",
|
||||
name="rpgpack",
|
||||
version=rpgpack.version.semantic,
|
||||
author="Stefano Pigozzi",
|
||||
author_email="ste.pigozzi@gmail.com",
|
||||
description="A Royalnet pack to play D&D by-chat",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="{packgithublink}",
|
||||
url="https://github.com/Steffo99/rpgpack",
|
||||
packages=setuptools.find_packages(),
|
||||
install_requires=install_requires,
|
||||
python_requires=">=3.7",
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# Imports go here!
|
||||
|
||||
|
||||
# Enter the commands of your Pack here!
|
||||
available_commands = [
|
||||
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
__all__ = [command.__name__ for command in available_commands]
|
Loading…
Reference in a new issue