Skip to content

Commit

Permalink
feat: Add Support for Imaginarium Theater (#122)
Browse files Browse the repository at this point in the history
* feat: Add draw func

* chore(assets): Update assets

* Update UI
  • Loading branch information
seriaati committed Jul 7, 2024
1 parent fe491e8 commit b61a8ea
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 69 deletions.
2 changes: 1 addition & 1 deletion hoyo-buddy-assets
2 changes: 1 addition & 1 deletion hoyo_buddy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,6 @@ def contains_traveler_id(character_id: str) -> bool:
}

GAME_CHALLENGE_TYPES: Final[dict[Game, tuple[ChallengeType, ...]]] = {
Game.GENSHIN: (ChallengeType.SPIRAL_ABYSS,),
Game.GENSHIN: (ChallengeType.SPIRAL_ABYSS, ChallengeType.IMG_THEATER),
Game.STARRAIL: (ChallengeType.MOC, ChallengeType.PURE_FICTION, ChallengeType.APC_SHADOW),
}
26 changes: 15 additions & 11 deletions hoyo_buddy/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,21 @@ def parsed_data(self) -> Challenge:
async def add_data(
cls, uid: int, challenge_type: ChallengeType, season_id: int, data: Challenge
) -> None:
try:
if isinstance(data, genshin.models.SpiralAbyss):
start_time = data.start_time
end_time = data.end_time
name = None
else:
season = next(season for season in data.seasons if season.id == season_id)
start_time = season.begin_time.datetime
end_time = season.end_time.datetime
name = season.name
if isinstance(data, genshin.models.SpiralAbyss):
start_time = data.start_time
end_time = data.end_time
name = None
elif isinstance(data, genshin.models.ImgTheaterData):
start_time = data.schedule.start_time
end_time = data.schedule.end_time
name = None
else:
season = next(season for season in data.seasons if season.id == season_id)
start_time = season.begin_time.datetime
end_time = season.end_time.datetime
name = season.name

try:
await cls.create(
uid=uid,
season_id=season_id,
Expand All @@ -269,7 +273,7 @@ async def add_data(
)
except exceptions.IntegrityError:
await cls.filter(uid=uid, season_id=season_id, challenge_type=challenge_type).update(
data=pickle.dumps(data)
data=pickle.dumps(data), name=name
)


Expand Down
1 change: 1 addition & 0 deletions hoyo_buddy/draw/funcs/hoyo/genshin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from .build_card import draw_genshin_card
from .characters import draw_character_card
from .exploration import ExplorationCard
from .img_theater import ImgTheaterCard
from .notes import draw_genshin_notes_card
173 changes: 173 additions & 0 deletions hoyo_buddy/draw/funcs/hoyo/genshin/img_theater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import io

import discord
import genshin
from PIL import Image, ImageDraw

from hoyo_buddy.bot.translator import LocaleStr, Translator
from hoyo_buddy.draw.drawer import Drawer


class ImgTheaterCard:
def __init__(
self,
theater: genshin.models.ImgTheaterData,
chara_consts: dict[int, int],
locale: str,
translator: Translator,
) -> None:
self._theater = theater
self._chara_consts = chara_consts
self._dark_mode = True # To write white colored texts
self._translator = translator
self._locale = locale
self._asset_dir = "hoyo-buddy-assets/assets/img-theater"
self._drawer: Drawer

@property
def locale(self) -> discord.Locale:
return discord.Locale(self._locale)

def _open_asset(self, asset: str) -> Image.Image:
return Image.open(f"{self._asset_dir}/{asset}")

def _write_large_block_texts(self) -> None:
self._drawer.write(
LocaleStr(key="img_theater_large_block_title"),
size=64,
position=(112, 98),
style="bold",
)

stats = self._theater.stats
lines = (
LocaleStr(
key="img_theater_stats_line_one",
act=stats.best_record,
),
LocaleStr(
key="img_theater_stats_line_two",
flower=stats.fantasia_flowers_used,
),
LocaleStr(
key="img_theater_stats_line_three",
support=stats.audience_support_trigger_num,
),
LocaleStr(
key="img_theater_stats_line_four",
assist=stats.player_assists,
),
)
line_height = 45
for i, line in enumerate(lines):
self._drawer.write(
line,
size=24,
position=(112, 190 + i * line_height),
)

def _write_legend_block_texts(self) -> None:
self._drawer.write(
LocaleStr(key="img_theater_legend_block_support_chara"),
size=24,
position=(933, 310),
anchor="lm",
)
self._drawer.write(
LocaleStr(key="img_theater_legend_block_trial_chara"),
size=24,
position=(933, 354),
anchor="lm",
)

def _draw_act_block(self, act: genshin.models.Act, pos: tuple[int, int]) -> None:
self._drawer.write(
LocaleStr(key="img_theater_act_block_title", act=act.round_id),
size=32,
style="bold",
position=(pos[0] + 21, pos[1] + 10),
)

medal = (
self._drawer.open_asset("medal.png")
if act.medal_obtained
else self._drawer.open_asset("medal_empty.png")
)
self._im.paste(medal, (pos[0] + 509, pos[1] + 10), medal)

padding = 146
start_pos = (pos[0] + 0, pos[1] + 75)

for character in act.characters:
if character.type is genshin.models.TheaterCharaType.SUPPORT:
name = "support_chara"
elif character.type is genshin.models.TheaterCharaType.TRIAL:
name = "trial_chara"
else:
name = "normal_chara"

block = self._drawer.open_asset(f"{name}_block.png")
const_flair = self._drawer.open_asset(f"{name}_const_flair.png")
level_flair = self._drawer.open_asset(f"{name}_level_flair.png")

block_draw = ImageDraw.Draw(block)
block_drawer = Drawer(
block_draw,
folder="img-theater",
dark_mode=self._dark_mode,
locale=self.locale,
translator=self._translator,
)

icon = block_drawer.open_static(character.icon)
icon = self._drawer.resize_crop(icon, (120, 120))
mask = self._drawer.open_asset("mask.png")
icon = self._drawer.crop_with_mask(icon, mask)
block.paste(icon, (2, 2), icon)

block.paste(const_flair, (92, 0), const_flair)
block_drawer.write(
str(self._chara_consts.get(character.id, "?")),
size=18,
position=(107, 15),
anchor="mm",
style="bold",
)

block.paste(level_flair, (2, 98), level_flair)
block_drawer.write(
str(character.level),
size=18,
position=(27, 110),
anchor="mm",
style="bold",
)

self._im.paste(block, start_pos, block)
start_pos = (start_pos[0] + padding, start_pos[1])

def draw(self) -> io.BytesIO:
self._im = self._open_asset("bg.png")
self._drawer = Drawer(
ImageDraw.Draw(self._im),
folder="img-theater",
dark_mode=self._dark_mode,
locale=self.locale,
translator=self._translator,
)

self._write_large_block_texts()
self._write_legend_block_texts()

start_pos = (76, 431)
x_padding = 601
y_padding = 255

for i, act in enumerate(self._theater.acts):
self._draw_act_block(
act, (start_pos[0] + i % 2 * x_padding, start_pos[1] + i // 2 * y_padding)
)

buffer = io.BytesIO()
self._im.save(buffer, format="WEBP", loseless=True)
return buffer
25 changes: 24 additions & 1 deletion hoyo_buddy/draw/main_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
from io import BytesIO

from genshin.models import Character as GenshinCharacter
from genshin.models import Notes as GenshinNote
from genshin.models import (
ImgTheaterData,
PartialGenshinUserStats,
SpiralAbyss,
StarRailAPCShadow,
Expand All @@ -27,6 +27,7 @@
StarRailNote,
StarRailPureFiction,
)
from genshin.models import Notes as GenshinNote
from genshin.models import StarRailDetailCharacter as StarRailCharacter

from ..bot.translator import Translator
Expand Down Expand Up @@ -382,3 +383,25 @@ async def draw_apc_shadow_card(
)
buffer.seek(0)
return File(buffer, filename=draw_input.filename)


async def draw_img_theater_card(
draw_input: DrawInput,
data: ImgTheaterData,
chara_consts: dict[int, int],
translator: Translator,
) -> File:
for act in data.acts:
icons = [chara.icon for chara in act.characters]
await download_and_save_static_images(icons, "img-theater", draw_input.session)

with timing("draw", tags={"type": "img_theater_card"}):
buffer = await draw_input.loop.run_in_executor(
draw_input.executor,
funcs.genshin.ImgTheaterCard(
data, chara_consts, draw_input.locale.value, translator
).draw,
)

buffer.seek(0)
return File(buffer, filename=draw_input.filename)
1 change: 1 addition & 0 deletions hoyo_buddy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,4 @@ class ChallengeType(StrEnum):
MOC = "Memory of chaos"
PURE_FICTION = "Pure fiction"
APC_SHADOW = "Apocalyptic shadow"
IMG_THEATER = "img_theater_large_block_title"
6 changes: 6 additions & 0 deletions hoyo_buddy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
| genshin.models.SpiralAbyss
| genshin.models.StarRailPureFiction
| genshin.models.StarRailAPCShadow
| genshin.models.ImgTheaterData
)
ChallengeWithBuff: TypeAlias = (
genshin.models.StarRailAPCShadow
| genshin.models.ImgTheaterData
| genshin.models.StarRailPureFiction
)

Interaction: TypeAlias = discord.Interaction[HoyoBuddy]
Expand Down
Loading

0 comments on commit b61a8ea

Please sign in to comment.