1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 11:34:18 +00:00
This commit is contained in:
Steffo 2020-09-16 02:37:31 +02:00
parent ada3fb1643
commit 2160aca582
19 changed files with 850 additions and 605 deletions

798
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,10 @@
[tool.poetry] [tool.poetry]
name = "rpgpack" name = "rpgpack"
version = "5.9.1" version = "5.9.2"
description = "A Dungeons & Dragons utilities pack for Royalnet" description = "A Dungeons & Dragons utilities pack for Royalnet"
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"] authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
license = "AGPL-3.0+" license = "AGPL-3.0-or-later"
readme = "README.md" readme = "README.md"
homepage = "https://github.com/Steffo99/rpgpack" homepage = "https://github.com/Steffo99/rpgpack"
classifiers = [ classifiers = [
@ -22,11 +22,18 @@
sortedcontainers = "^2.1.0" sortedcontainers = "^2.1.0"
aiohttp = "^3.5" aiohttp = "^3.5"
# Required by poetry?!
bcrypt = "3.2.0"
sqlalchemy = "1.3.19"
[tool.poetry.dependencies.royalnet] [tool.poetry.dependencies.royalnet]
version = "~5.10.0" version = "~5.11.12"
extras = [ extras = [
"telegram",
"alchemy_easy", "alchemy_easy",
"herald",
"telegram",
"discord",
"constellation",
] ]
# Development dependencies # Development dependencies

View file

@ -10,21 +10,23 @@ from ...utils import get_targets
class DndBattleTargetCommand(rc.Command, abc.ABC): class DndBattleTargetCommand(rc.Command, abc.ABC):
@abc.abstractmethod @abc.abstractmethod
async def _change(self, unit: DndBattleUnit, args: List[str]): async def _change(self, unit: DndBattleUnit, args: List[str]):
... raise NotImplementedError()
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
target = args[0] target = args[0]
units = await get_targets(data, target)
if len(units) == 0:
raise rc.InvalidInputError(f"No targets found matching [c]{target}[/c].")
for unit in units: async with data.session_acm() as session:
await self._change(unit, args[1:]) units = await get_targets(target, data=data, session=session)
if len(units) == 0:
raise rc.InvalidInputError(f"No targets found matching [c]{target}[/c].")
await data.session_commit() for unit in units:
await self._change(unit, args[1:])
message = [] await session.commit()
for unit in units:
message.append(f"{unit}")
await data.reply("\n\n".join(message)) message = []
for unit in units:
message.append(f"{unit}")
await data.reply("\n\n".join(message))

View file

@ -16,51 +16,55 @@ class DndactiveCommand(Command):
async def run(self, args: CommandArgs, data: CommandData) -> None: async def run(self, args: CommandArgs, data: CommandData) -> None:
identifier = args.optional(0) identifier = args.optional(0)
author = await data.get_author(error_if_none=True)
active_character = await get_active_character(data)
DndCharacterT = self.alchemy.get(DndCharacter) async with data.session_acm() as session:
DndActiveCharacterT = self.alchemy.get(DndActiveCharacter) author = await data.find_author(session=session, required=True)
active_character = await get_active_character(session=session, data=data)
# Display the active character DndCharacterT = self.alchemy.get(DndCharacter)
if identifier is None: DndActiveCharacterT = self.alchemy.get(DndActiveCharacter)
# Display the active character
if identifier is None:
if active_character is None:
await data.reply(" You haven't activated any character in this chat.")
else:
await data.reply(f" Your active character for this chat is [b]{active_character.character}[/b].")
return
# Find the character by name
try:
identifier = int(identifier)
except ValueError:
chars = await asyncify(session.query(DndCharacterT).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(session.query(DndCharacterT)
.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 active_character is None: if active_character is None:
await data.reply(" You haven't activated any character in this chat.") # Create a new active character
achar = DndActiveCharacterT(
character=char,
user=author,
interface_name=data.command.serf.__class__.__name__,
interface_data=pickle.dumps(get_interface_data(data)))
session.add(achar)
else: else:
await data.reply(f" Your active character for this chat is [b]{active_character.character}[/b].") # Change the active character
return active_character.character = char
await asyncify(session.commit)
# Find the character by name await data.reply(f"✅ Active character set to [b]{char}[/b]!")
try:
identifier = int(identifier)
except ValueError:
chars = await asyncify(data.session.query(DndCharacterT).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(DndCharacterT)
.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 active_character is None:
# Create a new active character
achar = DndActiveCharacterT(
character=char,
user=author,
interface_name=self.interface.name,
interface_data=pickle.dumps(get_interface_data(data)))
data.session.add(achar)
else:
# Change the active character
active_character.character = char
await data.session_commit()
await data.reply(f"✅ Active character set to [b]{char}[/b]!")

View file

@ -18,48 +18,49 @@ class DndactivebattleCommand(Command):
BattleT = self.alchemy.get(DndBattle) BattleT = self.alchemy.get(DndBattle)
ABattleT = self.alchemy.get(DndActiveBattle) ABattleT = self.alchemy.get(DndActiveBattle)
identifier = args.joined() async with data.session_acm() as session:
active_battle = await get_active_battle(data) identifier = args.joined()
active_battle = await get_active_battle(session=session, data=data)
# Display the active character # Display the active character
if identifier == "": if identifier == "":
if active_battle is None: if active_battle is None:
await data.reply(" No battles have ever been activated in this chat.") await data.reply(" No battles have ever been activated in this chat.")
else: else:
await data.reply(active_battle.battle.create_message()) await data.reply(active_battle.battle.create_message())
return return
# Find the battle # Find the battle
try: try:
identifier = int(identifier) identifier = int(identifier)
except ValueError: except ValueError:
# Find the battle by name # Find the battle by name
battles = await asyncify(data.session.query(BattleT).filter_by(name=identifier).all) battles = await asyncify(session.query(BattleT).filter_by(name=identifier).all)
if len(battles) >= 2: if len(battles) >= 2:
char_string = "\n".join([f"[c]{battle.id}[/c]" for battle in battles]) char_string = "\n".join([f"[c]{battle.id}[/c]" for battle in battles])
raise CommandError(f"Multiple battles share the name [b]{identifier}[/b], " raise CommandError(f"Multiple battles share the name [b]{identifier}[/b], "
f"please activate one of them by using their id:\n{char_string}") f"please activate one of them by using their id:\n{char_string}")
elif len(battles) == 1: elif len(battles) == 1:
battle = battles[0] battle = battles[0]
else:
battle = None
else: else:
battle = None # Find the battle by id
else: battle = await asyncify(session.query(BattleT)
# Find the battle by id
battle = await asyncify(data.session.query(BattleT)
.filter_by(id=identifier) .filter_by(id=identifier)
.one_or_none) .one_or_none)
if battle is None: if battle is None:
raise CommandError("No such battle found.") raise CommandError("No such battle found.")
# Check if the player already has an active character # Check if the player already has an active character
if active_battle is None: if active_battle is None:
# Create a new active battle # Create a new active battle
active_battle = ABattleT( active_battle = ABattleT(
battle=battle, battle=battle,
interface_name=self.interface.name, interface_name=data.command.serf.__class__.__name__,
interface_data=pickle.dumps(get_interface_data(data))) interface_data=pickle.dumps(get_interface_data(data)))
data.session.add(active_battle) session.add(active_battle)
else: else:
# Change the active character # Change the active character
active_battle.battle = battle active_battle.battle = battle
await data.session_commit() await asyncify(session.commit)
await data.reply(f"⚔️ [b]{battle}[/b]! Roll initiative!") await data.reply(f"⚔️ [b]{battle}[/b]! Roll initiative!")

View file

@ -26,38 +26,39 @@ class DndaddunitCommand(rc.Command):
DndBattleUnitT = self.alchemy.get(DndBattleUnit) DndBattleUnitT = self.alchemy.get(DndBattleUnit)
active_battle = await get_active_battle(data) async with data.session_acm() as session:
if active_battle is None: active_battle = await get_active_battle(session=session, data=data)
raise rc.CommandError("No battle is active in this chat.") if active_battle is None:
raise rc.CommandError("No battle is active in this chat.")
units_with_same_name = await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( units_with_same_name = await ru.asyncify(session.query(DndBattleUnitT).filter_by(
name=name, name=name,
battle=active_battle.battle battle=active_battle.battle
).all) ).all)
if len(units_with_same_name) != 0: if len(units_with_same_name) != 0:
raise rc.InvalidInputError("A unit with the same name already exists.") raise rc.InvalidInputError("A unit with the same name already exists.")
try: try:
health = Health.from_text(health) health = Health.from_text(health)
except ValueError: except ValueError:
raise rc.InvalidInputError("Invalid health string.") raise rc.InvalidInputError("Invalid health string.")
dbu = DndBattleUnitT( dbu = DndBattleUnitT(
linked_character_id=None, linked_character_id=None,
initiative=initiative, initiative=initiative,
faction=faction, faction=faction,
name=name, name=name,
health_string=health, health_string=health,
armor_class=armor_class, armor_class=armor_class,
battle=active_battle.battle battle=active_battle.battle
) )
data.session.add(dbu) session.add(dbu)
await data.session_commit() await ru.asyncify(session.commit)
await data.reply(f"{dbu}\n" await data.reply(f"{dbu}\n"
f"joins the battle!") f"joins the battle!")
if dbu.health.hidden: if dbu.health.hidden:
await data.delete_invoking() await data.delete_invoking()

View file

@ -1,5 +1,6 @@
import re import re
from royalnet.commands import * from royalnet.commands import *
import royalnet.utils as ru
from .dndnew import DndnewCommand from .dndnew import DndnewCommand
from ..tables import DndCharacter from ..tables import DndCharacter
from ..utils import get_active_character from ..utils import get_active_character
@ -15,20 +16,21 @@ class DndeditCommand(DndnewCommand):
async def run(self, args: CommandArgs, data: CommandData) -> None: async def run(self, args: CommandArgs, data: CommandData) -> None:
character_sheet = args.joined() character_sheet = args.joined()
active_character = await get_active_character(data) async with data.session_acm() as session:
active_character = await get_active_character(session=session, data=data)
if active_character is None: if active_character is None:
raise CommandError("You don't have an active character.") raise CommandError("You don't have an active character.")
char: DndCharacter = active_character.character char: DndCharacter = active_character.character
if character_sheet == "": if character_sheet == "":
await data.reply(char.to_edit_string()) await data.reply(char.to_edit_string())
return return
arguments = self._parse(character_sheet) arguments = self._parse(character_sheet)
for key in arguments: for key in arguments:
char.__setattr__(key, arguments[key]) char.__setattr__(key, arguments[key])
await data.session_commit() await ru.asyncify(session.commit)
await data.reply(f"✅ Edit successful!") await data.reply(f"✅ Edit successful!")

View file

@ -20,44 +20,45 @@ class DndinfoCommand(Command):
} }
async def run(self, args: CommandArgs, data: CommandData) -> None: async def run(self, args: CommandArgs, data: CommandData) -> None:
active_character = await get_active_character(data) async with data.session_acm() as session:
active_character = await get_active_character(data=data, session=session)
if active_character is None: if active_character is None:
raise CommandError("You don't have an active character.") raise CommandError("You don't have an active character.")
c: DndCharacter = active_character.character c: DndCharacter = active_character.character
r = f"[b]{c.name}[/b]\n" \ r = f"[b]{c.name}[/b]\n" \
f"🔰 Lv. {c.level}\n" \ f"🔰 Lv. {c.level}\n" \
f"\n" \ f"\n" \
f"❤️ {c.current_hp}/{c.max_hp}\n" \ f"❤️ {c.current_hp}/{c.max_hp}\n" \
f"🛡 {c.armor_class}\n" \ f"🛡 {c.armor_class}\n" \
f"\n" \ f"\n" \
f"{self._p_emoji[c.strength_save_proficiency.value]} Strength: [b]{c.strength:+d}[/b] ({c.strength_score})\n" \ f"{self._p_emoji[c.strength_save_proficiency.value]} Strength: [b]{c.strength:+d}[/b] ({c.strength_score})\n" \
f"{self._p_emoji[c.dexterity_save_proficiency.value]} Dexterity: [b]{c.dexterity:+d}[/b] ({c.dexterity_score})\n" \ f"{self._p_emoji[c.dexterity_save_proficiency.value]} Dexterity: [b]{c.dexterity:+d}[/b] ({c.dexterity_score})\n" \
f"{self._p_emoji[c.constitution_save_proficiency.value]} Constitution: [b]{c.constitution:+d}[/b] ({c.constitution_score})\n" \ f"{self._p_emoji[c.constitution_save_proficiency.value]} Constitution: [b]{c.constitution:+d}[/b] ({c.constitution_score})\n" \
f"{self._p_emoji[c.intelligence_save_proficiency.value]} Intelligence: [b]{c.intelligence:+d}[/b] ({c.intelligence_score})\n" \ f"{self._p_emoji[c.intelligence_save_proficiency.value]} Intelligence: [b]{c.intelligence:+d}[/b] ({c.intelligence_score})\n" \
f"{self._p_emoji[c.wisdom_save_proficiency.value]} Wisdom: [b]{c.wisdom:+d}[/b] ({c.wisdom_score})\n" \ f"{self._p_emoji[c.wisdom_save_proficiency.value]} Wisdom: [b]{c.wisdom:+d}[/b] ({c.wisdom_score})\n" \
f"{self._p_emoji[c.charisma_save_proficiency.value]} Charisma: [b]{c.charisma:+d}[/b] ({c.charisma_score})\n" \ f"{self._p_emoji[c.charisma_save_proficiency.value]} Charisma: [b]{c.charisma:+d}[/b] ({c.charisma_score})\n" \
f"\n" \ f"\n" \
f"{self._p_emoji[c.acrobatics_proficiency.value]} Acrobatics: [b]{c.acrobatics:+d}[/b]\n" \ f"{self._p_emoji[c.acrobatics_proficiency.value]} Acrobatics: [b]{c.acrobatics:+d}[/b]\n" \
f"{self._p_emoji[c.animal_handling_proficiency.value]} Animal Handling: [b]{c.animal_handling:+d}[/b]\n" \ f"{self._p_emoji[c.animal_handling_proficiency.value]} Animal Handling: [b]{c.animal_handling:+d}[/b]\n" \
f"{self._p_emoji[c.arcana_proficiency.value]} Arcana: [b]{c.arcana:+d}[/b]\n" \ f"{self._p_emoji[c.arcana_proficiency.value]} Arcana: [b]{c.arcana:+d}[/b]\n" \
f"{self._p_emoji[c.athletics_proficiency.value]} Athletics: [b]{c.athletics:+d}[/b]\n" \ f"{self._p_emoji[c.athletics_proficiency.value]} Athletics: [b]{c.athletics:+d}[/b]\n" \
f"{self._p_emoji[c.deception_proficiency.value]} Deception: [b]{c.deception:+d}[/b]\n" \ f"{self._p_emoji[c.deception_proficiency.value]} Deception: [b]{c.deception:+d}[/b]\n" \
f"{self._p_emoji[c.history_proficiency.value]} History: [b]{c.history:+d}[/b]\n" \ f"{self._p_emoji[c.history_proficiency.value]} History: [b]{c.history:+d}[/b]\n" \
f"{self._p_emoji[c.insight_proficiency.value]} Insight: [b]{c.insight:+d}[/b]\n" \ f"{self._p_emoji[c.insight_proficiency.value]} Insight: [b]{c.insight:+d}[/b]\n" \
f"{self._p_emoji[c.intimidation_proficiency.value]} Intimidation: [b]{c.intimidation:+d}[/b]\n" \ f"{self._p_emoji[c.intimidation_proficiency.value]} Intimidation: [b]{c.intimidation:+d}[/b]\n" \
f"{self._p_emoji[c.investigation_proficiency.value]} Investigation: [b]{c.investigation:+d}[/b]\n" \ f"{self._p_emoji[c.investigation_proficiency.value]} Investigation: [b]{c.investigation:+d}[/b]\n" \
f"{self._p_emoji[c.medicine_proficiency.value]} Medicine: [b]{c.medicine:+d}[/b]\n" \ f"{self._p_emoji[c.medicine_proficiency.value]} Medicine: [b]{c.medicine:+d}[/b]\n" \
f"{self._p_emoji[c.nature_proficiency.value]} Nature: [b]{c.nature:+d}[/b]\n" \ f"{self._p_emoji[c.nature_proficiency.value]} Nature: [b]{c.nature:+d}[/b]\n" \
f"{self._p_emoji[c.perception_proficiency.value]} Perception: [b]{c.perception:+d}[/b]\n" \ f"{self._p_emoji[c.perception_proficiency.value]} Perception: [b]{c.perception:+d}[/b]\n" \
f"{self._p_emoji[c.performance_proficiency.value]} Performance: [b]{c.performance:+d}[/b]\n" \ f"{self._p_emoji[c.performance_proficiency.value]} Performance: [b]{c.performance:+d}[/b]\n" \
f"{self._p_emoji[c.persuasion_proficiency.value]} Persuasion: [b]{c.persuasion:+d}[/b]\n" \ f"{self._p_emoji[c.persuasion_proficiency.value]} Persuasion: [b]{c.persuasion:+d}[/b]\n" \
f"{self._p_emoji[c.religion_proficiency.value]} Religion: [b]{c.religion:+d}[/b]\n" \ f"{self._p_emoji[c.religion_proficiency.value]} Religion: [b]{c.religion:+d}[/b]\n" \
f"{self._p_emoji[c.sleight_of_hand_proficiency.value]} Sleight of Hand: [b]{c.sleight_of_hand:+d}[/b]\n" \ f"{self._p_emoji[c.sleight_of_hand_proficiency.value]} Sleight of Hand: [b]{c.sleight_of_hand:+d}[/b]\n" \
f"{self._p_emoji[c.stealth_proficiency.value]} Stealth: [b]{c.stealth:+d}[/b]\n" \ f"{self._p_emoji[c.stealth_proficiency.value]} Stealth: [b]{c.stealth:+d}[/b]\n" \
f"{self._p_emoji[c.survival_proficiency.value]} Survival: [b]{c.survival:+d}[/b]\n" \ f"{self._p_emoji[c.survival_proficiency.value]} Survival: [b]{c.survival:+d}[/b]\n" \
f"\n" \ f"\n" \
f"{self._p_emoji[c.initiative_proficiency.value]} Initiative: [b]{c.initiative:+d}[/b]\n" f"{self._p_emoji[c.initiative_proficiency.value]} Initiative: [b]{c.initiative:+d}[/b]\n"
await data.reply(r) await data.reply(r)

View file

@ -2,6 +2,7 @@ import aiohttp
import sortedcontainers import sortedcontainers
import logging import logging
from royalnet.commands import * from royalnet.commands import *
import royalnet.serf as rs
from royalnet.utils import sentry_exc from royalnet.utils import sentry_exc
from ..utils import parse_5etools_entry from ..utils import parse_5etools_entry
@ -20,9 +21,9 @@ class DnditemCommand(Command):
_dnddata: sortedcontainers.SortedKeyList = None _dnddata: sortedcontainers.SortedKeyList = None
def __init__(self, interface: CommandInterface): def __init__(self, serf: rs.Serf, config: "ConfigDict"):
super().__init__(interface) super().__init__(serf, config)
self.loop.create_task(self._fetch_dnddata()) self.serf.tasks.add(self._fetch_dnddata())
async def _fetch_dnddata(self): async def _fetch_dnddata(self):
self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower()) self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower())

View file

@ -21,45 +21,46 @@ class DndjoinbattleCommand(rc.Command):
faction = Faction[args[0].upper()] faction = Faction[args[0].upper()]
initiative_mod = int(args.optional(1, default="0")) initiative_mod = int(args.optional(1, default="0"))
DndBattleUnitT = self.alchemy.get(DndBattleUnit) async with data.session_acm() as session:
DndBattleUnitT = self.alchemy.get(DndBattleUnit)
active_battle = await get_active_battle(data) active_battle = await get_active_battle(data=data, session=session)
if active_battle is None: if active_battle is None:
raise rc.CommandError("No battle is active in this chat.") raise rc.CommandError("No battle is active in this chat.")
active_character = await get_active_character(data) active_character = await get_active_character(data=data, session=session)
if active_character is None: if active_character is None:
raise rc.CommandError("You don't have an active character.") raise rc.CommandError("You don't have an active character.")
char: DndCharacter = active_character.character char: DndCharacter = active_character.character
units_with_same_name = await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( units_with_same_name = await ru.asyncify(session.query(DndBattleUnitT).filter_by(
name=char.name, name=char.name,
battle=active_battle.battle battle=active_battle.battle
).all) ).all)
if len(units_with_same_name) != 0: if len(units_with_same_name) != 0:
raise rc.InvalidInputError("A unit with the same name already exists.") raise rc.InvalidInputError("A unit with the same name already exists.")
roll = random.randrange(1, 21) roll = random.randrange(1, 21)
modifier = char.initiative + initiative_mod modifier = char.initiative + initiative_mod
modifier_str = f"{modifier:+d}" if modifier != 0 else "" modifier_str = f"{modifier:+d}" if modifier != 0 else ""
initiative = roll + modifier initiative = roll + modifier
dbu = DndBattleUnitT( dbu = DndBattleUnitT(
linked_character=char, linked_character=char,
initiative=initiative, initiative=initiative,
faction=faction, faction=faction,
name=char.name, name=char.name,
health_string=f"{char.current_hp}/{char.max_hp}", health_string=f"{char.current_hp}/{char.max_hp}",
armor_class=char.armor_class, armor_class=char.armor_class,
battle=active_battle.battle battle=active_battle.battle
) )
data.session.add(dbu) session.add(dbu)
await data.session_commit() await ru.asyncify(session.commit)
await data.reply(f"{dbu}\n" await data.reply(f"{dbu}\n"
f"joins the battle!\n" f"joins the battle!\n"
f"\n" f"\n"
f"🎲 1d20{modifier_str} = {roll}{modifier_str} = {initiative}") f"🎲 1d20{modifier_str} = {roll}{modifier_str} = {initiative}")

View file

@ -1,6 +1,7 @@
import re import re
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from royalnet.commands import * from royalnet.commands import *
import royalnet.utils as ru
from ..tables import DndCharacter from ..tables import DndCharacter
from ..types import DndProficiencyType from ..types import DndProficiencyType
@ -14,7 +15,8 @@ class DndnewCommand(Command):
syntax = "{name}\n{character_sheet}" syntax = "{name}\n{character_sheet}"
def _search_value(self, name: str, string: str): @staticmethod
def _search_value(name: str, string: str):
return re.search(r"\s*" + name + r"\s*([0-9.]+)\s*", string, re.IGNORECASE) return re.search(r"\s*" + name + r"\s*([0-9.]+)\s*", string, re.IGNORECASE)
def _parse(self, character_sheet: str) -> dict: def _parse(self, character_sheet: str) -> dict:
@ -50,20 +52,21 @@ class DndnewCommand(Command):
await data.reply(self._syntax()) await data.reply(self._syntax())
return return
creator = await data.get_author() async with data.session_acm() as session:
creator = await data.find_author(session=session, required=True)
name, rest = character_sheet.split("\n", 1) name, rest = character_sheet.split("\n", 1)
character = self.alchemy.get(DndCharacter)(name=name, creator=creator, **self._parse(rest)) character = self.alchemy.get(DndCharacter)(name=name, creator=creator, **self._parse(rest))
data.session.add(character) session.add(character)
try: try:
await data.session_commit() await ru.asyncify(session.commit)
except Exception as err: except Exception as err:
# THIS IS INTENDED # THIS IS INTENDED
if err.__class__.__name__ == "IntegrityError": if err.__class__.__name__ == "IntegrityError":
param_name = re.search(r'in column "(\S+)"', err.args[0]).group(1) param_name = re.search(r'in column "(\S+)"', err.args[0]).group(1)
raise CommandError(f"Mandatory parameter '{param_name}' is missing.") raise CommandError(f"Mandatory parameter '{param_name}' is missing.")
raise raise
await data.reply(f"✅ Character [b]{character.name}[/b] (ID: {character.character_id}) created!") await data.reply(f"✅ Character [b]{character.name}[/b] (ID: {character.character_id}) created!")

View file

@ -1,6 +1,7 @@
from typing import * from typing import *
import royalnet import royalnet
import royalnet.commands as rc import royalnet.commands as rc
import royalnet.utils as ru
from ..tables import DndBattle from ..tables import DndBattle
@ -18,12 +19,13 @@ class DndnewbattleCommand(rc.Command):
name = line_args[0] name = line_args[0]
description = line_args[1] if len(line_args) > 1 else None description = line_args[1] if len(line_args) > 1 else None
battle = BattleT( async with data.session_acm() as session:
name=name, battle = BattleT(
description=description name=name,
) description=description
)
data.session.add(battle) session.add(battle)
await data.session_commit() await ru.asyncify(session.commit)
await data.reply(f"✅ Battle [b]{battle.name}[/b] (ID: {battle.id}) created!") await data.reply(f"✅ Battle [b]{battle.name}[/b] (ID: {battle.id}) created!")

View file

@ -78,67 +78,68 @@ class DndrollCommand(Command):
} }
async def run(self, args: CommandArgs, data: CommandData) -> None: async def run(self, args: CommandArgs, data: CommandData) -> None:
active_character = await get_active_character(data) async with data.session_acm() as session:
if active_character is None: active_character = await get_active_character(session=session, data=data)
raise CommandError("You don't have an active character.") if active_character is None:
char = active_character.character raise CommandError("You don't have an active character.")
char = active_character.character
first = args[0] first = args[0]
second = args.optional(1) second = args.optional(1)
third = args.optional(2) third = args.optional(2)
advantage = False advantage = False
disadvantage = False disadvantage = False
extra_modifier = 0 extra_modifier = 0
if third: if third:
try: try:
extra_modifier = int(third) extra_modifier = int(third)
except ValueError: except ValueError:
raise InvalidInputError("Invalid modifier value (third parameter).") 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"): if second.startswith("a") or second.startswith("v"):
advantage = True advantage = True
elif second.startswith("d") or second.startswith("d"): elif second.startswith("d") or second.startswith("d"):
disadvantage = True disadvantage = True
else: else:
raise InvalidInputError("Invalid modifier value or advantage string (second parameter).") raise InvalidInputError("Invalid advantage string (second parameter).")
skill_short_name = first.lower() elif second:
for root in self._skill_names: try:
if skill_short_name.startswith(root): extra_modifier = int(second)
skill_name = self._skill_names[root] except ValueError:
break if second.startswith("a") or second.startswith("v"):
else: advantage = True
raise CommandError("Invalid skill name (first parameter).") elif second.startswith("d") or second.startswith("d"):
disadvantage = True
else:
raise InvalidInputError("Invalid modifier value or advantage string (second parameter).")
skill_modifier = int(char.__getattribute__(skill_name)) skill_short_name = first.lower()
modifier = skill_modifier + extra_modifier for root in self._skill_names:
modifier_str = f"{modifier:+d}" if modifier != 0 else "" if skill_short_name.startswith(root):
skill_name = self._skill_names[root]
break
else:
raise CommandError("Invalid skill name (first parameter).")
if advantage: skill_modifier = int(char.__getattribute__(skill_name))
roll_a = random.randrange(1, 21) modifier = skill_modifier + extra_modifier
roll_b = random.randrange(1, 21) modifier_str = f"{modifier:+d}" if modifier != 0 else ""
roll = max([roll_a, roll_b])
total = roll + modifier if advantage:
await data.reply(f"🎲 2d20h1{modifier_str} = ({roll_a}|{roll_b}){modifier_str} = [b]{total}[/b]") roll_a = random.randrange(1, 21)
elif disadvantage: roll_b = random.randrange(1, 21)
roll_a = random.randrange(1, 21) roll = max([roll_a, roll_b])
roll_b = random.randrange(1, 21) total = roll + modifier
roll = min([roll_a, roll_b]) await data.reply(f"🎲 [i]{skill_name.capitalize()}[/i]: 2d20h1{modifier_str} = ({roll_a}|{roll_b} ){modifier_str} = [b]{total}[/b]")
total = roll + modifier elif disadvantage:
await data.reply(f"🎲 2d20l1{modifier_str} = ({roll_a}|{roll_b}){modifier_str} = [b]{total}[/b]") roll_a = random.randrange(1, 21)
else: roll_b = random.randrange(1, 21)
roll = random.randrange(1, 21) roll = min([roll_a, roll_b])
total = roll + modifier total = roll + modifier
await data.reply(f"🎲 1d20{modifier_str} = {roll}{modifier_str} = [b]{total}[/b]") await data.reply(f"🎲 [i]{skill_name.capitalize()}[/i]: 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"🎲 [i]{skill_name.capitalize()}[/i]: 1d20{modifier_str} = {roll}{modifier_str} = [b]{total}[/b]")

View file

@ -3,6 +3,7 @@ import sortedcontainers
import logging import logging
from royalnet.commands import * from royalnet.commands import *
from royalnet.utils import ordinalformat, andformat, sentry_exc from royalnet.utils import ordinalformat, andformat, sentry_exc
import royalnet.serf as rs
from ..utils import parse_5etools_entry from ..utils import parse_5etools_entry
@ -20,9 +21,9 @@ class DndspellCommand(Command):
_dnddata: sortedcontainers.SortedKeyList = None _dnddata: sortedcontainers.SortedKeyList = None
def __init__(self, interface: CommandInterface): def __init__(self, serf: rs.Serf, config: "ConfigDict"):
super().__init__(interface) super().__init__(serf, config)
interface.loop.create_task(self._fetch_dnddata()) self.serf.tasks.add(self._fetch_dnddata())
async def _fetch_dnddata(self): async def _fetch_dnddata(self):
self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower()) self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower())

View file

@ -28,7 +28,7 @@ class RollCommand(Command):
except ValueError: except ValueError:
if isinstance(first, str) and "d20" in first: if isinstance(first, str) and "d20" in first:
raise InvalidInputError(f"Invalid value specified.\n" raise InvalidInputError(f"Invalid value specified.\n"
f"Were you looking for [c]{self.interface.prefix}dice[/c]?") f"Were you looking for [c]{self.serf.prefix}dice[/c]?")
else: else:
raise InvalidInputError("Invalid value specified.") raise InvalidInputError("Invalid value specified.")
result = random.randrange(minimum, maximum+1) result = random.randrange(minimum, maximum+1)

View file

@ -6,26 +6,25 @@ import royalnet.commands as rc
import pickle import pickle
async def get_active_battle(data: rc.CommandData) -> Optional[DndActiveBattle]: async def get_active_battle(*, data: rc.CommandData, session) -> Optional[DndActiveBattle]:
interface = data._interface alchemy = data.alchemy
alchemy = interface.alchemy
idata = get_interface_data(data) idata = get_interface_data(data)
DndAcBaT = alchemy.get(DndActiveBattle) DndAcBaT = alchemy.get(DndActiveBattle)
active_battles: List[DndActiveBattle] = await ru.asyncify( active_battles: List[DndActiveBattle] = await ru.asyncify(
data.session session
.query(DndAcBaT) .query(DndAcBaT)
.filter_by(interface_name=interface.name) .filter_by(interface_name=data.command.serf.__class__.__name__)
.all .all
) )
for active_battle in active_battles: for active_battle in active_battles:
if interface.name == "telegram": if data.command.serf.__class__.__name__ == "TelegramSerf":
# interface_data is chat id # interface_data is chat id
chat_id = pickle.loads(active_battle.interface_data) chat_id = pickle.loads(active_battle.interface_data)
if chat_id == idata: if chat_id == idata:
return active_battle return active_battle
elif interface.name == "discord": elif data.command.serf.__class__.__name__ == "DiscordSerf":
# interface_data is channel id # interface_data is channel id
chat_id = pickle.loads(active_battle.interface_data) chat_id = pickle.loads(active_battle.interface_data)
if chat_id == idata: if chat_id == idata:

View file

@ -6,27 +6,26 @@ import royalnet.commands as rc
import pickle import pickle
async def get_active_character(data: rc.CommandData) -> Optional[DndActiveCharacter]: async def get_active_character(*, data: rc.CommandData, session) -> Optional[DndActiveCharacter]:
interface = data._interface alchemy = data.alchemy
alchemy = interface.alchemy user = await data.find_author(session=session, required=True)
user = await data.get_author(error_if_none=True)
idata = get_interface_data(data) idata = get_interface_data(data)
DndAcChT = alchemy.get(DndActiveCharacter) DndAcChT = alchemy.get(DndActiveCharacter)
active_characters: List[DndActiveCharacter] = await ru.asyncify( active_characters: List[DndActiveCharacter] = await ru.asyncify(
data.session session
.query(DndAcChT) .query(DndAcChT)
.filter_by(interface_name=interface.name, user=user) .filter_by(interface_name=data.command.serf.__class__.__name__, user=user)
.all .all
) )
for active_character in active_characters: for active_character in active_characters:
if interface.name == "telegram": if data.command.serf.__class__.__name__ == "TelegramSerf":
# interface_data is chat id # interface_data is chat id
chat_id = pickle.loads(active_character.interface_data) chat_id = pickle.loads(active_character.interface_data)
if chat_id == idata: if chat_id == idata:
return active_character return active_character
elif interface.name == "discord": elif data.command.serf.__class__.__name__ == "DiscordSerf":
# interface_data is channel id # interface_data is channel id
chat_id = pickle.loads(active_character.interface_data) chat_id = pickle.loads(active_character.interface_data)
if chat_id == idata: if chat_id == idata:

View file

@ -2,9 +2,9 @@ import royalnet.commands as rc
def get_interface_data(data: rc.CommandData): def get_interface_data(data: rc.CommandData):
if data._interface.name == "telegram": if data.command.serf.__class__.__name__ == "TelegramSerf":
return data.message.chat.id return data.message.chat.id
elif data._interface.name == "discord": if data.command.serf.__class__.__name__ == "DiscordSerf":
return data.message.channel.id return data.message.channel.id
else: else:
raise rc.UnsupportedError("This interface isn't supported yet.") raise rc.UnsupportedError("This interface isn't supported yet.")

View file

@ -8,8 +8,8 @@ from ..types.faction import Faction
from sqlalchemy import and_ from sqlalchemy import and_
async def get_targets(data: rc.CommandData, target: Optional[str]) -> List[DndBattleUnit]: async def get_targets(target: Optional[str], *, data: rc.CommandData, session) -> List[DndBattleUnit]:
DndBattleUnitT = data._interface.alchemy.get(DndBattleUnit) DndBattleUnitT = data.alchemy.get(DndBattleUnit)
active_battle = await get_active_battle(data) active_battle = await get_active_battle(data)
if active_battle is None: if active_battle is None:
@ -23,14 +23,14 @@ async def get_targets(data: rc.CommandData, target: Optional[str]) -> List[DndBa
return [] return []
char = active_character.character char = active_character.character
return await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( return await ru.asyncify(session.query(DndBattleUnitT).filter_by(
linked_character=char, linked_character=char,
battle=battle battle=battle
).all) ).all)
# Get all # Get all
if target.upper() == "ALL": if target.upper() == "ALL":
return await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( return await ru.asyncify(session.query(DndBattleUnitT).filter_by(
battle=battle battle=battle
).all) ).all)
@ -40,13 +40,13 @@ async def get_targets(data: rc.CommandData, target: Optional[str]) -> List[DndBa
except ValueError: except ValueError:
pass pass
else: else:
return await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( return await ru.asyncify(session.query(DndBattleUnitT).filter_by(
faction=faction, faction=faction,
battle=battle battle=battle
).all) ).all)
# Get by ilike # Get by ilike
return await ru.asyncify(data.session.query(DndBattleUnitT).filter(and_( return await ru.asyncify(session.query(DndBattleUnitT).filter(and_(
DndBattleUnitT.name.ilike(target), DndBattleUnitT.name.ilike(target),
DndBattleUnitT.battle == battle DndBattleUnitT.battle == battle
)).all) )).all)