Add Pnm Kaimei riddles support
This commit is contained in:
parent
b2edbdce24
commit
29cf43dc5c
@ -28,6 +28,7 @@ from bemani.backend.popn.lapistoria import PopnMusicLapistoria
|
||||
from bemani.backend.popn.eclale import PopnMusicEclale
|
||||
from bemani.backend.popn.usaneko import PopnMusicUsaNeko
|
||||
from bemani.backend.popn.peace import PopnMusicPeace
|
||||
from bemani.backend.popn.kaimei import PopnMusicKaimei
|
||||
from bemani.common import Model, VersionConstants
|
||||
from bemani.data import Config, Data
|
||||
|
||||
@ -60,6 +61,7 @@ class PopnMusicFactory(Factory):
|
||||
PopnMusicEclale,
|
||||
PopnMusicUsaNeko,
|
||||
PopnMusicPeace,
|
||||
PopnMusicKaimei,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@ -79,8 +81,10 @@ class PopnMusicFactory(Factory):
|
||||
return VersionConstants.POPN_MUSIC_ECLALE
|
||||
if date >= 2016121400 and date < 2018101700:
|
||||
return VersionConstants.POPN_MUSIC_USANEKO
|
||||
if date >= 2018101700:
|
||||
if date >= 2018101700 and date < 2021042600:
|
||||
return VersionConstants.POPN_MUSIC_PEACE
|
||||
if date >= 2021042600:
|
||||
return VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES
|
||||
return None
|
||||
|
||||
if model.gamecode == 'G15':
|
||||
@ -113,6 +117,8 @@ class PopnMusicFactory(Factory):
|
||||
return PopnMusicEclale(data, config, model)
|
||||
if parentversion == VersionConstants.POPN_MUSIC_PEACE:
|
||||
return PopnMusicUsaNeko(data, config, model)
|
||||
if parentversion == VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES:
|
||||
return PopnMusicPeace(data, config, model)
|
||||
|
||||
# Unknown older version
|
||||
return None
|
||||
@ -128,6 +134,8 @@ class PopnMusicFactory(Factory):
|
||||
return PopnMusicUsaNeko(data, config, model)
|
||||
if version == VersionConstants.POPN_MUSIC_PEACE:
|
||||
return PopnMusicPeace(data, config, model)
|
||||
if version == VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES:
|
||||
return PopnMusicKaimei(data, config, model)
|
||||
|
||||
# Unknown game version
|
||||
return None
|
||||
|
491
bemani/backend/popn/kaimei.py
Normal file
491
bemani/backend/popn/kaimei.py
Normal file
@ -0,0 +1,491 @@
|
||||
# vim: set fileencoding=utf-8
|
||||
import math
|
||||
import random
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from bemani.backend.popn.base import PopnMusicBase
|
||||
from bemani.backend.popn.common import PopnMusicModernBase
|
||||
from bemani.backend.popn.peace import PopnMusicPeace
|
||||
from bemani.common import VersionConstants
|
||||
from bemani.common.validateddict import Profile
|
||||
from bemani.data.types import UserID
|
||||
from bemani.protocol.node import Node
|
||||
|
||||
|
||||
class PopnMusicKaimei(PopnMusicModernBase):
|
||||
|
||||
name: str = "Pop'n Music 解明リドルズ"
|
||||
version: int = VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES
|
||||
|
||||
# Biggest ID in the music DB
|
||||
GAME_MAX_MUSIC_ID: int = 2019
|
||||
|
||||
# Biggest deco part ID in the game
|
||||
GAME_MAX_DECO_ID: int = 133
|
||||
|
||||
# Item limits are as follows:
|
||||
# 0: 1877 - ID is the music ID that the player purchased/unlocked.
|
||||
# 1: 2344
|
||||
# 2: 3
|
||||
# 3: 133 - ID points at a character part that can be purchased on the character screen.
|
||||
# 4: 1
|
||||
# 5: 1
|
||||
# 6: 60
|
||||
|
||||
def previous_version(self) -> PopnMusicBase:
|
||||
return PopnMusicPeace(self.data, self.config, self.model)
|
||||
|
||||
@classmethod
|
||||
def get_settings(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
Return all of our front-end modifiably settings.
|
||||
"""
|
||||
return {
|
||||
'ints': [
|
||||
{
|
||||
'name': 'Music Open Phase',
|
||||
'tip': 'Default music phase for all players.',
|
||||
'category': 'game_config',
|
||||
'setting': 'music_phase',
|
||||
'values': {
|
||||
# The value goes to 30 now, but it starts where usaneko left off at 23
|
||||
# Unlocks a total of 10 songs
|
||||
23: 'No music unlocks',
|
||||
24: 'Phase 1',
|
||||
25: 'Phase 2',
|
||||
26: 'Phase 3',
|
||||
27: 'Phase 4',
|
||||
28: 'Phase 5',
|
||||
29: 'Phase 6',
|
||||
30: 'Phase MAX',
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Kaimei! MN tanteisha event Phase',
|
||||
'tip': 'Kaimei! MN tanteisha event phase for all players.',
|
||||
'category': 'game_config',
|
||||
'setting': 'mn_tanteisha_phase',
|
||||
'values': {
|
||||
0: 'Disabled',
|
||||
1: 'Roki',
|
||||
2: 'shiroro',
|
||||
3: 'PIERRE&JILL',
|
||||
4: 'ROSA',
|
||||
5: 'taoxiang',
|
||||
6: 'TangTang',
|
||||
7: 'OTOBEAR',
|
||||
8: 'kaorin',
|
||||
9: 'CHARLY',
|
||||
10: 'ALOE',
|
||||
11: 'RIE♥chan',
|
||||
12: 'hina',
|
||||
13: 'PAPYRUS',
|
||||
14: '雷蔵, miho, RIE♥chan, Ryusei Honey',
|
||||
15: 'Murasaki',
|
||||
16: 'Lucifelle',
|
||||
17: '六',
|
||||
18: 'stella',
|
||||
19: 'ちせ',
|
||||
20: 'LISA',
|
||||
21: 'SUMIRE',
|
||||
22: 'SHISHITUGU',
|
||||
23: 'WALKER',
|
||||
24: 'Candy',
|
||||
25: 'Jade',
|
||||
26: 'AYA',
|
||||
27: 'kaorin',
|
||||
28: 'Lord Meh',
|
||||
29: 'HAMANOV',
|
||||
30: 'Agent',
|
||||
31: 'Yima',
|
||||
32: 'ikkei',
|
||||
33: 'echidna',
|
||||
34: 'lithos',
|
||||
35: 'SMOKE',
|
||||
36: 'the KING',
|
||||
37: 'Kicoro',
|
||||
38: 'DEBORAH',
|
||||
39: 'Teruo',
|
||||
40: 'the TOWER',
|
||||
41: 'Mamoru-kun',
|
||||
42: 'Canopus',
|
||||
43: 'Mimi Nyami',
|
||||
44: 'iO-LOWER',
|
||||
45: 'BOY',
|
||||
46: 'Sergei',
|
||||
47: 'SAPPHIRE',
|
||||
48: 'Chocky',
|
||||
49: 'HAPPPY',
|
||||
50: 'SHOLLKEE',
|
||||
51: 'CHARA-O',
|
||||
52: 'Hugh, GRIM, SUMIKO',
|
||||
53: 'Peetan',
|
||||
54: 'SHARK',
|
||||
55: 'Nakajima-san',
|
||||
56: 'KIKYO',
|
||||
57: 'SUMIRE',
|
||||
58: 'NAKAJI',
|
||||
59: 'moi moi',
|
||||
60: 'TITICACA',
|
||||
61: 'MASAMUNE',
|
||||
62: 'YUMMY'
|
||||
},
|
||||
},
|
||||
{
|
||||
# For festive times, it's possible to change the welcome greeting. I'm not sure why you would want to change this, but now you can.
|
||||
'name': 'Holiday Greeting',
|
||||
'tip': 'Changes the payment selection confirmation sound.',
|
||||
'category': 'game_config',
|
||||
'setting': 'holiday_greeting',
|
||||
'values': {
|
||||
0: 'Okay!',
|
||||
1: 'Merry Christmas!',
|
||||
2: 'Happy New Year!',
|
||||
}
|
||||
},
|
||||
{
|
||||
# peace soundtrack hatsubai kinen SP event, 0 = off, 1 = active, 2 = off (0-2)
|
||||
'name': 'peace soundtrack hatsubai kinen SP',
|
||||
'tip': 'peace soundtrack hatsubai kinen SP for all players.',
|
||||
'category': 'game_config',
|
||||
'setting': 'peace_soundtrack',
|
||||
'values': {
|
||||
0: 'Not stated',
|
||||
1: 'Active',
|
||||
2: 'Ended',
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'MZD no kimagure tanteisha joshu',
|
||||
'tip': 'Boost increasing the Clarification Level, if four or more Requests still unresolved.',
|
||||
'category': 'game_config',
|
||||
'setting': 'tanteisha_joshu',
|
||||
'values': {
|
||||
0: 'Not stated',
|
||||
1: 'Active',
|
||||
2: 'Ended',
|
||||
}
|
||||
},
|
||||
{
|
||||
# Shutchou! pop'n quest Lively II event
|
||||
'name': 'Shutchou! pop\'n quest Lively phase',
|
||||
'tip': 'Shutchou! pop\'n quest Lively phase for all players.',
|
||||
'category': 'game_config',
|
||||
'setting': 'popn_quest_lively',
|
||||
'values': {
|
||||
0: 'Not started',
|
||||
1: 'fes 1',
|
||||
2: 'fes 2',
|
||||
3: 'fes FINAL',
|
||||
4: 'fes EXTRA',
|
||||
5: 'Ended',
|
||||
}
|
||||
},
|
||||
{
|
||||
# Shutchou! pop'n quest Lively II event
|
||||
'name': 'Shutchou! pop\'n quest Lively II phase',
|
||||
'tip': 'Shutchou! pop\'n quest Lively II phase for all players.',
|
||||
'category': 'game_config',
|
||||
'setting': 'popn_quest_lively_2',
|
||||
'values': {
|
||||
0: 'Not started',
|
||||
1: 'fes 1',
|
||||
2: 'fes 2',
|
||||
3: 'fes FINAL',
|
||||
4: 'fes EXTRA',
|
||||
5: 'fes THE END',
|
||||
6: 'Ended',
|
||||
}
|
||||
},
|
||||
],
|
||||
'bools': [
|
||||
# We don't currently support lobbies or anything, so this is commented out until
|
||||
# somebody gets around to implementing it.
|
||||
# {
|
||||
# 'name': 'Net Taisen',
|
||||
# 'tip': 'Enable Net Taisen, including win/loss display on song select',
|
||||
# 'category': 'game_config',
|
||||
# 'setting': 'enable_net_taisen',
|
||||
# },
|
||||
{
|
||||
'name': 'Force Song Unlock',
|
||||
'tip': 'Force unlock all songs.',
|
||||
'category': 'game_config',
|
||||
'setting': 'force_unlock_songs',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
def get_common_config(self) -> Tuple[Dict[int, int], bool]:
|
||||
game_config = self.get_game_config()
|
||||
music_phase = game_config.get_int('music_phase')
|
||||
holiday_greeting = game_config.get_int('holiday_greeting')
|
||||
enable_net_taisen = False # game_config.get_bool('enable_net_taisen')
|
||||
mn_tanteisha_phase = game_config.get_int('mn_tanteisha_phase')
|
||||
peace_soundtrack = game_config.get_int('peace_soundtrack')
|
||||
tanteisha_joshu = game_config.get_int('tanteisha_joshu')
|
||||
popn_quest_lively = game_config.get_int('popn_quest_lively')
|
||||
popn_quest_lively_2 = game_config.get_int('popn_quest_lively_2')
|
||||
|
||||
# Event phases
|
||||
return (
|
||||
{
|
||||
# Default song phase availability (0-30)
|
||||
# The following songs are unlocked when the phase is at or above the number specified:
|
||||
# For 23 and before, see usaneko/peace
|
||||
# 24 - 1929, 1930
|
||||
# 25 - 1964
|
||||
# 26 - 1966, 1967
|
||||
# 27 - 1975
|
||||
# 28 - 1994
|
||||
# 29 - 1995, 1996
|
||||
# 30 - 1997
|
||||
0: music_phase,
|
||||
# Unknown event (0-4)
|
||||
1: 4,
|
||||
# Holiday Greeting (0-2)
|
||||
2: holiday_greeting,
|
||||
# Unknown event (0-4)
|
||||
3: 4,
|
||||
# Unknown event (0-1)
|
||||
4: 1,
|
||||
# Enable Net Taisen, including win/loss display on song select (0-1)
|
||||
5: 1 if enable_net_taisen else 0,
|
||||
# Enable NAVI-kun shunkyoku toujou, allows song 1608 to be unlocked (0-1)
|
||||
6: 1,
|
||||
# Unknown event (0-1)
|
||||
7: 1,
|
||||
# Unknown event (0-2)
|
||||
8: 2,
|
||||
# Daily Mission (0-2)
|
||||
9: 2,
|
||||
# NAVI-kun Song phase availability (0-30)
|
||||
10: 30,
|
||||
# Unknown event (0-1)
|
||||
11: 1,
|
||||
# Unknown event (0-2)
|
||||
12: 2,
|
||||
# Enable Pop'n Peace preview song (0-1)
|
||||
13: 1,
|
||||
# Stamp Card Rally (0-39)
|
||||
14: 39,
|
||||
# Unknown event (0-2)
|
||||
15: 2,
|
||||
# Unknown event (0-3)
|
||||
16: 3,
|
||||
# Unknown event (0-8)
|
||||
17: 8,
|
||||
# FLOOR INFECTION event (0-1)
|
||||
18: 1,
|
||||
# pop'n music × NOSTALGIA kyouenkai (0-1)
|
||||
19: 1,
|
||||
# Event archive event (0-13)
|
||||
20: 13,
|
||||
# Pop'n event archive song phase availability (0-20)
|
||||
21: 20,
|
||||
# バンめし♪ ふるさとグランプリunlocks (split into two rounds) (0-2)
|
||||
22: 2,
|
||||
# いちかのBEMANI投票選抜戦2019 unlocks (0-1)
|
||||
23: 1,
|
||||
# ダンキラ!!! × pop'n music unlocks (0-1)
|
||||
24: 1,
|
||||
# Kaimei riddles events starts here
|
||||
# Kaimei! MN tanteisha event Phase (0-62)
|
||||
# When active, the following songs are available for unlock
|
||||
# 1: 1914
|
||||
# 2: 195
|
||||
# 3: 1915
|
||||
# 4: 1916
|
||||
# 5: 1896
|
||||
# 6: 1908
|
||||
# 7: 1931
|
||||
# 8: 1924
|
||||
# 9: 1925
|
||||
# 10: 1894
|
||||
# 11: 1926
|
||||
# 12: 1927
|
||||
# 13: 1928
|
||||
# 14: 1932, 1933, 1934, 1935
|
||||
# 15: 521
|
||||
# 16: 1936
|
||||
# 17: 1943
|
||||
# 18: 1937
|
||||
# 19: 1938
|
||||
# 20: 1939
|
||||
# 21: 1943
|
||||
# 22: 1941
|
||||
# 23: 1942
|
||||
# 24: 323
|
||||
# 25: 1946
|
||||
# 26: 575
|
||||
# 27: 1947
|
||||
# 28: 1955
|
||||
# 29: 1957
|
||||
# 30: 1958
|
||||
# 31: 1959
|
||||
# 32: 1960
|
||||
# 33: 1961
|
||||
# 34: 1963
|
||||
# 35: 1962
|
||||
# 36: 1968
|
||||
# 37: 1969
|
||||
# 38: 1965
|
||||
# 39: 1970
|
||||
# 40: 1976
|
||||
# 41: 1977
|
||||
# 42: 1978
|
||||
# 43: 1945
|
||||
# 44: 1944
|
||||
# 45: 1999
|
||||
# 46: 2000
|
||||
# 47: 2001
|
||||
# 48: 2002
|
||||
# 49: 2003
|
||||
# 50: 2004
|
||||
# 51: 2005
|
||||
# 52: 267, 1998, 2006
|
||||
# 53: 2011
|
||||
# 54: 2007
|
||||
# 55: 2008
|
||||
# 56: 2009
|
||||
# 57: 2010
|
||||
# 58: 2016
|
||||
# 59: 2012
|
||||
# 60: 2018
|
||||
# 61: 2013
|
||||
# 62: 2015
|
||||
25: mn_tanteisha_phase,
|
||||
# Unknown event (0-3)
|
||||
26: 3,
|
||||
# peace soundtrack hatsubai kinen SP (0-2)
|
||||
# When active, the following songs are available for unlock: 1971, 1972, 1973
|
||||
27: peace_soundtrack,
|
||||
# MZD no kimagure tanteisha joshu (0-2)
|
||||
28: tanteisha_joshu,
|
||||
# Shutchou! pop'n quest Lively (0-5)
|
||||
# When active, the following songs are available for unlock
|
||||
# 1: 1917, 1918
|
||||
# 2: 1919, 1921
|
||||
# 3: 1920, 1922, 1923
|
||||
# 4: 1974
|
||||
29: popn_quest_lively,
|
||||
# Shutchou! pop'n quest Lively II (0-6)
|
||||
# When active, the following songs are available for unlock
|
||||
# 1: 1989, 1990, 1991
|
||||
# 2: 1984, 1985, 1992
|
||||
# 3: 1982, 1983, 1988
|
||||
# 4: 1986, 1987, 1993
|
||||
# 5: 2017
|
||||
30: popn_quest_lively_2,
|
||||
},
|
||||
False,
|
||||
)
|
||||
|
||||
def format_profile(self, userid: UserID, profile: Profile) -> Node:
|
||||
root = PopnMusicModernBase.format_profile(self, userid, profile)
|
||||
|
||||
account = root.child('account')
|
||||
account.add_child(Node.s16('card_again_count', profile.get_int('point')))
|
||||
account.add_child(Node.s16('sp_riddles_id', profile.get_int('step')))
|
||||
|
||||
# Kaimei riddles events
|
||||
event2021 = Node.void('event2021')
|
||||
root.add_child(event2021)
|
||||
event2021.add_child(Node.u32('point', profile.get_int('point')))
|
||||
event2021.add_child(Node.u8('step', profile.get_int('step')))
|
||||
event2021.add_child(Node.u32_array('quest_point', profile.get_int_array('quest_point', 8, [0] * 8)))
|
||||
event2021.add_child(Node.u8('step_nos', profile.get_int('step_nos')))
|
||||
event2021.add_child(Node.u32_array('quest_point_nos', profile.get_int_array('quest_point_nos', 13, [0] * 13)))
|
||||
|
||||
riddles_data = Node.void('riddles_data')
|
||||
root.add_child(riddles_data)
|
||||
|
||||
# Generate Short Riddles for MN tanteisha
|
||||
randomRiddles: List[int] = []
|
||||
for x in range(3):
|
||||
riddle = 0
|
||||
while True:
|
||||
riddle = math.floor(random.randrange(1, 21, 1))
|
||||
try:
|
||||
randomRiddles.index(riddle)
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
randomRiddles.append(riddle)
|
||||
|
||||
sh_riddles = Node.void('sh_riddles')
|
||||
riddles_data.add_child(sh_riddles)
|
||||
sh_riddles.add_child(Node.u32('sh_riddles_id', riddle))
|
||||
|
||||
# Set up kaimei riddles achievements
|
||||
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
|
||||
for achievement in achievements:
|
||||
if achievement.type == 'riddle':
|
||||
kaimei_gauge = achievement.data.get_int('kaimei_gauge')
|
||||
is_cleared = achievement.data.get_bool('is_cleared')
|
||||
riddles_cleared = achievement.data.get_bool('riddles_cleared')
|
||||
select_count = achievement.data.get_int('select_count')
|
||||
other_count = achievement.data.get_int('other_count')
|
||||
|
||||
sp_riddles = Node.void('sp_riddles')
|
||||
riddles_data.add_child(sp_riddles)
|
||||
sp_riddles.add_child(Node.u16('kaimei_gauge', kaimei_gauge))
|
||||
sp_riddles.add_child(Node.bool('is_cleared', is_cleared))
|
||||
sp_riddles.add_child(Node.bool('riddles_cleared', riddles_cleared))
|
||||
sp_riddles.add_child(Node.u8('select_count', select_count))
|
||||
sp_riddles.add_child(Node.u32('other_count', other_count))
|
||||
|
||||
return root
|
||||
|
||||
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
|
||||
newprofile = PopnMusicModernBase.unformat_profile(self, userid, request, oldprofile)
|
||||
|
||||
account = request.child('account')
|
||||
if account is not None:
|
||||
newprofile.replace_int('card_again_count', account.child_value('card_again_count'))
|
||||
newprofile.replace_int('sp_riddles_id', account.child_value('sp_riddles_id'))
|
||||
|
||||
# Kaimei riddles events
|
||||
event2021 = request.child('event2021')
|
||||
if event2021 is not None:
|
||||
newprofile.replace_int('point', event2021.child_value('point'))
|
||||
newprofile.replace_int('step', event2021.child_value('step'))
|
||||
newprofile.replace_int_array('quest_point', 8, event2021.child_value('quest_point'))
|
||||
newprofile.replace_int('step_nos', event2021.child_value('step_nos'))
|
||||
newprofile.replace_int_array('quest_point_nos', 13, event2021.child_value('quest_point_nos'))
|
||||
|
||||
# Extract kaimei riddles achievements
|
||||
for node in request.children:
|
||||
if node.name == 'riddles_data':
|
||||
riddle_id = 0
|
||||
playedRiddle = request.child('account').child_value('sp_riddles_id')
|
||||
for riddle in node.children:
|
||||
kaimei_gauge = riddle.child_value('kaimei_gauge')
|
||||
is_cleared = riddle.child_value('is_cleared')
|
||||
riddles_cleared = riddle.child_value('riddles_cleared')
|
||||
select_count = riddle.child_value('select_count')
|
||||
other_count = riddle.child_value('other_count')
|
||||
|
||||
if (riddles_cleared or select_count >= 3):
|
||||
select_count = 3
|
||||
elif (playedRiddle == riddle_id):
|
||||
select_count += 1
|
||||
|
||||
self.data.local.user.put_achievement(
|
||||
self.game,
|
||||
self.version,
|
||||
userid,
|
||||
riddle_id,
|
||||
'riddle',
|
||||
{
|
||||
'kaimei_gauge': kaimei_gauge,
|
||||
'is_cleared': is_cleared,
|
||||
'riddles_cleared': riddles_cleared,
|
||||
'select_count': select_count,
|
||||
'other_count': other_count,
|
||||
},
|
||||
)
|
||||
|
||||
riddle_id += 1
|
||||
|
||||
return newprofile
|
@ -5,6 +5,7 @@ from bemani.client.popn.lapistoria import PopnMusicLapistoriaClient
|
||||
from bemani.client.popn.eclale import PopnMusicEclaleClient
|
||||
from bemani.client.popn.usaneko import PopnMusicUsaNekoClient
|
||||
from bemani.client.popn.peace import PopnMusicPeaceClient
|
||||
from bemani.client.popn.kaimei import PopnMusicKaimeiClient
|
||||
|
||||
|
||||
__all__ = [
|
||||
@ -15,4 +16,5 @@ __all__ = [
|
||||
"PopnMusicEclaleClient",
|
||||
"PopnMusicUsaNekoClient",
|
||||
"PopnMusicPeaceClient",
|
||||
"PopnMusicKaimeiClient",
|
||||
]
|
||||
|
672
bemani/client/popn/kaimei.py
Normal file
672
bemani/client/popn/kaimei.py
Normal file
@ -0,0 +1,672 @@
|
||||
import random
|
||||
import time
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from bemani.client.base import BaseClient
|
||||
from bemani.protocol import Node
|
||||
|
||||
|
||||
class PopnMusicKaimeiClient(BaseClient):
|
||||
NAME = 'TEST'
|
||||
|
||||
def verify_pcb24_boot(self, loc: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
pcb24 = Node.void('pcb24')
|
||||
call.add_child(pcb24)
|
||||
pcb24.set_attribute('method', 'boot')
|
||||
pcb24.add_child(Node.string('loc_id', loc))
|
||||
pcb24.add_child(Node.u8('loc_type', 0))
|
||||
pcb24.add_child(Node.string('loc_name', ''))
|
||||
pcb24.add_child(Node.string('country', 'US'))
|
||||
pcb24.add_child(Node.string('region', '.'))
|
||||
pcb24.add_child(Node.s16('pref', 51))
|
||||
pcb24.add_child(Node.string('customer', ''))
|
||||
pcb24.add_child(Node.string('company', ''))
|
||||
pcb24.add_child(Node.ipv4('gip', '127.0.0.1'))
|
||||
pcb24.add_child(Node.u16('gp', 10011))
|
||||
pcb24.add_child(Node.string('rom_number', 'M39-JB-G01'))
|
||||
pcb24.add_child(Node.u64('c_drive', 10028228608))
|
||||
pcb24.add_child(Node.u64('d_drive', 47945170944))
|
||||
pcb24.add_child(Node.u64('e_drive', 10394677248))
|
||||
pcb24.add_child(Node.string('etc', ''))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify that response is correct
|
||||
self.assert_path(resp, "response/pcb24/@status")
|
||||
|
||||
def __verify_common(self, root: str, resp: Node) -> None:
|
||||
self.assert_path(resp, f"response/{root}/phase/event_id")
|
||||
self.assert_path(resp, f"response/{root}/phase/phase")
|
||||
|
||||
# Area stuff is not needed unless enabling events.
|
||||
# self.assert_path(resp, f"response/{root}/area/area_id")
|
||||
# self.assert_path(resp, f"response/{root}/area/end_date")
|
||||
# self.assert_path(resp, f"response/{root}/area/medal_id")
|
||||
# self.assert_path(resp, f"response/{root}/area/is_limit")
|
||||
|
||||
self.assert_path(resp, f"response/{root}/choco/choco_id")
|
||||
self.assert_path(resp, f"response/{root}/choco/param")
|
||||
self.assert_path(resp, f"response/{root}/goods/item_id")
|
||||
self.assert_path(resp, f"response/{root}/goods/item_type")
|
||||
self.assert_path(resp, f"response/{root}/goods/price")
|
||||
self.assert_path(resp, f"response/{root}/goods/goods_type")
|
||||
|
||||
def verify_info24_common(self, loc: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
info24 = Node.void('info24')
|
||||
call.add_child(info24)
|
||||
info24.set_attribute('loc_id', loc)
|
||||
info24.set_attribute('method', 'common')
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify that response is correct
|
||||
self.__verify_common('info24', resp)
|
||||
|
||||
def verify_lobby24_getlist(self, loc: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
lobby24 = Node.void('lobby24')
|
||||
call.add_child(lobby24)
|
||||
lobby24.set_attribute('method', 'getList')
|
||||
lobby24.add_child(Node.string('location_id', loc))
|
||||
lobby24.add_child(Node.u8('net_version', 63))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify that response is correct
|
||||
self.assert_path(resp, "response/lobby24/@status")
|
||||
|
||||
def __verify_profile(self, resp: Node) -> None:
|
||||
self.assert_path(resp, "response/player24/account/name")
|
||||
self.assert_path(resp, "response/player24/account/g_pm_id")
|
||||
self.assert_path(resp, "response/player24/account/tutorial")
|
||||
self.assert_path(resp, "response/player24/account/area_id")
|
||||
self.assert_path(resp, "response/player24/account/use_navi")
|
||||
self.assert_path(resp, "response/player24/account/read_news")
|
||||
self.assert_path(resp, "response/player24/account/nice")
|
||||
self.assert_path(resp, "response/player24/account/favorite_chara")
|
||||
self.assert_path(resp, "response/player24/account/special_area")
|
||||
self.assert_path(resp, "response/player24/account/chocolate_charalist")
|
||||
self.assert_path(resp, "response/player24/account/chocolate_sp_chara")
|
||||
self.assert_path(resp, "response/player24/account/chocolate_pass_cnt")
|
||||
self.assert_path(resp, "response/player24/account/chocolate_hon_cnt")
|
||||
self.assert_path(resp, "response/player24/account/teacher_setting")
|
||||
self.assert_path(resp, "response/player24/account/welcom_pack")
|
||||
self.assert_path(resp, "response/player24/account/ranking_node")
|
||||
self.assert_path(resp, "response/player24/account/chara_ranking_kind_id")
|
||||
self.assert_path(resp, "response/player24/account/navi_evolution_flg")
|
||||
self.assert_path(resp, "response/player24/account/ranking_news_last_no")
|
||||
self.assert_path(resp, "response/player24/account/power_point")
|
||||
self.assert_path(resp, "response/player24/account/player_point")
|
||||
self.assert_path(resp, "response/player24/account/power_point_list")
|
||||
self.assert_path(resp, "response/player24/account/staff")
|
||||
self.assert_path(resp, "response/player24/account/item_type")
|
||||
self.assert_path(resp, "response/player24/account/item_id")
|
||||
self.assert_path(resp, "response/player24/account/is_conv")
|
||||
self.assert_path(resp, "response/player24/account/license_data")
|
||||
self.assert_path(resp, "response/player24/account/my_best")
|
||||
self.assert_path(resp, "response/player24/account/latest_music")
|
||||
self.assert_path(resp, "response/player24/account/total_play_cnt")
|
||||
self.assert_path(resp, "response/player24/account/today_play_cnt")
|
||||
self.assert_path(resp, "response/player24/account/consecutive_days")
|
||||
self.assert_path(resp, "response/player24/account/total_days")
|
||||
self.assert_path(resp, "response/player24/account/interval_day")
|
||||
self.assert_path(resp, "response/player24/account/active_fr_num")
|
||||
self.assert_path(resp, "response/player24/eaappli/relation")
|
||||
self.assert_path(resp, "response/player24/info/ep")
|
||||
self.assert_path(resp, "response/player24/config")
|
||||
self.assert_path(resp, "response/player24/option")
|
||||
self.assert_path(resp, "response/player24/custom_cate")
|
||||
self.assert_path(resp, "response/player24/navi_data")
|
||||
self.assert_path(resp, "response/player24/mission/mission_id")
|
||||
self.assert_path(resp, "response/player24/mission/gauge_point")
|
||||
self.assert_path(resp, "response/player24/mission/mission_comp")
|
||||
self.assert_path(resp, "response/player24/netvs")
|
||||
self.assert_path(resp, "response/player24/customize")
|
||||
self.assert_path(resp, "response/player24/stamp/stamp_id")
|
||||
self.assert_path(resp, "response/player24/stamp/cnt")
|
||||
|
||||
def verify_player24_read(self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'read')
|
||||
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
player24.add_child(Node.s8('pref', 51))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
if msg_type == 'new':
|
||||
# Verify that response is correct
|
||||
self.assert_path(resp, "response/player24/result")
|
||||
status = resp.child_value('player24/result')
|
||||
if status != 2:
|
||||
raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'')
|
||||
|
||||
return {
|
||||
'items': {},
|
||||
'characters': {},
|
||||
'points': {},
|
||||
}
|
||||
elif msg_type == 'query':
|
||||
# Verify that the response is correct
|
||||
self.__verify_profile(resp)
|
||||
|
||||
self.assert_path(resp, "response/player24/result")
|
||||
status = resp.child_value('player24/result')
|
||||
if status != 0:
|
||||
raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'')
|
||||
name = resp.child_value('player24/account/name')
|
||||
if name != self.NAME:
|
||||
raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'')
|
||||
|
||||
# Medals and items
|
||||
items: Dict[int, Dict[str, int]] = {}
|
||||
charas: Dict[int, Dict[str, int]] = {}
|
||||
courses: Dict[int, Dict[str, int]] = {}
|
||||
for obj in resp.child('player24').children:
|
||||
if obj.name == 'item':
|
||||
items[obj.child_value('id')] = {
|
||||
'type': obj.child_value('type'),
|
||||
'param': obj.child_value('param'),
|
||||
}
|
||||
elif obj.name == 'chara_param':
|
||||
charas[obj.child_value('chara_id')] = {
|
||||
'friendship': obj.child_value('friendship'),
|
||||
}
|
||||
elif obj.name == 'course_data':
|
||||
courses[obj.child_value('course_id')] = {
|
||||
'clear_type': obj.child_value('clear_type'),
|
||||
'clear_rank': obj.child_value('clear_rank'),
|
||||
'total_score': obj.child_value('total_score'),
|
||||
'count': obj.child_value('update_count'),
|
||||
'sheet_num': obj.child_value('sheet_num'),
|
||||
}
|
||||
|
||||
return {
|
||||
'items': items,
|
||||
'characters': charas,
|
||||
'courses': courses,
|
||||
'points': {0: {'points': resp.child_value('player24/account/player_point')}},
|
||||
}
|
||||
else:
|
||||
raise Exception(f'Unrecognized message type \'{msg_type}\'')
|
||||
|
||||
def verify_player24_read_score(self, ref_id: str) -> Dict[str, Dict[int, Dict[int, int]]]:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'read_score')
|
||||
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
player24.add_child(Node.s8('pref', 51))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify defaults
|
||||
self.assert_path(resp, "response/player24/@status")
|
||||
|
||||
# Grab scores
|
||||
scores: Dict[int, Dict[int, int]] = {}
|
||||
medals: Dict[int, Dict[int, int]] = {}
|
||||
ranks: Dict[int, Dict[int, int]] = {}
|
||||
for child in resp.child('player24').children:
|
||||
if child.name != 'music':
|
||||
continue
|
||||
|
||||
musicid = child.child_value('music_num')
|
||||
chart = child.child_value('sheet_num')
|
||||
score = child.child_value('score')
|
||||
medal = child.child_value('clear_type')
|
||||
rank = child.child_value('clear_rank')
|
||||
|
||||
if musicid not in scores:
|
||||
scores[musicid] = {}
|
||||
if musicid not in medals:
|
||||
medals[musicid] = {}
|
||||
if musicid not in ranks:
|
||||
ranks[musicid] = {}
|
||||
|
||||
scores[musicid][chart] = score
|
||||
medals[musicid][chart] = medal
|
||||
ranks[musicid][chart] = rank
|
||||
|
||||
return {
|
||||
'scores': scores,
|
||||
'medals': medals,
|
||||
'ranks': ranks,
|
||||
}
|
||||
|
||||
def verify_player24_start(self, ref_id: str, loc: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('loc_id', loc)
|
||||
player24.set_attribute('ref_id', ref_id)
|
||||
player24.set_attribute('method', 'start')
|
||||
player24.set_attribute('start_type', '0')
|
||||
pcb_card = Node.void('pcb_card')
|
||||
player24.add_child(pcb_card)
|
||||
pcb_card.add_child(Node.s8('card_enable', 1))
|
||||
pcb_card.add_child(Node.s8('card_soldout', 0))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify that response is correct
|
||||
self.__verify_common('player24', resp)
|
||||
|
||||
def verify_player24_update_ranking(self, ref_id: str, loc: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'update_ranking')
|
||||
player24.add_child(Node.s16('pref', 51))
|
||||
player24.add_child(Node.string('location_id', loc))
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
player24.add_child(Node.string('name', self.NAME))
|
||||
player24.add_child(Node.s16('chara_num', 1))
|
||||
player24.add_child(Node.s16('course_id', 12345))
|
||||
player24.add_child(Node.s32('total_score', 86000))
|
||||
player24.add_child(Node.s16('music_num', 1375))
|
||||
player24.add_child(Node.u8('sheet_num', 2))
|
||||
player24.add_child(Node.u8('clear_type', 7))
|
||||
player24.add_child(Node.u8('clear_rank', 5))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify that response is correct
|
||||
self.assert_path(resp, "response/player24/all_ranking/name")
|
||||
self.assert_path(resp, "response/player24/all_ranking/chara_num")
|
||||
self.assert_path(resp, "response/player24/all_ranking/total_score")
|
||||
self.assert_path(resp, "response/player24/all_ranking/clear_type")
|
||||
self.assert_path(resp, "response/player24/all_ranking/clear_rank")
|
||||
self.assert_path(resp, "response/player24/all_ranking/player_count")
|
||||
self.assert_path(resp, "response/player24/all_ranking/player_rank")
|
||||
|
||||
def verify_player24_logout(self, ref_id: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('ref_id', ref_id)
|
||||
player24.set_attribute('method', 'logout')
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify that response is correct
|
||||
self.assert_path(resp, "response/player24/@status")
|
||||
|
||||
def verify_player24_write(
|
||||
self,
|
||||
ref_id: str,
|
||||
item: Optional[Dict[str, int]]=None,
|
||||
character: Optional[Dict[str, int]]=None,
|
||||
) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'write')
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
|
||||
# Add required children
|
||||
config = Node.void('config')
|
||||
player24.add_child(config)
|
||||
config.add_child(Node.s16('chara', 1543))
|
||||
|
||||
if item is not None:
|
||||
itemnode = Node.void('item')
|
||||
player24.add_child(itemnode)
|
||||
itemnode.add_child(Node.u8('type', item['type']))
|
||||
itemnode.add_child(Node.u16('id', item['id']))
|
||||
itemnode.add_child(Node.u16('param', item['param']))
|
||||
itemnode.add_child(Node.bool('is_new', False))
|
||||
itemnode.add_child(Node.u64('get_time', 0))
|
||||
|
||||
if character is not None:
|
||||
chara_param = Node.void('chara_param')
|
||||
player24.add_child(chara_param)
|
||||
chara_param.add_child(Node.u16('chara_id', character['id']))
|
||||
chara_param.add_child(Node.u16('friendship', character['friendship']))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player24/@status")
|
||||
|
||||
def verify_player24_buy(self, ref_id: str, item: Dict[str, int]) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'buy')
|
||||
player24.add_child(Node.s32('play_id', 0))
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
player24.add_child(Node.u16('id', item['id']))
|
||||
player24.add_child(Node.u8('type', item['type']))
|
||||
player24.add_child(Node.u16('param', item['param']))
|
||||
player24.add_child(Node.s32('lumina', item['points']))
|
||||
player24.add_child(Node.u16('price', item['price']))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player24/@status")
|
||||
|
||||
def verify_player24_write_music(self, ref_id: str, score: Dict[str, Any]) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'write_music')
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
player24.add_child(Node.string('data_id', ref_id))
|
||||
player24.add_child(Node.string('name', self.NAME))
|
||||
player24.add_child(Node.u8('stage', 0))
|
||||
player24.add_child(Node.s16('music_num', score['id']))
|
||||
player24.add_child(Node.u8('sheet_num', score['chart']))
|
||||
player24.add_child(Node.u8('clear_type', score['medal']))
|
||||
player24.add_child(Node.s32('score', score['score']))
|
||||
player24.add_child(Node.s16('combo', 0))
|
||||
player24.add_child(Node.s16('cool', 0))
|
||||
player24.add_child(Node.s16('great', 0))
|
||||
player24.add_child(Node.s16('good', 0))
|
||||
player24.add_child(Node.s16('bad', 0))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player24/@status")
|
||||
|
||||
def verify_player24_new(self, ref_id: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player24 = Node.void('player24')
|
||||
call.add_child(player24)
|
||||
player24.set_attribute('method', 'new')
|
||||
|
||||
player24.add_child(Node.string('ref_id', ref_id))
|
||||
player24.add_child(Node.string('name', self.NAME))
|
||||
player24.add_child(Node.s8('pref', 51))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
|
||||
# Verify nodes
|
||||
self.__verify_profile(resp)
|
||||
|
||||
def verify(self, cardid: Optional[str]) -> None:
|
||||
# Verify boot sequence is okay
|
||||
self.verify_services_get(
|
||||
expected_services=[
|
||||
'pcbtracker',
|
||||
'pcbevent',
|
||||
'local',
|
||||
'message',
|
||||
'facility',
|
||||
'cardmng',
|
||||
'package',
|
||||
'posevent',
|
||||
'pkglist',
|
||||
'dlstatus',
|
||||
'eacoin',
|
||||
'lobby',
|
||||
'ntp',
|
||||
'keepalive'
|
||||
]
|
||||
)
|
||||
paseli_enabled = self.verify_pcbtracker_alive()
|
||||
self.verify_message_get()
|
||||
self.verify_package_list()
|
||||
location = self.verify_facility_get()
|
||||
self.verify_pcbevent_put()
|
||||
self.verify_pcb24_boot(location)
|
||||
self.verify_info24_common(location)
|
||||
self.verify_lobby24_getlist(location)
|
||||
|
||||
# Verify card registration and profile lookup
|
||||
if cardid is not None:
|
||||
card = cardid
|
||||
else:
|
||||
card = self.random_card()
|
||||
print(f"Generated random card ID {card} for use.")
|
||||
|
||||
if cardid is None:
|
||||
self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled)
|
||||
ref_id = self.verify_cardmng_getrefid(card)
|
||||
if len(ref_id) != 16:
|
||||
raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card')
|
||||
if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled):
|
||||
raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card')
|
||||
self.verify_player24_read(ref_id, msg_type='new')
|
||||
self.verify_player24_new(ref_id)
|
||||
else:
|
||||
print("Skipping new card checks for existing card")
|
||||
ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled)
|
||||
|
||||
# Verify pin handling and return card handling
|
||||
self.verify_cardmng_authpass(ref_id, correct=True)
|
||||
self.verify_cardmng_authpass(ref_id, correct=False)
|
||||
if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled):
|
||||
raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card')
|
||||
|
||||
# Verify proper handling of basic stuff
|
||||
self.verify_player24_read(ref_id, msg_type='query')
|
||||
self.verify_player24_start(ref_id, location)
|
||||
self.verify_player24_write(ref_id)
|
||||
self.verify_player24_logout(ref_id)
|
||||
|
||||
if cardid is None:
|
||||
# Verify unlocks/story mode work
|
||||
unlocks = self.verify_player24_read(ref_id, msg_type='query')
|
||||
for item in unlocks['items']:
|
||||
if item in [1592, 1608]:
|
||||
# Song unlocks after one play
|
||||
continue
|
||||
raise Exception('Got nonzero items count on a new card!')
|
||||
for _ in unlocks['characters']:
|
||||
raise Exception('Got nonzero characters count on a new card!')
|
||||
for _ in unlocks['courses']:
|
||||
raise Exception('Got nonzero course count on a new card!')
|
||||
if unlocks['points'][0]['points'] != 300:
|
||||
raise Exception('Got wrong default value for points on a new card!')
|
||||
|
||||
self.verify_player24_write(ref_id, item={'id': 4, 'type': 2, 'param': 69})
|
||||
unlocks = self.verify_player24_read(ref_id, msg_type='query')
|
||||
if 4 not in unlocks['items']:
|
||||
raise Exception('Expecting to see item ID 4 in items!')
|
||||
if unlocks['items'][4]['type'] != 2:
|
||||
raise Exception('Expecting to see item ID 4 to have type 2 in items!')
|
||||
if unlocks['items'][4]['param'] != 69:
|
||||
raise Exception('Expecting to see item ID 4 to have param 69 in items!')
|
||||
|
||||
self.verify_player24_write(ref_id, character={'id': 5, 'friendship': 420})
|
||||
unlocks = self.verify_player24_read(ref_id, msg_type='query')
|
||||
if 5 not in unlocks['characters']:
|
||||
raise Exception('Expecting to see chara ID 5 in characters!')
|
||||
if unlocks['characters'][5]['friendship'] != 420:
|
||||
raise Exception('Expecting to see chara ID 5 to have type 2 in characters!')
|
||||
|
||||
# Verify purchases work
|
||||
self.verify_player24_buy(ref_id, item={'id': 6, 'type': 3, 'param': 8, 'points': 400, 'price': 250})
|
||||
unlocks = self.verify_player24_read(ref_id, msg_type='query')
|
||||
if 6 not in unlocks['items']:
|
||||
raise Exception('Expecting to see item ID 6 in items!')
|
||||
if unlocks['items'][6]['type'] != 3:
|
||||
raise Exception('Expecting to see item ID 6 to have type 3 in items!')
|
||||
if unlocks['items'][6]['param'] != 8:
|
||||
raise Exception('Expecting to see item ID 6 to have param 8 in items!')
|
||||
if unlocks['points'][0]['points'] != 150:
|
||||
raise Exception(f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!')
|
||||
|
||||
# Verify course handling
|
||||
self.verify_player24_update_ranking(ref_id, location)
|
||||
unlocks = self.verify_player24_read(ref_id, msg_type='query')
|
||||
if 12345 not in unlocks['courses']:
|
||||
raise Exception('Expecting to see course ID 12345 in courses!')
|
||||
if unlocks['courses'][12345]['clear_type'] != 7:
|
||||
raise Exception('Expecting to see item ID 12345 to have clear_type 7 in courses!')
|
||||
if unlocks['courses'][12345]['clear_rank'] != 5:
|
||||
raise Exception('Expecting to see item ID 12345 to have clear_rank 5 in courses!')
|
||||
if unlocks['courses'][12345]['total_score'] != 86000:
|
||||
raise Exception('Expecting to see item ID 12345 to have total_score 86000 in courses!')
|
||||
if unlocks['courses'][12345]['count'] != 1:
|
||||
raise Exception('Expecting to see item ID 12345 to have count 1 in courses!')
|
||||
if unlocks['courses'][12345]['sheet_num'] != 2:
|
||||
raise Exception('Expecting to see item ID 12345 to have sheet_num 2 in courses!')
|
||||
|
||||
# Verify score handling
|
||||
scores = self.verify_player24_read_score(ref_id)
|
||||
for _ in scores['medals']:
|
||||
raise Exception('Got nonzero medals count on a new card!')
|
||||
for _ in scores['scores']:
|
||||
raise Exception('Got nonzero scores count on a new card!')
|
||||
|
||||
for phase in [1, 2]:
|
||||
if phase == 1:
|
||||
dummyscores = [
|
||||
# An okay score on a chart
|
||||
{
|
||||
'id': 987,
|
||||
'chart': 2,
|
||||
'medal': 5,
|
||||
'score': 76543,
|
||||
},
|
||||
# A good score on an easier chart of the same song
|
||||
{
|
||||
'id': 987,
|
||||
'chart': 0,
|
||||
'medal': 6,
|
||||
'score': 99999,
|
||||
},
|
||||
# A bad score on a hard chart
|
||||
{
|
||||
'id': 741,
|
||||
'chart': 3,
|
||||
'medal': 2,
|
||||
'score': 45000,
|
||||
},
|
||||
# A terrible score on an easy chart
|
||||
{
|
||||
'id': 742,
|
||||
'chart': 1,
|
||||
'medal': 2,
|
||||
'score': 1,
|
||||
},
|
||||
]
|
||||
# Random score to add in
|
||||
songid = random.randint(907, 950)
|
||||
chartid = random.randint(0, 3)
|
||||
score = random.randint(0, 100000)
|
||||
medal = random.randint(1, 11)
|
||||
dummyscores.append({
|
||||
'id': songid,
|
||||
'chart': chartid,
|
||||
'medal': medal,
|
||||
'score': score,
|
||||
})
|
||||
if phase == 2:
|
||||
dummyscores = [
|
||||
# A better score on the same chart
|
||||
{
|
||||
'id': 987,
|
||||
'chart': 2,
|
||||
'medal': 6,
|
||||
'score': 98765,
|
||||
},
|
||||
# A worse score on another same chart
|
||||
{
|
||||
'id': 987,
|
||||
'chart': 0,
|
||||
'medal': 3,
|
||||
'score': 12345,
|
||||
'expected_score': 99999,
|
||||
'expected_medal': 6,
|
||||
},
|
||||
]
|
||||
|
||||
for dummyscore in dummyscores:
|
||||
self.verify_player24_write_music(ref_id, dummyscore)
|
||||
scores = self.verify_player24_read_score(ref_id)
|
||||
for expected in dummyscores:
|
||||
newscore = scores['scores'][expected['id']][expected['chart']]
|
||||
newmedal = scores['medals'][expected['id']][expected['chart']]
|
||||
newrank = scores['ranks'][expected['id']][expected['chart']]
|
||||
|
||||
if 'expected_score' in expected:
|
||||
expected_score = expected['expected_score']
|
||||
else:
|
||||
expected_score = expected['score']
|
||||
if 'expected_medal' in expected:
|
||||
expected_medal = expected['expected_medal']
|
||||
else:
|
||||
expected_medal = expected['medal']
|
||||
|
||||
if newscore < 50000:
|
||||
expected_rank = 1
|
||||
elif newscore < 62000:
|
||||
expected_rank = 2
|
||||
elif newscore < 72000:
|
||||
expected_rank = 3
|
||||
elif newscore < 82000:
|
||||
expected_rank = 4
|
||||
elif newscore < 90000:
|
||||
expected_rank = 5
|
||||
elif newscore < 95000:
|
||||
expected_rank = 6
|
||||
elif newscore < 98000:
|
||||
expected_rank = 7
|
||||
else:
|
||||
expected_rank = 8
|
||||
|
||||
if newscore != expected_score:
|
||||
raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'')
|
||||
if newmedal != expected_medal:
|
||||
raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'')
|
||||
if newrank != expected_rank:
|
||||
raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'')
|
||||
|
||||
# Sleep so we don't end up putting in score history on the same second
|
||||
time.sleep(1)
|
||||
else:
|
||||
print("Skipping score checks for existing card")
|
||||
|
||||
# Verify paseli handling
|
||||
if paseli_enabled:
|
||||
print("PASELI enabled for this PCBID, executing PASELI checks")
|
||||
else:
|
||||
print("PASELI disabled for this PCBID, skipping PASELI checks")
|
||||
return
|
||||
|
||||
sessid, balance = self.verify_eacoin_checkin(card)
|
||||
if balance == 0:
|
||||
print("Skipping PASELI consume check because card has 0 balance")
|
||||
else:
|
||||
self.verify_eacoin_consume(sessid, balance, random.randint(0, balance))
|
||||
self.verify_eacoin_checkout(sessid)
|
@ -122,6 +122,7 @@ class VersionConstants:
|
||||
POPN_MUSIC_ECLALE: Final[int] = 23
|
||||
POPN_MUSIC_USANEKO: Final[int] = 24
|
||||
POPN_MUSIC_PEACE: Final[int] = 25
|
||||
POPN_MUSIC_KAIMEI_RIDDLES: Final[int] = 26
|
||||
|
||||
REFLEC_BEAT: Final[int] = 1
|
||||
REFLEC_BEAT_LIMELIGHT: Final[int] = 2
|
||||
|
@ -372,6 +372,7 @@ class ImportPopn(ImportBase):
|
||||
'23': VersionConstants.POPN_MUSIC_ECLALE,
|
||||
'24': VersionConstants.POPN_MUSIC_USANEKO,
|
||||
'25': VersionConstants.POPN_MUSIC_PEACE,
|
||||
'26': VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES,
|
||||
}.get(version, -1)
|
||||
|
||||
if actual_version == VersionConstants.POPN_MUSIC_TUNE_STREET:
|
||||
@ -383,7 +384,7 @@ class ImportPopn(ImportBase):
|
||||
# Newer pop'n has charts for easy, normal, hyper, another
|
||||
self.charts = [0, 1, 2, 3]
|
||||
else:
|
||||
raise Exception("Unsupported Pop'n Music version, expected one of the following: 19, 20, 21, 22, 23, 24, 25!")
|
||||
raise Exception("Unsupported Pop'n Music version, expected one of the following: 19, 20, 21, 22, 23, 24, 25, 26!")
|
||||
|
||||
super().__init__(config, GameConstants.POPN_MUSIC, actual_version, no_combine, update)
|
||||
|
||||
@ -1056,6 +1057,104 @@ class ImportPopn(ImportBase):
|
||||
'I'
|
||||
)
|
||||
|
||||
# Decoding function for chart masks
|
||||
def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]:
|
||||
return (
|
||||
mask & 0x0080000 > 0, # Easy chart bit
|
||||
True, # Always a normal chart
|
||||
mask & 0x1000000 > 0, # Hyper chart bit
|
||||
mask & 0x2000000 > 0, # Ex chart bit
|
||||
True, # Always a battle normal chart
|
||||
mask & 0x4000000 > 0, # Battle hyper chart bit
|
||||
)
|
||||
|
||||
elif self.version == VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES:
|
||||
# Based on M39:J:A:A:2022061300
|
||||
|
||||
# Normal offset for music DB, size
|
||||
offset = 0x2DEA68
|
||||
step = 172
|
||||
length = 2019
|
||||
|
||||
# Offset and step of file DB
|
||||
file_offset = 0x2CDB00
|
||||
file_step = 32
|
||||
|
||||
# Standard lookups
|
||||
genre_offset = 0
|
||||
title_offset = 1
|
||||
artist_offset = 2
|
||||
comment_offset = 3
|
||||
english_title_offset = 4
|
||||
english_artist_offset = 5
|
||||
extended_genre_offset = -1
|
||||
charts_offset = 8
|
||||
folder_offset = 9
|
||||
|
||||
# Offsets for normal chart difficulties
|
||||
easy_offset = 12
|
||||
normal_offset = 13
|
||||
hyper_offset = 14
|
||||
ex_offset = 15
|
||||
|
||||
# Offsets for battle chart difficulties
|
||||
battle_normal_offset = 16
|
||||
battle_hyper_offset = 17
|
||||
|
||||
# Offsets into which offset to seek to for file lookups
|
||||
easy_file_offset = 18
|
||||
normal_file_offset = 19
|
||||
hyper_file_offset = 20
|
||||
ex_file_offset = 21
|
||||
battle_normal_file_offset = 22
|
||||
battle_hyper_file_offset = 23
|
||||
|
||||
packedfmt = (
|
||||
'<'
|
||||
'I' # Genre
|
||||
'I' # Title
|
||||
'I' # Artist
|
||||
'I' # Comment
|
||||
'I' # English Title
|
||||
'I' # English Artist
|
||||
'H' # ??
|
||||
'H' # ??
|
||||
'I' # Available charts mask
|
||||
'I' # Folder
|
||||
'I' # Event unlocks?
|
||||
'I' # Event unlocks?
|
||||
'B' # Easy difficulty
|
||||
'B' # Normal difficulty
|
||||
'B' # Hyper difficulty
|
||||
'B' # EX difficulty
|
||||
'B' # Battle normal difficulty
|
||||
'B' # Battle hyper difficulty
|
||||
'xx' # Unknown pointer
|
||||
'H' # Easy chart pointer
|
||||
'H' # Normal chart pointer
|
||||
'H' # Hyper chart pointer
|
||||
'H' # EX chart pointer
|
||||
'H' # Battle normal pointer
|
||||
'H' # Battle hyper pointer
|
||||
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
)
|
||||
|
||||
# Offsets into file DB for finding file and folder.
|
||||
file_folder_offset = 0
|
||||
file_name_offset = 1
|
||||
|
||||
filefmt = (
|
||||
'<'
|
||||
'I' # Folder
|
||||
'I' # Filename
|
||||
'I'
|
||||
'I'
|
||||
'I'
|
||||
'I'
|
||||
'I'
|
||||
'I'
|
||||
)
|
||||
|
||||
# Decoding function for chart masks
|
||||
def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]:
|
||||
return (
|
||||
|
@ -28,6 +28,7 @@ from bemani.client.popn import (
|
||||
PopnMusicEclaleClient,
|
||||
PopnMusicUsaNekoClient,
|
||||
PopnMusicPeaceClient,
|
||||
PopnMusicKaimeiClient,
|
||||
)
|
||||
from bemani.client.ddr import (
|
||||
DDRX2Client,
|
||||
@ -102,6 +103,12 @@ def get_client(proto: ClientProtocol, pcbid: str, game: str, config: Dict[str, A
|
||||
pcbid,
|
||||
config,
|
||||
)
|
||||
if game == 'pnm-kaimei':
|
||||
return PopnMusicKaimeiClient(
|
||||
proto,
|
||||
pcbid,
|
||||
config,
|
||||
)
|
||||
if game == 'jubeat-saucer':
|
||||
return JubeatSaucerClient(
|
||||
proto,
|
||||
@ -342,6 +349,12 @@ def mainloop(address: str, port: int, configfile: str, action: str, game: str, c
|
||||
'old_profile_model': "M39:J:B:A",
|
||||
'avs': "2.15.8 r6631",
|
||||
},
|
||||
'pnm-kaimei': {
|
||||
'name': "Pop'n Music Kaimei riddles",
|
||||
'model': "M39:J:B:A:2022061300",
|
||||
'old_profile_model': "M39:J:B:A",
|
||||
'avs': "2.15.8 r6631",
|
||||
},
|
||||
'jubeat-saucer': {
|
||||
'name': "Jubeat Saucer",
|
||||
'model': "L44:J:A:A:2014012802",
|
||||
@ -560,6 +573,7 @@ def main() -> None:
|
||||
'pnm-23': 'pnm-eclale',
|
||||
'pnm-24': 'pnm-usaneko',
|
||||
'pnm-25': 'pnm-peace',
|
||||
'pnm-26': 'pnm-kaimei',
|
||||
'iidx-20': 'iidx-tricoro',
|
||||
'iidx-21': 'iidx-spada',
|
||||
'iidx-22': 'iidx-pendual',
|
||||
|
@ -13,6 +13,7 @@ set -e
|
||||
./read --series pnm --version 23 "$@"
|
||||
./read --series pnm --version 24 "$@"
|
||||
./read --series pnm --version 25 "$@"
|
||||
./read --series pnm --version 26 "$@"
|
||||
|
||||
# Init Jubeat
|
||||
./read --series jubeat --version saucer "$@"
|
||||
|
@ -10,6 +10,7 @@ declare -a arr=(
|
||||
"pnm-23"
|
||||
"pnm-24"
|
||||
"pnm-25"
|
||||
"pnm-26"
|
||||
"iidx-20"
|
||||
"iidx-21"
|
||||
"iidx-22"
|
||||
|
Loading…
Reference in New Issue
Block a user