From f425be7c95c3175024c04090428cf79a5b4149c8 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 16 Mar 2020 23:21:24 +0100 Subject: [PATCH] #2, #3, #4 and #5: Implement multi target commands --- pyproject.toml | 2 +- rpgpack/commands/abstract/__init__.py | 5 ++ rpgpack/commands/abstract/dndbattletarget.py | 30 +++++++++++ rpgpack/commands/dnddamage.py | 30 +++-------- rpgpack/commands/dnddeathsave.py | 32 +++--------- rpgpack/commands/dndextra.py | 29 +++-------- rpgpack/commands/dndheal.py | 28 +++-------- rpgpack/commands/dndstatus.py | 30 +++-------- rpgpack/types/faction.py | 7 +++ rpgpack/utils/__init__.py | 4 +- rpgpack/utils/findunitincurrentbattle.py | 39 --------------- rpgpack/utils/gettargets.py | 52 ++++++++++++++++++++ rpgpack/version.py | 2 +- 13 files changed, 131 insertions(+), 159 deletions(-) create mode 100644 rpgpack/commands/abstract/__init__.py create mode 100644 rpgpack/commands/abstract/dndbattletarget.py delete mode 100644 rpgpack/utils/findunitincurrentbattle.py create mode 100644 rpgpack/utils/gettargets.py diff --git a/pyproject.toml b/pyproject.toml index 7e5734ca..201a55b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ [tool.poetry] name = "rpgpack" - version = "5.4" + version = "5.5" description = "A Royalnet Pack to play role-playing-games" authors = ["Stefano Pigozzi "] license = "AGPL-3.0+" diff --git a/rpgpack/commands/abstract/__init__.py b/rpgpack/commands/abstract/__init__.py new file mode 100644 index 00000000..e7fa3ba7 --- /dev/null +++ b/rpgpack/commands/abstract/__init__.py @@ -0,0 +1,5 @@ +from .dndbattletarget import DndBattleTargetCommand + +__all__ = [ + "DndBattleTargetCommand", +] diff --git a/rpgpack/commands/abstract/dndbattletarget.py b/rpgpack/commands/abstract/dndbattletarget.py new file mode 100644 index 00000000..48c4a4f8 --- /dev/null +++ b/rpgpack/commands/abstract/dndbattletarget.py @@ -0,0 +1,30 @@ +import abc +from typing import * +import royalnet +import royalnet.commands as rc +import royalnet.utils as ru +from ...tables import DndBattleUnit +from ...utils import get_targets + + +class DndBattleTargetCommand(rc.Command, abc.ABC): + @abc.abstractmethod + async def _change(self, unit: DndBattleUnit, args: List[str]): + ... + + 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:]) + + await data.session_commit() + + message = [] + for unit in units: + message.append(f"{unit}") + + await data.reply("\n\n".join(message)) diff --git a/rpgpack/commands/dnddamage.py b/rpgpack/commands/dnddamage.py index 2c3e44bb..55fe40f7 100644 --- a/rpgpack/commands/dnddamage.py +++ b/rpgpack/commands/dnddamage.py @@ -1,34 +1,18 @@ from typing import * -import royalnet -import royalnet.commands as rc -import royalnet.utils as ru from ..tables import DndBattleUnit -from ..utils import find_unit_in_current_battle +from .abstract import DndBattleTargetCommand -class DnddamageCommand(rc.Command): +class DnddamageCommand(DndBattleTargetCommand): name: str = "dnddamage" - description: str = "Damage a unit in the currently active battle." + description: str = "Damage a target in the currently active battle." - syntax: str = "[name] {damage}" + syntax: str = "{target} {damage}" - aliases = ["dmg", "ddmg", "dnddmg", "damage", "ddamage"] - - async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: - if len(args) > 1: - name = args[0] - damage = int(args[1]) - else: - name = None - damage = int(args[0]) - - unit = await find_unit_in_current_battle(data, name) - if unit is None: - raise rc.InvalidInputError("No such unit is fighting in the currently active battle.") + aliases = ["damage", "ddamage", "dd"] + async def _change(self, unit: DndBattleUnit, args: List[str]): health = unit.health - health.change(-damage) + health.change(-int(args[0])) unit.health = health - await data.session_commit() - await data.reply(f"{unit}") diff --git a/rpgpack/commands/dnddeathsave.py b/rpgpack/commands/dnddeathsave.py index a46b27b7..bce0ec20 100644 --- a/rpgpack/commands/dnddeathsave.py +++ b/rpgpack/commands/dnddeathsave.py @@ -1,40 +1,24 @@ from typing import * -import royalnet import royalnet.commands as rc -import royalnet.utils as ru from ..tables import DndBattleUnit -from ..utils import find_unit_in_current_battle +from .abstract import DndBattleTargetCommand -class DnddeathsaveCommand(rc.Command): +class DnddeathsaveCommand(DndBattleTargetCommand): name: str = "dnddeathsave" - description: str = "Add a death save result to a unit in the currently active battle." + description: str = "Add a death save result to a target in the currently active battle." - syntax: str = "[name] {s|f}" + syntax: str = "{target} {s|f}" aliases = ["deathsave", "ddeathsave", "ds", "dds", "dndds"] - async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: - if len(args) > 1: - name = args[0] - result = args[1].lower() - else: - name = None - result = args[0].lower() - - unit = await find_unit_in_current_battle(data, name) - if unit is None: - raise rc.InvalidInputError("No such unit is fighting in the currently active battle.") - + async def _change(self, unit: DndBattleUnit, args: List[str]): health = unit.health - if result[0] == "s": + if args[0][0] == "s": health.deathsave_success() - elif result[0] == "f": + elif args[0][0] == "f": health.deathsave_failure() else: - raise rc.InvalidInputError("Unknown result type") + raise rc.InvalidInputError(f"Unknown result type [c]{args[0][0]}[/c].") unit.health = health - - await data.session_commit() - await data.reply(f"{unit}") diff --git a/rpgpack/commands/dndextra.py b/rpgpack/commands/dndextra.py index 38d37e91..aa9a7949 100644 --- a/rpgpack/commands/dndextra.py +++ b/rpgpack/commands/dndextra.py @@ -1,33 +1,16 @@ from typing import * -import royalnet -import royalnet.commands as rc -import royalnet.utils as ru from ..tables import DndBattleUnit -from ..utils import find_unit_in_current_battle +from .abstract import DndBattleTargetCommand -class DndextraCommand(rc.Command): +class DndextraCommand(DndBattleTargetCommand): name: str = "dndextra" - description: str = "Change the extras for a unit in the current battle." + description: str = "Change the extras for a target in the current battle." - syntax: str = "[name] {extra}" + syntax: str = "{target} {extra}" aliases = ["extra", "dextra"] - async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: - name = args.optional(0) - extra = " ".join(args[1:]) - - if name is not None: - unit: Optional[DndBattleUnit] = await find_unit_in_current_battle(data, name) - else: - unit = None - - if unit is None: - extra = " ".join(args) - unit: Optional[DndBattleUnit] = await find_unit_in_current_battle(data, None) - - unit.extra = extra - await data.session_commit() - await data.reply(f"{unit}") + async def _change(self, unit: DndBattleUnit, args: List[str]): + unit.extra = " ".join(args) diff --git a/rpgpack/commands/dndheal.py b/rpgpack/commands/dndheal.py index 84aec16b..6c6abaf8 100644 --- a/rpgpack/commands/dndheal.py +++ b/rpgpack/commands/dndheal.py @@ -1,34 +1,18 @@ from typing import * -import royalnet -import royalnet.commands as rc -import royalnet.utils as ru from ..tables import DndBattleUnit -from ..utils import find_unit_in_current_battle +from .abstract import DndBattleTargetCommand -class DndhealCommand(rc.Command): +class DndhealCommand(DndBattleTargetCommand): name: str = "dndheal" - description: str = "Heal a unit in the currently active battle." + description: str = "Heal a target in the currently active battle." - syntax: str = "[name] {heal}" + syntax: str = "{target} {heal}" aliases = ["heal", "dheal"] - async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: - if len(args) > 1: - name = args[0] - heal = int(args[1]) - else: - name = None - heal = int(args[0]) - - unit = await find_unit_in_current_battle(data, name) - if unit is None: - raise rc.InvalidInputError("No such unit is fighting in the currently active battle.") - + async def _change(self, unit: DndBattleUnit, args: List[str]): health = unit.health - health.change(heal) + health.change(int(args[0])) unit.health = health - await data.session_commit() - await data.reply(f"{unit}") diff --git a/rpgpack/commands/dndstatus.py b/rpgpack/commands/dndstatus.py index a7a6c89f..38fd0e88 100644 --- a/rpgpack/commands/dndstatus.py +++ b/rpgpack/commands/dndstatus.py @@ -1,34 +1,16 @@ from typing import * -import royalnet -import royalnet.commands as rc -import royalnet.utils as ru from ..tables import DndBattleUnit -from ..utils import find_unit_in_current_battle +from .abstract import DndBattleTargetCommand -class DndstatusCommand(rc.Command): +class DndstatusCommand(DndBattleTargetCommand): name: str = "dndstatus" - description: str = "Change the status for a unit in the current battle." + description: str = "Change the target for a unit in the current battle." - syntax: str = "[name] {status}" + syntax: str = "{target} {status}" aliases = ["status", "dstatus"] - async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: - name = args.optional(0) - status = " ".join(args[1:]) - - if name is not None: - unit: Optional[DndBattleUnit] = await find_unit_in_current_battle(data, name) - else: - unit = None - - if unit is None: - status = " ".join(args) - unit: Optional[DndBattleUnit] = await find_unit_in_current_battle(data, None) - - - unit.status = status - await data.session_commit() - await data.reply(f"{unit}") + async def _change(self, unit: DndBattleUnit, args: List[str]): + unit.status = " ".join(args) diff --git a/rpgpack/types/faction.py b/rpgpack/types/faction.py index 3cc84e56..395b676b 100644 --- a/rpgpack/types/faction.py +++ b/rpgpack/types/faction.py @@ -12,3 +12,10 @@ class Faction(enum.Enum): BLACK = "⚫️" WHITE = "⚪️" BROWN = "🟤" + + @classmethod + def get(cls, string: str): + try: + return cls[string.upper()] + except KeyError: + return cls(string) diff --git a/rpgpack/utils/__init__.py b/rpgpack/utils/__init__.py index a1149e4a..26187345 100644 --- a/rpgpack/utils/__init__.py +++ b/rpgpack/utils/__init__.py @@ -2,12 +2,12 @@ from .parse5etoolsentry import parse_5etools_entry from .getinterfacedata import get_interface_data from .getactivechar import get_active_character from .getactivebattle import get_active_battle -from .findunitincurrentbattle import find_unit_in_current_battle +from .gettargets import get_targets __all__ = [ "parse_5etools_entry", "get_interface_data", "get_active_character", "get_active_battle", - "find_unit_in_current_battle" + "get_targets" ] diff --git a/rpgpack/utils/findunitincurrentbattle.py b/rpgpack/utils/findunitincurrentbattle.py deleted file mode 100644 index 90de921d..00000000 --- a/rpgpack/utils/findunitincurrentbattle.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import * -import royalnet.commands as rc -import royalnet.utils as ru - -from ..tables import DndBattleUnit -from .getactivebattle import get_active_battle -from .getactivechar import get_active_character - -from sqlalchemy import func, and_ - - -async def find_unit_in_current_battle(data: rc.CommandData, name: Optional[str]) -> Optional[DndBattleUnit]: - DndBattleUnitT = data._interface.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.") - - if name is None: - active_character = await get_active_character(data) - if active_character is None: - raise rc.InvalidInputError("You currently have no active character.") - - unit = await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( - linked_character=active_character.character, - battle=active_battle.battle - ).one_or_none) - if unit is None: - raise rc.InvalidInputError("Your active character is not fighting in this battle.") - - else: - unit = await ru.asyncify(data.session.query(DndBattleUnitT).filter(and_( - func.lower(DndBattleUnitT.name) == func.lower(name), - DndBattleUnitT.battle == active_battle.battle - )).one_or_none) - if unit is None: - return None - - return unit diff --git a/rpgpack/utils/gettargets.py b/rpgpack/utils/gettargets.py new file mode 100644 index 00000000..1ce7f2e8 --- /dev/null +++ b/rpgpack/utils/gettargets.py @@ -0,0 +1,52 @@ +from typing import * +import royalnet.commands as rc +import royalnet.utils as ru +from ..tables import DndBattleUnit +from .getactivebattle import get_active_battle +from .getactivechar import get_active_character +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) + + active_battle = await get_active_battle(data) + if active_battle is None: + raise rc.CommandError("No battle is active in this chat.") + battle = active_battle.battle + + # Get the active character + if not target or target.upper() == "SELF": + active_character = await get_active_character(data) + if active_character is None: + return [] + char = active_character.character + + return await ru.asyncify(data.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( + battle=battle + ).all) + + # Get by faction + try: + faction = Faction.get(target) + except ValueError: + pass + else: + return await ru.asyncify(data.session.query(DndBattleUnitT).filter_by( + faction=faction, + battle=battle + ).all) + + # Get by ilike + return await ru.asyncify(data.session.query(DndBattleUnitT).filter(and_( + DndBattleUnitT.name.ilike(target), + DndBattleUnitT.battle == battle + )).all) diff --git a/rpgpack/version.py b/rpgpack/version.py index 03217183..63bf6999 100644 --- a/rpgpack/version.py +++ b/rpgpack/version.py @@ -1 +1 @@ -semantic = "5.4" +semantic = "5.5"