diff --git a/bemani/backend/jubeat/clan.py b/bemani/backend/jubeat/clan.py
index 3564a72..9610759 100644
--- a/bemani/backend/jubeat/clan.py
+++ b/bemani/backend/jubeat/clan.py
@@ -655,7 +655,7 @@ class JubeatClan(
evt = Node.void('event')
event_info.add_child(evt)
evt.set_attribute('type', str(event))
- evt.add_child(Node.u8('state', self.EVENT_STATUS_OPEN if self.EVENTS[event]['enabled'] else 0))
+ evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0))
# Each of the following two sections should have zero or more child nodes (no
# particular name) which look like the following:
@@ -1224,7 +1224,7 @@ class JubeatClan(
# Looks to be set to true when there's an old profile, stops tutorial from
# happening on first load.
- info.add_child(Node.bool('inherit', profile.get_bool('has_old_version')))
+ info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved')))
# Not saved, but loaded
info.add_child(Node.s32('mtg_entry_cnt', 123))
@@ -1582,6 +1582,7 @@ class JubeatClan(
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
newprofile = oldprofile.clone()
+ newprofile.replace_bool('saved', True)
data = request.child('data')
# Grab system information
diff --git a/bemani/backend/jubeat/festo.py b/bemani/backend/jubeat/festo.py
index 60197cd..895209d 100644
--- a/bemani/backend/jubeat/festo.py
+++ b/bemani/backend/jubeat/festo.py
@@ -5,7 +5,9 @@ from typing_extensions import Final
from bemani.backend.jubeat.base import JubeatBase
from bemani.backend.jubeat.common import (
+ JubeatDemodataGetHitchartHandler,
JubeatDemodataGetNewsHandler,
+ JubeatGamendRegisterHandler,
JubeatGametopGetMeetingHandler,
JubeatLobbyCheckHandler,
JubeatLoggerReportHandler,
@@ -13,16 +15,18 @@ from bemani.backend.jubeat.common import (
from bemani.backend.jubeat.clan import JubeatClan
from bemani.backend.base import Status
-from bemani.common import Profile, ValidatedDict, VersionConstants
-from bemani.data import Data, UserID, Score, Song
+from bemani.common import Profile, Time, ValidatedDict, VersionConstants
+from bemani.data import Data, Achievement, UserID, Score, Song
from bemani.protocol import Node
class JubeatFesto(
+ JubeatDemodataGetHitchartHandler,
JubeatDemodataGetNewsHandler,
+ JubeatGamendRegisterHandler,
JubeatGametopGetMeetingHandler,
- JubeatLoggerReportHandler,
JubeatLobbyCheckHandler,
+ JubeatLoggerReportHandler,
JubeatBase
):
@@ -81,11 +85,28 @@ class JubeatFesto(
EVENT_STATUS_OPEN: Final[int] = 0x1
EVENT_STATUS_COMPLETE: Final[int] = 0x2
- # TODO: Verify these
COURSE_STATUS_SEEN: Final[int] = 0x01
COURSE_STATUS_PLAYED: Final[int] = 0x02
COURSE_STATUS_CLEARED: Final[int] = 0x04
+ COURSE_TYPE_PERMANENT: Final[int] = 1
+ COURSE_TYPE_TIME_BASED: Final[int] = 2
+
+ COURSE_CLEAR_SCORE: Final[int] = 1
+ COURSE_CLEAR_COMBINED_SCORE: Final[int] = 2
+ COURSE_CLEAR_HAZARD: Final[int] = 3
+
+ COURSE_HAZARD_EXC1: Final[int] = 1
+ COURSE_HAZARD_EXC2: Final[int] = 2
+ COURSE_HAZARD_EXC3: Final[int] = 3
+ COURSE_HAZARD_FC1: Final[int] = 4
+ COURSE_HAZARD_FC2: Final[int] = 5
+ COURSE_HAZARD_FC3: Final[int] = 6
+
+ GAME_CHART_TYPE_BASIC: Final[int] = 0
+ GAME_CHART_TYPE_ADVANCED: Final[int] = 1
+ GAME_CHART_TYPE_EXTREME: Final[int] = 2
+
# Return the netlog service so that Festo doesn't crash on coin-in.
extra_services: List[str] = [
'netlog',
@@ -95,6 +116,30 @@ class JubeatFesto(
def previous_version(self) -> Optional[JubeatBase]:
return JubeatClan(self.data, self.config, self.model)
+ def game_to_db_chart(self, game_chart: int, hard_mode: bool) -> int:
+ if hard_mode:
+ return {
+ self.GAME_CHART_TYPE_BASIC: self.CHART_TYPE_HARD_BASIC,
+ self.GAME_CHART_TYPE_ADVANCED: self.CHART_TYPE_HARD_ADVANCED,
+ self.GAME_CHART_TYPE_EXTREME: self.CHART_TYPE_HARD_EXTREME,
+ }[game_chart]
+ else:
+ return {
+ self.GAME_CHART_TYPE_BASIC: self.CHART_TYPE_BASIC,
+ self.GAME_CHART_TYPE_ADVANCED: self.CHART_TYPE_ADVANCED,
+ self.GAME_CHART_TYPE_EXTREME: self.CHART_TYPE_EXTREME,
+ }[game_chart]
+
+ def db_to_game_chart(self, db_chart: int) -> int:
+ return {
+ self.CHART_TYPE_BASIC: self.GAME_CHART_TYPE_BASIC,
+ self.CHART_TYPE_ADVANCED: self.GAME_CHART_TYPE_ADVANCED,
+ self.CHART_TYPE_EXTREME: self.GAME_CHART_TYPE_EXTREME,
+ self.CHART_TYPE_HARD_BASIC: self.GAME_CHART_TYPE_BASIC,
+ self.CHART_TYPE_HARD_ADVANCED: self.GAME_CHART_TYPE_ADVANCED,
+ self.CHART_TYPE_HARD_EXTREME: self.GAME_CHART_TYPE_EXTREME,
+ }[db_chart]
+
@classmethod
def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]:
"""
@@ -131,8 +176,479 @@ class JubeatFesto(
data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily')
return events
+ @classmethod
+ def get_settings(cls) -> Dict[str, Any]:
+ """
+ Return all of our front-end modifiably settings.
+ """
+ return {
+ 'bools': [
+ {
+ 'name': 'Enable Stone Tablet Event',
+ 'tip': 'Enables the Stone Tablet event',
+ 'category': 'game_config',
+ 'setting': 'festo_dungeon',
+ },
+ {
+ 'name': '50th Anniversary Celebration',
+ 'tip': 'Display the 50th anniversary screen in attract mode',
+ 'category': 'game_config',
+ 'setting': '50th_anniversary',
+ },
+ ]
+ }
+
def __get_course_list(self) -> List[Dict[str, Any]]:
return [
+ # ASARI CUP
+ {
+ 'id': 1,
+ 'name': 'はじめてのビーチ',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 1,
+ 'score': 700000,
+ 'music': [
+ [(60000080, 0), (90000025, 0), (90000040, 0)],
+ [(60000086, 0), (70000047, 0)],
+ [(90000027, 0)],
+ ],
+ },
+ {
+ 'id': 2,
+ 'name': '【初段】超幸せハイテンション',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 1,
+ 'score': 700000,
+ 'music': [
+ [(60000100, 0), (90000030, 0), (90000079, 0)],
+ [(70000125, 0), (90000050, 0)],
+ [(70000106, 0)],
+ ],
+ },
+ {
+ 'id': 3,
+ 'name': 'アニメランニング',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 2,
+ 'score': 750000,
+ 'music': [
+ [(90000031, 0), (90000037, 0), (90000082, 0)],
+ [(80000120, 0)],
+ [(80000125, 0)],
+ ],
+ },
+ {
+ 'id': 4,
+ 'name': 'パブリックリゾート',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 2,
+ 'score': 750000,
+ 'music': [
+ [(90000040, 0), (50000296, 0), (90000044, 0)],
+ [(90000033, 0), (90000039, 0)],
+ [(80000091, 0)],
+ ],
+ },
+ {
+ 'id': 5,
+ 'name': '【二段】その笑顔は甘く蕩ける',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 3,
+ 'score': 800000,
+ 'music': [
+ [(50000268, 0), (70000039, 0), (90000051, 0)],
+ [(70000091, 0), (70000042, 0)],
+ [(60000053, 0)],
+ ],
+ },
+ # KISAGO CUP
+ {
+ 'id': 11,
+ 'name': '電脳享受空間',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 4,
+ 'score': 800000,
+ 'music': [
+ [(70000046, 1), (70000160, 1), (80000126, 1)],
+ [(80000031, 1), (80000097, 1)],
+ [(90000049, 1)],
+ ],
+ },
+ {
+ 'id': 12,
+ 'name': '孤高の少女は破滅を願う',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 4,
+ 'score': 850000,
+ 'music': [
+ [(50000202, 0), (70000117, 0), (70000134, 0)],
+ [(50000212, 0), (80000124, 1)],
+ [(90001008, 1)],
+ ],
+ },
+ {
+ 'id': 13,
+ 'name': 'スタミナアップ!',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 5,
+ 'score': 2600000,
+ 'music': [
+ [(50000242, 0), (90000037, 1)],
+ [(50000260, 1), (50000261, 1)],
+ [(90000081, 1)],
+ ],
+ },
+ {
+ 'id': 14,
+ 'name': '【三段】この花を貴方へ',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 4,
+ 'score': 850000,
+ 'music': [
+ [(90000034, 1), (90000037, 1), (90000042, 1)],
+ [(80000120, 1), (80001010, 1)],
+ [(40000051, 1)],
+ ],
+ },
+ {
+ 'id': 15,
+ 'name': '【四段】嗚呼、大繁盛!',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 6,
+ 'score': 2600000,
+ 'music': [
+ [(50000085, 2), (50000237, 2), (80000080, 2)],
+ [(50000172, 2), (50000235, 2)],
+ [(70000065, 2)],
+ ],
+ },
+ # MURU CUP
+ {
+ 'id': 21,
+ 'name': '黒船来航',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 7,
+ 'score': 850000,
+ 'music': [
+ [(50000086, 2), (60000066, 2), (80000040, 1)],
+ [(50000096, 2), (80000048, 2)],
+ [(50000091, 2)],
+ ],
+ },
+ {
+ 'id': 22,
+ 'name': '【五段】濁流を乗り越えて',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 7,
+ 'score': 2650000,
+ 'music': [
+ [(50000343, 2), (60000060, 1), (60000071, 2)],
+ [(60000027, 2), (80000048, 2)],
+ [(20000038, 2)],
+ ],
+ },
+ {
+ 'id': 23,
+ 'name': 'のんびり。ゆったり。ほがらかに。',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 8,
+ 'score': 950000,
+ 'music': [
+ [(40000154, 2), (80000124, 1), (80000126, 2)],
+ [(60000048, 2), (90000026, 2)],
+ [(90000050, 2)],
+ ],
+ },
+ {
+ 'id': 24,
+ 'name': '海・KOI・スィニョーレ!!',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 8,
+ 'score': 2650000,
+ 'music': [
+ [(50000201, 2)],
+ [(50000339, 2)],
+ [(50000038, 2)],
+ ],
+ },
+ {
+ 'id': 25,
+ 'name': '【六段】電柱を見ると思出す',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 9,
+ 'score': 2750000,
+ 'music': [
+ [(50000288, 2), (80000046, 2), (80001008, 2)],
+ [(50000207, 2), (70000117, 2)],
+ [(30000048, 2)],
+ ],
+ },
+ # SAZAE CUP
+ {
+ 'id': 31,
+ 'name': '超フェスタ!',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 10,
+ 'score': 930000,
+ 'music': [
+ [(70000076, 2), (70000077, 2)],
+ [(20000038, 2), (40000160, 2)],
+ [(70000145, 2)],
+ ],
+ },
+ {
+ 'id': 32,
+ 'name': '【七段】操り人形はほくそ笑む',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 10,
+ 'score': 2800000,
+ 'music': [
+ [(70000006, 2), (70000171, 2), (80000003, 2)],
+ [(50000078, 2), (50000324, 2)],
+ [(80000118, 2)],
+ ],
+ },
+ {
+ 'id': 33,
+ 'name': '絶体絶命スリーチャレンジ!',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_HAZARD,
+ 'hazard_type': self.COURSE_HAZARD_FC3,
+ 'difficulty': 11,
+ 'score': 2800000,
+ 'music': [
+ [(50000238, 2), (70000003, 2), (90000051, 1)],
+ [(50000027, 2), (50000387, 2)],
+ [(80000056, 2)],
+ ],
+ },
+ {
+ 'id': 34,
+ 'name': '天国の舞踏会',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 11,
+ 'score': 2800000,
+ 'music': [
+ [(60000065, 1)],
+ [(80001007, 2)],
+ [(90001007, 2)],
+ ],
+ },
+ {
+ 'id': 35,
+ 'name': '【八段】山の賽子',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 12,
+ 'score': 2820000,
+ 'music': [
+ [(50000200, 2), (50000291, 2), (60000003, 2)],
+ [(50000129, 2), (80000021, 2)],
+ [(80000087, 2)],
+ ],
+ },
+ # HOTATE CUP
+ {
+ 'id': 41,
+ 'name': 'The 8th KAC 個人部門',
+ 'course_type': self.COURSE_TYPE_TIME_BASED,
+ 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'hard': True,
+ 'difficulty': 14,
+ 'score': 700000,
+ 'music': [
+ [(90000052, 2)],
+ [(90000013, 2)],
+ [(70000167, 2)],
+ ],
+ },
+ {
+ 'id': 42,
+ 'name': 'The 8th KAC 団体部門',
+ 'course_type': self.COURSE_TYPE_TIME_BASED,
+ 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'hard': True,
+ 'difficulty': 14,
+ 'score': 700000,
+ 'music': [
+ [(90000009, 2)],
+ [(80000133, 2)],
+ [(80000101, 2)],
+ ],
+ },
+ {
+ 'id': 43,
+ 'name': 'BEMANI MASTER KOREA 2019',
+ 'course_type': self.COURSE_TYPE_TIME_BASED,
+ 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'hard': True,
+ 'difficulty': 14,
+ 'score': 700000,
+ 'music': [
+ [(90000003, 2)],
+ [(80000090, 2)],
+ [(90000009, 2)],
+ ],
+ },
+ {
+ 'id': 44,
+ 'name': '初めてのHARD MODE再び',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'hard': True,
+ 'difficulty': 13,
+ 'score': 2750000,
+ 'music': [
+ [(50000096, 2), (50000263, 2), (80000119, 2)],
+ [(60000021, 2), (60000075, 2)],
+ [(60000039, 2)],
+ ],
+ },
+ {
+ 'id': 45,
+ 'name': '【九段】2人からの挑戦状',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 13,
+ 'score': 2830000,
+ 'music': [
+ [(50000023, 2), (80000025, 2), (80000106, 2)],
+ [(50000124, 2), (80000082, 2)],
+ [(60000115, 2)],
+ ],
+ },
+ {
+ 'id': 46,
+ 'name': '天空の庭 太陽の園',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'difficulty': 13,
+ 'score': 965000,
+ 'music': [
+ [(40000153, 2)],
+ [(80000007, 2)],
+ [(70000173, 2)],
+ ],
+ },
+ {
+ 'id': 47,
+ 'name': '緊急!迅速!大混乱!',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'difficulty': 14,
+ 'score': 2900000,
+ 'music': [
+ [(20000040, 2), (50000244, 2), (70000145, 2)],
+ [(40000046, 2), (50000158, 2)],
+ [(40000057, 2)],
+ ],
+ },
+ {
+ 'id': 48,
+ 'name': '【十段】時の超越者',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'hard': True,
+ 'difficulty': 14,
+ 'score': 2820000,
+ 'music': [
+ [(20000051, 2), (50000249, 2), (70000108, 2)],
+ [(40000046, 2), (50000180, 2)],
+ [(50000134, 2)],
+ ],
+ },
+ # OSHAKO CUP
+ {
+ 'id': 51,
+ 'name': '【皆伝】甘味なのに甘くない',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'hard': True,
+ 'difficulty': 15,
+ 'score': 2850000,
+ 'music': [
+ [(90000010, 2)],
+ [(80000101, 2)],
+ [(50000102, 2)],
+ ],
+ },
+ {
+ 'id': 52,
+ 'name': '【伝導】真の青が魅せた空',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_SCORE,
+ 'hard': True,
+ 'difficulty': 15,
+ 'score': 970000,
+ 'music': [
+ [(50000332, 2)],
+ [(70000098, 2)],
+ [(90001005, 2)],
+ ],
+ },
+ {
+ 'id': 53,
+ 'name': '豪華絢爛高揚絶頂',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'hard': True,
+ 'difficulty': 16,
+ 'score': 2960000,
+ 'music': [
+ [(10000065, 2)],
+ [(50000323, 2)],
+ [(50000208, 2)],
+ ],
+ },
+ {
+ 'id': 54,
+ 'name': '絢爛豪華激情無常',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'hard': True,
+ 'difficulty': 16,
+ 'score': 2960000,
+ 'music': [
+ [(60000010, 2)],
+ [(70000110, 2)],
+ [(90000047, 2)],
+ ],
+ },
+ {
+ 'id': 55,
+ 'name': '【指神】王の降臨',
+ 'course_type': self.COURSE_TYPE_PERMANENT,
+ 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE,
+ 'hard': True,
+ 'difficulty': 16,
+ 'score': 2980000,
+ 'music': [
+ [(70000094, 2)],
+ [(80000088, 2)],
+ [(70000110, 2)],
+ ],
+ },
]
def __get_global_info(self) -> Node:
@@ -147,30 +663,9 @@ class JubeatFesto(
evt.set_attribute('type', str(event))
evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0))
- # Each of the following two sections should have zero or more child nodes (no
- # particular name) which look like the following:
- #
- # songid
- # start time?
- # end time?
- #
- # Share music?
- share_music = Node.void('share_music')
- info.add_child(share_music)
-
genre_def_music = Node.void('genre_def_music')
info.add_child(genre_def_music)
- weekly_music = Node.void('weekly_music')
- info.add_child(weekly_music)
- weekly_music.add_child(Node.s32("value", 0))
-
- # The following section should have zero or more child nodes (no particular
- # name) which look like the following, with a song ID in the node's id attribute:
- #
- weekly_music_list = Node.void('music_list')
- weekly_music.add_child(weekly_music_list)
-
info.add_child(Node.s32_array(
'black_jacket_list',
[
@@ -218,28 +713,30 @@ class JubeatFesto(
],
))
+ # Mapping of what markers and themes are allowed for profile customization
+ # by default. If this is set to all 0's then there are no markers or themes
+ # offered and the default marker is forced.
info.add_child(Node.s32_array(
- 'add_default_music_list',
+ 'white_marker_list',
[
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
+ -1, -1, -1, -1,
+ -1, -1, -1, -1,
+ -1, -1, -1, -1,
+ -1, -1, -1, -1,
],
))
+ info.add_child(Node.s32_array(
+ 'white_theme_list',
+ [
+ -1, -1, -1, -1,
+ -1, -1, -1, -1,
+ -1, -1, -1, -1,
+ -1, -1, -1, -1,
+ ],
+ ))
+
+ # Possibly default unlocks for songs. Need to investigate further.
info.add_child(Node.s32_array(
'open_music_list',
[
@@ -306,26 +803,6 @@ class JubeatFesto(
],
))
- info.add_child(Node.s32_array(
- 'white_marker_list',
- [
- -1, -1, -1, -1,
- -1, -1, -1, -1,
- -1, -1, -1, -1,
- -1, -1, -1, -1,
- ],
- ))
-
- info.add_child(Node.s32_array(
- 'white_theme_list',
- [
- -1, -1, -1, -1,
- -1, -1, -1, -1,
- -1, -1, -1, -1,
- -1, -1, -1, -1,
- ],
- ))
-
jbox = Node.void('jbox')
info.add_child(jbox)
jbox.add_child(Node.s32('point', 0))
@@ -343,40 +820,154 @@ class JubeatFesto(
born.add_child(Node.s8('status', 0))
born.add_child(Node.s16('year', 0))
+ game_config = self.get_game_config()
+ konami_logo_50th = Node.void('konami_logo_50th')
+ info.add_child(konami_logo_50th)
+ konami_logo_50th.add_child(Node.bool('is_available', game_config.get_bool('50th_anniversary')))
+
expert_option = Node.void('expert_option')
info.add_child(expert_option)
expert_option.add_child(Node.bool('is_available', True))
- # TODO: Make this configurable.
- konami_logo_50th = Node.void('konami_logo_50th')
- info.add_child(konami_logo_50th)
- konami_logo_50th.add_child(Node.bool('is_available', True))
-
# TODO: Make this configurable.
all_music_matching = Node.void('all_music_matching')
info.add_child(all_music_matching)
all_music_matching.add_child(Node.bool('is_available', True))
- question_list = Node.void('question_list')
- info.add_child(question_list)
-
department = Node.void('department')
info.add_child(department)
department.add_child(Node.void('shop_list'))
+ question_list = Node.void('question_list')
+ info.add_child(question_list)
+
+ # Set up TUNE RUN course requirements
+ clan_course_list = Node.void('course_list')
+ info.add_child(clan_course_list)
+
+ valid_courses: Set[int] = set()
+ for course in self.__get_course_list():
+ if course['id'] < 1:
+ raise Exception(f"Invalid course ID {course['id']} found in course list!")
+ if course['id'] in valid_courses:
+ raise Exception(f"Duplicate ID {course['id']} found in course list!")
+ if course['clear_type'] == self.COURSE_CLEAR_HAZARD and 'hazard_type' not in course:
+ raise Exception(f"Need 'hazard_type' set in course {course['id']}!")
+ if course['course_type'] == self.COURSE_TYPE_TIME_BASED and 'end_time' not in course:
+ raise Exception(f"Need 'end_time' set in course {course['id']}!")
+ if course['clear_type'] in [self.COURSE_CLEAR_SCORE, self.COURSE_CLEAR_COMBINED_SCORE] and 'score' not in course:
+ raise Exception(f"Need 'score' set in course {course['id']}!")
+ if course['clear_type'] == self.COURSE_CLEAR_SCORE and course['score'] > 1000000:
+ raise Exception(f"Invalid per-coure score in course {course['id']}!")
+ if course['clear_type'] == self.COURSE_CLEAR_COMBINED_SCORE and course['score'] <= 1000000:
+ raise Exception(f"Invalid combined score in course {course['id']}!")
+ valid_courses.add(course['id'])
+
+ # Basics
+ clan_course = Node.void('course')
+ clan_course_list.add_child(clan_course)
+ clan_course.set_attribute('release_code', '2018112700')
+ clan_course.set_attribute('version_id', '0')
+ clan_course.set_attribute('id', str(course['id']))
+ clan_course.set_attribute('course_type', str(course['course_type']))
+ clan_course.add_child(Node.s32('difficulty', course['difficulty']))
+ clan_course.add_child(Node.u64('etime', (course['end_time'] if 'end_time' in course else 0) * 1000))
+ clan_course.add_child(Node.string('name', course['name']))
+
+ # List of included songs
+ tune_list = Node.void('tune_list')
+ clan_course.add_child(tune_list)
+ for order, charts in enumerate(course['music']):
+ tune = Node.void('tune')
+ tune_list.add_child(tune)
+ tune.set_attribute('no', str(order + 1))
+
+ seq_list = Node.void('seq_list')
+ tune.add_child(seq_list)
+
+ for songid, chart in charts:
+ seq = Node.void('seq')
+ seq_list.add_child(seq)
+ seq.add_child(Node.s32('music_id', songid))
+ seq.add_child(Node.s32('difficulty', chart))
+ seq.add_child(Node.bool('is_secret', False)) # TODO: make this an attribute in course definition
+
+ # Clear criteria
+ clear = Node.void('clear')
+ clan_course.add_child(clear)
+ ex_option = Node.void('ex_option')
+ clear.add_child(ex_option)
+ ex_option.add_child(Node.bool('is_hard', course['hard'] if 'hard' in course else False))
+ ex_option.add_child(Node.s32('hazard_type', course['hazard_type'] if 'hazard_type' in course else 0))
+ clear.set_attribute('type', str(course['clear_type']))
+ clear.add_child(Node.s32('score', course['score'] if 'score' in course else 0))
+
+ reward_list = Node.void('reward_list')
+ clear.add_child(reward_list)
+
+ # Each of the following two sections should have zero or more child nodes (no
+ # particular name) which look like the following:
+ #
+ # songid
+ # start time?
+ # end time?
+ #
+ # Share music?
+ share_music = Node.void('share_music')
+ info.add_child(share_music)
+
+ weekly_music = Node.void('weekly_music')
+ info.add_child(weekly_music)
+ weekly_music.add_child(Node.s32("value", 0))
+
+ info.add_child(Node.s32_array(
+ 'add_default_music_list',
+ [
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ ],
+ ))
+
+ # The following section should have zero or more child nodes (no particular
+ # name) which look like the following, with a song ID in the node's id attribute:
+ #
+ weekly_music_list = Node.void('music_list')
+ weekly_music.add_child(weekly_music_list)
+
+ # Enable/disable festo dungeon.
+ if game_config.get_bool('festo_dungeon'):
+ festo_dungeon = Node.void('festo_dungeon')
+ info.add_child(festo_dungeon)
+ festo_dungeon.add_child(Node.u64('etime', (Time.now() + Time.SECONDS_IN_WEEK) * 1000))
+
# TODO: team_batle
+ info.add_child(Node.void('team_battle'))
# TODO: qr
# TODO: course_list
# TODO: emo_list
+ info.add_child(Node.void('emo_list'))
# TODO: hike_event
# TODO: tip_list
-
- # TODO: festo_dungeon
+ info.add_child(Node.void('tip_list'))
# TODO: travel
@@ -384,6 +975,26 @@ class JubeatFesto(
return info
+ def handle_shopinfo_regist_request(self, request: Node) -> Node:
+ # Update the name of this cab for admin purposes
+ self.update_machine_name(request.child_value('shop/name'))
+
+ shopinfo = Node.void('shopinfo')
+
+ data = Node.void('data')
+ shopinfo.add_child(data)
+ data.add_child(Node.u32('cabid', 1))
+ data.add_child(Node.string('locationid', 'nowhere'))
+ data.add_child(Node.u8('tax_phase', 1))
+
+ facility = Node.void('facility')
+ data.add_child(facility)
+ facility.add_child(Node.u32('exist', 1))
+
+ data.add_child(self.__get_global_info())
+
+ return shopinfo
+
def handle_demodata_get_info_request(self, request: Node) -> Node:
root = Node.void('demodata')
data = Node.void('data')
@@ -422,30 +1033,20 @@ class JubeatFesto(
root = Node.void('demodata')
return root
+ def handle_jbox_get_agreement_request(self, request: Node) -> Node:
+ root = Node.void('jbox')
+ root.add_child(Node.bool('is_agreement', True))
+ return root
+
+ def handle_jbox_get_list_request(self, request: Node) -> Node:
+ root = Node.void('jbox')
+ root.add_child(Node.void('selection_list'))
+ return root
+
def handle_ins_netlog_request(self, request: Node) -> Node:
root = Node.void('ins')
return root
- def handle_shopinfo_regist_request(self, request: Node) -> Node:
- # Update the name of this cab for admin purposes
- self.update_machine_name(request.child_value('shop/name'))
-
- shopinfo = Node.void('shopinfo')
-
- data = Node.void('data')
- shopinfo.add_child(data)
- data.add_child(Node.u32('cabid', 1))
- data.add_child(Node.string('locationid', 'nowhere'))
- data.add_child(Node.u8('tax_phase', 1))
-
- facility = Node.void('facility')
- data.add_child(facility)
- facility.add_child(Node.u32('exist', 1))
-
- data.add_child(self.__get_global_info())
-
- return shopinfo
-
def handle_recommend_get_recommend_request(self, request: Node) -> Node:
recommend = Node.void('recommend')
data = Node.void('data')
@@ -630,7 +1231,7 @@ class JubeatFesto(
# Looks to be set to true when there's an old profile, stops tutorial from
# happening on first load.
- info.add_child(Node.bool('inherit', profile.get_bool('has_old_version')))
+ info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved')))
# Last played data, for showing cursor and such
lastdict = profile.get_dict('last')
@@ -866,6 +1467,41 @@ class JubeatFesto(
# For some reason, this is on the course list node this time around.
category_list = Node.void('category_list')
course_list.add_child(category_list)
+ for categoryid in range(1, 7):
+ category = Node.void('category')
+ category_list.add_child(category)
+ category.set_attribute('id', str(categoryid))
+ category.add_child(Node.bool('is_display', True))
+
+ # Drop list
+ drop_list = Node.void('drop_list')
+ player.add_child(drop_list)
+
+ dropachievements: Dict[int, Achievement] = {}
+ for achievement in achievements:
+ if achievement.type == 'drop':
+ dropachievements[achievement.id] = achievement
+
+ for dropid in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
+ if dropid in dropachievements:
+ dropdata = dropachievements[dropid].data
+ else:
+ dropdata = ValidatedDict()
+
+ drop = Node.void('drop')
+ drop_list.add_child(drop)
+ drop.set_attribute('id', str(dropid))
+ drop.add_child(Node.s32('exp', dropdata.get_int('exp', -1)))
+ drop.add_child(Node.s32('flag', dropdata.get_int('flag', 0)))
+
+ item_list = Node.void('item_list')
+ drop.add_child(item_list)
+
+ for itemid in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
+ item = Node.void('item')
+ item_list.add_child(item)
+ item.set_attribute('id', str(itemid))
+ item.add_child(Node.s32('num', dropdata.get_int(f'item_{itemid}')))
# Fill in category
fill_in_category = Node.void('fill_in_category')
@@ -917,7 +1553,11 @@ class JubeatFesto(
# TODO: hike_event
- # TODO: festo_dungeon
+ # Festo dungeon
+ festo_dungeon = Node.void('festo_dungeon')
+ player.add_child(festo_dungeon)
+ festo_dungeon.add_child(Node.s32('phase', profile.get_int('festo_dungeon_phase')))
+ festo_dungeon.add_child(Node.s32('clear_flag', profile.get_int('festo_dungeon_clear_flag')))
# TODO: travel
diff --git a/bemani/backend/jubeat/prop.py b/bemani/backend/jubeat/prop.py
index 6b2b213..507ed17 100644
--- a/bemani/backend/jubeat/prop.py
+++ b/bemani/backend/jubeat/prop.py
@@ -401,7 +401,7 @@ class JubeatProp(
evt = Node.void('event')
event_info.add_child(evt)
evt.set_attribute('type', str(event))
- evt.add_child(Node.u8('state', 0x1 if self.EVENTS[event]['enabled'] else 0x0))
+ evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0))
# Each of the following three sections should have zero or more child nodes (no
# particular name) which look like the following:
diff --git a/bemani/backend/jubeat/qubell.py b/bemani/backend/jubeat/qubell.py
index 1fb270e..f3e3f0d 100644
--- a/bemani/backend/jubeat/qubell.py
+++ b/bemani/backend/jubeat/qubell.py
@@ -102,7 +102,7 @@ class JubeatQubell(
evt = Node.void('event')
event_info.add_child(evt)
evt.set_attribute('type', str(event))
- evt.add_child(Node.u8('state', 0x1 if self.EVENTS[event]['enabled'] else 0x0))
+ evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0))
# Each of the following two sections should have zero or more child nodes (no
# particular name) which look like the following:
@@ -549,7 +549,7 @@ class JubeatQubell(
# Looks to be set to true when there's an old profile, stops tutorial from
# happening on first load.
- info.add_child(Node.bool('inherit', profile.get_bool('has_old_version')))
+ info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved')))
# Not saved, but loaded
info.add_child(Node.s32('mtg_entry_cnt', 123))
@@ -925,6 +925,7 @@ class JubeatQubell(
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
newprofile = oldprofile.clone()
+ newprofile.replace_bool('saved', True)
data = request.child('data')
# Grab system information
diff --git a/bemani/backend/sdvx/heavenlyhaven.py b/bemani/backend/sdvx/heavenlyhaven.py
index ba5db04..687bd53 100644
--- a/bemani/backend/sdvx/heavenlyhaven.py
+++ b/bemani/backend/sdvx/heavenlyhaven.py
@@ -108,6 +108,12 @@ class SoundVoltexHeavenlyHaven(
'category': 'game_config',
'setting': 'force_unlock_crew',
},
+ {
+ 'name': '50th Anniversary Celebration',
+ 'tip': 'Display the 50th anniversary screen in attract mode',
+ 'category': 'game_config',
+ 'setting': '50th_anniversary',
+ },
],
}
@@ -3296,7 +3302,8 @@ class SoundVoltexHeavenlyHaven(
enable_event("DEMOGAME_PLAY")
enable_event("TOTAL_MEMORIAL_ENABLE")
enable_event("EVENT_IDS_SERIALCODE_TOHO_02")
- enable_event("KONAMI_50TH_LOGO")
+ if game_config.get_bool('50th_anniversary'):
+ enable_event("KONAMI_50TH_LOGO")
enable_event("KAC6TH_FINISH")
enable_event("KAC7TH_FINISH")
enable_event("KAC8TH_FINISH")