mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
Complete initiative tracker
This commit is contained in:
parent
479657d6ee
commit
bdf8177cc9
14 changed files with 336 additions and 11 deletions
|
@ -13,6 +13,12 @@ from .testfaction import TestfactionCommand
|
|||
from .dndnewbattle import DndnewbattleCommand
|
||||
from .dndactivebattle import DndactivebattleCommand
|
||||
from .dndaddunit import DndaddunitCommand
|
||||
from .dnddamage import DnddamageCommand
|
||||
from .dndheal import DndhealCommand
|
||||
from .dndstatus import DndstatusCommand
|
||||
from .dndextra import DndextraCommand
|
||||
from .dnddeathsave import DnddeathsaveCommand
|
||||
from .dndjoinbattle import DndjoinbattleCommand
|
||||
|
||||
# Enter the commands of your Pack here!
|
||||
available_commands = [
|
||||
|
@ -29,7 +35,13 @@ available_commands = [
|
|||
TestfactionCommand,
|
||||
DndnewbattleCommand,
|
||||
DndactivebattleCommand,
|
||||
DndaddunitCommand
|
||||
DndaddunitCommand,
|
||||
DnddamageCommand,
|
||||
DndhealCommand,
|
||||
DndstatusCommand,
|
||||
DndextraCommand,
|
||||
DnddeathsaveCommand,
|
||||
DndjoinbattleCommand,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
|
|
|
@ -2,7 +2,7 @@ from typing import *
|
|||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from ..types import Faction, Health
|
||||
from ..types import Faction
|
||||
from ..tables import DndBattleUnit
|
||||
from ..utils import get_active_battle
|
||||
|
||||
|
@ -12,7 +12,7 @@ class DndaddunitCommand(rc.Command):
|
|||
|
||||
description: str = "Add an Unit to a Battle."
|
||||
|
||||
aliases = ["dau", "dndau", "daddunit", ""]
|
||||
aliases = ["dau", "dndau", "addunit", "daddunit"]
|
||||
|
||||
syntax: str = "{faction} {name} {initiative} {health} {armorclass}"
|
||||
|
||||
|
@ -29,7 +29,16 @@ class DndaddunitCommand(rc.Command):
|
|||
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)
|
||||
|
||||
if len(units_with_same_name) != 0:
|
||||
raise rc.InvalidInputError("A unit with the same name already exists.")
|
||||
|
||||
dbu = DndBattleUnitT(
|
||||
linked_character_id=None,
|
||||
initiative=initiative,
|
||||
faction=faction,
|
||||
name=name,
|
||||
|
@ -41,5 +50,8 @@ class DndaddunitCommand(rc.Command):
|
|||
data.session.add(dbu)
|
||||
await data.session_commit()
|
||||
|
||||
await data.reply(f"✅ [b]{dbu.name}[/b] joined the battle!\n"
|
||||
f"{dbu}")
|
||||
await data.reply(f"{dbu}\n"
|
||||
f"joins the battle!")
|
||||
|
||||
if dbu.health.hidden:
|
||||
await data.delete_invoking()
|
34
rpgpack/commands/dnddamage.py
Normal file
34
rpgpack/commands/dnddamage.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
|
||||
|
||||
class DnddamageCommand(rc.Command):
|
||||
name: str = "dnddamage"
|
||||
|
||||
description: str = "Damage a unit in the currently active battle."
|
||||
|
||||
syntax: str = "[name] {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.")
|
||||
|
||||
health = unit.health
|
||||
health.change(-damage)
|
||||
unit.health = health
|
||||
await data.session_commit()
|
||||
await data.reply(f"{unit}")
|
40
rpgpack/commands/dnddeathsave.py
Normal file
40
rpgpack/commands/dnddeathsave.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
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
|
||||
|
||||
|
||||
class DnddeathsaveCommand(rc.Command):
|
||||
name: str = "dnddeathsave"
|
||||
|
||||
description: str = "Add a death save result to a unit in the currently active battle."
|
||||
|
||||
syntax: str = "[name] {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.")
|
||||
|
||||
health = unit.health
|
||||
if result[0] == "s":
|
||||
health.deathsave_success()
|
||||
elif result[0] == "f":
|
||||
health.deathsave_failure()
|
||||
else:
|
||||
raise rc.InvalidInputError("Unknown result type")
|
||||
unit.health = health
|
||||
|
||||
await data.session_commit()
|
||||
await data.reply(f"{unit}")
|
33
rpgpack/commands/dndextra.py
Normal file
33
rpgpack/commands/dndextra.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
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
|
||||
|
||||
|
||||
class DndextraCommand(rc.Command):
|
||||
name: str = "dndextra"
|
||||
|
||||
description: str = "Change the extras for a unit in the current battle."
|
||||
|
||||
syntax: str = "[name] {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}")
|
34
rpgpack/commands/dndheal.py
Normal file
34
rpgpack/commands/dndheal.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
|
||||
|
||||
class DndhealCommand(rc.Command):
|
||||
name: str = "dndheal"
|
||||
|
||||
description: str = "Heal a unit in the currently active battle."
|
||||
|
||||
syntax: str = "[name] {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.")
|
||||
|
||||
health = unit.health
|
||||
health.change(heal)
|
||||
unit.health = health
|
||||
await data.session_commit()
|
||||
await data.reply(f"{unit}")
|
65
rpgpack/commands/dndjoinbattle.py
Normal file
65
rpgpack/commands/dndjoinbattle.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
from typing import *
|
||||
import random
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from ..types import Faction
|
||||
from ..tables import DndBattleUnit, DndCharacter
|
||||
from ..utils import get_active_battle, get_active_character
|
||||
|
||||
|
||||
class DndjoinbattleCommand(rc.Command):
|
||||
name: str = "dndjoinbattle"
|
||||
|
||||
description: str = "Add your currently active character to the currently active battle."
|
||||
|
||||
aliases = ["joinbattle", "djoinbattle"]
|
||||
|
||||
syntax: str = "{faction} {initiative_mod}"
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
faction = Faction[args[0].upper()]
|
||||
initiative_mod = int(args.optional(1, default="0"))
|
||||
|
||||
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_character = await get_active_character(data)
|
||||
if active_character is None:
|
||||
raise rc.CommandError("You don't have an active 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)
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
await data.reply(f"{dbu}\n"
|
||||
f"joins the battle!\n"
|
||||
f"\n"
|
||||
f"🎲 1d20{modifier_str} = {roll}{modifier_str} = {initiative}")
|
34
rpgpack/commands/dndstatus.py
Normal file
34
rpgpack/commands/dndstatus.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
|
||||
|
||||
class DndstatusCommand(rc.Command):
|
||||
name: str = "dndstatus"
|
||||
|
||||
description: str = "Change the status for a unit in the current battle."
|
||||
|
||||
syntax: str = "[name] {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}")
|
|
@ -30,5 +30,5 @@ class DndBattle:
|
|||
string.append(f"{self.description}\n")
|
||||
string.append("\n")
|
||||
for unit in sorted(self.units, key=lambda u: -u.initiative):
|
||||
string.append(f"{unit}\n")
|
||||
string.append(f"{unit}\n\n")
|
||||
return "".join(string)
|
||||
|
|
|
@ -11,6 +11,14 @@ class DndBattleUnit:
|
|||
def id(self):
|
||||
return Column(Integer, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def linked_character_id(self):
|
||||
return Column(Integer, ForeignKey("dndcharacters.character_id"))
|
||||
|
||||
@declared_attr
|
||||
def linked_character(self):
|
||||
return relationship("DndCharacter", foreign_keys=self.linked_character_id, backref="as_battle_unit")
|
||||
|
||||
@declared_attr
|
||||
def battle_id(self):
|
||||
return Column(Integer, ForeignKey("dndbattle.id"))
|
||||
|
|
|
@ -188,6 +188,10 @@ class DndCharacter:
|
|||
def survival_proficiency(self):
|
||||
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
|
||||
|
||||
@declared_attr
|
||||
def initiative_proficiency(self):
|
||||
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
|
||||
|
||||
@property
|
||||
def strength_save(self):
|
||||
return self.strength + math.floor(self.proficiency_bonus * self.strength_save_proficiency.value)
|
||||
|
@ -284,6 +288,10 @@ class DndCharacter:
|
|||
def survival(self):
|
||||
return self.wisdom + math.floor(self.proficiency_bonus * self.survival_proficiency.value)
|
||||
|
||||
@property
|
||||
def initiative(self):
|
||||
return self.dexterity + math.floor(self.proficiency_bonus * self.initiative_proficiency.value)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__} {self.name}>"
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class Health:
|
|||
if self.temp_value > 0:
|
||||
string.append(f"{self.temp_value:+}")
|
||||
string.append("/")
|
||||
string.append(f"{self.max_value}̋")
|
||||
string.append(f"{self.max_value}")
|
||||
string.append("s" * self.deathsave_successes)
|
||||
string.append("f" * self.deathsave_failures)
|
||||
return "".join(string)
|
||||
|
@ -112,15 +112,19 @@ class Health:
|
|||
self.value = 0
|
||||
|
||||
def deathsave_success(self) -> None:
|
||||
if self.dying:
|
||||
if not self.dying:
|
||||
raise ValueError("Can't roll death saves while alive")
|
||||
if self.stable:
|
||||
raise ValueError("Successful death saves are capped at 3")
|
||||
raise ValueError("Can't roll death saves while stable")
|
||||
if self.dead:
|
||||
raise ValueError("Can't roll death saves while dead")
|
||||
self.deathsave_successes += 1
|
||||
|
||||
def deathsave_failure(self) -> None:
|
||||
if self.dying:
|
||||
if not self.dying:
|
||||
raise ValueError("Can't roll death saves while alive")
|
||||
if self.stable:
|
||||
raise ValueError("Can't roll death saves while stable")
|
||||
if self.dead:
|
||||
raise ValueError("Failing death saves are capped at 3")
|
||||
raise ValueError("Can't roll death saves while dead")
|
||||
self.deathsave_failures += 1
|
||||
|
|
|
@ -2,10 +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
|
||||
|
||||
__all__ = [
|
||||
"parse_5etools_entry",
|
||||
"get_interface_data",
|
||||
"get_active_character",
|
||||
"get_active_battle",
|
||||
"find_unit_in_current_battle"
|
||||
]
|
||||
|
|
39
rpgpack/utils/findunitincurrentbattle.py
Normal file
39
rpgpack/utils/findunitincurrentbattle.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
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
|
Loading…
Reference in a new issue