Merge remote-tracking branch 'upstream/trunk' into trunk
This commit is contained in:
commit
ede06b3e68
@ -1,6 +1,7 @@
|
||||
from abc import ABC
|
||||
from abc import ABC, abstractmethod
|
||||
import traceback
|
||||
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type
|
||||
from typing_extensions import Final
|
||||
|
||||
from bemani.common import Model, ValidatedDict, Profile, PlayStatistics, GameConstants, Time
|
||||
from bemani.data import Config, Data, Machine, UserID, RemoteUser
|
||||
@ -14,14 +15,14 @@ class Status:
|
||||
"""
|
||||
List of statuses we return to the game for various reasons.
|
||||
"""
|
||||
SUCCESS = 0
|
||||
NO_PROFILE = 109
|
||||
NOT_ALLOWED = 110
|
||||
NOT_REGISTERED = 112
|
||||
INVALID_PIN = 116
|
||||
SUCCESS: Final[int] = 0
|
||||
NO_PROFILE: Final[int] = 109
|
||||
NOT_ALLOWED: Final[int] = 110
|
||||
NOT_REGISTERED: Final[int] = 112
|
||||
INVALID_PIN: Final[int] = 116
|
||||
|
||||
|
||||
class Factory:
|
||||
class Factory(ABC):
|
||||
"""
|
||||
The base class every game factory inherits from. Defines a create method
|
||||
which should return some game class which can handle packets. Game classes
|
||||
@ -29,16 +30,17 @@ class Factory:
|
||||
Dispatch will look up in order to handle calls.
|
||||
"""
|
||||
|
||||
MANAGED_CLASSES: List[Type["Base"]] = []
|
||||
MANAGED_CLASSES: List[Type["Base"]]
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def register_all(cls) -> None:
|
||||
"""
|
||||
Subclasses of this class should use this function to register themselves
|
||||
with Base, using Base.register(). Factories specify the game code that
|
||||
they support, which Base will use when routing requests.
|
||||
"""
|
||||
raise Exception('Override this in subclass!')
|
||||
raise NotImplementedError('Override this in subclass!')
|
||||
|
||||
@classmethod
|
||||
def run_scheduled_work(cls, data: Data, config: Config) -> None:
|
||||
@ -84,6 +86,7 @@ class Factory:
|
||||
yield (game.game, game.version, game.get_settings())
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional['Base']:
|
||||
"""
|
||||
Given a modelstring and an optional parent model, return an instantiated game class that can handle a packet.
|
||||
@ -102,7 +105,7 @@ class Factory:
|
||||
A subclass of Base that hopefully has a handle_<call>_request method on it, for the particular
|
||||
call that Dispatch wants to resolve, or None if we can't look up a game.
|
||||
"""
|
||||
raise Exception('Override this in subclass!')
|
||||
raise NotImplementedError('Override this in subclass!')
|
||||
|
||||
|
||||
class Base(ABC):
|
||||
|
@ -18,6 +18,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
|
||||
game = GameConstants.POPN_MUSIC
|
||||
|
||||
# Play medals, as saved into/loaded from the DB
|
||||
PLAY_MEDAL_NO_PLAY = DBConstants.POPN_MUSIC_PLAY_MEDAL_NO_PLAY
|
||||
PLAY_MEDAL_CIRCLE_FAILED = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED
|
||||
PLAY_MEDAL_DIAMOND_FAILED = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED
|
||||
PLAY_MEDAL_STAR_FAILED = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED
|
||||
@ -188,6 +189,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
|
||||
"""
|
||||
# Range check medals
|
||||
if medal not in [
|
||||
self.PLAY_MEDAL_NO_PLAY,
|
||||
self.PLAY_MEDAL_CIRCLE_FAILED,
|
||||
self.PLAY_MEDAL_DIAMOND_FAILED,
|
||||
self.PLAY_MEDAL_STAR_FAILED,
|
||||
|
@ -276,6 +276,8 @@ class PopnMusicEclale(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
points = score.points
|
||||
medal = score.data.get_int('medal')
|
||||
@ -364,6 +366,8 @@ class PopnMusicEclale(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
points = score.points
|
||||
medal = score.data.get_int('medal')
|
||||
@ -458,6 +462,8 @@ class PopnMusicEclale(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
music = Node.void('music')
|
||||
root.add_child(music)
|
||||
|
@ -198,6 +198,8 @@ class PopnMusicFantasia(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score)
|
||||
hiscore_index = (score.id * 4) + {
|
||||
@ -289,6 +291,8 @@ class PopnMusicFantasia(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score)
|
||||
hiscore_index = (score.id * 4) + {
|
||||
@ -567,6 +571,8 @@ class PopnMusicFantasia(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score)
|
||||
hiscore_index = (score.id * 4) + {
|
||||
|
@ -1,12 +1,12 @@
|
||||
# vim: set fileencoding=utf-8
|
||||
import copy
|
||||
from typing import Dict, List
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from bemani.backend.popn.base import PopnMusicBase
|
||||
from bemani.backend.popn.sunnypark import PopnMusicSunnyPark
|
||||
|
||||
from bemani.backend.base import Status
|
||||
from bemani.common import Profile, VersionConstants, ID
|
||||
from bemani.common import ValidatedDict, Profile, VersionConstants, ID
|
||||
from bemani.data import UserID, Link
|
||||
from bemani.protocol import Node
|
||||
|
||||
@ -23,6 +23,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
GAME_CHART_TYPE_EX = 3
|
||||
|
||||
# Medal type, as returned from the game
|
||||
GAME_PLAY_MEDAL_NO_PLAY = 0
|
||||
GAME_PLAY_MEDAL_CIRCLE_FAILED = 1
|
||||
GAME_PLAY_MEDAL_DIAMOND_FAILED = 2
|
||||
GAME_PLAY_MEDAL_STAR_FAILED = 3
|
||||
@ -41,48 +42,101 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
def previous_version(self) -> PopnMusicBase:
|
||||
return PopnMusicSunnyPark(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': 'Story Mode',
|
||||
'tip': 'Story mode phase for all players.',
|
||||
'category': 'game_config',
|
||||
'setting': 'story_phase',
|
||||
'values': {
|
||||
0: 'Disabled',
|
||||
1: 'Phase 1',
|
||||
2: 'Phase 2',
|
||||
3: 'Phase 3',
|
||||
4: 'Phase 4',
|
||||
5: 'Phase 5',
|
||||
6: 'Phase 6',
|
||||
7: 'Phase 7',
|
||||
8: 'Phase 8',
|
||||
9: 'Phase 9',
|
||||
10: 'Phase 10',
|
||||
11: 'Phase 11',
|
||||
12: 'Phase 12',
|
||||
13: 'Phase 13',
|
||||
14: 'Phase 14',
|
||||
15: 'Phase 15',
|
||||
16: 'Phase 16',
|
||||
17: 'Phase 17',
|
||||
18: 'Phase 18',
|
||||
19: 'Phase 19',
|
||||
20: 'Phase 20',
|
||||
21: 'Phase 21',
|
||||
22: 'Phase 22',
|
||||
23: 'Phase 23',
|
||||
24: 'Phase 24',
|
||||
},
|
||||
},
|
||||
],
|
||||
'bools': [
|
||||
{
|
||||
'name': 'Force Song Unlock',
|
||||
'tip': 'Force unlock all songs.',
|
||||
'category': 'game_config',
|
||||
'setting': 'force_unlock_songs',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
def handle_info22_common_request(self, request: Node) -> Node:
|
||||
# TODO: Hook these up to config so we can change this
|
||||
game_config = self.get_game_config()
|
||||
story_phase = game_config.get_int('story_phase')
|
||||
|
||||
phases = {
|
||||
# Unknown event (0-16)
|
||||
0: 0,
|
||||
# Unknown event (0-11)
|
||||
1: 0,
|
||||
# Default song phase availability (0-16)
|
||||
0: 16,
|
||||
# Card phase (0-11)
|
||||
1: 11,
|
||||
# Pop'n Aura, max (0-11) (remove all aura requirements)
|
||||
2: 11,
|
||||
# Story (0-24)
|
||||
3: 1,
|
||||
# BEMANI ruins Discovery! (0-2)
|
||||
3: story_phase,
|
||||
# BEMANI ruins Discovery! 0 = off, 1 = active, 2 = off
|
||||
4: 0,
|
||||
# Unknown event, something to do with net taisen (0-2)
|
||||
5: 0,
|
||||
5: 2,
|
||||
# Unknown event (0-1)
|
||||
6: 0,
|
||||
6: 1,
|
||||
# Unknown event (0-1)
|
||||
7: 0,
|
||||
7: 1,
|
||||
# Unknown event (0-1)
|
||||
8: 0,
|
||||
# Unknown event (0-11)
|
||||
9: 0,
|
||||
# Unknown event (0-2)
|
||||
8: 1,
|
||||
# Course mode phase (0-11)
|
||||
9: 11,
|
||||
# Pon's Fate Purification Plan, 0 = off, 1 = active, 2 = off
|
||||
10: 0,
|
||||
# Unknown event (0-3)
|
||||
11: 0,
|
||||
11: 3,
|
||||
# Unknown event (0-1)
|
||||
12: 0,
|
||||
# Unknown event (0-2)
|
||||
13: 0,
|
||||
12: 1,
|
||||
# Appears to be unlocks for course mode including KAC stuff.
|
||||
13: 2,
|
||||
# Unknown event (0-4)
|
||||
14: 0,
|
||||
14: 4,
|
||||
# Unknown event (0-2)
|
||||
15: 0,
|
||||
15: 2,
|
||||
# Unknown event (0-2)
|
||||
16: 0,
|
||||
16: 2,
|
||||
# Unknown event (0-12)
|
||||
17: 0,
|
||||
# Unknown event (0-2)
|
||||
18: 0,
|
||||
# Unknown event (0-7)
|
||||
18: 2,
|
||||
# Bemani Summer Diary, 0 = off, 1-6 are phases, 7 = off
|
||||
19: 0,
|
||||
}
|
||||
|
||||
@ -198,6 +252,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
rivalid = links[no].other_userid
|
||||
rivalprofile = profiles[rivalid]
|
||||
scores = self.data.remote.music.get_scores(self.game, self.version, rivalid)
|
||||
achievements = self.data.local.user.get_achievements(self.game, self.version, rivalid)
|
||||
|
||||
# First, output general profile info.
|
||||
friend = Node.void('friend')
|
||||
@ -234,6 +289,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
}[score.chart]))
|
||||
music.set_attribute('score', str(points))
|
||||
music.set_attribute('clearmedal', str({
|
||||
self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY,
|
||||
self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED,
|
||||
self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED,
|
||||
self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED,
|
||||
@ -247,6 +303,27 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT,
|
||||
}[medal]))
|
||||
|
||||
for course in achievements:
|
||||
if course.type == 'course':
|
||||
total_score = course.data.get_int('total_score')
|
||||
clear_medal = course.data.get_int('clear_medal')
|
||||
clear_norma = course.data.get_int('clear_norma')
|
||||
stage1_score = course.data.get_int('stage1_score')
|
||||
stage2_score = course.data.get_int('stage2_score')
|
||||
stage3_score = course.data.get_int('stage3_score')
|
||||
stage4_score = course.data.get_int('stage4_score')
|
||||
|
||||
coursenode = Node.void('course')
|
||||
friend.add_child(coursenode)
|
||||
coursenode.set_attribute('course_id', str(course.id))
|
||||
coursenode.set_attribute('clear_medal', str(clear_medal))
|
||||
coursenode.set_attribute('clear_norma', str(clear_norma))
|
||||
coursenode.set_attribute('stage1_score', str(stage1_score))
|
||||
coursenode.set_attribute('stage2_score', str(stage2_score))
|
||||
coursenode.set_attribute('stage3_score', str(stage3_score))
|
||||
coursenode.set_attribute('stage4_score', str(stage4_score))
|
||||
coursenode.set_attribute('total_score', str(total_score))
|
||||
|
||||
return root
|
||||
|
||||
def handle_player22_conversion_request(self, request: Node) -> Node:
|
||||
@ -287,6 +364,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
'bad': request.child_value('bad')
|
||||
}
|
||||
medal = {
|
||||
self.GAME_PLAY_MEDAL_NO_PLAY: self.PLAY_MEDAL_NO_PLAY,
|
||||
self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED,
|
||||
self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED,
|
||||
self.GAME_PLAY_MEDAL_STAR_FAILED: self.PLAY_MEDAL_STAR_FAILED,
|
||||
@ -302,6 +380,119 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
self.update_score(userid, songid, chart, points, medal, combo=combo, stats=stats)
|
||||
return root
|
||||
|
||||
def handle_player22_write_course_request(self, request: Node) -> Node:
|
||||
refid = request.child_value('ref_id')
|
||||
|
||||
root = Node.void('player22')
|
||||
if refid is None:
|
||||
return root
|
||||
|
||||
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
|
||||
if userid is None:
|
||||
return root
|
||||
|
||||
# Grab info that we want to update
|
||||
total_score = request.child_value('total_score') or 0
|
||||
course_id = request.child_value('course_id')
|
||||
if course_id is not None:
|
||||
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
|
||||
pref = request.child_value('pref') or 51
|
||||
profile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0)
|
||||
|
||||
course = self.data.local.user.get_achievement(
|
||||
self.game,
|
||||
self.version,
|
||||
userid,
|
||||
course_id,
|
||||
"course",
|
||||
) or ValidatedDict({})
|
||||
|
||||
stage_scores: Dict[int, int] = {}
|
||||
for child in request.children:
|
||||
if child.name != 'stage':
|
||||
continue
|
||||
|
||||
stage = child.child_value('stage')
|
||||
score = child.child_value('score')
|
||||
|
||||
if isinstance(stage, int) and isinstance(score, int):
|
||||
stage_scores[stage] = score
|
||||
|
||||
# Update the scores if this was a new high score.
|
||||
if total_score > course.get_int('total_score'):
|
||||
course.replace_int('total_score', total_score)
|
||||
course.replace_int('stage1_score', stage_scores.get(0, 0))
|
||||
course.replace_int('stage2_score', stage_scores.get(1, 0))
|
||||
course.replace_int('stage3_score', stage_scores.get(2, 0))
|
||||
course.replace_int('stage4_score', stage_scores.get(3, 0))
|
||||
|
||||
# Only update ojamas used if this was an updated score.
|
||||
course.replace_int('clear_norma', request.child_value('clear_norma'))
|
||||
|
||||
# Only udpate what location and prefecture this was scored in
|
||||
# if we updated our score.
|
||||
course.replace_int('pref', pref)
|
||||
course.replace_int('lid', machine.arcade)
|
||||
|
||||
# Update medal and combo values.
|
||||
course.replace_int('max_combo', max(course.get_int('max_combo'), request.child_value('max_combo')))
|
||||
course.replace_int('clear_medal', max(course.get_int('clear_medal'), request.child_value('clear_medal')))
|
||||
|
||||
# Add one to the play count for this course.
|
||||
course.increment_int('play_cnt')
|
||||
|
||||
self.data.local.user.put_achievement(
|
||||
self.game,
|
||||
self.version,
|
||||
userid,
|
||||
course_id,
|
||||
"course",
|
||||
course,
|
||||
)
|
||||
|
||||
# Now, attempt to calculate ranking for this user for this run.
|
||||
all_courses = self.data.local.user.get_all_achievements(self.game, self.version, course_id, "course")
|
||||
global_ranking = sorted(all_courses, key=lambda entry: entry[1].data.get_int('total_score'), reverse=True)
|
||||
pref_ranking = [c for c in global_ranking if c[1].data.get_int('pref') == pref]
|
||||
local_ranking = [c for c in global_ranking if c[1].data.get_int('lid') == machine.arcade]
|
||||
|
||||
global_rank = len(global_ranking)
|
||||
pref_rank = len(pref_ranking)
|
||||
local_rank = len(local_ranking)
|
||||
|
||||
for i, rank in enumerate(global_ranking):
|
||||
if userid == rank[0]:
|
||||
global_rank = i + 1
|
||||
break
|
||||
for i, rank in enumerate(pref_ranking):
|
||||
if userid == rank[0]:
|
||||
pref_rank = i + 1
|
||||
break
|
||||
for i, rank in enumerate(local_ranking):
|
||||
if userid == rank[0]:
|
||||
local_rank = i + 1
|
||||
break
|
||||
|
||||
# Now, return it all.
|
||||
for rank_type, personal_rank, count in [
|
||||
('all_ranking', global_rank, len(global_ranking)),
|
||||
('pref_ranking', pref_rank, len(pref_ranking)),
|
||||
('location_ranking', local_rank, len(local_ranking)),
|
||||
]:
|
||||
ranknode = Node.void(rank_type)
|
||||
root.add_child(ranknode)
|
||||
ranknode.add_child(Node.string('name', profile.get_str('name', 'なし')))
|
||||
ranknode.add_child(Node.s16('chara_num', profile.get_int('chara', -1)))
|
||||
ranknode.add_child(Node.s32('stage1_score', stage_scores.get(0, 0)))
|
||||
ranknode.add_child(Node.s32('stage2_score', stage_scores.get(1, 0)))
|
||||
ranknode.add_child(Node.s32('stage3_score', stage_scores.get(2, 0)))
|
||||
ranknode.add_child(Node.s32('stage4_score', stage_scores.get(3, 0)))
|
||||
ranknode.add_child(Node.s32('total_score', total_score))
|
||||
ranknode.add_child(Node.s16('player_count', count))
|
||||
ranknode.add_child(Node.s16('player_rank', personal_rank))
|
||||
|
||||
return root
|
||||
|
||||
def format_profile(self, userid: UserID, profile: Profile) -> Node:
|
||||
root = Node.void('player22')
|
||||
|
||||
@ -380,6 +571,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
music.add_child(Node.s16('cnt', score.plays))
|
||||
music.add_child(Node.s32('score', points))
|
||||
music.add_child(Node.u8('clear_type', {
|
||||
self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY,
|
||||
self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED,
|
||||
self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED,
|
||||
self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED,
|
||||
@ -474,6 +666,17 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
customize.add_child(Node.u16('comment_1', customize_dict.get_int('comment_1')))
|
||||
customize.add_child(Node.u16('comment_2', customize_dict.get_int('comment_2')))
|
||||
|
||||
game_config = self.get_game_config()
|
||||
if game_config.get_bool('force_unlock_songs'):
|
||||
songs = self.data.local.music.get_all_songs(self.game, self.version)
|
||||
for song in songs:
|
||||
item = Node.void('item')
|
||||
root.add_child(item)
|
||||
item.add_child(Node.u8('type', 0))
|
||||
item.add_child(Node.u16('id', song.id))
|
||||
item.add_child(Node.u16('param', 15))
|
||||
item.add_child(Node.bool('is_new', False))
|
||||
|
||||
# Set up achievements
|
||||
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
|
||||
for achievement in achievements:
|
||||
@ -481,6 +684,20 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
itemtype = achievement.data.get_int('type')
|
||||
param = achievement.data.get_int('param')
|
||||
|
||||
# Maximum for each type is as follows:
|
||||
# 0, 1423 - These are song unlocks as far as I can tell, matches Eclale/UsaNeko.
|
||||
# 1, 2040
|
||||
# 2, 510
|
||||
# 3, 173
|
||||
# 4, 40
|
||||
# 5, 24
|
||||
# 6, 24
|
||||
# 7, 4158
|
||||
|
||||
if game_config.get_bool('force_unlock_songs') and itemtype == 0:
|
||||
# We already sent song unlocks in the force unlock section above.
|
||||
continue
|
||||
|
||||
item = Node.void('item')
|
||||
root.add_child(item)
|
||||
item.add_child(Node.u8('type', itemtype))
|
||||
@ -518,6 +735,34 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
story.add_child(Node.bool('is_cleared', cleared))
|
||||
story.add_child(Node.u32('clear_chapter', clear_chapter))
|
||||
|
||||
elif achievement.type == 'course':
|
||||
total_score = achievement.data.get_int('total_score')
|
||||
max_combo = achievement.data.get_int('max_combo')
|
||||
play_cnt = achievement.data.get_int('play_cnt')
|
||||
clear_medal = achievement.data.get_int('clear_medal')
|
||||
clear_norma = achievement.data.get_int('clear_norma')
|
||||
stage1_score = achievement.data.get_int('stage1_score')
|
||||
stage2_score = achievement.data.get_int('stage2_score')
|
||||
stage3_score = achievement.data.get_int('stage3_score')
|
||||
stage4_score = achievement.data.get_int('stage4_score')
|
||||
|
||||
course = Node.void('course')
|
||||
root.add_child(course)
|
||||
course.add_child(Node.s16('course_id', achievement.id))
|
||||
course.add_child(Node.u8('clear_medal', clear_medal))
|
||||
course.add_child(Node.u8('clear_norma', clear_norma))
|
||||
course.add_child(Node.s32('stage1_score', stage1_score))
|
||||
course.add_child(Node.s32('stage2_score', stage2_score))
|
||||
course.add_child(Node.s32('stage3_score', stage3_score))
|
||||
course.add_child(Node.s32('stage4_score', stage4_score))
|
||||
course.add_child(Node.s32('total_score', total_score))
|
||||
course.add_child(Node.s16('max_cmbo', max_combo)) # Yes, it is misspelled.
|
||||
course.add_child(Node.s16('play_cnt', play_cnt))
|
||||
course.add_child(Node.s16('all_rank', 1)) # Unclear what this does.
|
||||
|
||||
# There are also course_rank nodes, but it doesn't appear they get displayed
|
||||
# to the user anywhere.
|
||||
|
||||
return root
|
||||
|
||||
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
|
||||
@ -584,6 +829,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
self.update_play_statistics(userid)
|
||||
|
||||
# Extract achievements
|
||||
game_config = self.get_game_config()
|
||||
for node in request.children:
|
||||
if node.name == 'item':
|
||||
if not node.child_value('is_new'):
|
||||
@ -594,6 +840,10 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
itemtype = node.child_value('type')
|
||||
param = node.child_value('param')
|
||||
|
||||
if game_config.get_bool('force_unlock_songs') and itemtype == 0:
|
||||
# If we enabled force song unlocks, don't save songs to the profile.
|
||||
continue
|
||||
|
||||
self.data.local.user.put_achievement(
|
||||
self.game,
|
||||
self.version,
|
||||
@ -698,6 +948,7 @@ class PopnMusicLapistoria(PopnMusicBase):
|
||||
music.add_child(Node.u8('clear_type', 0))
|
||||
music.add_child(Node.s32('old_score', points))
|
||||
music.add_child(Node.u8('old_clear_type', {
|
||||
self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY,
|
||||
self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED,
|
||||
self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED,
|
||||
self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED,
|
||||
|
@ -175,6 +175,8 @@ class PopnMusicSunnyPark(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score)
|
||||
hiscore_index = (score.id * 4) + {
|
||||
@ -348,6 +350,8 @@ class PopnMusicSunnyPark(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score)
|
||||
|
||||
@ -653,6 +657,8 @@ class PopnMusicSunnyPark(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score)
|
||||
hiscore_index = (score.id * 4) + {
|
||||
|
@ -238,6 +238,8 @@ class PopnMusicTuneStreet(PopnMusicBase):
|
||||
self.CHART_TYPE_EASY,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
flags = self.__format_flags_for_score(score)
|
||||
|
||||
|
@ -167,7 +167,7 @@ class PopnMusicUsaNeko(PopnMusicBase):
|
||||
phase.add_child(Node.s16('event_id', phaseid))
|
||||
phase.add_child(Node.s16('phase', phase_value))
|
||||
|
||||
# Gather course informatino and course ranking for users.
|
||||
# Gather course information and course ranking for users.
|
||||
course_infos, achievements, profiles = Parallel.execute([
|
||||
lambda: self.data.local.game.get_all_time_sensitive_settings(self.game, self.version, 'course'),
|
||||
lambda: self.data.local.user.get_all_achievements(self.game, self.version),
|
||||
@ -555,6 +555,8 @@ class PopnMusicUsaNeko(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
points = score.points
|
||||
medal = score.data.get_int('medal')
|
||||
@ -617,6 +619,8 @@ class PopnMusicUsaNeko(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
music = Node.void('music')
|
||||
root.add_child(music)
|
||||
@ -757,6 +761,8 @@ class PopnMusicUsaNeko(PopnMusicBase):
|
||||
self.CHART_TYPE_EX,
|
||||
]:
|
||||
continue
|
||||
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
|
||||
continue
|
||||
|
||||
music = Node.void('music')
|
||||
root.add_child(music)
|
||||
|
@ -94,6 +94,7 @@ class PopnMusicLapistoriaClient(BaseClient):
|
||||
# Extract and return score data
|
||||
medals: Dict[int, List[int]] = {}
|
||||
scores: Dict[int, List[int]] = {}
|
||||
courses: Dict[int, Dict[str, int]] = {}
|
||||
for child in resp.child('player22').children:
|
||||
if child.name == 'music':
|
||||
songid = child.child_value('music_num')
|
||||
@ -108,7 +109,27 @@ class PopnMusicLapistoriaClient(BaseClient):
|
||||
scores[songid] = [0, 0, 0, 0]
|
||||
scores[songid][chart] = points
|
||||
|
||||
return {'medals': medals, 'scores': scores}
|
||||
if child.name == "course":
|
||||
courseid = child.child_value('course_id')
|
||||
medal = child.child_value('clear_medal')
|
||||
combo = child.child_value('max_cmbo')
|
||||
stage1 = child.child_value('stage1_score')
|
||||
stage2 = child.child_value('stage2_score')
|
||||
stage3 = child.child_value('stage3_score')
|
||||
stage4 = child.child_value('stage4_score')
|
||||
total = child.child_value('total_score')
|
||||
courses[courseid] = {
|
||||
'id': courseid,
|
||||
'medal': medal,
|
||||
'combo': combo,
|
||||
'stage1': stage1,
|
||||
'stage2': stage2,
|
||||
'stage3': stage3,
|
||||
'stage4': stage4,
|
||||
'total': total,
|
||||
}
|
||||
|
||||
return {'medals': medals, 'scores': scores, 'courses': courses}
|
||||
|
||||
else:
|
||||
raise Exception(f'Unrecognized message type \'{msg_type}\'')
|
||||
@ -164,6 +185,49 @@ class PopnMusicLapistoriaClient(BaseClient):
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player22/@status")
|
||||
|
||||
def verify_player22_write_course(self, ref_id: str, course: Dict[str, int]) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
# Construct node
|
||||
player22 = Node.void('player22')
|
||||
call.add_child(player22)
|
||||
player22.set_attribute('method', 'write_course')
|
||||
player22.add_child(Node.s16('pref', 51))
|
||||
player22.add_child(Node.string('location_id', 'JP-1'))
|
||||
player22.add_child(Node.string('ref_id', ref_id))
|
||||
player22.add_child(Node.string('data_id', ref_id))
|
||||
player22.add_child(Node.string('name', self.NAME))
|
||||
player22.add_child(Node.s16('chara_num', 1543))
|
||||
player22.add_child(Node.s32('play_id', 0))
|
||||
player22.add_child(Node.s16('course_id', course['id']))
|
||||
player22.add_child(Node.s16('stage1_music_num', 148))
|
||||
player22.add_child(Node.u8('stage1_sheet_num', 1))
|
||||
player22.add_child(Node.s16('stage2_music_num', 550))
|
||||
player22.add_child(Node.u8('stage2_sheet_num', 1))
|
||||
player22.add_child(Node.s16('stage3_music_num', 1113))
|
||||
player22.add_child(Node.u8('stage3_sheet_num', 1))
|
||||
player22.add_child(Node.s16('stage4_music_num', 341))
|
||||
player22.add_child(Node.u8('stage4_sheet_num', 1))
|
||||
player22.add_child(Node.u8('norma_type', 2))
|
||||
player22.add_child(Node.s32('norma_1_num', 5))
|
||||
player22.add_child(Node.s32('norma_2_num', 0))
|
||||
player22.add_child(Node.u8('clear_medal', course['medal']))
|
||||
player22.add_child(Node.u8('clear_norma', 2))
|
||||
player22.add_child(Node.s32('total_score', course['total']))
|
||||
player22.add_child(Node.s16('max_combo', course['combo']))
|
||||
|
||||
for stage, music in enumerate([148, 550, 1113, 341]):
|
||||
stagenode = Node.void('stage')
|
||||
player22.add_child(stagenode)
|
||||
stagenode.add_child(Node.u8('stage', stage))
|
||||
stagenode.add_child(Node.s16('music_num', music))
|
||||
stagenode.add_child(Node.u8('sheet_num', 1))
|
||||
stagenode.add_child(Node.s32('score', course[f'stage{stage + 1}']))
|
||||
|
||||
# Swap with server
|
||||
resp = self.exchange('', call)
|
||||
self.assert_path(resp, "response/player22/@status")
|
||||
|
||||
def verify_player22_new(self, ref_id: str) -> None:
|
||||
call = self.call_node()
|
||||
|
||||
@ -250,6 +314,8 @@ class PopnMusicLapistoriaClient(BaseClient):
|
||||
for i in range(4):
|
||||
if score[i] != 0:
|
||||
raise Exception('Got nonzero scores count on a new card!')
|
||||
for _ in scores['courses']:
|
||||
raise Exception('Got nonzero courses count on a new card!')
|
||||
|
||||
for phase in [1, 2]:
|
||||
if phase == 1:
|
||||
@ -338,6 +404,29 @@ class PopnMusicLapistoriaClient(BaseClient):
|
||||
|
||||
# Sleep so we don't end up putting in score history on the same second
|
||||
time.sleep(1)
|
||||
|
||||
# Write a random course so we know we can retrieve them.
|
||||
course = {
|
||||
'id': random.randint(1, 100),
|
||||
'medal': 2,
|
||||
'combo': random.randint(10, 100),
|
||||
'stage1': random.randint(70000, 100000),
|
||||
'stage2': random.randint(70000, 100000),
|
||||
'stage3': random.randint(70000, 100000),
|
||||
'stage4': random.randint(70000, 100000),
|
||||
}
|
||||
course['total'] = sum(course[f'stage{i + 1}'] for i in range(4))
|
||||
self.verify_player22_write_course(ref_id, course)
|
||||
|
||||
# Now, grab the profile one more time and see that it is there.
|
||||
scores = self.verify_player22_read(ref_id, msg_type='query')
|
||||
if len(scores['courses']) != 1:
|
||||
raise Exception("Did not get a course back after saving!")
|
||||
if course['id'] not in scores['courses']:
|
||||
raise Exception("Did not get expected course back after saving!")
|
||||
for key in ['medal', 'combo', 'stage1', 'stage2', 'stage3', 'stage4', 'total']:
|
||||
if course[key] != scores['courses'][course['id']][key]:
|
||||
raise Exception(f'Expected a {key} of \'{course[key]}\' but got \'{scores["courses"][course["id"]][key]}\'')
|
||||
else:
|
||||
print("Skipping score checks for existing card")
|
||||
|
||||
|
@ -240,6 +240,7 @@ class DBConstants:
|
||||
MUSECA_CLEAR_TYPE_CLEARED: Final[int] = 200
|
||||
MUSECA_CLEAR_TYPE_FULL_COMBO: Final[int] = 300
|
||||
|
||||
POPN_MUSIC_PLAY_MEDAL_NO_PLAY: Final[int] = 50
|
||||
POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED: Final[int] = 100
|
||||
POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED: Final[int] = 200
|
||||
POPN_MUSIC_PLAY_MEDAL_STAR_FAILED: Final[int] = 300
|
||||
|
@ -633,9 +633,9 @@ class UserData(BaseData):
|
||||
|
||||
return [UserID(result['userid']) for result in cursor.fetchall()]
|
||||
|
||||
def get_all_achievements(self, game: GameConstants, version: int) -> List[Tuple[UserID, Achievement]]:
|
||||
def get_all_achievements(self, game: GameConstants, version: int, achievementid: Optional[int] = None, achievementtype: Optional[str] = None) -> List[Tuple[UserID, Achievement]]:
|
||||
"""
|
||||
Given a game/version, find all achievements for al players.
|
||||
Given a game/version, find all achievements for all players.
|
||||
|
||||
Parameters:
|
||||
game - Enum value identifier of the game looking up the user.
|
||||
@ -649,7 +649,14 @@ class UserData(BaseData):
|
||||
"refid.userid AS userid FROM achievement, refid WHERE refid.game = :game AND "
|
||||
"refid.version = :version AND refid.refid = achievement.refid"
|
||||
)
|
||||
cursor = self.execute(sql, {'game': game.value, 'version': version})
|
||||
params: Dict[str, Any] = {'game': game.value, 'version': version}
|
||||
if achievementtype is not None:
|
||||
sql += " AND achievement.type = :type"
|
||||
params['type'] = achievementtype
|
||||
if achievementid is not None:
|
||||
sql += " AND achievement.id = :id"
|
||||
params['id'] = achievementid
|
||||
cursor = self.execute(sql, params)
|
||||
|
||||
achievements = []
|
||||
for result in cursor.fetchall():
|
||||
|
@ -15,7 +15,7 @@ from bemani.frontend.types import g
|
||||
bishi_pages = Blueprint(
|
||||
'bishi_pages',
|
||||
__name__,
|
||||
url_prefix='/bishi',
|
||||
url_prefix=f'/{GameConstants.BISHI_BASHI.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ from bemani.frontend.types import g
|
||||
ddr_pages = Blueprint(
|
||||
'ddr_pages',
|
||||
__name__,
|
||||
url_prefix='/ddr',
|
||||
url_prefix=f'/{GameConstants.DDR.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ from bemani.frontend.types import g
|
||||
iidx_pages = Blueprint(
|
||||
'iidx_pages',
|
||||
__name__,
|
||||
url_prefix='/iidx',
|
||||
url_prefix=f'/{GameConstants.IIDX.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ from bemani.frontend.types import g
|
||||
jubeat_pages = Blueprint(
|
||||
'jubeat_pages',
|
||||
__name__,
|
||||
url_prefix='/jubeat',
|
||||
url_prefix=f'/{GameConstants.JUBEAT.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ from bemani.frontend.types import g
|
||||
museca_pages = Blueprint(
|
||||
'museca_pages',
|
||||
__name__,
|
||||
url_prefix='/museca',
|
||||
url_prefix=f'/{GameConstants.MUSECA.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ from bemani.frontend.types import g
|
||||
popn_pages = Blueprint(
|
||||
'popn_pages',
|
||||
__name__,
|
||||
url_prefix='/pnm',
|
||||
url_prefix=f'/{GameConstants.POPN_MUSIC.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -26,7 +26,7 @@ class PopnMusicFrontend(FrontendBase):
|
||||
VersionConstants.POPN_MUSIC_TUNE_STREET: 0,
|
||||
VersionConstants.POPN_MUSIC_FANTASIA: 2,
|
||||
VersionConstants.POPN_MUSIC_SUNNY_PARK: 2,
|
||||
VersionConstants.POPN_MUSIC_LAPISTORIA: 4,
|
||||
VersionConstants.POPN_MUSIC_LAPISTORIA: 2,
|
||||
VersionConstants.POPN_MUSIC_ECLALE: 4,
|
||||
VersionConstants.POPN_MUSIC_USANEKO: 4,
|
||||
}
|
||||
@ -40,6 +40,7 @@ class PopnMusicFrontend(FrontendBase):
|
||||
formatted_score['combo'] = score.data.get_int('combo', -1)
|
||||
formatted_score['medal'] = score.data.get_int('medal')
|
||||
formatted_score['status'] = {
|
||||
PopnMusicBase.PLAY_MEDAL_NO_PLAY: "No Play",
|
||||
PopnMusicBase.PLAY_MEDAL_CIRCLE_FAILED: "○ Failed",
|
||||
PopnMusicBase.PLAY_MEDAL_DIAMOND_FAILED: "◇ Failed",
|
||||
PopnMusicBase.PLAY_MEDAL_STAR_FAILED: "☆ Failed",
|
||||
|
@ -15,7 +15,7 @@ from bemani.frontend.types import g
|
||||
reflec_pages = Blueprint(
|
||||
'reflec_pages',
|
||||
__name__,
|
||||
url_prefix='/reflec',
|
||||
url_prefix=f'/{GameConstants.REFLEC_BEAT.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ from bemani.frontend.types import g
|
||||
sdvx_pages = Blueprint(
|
||||
'sdvx_pages',
|
||||
__name__,
|
||||
url_prefix='/sdvx',
|
||||
url_prefix=f'/{GameConstants.SDVX.value}',
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user