diff --git a/titles/chuni/frontend.py b/titles/chuni/frontend.py index 69f1ae9..381c08c 100644 --- a/titles/chuni/frontend.py +++ b/titles/chuni/frontend.py @@ -11,7 +11,7 @@ from core.frontend import FE_Base, UserSession from core.config import CoreConfig from .database import ChuniData from .config import ChuniConfig -from .const import ChuniConstants +from .const import ChuniConstants, AvatarCategory, ItemKind def pairwise(iterable): @@ -99,6 +99,12 @@ class ChuniFrontend(FE_Base): Route("/{index}", self.render_GET_playlog, methods=['GET']), ]), Route("/favorites", self.render_GET_favorites, methods=['GET']), + Route("/userbox", self.render_GET_userbox, methods=['GET']), + Route("/avatar", self.render_GET_avatar, methods=['GET']), + Route("/update.map-icon", self.update_map_icon, methods=['POST']), + Route("/update.system-voice", self.update_system_voice, methods=['POST']), + Route("/update.userbox", self.update_userbox, methods=['POST']), + Route("/update.avatar", self.update_avatar, methods=['POST']), Route("/update.name", self.update_name, methods=['POST']), Route("/update.favorite_music_playlog", self.update_favorite_music_playlog, methods=['POST']), Route("/update.favorite_music_favorites", self.update_favorite_music_favorites, methods=['POST']), @@ -123,15 +129,33 @@ class ChuniFrontend(FE_Base): usr_sesh.chunithm_version = versions[0] profile = await self.data.profile.get_profile_data(usr_sesh.user_id, usr_sesh.chunithm_version) + user_id = usr_sesh.user_id + version = usr_sesh.chunithm_version + + # While map icons and system voices weren't present prior to AMAZON, we don't need to bother checking + # version here - it'll just end up being empty sets and the jinja will ignore the variables anyway. + user_map_icons = await self.data.item.get_items(user_id, ItemKind.MAP_ICON.value) + user_map_icons = [icon["itemId"] for icon in user_map_icons] + [profile.mapIconId] + user_system_voices = await self.data.item.get_items(user_id, ItemKind.SYSTEM_VOICE.value) + user_system_voices = [icon["itemId"] for icon in user_system_voices] + [profile.voiceId] + + map_icons, total_map_icons = await self.get_available_map_icons(version, user_map_icons) + system_voices, total_system_voices = await self.get_available_system_voices(version, user_system_voices) + resp = Response(template.render( title=f"{self.core_config.server.name} | {self.nav_name}", game_list=self.environment.globals["game_list"], sesh=vars(usr_sesh), - user_id=usr_sesh.user_id, + user_id=user_id, profile=profile, version_list=ChuniConstants.VERSION_NAMES, versions=versions, - cur_version=usr_sesh.chunithm_version + cur_version=version, + cur_version_name=ChuniConstants.game_ver_to_string(version), + map_icons=map_icons, + system_voices=system_voices, + total_map_icons=total_map_icons, + total_system_voices=total_system_voices ), media_type="text/html; charset=utf-8") if usr_sesh.chunithm_version >= 0: @@ -189,6 +213,8 @@ class ChuniFrontend(FE_Base): profile=profile, hot_list=hot_list, base_list=base_list, + cur_version=usr_sesh.chunithm_version, + cur_version_name=ChuniConstants.game_ver_to_string(usr_sesh.chunithm_version) ), media_type="text/html; charset=utf-8") else: return RedirectResponse("/gate/", 303) @@ -217,7 +243,9 @@ class ChuniFrontend(FE_Base): title=f"{self.core_config.server.name} | {self.nav_name}", game_list=self.environment.globals["game_list"], sesh=vars(usr_sesh), - playlog_count=0 + playlog_count=0, + cur_version=version, + cur_version_name=ChuniConstants.game_ver_to_string(version) ), media_type="text/html; charset=utf-8") playlog = await self.data.score.get_playlogs_limited(user_id, version, index, 20) playlog_with_title = [] @@ -257,6 +285,7 @@ class ChuniFrontend(FE_Base): user_id=user_id, playlog=playlog_with_title, playlog_count=playlog_count, + cur_version=version, cur_version_name=ChuniConstants.game_ver_to_string(version) ), media_type="text/html; charset=utf-8") else: @@ -319,11 +348,318 @@ class ChuniFrontend(FE_Base): user_id=user_id, favorites_by_genre=favorites_by_genre, favorites_count=favorites_count, + cur_version=version, cur_version_name=ChuniConstants.game_ver_to_string(version) ), media_type="text/html; charset=utf-8") else: return RedirectResponse("/gate/", 303) + async def get_available_map_icons(self, version: int, user_unlocked_items: List[int]) -> (List[dict], int): + items = dict() + rows = await self.data.static.get_map_icons(version) + if rows: + for row in rows: + # Only include items that are either available by default or in the user unlocked list + if row["defaultHave"] or row["mapIconId"] in user_unlocked_items: + item = dict() + item["id"] = row["mapIconId"] + item["name"] = row["name"] + item["iconPath"] = path.splitext(row["iconPath"])[0] + ".png" + items[row["mapIconId"]] = item + + return (items, len(rows)) + + async def get_available_system_voices(self, version: int, user_unlocked_items: List[int]) -> (List[dict], int): + items = dict() + rows = await self.data.static.get_system_voices(version) + if rows: + for row in rows: + # Only include items that are either available by default or in the user unlocked list + if row["defaultHave"] or row["voiceId"] in user_unlocked_items: + item = dict() + item["id"] = row["voiceId"] + item["name"] = row["name"] + item["imagePath"] = path.splitext(row["imagePath"])[0] + ".png" + items[row["voiceId"]] = item + + return (items, len(rows)) + + async def get_available_nameplates(self, version: int, user_unlocked_items: List[int]) -> (List[dict], int): + items = dict() + rows = await self.data.static.get_nameplates(version) + if rows: + for row in rows: + # Only include items that are either available by default or in the user unlocked list + if row["defaultHave"] or row["nameplateId"] in user_unlocked_items: + item = dict() + item["id"] = row["nameplateId"] + item["name"] = row["name"] + item["texturePath"] = path.splitext(row["texturePath"])[0] + ".png" + items[row["nameplateId"]] = item + + return (items, len(rows)) + + async def get_available_trophies(self, version: int, user_unlocked_items: List[int]) -> (List[dict], int): + items = dict() + rows = await self.data.static.get_trophies(version) + if rows: + for row in rows: + # Only include items that are either available by default or in the user unlocked list + if row["defaultHave"] or row["trophyId"] in user_unlocked_items: + item = dict() + item["id"] = row["trophyId"] + item["name"] = row["name"] + item["rarity"] = row["rareType"] + items[row["trophyId"]] = item + + return (items, len(rows)) + + async def get_available_characters(self, version: int, user_unlocked_items: List[int]) -> (List[dict], int): + items = dict() + rows = await self.data.static.get_characters(version) + if rows: + for row in rows: + # Only include items that are either available by default or in the user unlocked list + if row["defaultHave"] or row["characterId"] in user_unlocked_items: + item = dict() + item["id"] = row["characterId"] + item["name"] = row["name"] + item["iconPath"] = path.splitext(row["imagePath3"])[0] + ".png" + items[row["characterId"]] = item + + return (items, len(rows)) + + async def get_available_avatar_items(self, version: int, category: AvatarCategory, user_unlocked_items: List[int]) -> (List[dict], int): + items = dict() + rows = await self.data.static.get_avatar_items(version, category.value) + if rows: + for row in rows: + # Only include items that are either available by default or in the user unlocked list + if row["defaultHave"] or row["avatarAccessoryId"] in user_unlocked_items: + item = dict() + item["id"] = row["avatarAccessoryId"] + item["name"] = row["name"] + item["iconPath"] = path.splitext(row["iconPath"])[0] + ".png" + item["texturePath"] = path.splitext(row["texturePath"])[0] + ".png" + items[row["avatarAccessoryId"]] = item + + return (items, len(rows)) + + async def render_GET_userbox(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/chuni/templates/chuni_userbox.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + if usr_sesh.chunithm_version < 0: + return RedirectResponse("/game/chuni/", 303) + + user_id = usr_sesh.user_id + version = usr_sesh.chunithm_version + + # Get the user profile so we know how the userbox is currently configured + profile = await self.data.profile.get_profile_data(user_id, version) + # Get all the user unlocked components so we know what to populate as options + user_nameplates = await self.data.item.get_items(user_id, ItemKind.NAMEPLATE.value) + user_nameplates = [item["itemId"] for item in user_nameplates] + [profile.nameplateId] + user_trophies = await self.data.item.get_items(user_id, ItemKind.TROPHY.value) + user_trophies = [item["itemId"] for item in user_trophies] + [profile.trophyId] + user_characters = await self.data.item.get_characters(user_id) + user_characters = [chara["characterId"] for chara in user_characters] + [profile.charaIllustId] + + # Build up available list of components + nameplates, total_nameplates = await self.get_available_nameplates(version, user_nameplates) + trophies, total_trophies = await self.get_available_trophies(version, user_trophies) + characters, total_characters = await self.get_available_characters(version, user_characters) + + # Get the user's team + team_name = "ARTEMiS" + if profile["teamId"]: + team = await self.data.profile.get_team_by_id(profile["teamId"]) + team_name = team["teamName"] + # Figure out the rating color we should use (rank maps to the stylesheet) + rating = profile.playerRating / 100; + rating_rank = 0 + if rating >= 16: + rating_rank = 8 + elif rating >= 15.25: + rating_rank = 7 + elif rating >= 14.5: + rating_rank = 6 + elif rating >= 13.25: + rating_rank = 5 + elif rating >= 12: + rating_rank = 4 + elif rating >= 10: + rating_rank = 3 + elif rating >= 7: + rating_rank = 2 + elif rating >= 4: + rating_rank = 1 + + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=user_id, + cur_version=version, + cur_version_name=ChuniConstants.game_ver_to_string(version), + profile=profile, + team_name=team_name, + rating_rank=rating_rank, + nameplates=nameplates, + trophies=trophies, + characters=characters, + total_nameplates=total_nameplates, + total_trophies=total_trophies, + total_characters=total_characters + ), media_type="text/html; charset=utf-8") + else: + return RedirectResponse("/gate/", 303) + + async def render_GET_avatar(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/chuni/templates/chuni_avatar.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + if usr_sesh.chunithm_version < 11: + # Avatar configuration only for NEW!! and newer + return RedirectResponse("/game/chuni/", 303) + + user_id = usr_sesh.user_id + version = usr_sesh.chunithm_version + + # Get the user profile so we know what avatar items are currently in use + profile = await self.data.profile.get_profile_data(user_id, version) + # Get all the user avatar accessories so we know what to populate + user_accessories = await self.data.item.get_items(user_id, ItemKind.AVATAR_ACCESSORY.value) + user_accessories = [item["itemId"] for item in user_accessories] + \ + [profile.avatarBack, profile.avatarItem, profile.avatarWear, \ + profile.avatarFront, profile.avatarSkin, profile.avatarHead, profile.avatarFace] + + # Build up available list of items for each avatar category + wears, total_wears = await self.get_available_avatar_items(version, AvatarCategory.WEAR, user_accessories) + faces, total_faces = await self.get_available_avatar_items(version, AvatarCategory.FACE, user_accessories) + heads, total_heads = await self.get_available_avatar_items(version, AvatarCategory.HEAD, user_accessories) + skins, total_skins = await self.get_available_avatar_items(version, AvatarCategory.SKIN, user_accessories) + items, total_items = await self.get_available_avatar_items(version, AvatarCategory.ITEM, user_accessories) + fronts, total_fronts = await self.get_available_avatar_items(version, AvatarCategory.FRONT, user_accessories) + backs, total_backs = await self.get_available_avatar_items(version, AvatarCategory.BACK, user_accessories) + + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=user_id, + cur_version=version, + cur_version_name=ChuniConstants.game_ver_to_string(version), + profile=profile, + wears=wears, + faces=faces, + heads=heads, + skins=skins, + items=items, + fronts=fronts, + backs=backs, + total_wears=total_wears, + total_faces=total_faces, + total_heads=total_heads, + total_skins=total_skins, + total_items=total_items, + total_fronts=total_fronts, + total_backs=total_backs + ), media_type="text/html; charset=utf-8") + else: + return RedirectResponse("/gate/", 303) + + async def update_map_icon(self, request: Request) -> bytes: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + form_data = await request.form() + new_map_icon: str = form_data.get("id") + + if not new_map_icon: + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_map_icon(usr_sesh.user_id, usr_sesh.chunithm_version, new_map_icon): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/chuni/", 303) + + async def update_system_voice(self, request: Request) -> bytes: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + form_data = await request.form() + new_system_voice: str = form_data.get("id") + + if not new_system_voice: + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_system_voice(usr_sesh.user_id, usr_sesh.chunithm_version, new_system_voice): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/chuni/", 303) + + async def update_userbox(self, request: Request) -> bytes: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + form_data = await request.form() + new_nameplate: str = form_data.get("nameplate") + new_trophy: str = form_data.get("trophy") + new_character: str = form_data.get("character") + + if not new_nameplate or \ + not new_trophy or \ + not new_character: + return RedirectResponse("/game/chuni/userbox?e=4", 303) + + if not await self.data.profile.update_userbox(usr_sesh.user_id, usr_sesh.chunithm_version, new_nameplate, new_trophy, new_character): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/chuni/userbox", 303) + + async def update_avatar(self, request: Request) -> bytes: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + form_data = await request.form() + new_wear: str = form_data.get("wear") + new_face: str = form_data.get("face") + new_head: str = form_data.get("head") + new_skin: str = form_data.get("skin") + new_item: str = form_data.get("item") + new_front: str = form_data.get("front") + new_back: str = form_data.get("back") + + if not new_wear or \ + not new_face or \ + not new_head or \ + not new_skin or \ + not new_item or \ + not new_front or \ + not new_back: + return RedirectResponse("/game/chuni/avatar?e=4", 303) + + if not await self.data.profile.update_avatar(usr_sesh.user_id, usr_sesh.chunithm_version, new_wear, new_face, new_head, new_skin, new_item, new_front, new_back): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/chuni/avatar", 303) + + async def update_name(self, request: Request) -> bytes: usr_sesh = self.validate_session(request) if not usr_sesh: diff --git a/titles/chuni/templates/chuni_avatar.jinja b/titles/chuni/templates/chuni_avatar.jinja new file mode 100644 index 0000000..f53ce5c --- /dev/null +++ b/titles/chuni/templates/chuni_avatar.jinja @@ -0,0 +1,302 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + + +
+ ![]() ![]() |
+ ||
Wear: | ||
Face: | ||
Head: | ||
Skin: | ||
Item: | ||
Front: | ||
Back: | ||
+ + + |
+
+ ![]() {{team_name}}
+
+
+ ![]()
+ Lv.
+ {{ profile.level }} {{ profile.userName }}
+
+
+
+
+ ![]() | |
Nameplate: | |
Trophy: |
+
+ |
Character: | |
+ + + |