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

View file

@ -10,21 +10,23 @@ from ...utils import get_targets
class DndBattleTargetCommand(rc.Command, abc.ABC):
@abc.abstractmethod
async def _change(self, unit: DndBattleUnit, args: List[str]):
...
raise NotImplementedError()
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
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:
await self._change(unit, args[1:])
async with data.session_acm() as session:
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 = []
for unit in units:
message.append(f"{unit}")
await session.commit()
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:
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)
DndActiveCharacterT = self.alchemy.get(DndActiveCharacter)
async with data.session_acm() as session:
author = await data.find_author(session=session, required=True)
active_character = await get_active_character(session=session, data=data)
# Display the active character
if identifier is None:
DndCharacterT = self.alchemy.get(DndCharacter)
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:
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:
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(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]!")
# Change the active character
active_character.character = char
await asyncify(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)
ABattleT = self.alchemy.get(DndActiveBattle)
identifier = args.joined()
active_battle = await get_active_battle(data)
async with data.session_acm() as session:
identifier = args.joined()
active_battle = await get_active_battle(session=session, data=data)
# Display the active character
if identifier == "":
if active_battle is None:
await data.reply(" No battles have ever been activated in this chat.")
else:
await data.reply(active_battle.battle.create_message())
return
# Display the active character
if identifier == "":
if active_battle is None:
await data.reply(" No battles have ever been activated in this chat.")
else:
await data.reply(active_battle.battle.create_message())
return
# Find the battle
try:
identifier = int(identifier)
except ValueError:
# Find the battle by name
battles = await asyncify(data.session.query(BattleT).filter_by(name=identifier).all)
if len(battles) >= 2:
char_string = "\n".join([f"[c]{battle.id}[/c]" for battle in battles])
raise CommandError(f"Multiple battles share the name [b]{identifier}[/b], "
f"please activate one of them by using their id:\n{char_string}")
elif len(battles) == 1:
battle = battles[0]
# Find the battle
try:
identifier = int(identifier)
except ValueError:
# Find the battle by name
battles = await asyncify(session.query(BattleT).filter_by(name=identifier).all)
if len(battles) >= 2:
char_string = "\n".join([f"[c]{battle.id}[/c]" for battle in battles])
raise CommandError(f"Multiple battles share the name [b]{identifier}[/b], "
f"please activate one of them by using their id:\n{char_string}")
elif len(battles) == 1:
battle = battles[0]
else:
battle = None
else:
battle = None
else:
# Find the battle by id
battle = await asyncify(data.session.query(BattleT)
# Find the battle by id
battle = await asyncify(session.query(BattleT)
.filter_by(id=identifier)
.one_or_none)
if battle is None:
raise CommandError("No such battle found.")
# Check if the player already has an active character
if active_battle is None:
# Create a new active battle
active_battle = ABattleT(
battle=battle,
interface_name=self.interface.name,
interface_data=pickle.dumps(get_interface_data(data)))
data.session.add(active_battle)
else:
# Change the active character
active_battle.battle = battle
await data.session_commit()
await data.reply(f"⚔️ [b]{battle}[/b]! Roll initiative!")
if battle is None:
raise CommandError("No such battle found.")
# Check if the player already has an active character
if active_battle is None:
# Create a new active battle
active_battle = ABattleT(
battle=battle,
interface_name=data.command.serf.__class__.__name__,
interface_data=pickle.dumps(get_interface_data(data)))
session.add(active_battle)
else:
# Change the active character
active_battle.battle = battle
await asyncify(session.commit)
await data.reply(f"⚔️ [b]{battle}[/b]! Roll initiative!")

View file

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

View file

@ -1,5 +1,6 @@
import re
from royalnet.commands import *
import royalnet.utils as ru
from .dndnew import DndnewCommand
from ..tables import DndCharacter
from ..utils import get_active_character
@ -15,20 +16,21 @@ class DndeditCommand(DndnewCommand):
async def run(self, args: CommandArgs, data: CommandData) -> None:
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:
raise CommandError("You don't have an active character.")
char: DndCharacter = active_character.character
if active_character is None:
raise CommandError("You don't have an active character.")
char: DndCharacter = active_character.character
if character_sheet == "":
await data.reply(char.to_edit_string())
return
if character_sheet == "":
await data.reply(char.to_edit_string())
return
arguments = self._parse(character_sheet)
for key in arguments:
char.__setattr__(key, arguments[key])
arguments = self._parse(character_sheet)
for key in arguments:
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:
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:
raise CommandError("You don't have an active character.")
c: DndCharacter = active_character.character
if active_character is None:
raise CommandError("You don't have an active character.")
c: DndCharacter = active_character.character
r = f"[b]{c.name}[/b]\n" \
f"🔰 Lv. {c.level}\n" \
f"\n" \
f"❤️ {c.current_hp}/{c.max_hp}\n" \
f"🛡 {c.armor_class}\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.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.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.charisma_save_proficiency.value]} Charisma: [b]{c.charisma:+d}[/b] ({c.charisma_score})\n" \
f"\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.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.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.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.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.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.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.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.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"\n" \
f"{self._p_emoji[c.initiative_proficiency.value]} Initiative: [b]{c.initiative:+d}[/b]\n"
r = f"[b]{c.name}[/b]\n" \
f"🔰 Lv. {c.level}\n" \
f"\n" \
f"❤️ {c.current_hp}/{c.max_hp}\n" \
f"🛡 {c.armor_class}\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.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.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.charisma_save_proficiency.value]} Charisma: [b]{c.charisma:+d}[/b] ({c.charisma_score})\n" \
f"\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.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.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.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.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.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.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.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.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"\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 logging
from royalnet.commands import *
import royalnet.serf as rs
from royalnet.utils import sentry_exc
from ..utils import parse_5etools_entry
@ -20,9 +21,9 @@ class DnditemCommand(Command):
_dnddata: sortedcontainers.SortedKeyList = None
def __init__(self, interface: CommandInterface):
super().__init__(interface)
self.loop.create_task(self._fetch_dnddata())
def __init__(self, serf: rs.Serf, config: "ConfigDict"):
super().__init__(serf, config)
self.serf.tasks.add(self._fetch_dnddata())
async def _fetch_dnddata(self):
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()]
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)
if active_battle is None:
raise rc.CommandError("No battle is active in this chat.")
active_battle = await get_active_battle(data=data, session=session)
if active_battle is None:
raise rc.CommandError("No battle is active in this chat.")
active_character = await get_active_character(data)
if active_character is None:
raise rc.CommandError("You don't have an active character.")
active_character = await get_active_character(data=data, session=session)
if active_character is None:
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(
name=char.name,
battle=active_battle.battle
).all)
units_with_same_name = await ru.asyncify(session.query(DndBattleUnitT).filter_by(
name=char.name,
battle=active_battle.battle
).all)
if len(units_with_same_name) != 0:
raise rc.InvalidInputError("A unit with the same name already exists.")
if len(units_with_same_name) != 0:
raise rc.InvalidInputError("A unit with the same name already exists.")
roll = random.randrange(1, 21)
modifier = char.initiative + initiative_mod
modifier_str = f"{modifier:+d}" if modifier != 0 else ""
initiative = roll + modifier
roll = random.randrange(1, 21)
modifier = char.initiative + initiative_mod
modifier_str = f"{modifier:+d}" if modifier != 0 else ""
initiative = roll + modifier
dbu = DndBattleUnitT(
linked_character=char,
initiative=initiative,
faction=faction,
name=char.name,
health_string=f"{char.current_hp}/{char.max_hp}",
armor_class=char.armor_class,
battle=active_battle.battle
)
dbu = DndBattleUnitT(
linked_character=char,
initiative=initiative,
faction=faction,
name=char.name,
health_string=f"{char.current_hp}/{char.max_hp}",
armor_class=char.armor_class,
battle=active_battle.battle
)
data.session.add(dbu)
await data.session_commit()
session.add(dbu)
await ru.asyncify(session.commit)
await data.reply(f"{dbu}\n"
f"joins the battle!\n"
f"\n"
f"🎲 1d20{modifier_str} = {roll}{modifier_str} = {initiative}")
await data.reply(f"{dbu}\n"
f"joins the battle!\n"
f"\n"
f"🎲 1d20{modifier_str} = {roll}{modifier_str} = {initiative}")

View file

@ -1,6 +1,7 @@
import re
# noinspection PyUnresolvedReferences
from royalnet.commands import *
import royalnet.utils as ru
from ..tables import DndCharacter
from ..types import DndProficiencyType
@ -14,7 +15,8 @@ class DndnewCommand(Command):
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)
def _parse(self, character_sheet: str) -> dict:
@ -50,20 +52,21 @@ class DndnewCommand(Command):
await data.reply(self._syntax())
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))
data.session.add(character)
character = self.alchemy.get(DndCharacter)(name=name, creator=creator, **self._parse(rest))
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
try:
await ru.asyncify(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] (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 *
import royalnet
import royalnet.commands as rc
import royalnet.utils as ru
from ..tables import DndBattle
@ -18,12 +19,13 @@ class DndnewbattleCommand(rc.Command):
name = line_args[0]
description = line_args[1] if len(line_args) > 1 else None
battle = BattleT(
name=name,
description=description
)
async with data.session_acm() as session:
battle = BattleT(
name=name,
description=description
)
data.session.add(battle)
await data.session_commit()
session.add(battle)
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:
active_character = await get_active_character(data)
if active_character is None:
raise CommandError("You don't have an active character.")
char = active_character.character
async with data.session_acm() as session:
active_character = await get_active_character(session=session, data=data)
if active_character is None:
raise CommandError("You don't have an active character.")
char = active_character.character
first = args[0]
second = args.optional(1)
third = args.optional(2)
first = args[0]
second = args.optional(1)
third = args.optional(2)
advantage = False
disadvantage = False
extra_modifier = 0
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 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 modifier value or advantage string (second parameter).")
raise InvalidInputError("Invalid 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).")
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_modifier = int(char.__getattribute__(skill_name))
modifier = skill_modifier + extra_modifier
modifier_str = f"{modifier:+d}" if modifier != 0 else ""
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).")
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]")
skill_modifier = int(char.__getattribute__(skill_name))
modifier = skill_modifier + extra_modifier
modifier_str = f"{modifier:+d}" if modifier != 0 else ""
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"🎲 [i]{skill_name.capitalize()}[/i]: 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"🎲 [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
from royalnet.commands import *
from royalnet.utils import ordinalformat, andformat, sentry_exc
import royalnet.serf as rs
from ..utils import parse_5etools_entry
@ -20,9 +21,9 @@ class DndspellCommand(Command):
_dnddata: sortedcontainers.SortedKeyList = None
def __init__(self, interface: CommandInterface):
super().__init__(interface)
interface.loop.create_task(self._fetch_dnddata())
def __init__(self, serf: rs.Serf, config: "ConfigDict"):
super().__init__(serf, config)
self.serf.tasks.add(self._fetch_dnddata())
async def _fetch_dnddata(self):
self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower())

View file

@ -28,7 +28,7 @@ class RollCommand(Command):
except ValueError:
if isinstance(first, str) and "d20" in first:
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:
raise InvalidInputError("Invalid value specified.")
result = random.randrange(minimum, maximum+1)

View file

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

View file

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

View file

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

View file

@ -8,8 +8,8 @@ from ..types.faction import Faction
from sqlalchemy import and_
async def get_targets(data: rc.CommandData, target: Optional[str]) -> List[DndBattleUnit]:
DndBattleUnitT = data._interface.alchemy.get(DndBattleUnit)
async def get_targets(target: Optional[str], *, data: rc.CommandData, session) -> List[DndBattleUnit]:
DndBattleUnitT = data.alchemy.get(DndBattleUnit)
active_battle = await get_active_battle(data)
if active_battle is None:
@ -23,14 +23,14 @@ async def get_targets(data: rc.CommandData, target: Optional[str]) -> List[DndBa
return []
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,
battle=battle
).all)
# Get 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
).all)
@ -40,13 +40,13 @@ async def get_targets(data: rc.CommandData, target: Optional[str]) -> List[DndBa
except ValueError:
pass
else:
return await ru.asyncify(data.session.query(DndBattleUnitT).filter_by(
return await ru.asyncify(session.query(DndBattleUnitT).filter_by(
faction=faction,
battle=battle
).all)
# 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.battle == battle
)).all)