1
Fork 0
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:
Steffo 2020-03-04 19:29:33 +01:00
parent 479657d6ee
commit bdf8177cc9
14 changed files with 336 additions and 11 deletions

View file

@ -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__

View file

@ -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()

View 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}")

View 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}")

View 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}")

View 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}")

View 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}")

View 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}")

View file

@ -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)

View file

@ -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"))

View file

@ -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}>"

View file

@ -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

View file

@ -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"
]

View 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