You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fabula/embedgen.py

226 lines
6.5 KiB

from math import ceil
from typing import Iterable
from discord_webhook import DiscordEmbed, AsyncDiscordWebhook
from enums import CantMoveEmoji, DoneMovingEmoji, NotMovedEmoji, TurnsLeftEmoji, get_indicator_key
from model import Character
def simple_character_field(c: Character) -> tuple[str, str]:
header, statuses = character_header(c)
if c.visibility.show_stats:
body = [
f"**HP** {c.hp}/{c.max_hp}",
f"**MP** {c.mp}/{c.max_mp}",
]
else:
hp_caption, _ = hp_text(c.hp, c.max_hp)
mp_caption, _ = mp_text(c.mp, c.max_mp)
body = [
f"**HP** {hp_caption}",
f"**MP** {mp_caption}",
]
body.extend(character_details(c, statuses))
return "".join(header), " / ".join(body)
def detailed_character_field(c: Character) -> tuple[str, str]:
header, statuses = character_header(c)
if c.visibility.show_stats:
body = [
f'**HP** {bar_string(c.hp, c.max_hp, length=80)} {c.hp}/{c.max_hp}',
f'**MP** {bar_string(c.mp, c.max_mp, length=80)} {c.mp}/{c.max_mp}',
]
else:
hp_caption, hp_value = hp_text(c.hp, c.max_hp)
mp_caption, mp_value = mp_text(c.mp, c.max_mp)
body = [
f'**HP** {bar_string(hp_value, 5, length=80)} {hp_caption}',
f'**MP** {bar_string(mp_value, 5, length=80)} {mp_caption}',
]
details = character_details(c, statuses)
if len(details) > 0:
body.append(" / ".join(details))
return "".join(header), "\n".join(body)
def detailed_character_embed(c: Character) -> DiscordEmbed:
header, statuses = character_header(c)
if c.visibility.show_stats:
fields = [
(
f"**HP** {c.hp}/{c.max_hp}",
bar_string(c.hp, c.max_hp, length=80),
False,
),
(
f"**MP** {c.mp}/{c.max_mp}",
bar_string(c.mp, c.max_mp, length=80),
False,
),
]
else:
hp_caption, _ = hp_text(c.hp, c.max_hp)
mp_caption, _ = mp_text(c.mp, c.max_mp)
fields = [
(
f"**HP**",
hp_caption,
True,
),
(
f"**MP**",
mp_caption,
True,
),
]
if c.max_ip > 0:
fields.append((f"**IP**", f"{c.ip}/{c.max_ip}", True))
if c.sp is not None:
fields.append((f"**{c.character_type.sp_name_abbr}**", str(c.sp), True))
if len(statuses) > 0:
fields.append(("**Status**", f'_{", ".join(statuses)}_', True))
result = DiscordEmbed(
title="".join(header),
)
if c.player_name is not None:
result.set_footer(text=f'Player: {c.player_name}')
if c.color is not None:
result.set_color(c.color)
if c.thumbnail is not None:
result.set_thumbnail(c.thumbnail)
for name, value, inline in fields:
result.add_embed_field(name, value, inline)
return result
def character_header(c: Character) -> tuple[list[str], list[str]]:
header = [
turn_icon(c),
get_indicator_key(c.access_key),
" ",
f'**{c.name}**' if c.visibility.show_name else '***???***'
]
statuses = list(sorted(str(s) for s in c.statuses))
hp = hp_marker(c.hp, c.max_hp)
if hp is not None:
header.append(" ")
icon, status = hp
statuses.insert(0, status),
header.append(icon)
return header, statuses
def character_details(c: Character, statuses: list[str]):
result = []
if c.max_ip > 0:
result.append(f"**IP** {c.ip}/{c.max_ip}")
if c.sp is not None:
result.append(f"**{c.character_type.sp_name_abbr}** {c.sp}")
if len(statuses) > 0:
result.append(f'_{", ".join(statuses)}_')
return result
def bar_string(val: int, mx: int, delta: int | None = None, length: int | None = None) -> str:
effective_delta = delta if delta is not None else 0
length = length if length is not None else mx
if effective_delta >= 0:
filled_size = ceil(length * val / mx)
prev_size = ceil(length * (val + effective_delta) / mx)
delta_size = filled_size - prev_size
bar = '\\|' if prev_size % 2 == 1 else ""
bar += (prev_size // 2) * "\\||"
if delta_size > 0:
bar += f'**{"" * delta_size}**'
bar += (length - filled_size) * "·"
else:
filled_size = ceil(length * val / mx)
prev_size = ceil(length * (val - effective_delta) / mx)
delta_size = prev_size - filled_size
bar = '\\|' if filled_size % 2 == 1 else ""
bar += (filled_size // 2) * "\\||"
if delta_size > 0:
bar += f'~~{"·" * delta_size}~~'
bar += (length - prev_size) * "·"
return bar
def turn_icon(c: Character) -> str:
if c.max_turns <= 0 or c.hp == 0:
return CantMoveEmoji
elif c.turns_left <= 0:
return DoneMovingEmoji
elif c.max_turns == 1:
return NotMovedEmoji
else:
return TurnsLeftEmoji[-1] if c.turns_left >= len(TurnsLeftEmoji) else TurnsLeftEmoji[c.turns_left]
def hp_marker(hp: int, max_hp: int) -> tuple[str, str] | None:
if hp == 0:
return "🏳", "**Surrendered**"
elif hp * 2 < max_hp:
return "", "**Crisis**"
else:
return None
def hp_text(hp: int, max_hp: int) -> tuple[str, int]:
if hp == 0:
return "Defeated", 0
elif hp * 4 < max_hp:
return "Peril", 1
elif hp * 2 < max_hp:
return "Crisis", 2
elif hp * 4 < 3 * max_hp:
return "Wounded", 3
elif hp < max_hp:
return "Scratched", 4
else:
return "Full", 5
def mp_text(mp: int, max_mp: int) -> tuple[str, int]:
if mp == 0:
return "Empty", 0
elif mp * 4 < max_mp:
return "Exhausted", 1
elif mp * 2 < max_mp:
return "Tired", 2
elif mp * 4 < 3 * max_mp:
return "Flagging", 3
elif mp < max_mp:
return "Energetic", 4
else:
return "Full", 5
class WebhookExecutor(object):
__slots__ = ["url"]
def __init__(self, url: str):
self.url = url
async def run(self, embeds: Iterable[DiscordEmbed]):
webhook = AsyncDiscordWebhook(
url=self.url,
username="Party Status",
avatar_url="https://media.discordapp.net/attachments/989315969920929862/" +
"1084018762606444634/heartmonitor.png",
embeds=embeds,
)
await webhook.execute(False)