mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
Add more dnd commands
This commit is contained in:
parent
d31b6e4100
commit
12f6967fa6
13 changed files with 288 additions and 16 deletions
|
@ -144,6 +144,8 @@ class DiscordBot(GenericBot):
|
||||||
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
|
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
|
||||||
error_message += '\n'.join(e.args)
|
error_message += '\n'.join(e.args)
|
||||||
await data.reply(error_message)
|
await data.reply(error_message)
|
||||||
|
# Close the data session
|
||||||
|
await data.session_close()
|
||||||
|
|
||||||
async def on_connect(cli):
|
async def on_connect(cli):
|
||||||
log.debug("Connected to Discord")
|
log.debug("Connected to Discord")
|
||||||
|
|
|
@ -178,8 +178,8 @@ class TelegramBot(GenericBot):
|
||||||
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
|
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n"
|
||||||
error_message += '\n'.join(e.args)
|
error_message += '\n'.join(e.args)
|
||||||
await data.reply(error_message)
|
await data.reply(error_message)
|
||||||
if __debug__:
|
# Close the data session
|
||||||
raise
|
await data.session_close()
|
||||||
|
|
||||||
async def _handle_callback_query(self, update: telegram.Update):
|
async def _handle_callback_query(self, update: telegram.Update):
|
||||||
query: telegram.CallbackQuery = update.callback_query
|
query: telegram.CallbackQuery = update.callback_query
|
||||||
|
|
|
@ -26,5 +26,10 @@ class Command:
|
||||||
def __init__(self, interface: CommandInterface):
|
def __init__(self, interface: CommandInterface):
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alchemy(self):
|
||||||
|
"""A shortcut to ``self.interface.alchemy``"""
|
||||||
|
return self.interface.alchemy
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
@ -2,6 +2,7 @@ import typing
|
||||||
import warnings
|
import warnings
|
||||||
from .commanderrors import UnsupportedError
|
from .commanderrors import UnsupportedError
|
||||||
from .commandinterface import CommandInterface
|
from .commandinterface import CommandInterface
|
||||||
|
from ..utils import asyncify
|
||||||
|
|
||||||
|
|
||||||
class CommandData:
|
class CommandData:
|
||||||
|
@ -12,6 +13,18 @@ class CommandData:
|
||||||
else:
|
else:
|
||||||
self.session = None
|
self.session = None
|
||||||
|
|
||||||
|
async def session_commit(self):
|
||||||
|
"""Commit the changes to the session."""
|
||||||
|
await asyncify(self.session.commit)
|
||||||
|
|
||||||
|
async def session_close(self):
|
||||||
|
"""Close the opened session.
|
||||||
|
|
||||||
|
Remember to call this when the data is disposed of!"""
|
||||||
|
if self.session:
|
||||||
|
await asyncify(self.session.close)
|
||||||
|
self.session = None
|
||||||
|
|
||||||
async def reply(self, text: str) -> None:
|
async def reply(self, text: str) -> None:
|
||||||
"""Send a text message to the channel where the call was made.
|
"""Send a text message to the channel where the call was made.
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
# Imports go here!
|
# Imports go here!
|
||||||
from .roll import RollCommand
|
from .roll import RollCommand
|
||||||
from .dice import DiceCommand
|
from .dice import DiceCommand
|
||||||
|
from .dndactive import DndactiveCommand
|
||||||
|
from .dndinfo import DndinfoCommand
|
||||||
|
from .dndnew import DndnewCommand
|
||||||
|
from .dndedit import DndeditCommand
|
||||||
|
from .dndroll import DndrollCommand
|
||||||
|
|
||||||
# Enter the commands of your Pack here!
|
# Enter the commands of your Pack here!
|
||||||
available_commands = [
|
available_commands = [
|
||||||
RollCommand,
|
RollCommand,
|
||||||
DiceCommand,
|
DiceCommand,
|
||||||
|
DndactiveCommand,
|
||||||
|
DndinfoCommand,
|
||||||
|
DndnewCommand,
|
||||||
|
DndeditCommand,
|
||||||
|
DndrollCommand,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Don't change this, it should automatically generate __all__
|
# Don't change this, it should automatically generate __all__
|
||||||
|
|
|
@ -1,12 +1,52 @@
|
||||||
from royalnet.commands import *
|
from royalnet.commands import *
|
||||||
|
from royalnet.utils import asyncify
|
||||||
|
from ..tables import DndCharacter, DndActiveCharacter
|
||||||
|
|
||||||
|
|
||||||
class DndactiveCommand(Command):
|
class DndactiveCommand(Command):
|
||||||
name: str = "dndactive"
|
name: str = "dndactive"
|
||||||
|
|
||||||
description: str = "Set the active D&D character."
|
description: str = "Set a DnD character as active."
|
||||||
|
|
||||||
aliases = ["da", "dnda", "active"]
|
aliases = ["da", "dnda", "active", "dactive"]
|
||||||
|
|
||||||
|
syntax = "{name|id}"
|
||||||
|
|
||||||
|
tables = {DndCharacter, DndActiveCharacter}
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
|
identifier = args.optional(0)
|
||||||
|
if identifier is None:
|
||||||
|
# Display the active character identifiers
|
||||||
...
|
...
|
||||||
|
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
|
||||||
|
author = await data.get_author(error_if_none=True)
|
||||||
|
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]!")
|
||||||
|
|
59
royalnet/packs/rpg/commands/dndedit.py
Normal file
59
royalnet/packs/rpg/commands/dndedit.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
from royalnet.commands import *
|
||||||
|
from ..tables import DndCharacter, DndActiveCharacter
|
||||||
|
|
||||||
|
|
||||||
|
class DndeditCommand(Command):
|
||||||
|
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+)")
|
||||||
|
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 = 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)
|
||||||
|
await data.session_commit()
|
||||||
|
await data.reply(f"✅ Edit successful!")
|
19
royalnet/packs/rpg/commands/dndinfo.py
Normal file
19
royalnet/packs/rpg/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())
|
|
@ -5,9 +5,9 @@ from ..tables import DndCharacter
|
||||||
class DndnewCommand(Command):
|
class DndnewCommand(Command):
|
||||||
name: str = "dndnew"
|
name: str = "dndnew"
|
||||||
|
|
||||||
description: str = "Create a new D&D character."
|
description: str = "Create a new DnD character."
|
||||||
|
|
||||||
aliases = ["dn", "dndn", "new"]
|
aliases = ["dn", "dndn", "new", "dnew"]
|
||||||
|
|
||||||
syntax = "{name}\n" \
|
syntax = "{name}\n" \
|
||||||
"LV {level}\n" \
|
"LV {level}\n" \
|
||||||
|
@ -19,11 +19,41 @@ class DndnewCommand(Command):
|
||||||
"WIS {wisdom}\n" \
|
"WIS {wisdom}\n" \
|
||||||
"CHA {charisma}\n" \
|
"CHA {charisma}\n" \
|
||||||
"\n" \
|
"\n" \
|
||||||
"MAXHP {maxhp}\n" \
|
"MAXHP {max_hp}\n" \
|
||||||
"AC {armorclass}"
|
"AC {armor_class}"
|
||||||
|
|
||||||
tables = {}
|
tables = {DndCharacter}
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
name = args[0]
|
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+)")
|
||||||
|
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!")
|
||||||
|
|
88
royalnet/packs/rpg/commands/dndroll.py
Normal file
88
royalnet/packs/rpg/commands/dndroll.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import typing
|
||||||
|
import random
|
||||||
|
from royalnet.commands import *
|
||||||
|
from royalnet.utils import plusformat
|
||||||
|
from ..tables import DndCharacter, DndActiveCharacter
|
||||||
|
|
||||||
|
|
||||||
|
class DndrollCommand(Command):
|
||||||
|
name: str = "dndroll"
|
||||||
|
|
||||||
|
description: str = "Roll as the active DnD character."
|
||||||
|
|
||||||
|
aliases = ["dr", "dndr", "droll"]
|
||||||
|
|
||||||
|
syntax = "{stat} [proficiency] [modifier]"
|
||||||
|
|
||||||
|
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.")
|
||||||
|
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)
|
||||||
|
|
||||||
|
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]"
|
||||||
|
else:
|
||||||
|
raise CommandError(f"Unknown proficiency type '{second}'")
|
||||||
|
proficiency_mod: int = int(char.proficiency_bonus * proficiency_mul)
|
||||||
|
else:
|
||||||
|
proficiency_name: str = ""
|
||||||
|
proficiency_mod: int = 0
|
||||||
|
|
||||||
|
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]"
|
||||||
|
else:
|
||||||
|
raise CommandError(f"Unknown stat '{stat}'")
|
||||||
|
|
||||||
|
total_mod = stat_mod + proficiency_mod + extra_mod
|
||||||
|
|
||||||
|
roll = random.randrange(1, 21)
|
||||||
|
|
||||||
|
result = roll + total_mod
|
||||||
|
|
||||||
|
await data.reply(f"🎲 Rolling {stat_name}{proficiency_name}{plusformat(extra_mod, empty_if_zero=True)}:\n"
|
||||||
|
f"1d20"
|
||||||
|
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]")
|
|
@ -16,11 +16,11 @@ class DndActiveCharacter:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def character(self):
|
def character(self):
|
||||||
return relationship("DndCharacter", foreign_keys=self.character_id, backref="activations", use_scalar=True)
|
return relationship("DndCharacter", foreign_keys=self.character_id, backref="activated_by")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def user(self):
|
def user(self):
|
||||||
return relationship("User", foreign_keys=self.user_id, backref="active_dnd_character", use_scalar=True)
|
return relationship("User", foreign_keys=self.user_id, backref=backref("dnd_active_character", uselist=False))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__qualname__} for {self.user_id}: {self.character_id}>"
|
return f"<{self.__class__.__qualname__} for {self.user_id}: {self.character_id}>"
|
||||||
|
|
|
@ -90,6 +90,9 @@ class DndCharacter:
|
||||||
return f"<{self.__class__.__qualname__} {self.name}>"
|
return f"<{self.__class__.__qualname__} {self.name}>"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
return f"{self.name}"
|
||||||
|
|
||||||
|
def character_sheet(self):
|
||||||
return f"{self.name}\n" \
|
return f"{self.name}\n" \
|
||||||
f"LV {self.level}\n\n" \
|
f"LV {self.level}\n\n" \
|
||||||
f"STR {self.strength}\n" \
|
f"STR {self.strength}\n" \
|
||||||
|
|
|
@ -22,15 +22,18 @@ def andformat(l: typing.List[str], middle=", ", final=" and ") -> str:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def plusformat(i: int) -> str:
|
def plusformat(i: int, empty_if_zero: bool = False) -> str:
|
||||||
"""Convert an :py:class:`int` to a :py:class:`str`, prepending a ``+`` if it's greater than 0.
|
"""Convert an :py:class:`int` to a :py:class:`str`, prepending a ``+`` if it's greater than 0.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
i: the :py:class:`int` to convert.
|
i: the :py:class:`int` to convert.
|
||||||
|
empty_if_zero: Return an empty string if ``i`` is zero.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The resulting :py:class:`str`."""
|
The resulting :py:class:`str`."""
|
||||||
if i >= 0:
|
if i == 0 and empty_if_zero:
|
||||||
|
return ""
|
||||||
|
if i > 0:
|
||||||
return f"+{i}"
|
return f"+{i}"
|
||||||
return str(i)
|
return str(i)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue