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 .dndnewbattle import DndnewbattleCommand
|
||||||
from .dndactivebattle import DndactivebattleCommand
|
from .dndactivebattle import DndactivebattleCommand
|
||||||
from .dndaddunit import DndaddunitCommand
|
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!
|
# Enter the commands of your Pack here!
|
||||||
available_commands = [
|
available_commands = [
|
||||||
|
@ -29,7 +35,13 @@ available_commands = [
|
||||||
TestfactionCommand,
|
TestfactionCommand,
|
||||||
DndnewbattleCommand,
|
DndnewbattleCommand,
|
||||||
DndactivebattleCommand,
|
DndactivebattleCommand,
|
||||||
DndaddunitCommand
|
DndaddunitCommand,
|
||||||
|
DnddamageCommand,
|
||||||
|
DndhealCommand,
|
||||||
|
DndstatusCommand,
|
||||||
|
DndextraCommand,
|
||||||
|
DnddeathsaveCommand,
|
||||||
|
DndjoinbattleCommand,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Don't change this, it should automatically generate __all__
|
# Don't change this, it should automatically generate __all__
|
||||||
|
|
|
@ -2,7 +2,7 @@ from typing import *
|
||||||
import royalnet
|
import royalnet
|
||||||
import royalnet.commands as rc
|
import royalnet.commands as rc
|
||||||
import royalnet.utils as ru
|
import royalnet.utils as ru
|
||||||
from ..types import Faction, Health
|
from ..types import Faction
|
||||||
from ..tables import DndBattleUnit
|
from ..tables import DndBattleUnit
|
||||||
from ..utils import get_active_battle
|
from ..utils import get_active_battle
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class DndaddunitCommand(rc.Command):
|
||||||
|
|
||||||
description: str = "Add an Unit to a Battle."
|
description: str = "Add an Unit to a Battle."
|
||||||
|
|
||||||
aliases = ["dau", "dndau", "daddunit", ""]
|
aliases = ["dau", "dndau", "addunit", "daddunit"]
|
||||||
|
|
||||||
syntax: str = "{faction} {name} {initiative} {health} {armorclass}"
|
syntax: str = "{faction} {name} {initiative} {health} {armorclass}"
|
||||||
|
|
||||||
|
@ -29,7 +29,16 @@ class DndaddunitCommand(rc.Command):
|
||||||
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.")
|
||||||
|
|
||||||
|
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(
|
dbu = DndBattleUnitT(
|
||||||
|
linked_character_id=None,
|
||||||
initiative=initiative,
|
initiative=initiative,
|
||||||
faction=faction,
|
faction=faction,
|
||||||
name=name,
|
name=name,
|
||||||
|
@ -41,5 +50,8 @@ class DndaddunitCommand(rc.Command):
|
||||||
data.session.add(dbu)
|
data.session.add(dbu)
|
||||||
await data.session_commit()
|
await data.session_commit()
|
||||||
|
|
||||||
await data.reply(f"✅ [b]{dbu.name}[/b] joined the battle!\n"
|
await data.reply(f"{dbu}\n"
|
||||||
f"{dbu}")
|
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(f"{self.description}\n")
|
||||||
string.append("\n")
|
string.append("\n")
|
||||||
for unit in sorted(self.units, key=lambda u: -u.initiative):
|
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)
|
return "".join(string)
|
||||||
|
|
|
@ -11,6 +11,14 @@ class DndBattleUnit:
|
||||||
def id(self):
|
def id(self):
|
||||||
return Column(Integer, primary_key=True)
|
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
|
@declared_attr
|
||||||
def battle_id(self):
|
def battle_id(self):
|
||||||
return Column(Integer, ForeignKey("dndbattle.id"))
|
return Column(Integer, ForeignKey("dndbattle.id"))
|
||||||
|
|
|
@ -188,6 +188,10 @@ class DndCharacter:
|
||||||
def survival_proficiency(self):
|
def survival_proficiency(self):
|
||||||
return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE)
|
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
|
@property
|
||||||
def strength_save(self):
|
def strength_save(self):
|
||||||
return self.strength + math.floor(self.proficiency_bonus * self.strength_save_proficiency.value)
|
return self.strength + math.floor(self.proficiency_bonus * self.strength_save_proficiency.value)
|
||||||
|
@ -284,6 +288,10 @@ class DndCharacter:
|
||||||
def survival(self):
|
def survival(self):
|
||||||
return self.wisdom + math.floor(self.proficiency_bonus * self.survival_proficiency.value)
|
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):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__qualname__} {self.name}>"
|
return f"<{self.__class__.__qualname__} {self.name}>"
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Health:
|
||||||
if self.temp_value > 0:
|
if self.temp_value > 0:
|
||||||
string.append(f"{self.temp_value:+}")
|
string.append(f"{self.temp_value:+}")
|
||||||
string.append("/")
|
string.append("/")
|
||||||
string.append(f"{self.max_value}̋")
|
string.append(f"{self.max_value}")
|
||||||
string.append("s" * self.deathsave_successes)
|
string.append("s" * self.deathsave_successes)
|
||||||
string.append("f" * self.deathsave_failures)
|
string.append("f" * self.deathsave_failures)
|
||||||
return "".join(string)
|
return "".join(string)
|
||||||
|
@ -112,15 +112,19 @@ class Health:
|
||||||
self.value = 0
|
self.value = 0
|
||||||
|
|
||||||
def deathsave_success(self) -> None:
|
def deathsave_success(self) -> None:
|
||||||
if self.dying:
|
if not self.dying:
|
||||||
raise ValueError("Can't roll death saves while alive")
|
raise ValueError("Can't roll death saves while alive")
|
||||||
if self.stable:
|
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
|
self.deathsave_successes += 1
|
||||||
|
|
||||||
def deathsave_failure(self) -> None:
|
def deathsave_failure(self) -> None:
|
||||||
if self.dying:
|
if not self.dying:
|
||||||
raise ValueError("Can't roll death saves while alive")
|
raise ValueError("Can't roll death saves while alive")
|
||||||
|
if self.stable:
|
||||||
|
raise ValueError("Can't roll death saves while stable")
|
||||||
if self.dead:
|
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
|
self.deathsave_failures += 1
|
||||||
|
|
|
@ -2,10 +2,12 @@ from .parse5etoolsentry import parse_5etools_entry
|
||||||
from .getinterfacedata import get_interface_data
|
from .getinterfacedata import get_interface_data
|
||||||
from .getactivechar import get_active_character
|
from .getactivechar import get_active_character
|
||||||
from .getactivebattle import get_active_battle
|
from .getactivebattle import get_active_battle
|
||||||
|
from .findunitincurrentbattle import find_unit_in_current_battle
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"parse_5etools_entry",
|
"parse_5etools_entry",
|
||||||
"get_interface_data",
|
"get_interface_data",
|
||||||
"get_active_character",
|
"get_active_character",
|
||||||
"get_active_battle",
|
"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