1
0
mirror of synced 2025-01-18 14:14:03 +01:00

Format code with black, include wrapper script to handle that, update linter checks to accomodate, fix a few miscelaneous type errors.

This commit is contained in:
Jennifer Taylor 2022-10-15 18:56:30 +00:00
parent d6cddebe88
commit 25e162042a
271 changed files with 68554 additions and 52909 deletions

View File

@ -116,6 +116,13 @@ bring your production DB up to sync with the code you are deploying. Run it like
`./dbutils --help` to see all options. The config file that this works on is the same
that is given to "api", "services" and "frontend".
## formatfiles
A simple wrapper frontend to black, the formatter used on this project. Running this will
auto-format all of the python code that might need formatting, leaving the rest out. When
submitting pull requests make sure to run this so that your code conforms to the style
used by this project!
## frontend
Development version of a frontend server allowing for account and server administration
@ -722,7 +729,8 @@ up your MySQL instance, see the `examples/` directory.
Contributions are welcome! Before submitting a pull request, ensure that your code
is type-hint clean by running `./verifytyping` and ensure that it hasn't broken basic
libraries with `./verifylibs`. Make sure that it is also lint-clean with `./verifylint`.
If you are changing code related to a particular game, it is nice to include a
You should also make sure its formatted correctly by running `./formatfiles`.
If you are changing code related to a particular game, it is required to include a
verification in the form of a game traffic emulator, so that basic functionality can
be verified. To ensure you haven't broken another game with your changes, its recommended
to run the traffic generator against your code with various games. For convenience, you

View File

@ -6,22 +6,25 @@ from flask import Flask, abort, request, Response
from functools import wraps
from bemani.api.exceptions import APIException
from bemani.api.objects import RecordsObject, ProfileObject, StatisticsObject, CatalogObject
from bemani.api.objects import (
RecordsObject,
ProfileObject,
StatisticsObject,
CatalogObject,
)
from bemani.api.types import g
from bemani.common import GameConstants, APIConstants, VersionConstants
from bemani.data import Config, Data
app = Flask(
__name__
)
app = Flask(__name__)
config = Config()
SUPPORTED_VERSIONS: List[str] = ['v1']
SUPPORTED_VERSIONS: List[str] = ["v1"]
def jsonify_response(data: Dict[str, Any], code: int=200) -> Response:
def jsonify_response(data: Dict[str, Any], code: int = 200) -> Response:
return Response(
json.dumps(data).encode('utf8'),
json.dumps(data).encode("utf8"),
content_type="application/json; charset=utf-8",
status=code,
)
@ -35,15 +38,15 @@ def before_request() -> None:
g.data = Data(config)
g.authorized = False
authkey = request.headers.get('Authorization')
authkey = request.headers.get("Authorization")
if authkey is not None:
try:
authtype, authtoken = authkey.split(' ', 1)
authtype, authtoken = authkey.split(" ", 1)
except ValueError:
authtype = None
authtoken = None
if authtype.lower() == 'token':
if authtype.lower() == "token":
g.authorized = g.data.local.api.validate_client(authtoken)
@ -59,7 +62,7 @@ def after_request(response: Response) -> Response:
@app.teardown_request
def teardown_request(exception: Any) -> None:
data = getattr(g, 'data', None)
data = getattr(g, "data", None)
if data is not None:
data.close()
@ -69,11 +72,12 @@ def authrequired(func: Callable) -> Callable:
def decoratedfunction(*args: Any, **kwargs: Any) -> Response:
if not g.authorized:
return jsonify_response(
{'error': 'Unauthorized client!'},
{"error": "Unauthorized client!"},
401,
)
else:
return func(*args, **kwargs)
return decoratedfunction
@ -81,27 +85,30 @@ def jsonify(func: Callable) -> Callable:
@wraps(func)
def decoratedfunction(*args: Any, **kwargs: Any) -> Response:
return jsonify_response(func(*args, **kwargs))
return decoratedfunction
@app.errorhandler(Exception)
def server_exception(exception: Any) -> Response:
stack = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))
stack = "".join(
traceback.format_exception(type(exception), exception, exception.__traceback__)
)
print(stack)
try:
g.data.local.network.put_event(
'exception',
"exception",
{
'service': 'api',
'request': request.url,
'traceback': stack,
"service": "api",
"request": request.url,
"traceback": stack,
},
)
except Exception:
pass
return jsonify_response(
{'error': 'Exception occured while processing request.'},
{"error": "Exception occured while processing request."},
500,
)
@ -109,7 +116,7 @@ def server_exception(exception: Any) -> Response:
@app.errorhandler(APIException)
def api_exception(exception: Any) -> Response:
return jsonify_response(
{'error': exception.message},
{"error": exception.message},
exception.code,
)
@ -117,7 +124,7 @@ def api_exception(exception: Any) -> Response:
@app.errorhandler(500)
def server_error(error: Any) -> Response:
return jsonify_response(
{'error': 'Exception occured while processing request.'},
{"error": "Exception occured while processing request."},
500,
)
@ -125,7 +132,7 @@ def server_error(error: Any) -> Response:
@app.errorhandler(501)
def protocol_error(error: Any) -> Response:
return jsonify_response(
{'error': 'Unsupported protocol version in request.'},
{"error": "Unsupported protocol version in request."},
501,
)
@ -133,7 +140,7 @@ def protocol_error(error: Any) -> Response:
@app.errorhandler(400)
def bad_json(error: Any) -> Response:
return jsonify_response(
{'error': 'Request JSON could not be decoded.'},
{"error": "Request JSON could not be decoded."},
500,
)
@ -141,7 +148,7 @@ def bad_json(error: Any) -> Response:
@app.errorhandler(404)
def unrecognized_object(error: Any) -> Response:
return jsonify_response(
{'error': 'Unrecognized request game/version or object.'},
{"error": "Unrecognized request game/version or object."},
404,
)
@ -149,50 +156,50 @@ def unrecognized_object(error: Any) -> Response:
@app.errorhandler(405)
def invalid_request(error: Any) -> Response:
return jsonify_response(
{'error': 'Invalid request URI or method.'},
{"error": "Invalid request URI or method."},
405,
)
@app.route('/<path:path>', methods=['GET', 'POST'])
@app.route("/<path:path>", methods=["GET", "POST"])
@authrequired
def catch_all(path: str) -> Response:
abort(405)
@app.route('/', methods=['GET', 'POST'])
@app.route("/", methods=["GET", "POST"])
@authrequired
@jsonify
def info() -> Dict[str, Any]:
requestdata = request.get_json()
if requestdata is None:
raise APIException('Request JSON could not be decoded.')
raise APIException("Request JSON could not be decoded.")
if requestdata:
raise APIException('Unrecognized parameters for request.')
raise APIException("Unrecognized parameters for request.")
return {
'versions': SUPPORTED_VERSIONS,
'name': g.config.name,
'email': g.config.email,
"versions": SUPPORTED_VERSIONS,
"name": g.config.name,
"email": g.config.email,
}
@app.route('/<protoversion>/<requestgame>/<requestversion>', methods=['GET', 'POST'])
@app.route("/<protoversion>/<requestgame>/<requestversion>", methods=["GET", "POST"])
@authrequired
@jsonify
def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str, Any]:
requestdata = request.get_json()
for expected in ['type', 'ids', 'objects']:
for expected in ["type", "ids", "objects"]:
if expected not in requestdata:
raise APIException('Missing parameters for request.')
raise APIException("Missing parameters for request.")
for param in requestdata:
if param not in ['type', 'ids', 'objects', 'since', 'until']:
raise APIException('Unrecognized parameters for request.')
if param not in ["type", "ids", "objects", "since", "until"]:
raise APIException("Unrecognized parameters for request.")
args = copy.deepcopy(requestdata)
del args['type']
del args['ids']
del args['objects']
del args["type"]
del args["ids"]
del args["objects"]
if protoversion not in SUPPORTED_VERSIONS:
# Don't know about this protocol version
@ -201,13 +208,13 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str
# Figure out what games we support based on config, and map those.
gamemapping = {}
for (gameid, constant) in [
('ddr', GameConstants.DDR),
('iidx', GameConstants.IIDX),
('jubeat', GameConstants.JUBEAT),
('museca', GameConstants.MUSECA),
('popnmusic', GameConstants.POPN_MUSIC),
('reflecbeat', GameConstants.REFLEC_BEAT),
('soundvoltex', GameConstants.SDVX),
("ddr", GameConstants.DDR),
("iidx", GameConstants.IIDX),
("jubeat", GameConstants.JUBEAT),
("museca", GameConstants.MUSECA),
("popnmusic", GameConstants.POPN_MUSIC),
("reflecbeat", GameConstants.REFLEC_BEAT),
("soundvoltex", GameConstants.SDVX),
]:
if constant in g.config.support:
gamemapping[gameid] = constant
@ -216,73 +223,77 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str
# Don't support this game!
abort(404)
if requestversion[0] == 'o':
if requestversion[0] == "o":
omnimix = True
requestversion = requestversion[1:]
else:
omnimix = False
version = {
GameConstants.DDR: {
'12': VersionConstants.DDR_X2,
'13': VersionConstants.DDR_X3_VS_2NDMIX,
'14': VersionConstants.DDR_2013,
'15': VersionConstants.DDR_2014,
'16': VersionConstants.DDR_ACE,
},
GameConstants.IIDX: {
'20': VersionConstants.IIDX_TRICORO,
'21': VersionConstants.IIDX_SPADA,
'22': VersionConstants.IIDX_PENDUAL,
'23': VersionConstants.IIDX_COPULA,
'24': VersionConstants.IIDX_SINOBUZ,
'25': VersionConstants.IIDX_CANNON_BALLERS,
'26': VersionConstants.IIDX_ROOTAGE,
},
GameConstants.JUBEAT: {
'5': VersionConstants.JUBEAT_SAUCER,
'5a': VersionConstants.JUBEAT_SAUCER_FULFILL,
'6': VersionConstants.JUBEAT_PROP,
'7': VersionConstants.JUBEAT_QUBELL,
'8': VersionConstants.JUBEAT_CLAN,
'9': VersionConstants.JUBEAT_FESTO,
},
GameConstants.MUSECA: {
'1': VersionConstants.MUSECA,
'1p': VersionConstants.MUSECA_1_PLUS,
},
GameConstants.POPN_MUSIC: {
'19': VersionConstants.POPN_MUSIC_TUNE_STREET,
'20': VersionConstants.POPN_MUSIC_FANTASIA,
'21': VersionConstants.POPN_MUSIC_SUNNY_PARK,
'22': VersionConstants.POPN_MUSIC_LAPISTORIA,
'23': VersionConstants.POPN_MUSIC_ECLALE,
'24': VersionConstants.POPN_MUSIC_USANEKO,
},
GameConstants.REFLEC_BEAT: {
'1': VersionConstants.REFLEC_BEAT,
'2': VersionConstants.REFLEC_BEAT_LIMELIGHT,
# We don't support non-final COLETTE, so just return scores for
# final colette to any network that asks.
'3w': VersionConstants.REFLEC_BEAT_COLETTE,
'3sp': VersionConstants.REFLEC_BEAT_COLETTE,
'3su': VersionConstants.REFLEC_BEAT_COLETTE,
'3a': VersionConstants.REFLEC_BEAT_COLETTE,
'3as': VersionConstants.REFLEC_BEAT_COLETTE,
# We don't support groovin'!!, so just return upper scores.
'4': VersionConstants.REFLEC_BEAT_GROOVIN,
'4u': VersionConstants.REFLEC_BEAT_GROOVIN,
'5': VersionConstants.REFLEC_BEAT_VOLZZA,
'5a': VersionConstants.REFLEC_BEAT_VOLZZA_2,
'6': VersionConstants.REFLEC_BEAT_REFLESIA,
},
GameConstants.SDVX: {
'1': VersionConstants.SDVX_BOOTH,
'2': VersionConstants.SDVX_INFINITE_INFECTION,
'3': VersionConstants.SDVX_GRAVITY_WARS,
'4': VersionConstants.SDVX_HEAVENLY_HAVEN,
},
}.get(game, {}).get(requestversion)
version = (
{
GameConstants.DDR: {
"12": VersionConstants.DDR_X2,
"13": VersionConstants.DDR_X3_VS_2NDMIX,
"14": VersionConstants.DDR_2013,
"15": VersionConstants.DDR_2014,
"16": VersionConstants.DDR_ACE,
},
GameConstants.IIDX: {
"20": VersionConstants.IIDX_TRICORO,
"21": VersionConstants.IIDX_SPADA,
"22": VersionConstants.IIDX_PENDUAL,
"23": VersionConstants.IIDX_COPULA,
"24": VersionConstants.IIDX_SINOBUZ,
"25": VersionConstants.IIDX_CANNON_BALLERS,
"26": VersionConstants.IIDX_ROOTAGE,
},
GameConstants.JUBEAT: {
"5": VersionConstants.JUBEAT_SAUCER,
"5a": VersionConstants.JUBEAT_SAUCER_FULFILL,
"6": VersionConstants.JUBEAT_PROP,
"7": VersionConstants.JUBEAT_QUBELL,
"8": VersionConstants.JUBEAT_CLAN,
"9": VersionConstants.JUBEAT_FESTO,
},
GameConstants.MUSECA: {
"1": VersionConstants.MUSECA,
"1p": VersionConstants.MUSECA_1_PLUS,
},
GameConstants.POPN_MUSIC: {
"19": VersionConstants.POPN_MUSIC_TUNE_STREET,
"20": VersionConstants.POPN_MUSIC_FANTASIA,
"21": VersionConstants.POPN_MUSIC_SUNNY_PARK,
"22": VersionConstants.POPN_MUSIC_LAPISTORIA,
"23": VersionConstants.POPN_MUSIC_ECLALE,
"24": VersionConstants.POPN_MUSIC_USANEKO,
},
GameConstants.REFLEC_BEAT: {
"1": VersionConstants.REFLEC_BEAT,
"2": VersionConstants.REFLEC_BEAT_LIMELIGHT,
# We don't support non-final COLETTE, so just return scores for
# final colette to any network that asks.
"3w": VersionConstants.REFLEC_BEAT_COLETTE,
"3sp": VersionConstants.REFLEC_BEAT_COLETTE,
"3su": VersionConstants.REFLEC_BEAT_COLETTE,
"3a": VersionConstants.REFLEC_BEAT_COLETTE,
"3as": VersionConstants.REFLEC_BEAT_COLETTE,
# We don't support groovin'!!, so just return upper scores.
"4": VersionConstants.REFLEC_BEAT_GROOVIN,
"4u": VersionConstants.REFLEC_BEAT_GROOVIN,
"5": VersionConstants.REFLEC_BEAT_VOLZZA,
"5a": VersionConstants.REFLEC_BEAT_VOLZZA_2,
"6": VersionConstants.REFLEC_BEAT_REFLESIA,
},
GameConstants.SDVX: {
"1": VersionConstants.SDVX_BOOTH,
"2": VersionConstants.SDVX_INFINITE_INFECTION,
"3": VersionConstants.SDVX_GRAVITY_WARS,
"4": VersionConstants.SDVX_HEAVENLY_HAVEN,
},
}
.get(game, {})
.get(requestversion)
)
if version is None:
# Don't support this version!
abort(404)
@ -290,30 +301,30 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str
# Attempt to coerce ID type. If we fail, provide the correct failure message.
idtype = None
try:
idtype = APIConstants(requestdata['type'])
idtype = APIConstants(requestdata["type"])
except ValueError:
pass
if idtype is None:
raise APIException('Invalid ID type provided!')
raise APIException("Invalid ID type provided!")
# Validate the provided IDs given the ID type above.
ids = requestdata['ids']
ids = requestdata["ids"]
if idtype == APIConstants.ID_TYPE_CARD and len(ids) == 0:
raise APIException('Invalid number of IDs given!')
raise APIException("Invalid number of IDs given!")
if idtype == APIConstants.ID_TYPE_SONG and len(ids) not in [1, 2]:
raise APIException('Invalid number of IDs given!')
raise APIException("Invalid number of IDs given!")
if idtype == APIConstants.ID_TYPE_INSTANCE and len(ids) != 3:
raise APIException('Invalid number of IDs given!')
raise APIException("Invalid number of IDs given!")
if idtype == APIConstants.ID_TYPE_SERVER and len(ids) != 0:
raise APIException('Invalid number of IDs given!')
raise APIException("Invalid number of IDs given!")
responsedata = {}
for obj in requestdata['objects']:
for obj in requestdata["objects"]:
handler = {
'records': RecordsObject,
'profile': ProfileObject,
'statistics': StatisticsObject,
'catalog': CatalogObject,
"records": RecordsObject,
"profile": ProfileObject,
"statistics": StatisticsObject,
"catalog": CatalogObject,
}.get(obj)
if handler is None:
# Don't support this object type
@ -321,7 +332,7 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str
inst = handler(g.data, game, version, omnimix)
try:
fetchmethod = getattr(inst, f'fetch_{protoversion}')
fetchmethod = getattr(inst, f"fetch_{protoversion}")
except AttributeError:
# Don't know how to handle this object for this version
abort(501)

View File

@ -1,5 +1,4 @@
class APIException(Exception):
def __init__(self, msg: str, code: int=500) -> None:
def __init__(self, msg: str, code: int = 500) -> None:
self.message = msg
self.code = code

View File

@ -14,11 +14,15 @@ class BaseObject:
various fetch versions.
"""
def __init__(self, data: Data, game: GameConstants, version: int, omnimix: bool) -> None:
def __init__(
self, data: Data, game: GameConstants, version: int, omnimix: bool
) -> None:
self.data = data
self.game = game
self.version = version
self.omnimix = omnimix
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> Any:
raise APIException('Object fetch not supported for this version!')
def fetch_v1(
self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]
) -> Any:
raise APIException("Object fetch not supported for this version!")

View File

@ -7,31 +7,30 @@ from bemani.data import Song
class CatalogObject(BaseObject):
def __format_ddr_song(self, song: Song) -> Dict[str, Any]:
groove = song.data.get_dict('groove')
groove = song.data.get_dict("groove")
return {
'editid': str(song.data.get_int('edit_id')),
'difficulty': song.data.get_int('difficulty'),
'bpm_min': song.data.get_int('bpm_min'),
'bpm_max': song.data.get_int('bpm_max'),
'category': str(song.data.get_int('category')),
'groove': {
'air': groove.get_int('air'),
'chaos': groove.get_int('chaos'),
'freeze': groove.get_int('freeze'),
'stream': groove.get_int('stream'),
'voltage': groove.get_int('voltage'),
"editid": str(song.data.get_int("edit_id")),
"difficulty": song.data.get_int("difficulty"),
"bpm_min": song.data.get_int("bpm_min"),
"bpm_max": song.data.get_int("bpm_max"),
"category": str(song.data.get_int("category")),
"groove": {
"air": groove.get_int("air"),
"chaos": groove.get_int("chaos"),
"freeze": groove.get_int("freeze"),
"stream": groove.get_int("stream"),
"voltage": groove.get_int("voltage"),
},
}
def __format_iidx_song(self, song: Song) -> Dict[str, Any]:
return {
'difficulty': song.data.get_int('difficulty'),
'bpm_min': song.data.get_int('bpm_min'),
'bpm_max': song.data.get_int('bpm_max'),
'notecount': song.data.get_int('notecount'),
'category': str(int(song.id / 1000)),
"difficulty": song.data.get_int("difficulty"),
"bpm_min": song.data.get_int("bpm_min"),
"bpm_max": song.data.get_int("bpm_max"),
"notecount": song.data.get_int("notecount"),
"category": str(int(song.id / 1000)),
}
def __format_jubeat_song(self, song: Song) -> Dict[str, Any]:
@ -46,67 +45,69 @@ class CatalogObject(BaseObject):
6: VersionConstants.JUBEAT_PROP,
7: VersionConstants.JUBEAT_QUBELL,
8: VersionConstants.JUBEAT_CLAN,
9: VersionConstants.JUBEAT_FESTO
9: VersionConstants.JUBEAT_FESTO,
}.get(int(song.id / 10000000), VersionConstants.JUBEAT)
# Map the category to the version numbers defined on BEMAPI.
categorymapping = {
VersionConstants.JUBEAT: '1',
VersionConstants.JUBEAT_RIPPLES: '2',
VersionConstants.JUBEAT_RIPPLES_APPEND: '2a',
VersionConstants.JUBEAT_KNIT: '3',
VersionConstants.JUBEAT_KNIT_APPEND: '3a',
VersionConstants.JUBEAT_COPIOUS: '4',
VersionConstants.JUBEAT_COPIOUS_APPEND: '4a',
VersionConstants.JUBEAT_SAUCER: '5',
VersionConstants.JUBEAT_SAUCER_FULFILL: '5a',
VersionConstants.JUBEAT_PROP: '6',
VersionConstants.JUBEAT_QUBELL: '7',
VersionConstants.JUBEAT_CLAN: '8',
VersionConstants.JUBEAT_FESTO: '9',
VersionConstants.JUBEAT: "1",
VersionConstants.JUBEAT_RIPPLES: "2",
VersionConstants.JUBEAT_RIPPLES_APPEND: "2a",
VersionConstants.JUBEAT_KNIT: "3",
VersionConstants.JUBEAT_KNIT_APPEND: "3a",
VersionConstants.JUBEAT_COPIOUS: "4",
VersionConstants.JUBEAT_COPIOUS_APPEND: "4a",
VersionConstants.JUBEAT_SAUCER: "5",
VersionConstants.JUBEAT_SAUCER_FULFILL: "5a",
VersionConstants.JUBEAT_PROP: "6",
VersionConstants.JUBEAT_QUBELL: "7",
VersionConstants.JUBEAT_CLAN: "8",
VersionConstants.JUBEAT_FESTO: "9",
}
return {
'difficulty': song.data.get_int('difficulty'),
'category': categorymapping.get(song.data.get_int('version', defaultcategory), '1'),
'bpm_min': song.data.get_int('bpm_min'),
'bpm_max': song.data.get_int('bpm_max'),
"difficulty": song.data.get_int("difficulty"),
"category": categorymapping.get(
song.data.get_int("version", defaultcategory), "1"
),
"bpm_min": song.data.get_int("bpm_min"),
"bpm_max": song.data.get_int("bpm_max"),
}
def __format_museca_song(self, song: Song) -> Dict[str, Any]:
return {
'difficulty': song.data.get_int('difficulty'),
'bpm_min': song.data.get_int('bpm_min'),
'bpm_max': song.data.get_int('bpm_max'),
'limited': song.data.get_int('limited'),
"difficulty": song.data.get_int("difficulty"),
"bpm_min": song.data.get_int("bpm_min"),
"bpm_max": song.data.get_int("bpm_max"),
"limited": song.data.get_int("limited"),
}
def __format_popn_song(self, song: Song) -> Dict[str, Any]:
return {
'difficulty': song.data.get_int('difficulty'),
'category': song.data.get_str('category'),
"difficulty": song.data.get_int("difficulty"),
"category": song.data.get_str("category"),
}
def __format_reflec_song(self, song: Song) -> Dict[str, Any]:
return {
'difficulty': song.data.get_int('difficulty'),
'category': str(song.data.get_int('folder')),
'musicid': song.data.get_str('chart_id'),
"difficulty": song.data.get_int("difficulty"),
"category": str(song.data.get_int("folder")),
"musicid": song.data.get_str("chart_id"),
}
def __format_sdvx_song(self, song: Song) -> Dict[str, Any]:
return {
'difficulty': song.data.get_int('difficulty'),
'bpm_min': song.data.get_int('bpm_min'),
'bpm_max': song.data.get_int('bpm_max'),
'limited': song.data.get_int('limited'),
"difficulty": song.data.get_int("difficulty"),
"bpm_min": song.data.get_int("bpm_min"),
"bpm_max": song.data.get_int("bpm_max"),
"limited": song.data.get_int("limited"),
}
def __format_song(self, song: Song) -> Dict[str, Any]:
base = {
'song': str(song.id),
'chart': str(song.chart),
'title': song.name or "",
'artist': song.artist or "",
'genre': song.genre or "",
"song": str(song.id),
"chart": str(song.chart),
"title": song.name or "",
"artist": song.artist or "",
"genre": song.genre or "",
}
if self.game == GameConstants.DDR:
@ -198,7 +199,7 @@ class CatalogObject(BaseObject):
"type": item.type[3:],
}
for item in items
if item.type in ['qp_body', 'qp_face', 'qp_hair', 'qp_hand', 'qp_head']
if item.type in ["qp_body", "qp_face", "qp_hair", "qp_hand", "qp_head"]
],
}
@ -222,17 +223,22 @@ class CatalogObject(BaseObject):
else:
return self.version
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]:
def fetch_v1(
self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]
) -> Dict[str, List[Dict[str, Any]]]:
# Verify IDs
if idtype != APIConstants.ID_TYPE_SERVER:
raise APIException(
'Unsupported ID for lookup!',
"Unsupported ID for lookup!",
405,
)
# Fetch the songs
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
if self.game == GameConstants.JUBEAT and self.version == VersionConstants.JUBEAT_CLAN:
if (
self.game == GameConstants.JUBEAT
and self.version == VersionConstants.JUBEAT_CLAN
):
# There's always a special case. We don't store all music IDs since those in
# the range of 80000301-80000347 are actually the same song, but copy-pasted
# for different prefectures and slightly different charts. So, we need to copy
@ -255,7 +261,7 @@ class CatalogObject(BaseObject):
)
songs.extend(additions)
retval = {
'songs': [self.__format_song(song) for song in songs],
"songs": [self.__format_song(song) for song in songs],
}
# Fetch any optional extras per-game, return

View File

@ -7,24 +7,23 @@ from bemani.data import UserID
class ProfileObject(BaseObject):
def __format_ddr_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
return {
'area': profile.get_int('area', -1) if exact else -1,
"area": profile.get_int("area", -1) if exact else -1,
}
def __format_iidx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
qpro = profile.get_dict('qpro')
qpro = profile.get_dict("qpro")
return {
'area': profile.get_int('pid', -1),
'qpro': {
'head': qpro.get_int('head', -1) if exact else -1,
'hair': qpro.get_int('hair', -1) if exact else -1,
'face': qpro.get_int('face', -1) if exact else -1,
'body': qpro.get_int('body', -1) if exact else -1,
'hand': qpro.get_int('hand', -1) if exact else -1,
}
"area": profile.get_int("pid", -1),
"qpro": {
"head": qpro.get_int("head", -1) if exact else -1,
"hair": qpro.get_int("hair", -1) if exact else -1,
"face": qpro.get_int("face", -1) if exact else -1,
"body": qpro.get_int("body", -1) if exact else -1,
"hand": qpro.get_int("hand", -1) if exact else -1,
},
}
def __format_jubeat_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
@ -35,25 +34,27 @@ class ProfileObject(BaseObject):
def __format_popn_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
return {
'character': profile.get_int('chara', -1) if exact else -1,
"character": profile.get_int("chara", -1) if exact else -1,
}
def __format_reflec_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
return {
'icon': profile.get_dict('config').get_int('icon_id', -1) if exact else -1,
"icon": profile.get_dict("config").get_int("icon_id", -1) if exact else -1,
}
def __format_sdvx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
return {}
def __format_profile(self, cardids: List[str], profile: Profile, settings: ValidatedDict, exact: bool) -> Dict[str, Any]:
def __format_profile(
self, cardids: List[str], profile: Profile, settings: ValidatedDict, exact: bool
) -> Dict[str, Any]:
base = {
'name': profile.get_str('name'),
'cards': cardids,
'registered': settings.get_int('first_play_timestamp', -1),
'updated': settings.get_int('last_play_timestamp', -1),
'plays': settings.get_int('total_plays', -1),
'match': 'exact' if exact else 'partial',
"name": profile.get_str("name"),
"cards": cardids,
"registered": settings.get_int("first_play_timestamp", -1),
"updated": settings.get_int("last_play_timestamp", -1),
"plays": settings.get_int("total_plays", -1),
"match": "exact" if exact else "partial",
}
if self.game == GameConstants.DDR:
@ -73,19 +74,23 @@ class ProfileObject(BaseObject):
return base
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]:
def fetch_v1(
self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]
) -> List[Dict[str, Any]]:
# Fetch the profiles
profiles: List[Tuple[UserID, Profile]] = []
if idtype == APIConstants.ID_TYPE_SERVER:
profiles.extend(self.data.local.user.get_all_profiles(self.game, self.version))
profiles.extend(
self.data.local.user.get_all_profiles(self.game, self.version)
)
elif idtype == APIConstants.ID_TYPE_SONG:
raise APIException(
'Unsupported ID for lookup!',
"Unsupported ID for lookup!",
405,
)
elif idtype == APIConstants.ID_TYPE_INSTANCE:
raise APIException(
'Unsupported ID for lookup!',
"Unsupported ID for lookup!",
405,
)
elif idtype == APIConstants.ID_TYPE_CARD:
@ -103,11 +108,13 @@ class ProfileObject(BaseObject):
# in the case that we returned scores for a user that doesn't have a
# profile on a particular version. We allow that on this network, so in
# order to not break remote networks, try our best to return any profile.
profile = self.data.local.user.get_any_profile(self.game, self.version, userid)
profile = self.data.local.user.get_any_profile(
self.game, self.version, userid
)
if profile is not None:
profiles.append((userid, profile))
else:
raise APIException('Invalid ID type!')
raise APIException("Invalid ID type!")
# Now, fetch the users, and filter out profiles belonging to orphaned users
retval: List[Dict[str, Any]] = []
@ -126,6 +133,13 @@ class ProfileObject(BaseObject):
if settings is None:
settings = ValidatedDict({})
retval.append(self.__format_profile(id_to_cards[userid], profile, settings, profile.version == self.version))
retval.append(
self.__format_profile(
id_to_cards[userid],
profile,
settings,
profile.version == self.version,
)
)
return retval

View File

@ -7,15 +7,14 @@ from bemani.data import Score, UserID
class RecordsObject(BaseObject):
def __format_ddr_record(self, record: Score) -> Dict[str, Any]:
halo = {
DBConstants.DDR_HALO_NONE: 'none',
DBConstants.DDR_HALO_GOOD_FULL_COMBO: 'gfc',
DBConstants.DDR_HALO_GREAT_FULL_COMBO: 'fc',
DBConstants.DDR_HALO_PERFECT_FULL_COMBO: 'pfc',
DBConstants.DDR_HALO_MARVELOUS_FULL_COMBO: 'mfc',
}.get(record.data.get_int('halo'), 'none')
DBConstants.DDR_HALO_NONE: "none",
DBConstants.DDR_HALO_GOOD_FULL_COMBO: "gfc",
DBConstants.DDR_HALO_GREAT_FULL_COMBO: "fc",
DBConstants.DDR_HALO_PERFECT_FULL_COMBO: "pfc",
DBConstants.DDR_HALO_MARVELOUS_FULL_COMBO: "mfc",
}.get(record.data.get_int("halo"), "none")
rank = {
DBConstants.DDR_RANK_AAA: "AAA",
DBConstants.DDR_RANK_AA_PLUS: "AA+",
@ -33,175 +32,175 @@ class RecordsObject(BaseObject):
DBConstants.DDR_RANK_D_PLUS: "D+",
DBConstants.DDR_RANK_D: "D",
DBConstants.DDR_RANK_E: "E",
}.get(record.data.get_int('rank'), 'E')
}.get(record.data.get_int("rank"), "E")
if self.version == VersionConstants.DDR_ACE:
# DDR Ace is specia
ghost = [int(x) for x in record.data.get_str('ghost')]
ghost = [int(x) for x in record.data.get_str("ghost")]
else:
if 'trace' not in record.data:
if "trace" not in record.data:
ghost = []
else:
ghost = record.data.get_int_array('trace', len(record.data['trace']))
ghost = record.data.get_int_array("trace", len(record.data["trace"]))
return {
'rank': rank,
'halo': halo,
'combo': record.data.get_int('combo'),
'ghost': ghost,
"rank": rank,
"halo": halo,
"combo": record.data.get_int("combo"),
"ghost": ghost,
}
def __format_iidx_record(self, record: Score) -> Dict[str, Any]:
status = {
DBConstants.IIDX_CLEAR_STATUS_NO_PLAY: 'np',
DBConstants.IIDX_CLEAR_STATUS_FAILED: 'failed',
DBConstants.IIDX_CLEAR_STATUS_ASSIST_CLEAR: 'ac',
DBConstants.IIDX_CLEAR_STATUS_EASY_CLEAR: 'ec',
DBConstants.IIDX_CLEAR_STATUS_CLEAR: 'nc',
DBConstants.IIDX_CLEAR_STATUS_HARD_CLEAR: 'hc',
DBConstants.IIDX_CLEAR_STATUS_EX_HARD_CLEAR: 'exhc',
DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO: 'fc',
}.get(record.data.get_int('clear_status'), 'np')
DBConstants.IIDX_CLEAR_STATUS_NO_PLAY: "np",
DBConstants.IIDX_CLEAR_STATUS_FAILED: "failed",
DBConstants.IIDX_CLEAR_STATUS_ASSIST_CLEAR: "ac",
DBConstants.IIDX_CLEAR_STATUS_EASY_CLEAR: "ec",
DBConstants.IIDX_CLEAR_STATUS_CLEAR: "nc",
DBConstants.IIDX_CLEAR_STATUS_HARD_CLEAR: "hc",
DBConstants.IIDX_CLEAR_STATUS_EX_HARD_CLEAR: "exhc",
DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO: "fc",
}.get(record.data.get_int("clear_status"), "np")
return {
'status': status,
'miss': record.data.get_int('miss_count', -1),
'ghost': [b for b in record.data.get_bytes('ghost')],
'pgreat': record.data.get_int('pgreats', -1),
'great': record.data.get_int('greats', -1),
"status": status,
"miss": record.data.get_int("miss_count", -1),
"ghost": [b for b in record.data.get_bytes("ghost")],
"pgreat": record.data.get_int("pgreats", -1),
"great": record.data.get_int("greats", -1),
}
def __format_jubeat_record(self, record: Score) -> Dict[str, Any]:
status = {
DBConstants.JUBEAT_PLAY_MEDAL_FAILED: 'failed',
DBConstants.JUBEAT_PLAY_MEDAL_CLEARED: 'cleared',
DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO: 'nfc',
DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO: 'fc',
DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT: 'nec',
DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT: 'exc',
}.get(record.data.get_int('medal'), 'failed')
if 'ghost' not in record.data:
DBConstants.JUBEAT_PLAY_MEDAL_FAILED: "failed",
DBConstants.JUBEAT_PLAY_MEDAL_CLEARED: "cleared",
DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO: "nfc",
DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO: "fc",
DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT: "nec",
DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT: "exc",
}.get(record.data.get_int("medal"), "failed")
if "ghost" not in record.data:
ghost: List[int] = []
else:
ghost = record.data.get_int_array('ghost', len(record.data['ghost']))
ghost = record.data.get_int_array("ghost", len(record.data["ghost"]))
return {
'status': status,
'combo': record.data.get_int('combo', -1),
'ghost': ghost,
'music_rate': record.data.get_int('music_rate'),
"status": status,
"combo": record.data.get_int("combo", -1),
"ghost": ghost,
"music_rate": record.data.get_int("music_rate"),
}
def __format_museca_record(self, record: Score) -> Dict[str, Any]:
rank = {
DBConstants.MUSECA_GRADE_DEATH: 'death',
DBConstants.MUSECA_GRADE_POOR: 'poor',
DBConstants.MUSECA_GRADE_MEDIOCRE: 'mediocre',
DBConstants.MUSECA_GRADE_GOOD: 'good',
DBConstants.MUSECA_GRADE_GREAT: 'great',
DBConstants.MUSECA_GRADE_EXCELLENT: 'excellent',
DBConstants.MUSECA_GRADE_SUPERB: 'superb',
DBConstants.MUSECA_GRADE_MASTERPIECE: 'masterpiece',
DBConstants.MUSECA_GRADE_PERFECT: 'perfect'
}.get(record.data.get_int('grade'), 'death')
DBConstants.MUSECA_GRADE_DEATH: "death",
DBConstants.MUSECA_GRADE_POOR: "poor",
DBConstants.MUSECA_GRADE_MEDIOCRE: "mediocre",
DBConstants.MUSECA_GRADE_GOOD: "good",
DBConstants.MUSECA_GRADE_GREAT: "great",
DBConstants.MUSECA_GRADE_EXCELLENT: "excellent",
DBConstants.MUSECA_GRADE_SUPERB: "superb",
DBConstants.MUSECA_GRADE_MASTERPIECE: "masterpiece",
DBConstants.MUSECA_GRADE_PERFECT: "perfect",
}.get(record.data.get_int("grade"), "death")
status = {
DBConstants.MUSECA_CLEAR_TYPE_FAILED: 'failed',
DBConstants.MUSECA_CLEAR_TYPE_CLEARED: 'cleared',
DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO: 'fc',
}.get(record.data.get_int('clear_type'), 'failed')
DBConstants.MUSECA_CLEAR_TYPE_FAILED: "failed",
DBConstants.MUSECA_CLEAR_TYPE_CLEARED: "cleared",
DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO: "fc",
}.get(record.data.get_int("clear_type"), "failed")
return {
'rank': rank,
'status': status,
'combo': record.data.get_int('combo', -1),
'buttonrate': record.data.get_dict('stats').get_int('btn_rate'),
'longrate': record.data.get_dict('stats').get_int('long_rate'),
'volrate': record.data.get_dict('stats').get_int('vol_rate'),
"rank": rank,
"status": status,
"combo": record.data.get_int("combo", -1),
"buttonrate": record.data.get_dict("stats").get_int("btn_rate"),
"longrate": record.data.get_dict("stats").get_int("long_rate"),
"volrate": record.data.get_dict("stats").get_int("vol_rate"),
}
def __format_popn_record(self, record: Score) -> Dict[str, Any]:
status = {
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED: 'cf',
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED: 'df',
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED: 'sf',
DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR: 'ec',
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED: 'cc',
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED: 'dc',
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED: 'sc',
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO: 'cfc',
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO: 'dfc',
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO: 'sfc',
DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT: 'p',
}.get(record.data.get_int('medal'), 'cf')
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED: "cf",
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED: "df",
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED: "sf",
DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR: "ec",
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED: "cc",
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED: "dc",
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED: "sc",
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO: "cfc",
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO: "dfc",
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO: "sfc",
DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT: "p",
}.get(record.data.get_int("medal"), "cf")
return {
'status': status,
'combo': record.data.get_int('combo', -1),
"status": status,
"combo": record.data.get_int("combo", -1),
}
def __format_reflec_record(self, record: Score) -> Dict[str, Any]:
status = {
DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY: 'np',
DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED: 'failed',
DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED: 'cleared',
DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED: 'hc',
DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED: 'shc',
}.get(record.data.get_int('clear_type'), 'np')
DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY: "np",
DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED: "failed",
DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED: "cleared",
DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED: "hc",
DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED: "shc",
}.get(record.data.get_int("clear_type"), "np")
halo = {
DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE: 'none',
DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO: 'ac',
DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO: 'fc',
DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST: 'fcaj',
}.get(record.data.get_int('combo_type'), 'none')
DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE: "none",
DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO: "ac",
DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO: "fc",
DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST: "fcaj",
}.get(record.data.get_int("combo_type"), "none")
return {
'rate': record.data.get_int('achievement_rate'),
'status': status,
'halo': halo,
'combo': record.data.get_int('combo', -1),
'miss': record.data.get_int('miss_count', -1),
"rate": record.data.get_int("achievement_rate"),
"status": status,
"halo": halo,
"combo": record.data.get_int("combo", -1),
"miss": record.data.get_int("miss_count", -1),
}
def __format_sdvx_record(self, record: Score) -> Dict[str, Any]:
status = {
DBConstants.SDVX_CLEAR_TYPE_NO_PLAY: 'np',
DBConstants.SDVX_CLEAR_TYPE_FAILED: 'failed',
DBConstants.SDVX_CLEAR_TYPE_CLEAR: 'cleared',
DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR: 'hc',
DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN: 'uc',
DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: 'puc',
}.get(record.data.get_int('clear_type'), 'np')
DBConstants.SDVX_CLEAR_TYPE_NO_PLAY: "np",
DBConstants.SDVX_CLEAR_TYPE_FAILED: "failed",
DBConstants.SDVX_CLEAR_TYPE_CLEAR: "cleared",
DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR: "hc",
DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN: "uc",
DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: "puc",
}.get(record.data.get_int("clear_type"), "np")
rank = {
DBConstants.SDVX_GRADE_NO_PLAY: 'E',
DBConstants.SDVX_GRADE_D: 'D',
DBConstants.SDVX_GRADE_C: 'C',
DBConstants.SDVX_GRADE_B: 'B',
DBConstants.SDVX_GRADE_A: 'A',
DBConstants.SDVX_GRADE_A_PLUS: 'A+',
DBConstants.SDVX_GRADE_AA: 'AA',
DBConstants.SDVX_GRADE_AA_PLUS: 'AA+',
DBConstants.SDVX_GRADE_AAA: 'AAA',
DBConstants.SDVX_GRADE_AAA_PLUS: 'AAA+',
DBConstants.SDVX_GRADE_S: 'S',
}.get(record.data.get_int('grade'), 'E')
DBConstants.SDVX_GRADE_NO_PLAY: "E",
DBConstants.SDVX_GRADE_D: "D",
DBConstants.SDVX_GRADE_C: "C",
DBConstants.SDVX_GRADE_B: "B",
DBConstants.SDVX_GRADE_A: "A",
DBConstants.SDVX_GRADE_A_PLUS: "A+",
DBConstants.SDVX_GRADE_AA: "AA",
DBConstants.SDVX_GRADE_AA_PLUS: "AA+",
DBConstants.SDVX_GRADE_AAA: "AAA",
DBConstants.SDVX_GRADE_AAA_PLUS: "AAA+",
DBConstants.SDVX_GRADE_S: "S",
}.get(record.data.get_int("grade"), "E")
return {
'status': status,
'rank': rank,
'combo': record.data.get_int('combo', -1),
'buttonrate': record.data.get_dict('stats').get_int('btn_rate'),
'longrate': record.data.get_dict('stats').get_int('long_rate'),
'volrate': record.data.get_dict('stats').get_int('vol_rate'),
"status": status,
"rank": rank,
"combo": record.data.get_int("combo", -1),
"buttonrate": record.data.get_dict("stats").get_int("btn_rate"),
"longrate": record.data.get_dict("stats").get_int("long_rate"),
"volrate": record.data.get_dict("stats").get_int("vol_rate"),
}
def __format_record(self, cardids: List[str], record: Score) -> Dict[str, Any]:
base = {
'cards': cardids,
'song': str(record.id),
'chart': str(record.chart),
'points': record.points,
'timestamp': record.timestamp,
'updated': record.update,
"cards": cardids,
"song": str(record.id),
"chart": str(record.chart),
"points": record.points,
"timestamp": record.timestamp,
"updated": record.update,
}
if self.game == GameConstants.DDR:
@ -231,9 +230,11 @@ class RecordsObject(BaseObject):
else:
return self.version
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]:
since = params.get('since')
until = params.get('until')
def fetch_v1(
self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]
) -> List[Dict[str, Any]]:
since = params.get("since")
until = params.get("until")
# Fetch the scores
records: List[Tuple[UserID, Score]] = []
@ -241,7 +242,9 @@ class RecordsObject(BaseObject):
# Because of the way this query works, we can't apply since/until to it directly.
# If we did, it would miss higher scores earned before since or after until, and
# incorrectly report records.
records.extend(self.data.local.music.get_all_records(self.game, self.music_version))
records.extend(
self.data.local.music.get_all_records(self.game, self.music_version)
)
elif idtype == APIConstants.ID_TYPE_SONG:
if len(ids) == 1:
songid = int(ids[0])
@ -249,14 +252,25 @@ class RecordsObject(BaseObject):
else:
songid = int(ids[0])
chart = int(ids[1])
records.extend(self.data.local.music.get_all_scores(self.game, self.music_version, songid=songid, songchart=chart, since=since, until=until))
records.extend(
self.data.local.music.get_all_scores(
self.game,
self.music_version,
songid=songid,
songchart=chart,
since=since,
until=until,
)
)
elif idtype == APIConstants.ID_TYPE_INSTANCE:
songid = int(ids[0])
chart = int(ids[1])
cardid = ids[2]
userid = self.data.local.user.from_cardid(cardid)
if userid is not None:
score = self.data.local.music.get_score(self.game, self.music_version, userid, songid, chart)
score = self.data.local.music.get_score(
self.game, self.music_version, userid, songid, chart
)
if score is not None:
records.append((userid, score))
elif idtype == APIConstants.ID_TYPE_CARD:
@ -270,9 +284,20 @@ class RecordsObject(BaseObject):
continue
users.add(userid)
records.extend([(userid, score) for score in self.data.local.music.get_scores(self.game, self.music_version, userid, since=since, until=until)])
records.extend(
[
(userid, score)
for score in self.data.local.music.get_scores(
self.game,
self.music_version,
userid,
since=since,
until=until,
)
]
)
else:
raise APIException('Invalid ID type!')
raise APIException("Invalid ID type!")
# Now, fetch the users, and filter out scores belonging to orphaned users
id_to_cards: Dict[UserID, List[str]] = {}

View File

@ -7,20 +7,21 @@ from bemani.data import Attempt, UserID
class StatisticsObject(BaseObject):
def __format_statistics(self, stats: Dict[str, Any]) -> Dict[str, Any]:
return {
'cards': [],
'song': str(stats['id']),
'chart': str(stats['chart']),
'plays': stats.get('plays', -1),
'clears': stats.get('clears', -1),
'combos': stats.get('combos', -1),
"cards": [],
"song": str(stats["id"]),
"chart": str(stats["chart"]),
"plays": stats.get("plays", -1),
"clears": stats.get("clears", -1),
"combos": stats.get("combos", -1),
}
def __format_user_statistics(self, cardids: List[str], stats: Dict[str, Any]) -> Dict[str, Any]:
def __format_user_statistics(
self, cardids: List[str], stats: Dict[str, Any]
) -> Dict[str, Any]:
base = self.__format_statistics(stats)
base['cards'] = cardids
base["cards"] = cardids
return base
@property
@ -42,11 +43,20 @@ class StatisticsObject(BaseObject):
}:
return True
if self.game == GameConstants.IIDX:
return attempt.data.get_int('clear_status') != DBConstants.IIDX_CLEAR_STATUS_NO_PLAY
return (
attempt.data.get_int("clear_status")
!= DBConstants.IIDX_CLEAR_STATUS_NO_PLAY
)
if self.game == GameConstants.REFLEC_BEAT:
return attempt.data.get_int('clear_type') != DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY
return (
attempt.data.get_int("clear_type")
!= DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY
)
if self.game == GameConstants.SDVX:
return attempt.data.get_int('clear_type') != DBConstants.SDVX_CLEAR_TYPE_NO_PLAY
return (
attempt.data.get_int("clear_type")
!= DBConstants.SDVX_CLEAR_TYPE_NO_PLAY
)
return False
@ -55,29 +65,39 @@ class StatisticsObject(BaseObject):
return False
if self.game == GameConstants.DDR:
return attempt.data.get_int('rank') != DBConstants.DDR_RANK_E
return attempt.data.get_int("rank") != DBConstants.DDR_RANK_E
if self.game == GameConstants.IIDX:
return attempt.data.get_int('clear_status') != DBConstants.IIDX_CLEAR_STATUS_FAILED
return (
attempt.data.get_int("clear_status")
!= DBConstants.IIDX_CLEAR_STATUS_FAILED
)
if self.game == GameConstants.JUBEAT:
return attempt.data.get_int('medal') != DBConstants.JUBEAT_PLAY_MEDAL_FAILED
return attempt.data.get_int("medal") != DBConstants.JUBEAT_PLAY_MEDAL_FAILED
if self.game == GameConstants.MUSECA:
return attempt.data.get_int('clear_type') != DBConstants.MUSECA_CLEAR_TYPE_FAILED
return (
attempt.data.get_int("clear_type")
!= DBConstants.MUSECA_CLEAR_TYPE_FAILED
)
if self.game == GameConstants.POPN_MUSIC:
return attempt.data.get_int('medal') not in [
return attempt.data.get_int("medal") not in [
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED,
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED,
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED,
]
if self.game == GameConstants.REFLEC_BEAT:
return attempt.data.get_int('clear_type') != DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED
if self.game == GameConstants.SDVX:
return (
attempt.data.get_int('grade') != DBConstants.SDVX_GRADE_NO_PLAY and
attempt.data.get_int('clear_type') not in [
DBConstants.SDVX_CLEAR_TYPE_NO_PLAY,
DBConstants.SDVX_CLEAR_TYPE_FAILED,
]
attempt.data.get_int("clear_type")
!= DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED
)
if self.game == GameConstants.SDVX:
return attempt.data.get_int(
"grade"
) != DBConstants.SDVX_GRADE_NO_PLAY and attempt.data.get_int(
"clear_type"
) not in [
DBConstants.SDVX_CLEAR_TYPE_NO_PLAY,
DBConstants.SDVX_CLEAR_TYPE_FAILED,
]
return False
@ -86,31 +106,37 @@ class StatisticsObject(BaseObject):
return False
if self.game == GameConstants.DDR:
return attempt.data.get_int('halo') != DBConstants.DDR_HALO_NONE
return attempt.data.get_int("halo") != DBConstants.DDR_HALO_NONE
if self.game == GameConstants.IIDX:
return attempt.data.get_int('clear_status') == DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO
return (
attempt.data.get_int("clear_status")
== DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO
)
if self.game == GameConstants.JUBEAT:
return attempt.data.get_int('medal') in [
return attempt.data.get_int("medal") in [
DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO,
DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT,
DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT,
]
if self.game == GameConstants.MUSECA:
return attempt.data.get_int('clear_type') == DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO
return (
attempt.data.get_int("clear_type")
== DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO
)
if self.game == GameConstants.POPN_MUSIC:
return attempt.data.get_int('medal') in [
return attempt.data.get_int("medal") in [
DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO,
DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO,
DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO,
DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT,
]
if self.game == GameConstants.REFLEC_BEAT:
return attempt.data.get_int('combo_type') in [
return attempt.data.get_int("combo_type") in [
DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO,
DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST,
]
if self.game == GameConstants.SDVX:
return attempt.data.get_int('clear_type') in [
return attempt.data.get_int("clear_type") in [
DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN,
DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN,
]
@ -125,29 +151,31 @@ class StatisticsObject(BaseObject):
stats[attempt.id] = {}
if attempt.chart not in stats[attempt.id]:
stats[attempt.id][attempt.chart] = {
'plays': 0,
'clears': 0,
'combos': 0,
"plays": 0,
"clears": 0,
"combos": 0,
}
if self.__is_play(attempt):
stats[attempt.id][attempt.chart]['plays'] += 1
stats[attempt.id][attempt.chart]["plays"] += 1
if self.__is_clear(attempt):
stats[attempt.id][attempt.chart]['clears'] += 1
stats[attempt.id][attempt.chart]["clears"] += 1
if self.__is_combo(attempt):
stats[attempt.id][attempt.chart]['combos'] += 1
stats[attempt.id][attempt.chart]["combos"] += 1
retval = []
for songid in stats:
for songchart in stats[songid]:
stat = stats[songid][songchart]
stat['id'] = songid
stat['chart'] = songchart
stat["id"] = songid
stat["chart"] = songchart
retval.append(self.__format_statistics(stat))
return retval
def __aggregate_local(self, cards: Dict[int, List[str]], attempts: List[Tuple[UserID, Attempt]]) -> List[Dict[str, Any]]:
def __aggregate_local(
self, cards: Dict[int, List[str]], attempts: List[Tuple[UserID, Attempt]]
) -> List[Dict[str, Any]]:
stats: Dict[UserID, Dict[int, Dict[int, Dict[str, int]]]] = {}
for (userid, attempt) in attempts:
@ -157,36 +185,43 @@ class StatisticsObject(BaseObject):
stats[userid][attempt.id] = {}
if attempt.chart not in stats[userid][attempt.id]:
stats[userid][attempt.id][attempt.chart] = {
'plays': 0,
'clears': 0,
'combos': 0,
"plays": 0,
"clears": 0,
"combos": 0,
}
if self.__is_play(attempt):
stats[userid][attempt.id][attempt.chart]['plays'] += 1
stats[userid][attempt.id][attempt.chart]["plays"] += 1
if self.__is_clear(attempt):
stats[userid][attempt.id][attempt.chart]['clears'] += 1
stats[userid][attempt.id][attempt.chart]["clears"] += 1
if self.__is_combo(attempt):
stats[userid][attempt.id][attempt.chart]['combos'] += 1
stats[userid][attempt.id][attempt.chart]["combos"] += 1
retval = []
for userid in stats:
for songid in stats[userid]:
for songchart in stats[userid][songid]:
stat = stats[userid][songid][songchart]
stat['id'] = songid
stat['chart'] = songchart
stat["id"] = songid
stat["chart"] = songchart
retval.append(self.__format_user_statistics(cards[userid], stat))
return retval
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]:
def fetch_v1(
self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]
) -> List[Dict[str, Any]]:
retval: List[Dict[str, Any]] = []
# Fetch the attempts
if idtype == APIConstants.ID_TYPE_SERVER:
retval = self.__aggregate_global(
[attempt[1] for attempt in self.data.local.music.get_all_attempts(self.game, self.music_version)]
[
attempt[1]
for attempt in self.data.local.music.get_all_attempts(
self.game, self.music_version
)
]
)
elif idtype == APIConstants.ID_TYPE_SONG:
if len(ids) == 1:
@ -196,7 +231,12 @@ class StatisticsObject(BaseObject):
songid = int(ids[0])
chart = int(ids[1])
retval = self.__aggregate_global(
[attempt[1] for attempt in self.data.local.music.get_all_attempts(self.game, self.music_version, songid=songid, songchart=chart)]
[
attempt[1]
for attempt in self.data.local.music.get_all_attempts(
self.game, self.music_version, songid=songid, songchart=chart
)
]
)
elif idtype == APIConstants.ID_TYPE_INSTANCE:
songid = int(ids[0])
@ -206,7 +246,13 @@ class StatisticsObject(BaseObject):
if userid is not None:
retval = self.__aggregate_local(
{userid: self.data.local.user.get_cards(userid)},
self.data.local.music.get_all_attempts(self.game, self.music_version, songid=songid, songchart=chart, userid=userid)
self.data.local.music.get_all_attempts(
self.game,
self.music_version,
songid=songid,
songchart=chart,
userid=userid,
),
)
elif idtype == APIConstants.ID_TYPE_CARD:
id_to_cards: Dict[int, List[str]] = {}
@ -221,10 +267,12 @@ class StatisticsObject(BaseObject):
id_to_cards[userid] = self.data.local.user.get_cards(userid)
attempts.extend(
self.data.local.music.get_all_attempts(self.game, self.music_version, userid=userid)
self.data.local.music.get_all_attempts(
self.game, self.music_version, userid=userid
)
)
retval = self.__aggregate_local(id_to_cards, attempts)
else:
raise APIException('Invalid ID type!')
raise APIException("Invalid ID type!")
return retval

View File

@ -3,7 +3,15 @@ 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, RegionConstants, Time
from bemani.common import (
Model,
ValidatedDict,
Profile,
PlayStatistics,
GameConstants,
RegionConstants,
Time,
)
from bemani.data import Config, Data, Arcade, Machine, UserID, RemoteUser
@ -15,6 +23,7 @@ class Status:
"""
List of statuses we return to the game for various reasons.
"""
SUCCESS: Final[int] = 0
NO_PROFILE: Final[int] = 109
NOT_ALLOWED: Final[int] = 110
@ -41,7 +50,7 @@ class Factory(ABC):
with Base, using Base.register(). Factories specify the game code that
they support, which Base will use when routing requests.
"""
raise NotImplementedError('Override this in subclass!')
raise NotImplementedError("Override this in subclass!")
@classmethod
def run_scheduled_work(cls, data: Data, config: Config) -> None:
@ -59,10 +68,10 @@ class Factory(ABC):
stack = traceback.format_exc()
print(stack)
data.local.network.put_event(
'exception',
"exception",
{
'service': 'scheduler',
'traceback': stack,
"service": "scheduler",
"traceback": stack,
},
)
for event in events:
@ -88,7 +97,13 @@ class Factory(ABC):
@classmethod
@abstractmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional['Base']:
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.
@ -106,7 +121,7 @@ class Factory(ABC):
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 NotImplementedError('Override this in subclass!')
raise NotImplementedError("Override this in subclass!")
class Base(ABC):
@ -172,7 +187,13 @@ class Base(ABC):
self.model = model
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional['Base']:
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.
@ -200,7 +221,9 @@ class Base(ABC):
return Base(data, config, model)
else:
# Return the registered module providing this game
return cls.__registered_games[model.gamecode].create(data, config, model, parentmodel=parentmodel)
return cls.__registered_games[model.gamecode].create(
data, config, model, parentmodel=parentmodel
)
@classmethod
def register(cls, gamecode: str, handler: Type[Factory]) -> None:
@ -216,7 +239,9 @@ class Base(ABC):
cls.__registered_handlers.add(handler)
@classmethod
def run_scheduled_work(cls, data: Data, config: Config) -> List[Tuple[str, Dict[str, Any]]]:
def run_scheduled_work(
cls, data: Data, config: Config
) -> List[Tuple[str, Dict[str, Any]]]:
"""
Run any out-of-band scheduled work that is applicable to this game.
"""
@ -267,7 +292,10 @@ class Base(ABC):
Returns:
True if the profile exists, False if not.
"""
return self.data.local.user.get_profile(self.game, self.version, userid) is not None
return (
self.data.local.user.get_profile(self.game, self.version, userid)
is not None
)
def get_profile(self, userid: UserID) -> Optional[Profile]:
"""
@ -321,9 +349,16 @@ class Base(ABC):
or an empty dictionary if nothing was found.
"""
userids = list(set(userids))
profiles = self.data.remote.user.get_any_profiles(self.game, self.version, userids)
profiles = self.data.remote.user.get_any_profiles(
self.game, self.version, userids
)
return [
(userid, profile if profile is not None else Profile(self.game, self.version, "", 0))
(
userid,
profile
if profile is not None
else Profile(self.game, self.version, "", 0),
)
for (userid, profile) in profiles
]
@ -336,10 +371,12 @@ class Base(ABC):
profile - A dictionary that should be looked up later using get_profile.
"""
if RemoteUser.is_remote(userid):
raise Exception('Trying to save a remote profile locally!')
raise Exception("Trying to save a remote profile locally!")
self.data.local.user.put_profile(self.game, self.version, userid, profile)
def update_play_statistics(self, userid: UserID, stats: Optional[PlayStatistics] = None) -> None:
def update_play_statistics(
self, userid: UserID, stats: Optional[PlayStatistics] = None
) -> None:
"""
Given a user ID, calculate new play statistics.
@ -351,59 +388,65 @@ class Base(ABC):
stats - A play statistics object we should store extra data from.
"""
if RemoteUser.is_remote(userid):
raise Exception('Trying to save remote statistics locally!')
raise Exception("Trying to save remote statistics locally!")
# We store the play statistics in a series-wide settings blob so its available
# across all game versions, since it isn't game-specific.
settings = self.data.local.game.get_settings(self.game, userid) or ValidatedDict({})
settings = self.data.local.game.get_settings(
self.game, userid
) or ValidatedDict({})
if stats is not None:
for key in stats:
# Make sure we don't override anything we manage here
if key in {
'total_plays',
'today_plays',
'total_days',
'first_play_timestamp',
'last_play_timestamp',
'last_play_date',
'consecutive_days',
"total_plays",
"today_plays",
"total_days",
"first_play_timestamp",
"last_play_timestamp",
"last_play_date",
"consecutive_days",
}:
continue
# Safe to copy over
settings[key] = stats[key]
settings.replace_int('total_plays', settings.get_int('total_plays') + 1)
settings.replace_int('first_play_timestamp', settings.get_int('first_play_timestamp', Time.now()))
settings.replace_int('last_play_timestamp', Time.now())
settings.replace_int("total_plays", settings.get_int("total_plays") + 1)
settings.replace_int(
"first_play_timestamp", settings.get_int("first_play_timestamp", Time.now())
)
settings.replace_int("last_play_timestamp", Time.now())
last_play_date = settings.get_int_array('last_play_date', 3)
last_play_date = settings.get_int_array("last_play_date", 3)
today_play_date = Time.todays_date()
yesterday_play_date = Time.yesterdays_date()
if (
last_play_date[0] == today_play_date[0] and
last_play_date[1] == today_play_date[1] and
last_play_date[2] == today_play_date[2]
last_play_date[0] == today_play_date[0]
and last_play_date[1] == today_play_date[1]
and last_play_date[2] == today_play_date[2]
):
# We already played today, add one.
settings.replace_int('today_plays', settings.get_int('today_plays') + 1)
settings.replace_int("today_plays", settings.get_int("today_plays") + 1)
else:
# We played on a new day, so count total days up.
settings.replace_int('total_days', settings.get_int('total_days') + 1)
settings.replace_int("total_days", settings.get_int("total_days") + 1)
# We played only once today (the play we are saving).
settings.replace_int('today_plays', 1)
settings.replace_int("today_plays", 1)
if (
last_play_date[0] == yesterday_play_date[0] and
last_play_date[1] == yesterday_play_date[1] and
last_play_date[2] == yesterday_play_date[2]
last_play_date[0] == yesterday_play_date[0]
and last_play_date[1] == yesterday_play_date[1]
and last_play_date[2] == yesterday_play_date[2]
):
# We played yesterday, add one to consecutive days
settings.replace_int('consecutive_days', settings.get_int('consecutive_days') + 1)
settings.replace_int(
"consecutive_days", settings.get_int("consecutive_days") + 1
)
else:
# We haven't played yesterday, so we have only one consecutive day.
settings.replace_int('consecutive_days', 1)
settings.replace_int_array('last_play_date', 3, today_play_date)
settings.replace_int("consecutive_days", 1)
settings.replace_int_array("last_play_date", 3, today_play_date)
# Save back
self.data.local.game.put_settings(self.game, userid, settings)
@ -442,9 +485,13 @@ class Base(ABC):
def get_machine_region(self) -> int:
arcade = self.get_arcade()
if arcade is None:
return RegionConstants.db_to_game_region(self.requires_extended_regions, self.config.server.region)
return RegionConstants.db_to_game_region(
self.requires_extended_regions, self.config.server.region
)
else:
return RegionConstants.db_to_game_region(self.requires_extended_regions, arcade.region)
return RegionConstants.db_to_game_region(
self.requires_extended_regions, arcade.region
)
def get_game_config(self) -> ValidatedDict:
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
@ -452,7 +499,9 @@ class Base(ABC):
# If this machine belongs to an arcade, use its settings. If the settings aren't present,
# default to the game's defaults.
if machine.arcade is not None:
settings = self.data.local.machine.get_settings(machine.arcade, self.game, self.version, 'game_config')
settings = self.data.local.machine.get_settings(
machine.arcade, self.game, self.version, "game_config"
)
if settings is None:
settings = ValidatedDict()
return settings
@ -460,7 +509,12 @@ class Base(ABC):
# If this machine does not belong to an arcade, use the server-wide settings. If the settings
# aren't present, default ot the game's default.
else:
settings = self.data.local.machine.get_settings(self.data.local.machine.DEFAULT_SETTINGS_ARCADE, self.game, self.version, 'game_config')
settings = self.data.local.machine.get_settings(
self.data.local.machine.DEFAULT_SETTINGS_ARCADE,
self.game,
self.version,
"game_config",
)
if settings is None:
settings = ValidatedDict()
return settings
@ -511,38 +565,38 @@ class Base(ABC):
)
# Calculate whether we are on our first play of the day or not.
last_play_date = settings.get_int_array('last_play_date', 3)
last_play_date = settings.get_int_array("last_play_date", 3)
if (
last_play_date[0] == today_play_date[0] and
last_play_date[1] == today_play_date[1] and
last_play_date[2] == today_play_date[2]
last_play_date[0] == today_play_date[0]
and last_play_date[1] == today_play_date[1]
and last_play_date[2] == today_play_date[2]
):
# We last played today, so the total days and today plays are accurate
# as stored.
today_count = settings.get_int('today_plays', 0)
total_days = settings.get_int('total_days', 1)
consecutive_days = settings.get_int('consecutive_days', 1)
today_count = settings.get_int("today_plays", 0)
total_days = settings.get_int("total_days", 1)
consecutive_days = settings.get_int("consecutive_days", 1)
else:
if (
last_play_date[0] != 0 and
last_play_date[1] != 0 and
last_play_date[2] != 0
last_play_date[0] != 0
and last_play_date[1] != 0
and last_play_date[2] != 0
):
# We've played before but not today, so the total days is
# the stored count plus today.
total_days = settings.get_int('total_days') + 1
total_days = settings.get_int("total_days") + 1
else:
# We've never played before, so the total days is just 1.
total_days = 1
if (
last_play_date[0] == yesterday_play_date[0] and
last_play_date[1] == yesterday_play_date[1] and
last_play_date[2] == yesterday_play_date[2]
last_play_date[0] == yesterday_play_date[0]
and last_play_date[1] == yesterday_play_date[1]
and last_play_date[2] == yesterday_play_date[2]
):
# We've played before, and it was yesterday, so today is the
# next consecutive day. So add the current value and today.
consecutive_days = settings.get_int('consecutive_days') + 1
consecutive_days = settings.get_int("consecutive_days") + 1
else:
# This is the first consecutive day, we've either never played
# or we played a bunch but in the past before yesterday.
@ -553,25 +607,27 @@ class Base(ABC):
# Grab any extra settings that a game may have stored here.
extra_settings: Dict[str, Any] = {
key: value for (key, value) in settings.items()
if key not in {
'total_plays',
'today_plays',
'total_days',
'first_play_timestamp',
'last_play_timestamp',
'last_play_date',
'consecutive_days',
key: value
for (key, value) in settings.items()
if key
not in {
"total_plays",
"today_plays",
"total_days",
"first_play_timestamp",
"last_play_timestamp",
"last_play_date",
"consecutive_days",
}
}
return PlayStatistics(
self.game,
settings.get_int('total_plays') + 1,
settings.get_int("total_plays") + 1,
today_count + 1,
total_days,
consecutive_days,
settings.get_int('first_play_timestamp', Time.now()),
settings.get_int('last_play_timestamp', Time.now()),
settings.get_int("first_play_timestamp", Time.now()),
settings.get_int("last_play_timestamp", Time.now()),
extra_settings,
)

View File

@ -15,7 +15,7 @@ class BishiBashiBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
game: GameConstants = GameConstants.BISHI_BASHI
def previous_version(self) -> Optional['BishiBashiBase']:
def previous_version(self) -> Optional["BishiBashiBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.

View File

@ -1,6 +1,7 @@
# vim: set fileencoding=utf-8
import binascii
import base64
try:
# Python <= 3.9
from collections import Iterable
@ -30,58 +31,58 @@ class TheStarBishiBashi(
Return all of our front-end modifiably settings.
"""
return {
'bools': [
"bools": [
{
'name': 'Force Unlock All Characters',
'tip': 'Force unlock all characters on select screen.',
'category': 'game_config',
'setting': 'force_unlock_characters',
"name": "Force Unlock All Characters",
"tip": "Force unlock all characters on select screen.",
"category": "game_config",
"setting": "force_unlock_characters",
},
{
'name': 'Unlock Non-Gacha Characters',
'tip': 'Unlock characters that require playing a different game to unlock.',
'category': 'game_config',
'setting': 'force_unlock_eamuse_characters',
"name": "Unlock Non-Gacha Characters",
"tip": "Unlock characters that require playing a different game to unlock.",
"category": "game_config",
"setting": "force_unlock_eamuse_characters",
},
{
'name': 'Enable DLC levels',
'tip': 'Enable extra DLC levels on newer cabinets.',
'category': 'game_config',
'setting': 'enable_dlc_levels',
"name": "Enable DLC levels",
"tip": "Enable extra DLC levels on newer cabinets.",
"category": "game_config",
"setting": "enable_dlc_levels",
},
],
'strs': [
"strs": [
{
'name': 'Scrolling Announcement',
'tip': 'An announcement that scrolls by in attract mode.',
'category': 'game_config',
'setting': 'big_announcement',
"name": "Scrolling Announcement",
"tip": "An announcement that scrolls by in attract mode.",
"category": "game_config",
"setting": "big_announcement",
},
],
'longstrs': [
"longstrs": [
{
'name': 'Bulletin Board Announcement',
'tip': 'An announcement displayed on a bulletin board in attract mode.',
'category': 'game_config',
'setting': 'bb_announcement',
"name": "Bulletin Board Announcement",
"tip": "An announcement displayed on a bulletin board in attract mode.",
"category": "game_config",
"setting": "bb_announcement",
},
],
}
def __update_shop_name(self, profiledata: bytes) -> None:
# Figure out the profile type
csvs = profiledata.split(b',')
csvs = profiledata.split(b",")
if len(csvs) < 2:
# Not long enough to care about
return
datatype = csvs[1].decode('ascii')
if datatype != 'IBBDAT00':
datatype = csvs[1].decode("ascii")
if datatype != "IBBDAT00":
# Not the right profile type requested
return
# Grab the shop name
try:
shopname = csvs[30].decode('shift-jis')
shopname = csvs[30].decode("shift-jis")
except Exception:
return
self.update_machine_name(shopname)
@ -98,7 +99,9 @@ class TheStarBishiBashi(
data = data.replace(";", "#;")
return data
def __generate_setting(self, key: str, values: Union[int, str, Sequence[int], Sequence[str]]) -> str:
def __generate_setting(
self, key: str, values: Union[int, str, Sequence[int], Sequence[str]]
) -> str:
if isinstance(values, Iterable) and not isinstance(values, str):
values = ",".join(self.__escape_string(x) for x in values)
else:
@ -108,18 +111,18 @@ class TheStarBishiBashi(
def handle_system_getmaster_request(self, request: Node) -> Node:
# See if we can grab the request
data = request.child('data')
data = request.child("data")
if not data:
root = Node.void('system')
root.add_child(Node.s32('result', 0))
root = Node.void("system")
root.add_child(Node.s32("result", 0))
return root
# Figure out what type of messsage this is
reqtype = data.child_value('datatype')
reqkey = data.child_value('datakey')
reqtype = data.child_value("datatype")
reqkey = data.child_value("datakey")
# System message
root = Node.void('system')
root = Node.void("system")
if reqtype == "S_SRVMSG" and reqkey == "INFO":
# Settings that we can tweak from the server.
@ -179,40 +182,51 @@ class TheStarBishiBashi(
settings: Dict[str, Union[int, str, Sequence[int], Sequence[str]]] = {}
game_config = self.get_game_config()
enable_dlc_levels = game_config.get_bool('enable_dlc_levels')
enable_dlc_levels = game_config.get_bool("enable_dlc_levels")
if enable_dlc_levels:
settings['MAL'] = 1
force_unlock_characters = game_config.get_bool('force_unlock_eamuse_characters')
settings["MAL"] = 1
force_unlock_characters = game_config.get_bool(
"force_unlock_eamuse_characters"
)
if force_unlock_characters:
settings['ALL'] = 1
scrolling_message = game_config.get_str('big_announcement')
settings["ALL"] = 1
scrolling_message = game_config.get_str("big_announcement")
if scrolling_message:
settings['CM'] = scrolling_message
bb_message = game_config.get_str('bb_announcement')
settings["CM"] = scrolling_message
bb_message = game_config.get_str("bb_announcement")
if bb_message:
settings['IM'] = bb_message
settings["IM"] = bb_message
# Generate system message
settings_str = ";".join(self.__generate_setting(key, vals) for key, vals in settings.items())
settings_str = ";".join(
self.__generate_setting(key, vals) for key, vals in settings.items()
)
# Send it to the client, making sure to inform the client that it was valid.
root.add_child(Node.string('strdata1', base64.b64encode(settings_str.encode('ascii')).decode('ascii')))
root.add_child(Node.string('strdata2', ""))
root.add_child(Node.u64('updatedate', Time.now() * 1000))
root.add_child(Node.s32('result', 1))
root.add_child(
Node.string(
"strdata1",
base64.b64encode(settings_str.encode("ascii")).decode("ascii"),
)
)
root.add_child(Node.string("strdata2", ""))
root.add_child(Node.u64("updatedate", Time.now() * 1000))
root.add_child(Node.s32("result", 1))
else:
# Unknown message.
root.add_child(Node.s32('result', 0))
root.add_child(Node.s32("result", 0))
return root
def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node:
# Look up user by refid
refid = request.child_value('data/eaid')
refid = request.child_value("data/eaid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
root = Node.void('playerdata')
root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here.
root = Node.void("playerdata")
root.add_child(
Node.s32("result", 1)
) # Unclear if this is the right thing to do here.
return root
# Extract new profile info from old profile
@ -227,14 +241,14 @@ class TheStarBishiBashi(
self.put_profile(userid, newprofile)
# Return success!
root = Node.void('playerdata')
root.add_child(Node.s32('result', 0))
root = Node.void("playerdata")
root.add_child(Node.s32("result", 0))
return root
def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node:
# Look up user by refid
refid = request.child_value('data/eaid')
profiletype = request.child_value('data/recv_csv').split(',')[0]
refid = request.child_value("data/eaid")
profiletype = request.child_value("data/recv_csv").split(",")[0]
profile = None
userid = None
if refid is not None:
@ -244,40 +258,44 @@ class TheStarBishiBashi(
if profile is not None:
return self.format_profile(userid, profiletype, profile)
else:
root = Node.void('playerdata')
root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here.
root = Node.void("playerdata")
root.add_child(
Node.s32("result", 1)
) # Unclear if this is the right thing to do here.
return root
def format_profile(self, userid: UserID, profiletype: str, profile: Profile) -> Node:
root = Node.void('playerdata')
root.add_child(Node.s32('result', 0))
player = Node.void('player')
def format_profile(
self, userid: UserID, profiletype: str, profile: Profile
) -> Node:
root = Node.void("playerdata")
root.add_child(Node.s32("result", 0))
player = Node.void("player")
root.add_child(player)
records = 0
for i in range(len(profile['strdatas'])):
strdata = profile['strdatas'][i]
bindata = profile['bindatas'][i]
for i in range(len(profile["strdatas"])):
strdata = profile["strdatas"][i]
bindata = profile["bindatas"][i]
# Figure out the profile type
csvs = strdata.split(b',')
csvs = strdata.split(b",")
if len(csvs) < 2:
# Not long enough to care about
continue
datatype = csvs[1].decode('ascii')
datatype = csvs[1].decode("ascii")
if datatype != profiletype:
# Not the right profile type requested
continue
game_config = self.get_game_config()
force_unlock_characters = game_config.get_bool('force_unlock_characters')
force_unlock_characters = game_config.get_bool("force_unlock_characters")
if force_unlock_characters:
csvs[11] = b'3ffffffffffff'
csvs[11] = b"3ffffffffffff"
else:
# Reward characters based on playing other games on the network
hexdata = csvs[11].decode('ascii')
hexdata = csvs[11].decode("ascii")
while (len(hexdata) & 1) != 0:
hexdata = '0' + hexdata
hexdata = "0" + hexdata
unlock_bits = [b for b in binascii.unhexlify(hexdata)]
while len(unlock_bits) < 7:
unlock_bits.insert(0, 0)
@ -309,32 +327,34 @@ class TheStarBishiBashi(
# Reconstruct table
unlock_bits = unlock_bits[::-1]
csvs[11] = ''.join([f'{x:02x}' for x in unlock_bits]).encode('ascii')
csvs[11] = "".join([f"{x:02x}" for x in unlock_bits]).encode("ascii")
# This is a valid profile node for this type, lets return only the profile values
strdata = b','.join(csvs[2:])
record = Node.void('record')
strdata = b",".join(csvs[2:])
record = Node.void("record")
player.add_child(record)
d = Node.string('d', base64.b64encode(strdata).decode('ascii'))
d = Node.string("d", base64.b64encode(strdata).decode("ascii"))
record.add_child(d)
d.add_child(Node.string('bin1', base64.b64encode(bindata).decode('ascii')))
d.add_child(Node.string("bin1", base64.b64encode(bindata).decode("ascii")))
# Remember that we had this record
records = records + 1
player.add_child(Node.u32('record_num', records))
player.add_child(Node.u32("record_num", records))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool
) -> Profile:
# Profile save request, data values are base64 encoded.
# d is a CSV, and bin1 is binary data.
newprofile = oldprofile.clone()
strdatas: List[bytes] = []
bindatas: List[bytes] = []
record = request.child('data/record')
record = request.child("data/record")
for node in record.children:
if node.name != 'd':
if node.name != "d":
continue
profile = base64.b64decode(node.value)
@ -344,10 +364,10 @@ class TheStarBishiBashi(
if is_new:
self.__update_shop_name(profile)
strdatas.append(profile)
bindatas.append(base64.b64decode(node.child_value('bin1')))
bindatas.append(base64.b64decode(node.child_value("bin1")))
newprofile['strdatas'] = strdatas
newprofile['bindatas'] = bindatas
newprofile["strdatas"] = strdatas
newprofile["bindatas"] = bindatas
# Keep track of play statistics across all versions
self.update_play_statistics(userid)

View File

@ -14,13 +14,19 @@ class BishiBashiFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['IBB']:
for gamecode in ["IBB"]:
Base.register(gamecode, BishiBashiFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
if model.gamecode == 'IBB':
if model.gamecode == "IBB":
return TheStarBishiBashi(data, config, model)
# Unknown game version

View File

@ -19,14 +19,14 @@ class CardManagerHandler(Base):
# Given a cardid, look up the dataid/refid (same thing in this system).
# If the card doesn't exist or isn't allowed, return a status specifying this
# instead of the results of the dataid/refid lookup.
cardid = request.attribute('cardid')
modelstring = request.attribute('model')
cardid = request.attribute("cardid")
modelstring = request.attribute("model")
userid = self.data.local.user.from_cardid(cardid)
if userid is None:
# This user doesn't exist, force system to create new account
root = Node.void('cardmng')
root.set_attribute('status', str(Status.NOT_REGISTERED))
root = Node.void("cardmng")
root.set_attribute("status", str(Status.NOT_REGISTERED))
return root
# Special handling for looking up whether the previous game's profile existed. If we
@ -43,77 +43,82 @@ class CardManagerHandler(Base):
refid = self.data.local.user.get_refid(self.game, self.version, userid)
paseli_enabled = self.supports_paseli and self.config.paseli.enabled
newflag = self.data.remote.user.get_any_profile(self.game, self.version, userid) is None
newflag = (
self.data.remote.user.get_any_profile(self.game, self.version, userid)
is None
)
root = Node.void('cardmng')
root.set_attribute('refid', refid)
root.set_attribute('dataid', refid)
root = Node.void("cardmng")
root.set_attribute("refid", refid)
root.set_attribute("dataid", refid)
# Unsure what this does, but it appears not to matter so we set it to my best guess.
root.set_attribute('newflag', '1' if newflag else '0')
root.set_attribute("newflag", "1" if newflag else "0")
# Whether we've bound a profile to this refid/dataid or not. This includes current profiles and any
# older game profiles that might exist that we should do a conversion from.
root.set_attribute('binded', '1' if bound else '0')
root.set_attribute("binded", "1" if bound else "0")
# Whether this version of the profile is expired (was converted to newer version). We support forwards
# and backwards compatibility so some games will always set this to 0.
root.set_attribute('expired', '1' if expired else '0')
root.set_attribute("expired", "1" if expired else "0")
# Whether to allow paseli, as enabled by the operator and arcade owner.
root.set_attribute('ecflag', '1' if paseli_enabled else '0')
root.set_attribute("ecflag", "1" if paseli_enabled else "0")
# I have absolutely no idea what these do.
root.set_attribute('useridflag', '1')
root.set_attribute('extidflag', '1')
root.set_attribute("useridflag", "1")
root.set_attribute("extidflag", "1")
return root
def handle_cardmng_authpass_request(self, request: Node) -> Node:
# Given a dataid/refid previously found via inquire, verify the pin
refid = request.attribute('refid')
pin = request.attribute('pass')
refid = request.attribute("refid")
pin = request.attribute("pass")
userid = self.data.local.user.from_refid(self.game, self.version, refid)
if userid is not None:
valid = self.data.local.user.validate_pin(userid, pin)
else:
valid = False
root = Node.void('cardmng')
root.set_attribute('status', str(Status.SUCCESS if valid else Status.INVALID_PIN))
root = Node.void("cardmng")
root.set_attribute(
"status", str(Status.SUCCESS if valid else Status.INVALID_PIN)
)
return root
def handle_cardmng_getrefid_request(self, request: Node) -> Node:
# Given a cardid and a pin, register the card with the system and generate a new dataid/refid + extid
cardid = request.attribute('cardid')
pin = request.attribute('passwd')
cardid = request.attribute("cardid")
pin = request.attribute("passwd")
userid = self.data.local.user.create_account(cardid, pin)
if userid is None:
# This user can't be created
root = Node.void('cardmng')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("cardmng")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
refid = self.data.local.user.create_refid(self.game, self.version, userid)
root = Node.void('cardmng')
root.set_attribute('dataid', refid)
root.set_attribute('refid', refid)
root = Node.void("cardmng")
root.set_attribute("dataid", refid)
root.set_attribute("refid", refid)
return root
def handle_cardmng_bindmodel_request(self, request: Node) -> Node:
# Given a refid, bind the user's card to the current version of the game
refid = request.attribute('refid')
refid = request.attribute("refid")
userid = self.data.local.user.from_refid(self.game, self.version, refid)
self.bind_profile(userid)
root = Node.void('cardmng')
root.set_attribute('dataid', refid)
root = Node.void("cardmng")
root.set_attribute("dataid", refid)
return root
def handle_cardmng_getkeepspan_request(self, request: Node) -> Node:
# Unclear what this method does, return an arbitrary span
root = Node.void('cardmng')
root.set_attribute('keepspan', '30')
root = Node.void("cardmng")
root.set_attribute("keepspan", "30")
return root
def handle_cardmng_getdatalist_request(self, request: Node) -> Node:
# Unclear what this method does, return a dummy response
root = Node.void('cardmng')
root = Node.void("cardmng")
return root

View File

@ -16,42 +16,45 @@ class CoreHandler(Base):
each server which handles a particular service. For us, this is always
our URL since we serve everything.
"""
def item(name: str, url: str) -> Node:
node = Node.void('item')
node.set_attribute('name', name)
node.set_attribute('url', url)
node = Node.void("item")
node.set_attribute("name", name)
node.set_attribute("url", url)
return node
url = f'{"https" if self.config.server.https else "http"}://{self.config.server.address}:{self.config.server.port}/'
root = Node.void('services')
root.set_attribute('expire', '600')
root = Node.void("services")
root.set_attribute("expire", "600")
# This can be set to 'operation', 'debug', 'test', and 'factory'.
root.set_attribute('mode', 'operation')
root.set_attribute('product_domain', '1')
root.set_attribute("mode", "operation")
root.set_attribute("product_domain", "1")
root.add_child(item('cardmng', url))
root.add_child(item('dlstatus', url))
root.add_child(item('eacoin', url))
root.add_child(item('facility', url))
root.add_child(item('lobby', url))
root.add_child(item('local', url))
root.add_child(item('message', url))
root.add_child(item('package', url))
root.add_child(item('pcbevent', url))
root.add_child(item('pcbtracker', url))
root.add_child(item('pkglist', url))
root.add_child(item('posevent', url))
root.add_child(item("cardmng", url))
root.add_child(item("dlstatus", url))
root.add_child(item("eacoin", url))
root.add_child(item("facility", url))
root.add_child(item("lobby", url))
root.add_child(item("local", url))
root.add_child(item("message", url))
root.add_child(item("package", url))
root.add_child(item("pcbevent", url))
root.add_child(item("pcbtracker", url))
root.add_child(item("pkglist", url))
root.add_child(item("posevent", url))
for srv in self.extra_services:
root.add_child(item(srv, url))
root.add_child(item('ntp', 'ntp://pool.ntp.org/'))
root.add_child(item("ntp", "ntp://pool.ntp.org/"))
# Translate keepalive to a raw IP because we can't give out a host here
keepalive = socket.gethostbyname(self.config.server.keepalive)
root.add_child(item(
'keepalive',
f'http://{keepalive}/core/keepalive?pa={keepalive}&ia={keepalive}&ga={keepalive}&ma={keepalive}&t1=2&t2=10',
))
root.add_child(
item(
"keepalive",
f"http://{keepalive}/core/keepalive?pa={keepalive}&ia={keepalive}&ga={keepalive}&ma={keepalive}&t1=2&t2=10",
)
)
return root
def handle_pcbtracker_alive_request(self, request: Node) -> Node:
@ -60,9 +63,12 @@ class CoreHandler(Base):
which returns whether PASELI should be active or not for this session.
"""
# Reports that a machine is booting. Overloaded to enable/disable paseli
root = Node.void('pcbtracker')
root.set_attribute('ecenable', '1' if (self.supports_paseli and self.config.paseli.enabled) else '0')
root.set_attribute('expire', '600')
root = Node.void("pcbtracker")
root.set_attribute(
"ecenable",
"1" if (self.supports_paseli and self.config.paseli.enabled) else "0",
)
root.set_attribute("expire", "600")
return root
def handle_pcbevent_put_request(self, request: Node) -> Node:
@ -70,23 +76,23 @@ class CoreHandler(Base):
Handle a PCBEvent request. We do nothing for this aside from logging the event.
"""
for item in request.children:
if item.name == 'item':
name = item.child_value('name')
value = item.child_value('value')
timestamp = item.child_value('time')
if item.name == "item":
name = item.child_value("name")
value = item.child_value("value")
timestamp = item.child_value("time")
self.data.local.network.put_event(
'pcbevent',
"pcbevent",
{
'name': name,
'value': value,
'model': str(self.model),
'pcbid': self.config.machine.pcbid,
'ip': self.config.client.address,
"name": name,
"value": value,
"model": str(self.model),
"pcbid": self.config.machine.pcbid,
"ip": self.config.client.address,
},
timestamp=timestamp,
)
return Node.void('pcbevent')
return Node.void("pcbevent")
def handle_package_list_request(self, request: Node) -> Node:
"""
@ -94,8 +100,8 @@ class CoreHandler(Base):
We don't support this at the moment.
"""
# List all available update packages on the server
root = Node.void('package')
root.set_attribute('expire', '600')
root = Node.void("package")
root.set_attribute("expire", "600")
return root
def handle_message_get_request(self, request: Node) -> Node:
@ -103,8 +109,8 @@ class CoreHandler(Base):
I have absolutely no fucking idea what this does, but it might be for
operator messages?
"""
root = Node.void('message')
root.set_attribute('expire', '600')
root = Node.void("message")
root.set_attribute("expire", "600")
return root
def handle_dlstatus_progress_request(self, request: Node) -> Node:
@ -112,7 +118,7 @@ class CoreHandler(Base):
I have absolutely no fucking idea what this does either, download
status reports maybe?
"""
return Node.void('dlstatus')
return Node.void("dlstatus")
def handle_facility_get_request(self, request: Node) -> Node:
"""
@ -166,45 +172,57 @@ class CoreHandler(Base):
country = "XX"
regionstr = ""
root = Node.void('facility')
root.set_attribute('expire', '600')
location = Node.void('location')
location.add_child(Node.string('id', ID.format_machine_id(machine.id, region=country)))
location.add_child(Node.string('country', country))
location.add_child(Node.string('region', regionstr))
location.add_child(Node.string('name', machine.name))
location.add_child(Node.u8('type', 0))
root = Node.void("facility")
root.set_attribute("expire", "600")
location = Node.void("location")
location.add_child(
Node.string("id", ID.format_machine_id(machine.id, region=country))
)
location.add_child(Node.string("country", country))
location.add_child(Node.string("region", regionstr))
location.add_child(Node.string("name", machine.name))
location.add_child(Node.u8("type", 0))
line = Node.void('line')
line.add_child(Node.string('id', '.'))
line.add_child(Node.u8('class', 0))
line = Node.void("line")
line.add_child(Node.string("id", "."))
line.add_child(Node.u8("class", 0))
portfw = Node.void('portfw')
portfw.add_child(Node.ipv4('globalip', self.config.client.address))
portfw.add_child(Node.u16('globalport', machine.port))
portfw.add_child(Node.u16('privateport', machine.port))
portfw = Node.void("portfw")
portfw.add_child(Node.ipv4("globalip", self.config.client.address))
portfw.add_child(Node.u16("globalport", machine.port))
portfw.add_child(Node.u16("privateport", machine.port))
public = Node.void('public')
public.add_child(Node.u8('flag', 1))
public.add_child(Node.string('name', '.'))
public.add_child(Node.string('latitude', '0'))
public.add_child(Node.string('longitude', '0'))
public = Node.void("public")
public.add_child(Node.u8("flag", 1))
public.add_child(Node.string("name", "."))
public.add_child(Node.string("latitude", "0"))
public.add_child(Node.string("longitude", "0"))
share = Node.void('share')
eacoin = Node.void('eacoin')
eacoin.add_child(Node.s32('notchamount', 3000))
eacoin.add_child(Node.s32('notchcount', 3))
eacoin.add_child(Node.s32('supplylimit', 10000))
share = Node.void("share")
eacoin = Node.void("eacoin")
eacoin.add_child(Node.s32("notchamount", 3000))
eacoin.add_child(Node.s32("notchcount", 3))
eacoin.add_child(Node.s32("supplylimit", 10000))
eapass = Node.void('eapass')
eapass.add_child(Node.u16('valid', 365))
eapass = Node.void("eapass")
eapass.add_child(Node.u16("valid", 365))
url = Node.void('url')
url.add_child(Node.string('eapass', self.config.server.uri or 'www.ea-pass.konami.net'))
url.add_child(Node.string('arcadefan', self.config.server.uri or 'www.konami.jp/am'))
url.add_child(Node.string('konaminetdx', self.config.server.uri or 'http://am.573.jp'))
url.add_child(Node.string('konamiid', self.config.server.uri or 'https://id.konami.net'))
url.add_child(Node.string('eagate', self.config.server.uri or 'http://eagate.573.jp'))
url = Node.void("url")
url.add_child(
Node.string("eapass", self.config.server.uri or "www.ea-pass.konami.net")
)
url.add_child(
Node.string("arcadefan", self.config.server.uri or "www.konami.jp/am")
)
url.add_child(
Node.string("konaminetdx", self.config.server.uri or "http://am.573.jp")
)
url.add_child(
Node.string("konamiid", self.config.server.uri or "https://id.konami.net")
)
url.add_child(
Node.string("eagate", self.config.server.uri or "http://eagate.573.jp")
)
share.add_child(eacoin)
share.add_child(url)

View File

@ -27,32 +27,32 @@ class PASELIHandler(Base):
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
root = Node.void('eacoin')
cardid = request.child_value('cardid')
pin = request.child_value('passwd')
root = Node.void("eacoin")
cardid = request.child_value("cardid")
pin = request.child_value("passwd")
if cardid is None or pin is None:
# Refuse to return anything
print("Invalid eacoin checkin request, missing cardid or pin")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
userid = self.data.local.user.from_cardid(cardid)
if userid is None:
# Refuse to do anything
print("No user for eacoin checkin request")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
valid = self.data.local.user.validate_pin(userid, pin)
if not valid:
# Refuse to do anything
print("User entered invalid pin for eacoin checkin request")
root.set_attribute('status', str(Status.INVALID_PIN))
root.set_attribute("status", str(Status.INVALID_PIN))
return root
session = self.data.local.user.create_session(userid)
@ -65,75 +65,77 @@ class PASELIHandler(Base):
# enabled, so there's no way to find a balance.
balance = 0
else:
balance = self.data.local.user.get_balance(userid, self.config.machine.arcade)
balance = self.data.local.user.get_balance(
userid, self.config.machine.arcade
)
root.add_child(Node.s16('sequence', 0))
root.add_child(Node.u8('acstatus', 0))
root.add_child(Node.string('acid', 'DUMMY_ID'))
root.add_child(Node.string('acname', 'DUMMY_NAME'))
root.add_child(Node.s32('balance', balance))
root.add_child(Node.string('sessid', session))
root.add_child(Node.s16("sequence", 0))
root.add_child(Node.u8("acstatus", 0))
root.add_child(Node.string("acid", "DUMMY_ID"))
root.add_child(Node.string("acname", "DUMMY_NAME"))
root.add_child(Node.s32("balance", balance))
root.add_child(Node.string("sessid", session))
return root
def handle_eacoin_opcheckin_request(self, request: Node) -> Node:
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
root = Node.void('eacoin')
passwd = request.child_value('passwd')
root = Node.void("eacoin")
passwd = request.child_value("passwd")
if passwd is None:
# Refuse to return anything
print("Invalid eacoin checkin request, missing passwd")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
if self.config.machine.arcade is None:
# Machine doesn't belong to an arcade
print("Machine doesn't belong to an arcade")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
arcade = self.data.local.machine.get_arcade(self.config.machine.arcade)
if arcade is None:
# Refuse to do anything
print("No arcade for operator checkin request")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
if arcade.pin != passwd:
# Refuse to do anything
print("User entered invalid pin for operator checkin request")
root.set_attribute('status', str(Status.INVALID_PIN))
root.set_attribute("status", str(Status.INVALID_PIN))
return root
session = self.data.local.machine.create_session(arcade.id)
root.add_child(Node.string('sessid', session))
root.add_child(Node.string("sessid", session))
return root
def handle_eacoin_consume_request(self, request: Node) -> Node:
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
def make_resp(status: int, balance: int) -> Node:
root = Node.void('eacoin')
root.add_child(Node.u8('acstatus', status))
root.add_child(Node.u8('autocharge', 0))
root.add_child(Node.s32('balance', balance))
root = Node.void("eacoin")
root.add_child(Node.u8("acstatus", status))
root.add_child(Node.u8("autocharge", 0))
root.add_child(Node.s32("balance", balance))
return root
session = request.child_value('sessid')
payment = request.child_value('payment')
service = request.child_value('service')
details = request.child_value('detail')
session = request.child_value("sessid")
payment = request.child_value("payment")
service = request.child_value("service")
details = request.child_value("detail")
if session is None or payment is None:
# Refuse to do anything
print("Invalid eacoin consume request, missing sessid or payment")
@ -156,20 +158,27 @@ class PASELIHandler(Base):
else:
# Look up the new balance based on this delta. If there isn't enough,
# we will end up returning None here and exit without performing.
balance = self.data.local.user.update_balance(userid, self.config.machine.arcade, -payment)
balance = self.data.local.user.update_balance(
userid, self.config.machine.arcade, -payment
)
if balance is None:
print("Not enough balance for eacoin consume request")
return make_resp(1, self.data.local.user.get_balance(userid, self.config.machine.arcade))
return make_resp(
1,
self.data.local.user.get_balance(
userid, self.config.machine.arcade
),
)
else:
self.data.local.network.put_event(
'paseli_transaction',
"paseli_transaction",
{
'delta': -payment,
'balance': balance,
'service': -service,
'reason': details,
'pcbid': self.config.machine.pcbid,
"delta": -payment,
"balance": balance,
"service": -service,
"reason": details,
"pcbid": self.config.machine.pcbid,
},
userid=userid,
arcadeid=self.config.machine.arcade,
@ -181,16 +190,16 @@ class PASELIHandler(Base):
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
root = Node.void('eacoin')
sessid = request.child_value('sessid')
logtype = request.child_value('logtype')
target = request.child_value('target')
limit = request.child_value('perpage')
offset = request.child_value('offset')
root = Node.void("eacoin")
sessid = request.child_value("sessid")
logtype = request.child_value("logtype")
target = request.child_value("target")
limit = request.child_value("perpage")
offset = request.child_value("offset")
# Try to determine whether its a user or an arcade session
userid = self.data.local.user.from_session(sessid)
@ -217,47 +226,59 @@ class PASELIHandler(Base):
events = self.data.local.network.get_events(
userid=userid,
arcadeid=arcadeid,
event='paseli_transaction',
event="paseli_transaction",
)
# Further filter it down to the current PCBID
events = [event for event in events if event.data.get('pcbid') == target]
events = [event for event in events if event.data.get("pcbid") == target]
# Grab the end of day today as a timestamp
end_of_today = Time.end_of_today()
time_format = '%Y-%m-%d %H:%M:%S'
date_format = '%Y-%m-%d'
time_format = "%Y-%m-%d %H:%M:%S"
date_format = "%Y-%m-%d"
# Set up common structure
lognode = Node.void(logtype)
topic = Node.void('topic')
topic = Node.void("topic")
lognode.add_child(topic)
summary = Node.void('summary')
summary = Node.void("summary")
lognode.add_child(summary)
# Display what day we are summed to
topic.add_child(Node.string('sumdate', Time.format(Time.now(), date_format)))
topic.add_child(Node.string("sumdate", Time.format(Time.now(), date_format)))
if logtype == 'last7days':
if logtype == "last7days":
# We show today in the today total, last 7 days prior in the week total
beginning_of_today = end_of_today - Time.SECONDS_IN_DAY
end_of_week = beginning_of_today
beginning_of_week = end_of_week - Time.SECONDS_IN_WEEK
topic.add_child(Node.string('sumfrom', Time.format(beginning_of_week, date_format)))
topic.add_child(Node.string('sumto', Time.format(end_of_week, date_format)))
today_total = sum([
-event.data.get_int('delta') for event in events
if event.timestamp >= beginning_of_today and event.timestamp < end_of_today
])
topic.add_child(
Node.string("sumfrom", Time.format(beginning_of_week, date_format))
)
topic.add_child(Node.string("sumto", Time.format(end_of_week, date_format)))
today_total = sum(
[
-event.data.get_int("delta")
for event in events
if event.timestamp >= beginning_of_today
and event.timestamp < end_of_today
]
)
today_total = sum([
-event.data.get_int('delta') for event in events
if event.timestamp >= beginning_of_today and event.timestamp < end_of_today
])
today_total = sum(
[
-event.data.get_int("delta")
for event in events
if event.timestamp >= beginning_of_today
and event.timestamp < end_of_today
]
)
week_txns = [
-event.data.get_int('delta') for event in events
if event.timestamp >= beginning_of_week and event.timestamp < end_of_week
-event.data.get_int("delta")
for event in events
if event.timestamp >= beginning_of_week
and event.timestamp < end_of_week
]
week_total = sum(week_txns)
if len(week_txns) > 0:
@ -272,23 +293,38 @@ class PASELIHandler(Base):
end_of_day = end_of_week - (days * Time.SECONDS_IN_DAY)
start_of_day = end_of_day - Time.SECONDS_IN_DAY
items.append(sum([
-event.data.get_int('delta') for event in events
if event.timestamp >= start_of_day and event.timestamp < end_of_day
]))
items.append(
sum(
[
-event.data.get_int("delta")
for event in events
if event.timestamp >= start_of_day
and event.timestamp < end_of_day
]
)
)
topic.add_child(Node.s32('today', today_total))
topic.add_child(Node.s32('average', week_avg))
topic.add_child(Node.s32('total', week_total))
summary.add_child(Node.s32_array('items', items))
topic.add_child(Node.s32("today", today_total))
topic.add_child(Node.s32("average", week_avg))
topic.add_child(Node.s32("total", week_total))
summary.add_child(Node.s32_array("items", items))
if logtype == 'last52weeks':
if logtype == "last52weeks":
# Start one week back, since the operator can look at last7days for newer stuff.
beginning_of_today = end_of_today - Time.SECONDS_IN_DAY
end_of_52_weeks = beginning_of_today - Time.SECONDS_IN_WEEK
topic.add_child(Node.string('sumfrom', Time.format(end_of_52_weeks - (52 * Time.SECONDS_IN_WEEK), date_format)))
topic.add_child(Node.string('sumto', Time.format(end_of_52_weeks, date_format)))
topic.add_child(
Node.string(
"sumfrom",
Time.format(
end_of_52_weeks - (52 * Time.SECONDS_IN_WEEK), date_format
),
)
)
topic.add_child(
Node.string("sumto", Time.format(end_of_52_weeks, date_format))
)
# We index backwards, where index 0 = the first week back, 1 = the next week back after that, etc...
items = []
@ -296,45 +332,53 @@ class PASELIHandler(Base):
end_of_range = end_of_52_weeks - (weeks * Time.SECONDS_IN_WEEK)
beginning_of_range = end_of_range - Time.SECONDS_IN_WEEK
items.append(sum([
-event.data.get_int('delta') for event in events
if event.timestamp >= beginning_of_range and event.timestamp < end_of_range
]))
items.append(
sum(
[
-event.data.get_int("delta")
for event in events
if event.timestamp >= beginning_of_range
and event.timestamp < end_of_range
]
)
)
summary.add_child(Node.s32_array('items', items))
summary.add_child(Node.s32_array("items", items))
if logtype == 'eachday':
if logtype == "eachday":
start_ts = Time.now()
end_ts = Time.now()
weekdays = [0] * 7
for event in events:
event_day = Time.days_into_week(event.timestamp)
weekdays[event_day] = weekdays[event_day] - event.data.get_int('delta')
weekdays[event_day] = weekdays[event_day] - event.data.get_int("delta")
if event.timestamp < start_ts:
start_ts = event.timestamp
topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format)))
topic.add_child(Node.string('sumto', Time.format(end_ts, date_format)))
summary.add_child(Node.s32_array('items', weekdays))
topic.add_child(Node.string("sumfrom", Time.format(start_ts, date_format)))
topic.add_child(Node.string("sumto", Time.format(end_ts, date_format)))
summary.add_child(Node.s32_array("items", weekdays))
if logtype == 'eachhour':
if logtype == "eachhour":
start_ts = Time.now()
end_ts = Time.now()
hours = [0] * 24
for event in events:
event_hour = int((event.timestamp % Time.SECONDS_IN_DAY) / Time.SECONDS_IN_HOUR)
hours[event_hour] = hours[event_hour] - event.data.get_int('delta')
event_hour = int(
(event.timestamp % Time.SECONDS_IN_DAY) / Time.SECONDS_IN_HOUR
)
hours[event_hour] = hours[event_hour] - event.data.get_int("delta")
if event.timestamp < start_ts:
start_ts = event.timestamp
topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format)))
topic.add_child(Node.string('sumto', Time.format(end_ts, date_format)))
summary.add_child(Node.s32_array('items', hours))
topic.add_child(Node.string("sumfrom", Time.format(start_ts, date_format)))
topic.add_child(Node.string("sumto", Time.format(end_ts, date_format)))
summary.add_child(Node.s32_array("items", hours))
if logtype == 'detail':
history = Node.void('history')
if logtype == "detail":
history = Node.void("history")
lognode.add_child(history)
# Respect details paging
@ -345,7 +389,7 @@ class PASELIHandler(Base):
# Output the details themselves
for event in events:
card_no = ''
card_no = ""
if event.userid is not None:
user = self.data.local.user.get_user(event.userid)
if user is not None:
@ -353,24 +397,30 @@ class PASELIHandler(Base):
if len(cards) > 0:
card_no = CardCipher.encode(cards[0])
item = Node.void('item')
item = Node.void("item")
history.add_child(item)
item.add_child(Node.string('date', Time.format(event.timestamp, time_format)))
item.add_child(Node.s32('consume', -event.data.get_int('delta')))
item.add_child(Node.s32('service', -event.data.get_int('service')))
item.add_child(Node.string('cardtype', ''))
item.add_child(Node.string('cardno', ' ' * self.paseli_padding + card_no))
item.add_child(Node.string('title', ''))
item.add_child(Node.string('systemid', ''))
item.add_child(
Node.string("date", Time.format(event.timestamp, time_format))
)
item.add_child(Node.s32("consume", -event.data.get_int("delta")))
item.add_child(Node.s32("service", -event.data.get_int("service")))
item.add_child(Node.string("cardtype", ""))
item.add_child(
Node.string("cardno", " " * self.paseli_padding + card_no)
)
item.add_child(Node.string("title", ""))
item.add_child(Node.string("systemid", ""))
if logtype == 'lastmonths':
if logtype == "lastmonths":
year, month, _ = Time.todays_date()
this_month = Time.timestamp_from_date(year, month)
last_month = Time.timestamp_from_date(year, month - 1)
month_before = Time.timestamp_from_date(year, month - 2)
topic.add_child(Node.string('sumfrom', Time.format(month_before, date_format)))
topic.add_child(Node.string('sumto', Time.format(this_month, date_format)))
topic.add_child(
Node.string("sumfrom", Time.format(month_before, date_format))
)
topic.add_child(Node.string("sumto", Time.format(this_month, date_format)))
for (start, end) in [(month_before, last_month), (last_month, this_month)]:
year, month, _ = Time.date_from_timestamp(start)
@ -384,18 +434,24 @@ class PASELIHandler(Base):
items.append(0)
else:
# Sum up all the txns for this day
items.append(sum([
-event.data.get_int('delta') for event in events
if event.timestamp >= begin_ts and event.timestamp < end_ts
]))
items.append(
sum(
[
-event.data.get_int("delta")
for event in events
if event.timestamp >= begin_ts
and event.timestamp < end_ts
]
)
)
item = Node.void('item')
item = Node.void("item")
summary.add_child(item)
item.add_child(Node.s32('year', year))
item.add_child(Node.s32('month', month))
item.add_child(Node.s32_array('items', items))
item.add_child(Node.s32("year", year))
item.add_child(Node.s32("month", month))
item.add_child(Node.s32_array("items", items))
root.add_child(Node.u8('processing', 0))
root.add_child(Node.u8("processing", 0))
root.add_child(lognode)
return root
@ -403,37 +459,37 @@ class PASELIHandler(Base):
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
root = Node.void('eacoin')
oldpass = request.child_value('passwd')
newpass = request.child_value('newpasswd')
root = Node.void("eacoin")
oldpass = request.child_value("passwd")
newpass = request.child_value("newpasswd")
if oldpass is None or newpass is None:
# Refuse to return anything
print("Invalid eacoin pass change request, missing passwd")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
if self.config.machine.arcade is None:
# Machine doesn't belong to an arcade
print("Machine doesn't belong to an arcade")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
arcade = self.data.local.machine.get_arcade(self.config.machine.arcade)
if arcade is None:
# Refuse to do anything
print("No arcade for operator pass change request")
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
if arcade.pin != oldpass:
# Refuse to do anything
print("User entered invalid pin for operator pass change request")
root.set_attribute('status', str(Status.INVALID_PIN))
root.set_attribute("status", str(Status.INVALID_PIN))
return root
arcade.pin = newpass
@ -444,30 +500,30 @@ class PASELIHandler(Base):
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
session = request.child_value('sessid')
session = request.child_value("sessid")
if session is not None:
# Destroy the session so it can't be used for any other purchases
self.data.local.user.destroy_session(session)
root = Node.void('eacoin')
root = Node.void("eacoin")
return root
def handle_eacoin_opcheckout_request(self, request: Node) -> Node:
if not self.config.paseli.enabled:
# Refuse to respond, we don't have PASELI enabled
print("PASELI not enabled, ignoring eacoin request")
root = Node.void('eacoin')
root.set_attribute('status', str(Status.NOT_ALLOWED))
root = Node.void("eacoin")
root.set_attribute("status", str(Status.NOT_ALLOWED))
return root
session = request.child_value('sessid')
session = request.child_value("sessid")
if session is not None:
# Destroy the session so it can't be used for any other purchases
self.data.local.machine.destroy_session(session)
root = Node.void('eacoin')
root = Node.void("eacoin")
return root

View File

@ -4,7 +4,14 @@ from typing_extensions import Final
from bemani.backend.base import Base
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
from bemani.common import Model, Profile, ValidatedDict, GameConstants, DBConstants, Time
from bemani.common import (
Model,
Profile,
ValidatedDict,
GameConstants,
DBConstants,
Time,
)
from bemani.data import Config, Data, Score, UserID, ScoreSaveException
from bemani.protocol import Node
@ -54,12 +61,12 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Return the local2 service so that DDR Ace will send certain packets.
extra_services: List[str] = [
'local2',
"local2",
]
def __init__(self, data: Data, config: Config, model: Model) -> None:
super().__init__(data, config, model)
if model.rev == 'X':
if model.rev == "X":
self.omnimix = True
else:
self.omnimix = False
@ -74,39 +81,39 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
"""
Given a game's rank constant, return the rank as defined above.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def db_to_game_rank(self, db_rank: int) -> int:
"""
Given a rank as defined above, return the game's rank constant.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def game_to_db_chart(self, game_chart: int) -> int:
"""
Given a game's chart for a song, return the chart as defined above.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def db_to_game_chart(self, db_chart: int) -> int:
"""
Given a chart as defined above, return the game's chart constant.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def game_to_db_halo(self, game_halo: int) -> int:
"""
Given a game's halo constant, return the halo as defined above.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def db_to_game_halo(self, db_halo: int) -> int:
"""
Given a halo as defined above, return the game's halo constant.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def previous_version(self) -> Optional['DDRBase']:
def previous_version(self) -> Optional["DDRBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -118,16 +125,20 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('game')
return Node.void("game")
def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node:
def format_scores(
self, userid: UserID, profile: Profile, scores: List[Score]
) -> Node:
"""
Base handler for a score list. Given a userid, profile and a score list,
return a Node representing a score list. Should be overridden.
"""
return Node.void('game')
return Node.void("game")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -153,7 +164,9 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Now, return it
return self.format_profile(userid, profile)
def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], area: Optional[int]) -> None:
def new_profile_by_refid(
self, refid: Optional[str], name: Optional[str], area: Optional[int]
) -> None:
"""
Given a RefID and a name/area, create a new profile.
"""
@ -169,8 +182,8 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
refid,
0,
{
'name': name,
'area': area,
"name": name,
"area": area,
},
)
self.put_profile(userid, defaultprofile)
@ -199,8 +212,8 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
rank: int,
halo: int,
combo: int,
trace: Optional[List[int]]=None,
ghost: Optional[str]=None,
trace: Optional[List[int]] = None,
ghost: Optional[str] = None,
) -> None:
"""
Given various pieces of a score, update the user's high score and score
@ -219,7 +232,7 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
self.CHART_DOUBLE_EXPERT,
self.CHART_DOUBLE_CHALLENGE,
]:
raise Exception(f'Invalid chart {chart}')
raise Exception(f"Invalid chart {chart}")
if halo not in [
self.HALO_NONE,
self.HALO_GOOD_FULL_COMBO,
@ -227,7 +240,7 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
self.HALO_PERFECT_FULL_COMBO,
self.HALO_MARVELOUS_FULL_COMBO,
]:
raise Exception(f'Invalid halo {halo}')
raise Exception(f"Invalid halo {halo}")
if rank not in [
self.RANK_E,
self.RANK_D,
@ -246,7 +259,7 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
self.RANK_AA_PLUS,
self.RANK_AAA,
]:
raise Exception(f'Invalid rank {rank}')
raise Exception(f"Invalid rank {rank}")
if userid is not None:
oldscore = self.data.local.music.get_score(
@ -277,26 +290,26 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
scoredata = oldscore.data
# Save combo
history.replace_int('combo', combo)
scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
history.replace_int("combo", combo)
scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo))
# Save halo
history.replace_int('halo', halo)
scoredata.replace_int('halo', max(scoredata.get_int('halo'), halo))
history.replace_int("halo", halo)
scoredata.replace_int("halo", max(scoredata.get_int("halo"), halo))
# Save rank
history.replace_int('rank', rank)
scoredata.replace_int('rank', max(scoredata.get_int('rank'), rank))
history.replace_int("rank", rank)
scoredata.replace_int("rank", max(scoredata.get_int("rank"), rank))
# Save ghost steps
if trace is not None:
history.replace_int_array('trace', len(trace), trace)
history.replace_int_array("trace", len(trace), trace)
if raised:
scoredata.replace_int_array('trace', len(trace), trace)
scoredata.replace_int_array("trace", len(trace), trace)
if ghost is not None:
history.replace_str('ghost', ghost)
history.replace_str("ghost", ghost)
if raised:
scoredata.replace_str('ghost', ghost)
scoredata.replace_str("ghost", ghost)
# Look up where this score was earned
lid = self.get_machine_id()

View File

@ -7,60 +7,52 @@ from bemani.protocol import Node
class DDRGameShopHandler(DDRBase):
def handle_game_shop_request(self, request: Node) -> Node:
self.update_machine_name(request.attribute('name'))
self.update_machine_name(request.attribute("name"))
game = Node.void('game')
game.set_attribute('stop', '0')
game = Node.void("game")
game.set_attribute("stop", "0")
return game
class DDRGameLogHandler(DDRBase):
def handle_game_log_request(self, request: Node) -> Node:
return Node.void('game')
return Node.void("game")
class DDRGameMessageHandler(DDRBase):
def handle_game_message_request(self, request: Node) -> Node:
return Node.void('game')
return Node.void("game")
class DDRGameRankingHandler(DDRBase):
def handle_game_ranking_request(self, request: Node) -> Node:
# Ranking request, unknown what its for
return Node.void('game')
return Node.void("game")
class DDRGameLockHandler(DDRBase):
def handle_game_lock_request(self, request: Node) -> Node:
game = Node.void('game')
game.set_attribute('now_login', '0')
game = Node.void("game")
game.set_attribute("now_login", "0")
return game
class DDRGameTaxInfoHandler(DDRBase):
def handle_game_tax_info_request(self, request: Node) -> Node:
game = Node.void('game')
tax_info = Node.void('tax_info')
game = Node.void("game")
tax_info = Node.void("tax_info")
game.add_child(tax_info)
tax_info.set_attribute('tax_phase', '0')
tax_info.set_attribute("tax_phase", "0")
return game
class DDRGameRecorderHandler(DDRBase):
def handle_game_recorder_request(self, request: Node) -> Node:
return Node.void('game')
return Node.void("game")
class DDRGameHiscoreHandler(DDRBase):
def handle_game_hiscore_request(self, request: Node) -> Node:
records = self.data.remote.music.get_all_records(self.game, self.music_version)
@ -71,13 +63,16 @@ class DDRGameHiscoreHandler(DDRBase):
sortedrecords[score.id] = {}
sortedrecords[score.id][score.chart] = (userid, score)
missing_profiles.append(userid)
users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_profiles)}
users = {
userid: profile
for (userid, profile) in self.get_any_profiles(missing_profiles)
}
game = Node.void('game')
game = Node.void("game")
for song in sortedrecords:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink_num', str(song))
music.set_attribute("reclink_num", str(song))
for chart in sortedrecords[song]:
userid, score = sortedrecords[song][chart]
@ -86,36 +81,44 @@ class DDRGameHiscoreHandler(DDRBase):
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int('rank'))
combo_type = self.db_to_game_halo(score.data.get_int('halo'))
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.string('name', users[userid].get_str('name')))
typenode.add_child(Node.u32('score', score.points))
typenode.add_child(Node.u16('area', users[userid].get_int('area', self.get_machine_region())))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.u32('code', users[userid].extid))
typenode.add_child(Node.string("name", users[userid].get_str("name")))
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(
Node.u16(
"area", users[userid].get_int("area", self.get_machine_region())
)
)
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
typenode.add_child(Node.u32("code", users[userid].extid))
return game
class DDRGameAreaHiscoreHandler(DDRBase):
def handle_game_area_hiscore_request(self, request: Node) -> Node:
shop_area = int(request.attribute('shop_area'))
shop_area = int(request.attribute("shop_area"))
# First, get all users that are in the current shop's area
area_users = {
uid: prof for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version)
if prof.get_int('area', self.get_machine_region()) == shop_area
uid: prof
for (uid, prof) in self.data.local.user.get_all_profiles(
self.game, self.version
)
if prof.get_int("area", self.get_machine_region()) == shop_area
}
# Second, look up records belonging only to those users
records = self.data.local.music.get_all_records(self.game, self.music_version, userlist=list(area_users.keys()))
records = self.data.local.music.get_all_records(
self.game, self.music_version, userlist=list(area_users.keys())
)
# Now, do the same lazy thing as 'hiscore' because I don't want
# to think about how to change this knowing that we only pulled
@ -126,15 +129,18 @@ class DDRGameAreaHiscoreHandler(DDRBase):
area_records[score.id] = {}
area_records[score.id][score.chart] = (userid, score)
game = Node.void('game')
game = Node.void("game")
for song in area_records:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink_num', str(song))
music.set_attribute("reclink_num", str(song))
for chart in area_records[song]:
userid, score = area_records[song][chart]
if area_users[userid].get_int('area', self.get_machine_region()) != shop_area:
if (
area_users[userid].get_int("area", self.get_machine_region())
!= shop_area
):
# Don't return this, this user isn't in this area
continue
try:
@ -142,29 +148,35 @@ class DDRGameAreaHiscoreHandler(DDRBase):
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int('rank'))
combo_type = self.db_to_game_halo(score.data.get_int('halo'))
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.string('name', area_users[userid].get_str('name')))
typenode.add_child(Node.u32('score', score.points))
typenode.add_child(Node.u16('area', area_users[userid].get_int('area', self.get_machine_region())))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.u32('code', area_users[userid].extid))
typenode.add_child(
Node.string("name", area_users[userid].get_str("name"))
)
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(
Node.u16(
"area",
area_users[userid].get_int("area", self.get_machine_region()),
)
)
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
typenode.add_child(Node.u32("code", area_users[userid].extid))
return game
class DDRGameScoreHandler(DDRBase):
def handle_game_score_request(self, request: Node) -> Node:
refid = request.attribute('refid')
songid = int(request.attribute('mid'))
chart = self.game_to_db_chart(int(request.attribute('type')))
refid = request.attribute("refid")
songid = int(request.attribute("mid"))
chart = self.game_to_db_chart(int(request.attribute("type")))
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
@ -185,22 +197,21 @@ class DDRGameScoreHandler(DDRBase):
recentscores.append(0)
# Return the most recent five scores
game = Node.void('game')
game = Node.void("game")
for i in range(len(recentscores)):
game.set_attribute(f'sc{i + 1}', str(recentscores[i]))
game.set_attribute(f"sc{i + 1}", str(recentscores[i]))
return game
class DDRGameTraceHandler(DDRBase):
def handle_game_trace_request(self, request: Node) -> Node:
extid = int(request.attribute('code'))
chart = int(request.attribute('type'))
cid = intish(request.attribute('cid'))
mid = intish(request.attribute('mid'))
extid = int(request.attribute("code"))
chart = int(request.attribute("type"))
cid = intish(request.attribute("cid"))
mid = intish(request.attribute("mid"))
# Base packet is just game, if we find something we add to it
game = Node.void('game')
game = Node.void("game")
# Rival trace loading
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
@ -217,9 +228,9 @@ class DDRGameTraceHandler(DDRBase):
mid,
self.game_to_db_chart(chart),
)
if songscore is not None and 'trace' in songscore.data:
game.add_child(Node.u32('size', len(songscore.data['trace'])))
game.add_child(Node.u8_array('trace', songscore.data['trace']))
if songscore is not None and "trace" in songscore.data:
game.add_child(Node.u32("size", len(songscore.data["trace"])))
game.add_child(Node.u8_array("trace", songscore.data["trace"]))
elif cid is not None:
# Load trace from achievement
@ -228,35 +239,33 @@ class DDRGameTraceHandler(DDRBase):
self.version,
userid,
(cid * 4) + chart,
'course',
"course",
)
if coursescore is not None and 'trace' in coursescore:
game.add_child(Node.u32('size', len(coursescore['trace'])))
game.add_child(Node.u8_array('trace', coursescore['trace']))
if coursescore is not None and "trace" in coursescore:
game.add_child(Node.u32("size", len(coursescore["trace"])))
game.add_child(Node.u8_array("trace", coursescore["trace"]))
# Nothing found, return empty
return game
class DDRGameLoadHandler(DDRBase):
def handle_game_load_request(self, request: Node) -> Node:
refid = request.attribute('refid')
refid = request.attribute("refid")
profile = self.get_profile_by_refid(refid)
if profile is not None:
return profile
game = Node.void('game')
game.set_attribute('none', '0')
game = Node.void("game")
game.set_attribute("none", "0")
return game
class DDRGameLoadDailyHandler(DDRBase):
def handle_game_load_daily_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
refid = request.attribute('refid')
game = Node.void('game')
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
game = Node.void("game")
profiledict = None
if extid is not None:
@ -272,24 +281,23 @@ class DDRGameLoadDailyHandler(DDRBase):
play_stats = self.get_play_statistics(userid)
# Day play counts
daycount = Node.void('daycount')
daycount = Node.void("daycount")
game.add_child(daycount)
daycount.set_attribute('playcount', str(play_stats.today_plays))
daycount.set_attribute("playcount", str(play_stats.today_plays))
# Daily combo stuff, unclear how this works
dailycombo = Node.void('dailycombo')
dailycombo = Node.void("dailycombo")
game.add_child(dailycombo)
dailycombo.set_attribute('daily_combo', str(0))
dailycombo.set_attribute('daily_combo_lv', str(0))
dailycombo.set_attribute("daily_combo", str(0))
dailycombo.set_attribute("daily_combo_lv", str(0))
return game
class DDRGameOldHandler(DDRBase):
def handle_game_old_request(self, request: Node) -> Node:
refid = request.attribute('refid')
game = Node.void('game')
refid = request.attribute("refid")
game = Node.void("game")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
previous_version: Optional[DDRBase] = None
@ -300,41 +308,40 @@ class DDRGameOldHandler(DDRBase):
if previous_version is not None:
oldprofile = previous_version.get_profile(userid)
if oldprofile is not None:
game.set_attribute('name', oldprofile.get_str('name'))
game.set_attribute('area', str(oldprofile.get_int('area', self.get_machine_region())))
game.set_attribute("name", oldprofile.get_str("name"))
game.set_attribute(
"area", str(oldprofile.get_int("area", self.get_machine_region()))
)
return game
class DDRGameNewHandler(DDRBase):
def handle_game_new_request(self, request: Node) -> Node:
refid = request.attribute('refid')
area = int(request.attribute('area'))
name = request.attribute('name').strip()
refid = request.attribute("refid")
area = int(request.attribute("area"))
name = request.attribute("name").strip()
# Create a new profile for this user!
self.new_profile_by_refid(refid, name, area)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game
class DDRGameSaveHandler(DDRBase):
def handle_game_save_request(self, request: Node) -> Node:
refid = request.attribute('refid')
refid = request.attribute("refid")
self.put_profile_by_refid(refid, request)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game
class DDRGameFriendHandler(DDRBase):
def handle_game_friend_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
extid = intish(request.attribute("code"))
userid = None
friend = None
@ -347,62 +354,63 @@ class DDRGameFriendHandler(DDRBase):
if friend is None:
# Return an empty node to tell the game we don't have a player here
game = Node.void('game')
game = Node.void("game")
return game
game = Node.void('game')
game.set_attribute('data', '1')
game.add_child(Node.u32('code', friend.extid))
game.add_child(Node.string('name', friend.get_str('name')))
game.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region())))
game.add_child(Node.u32('exp', play_stats.get_int('exp')))
game.add_child(Node.u32('star', friend.get_int('star')))
game = Node.void("game")
game.set_attribute("data", "1")
game.add_child(Node.u32("code", friend.extid))
game.add_child(Node.string("name", friend.get_str("name")))
game.add_child(
Node.u8("area", friend.get_int("area", self.get_machine_region()))
)
game.add_child(Node.u32("exp", play_stats.get_int("exp")))
game.add_child(Node.u32("star", friend.get_int("star")))
# Drill rankings
if 'title' in friend:
title = Node.void('title')
if "title" in friend:
title = Node.void("title")
game.add_child(title)
titledict = friend.get_dict('title')
if 't' in titledict:
title.set_attribute('t', str(titledict.get_int('t')))
if 's' in titledict:
title.set_attribute('s', str(titledict.get_int('s')))
if 'd' in titledict:
title.set_attribute('d', str(titledict.get_int('d')))
titledict = friend.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if 'title_gr' in friend:
title_gr = Node.void('title_gr')
if "title_gr" in friend:
title_gr = Node.void("title_gr")
game.add_child(title_gr)
title_grdict = friend.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = friend.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
game.add_child(gr_s)
index = 1
for entry in friend.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
game.add_child(gr_d)
index = 1
for entry in friend.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
return game
class DDRGameLoadCourseHandler(DDRBase):
def handle_game_load_c_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
refid = request.attribute('refid')
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
if extid is not None:
# Rival score loading
@ -413,8 +421,10 @@ class DDRGameLoadCourseHandler(DDRBase):
coursedata = [0] * 3200
if userid is not None:
for course in self.data.local.user.get_achievements(self.game, self.version, userid):
if course.type != 'course':
for course in self.data.local.user.get_achievements(
self.game, self.version, userid
):
if course.type != "course":
continue
# Grab course ID and chart (kinda pointless because we add it right back up
@ -425,35 +435,36 @@ class DDRGameLoadCourseHandler(DDRBase):
# Populate course data
index = ((courseid * 4) + coursechart) * 8
if index >= 0 and index <= (len(coursedata) - 8):
coursedata[index + 0] = int(course.data.get_int('score') / 10000)
coursedata[index + 1] = course.data.get_int('score') % 10000
coursedata[index + 2] = course.data.get_int('combo')
coursedata[index + 3] = self.db_to_game_rank(course.data.get_int('rank'))
coursedata[index + 5] = course.data.get_int('stage')
coursedata[index + 6] = course.data.get_int('combo_type')
coursedata[index + 0] = int(course.data.get_int("score") / 10000)
coursedata[index + 1] = course.data.get_int("score") % 10000
coursedata[index + 2] = course.data.get_int("combo")
coursedata[index + 3] = self.db_to_game_rank(
course.data.get_int("rank")
)
coursedata[index + 5] = course.data.get_int("stage")
coursedata[index + 6] = course.data.get_int("combo_type")
game = Node.void('game')
game.add_child(Node.u16_array('course', coursedata))
game = Node.void("game")
game.add_child(Node.u16_array("course", coursedata))
return game
class DDRGameSaveCourseHandler(DDRBase):
def handle_game_save_c_request(self, request: Node) -> Node:
refid = request.attribute('refid')
courseid = int(request.attribute('cid'))
chart = int(request.attribute('ctype'))
refid = request.attribute("refid")
courseid = int(request.attribute("cid"))
chart = int(request.attribute("ctype"))
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
# Calculate statistics
data = request.child('data')
points = int(data.attribute('score'))
combo = int(data.attribute('combo'))
combo_type = int(data.attribute('combo_type'))
stage = int(data.attribute('stage'))
rank = self.game_to_db_rank(int(data.attribute('rank')))
trace = request.child_value('trace')
data = request.child("data")
points = int(data.attribute("score"))
combo = int(data.attribute("combo"))
combo_type = int(data.attribute("combo_type"))
stage = int(data.attribute("stage"))
rank = self.game_to_db_rank(int(data.attribute("rank")))
trace = request.child_value("trace")
# Grab the old course score
oldcourse = self.data.local.user.get_achievement(
@ -461,38 +472,38 @@ class DDRGameSaveCourseHandler(DDRBase):
self.version,
userid,
(courseid * 4) + chart,
'course',
"course",
)
if oldcourse is not None:
highscore = points > oldcourse.get_int('score')
highscore = points > oldcourse.get_int("score")
points = max(points, oldcourse.get_int('score'))
combo = max(combo, oldcourse.get_int('combo'))
stage = max(stage, oldcourse.get_int('stage'))
rank = max(rank, oldcourse.get_int('rank'))
combo_type = max(combo_type, oldcourse.get_int('combo_type'))
points = max(points, oldcourse.get_int("score"))
combo = max(combo, oldcourse.get_int("combo"))
stage = max(stage, oldcourse.get_int("stage"))
rank = max(rank, oldcourse.get_int("rank"))
combo_type = max(combo_type, oldcourse.get_int("combo_type"))
if not highscore:
# Don't overwrite the ghost for a non-highscore
trace = oldcourse.get_int_array('trace', len(trace))
trace = oldcourse.get_int_array("trace", len(trace))
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
(courseid * 4) + chart,
'course',
"course",
{
'score': points,
'combo': combo,
'stage': stage,
'rank': rank,
'combo_type': combo_type,
'trace': trace,
"score": points,
"combo": combo,
"stage": stage,
"rank": rank,
"combo_type": combo_type,
"trace": trace,
},
)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game

View File

@ -53,7 +53,7 @@ class DDR2013(
DDRBase,
):
name: str = 'DanceDanceRevolution 2013'
name: str = "DanceDanceRevolution 2013"
version: int = VersionConstants.DDR_2013
GAME_STYLE_SINGLE: Final[int] = 0
@ -160,30 +160,32 @@ class DDR2013(
return combo_type
def handle_game_common_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
for flagid in range(256):
flag = Node.void('flag')
flag = Node.void("flag")
game.add_child(flag)
flag.set_attribute('id', str(flagid))
flag.set_attribute('t', '0')
flag.set_attribute('s1', '0')
flag.set_attribute('s2', '0')
flag.set_attribute('area', str(self.get_machine_region()))
flag.set_attribute('is_final', '1')
flag.set_attribute("id", str(flagid))
flag.set_attribute("t", "0")
flag.set_attribute("s1", "0")
flag.set_attribute("s2", "0")
flag.set_attribute("area", str(self.get_machine_region()))
flag.set_attribute("is_final", "1")
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS)
hit_chart = self.data.local.music.get_hit_chart(
self.game, self.music_version, self.GAME_MAX_SONGS
)
counts_by_reflink = [0] * self.GAME_MAX_SONGS
for (reflink, plays) in hit_chart:
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
counts_by_reflink[reflink] = plays
game.add_child(Node.u32_array('cnt_music', counts_by_reflink))
game.add_child(Node.u32_array("cnt_music", counts_by_reflink))
return game
def handle_game_load_m_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
refid = request.attribute('refid')
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
if extid is not None:
# Rival score loading
@ -193,7 +195,9 @@ class DDR2013(
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
else:
scores = []
@ -203,11 +207,11 @@ class DDR2013(
sortedscores[score.id] = {}
sortedscores[score.id][score.chart] = score
game = Node.void('game')
game = Node.void("game")
for song in sortedscores:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink', str(song))
music.set_attribute("reclink", str(song))
for chart in sortedscores[song]:
score = sortedscores[song][chart]
@ -216,41 +220,41 @@ class DDR2013(
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int('rank'))
combo_type = self.db_to_game_halo(score.data.get_int('halo'))
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.u32('score', score.points))
typenode.add_child(Node.u16('count', score.plays))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(Node.u16("count", score.plays))
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
return game
def handle_game_save_m_request(self, request: Node) -> Node:
refid = request.attribute('refid')
songid = int(request.attribute('mid'))
chart = self.game_to_db_chart(int(request.attribute('mtype')))
refid = request.attribute("refid")
songid = int(request.attribute("mid"))
chart = self.game_to_db_chart(int(request.attribute("mtype")))
# Calculate statistics
data = request.child('data')
points = int(data.attribute('score'))
combo = int(data.attribute('combo'))
rank = self.game_to_db_rank(int(data.attribute('rank')))
data = request.child("data")
points = int(data.attribute("score"))
combo = int(data.attribute("combo"))
rank = self.game_to_db_rank(int(data.attribute("rank")))
if points == 1000000:
halo = self.HALO_MARVELOUS_FULL_COMBO
elif int(data.attribute('perf_fc')) != 0:
elif int(data.attribute("perf_fc")) != 0:
halo = self.HALO_PERFECT_FULL_COMBO
elif int(data.attribute('great_fc')) != 0:
elif int(data.attribute("great_fc")) != 0:
halo = self.HALO_GREAT_FULL_COMBO
elif int(data.attribute('good_fc')) != 0:
elif int(data.attribute("good_fc")) != 0:
halo = self.HALO_GOOD_FULL_COMBO
else:
halo = self.HALO_NONE
trace = request.child_value('trace')
trace = request.child_value("trace")
# Save the score, regardless of whether we have a refid. If we save
# an anonymous score, it only goes into the DB to count against the
@ -268,355 +272,371 @@ class DDR2013(
)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('game')
root = Node.void("game")
# Look up play stats we bridge to every mix
play_stats = self.get_play_statistics(userid)
# Basic game settings
root.add_child(Node.string('seq', ''))
root.add_child(Node.u32('code', profile.extid))
root.add_child(Node.string('name', profile.get_str('name')))
root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region())))
root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays')))
root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays')))
root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess
root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0')))
root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1')))
root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2')))
root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3')))
root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4')))
root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5')))
root.add_child(Node.u32('exp', play_stats.get_int('exp')))
root.add_child(Node.u32('exp_o', profile.get_int('exp_o')))
root.add_child(Node.u32('star', profile.get_int('star')))
root.add_child(Node.u32('star_c', profile.get_int('star_c')))
root.add_child(Node.u8('combo', profile.get_int('combo', 0)))
root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0)))
root.add_child(Node.string("seq", ""))
root.add_child(Node.u32("code", profile.extid))
root.add_child(Node.string("name", profile.get_str("name")))
root.add_child(
Node.u8("area", profile.get_int("area", self.get_machine_region()))
)
root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays")))
root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays")))
root.add_child(
Node.u32("cnt_b", play_stats.get_int("battle_plays"))
) # This could be wrong, its a guess
root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0")))
root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1")))
root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2")))
root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3")))
root.add_child(Node.u32("cnt_m4", play_stats.get_int("cnt_m4")))
root.add_child(Node.u32("cnt_m5", play_stats.get_int("cnt_m5")))
root.add_child(Node.u32("exp", play_stats.get_int("exp")))
root.add_child(Node.u32("exp_o", profile.get_int("exp_o")))
root.add_child(Node.u32("star", profile.get_int("star")))
root.add_child(Node.u32("star_c", profile.get_int("star_c")))
root.add_child(Node.u8("combo", profile.get_int("combo", 0)))
root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0)))
# Character stuff
chara = Node.void('chara')
chara = Node.void("chara")
root.add_child(chara)
chara.set_attribute('my', str(profile.get_int('chara', 30)))
root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96)))
chara.set_attribute("my", str(profile.get_int("chara", 30)))
root.add_child(
Node.u16_array(
"chara_opt", profile.get_int_array("chara_opt", 96, [208] * 96)
)
)
# Drill rankings
if 'title_gr' in profile:
title_gr = Node.void('title_gr')
if "title_gr" in profile:
title_gr = Node.void("title_gr")
root.add_child(title_gr)
title_grdict = profile.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = profile.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Calorie mode
if 'weight' in profile:
if "weight" in profile:
workouts = self.data.local.user.get_time_based_achievements(
self.game,
self.version,
userid,
achievementtype='workout',
achievementtype="workout",
since=Time.now() - Time.SECONDS_IN_DAY,
)
total = sum([w.data.get_int('calories') for w in workouts])
workout = Node.void('workout')
total = sum([w.data.get_int("calories") for w in workouts])
workout = Node.void("workout")
root.add_child(workout)
workout.set_attribute('weight', str(profile.get_int('weight')))
workout.set_attribute('day', str(total))
workout.set_attribute('disp', '1')
workout.set_attribute("weight", str(profile.get_int("weight")))
workout.set_attribute("day", str(total))
workout.set_attribute("disp", "1")
# Daily play counts
daycount = Node.void('daycount')
daycount = Node.void("daycount")
root.add_child(daycount)
daycount.set_attribute('playcount', str(play_stats.today_plays))
daycount.set_attribute("playcount", str(play_stats.today_plays))
# Daily combo stuff, unknown how this works
dailycombo = Node.void('dailycombo')
dailycombo = Node.void("dailycombo")
root.add_child(dailycombo)
dailycombo.set_attribute('daily_combo', str(0))
dailycombo.set_attribute('daily_combo_lv', str(0))
dailycombo.set_attribute("daily_combo", str(0))
dailycombo.set_attribute("daily_combo_lv", str(0))
# Last cursor settings
last = Node.void('last')
last = Node.void("last")
root.add_child(last)
lastdict = profile.get_dict('last')
last.set_attribute('rival1', str(lastdict.get_int('rival1', -1)))
last.set_attribute('rival2', str(lastdict.get_int('rival2', -1)))
last.set_attribute('rival3', str(lastdict.get_int('rival3', -1)))
last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in 2013
last.set_attribute('style', str(lastdict.get_int('style')))
last.set_attribute('mode', str(lastdict.get_int('mode')))
last.set_attribute('cate', str(lastdict.get_int('cate')))
last.set_attribute('sort', str(lastdict.get_int('sort')))
last.set_attribute('mid', str(lastdict.get_int('mid')))
last.set_attribute('mtype', str(lastdict.get_int('mtype')))
last.set_attribute('cid', str(lastdict.get_int('cid')))
last.set_attribute('ctype', str(lastdict.get_int('ctype')))
last.set_attribute('sid', str(lastdict.get_int('sid')))
lastdict = profile.get_dict("last")
last.set_attribute("rival1", str(lastdict.get_int("rival1", -1)))
last.set_attribute("rival2", str(lastdict.get_int("rival2", -1)))
last.set_attribute("rival3", str(lastdict.get_int("rival3", -1)))
last.set_attribute(
"fri", str(lastdict.get_int("rival1", -1))
) # This literally goes to the same memory in 2013
last.set_attribute("style", str(lastdict.get_int("style")))
last.set_attribute("mode", str(lastdict.get_int("mode")))
last.set_attribute("cate", str(lastdict.get_int("cate")))
last.set_attribute("sort", str(lastdict.get_int("sort")))
last.set_attribute("mid", str(lastdict.get_int("mid")))
last.set_attribute("mtype", str(lastdict.get_int("mtype")))
last.set_attribute("cid", str(lastdict.get_int("cid")))
last.set_attribute("ctype", str(lastdict.get_int("ctype")))
last.set_attribute("sid", str(lastdict.get_int("sid")))
# Result stars
result_star = Node.void('result_star')
result_star = Node.void("result_star")
root.add_child(result_star)
result_stars = profile.get_int_array('result_stars', 9)
result_stars = profile.get_int_array("result_stars", 9)
for i in range(9):
result_star.set_attribute(f'slot{i + 1}', str(result_stars[i]))
result_star.set_attribute(f"slot{i + 1}", str(result_stars[i]))
# Target stuff
target = Node.void('target')
target = Node.void("target")
root.add_child(target)
target.set_attribute('flag', str(profile.get_int('target_flag')))
target.set_attribute('setnum', str(profile.get_int('target_setnum')))
target.set_attribute("flag", str(profile.get_int("target_flag")))
target.set_attribute("setnum", str(profile.get_int("target_setnum")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
root.add_child(gr_s)
index = 1
for entry in profile.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
root.add_child(gr_d)
index = 1
for entry in profile.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Options in menus
root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16)))
root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16)))
root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16)))
root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16)))
# Unlock flags
root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256)))
root.add_child(
Node.u8_array("flag", profile.get_int_array("flag", 256, [1] * 256))
)
# Ranking display?
root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100)))
root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100)))
# Rivals
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
friend = self.get_profile(link.other_userid)
play_stats = self.get_play_statistics(link.other_userid)
if friend is not None:
friendnode = Node.void('friend')
friendnode = Node.void("friend")
root.add_child(friendnode)
friendnode.set_attribute('pos', str(pos))
friendnode.set_attribute('vs', '0')
friendnode.set_attribute('up', '0')
friendnode.add_child(Node.u32('code', friend.extid))
friendnode.add_child(Node.string('name', friend.get_str('name')))
friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region())))
friendnode.add_child(Node.u32('exp', play_stats.get_int('exp')))
friendnode.add_child(Node.u32('star', friend.get_int('star')))
friendnode.set_attribute("pos", str(pos))
friendnode.set_attribute("vs", "0")
friendnode.set_attribute("up", "0")
friendnode.add_child(Node.u32("code", friend.extid))
friendnode.add_child(Node.string("name", friend.get_str("name")))
friendnode.add_child(
Node.u8("area", friend.get_int("area", self.get_machine_region()))
)
friendnode.add_child(Node.u32("exp", play_stats.get_int("exp")))
friendnode.add_child(Node.u32("star", friend.get_int("star")))
# Drill rankings
if 'title' in friend:
title = Node.void('title')
if "title" in friend:
title = Node.void("title")
friendnode.add_child(title)
titledict = friend.get_dict('title')
if 't' in titledict:
title.set_attribute('t', str(titledict.get_int('t')))
if 's' in titledict:
title.set_attribute('s', str(titledict.get_int('s')))
if 'd' in titledict:
title.set_attribute('d', str(titledict.get_int('d')))
titledict = friend.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if 'title_gr' in friend:
title_gr = Node.void('title_gr')
if "title_gr" in friend:
title_gr = Node.void("title_gr")
friendnode.add_child(title_gr)
title_grdict = friend.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = friend.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
friendnode.add_child(gr_s)
index = 1
for entry in friend.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
friendnode.add_child(gr_d)
index = 1
for entry in friend.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Play area
areas = profile.get_int_array('play_area', 55)
play_area = Node.void('play_area')
areas = profile.get_int_array("play_area", 55)
play_area = Node.void("play_area")
root.add_child(play_area)
for i in range(len(areas)):
play_area.set_attribute(f'play_cnt{i}', str(areas[i]))
play_area.set_attribute(f"play_cnt{i}", str(areas[i]))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
play_stats = self.get_play_statistics(userid)
# Grab last node and accessories so we can make decisions based on type
last = request.child('last')
lastdict = newprofile.get_dict('last')
mode = int(last.attribute('mode'))
style = int(last.attribute('style'))
last = request.child("last")
lastdict = newprofile.get_dict("last")
mode = int(last.attribute("mode"))
style = int(last.attribute("style"))
is_dp = style == self.GAME_STYLE_DOUBLE
# Drill rankings
title = request.child('title')
title_gr = request.child('title_gr')
titledict = newprofile.get_dict('title')
title_grdict = newprofile.get_dict('title_gr')
title = request.child("title")
title_gr = request.child("title_gr")
titledict = newprofile.get_dict("title")
title_grdict = newprofile.get_dict("title_gr")
# Groove radar level ups
gr = request.child('gr')
gr = request.child("gr")
# Set the correct values depending on if we're single or double play
if is_dp:
play_stats.increment_int('double_plays')
play_stats.increment_int("double_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_d',
"gr_d",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('d', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("d", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('d', title.value)
newprofile.replace_dict('title_gr', title_grdict)
title_grdict.replace_int("d", title.value)
newprofile.replace_dict("title_gr", title_grdict)
else:
play_stats.increment_int('single_plays')
play_stats.increment_int("single_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_s',
"gr_s",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('s', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("s", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('s', title.value)
newprofile.replace_dict('title_gr', title_grdict)
play_stats.increment_int(f'cnt_m{mode}')
title_grdict.replace_int("s", title.value)
newprofile.replace_dict("title_gr", title_grdict)
play_stats.increment_int(f"cnt_m{mode}")
# Result stars
result_star = request.child('result_star')
result_star = request.child("result_star")
if result_star is not None:
newprofile.replace_int_array(
'result_stars',
"result_stars",
9,
[
intish(result_star.attribute('slot1')),
intish(result_star.attribute('slot2')),
intish(result_star.attribute('slot3')),
intish(result_star.attribute('slot4')),
intish(result_star.attribute('slot5')),
intish(result_star.attribute('slot6')),
intish(result_star.attribute('slot7')),
intish(result_star.attribute('slot8')),
intish(result_star.attribute('slot9')),
intish(result_star.attribute("slot1")),
intish(result_star.attribute("slot2")),
intish(result_star.attribute("slot3")),
intish(result_star.attribute("slot4")),
intish(result_star.attribute("slot5")),
intish(result_star.attribute("slot6")),
intish(result_star.attribute("slot7")),
intish(result_star.attribute("slot8")),
intish(result_star.attribute("slot9")),
],
)
# Target stuff
target = request.child('target')
target = request.child("target")
if target is not None:
newprofile.replace_int('target_flag', intish(target.attribute('flag')))
newprofile.replace_int('target_setnum', intish(target.attribute('setnum')))
newprofile.replace_int("target_flag", intish(target.attribute("flag")))
newprofile.replace_int("target_setnum", intish(target.attribute("setnum")))
# Update last attributes
lastdict.replace_int('rival1', intish(last.attribute('rival1')))
lastdict.replace_int('rival2', intish(last.attribute('rival2')))
lastdict.replace_int('rival3', intish(last.attribute('rival3')))
lastdict.replace_int('style', intish(last.attribute('style')))
lastdict.replace_int('mode', intish(last.attribute('mode')))
lastdict.replace_int('cate', intish(last.attribute('cate')))
lastdict.replace_int('sort', intish(last.attribute('sort')))
lastdict.replace_int('mid', intish(last.attribute('mid')))
lastdict.replace_int('mtype', intish(last.attribute('mtype')))
lastdict.replace_int('cid', intish(last.attribute('cid')))
lastdict.replace_int('ctype', intish(last.attribute('ctype')))
lastdict.replace_int('sid', intish(last.attribute('sid')))
newprofile.replace_dict('last', lastdict)
lastdict.replace_int("rival1", intish(last.attribute("rival1")))
lastdict.replace_int("rival2", intish(last.attribute("rival2")))
lastdict.replace_int("rival3", intish(last.attribute("rival3")))
lastdict.replace_int("style", intish(last.attribute("style")))
lastdict.replace_int("mode", intish(last.attribute("mode")))
lastdict.replace_int("cate", intish(last.attribute("cate")))
lastdict.replace_int("sort", intish(last.attribute("sort")))
lastdict.replace_int("mid", intish(last.attribute("mid")))
lastdict.replace_int("mtype", intish(last.attribute("mtype")))
lastdict.replace_int("cid", intish(last.attribute("cid")))
lastdict.replace_int("ctype", intish(last.attribute("ctype")))
lastdict.replace_int("sid", intish(last.attribute("sid")))
newprofile.replace_dict("last", lastdict)
# Grab character options
chara = request.child('chara')
chara = request.child("chara")
if chara is not None:
newprofile.replace_int('chara', intish(chara.attribute('my')))
chara_opt = request.child('chara_opt')
newprofile.replace_int("chara", intish(chara.attribute("my")))
chara_opt = request.child("chara_opt")
if chara_opt is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96])
newprofile.replace_int_array("chara_opt", 96, chara_opt.value[:96])
# Options
opt = request.child('opt')
opt = request.child("opt")
if opt is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('opt', 16, opt.value[:16])
newprofile.replace_int_array("opt", 16, opt.value[:16])
# Experience and stars
exp = request.child_value('exp')
exp = request.child_value("exp")
if exp is not None:
play_stats.replace_int('exp', play_stats.get_int('exp') + exp)
star = request.child_value('star')
play_stats.replace_int("exp", play_stats.get_int("exp") + exp)
star = request.child_value("star")
if star is not None:
newprofile.replace_int('star', newprofile.get_int('star') + star)
star_c = request.child_value('star_c')
newprofile.replace_int("star", newprofile.get_int("star") + star)
star_c = request.child_value("star_c")
if star_c is not None:
newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp)
newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp)
# Update game flags
for child in request.children:
if child.name != 'flag':
if child.name != "flag":
continue
try:
value = int(child.attribute('data'))
offset = int(child.attribute('no'))
value = int(child.attribute("data"))
offset = int(child.attribute("no"))
except ValueError:
continue
flags = newprofile.get_int_array('flag', 256, [1] * 256)
flags = newprofile.get_int_array("flag", 256, [1] * 256)
if offset < 0 or offset >= len(flags):
continue
flags[offset] = value
newprofile.replace_int_array('flag', 256, flags)
newprofile.replace_int_array("flag", 256, flags)
# Workout mode support
newweight = -1
oldweight = newprofile.get_int('weight')
oldweight = newprofile.get_int("weight")
for child in request.children:
if child.name != 'weight':
if child.name != "weight":
continue
newweight = child.value
if newweight < 0:
@ -625,14 +645,14 @@ class DDR2013(
# Either update or unset the weight depending on the game
if newweight == 0:
# Weight is unset or we declined to use this feature, remove from profile
if 'weight' in newprofile:
del newprofile['weight']
if "weight" in newprofile:
del newprofile["weight"]
else:
# Weight has been set or previously retrieved, we should save calories
newprofile.replace_int('weight', newweight)
newprofile.replace_int("weight", newweight)
total = 0
for child in request.children:
if child.name != 'calory':
if child.name != "calory":
continue
total += child.value
self.data.local.user.put_time_based_achievement(
@ -640,10 +660,10 @@ class DDR2013(
self.version,
userid,
0,
'workout',
"workout",
{
'calories': total,
'weight': newweight,
"calories": total,
"weight": newweight,
},
)
@ -651,7 +671,7 @@ class DDR2013(
oldfriends: List[Optional[UserID]] = [None] * 10
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
@ -660,11 +680,11 @@ class DDR2013(
# Save any rivals that were added/removed/changed
newfriends = oldfriends[:]
for child in request.children:
if child.name != 'friend':
if child.name != "friend":
continue
code = int(child.attribute('code'))
pos = int(child.attribute('pos'))
code = int(child.attribute("code"))
pos = int(child.attribute("pos"))
if pos >= 0 and pos < 10:
if code == 0:
@ -672,7 +692,9 @@ class DDR2013(
newfriends[pos] = None
else:
# Try looking up the userid
newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code)
newfriends[pos] = self.data.remote.user.from_extid(
self.game, self.version, code
)
# Diff the set of links to determine updates
for i in range(10):
@ -685,7 +707,7 @@ class DDR2013(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
elif oldfriends[i] is None:
@ -694,7 +716,7 @@ class DDR2013(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
@ -704,24 +726,24 @@ class DDR2013(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
self.data.local.user.put_link(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
# Play area counter
shop_area = int(request.attribute('shop_area'))
shop_area = int(request.attribute("shop_area"))
if shop_area >= 0 and shop_area < 55:
areas = newprofile.get_int_array('play_area', 55)
areas = newprofile.get_int_array("play_area", 55)
areas[shop_area] = areas[shop_area] + 1
newprofile.replace_int_array('play_area', 55, areas)
newprofile.replace_int_array("play_area", 55, areas)
# Keep track of play statistics
self.update_play_statistics(userid, play_stats)

View File

@ -47,7 +47,7 @@ class DDR2014(
DDRBase,
):
name: str = 'DanceDanceRevolution 2014'
name: str = "DanceDanceRevolution 2014"
version: int = VersionConstants.DDR_2014
GAME_STYLE_SINGLE: Final[int] = 0
@ -154,47 +154,53 @@ class DDR2014(
return combo_type
def handle_game_common_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
for flagid in range(512):
flag = Node.void('flag')
flag = Node.void("flag")
game.add_child(flag)
flag.set_attribute('id', str(flagid))
flag.set_attribute('t', '0')
flag.set_attribute('s1', '0')
flag.set_attribute('s2', '0')
flag.set_attribute('area', str(self.get_machine_region()))
flag.set_attribute('is_final', '0')
flag.set_attribute("id", str(flagid))
flag.set_attribute("t", "0")
flag.set_attribute("s1", "0")
flag.set_attribute("s2", "0")
flag.set_attribute("area", str(self.get_machine_region()))
flag.set_attribute("is_final", "0")
# Last month's hit chart
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS, 30)
hit_chart = self.data.local.music.get_hit_chart(
self.game, self.music_version, self.GAME_MAX_SONGS, 30
)
counts_by_reflink = [0] * self.GAME_MAX_SONGS
for (reflink, plays) in hit_chart:
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
counts_by_reflink[reflink] = plays
game.add_child(Node.u32_array('cnt_music_monthly', counts_by_reflink))
game.add_child(Node.u32_array("cnt_music_monthly", counts_by_reflink))
# Last week's hit chart
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS, 7)
hit_chart = self.data.local.music.get_hit_chart(
self.game, self.music_version, self.GAME_MAX_SONGS, 7
)
counts_by_reflink = [0] * self.GAME_MAX_SONGS
for (reflink, plays) in hit_chart:
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
counts_by_reflink[reflink] = plays
game.add_child(Node.u32_array('cnt_music_weekly', counts_by_reflink))
game.add_child(Node.u32_array("cnt_music_weekly", counts_by_reflink))
# Last day's hit chart
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS, 1)
hit_chart = self.data.local.music.get_hit_chart(
self.game, self.music_version, self.GAME_MAX_SONGS, 1
)
counts_by_reflink = [0] * self.GAME_MAX_SONGS
for (reflink, plays) in hit_chart:
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
counts_by_reflink[reflink] = plays
game.add_child(Node.u32_array('cnt_music_daily', counts_by_reflink))
game.add_child(Node.u32_array("cnt_music_daily", counts_by_reflink))
return game
def handle_game_load_m_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
refid = request.attribute('refid')
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
if extid is not None:
# Rival score loading
@ -204,7 +210,9 @@ class DDR2014(
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
else:
scores = []
@ -214,11 +222,11 @@ class DDR2014(
sortedscores[score.id] = {}
sortedscores[score.id][score.chart] = score
game = Node.void('game')
game = Node.void("game")
for song in sortedscores:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink', str(song))
music.set_attribute("reclink", str(song))
for chart in sortedscores[song]:
score = sortedscores[song][chart]
@ -227,17 +235,17 @@ class DDR2014(
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int('rank'))
combo_type = self.db_to_game_halo(score.data.get_int('halo'))
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.u32('score', score.points))
typenode.add_child(Node.u16('count', score.plays))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(Node.u16("count", score.plays))
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
# The game optionally receives hard, life8, life4, risky, assist_clear, normal_clear
# u8 values too, and saves music scores with these set, but the UI doesn't appear to
# do anything with them, so we don't care.
@ -245,26 +253,26 @@ class DDR2014(
return game
def handle_game_save_m_request(self, request: Node) -> Node:
refid = request.attribute('refid')
songid = int(request.attribute('mid'))
chart = self.game_to_db_chart(int(request.attribute('mtype')))
refid = request.attribute("refid")
songid = int(request.attribute("mid"))
chart = self.game_to_db_chart(int(request.attribute("mtype")))
# Calculate statistics
data = request.child('data')
points = int(data.attribute('score'))
combo = int(data.attribute('combo'))
rank = self.game_to_db_rank(int(data.attribute('rank')))
data = request.child("data")
points = int(data.attribute("score"))
combo = int(data.attribute("combo"))
rank = self.game_to_db_rank(int(data.attribute("rank")))
if points == 1000000:
halo = self.HALO_MARVELOUS_FULL_COMBO
elif int(data.attribute('perf_fc')) != 0:
elif int(data.attribute("perf_fc")) != 0:
halo = self.HALO_PERFECT_FULL_COMBO
elif int(data.attribute('great_fc')) != 0:
elif int(data.attribute("great_fc")) != 0:
halo = self.HALO_GREAT_FULL_COMBO
elif int(data.attribute('good_fc')) != 0:
elif int(data.attribute("good_fc")) != 0:
halo = self.HALO_GOOD_FULL_COMBO
else:
halo = self.HALO_NONE
trace = request.child_value('trace')
trace = request.child_value("trace")
# Save the score, regardless of whether we have a refid. If we save
# an anonymous score, it only goes into the DB to count against the
@ -282,24 +290,24 @@ class DDR2014(
)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game
def handle_game_load_edit_request(self, request: Node) -> Node:
return Node.void('game')
return Node.void("game")
def handle_game_save_resultshot_request(self, request: Node) -> Node:
return Node.void('game')
return Node.void("game")
def handle_game_trace_request(self, request: Node) -> Node:
# This is almost identical to 2013 and below, except it will never
# even try to request course traces, so we fork from common functionality.
extid = int(request.attribute('code'))
chart = int(request.attribute('type'))
mid = intish(request.attribute('mid'))
extid = int(request.attribute("code"))
chart = int(request.attribute("type"))
mid = intish(request.attribute("mid"))
# Base packet is just game, if we find something we add to it
game = Node.void('game')
game = Node.void("game")
# Rival trace loading
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
@ -315,369 +323,389 @@ class DDR2014(
mid,
self.game_to_db_chart(chart),
)
if songscore is not None and 'trace' in songscore.data:
game.add_child(Node.u32('size', len(songscore.data['trace'])))
game.add_child(Node.u8_array('trace', songscore.data['trace']))
if songscore is not None and "trace" in songscore.data:
game.add_child(Node.u32("size", len(songscore.data["trace"])))
game.add_child(Node.u8_array("trace", songscore.data["trace"]))
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('game')
root = Node.void("game")
# Look up play stats we bridge to every mix
play_stats = self.get_play_statistics(userid)
# Basic game settings
root.add_child(Node.string('seq', ''))
root.add_child(Node.u32('code', profile.extid))
root.add_child(Node.string('name', profile.get_str('name')))
root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region())))
root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays')))
root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays')))
root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess
root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0')))
root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1')))
root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2')))
root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3')))
root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4')))
root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5')))
root.add_child(Node.u32('exp', play_stats.get_int('exp')))
root.add_child(Node.u32('exp_o', profile.get_int('exp_o')))
root.add_child(Node.u32('star', profile.get_int('star')))
root.add_child(Node.u32('star_c', profile.get_int('star_c')))
root.add_child(Node.u8('combo', profile.get_int('combo', 0)))
root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0)))
root.add_child(Node.string("seq", ""))
root.add_child(Node.u32("code", profile.extid))
root.add_child(Node.string("name", profile.get_str("name")))
root.add_child(
Node.u8("area", profile.get_int("area", self.get_machine_region()))
)
root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays")))
root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays")))
root.add_child(
Node.u32("cnt_b", play_stats.get_int("battle_plays"))
) # This could be wrong, its a guess
root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0")))
root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1")))
root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2")))
root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3")))
root.add_child(Node.u32("cnt_m4", play_stats.get_int("cnt_m4")))
root.add_child(Node.u32("cnt_m5", play_stats.get_int("cnt_m5")))
root.add_child(Node.u32("exp", play_stats.get_int("exp")))
root.add_child(Node.u32("exp_o", profile.get_int("exp_o")))
root.add_child(Node.u32("star", profile.get_int("star")))
root.add_child(Node.u32("star_c", profile.get_int("star_c")))
root.add_child(Node.u8("combo", profile.get_int("combo", 0)))
root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0)))
# Character stuff
chara = Node.void('chara')
chara = Node.void("chara")
root.add_child(chara)
chara.set_attribute('my', str(profile.get_int('chara', 30)))
root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96)))
chara.set_attribute("my", str(profile.get_int("chara", 30)))
root.add_child(
Node.u16_array(
"chara_opt", profile.get_int_array("chara_opt", 96, [208] * 96)
)
)
# Drill rankings
if 'title_gr' in profile:
title_gr = Node.void('title_gr')
if "title_gr" in profile:
title_gr = Node.void("title_gr")
root.add_child(title_gr)
title_grdict = profile.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = profile.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Calorie mode
if 'weight' in profile:
if "weight" in profile:
workouts = self.data.local.user.get_time_based_achievements(
self.game,
self.version,
userid,
achievementtype='workout',
achievementtype="workout",
since=Time.now() - Time.SECONDS_IN_DAY,
)
total = sum([w.data.get_int('calories') for w in workouts])
workout = Node.void('workout')
total = sum([w.data.get_int("calories") for w in workouts])
workout = Node.void("workout")
root.add_child(workout)
workout.set_attribute('weight', str(profile.get_int('weight')))
workout.set_attribute('day', str(total))
workout.set_attribute('disp', '1')
workout.set_attribute("weight", str(profile.get_int("weight")))
workout.set_attribute("day", str(total))
workout.set_attribute("disp", "1")
# Unsure if this should be last day, or total calories ever
totalcalorie = Node.void('totalcalorie')
totalcalorie = Node.void("totalcalorie")
root.add_child(totalcalorie)
totalcalorie.set_attribute('total', str(total))
totalcalorie.set_attribute("total", str(total))
# Daily play counts
daycount = Node.void('daycount')
daycount = Node.void("daycount")
root.add_child(daycount)
daycount.set_attribute('playcount', str(play_stats.today_plays))
daycount.set_attribute("playcount", str(play_stats.today_plays))
# Daily combo stuff, unknown how this works
dailycombo = Node.void('dailycombo')
dailycombo = Node.void("dailycombo")
root.add_child(dailycombo)
dailycombo.set_attribute('daily_combo', str(0))
dailycombo.set_attribute('daily_combo_lv', str(0))
dailycombo.set_attribute("daily_combo", str(0))
dailycombo.set_attribute("daily_combo_lv", str(0))
# Last cursor settings
last = Node.void('last')
last = Node.void("last")
root.add_child(last)
lastdict = profile.get_dict('last')
last.set_attribute('rival1', str(lastdict.get_int('rival1', -1)))
last.set_attribute('rival2', str(lastdict.get_int('rival2', -1)))
last.set_attribute('rival3', str(lastdict.get_int('rival3', -1)))
last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in 2014
last.set_attribute('style', str(lastdict.get_int('style')))
last.set_attribute('mode', str(lastdict.get_int('mode')))
last.set_attribute('cate', str(lastdict.get_int('cate')))
last.set_attribute('sort', str(lastdict.get_int('sort')))
last.set_attribute('mid', str(lastdict.get_int('mid')))
last.set_attribute('mtype', str(lastdict.get_int('mtype')))
last.set_attribute('cid', str(lastdict.get_int('cid')))
last.set_attribute('ctype', str(lastdict.get_int('ctype')))
last.set_attribute('sid', str(lastdict.get_int('sid')))
lastdict = profile.get_dict("last")
last.set_attribute("rival1", str(lastdict.get_int("rival1", -1)))
last.set_attribute("rival2", str(lastdict.get_int("rival2", -1)))
last.set_attribute("rival3", str(lastdict.get_int("rival3", -1)))
last.set_attribute(
"fri", str(lastdict.get_int("rival1", -1))
) # This literally goes to the same memory in 2014
last.set_attribute("style", str(lastdict.get_int("style")))
last.set_attribute("mode", str(lastdict.get_int("mode")))
last.set_attribute("cate", str(lastdict.get_int("cate")))
last.set_attribute("sort", str(lastdict.get_int("sort")))
last.set_attribute("mid", str(lastdict.get_int("mid")))
last.set_attribute("mtype", str(lastdict.get_int("mtype")))
last.set_attribute("cid", str(lastdict.get_int("cid")))
last.set_attribute("ctype", str(lastdict.get_int("ctype")))
last.set_attribute("sid", str(lastdict.get_int("sid")))
# Result stars
result_star = Node.void('result_star')
result_star = Node.void("result_star")
root.add_child(result_star)
result_stars = profile.get_int_array('result_stars', 9)
result_stars = profile.get_int_array("result_stars", 9)
for i in range(9):
result_star.set_attribute(f'slot{i + 1}', str(result_stars[i]))
result_star.set_attribute(f"slot{i + 1}", str(result_stars[i]))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
root.add_child(gr_s)
index = 1
for entry in profile.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
root.add_child(gr_d)
index = 1
for entry in profile.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Options in menus
root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16)))
root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16)))
option_ver = Node.void('option_ver')
root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16)))
root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16)))
option_ver = Node.void("option_ver")
root.add_child(option_ver)
option_ver.set_attribute('ver', str(profile.get_int('option_ver', 2)))
if 'option_02' in profile:
root.add_child(Node.s16_array('option_02', profile.get_int_array('option_02', 24)))
option_ver.set_attribute("ver", str(profile.get_int("option_ver", 2)))
if "option_02" in profile:
root.add_child(
Node.s16_array("option_02", profile.get_int_array("option_02", 24))
)
# Unlock flags
root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 512, [1] * 512)[:256]))
root.add_child(Node.u8_array('flag_ex', profile.get_int_array('flag', 512, [1] * 512)))
root.add_child(
Node.u8_array("flag", profile.get_int_array("flag", 512, [1] * 512)[:256])
)
root.add_child(
Node.u8_array("flag_ex", profile.get_int_array("flag", 512, [1] * 512))
)
# Ranking display?
root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100)))
root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100)))
# Rivals
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
friend = self.get_profile(link.other_userid)
play_stats = self.get_play_statistics(link.other_userid)
if friend is not None:
friendnode = Node.void('friend')
friendnode = Node.void("friend")
root.add_child(friendnode)
friendnode.set_attribute('pos', str(pos))
friendnode.set_attribute('vs', '0')
friendnode.set_attribute('up', '0')
friendnode.add_child(Node.u32('code', friend.extid))
friendnode.add_child(Node.string('name', friend.get_str('name')))
friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region())))
friendnode.add_child(Node.u32('exp', play_stats.get_int('exp')))
friendnode.add_child(Node.u32('star', friend.get_int('star')))
friendnode.set_attribute("pos", str(pos))
friendnode.set_attribute("vs", "0")
friendnode.set_attribute("up", "0")
friendnode.add_child(Node.u32("code", friend.extid))
friendnode.add_child(Node.string("name", friend.get_str("name")))
friendnode.add_child(
Node.u8("area", friend.get_int("area", self.get_machine_region()))
)
friendnode.add_child(Node.u32("exp", play_stats.get_int("exp")))
friendnode.add_child(Node.u32("star", friend.get_int("star")))
# Drill rankings
if 'title' in friend:
title = Node.void('title')
if "title" in friend:
title = Node.void("title")
friendnode.add_child(title)
titledict = friend.get_dict('title')
if 't' in titledict:
title.set_attribute('t', str(titledict.get_int('t')))
if 's' in titledict:
title.set_attribute('s', str(titledict.get_int('s')))
if 'd' in titledict:
title.set_attribute('d', str(titledict.get_int('d')))
titledict = friend.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if 'title_gr' in friend:
title_gr = Node.void('title_gr')
if "title_gr" in friend:
title_gr = Node.void("title_gr")
friendnode.add_child(title_gr)
title_grdict = friend.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = friend.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
friendnode.add_child(gr_s)
index = 1
for entry in friend.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
friendnode.add_child(gr_d)
index = 1
for entry in friend.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Target stuff
target = Node.void('target')
target = Node.void("target")
root.add_child(target)
target.set_attribute('flag', str(profile.get_int('target_flag')))
target.set_attribute('setnum', str(profile.get_int('target_setnum')))
target.set_attribute("flag", str(profile.get_int("target_flag")))
target.set_attribute("setnum", str(profile.get_int("target_setnum")))
# Play area
areas = profile.get_int_array('play_area', 55)
play_area = Node.void('play_area')
areas = profile.get_int_array("play_area", 55)
play_area = Node.void("play_area")
root.add_child(play_area)
for i in range(len(areas)):
play_area.set_attribute(f'play_cnt{i}', str(areas[i]))
play_area.set_attribute(f"play_cnt{i}", str(areas[i]))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
play_stats = self.get_play_statistics(userid)
# Grab last node and accessories so we can make decisions based on type
last = request.child('last')
lastdict = newprofile.get_dict('last')
mode = int(last.attribute('mode'))
style = int(last.attribute('style'))
last = request.child("last")
lastdict = newprofile.get_dict("last")
mode = int(last.attribute("mode"))
style = int(last.attribute("style"))
is_dp = style == self.GAME_STYLE_DOUBLE
# Drill rankings
title = request.child('title')
title_gr = request.child('title_gr')
titledict = newprofile.get_dict('title')
title_grdict = newprofile.get_dict('title_gr')
title = request.child("title")
title_gr = request.child("title_gr")
titledict = newprofile.get_dict("title")
title_grdict = newprofile.get_dict("title_gr")
# Groove radar level ups
gr = request.child('gr')
gr = request.child("gr")
# Set the correct values depending on if we're single or double play
if is_dp:
play_stats.increment_int('double_plays')
play_stats.increment_int("double_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_d',
"gr_d",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('d', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("d", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('d', title.value)
newprofile.replace_dict('title_gr', title_grdict)
title_grdict.replace_int("d", title.value)
newprofile.replace_dict("title_gr", title_grdict)
else:
play_stats.increment_int('single_plays')
play_stats.increment_int("single_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_s',
"gr_s",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('s', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("s", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('s', title.value)
newprofile.replace_dict('title_gr', title_grdict)
play_stats.increment_int(f'cnt_m{mode}')
title_grdict.replace_int("s", title.value)
newprofile.replace_dict("title_gr", title_grdict)
play_stats.increment_int(f"cnt_m{mode}")
# Result stars
result_star = request.child('result_star')
result_star = request.child("result_star")
if result_star is not None:
newprofile.replace_int_array(
'result_stars',
"result_stars",
9,
[
intish(result_star.attribute('slot1')),
intish(result_star.attribute('slot2')),
intish(result_star.attribute('slot3')),
intish(result_star.attribute('slot4')),
intish(result_star.attribute('slot5')),
intish(result_star.attribute('slot6')),
intish(result_star.attribute('slot7')),
intish(result_star.attribute('slot8')),
intish(result_star.attribute('slot9')),
intish(result_star.attribute("slot1")),
intish(result_star.attribute("slot2")),
intish(result_star.attribute("slot3")),
intish(result_star.attribute("slot4")),
intish(result_star.attribute("slot5")),
intish(result_star.attribute("slot6")),
intish(result_star.attribute("slot7")),
intish(result_star.attribute("slot8")),
intish(result_star.attribute("slot9")),
],
)
# Target stuff
target = request.child('target')
target = request.child("target")
if target is not None:
newprofile.replace_int('target_flag', intish(target.attribute('flag')))
newprofile.replace_int('target_setnum', intish(target.attribute('setnum')))
newprofile.replace_int("target_flag", intish(target.attribute("flag")))
newprofile.replace_int("target_setnum", intish(target.attribute("setnum")))
# Update last attributes
lastdict.replace_int('rival1', intish(last.attribute('rival1')))
lastdict.replace_int('rival2', intish(last.attribute('rival2')))
lastdict.replace_int('rival3', intish(last.attribute('rival3')))
lastdict.replace_int('style', intish(last.attribute('style')))
lastdict.replace_int('mode', intish(last.attribute('mode')))
lastdict.replace_int('cate', intish(last.attribute('cate')))
lastdict.replace_int('sort', intish(last.attribute('sort')))
lastdict.replace_int('mid', intish(last.attribute('mid')))
lastdict.replace_int('mtype', intish(last.attribute('mtype')))
lastdict.replace_int('cid', intish(last.attribute('cid')))
lastdict.replace_int('ctype', intish(last.attribute('ctype')))
lastdict.replace_int('sid', intish(last.attribute('sid')))
newprofile.replace_dict('last', lastdict)
lastdict.replace_int("rival1", intish(last.attribute("rival1")))
lastdict.replace_int("rival2", intish(last.attribute("rival2")))
lastdict.replace_int("rival3", intish(last.attribute("rival3")))
lastdict.replace_int("style", intish(last.attribute("style")))
lastdict.replace_int("mode", intish(last.attribute("mode")))
lastdict.replace_int("cate", intish(last.attribute("cate")))
lastdict.replace_int("sort", intish(last.attribute("sort")))
lastdict.replace_int("mid", intish(last.attribute("mid")))
lastdict.replace_int("mtype", intish(last.attribute("mtype")))
lastdict.replace_int("cid", intish(last.attribute("cid")))
lastdict.replace_int("ctype", intish(last.attribute("ctype")))
lastdict.replace_int("sid", intish(last.attribute("sid")))
newprofile.replace_dict("last", lastdict)
# Grab character options
chara = request.child('chara')
chara = request.child("chara")
if chara is not None:
newprofile.replace_int('chara', intish(chara.attribute('my')))
chara_opt = request.child('chara_opt')
newprofile.replace_int("chara", intish(chara.attribute("my")))
chara_opt = request.child("chara_opt")
if chara_opt is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96])
newprofile.replace_int_array("chara_opt", 96, chara_opt.value[:96])
# Options
option_02 = request.child('option_02')
option_02 = request.child("option_02")
if option_02 is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('option_02', 24, option_02.value[:24])
newprofile.replace_int_array("option_02", 24, option_02.value[:24])
# Experience and stars
exp = request.child_value('exp')
exp = request.child_value("exp")
if exp is not None:
play_stats.replace_int('exp', play_stats.get_int('exp') + exp)
star = request.child_value('star')
play_stats.replace_int("exp", play_stats.get_int("exp") + exp)
star = request.child_value("star")
if star is not None:
newprofile.replace_int('star', newprofile.get_int('star') + star)
star_c = request.child_value('star_c')
newprofile.replace_int("star", newprofile.get_int("star") + star)
star_c = request.child_value("star_c")
if star_c is not None:
newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp)
newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp)
# Update game flags
for child in request.children:
if child.name not in ['flag', 'flag_ex']:
if child.name not in ["flag", "flag_ex"]:
continue
try:
value = int(child.attribute('data'))
offset = int(child.attribute('no'))
value = int(child.attribute("data"))
offset = int(child.attribute("no"))
except ValueError:
continue
flags = newprofile.get_int_array('flag', 512, [1] * 512)
flags = newprofile.get_int_array("flag", 512, [1] * 512)
if offset < 0 or offset >= len(flags):
continue
flags[offset] = value
newprofile.replace_int_array('flag', 512, flags)
newprofile.replace_int_array("flag", 512, flags)
# Workout mode support
newweight = -1
oldweight = newprofile.get_int('weight')
oldweight = newprofile.get_int("weight")
for child in request.children:
if child.name != 'weight':
if child.name != "weight":
continue
newweight = child.value
if newweight < 0:
@ -686,14 +714,14 @@ class DDR2014(
# Either update or unset the weight depending on the game
if newweight == 0:
# Weight is unset or we declined to use this feature, remove from profile
if 'weight' in newprofile:
del newprofile['weight']
if "weight" in newprofile:
del newprofile["weight"]
else:
# Weight has been set or previously retrieved, we should save calories
newprofile.replace_int('weight', newweight)
newprofile.replace_int("weight", newweight)
total = 0
for child in request.children:
if child.name != 'calory':
if child.name != "calory":
continue
total += child.value
self.data.local.user.put_time_based_achievement(
@ -701,10 +729,10 @@ class DDR2014(
self.version,
userid,
0,
'workout',
"workout",
{
'calories': total,
'weight': newweight,
"calories": total,
"weight": newweight,
},
)
@ -712,7 +740,7 @@ class DDR2014(
oldfriends: List[Optional[UserID]] = [None] * 10
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
@ -721,11 +749,11 @@ class DDR2014(
# Save any rivals that were added/removed/changed
newfriends = oldfriends[:]
for child in request.children:
if child.name != 'friend':
if child.name != "friend":
continue
code = int(child.attribute('code'))
pos = int(child.attribute('pos'))
code = int(child.attribute("code"))
pos = int(child.attribute("pos"))
if pos >= 0 and pos < 10:
if code == 0:
@ -733,7 +761,9 @@ class DDR2014(
newfriends[pos] = None
else:
# Try looking up the userid
newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code)
newfriends[pos] = self.data.remote.user.from_extid(
self.game, self.version, code
)
# Diff the set of links to determine updates
for i in range(10):
@ -746,7 +776,7 @@ class DDR2014(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
elif oldfriends[i] is None:
@ -755,7 +785,7 @@ class DDR2014(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
@ -765,24 +795,24 @@ class DDR2014(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
self.data.local.user.put_link(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
# Play area counter
shop_area = int(request.attribute('shop_area'))
shop_area = int(request.attribute("shop_area"))
if shop_area >= 0 and shop_area < 55:
areas = newprofile.get_int_array('play_area', 55)
areas = newprofile.get_int_array("play_area", 55)
areas[shop_area] = areas[shop_area] + 1
newprofile.replace_int_array('play_area', 55, areas)
newprofile.replace_int_array("play_area", 55, areas)
# Keep track of play statistics
self.update_play_statistics(userid, play_stats)

View File

@ -10,7 +10,7 @@ class DDRA20(
DDRBase,
):
name: str = 'DanceDanceRevolution A20'
name: str = "DanceDanceRevolution A20"
version: int = VersionConstants.DDR_A20
def previous_version(self) -> Optional[DDRBase]:
@ -18,7 +18,7 @@ class DDRA20(
@property
def supports_paseli(self) -> bool:
if self.model.dest != 'J':
if self.model.dest != "J":
# DDR Ace in USA mode doesn't support PASELI properly.
# When in Asia mode it shows PASELI but won't let you select it.
return False

View File

@ -6,7 +6,15 @@ from typing_extensions import Final
from bemani.backend.ess import EventLogHandler
from bemani.backend.ddr.base import DDRBase
from bemani.backend.ddr.ddr2014 import DDR2014
from bemani.common import Profile, ValidatedDict, VersionConstants, CardCipher, Time, ID, intish
from bemani.common import (
Profile,
ValidatedDict,
VersionConstants,
CardCipher,
Time,
ID,
intish,
)
from bemani.data import Data, Achievement, Machine, Score, UserID
from bemani.protocol import Node
@ -16,7 +24,7 @@ class DDRAce(
EventLogHandler,
):
name: str = 'DanceDanceRevolution A'
name: str = "DanceDanceRevolution A"
version: int = VersionConstants.DDR_ACE
GAME_STYLE_SINGLE: Final[int] = 0
@ -106,7 +114,9 @@ class DDRAce(
return DDR2014(self.data, self.config, self.model)
@classmethod
def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]:
def run_scheduled_work(
cls, data: Data, config: Dict[str, Any]
) -> List[Tuple[str, Dict[str, Any]]]:
# DDR Ace has a weird bug where it sends a profile save for a blank
# profile before reading it back when creating a new profile. If there
# is no profile on read-back, it errors out, and it also uses the name
@ -125,20 +135,25 @@ class DDRAce(
events = []
for userid, profile in profiles:
if profile.get_str('name') == "" and profile.get_int('write_time') < several_minutes_ago:
if (
profile.get_str("name") == ""
and profile.get_int("write_time") < several_minutes_ago
):
data.local.user.delete_profile(cls.game, cls.version, userid)
events.append((
'ddr_profile_purge',
{
'userid': userid,
},
))
events.append(
(
"ddr_profile_purge",
{
"userid": userid,
},
)
)
return events
@property
def supports_paseli(self) -> bool:
if self.model.dest != 'J':
if self.model.dest != "J":
# DDR Ace in USA mode doesn't support PASELI properly.
# When in Asia mode it shows PASELI but won't let you select it.
return False
@ -237,19 +252,25 @@ class DDRAce(
return self.GAME_HALO_NONE
def handle_tax_get_phase_request(self, request: Node) -> Node:
tax = Node.void('tax')
tax.add_child(Node.s32('phase', 0))
tax = Node.void("tax")
tax.add_child(Node.s32("phase", 0))
return tax
def __handle_userload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None:
def __handle_userload(
self, userid: Optional[UserID], requestdata: Node, response: Node
) -> None:
has_profile: bool = False
achievements: List[Achievement] = []
scores: List[Score] = []
if userid is not None:
has_profile = self.has_profile(userid)
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
achievements = self.data.local.user.get_achievements(
self.game, self.version, userid
)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
# Place scores into an arrangement for easier distribution to Ace.
scores_by_mcode: Dict[int, List[Optional[Score]]] = {}
@ -260,34 +281,43 @@ class DDRAce(
scores_by_mcode[score.id][self.db_to_game_chart(score.chart)] = score
# First, set new flag
response.add_child(Node.bool('is_new', not has_profile))
response.add_child(Node.bool("is_new", not has_profile))
# Now, return the scores to Ace
for mcode in scores_by_mcode:
music = Node.void('music')
music = Node.void("music")
response.add_child(music)
music.add_child(Node.u32('mcode', mcode))
music.add_child(Node.u32("mcode", mcode))
scores_that_matter = scores_by_mcode[mcode]
while scores_that_matter[-1] is None:
scores_that_matter = scores_that_matter[:-1]
for score in scores_that_matter:
note = Node.void('note')
note = Node.void("note")
music.add_child(note)
if score is None:
note.add_child(Node.u16('count', 0))
note.add_child(Node.u8('rank', 0))
note.add_child(Node.u8('clearkind', 0))
note.add_child(Node.s32('score', 0))
note.add_child(Node.s32('ghostid', 0))
note.add_child(Node.u16("count", 0))
note.add_child(Node.u8("rank", 0))
note.add_child(Node.u8("clearkind", 0))
note.add_child(Node.s32("score", 0))
note.add_child(Node.s32("ghostid", 0))
else:
note.add_child(Node.u16('count', score.plays))
note.add_child(Node.u8('rank', self.db_to_game_rank(score.data.get_int('rank'))))
note.add_child(Node.u8('clearkind', self.db_to_game_halo(score.data.get_int('halo'))))
note.add_child(Node.s32('score', score.points))
note.add_child(Node.s32('ghostid', score.key))
note.add_child(Node.u16("count", score.plays))
note.add_child(
Node.u8(
"rank", self.db_to_game_rank(score.data.get_int("rank"))
)
)
note.add_child(
Node.u8(
"clearkind",
self.db_to_game_halo(score.data.get_int("halo")),
)
)
note.add_child(Node.s32("score", score.points))
note.add_child(Node.s32("ghostid", score.key))
# Active event settings
activeevents = [
@ -331,69 +361,83 @@ class DDRAce(
# Event reward settings
rewards = {
'30': {
"30": {
999: 5,
}
}
# Now handle event progress and activation.
events = {ach.id: ach.data for ach in achievements if ach.type == '9999'}
progress = [ach for ach in achievements if ach.type != '9999']
events = {ach.id: ach.data for ach in achievements if ach.type == "9999"}
progress = [ach for ach in achievements if ach.type != "9999"]
# Make sure we always send a babylon's adventure save event or the game won't send progress
babylon_included = False
for evtprogress in progress:
if evtprogress.id == 999 and evtprogress.type == '30':
if evtprogress.id == 999 and evtprogress.type == "30":
babylon_included = True
break
if not babylon_included:
progress.append(Achievement(
999,
'30',
None,
{
'completed': False,
'progress': 0,
},
))
progress.append(
Achievement(
999,
"30",
None,
{
"completed": False,
"progress": 0,
},
)
)
for event in activeevents:
# Get completion data
playerstats = events.get(event, ValidatedDict({'completed': False}))
playerstats = events.get(event, ValidatedDict({"completed": False}))
# Return the data
eventdata = Node.void('eventdata')
eventdata = Node.void("eventdata")
response.add_child(eventdata)
eventdata.add_child(Node.u32('eventid', event))
eventdata.add_child(Node.s32('eventtype', 9999))
eventdata.add_child(Node.u32('eventno', 0))
eventdata.add_child(Node.s64('condition', 0))
eventdata.add_child(Node.u32('reward', 0))
eventdata.add_child(Node.s32('comptime', 1 if playerstats.get_bool('completed') else 0))
eventdata.add_child(Node.s64('savedata', 0))
eventdata.add_child(Node.u32("eventid", event))
eventdata.add_child(Node.s32("eventtype", 9999))
eventdata.add_child(Node.u32("eventno", 0))
eventdata.add_child(Node.s64("condition", 0))
eventdata.add_child(Node.u32("reward", 0))
eventdata.add_child(
Node.s32("comptime", 1 if playerstats.get_bool("completed") else 0)
)
eventdata.add_child(Node.s64("savedata", 0))
for evtprogress in progress:
# Babylon's adventure progres and anything else the game sends
eventdata = Node.void('eventdata')
eventdata = Node.void("eventdata")
response.add_child(eventdata)
eventdata.add_child(Node.u32('eventid', evtprogress.id))
eventdata.add_child(Node.s32('eventtype', int(evtprogress.type)))
eventdata.add_child(Node.u32('eventno', 0))
eventdata.add_child(Node.s64('condition', 0))
eventdata.add_child(Node.u32('reward', rewards.get(evtprogress.type, {}).get(evtprogress.id)))
eventdata.add_child(Node.s32('comptime', 1 if evtprogress.data.get_bool('completed') else 0))
eventdata.add_child(Node.s64('savedata', evtprogress.data.get_int('progress')))
eventdata.add_child(Node.u32("eventid", evtprogress.id))
eventdata.add_child(Node.s32("eventtype", int(evtprogress.type)))
eventdata.add_child(Node.u32("eventno", 0))
eventdata.add_child(Node.s64("condition", 0))
eventdata.add_child(
Node.u32(
"reward", rewards.get(evtprogress.type, {}).get(evtprogress.id)
)
)
eventdata.add_child(
Node.s32("comptime", 1 if evtprogress.data.get_bool("completed") else 0)
)
eventdata.add_child(
Node.s64("savedata", evtprogress.data.get_int("progress"))
)
def __handle_usersave(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None:
def __handle_usersave(
self, userid: Optional[UserID], requestdata: Node, response: Node
) -> None:
if userid is None:
# the game sends us empty user ID strings when a guest is playing.
# Return early so it doesn't wait a minute and a half to show the
# results screen.
return
if requestdata.child_value('isgameover'):
style = int(requestdata.child_value('playstyle'))
if requestdata.child_value("isgameover"):
style = int(requestdata.child_value("playstyle"))
is_dp = style == self.GAME_STYLE_DOUBLE
# We don't save anything for gameover requests, since we
@ -401,52 +445,52 @@ class DDRAce(
# as a spot to bump play counts and such
play_stats = self.get_play_statistics(userid)
if is_dp:
play_stats.increment_int('double_plays')
play_stats.increment_int("double_plays")
else:
play_stats.increment_int('single_plays')
play_stats.increment_int("single_plays")
self.update_play_statistics(userid, play_stats)
# Now is a good time to check if we have workout mode enabled,
# and if so, store the calories earned for this set.
profile = self.get_profile(userid)
enabled = profile.get_bool('workout_mode')
weight = profile.get_int('weight')
enabled = profile.get_bool("workout_mode")
weight = profile.get_int("weight")
if enabled and weight > 0:
# We enabled weight display, find the calories and save them
total = 0
for child in requestdata.children:
if child.name != 'note':
if child.name != "note":
continue
total = total + (child.child_value('calorie') or 0)
total = total + (child.child_value("calorie") or 0)
self.data.local.user.put_time_based_achievement(
self.game,
self.version,
userid,
0,
'workout',
"workout",
{
'calories': total,
'weight': weight,
"calories": total,
"weight": weight,
},
)
# Find any event updates
for child in requestdata.children:
if child.name != 'event':
if child.name != "event":
continue
# Skip empty events or events we don't support
eventid = child.child_value('eventid')
eventtype = child.child_value('eventtype')
eventid = child.child_value("eventid")
eventtype = child.child_value("eventtype")
if eventid == 0 or eventtype == 0:
continue
# Save data to replay to the client later
completed = child.child_value('comptime') != 0
progress = child.child_value('savedata')
completed = child.child_value("comptime") != 0
progress = child.child_value("savedata")
self.data.local.user.put_achievement(
self.game,
@ -455,8 +499,8 @@ class DDRAce(
eventid,
str(eventtype),
{
'completed': completed,
'progress': progress,
"completed": completed,
"progress": progress,
},
)
@ -466,23 +510,23 @@ class DDRAce(
score = None
stagenum = 0
for child in requestdata.children:
if child.name != 'note':
if child.name != "note":
continue
if child.child_value('stagenum') > stagenum:
if child.child_value("stagenum") > stagenum:
score = child
stagenum = child.child_value('stagenum')
stagenum = child.child_value("stagenum")
if score is None:
raise Exception('Couldn\'t find newest score to save!')
raise Exception("Couldn't find newest score to save!")
songid = score.child_value('mcode')
chart = self.game_to_db_chart(score.child_value('notetype'))
rank = self.game_to_db_rank(score.child_value('rank'))
halo = self.game_to_db_halo(score.child_value('clearkind'))
points = score.child_value('score')
combo = score.child_value('maxcombo')
ghost = score.child_value('ghost')
songid = score.child_value("mcode")
chart = self.game_to_db_chart(score.child_value("notetype"))
rank = self.game_to_db_rank(score.child_value("rank"))
halo = self.game_to_db_halo(score.child_value("clearkind"))
points = score.child_value("score")
combo = score.child_value("maxcombo")
ghost = score.child_value("ghost")
self.update_score(
userid,
songid,
@ -494,15 +538,17 @@ class DDRAce(
ghost=ghost,
)
def __handle_rivalload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None:
data = Node.void('data')
def __handle_rivalload(
self, userid: Optional[UserID], requestdata: Node, response: Node
) -> None:
data = Node.void("data")
response.add_child(data)
data.add_child(Node.s32('recordtype', requestdata.child_value('loadflag')))
data.add_child(Node.s32("recordtype", requestdata.child_value("loadflag")))
thismachine = self.data.local.machine.get_machine(self.config.machine.pcbid)
machines_by_id: Dict[int, Optional[Machine]] = {thismachine.id: thismachine}
loadkind = requestdata.child_value('loadflag')
loadkind = requestdata.child_value("loadflag")
profiles_by_userid: Dict[UserID, Profile] = {}
def get_machine(lid: int) -> Optional[Machine]:
@ -522,7 +568,9 @@ class DDRAce(
if loadkind == self.GAME_RIVAL_TYPE_WORLD:
# Just load all scores for this network
scores = self.data.remote.music.get_all_records(self.game, self.music_version)
scores = self.data.remote.music.get_all_records(
self.game, self.music_version
)
elif loadkind == self.GAME_RIVAL_TYPE_AREA:
if thismachine.arcade is not None:
match_arcade = thismachine.arcade
@ -540,57 +588,71 @@ class DDRAce(
# If we have an arcade to match, see if this user's location matches the arcade.
# If we don't, just match lid directly
if match_arcade is not None:
theirmachine = get_machine(profiledata.get_int('lid'))
theirmachine = get_machine(profiledata.get_int("lid"))
if theirmachine is not None and theirmachine.arcade == match_arcade:
userids.append(userid)
elif match_machine is not None:
if profiledata.get_int('lid') == match_machine:
if profiledata.get_int("lid") == match_machine:
userids.append(userid)
# Load all scores for users in the area
scores = self.data.local.music.get_all_records(self.game, self.music_version, userlist=userids)
scores = self.data.local.music.get_all_records(
self.game, self.music_version, userlist=userids
)
elif loadkind == self.GAME_RIVAL_TYPE_MACHINE:
# Load up all scores and filter them by those earned at this location
scores = self.data.local.music.get_all_records(self.game, self.music_version, locationlist=[thismachine.id])
scores = self.data.local.music.get_all_records(
self.game, self.music_version, locationlist=[thismachine.id]
)
elif loadkind in [
self.GAME_RIVAL_TYPE_RIVAL1,
self.GAME_RIVAL_TYPE_RIVAL2,
self.GAME_RIVAL_TYPE_RIVAL3,
]:
# Load up this user's highscores, format the way the below code expects it
extid = requestdata.child_value('ddrcode')
extid = requestdata.child_value("ddrcode")
otherid = self.data.remote.user.from_extid(self.game, self.version, extid)
userscores = self.data.remote.music.get_scores(self.game, self.music_version, otherid)
userscores = self.data.remote.music.get_scores(
self.game, self.music_version, otherid
)
scores = [(otherid, score) for score in userscores]
else:
# Nothing here
scores = []
missing_users = [userid for (userid, _) in scores if userid not in profiles_by_userid]
missing_users = [
userid for (userid, _) in scores if userid not in profiles_by_userid
]
for (userid, profile) in self.get_any_profiles(missing_users):
profiles_by_userid[userid] = profile
for userid, score in scores:
if profiles_by_userid.get(userid) is None:
raise Exception(f'Logic error, couldn\'t find any profile for {userid}')
raise Exception(f"Logic error, couldn't find any profile for {userid}")
profiledata = profiles_by_userid[userid]
record = Node.void('record')
record = Node.void("record")
data.add_child(record)
record.add_child(Node.u32('mcode', score.id))
record.add_child(Node.u8('notetype', self.db_to_game_chart(score.chart)))
record.add_child(Node.u8('rank', self.db_to_game_rank(score.data.get_int('rank'))))
record.add_child(Node.u8('clearkind', self.db_to_game_halo(score.data.get_int('halo'))))
record.add_child(Node.u8('flagdata', 0))
record.add_child(Node.string('name', profiledata.get_str('name')))
record.add_child(Node.s32('area', profiledata.get_int('area', 58)))
record.add_child(Node.s32('code', profiledata.extid))
record.add_child(Node.s32('score', score.points))
record.add_child(Node.s32('ghostid', score.key))
record.add_child(Node.u32("mcode", score.id))
record.add_child(Node.u8("notetype", self.db_to_game_chart(score.chart)))
record.add_child(
Node.u8("rank", self.db_to_game_rank(score.data.get_int("rank")))
)
record.add_child(
Node.u8("clearkind", self.db_to_game_halo(score.data.get_int("halo")))
)
record.add_child(Node.u8("flagdata", 0))
record.add_child(Node.string("name", profiledata.get_str("name")))
record.add_child(Node.s32("area", profiledata.get_int("area", 58)))
record.add_child(Node.s32("code", profiledata.extid))
record.add_child(Node.s32("score", score.points))
record.add_child(Node.s32("ghostid", score.key))
def __handle_usernew(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None:
def __handle_usernew(
self, userid: Optional[UserID], requestdata: Node, response: Node
) -> None:
if userid is None:
raise Exception('Expecting valid UserID to create new profile!')
raise Exception("Expecting valid UserID to create new profile!")
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
profile = Profile(
@ -599,27 +661,35 @@ class DDRAce(
"",
0,
{
'lid': machine.id,
"lid": machine.id,
},
)
self.put_profile(userid, profile)
response.add_child(Node.string('seq', ID.format_extid(profile.extid)))
response.add_child(Node.s32('code', profile.extid))
response.add_child(Node.string('shoparea', ''))
response.add_child(Node.string("seq", ID.format_extid(profile.extid)))
response.add_child(Node.s32("code", profile.extid))
response.add_child(Node.string("shoparea", ""))
def __handle_inheritance(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None:
def __handle_inheritance(
self, userid: Optional[UserID], requestdata: Node, response: Node
) -> None:
if userid is not None:
previous_version = self.previous_version()
profile = previous_version.get_profile(userid)
else:
profile = None
response.add_child(Node.s32('InheritanceStatus', 1 if profile is not None else 0))
response.add_child(
Node.s32("InheritanceStatus", 1 if profile is not None else 0)
)
def __handle_ghostload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None:
ghostid = requestdata.child_value('ghostid')
ghost = self.data.local.music.get_score_by_key(self.game, self.music_version, ghostid)
def __handle_ghostload(
self, userid: Optional[UserID], requestdata: Node, response: Node
) -> None:
ghostid = requestdata.child_value("ghostid")
ghost = self.data.local.music.get_score_by_key(
self.game, self.music_version, ghostid
)
if ghost is None:
return
@ -628,110 +698,188 @@ class DDRAce(
if profile is None:
return
if 'ghost' not in score.data:
if "ghost" not in score.data:
return
ghostdata = Node.void('ghostdata')
ghostdata = Node.void("ghostdata")
response.add_child(ghostdata)
ghostdata.add_child(Node.s32('code', profile.extid))
ghostdata.add_child(Node.u32('mcode', score.id))
ghostdata.add_child(Node.u8('notetype', self.db_to_game_chart(score.chart)))
ghostdata.add_child(Node.s32('ghostsize', len(score.data['ghost'])))
ghostdata.add_child(Node.string('ghost', score.data['ghost']))
ghostdata.add_child(Node.s32("code", profile.extid))
ghostdata.add_child(Node.u32("mcode", score.id))
ghostdata.add_child(Node.u8("notetype", self.db_to_game_chart(score.chart)))
ghostdata.add_child(Node.s32("ghostsize", len(score.data["ghost"])))
ghostdata.add_child(Node.string("ghost", score.data["ghost"]))
def handle_playerdata_usergamedata_advanced_request(self, request: Node) -> Optional[Node]:
playerdata = Node.void('playerdata')
def handle_playerdata_usergamedata_advanced_request(
self, request: Node
) -> Optional[Node]:
playerdata = Node.void("playerdata")
# DDR Ace decides to be difficult and have a third level of packet switching
mode = request.child_value('data/mode')
refid = request.child_value('data/refid')
extid = request.child_value('data/ddrcode')
mode = request.child_value("data/mode")
refid = request.child_value("data/refid")
extid = request.child_value("data/ddrcode")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
# Possibly look up by extid instead
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if mode == 'userload':
self.__handle_userload(userid, request.child('data'), playerdata)
elif mode == 'usersave':
self.__handle_usersave(userid, request.child('data'), playerdata)
elif mode == 'rivalload':
self.__handle_rivalload(userid, request.child('data'), playerdata)
elif mode == 'usernew':
self.__handle_usernew(userid, request.child('data'), playerdata)
elif mode == 'inheritance':
self.__handle_inheritance(userid, request.child('data'), playerdata)
elif mode == 'ghostload':
self.__handle_ghostload(userid, request.child('data'), playerdata)
if mode == "userload":
self.__handle_userload(userid, request.child("data"), playerdata)
elif mode == "usersave":
self.__handle_usersave(userid, request.child("data"), playerdata)
elif mode == "rivalload":
self.__handle_rivalload(userid, request.child("data"), playerdata)
elif mode == "usernew":
self.__handle_usernew(userid, request.child("data"), playerdata)
elif mode == "inheritance":
self.__handle_inheritance(userid, request.child("data"), playerdata)
elif mode == "ghostload":
self.__handle_ghostload(userid, request.child("data"), playerdata)
else:
# We don't support this
return None
playerdata.add_child(Node.s32('result', 0))
playerdata.add_child(Node.s32("result", 0))
return playerdata
def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node:
playerdata = Node.void('playerdata')
refid = request.child_value('data/refid')
playerdata = Node.void("playerdata")
refid = request.child_value("data/refid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
profile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0)
usergamedata = profile.get_dict('usergamedata')
profile = self.get_profile(userid) or Profile(
self.game, self.version, refid, 0
)
usergamedata = profile.get_dict("usergamedata")
for record in request.child('data/record').children:
if record.name != 'd':
for record in request.child("data/record").children:
if record.name != "d":
continue
strdata = base64.b64decode(record.value)
bindata = base64.b64decode(record.child_value('bin1'))
bindata = base64.b64decode(record.child_value("bin1"))
# Grab and format the profile objects
strdatalist = strdata.split(b',')
profiletype = strdatalist[1].decode('utf-8')
strdatalist = strdata.split(b",")
profiletype = strdatalist[1].decode("utf-8")
strdatalist = strdatalist[2:]
# Extract relevant bits for frontend/API
if profiletype == 'COMMON':
profile.replace_str('name', strdatalist[self.GAME_COMMON_NAME_OFFSET].decode('ascii'))
profile.replace_int('area', intish(strdatalist[self.GAME_COMMON_AREA_OFFSET].decode('ascii'), 16))
profile.replace_bool('workout_mode', int(strdatalist[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET].decode('ascii'), 16) != 0)
profile.replace_int('weight', int(float(strdatalist[self.GAME_COMMON_WEIGHT_OFFSET].decode('ascii')) * 10))
profile.replace_int('character', int(strdatalist[self.GAME_COMMON_CHARACTER_OFFSET].decode('ascii'), 16))
if profiletype == 'OPTION':
profile.replace_int('combo', int(strdatalist[self.GAME_OPTION_COMBO_POSITION_OFFSET].decode('ascii'), 16))
profile.replace_int('early_late', int(strdatalist[self.GAME_OPTION_FAST_SLOW_OFFSET].decode('ascii'), 16))
profile.replace_int('arrowskin', int(strdatalist[self.GAME_OPTION_ARROW_SKIN_OFFSET].decode('ascii'), 16))
profile.replace_int('guidelines', int(strdatalist[self.GAME_OPTION_GUIDELINE_OFFSET].decode('ascii'), 16))
profile.replace_int('filter', int(strdatalist[self.GAME_OPTION_FILTER_OFFSET].decode('ascii'), 16))
if profiletype == "COMMON":
profile.replace_str(
"name",
strdatalist[self.GAME_COMMON_NAME_OFFSET].decode("ascii"),
)
profile.replace_int(
"area",
intish(
strdatalist[self.GAME_COMMON_AREA_OFFSET].decode("ascii"),
16,
),
)
profile.replace_bool(
"workout_mode",
int(
strdatalist[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET].decode(
"ascii"
),
16,
)
!= 0,
)
profile.replace_int(
"weight",
int(
float(
strdatalist[self.GAME_COMMON_WEIGHT_OFFSET].decode(
"ascii"
)
)
* 10
),
)
profile.replace_int(
"character",
int(
strdatalist[self.GAME_COMMON_CHARACTER_OFFSET].decode(
"ascii"
),
16,
),
)
if profiletype == "OPTION":
profile.replace_int(
"combo",
int(
strdatalist[self.GAME_OPTION_COMBO_POSITION_OFFSET].decode(
"ascii"
),
16,
),
)
profile.replace_int(
"early_late",
int(
strdatalist[self.GAME_OPTION_FAST_SLOW_OFFSET].decode(
"ascii"
),
16,
),
)
profile.replace_int(
"arrowskin",
int(
strdatalist[self.GAME_OPTION_ARROW_SKIN_OFFSET].decode(
"ascii"
),
16,
),
)
profile.replace_int(
"guidelines",
int(
strdatalist[self.GAME_OPTION_GUIDELINE_OFFSET].decode(
"ascii"
),
16,
),
)
profile.replace_int(
"filter",
int(
strdatalist[self.GAME_OPTION_FILTER_OFFSET].decode("ascii"),
16,
),
)
usergamedata[profiletype] = {
'strdata': b','.join(strdatalist),
'bindata': bindata,
"strdata": b",".join(strdatalist),
"bindata": bindata,
}
profile.replace_dict('usergamedata', usergamedata)
profile.replace_int('write_time', Time.now())
profile.replace_dict("usergamedata", usergamedata)
profile.replace_int("write_time", Time.now())
self.put_profile(userid, profile)
playerdata.add_child(Node.s32('result', 0))
playerdata.add_child(Node.s32("result", 0))
return playerdata
def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node:
playerdata = Node.void('playerdata')
playerdata = Node.void("playerdata")
player = Node.void('player')
player = Node.void("player")
playerdata.add_child(player)
refid = request.child_value('data/refid')
refid = request.child_value("data/refid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
profile = self.get_profile(userid)
links = self.data.local.user.get_links(self.game, self.version, userid)
records = 0
record = Node.void('record')
record = Node.void("record")
player.add_child(record)
def acehex(val: int) -> str:
@ -739,70 +887,92 @@ class DDRAce(
if profile is None:
# Just return a default empty node
record.add_child(Node.string('d', '<NODATA>'))
record.add_child(Node.string("d", "<NODATA>"))
records = 1
else:
# Figure out what profiles are being requested
profiletypes = request.child_value('data/recv_csv').split(',')[::2]
usergamedata = profile.get_dict('usergamedata')
profiletypes = request.child_value("data/recv_csv").split(",")[::2]
usergamedata = profile.get_dict("usergamedata")
for ptype in profiletypes:
if ptype in usergamedata:
records = records + 1
if ptype == "COMMON":
# Return basic profile options
name = profile.get_str('name')
area = profile.get_int('area', self.get_machine_region())
name = profile.get_str("name")
area = profile.get_int("area", self.get_machine_region())
if name == "":
# This is a bogus profile created by the first login, substitute the
# previous version values so that profile succession works.
previous_version = self.previous_version()
old_profile = previous_version.get_profile(userid)
if old_profile is not None:
name = old_profile.get_str('name')
area = old_profile.get_int('area', self.get_machine_region())
name = old_profile.get_str("name")
area = old_profile.get_int(
"area", self.get_machine_region()
)
else:
area = self.get_machine_region()
common = usergamedata[ptype]['strdata'].split(b',')
common[self.GAME_COMMON_NAME_OFFSET] = name.encode('ascii')
common[self.GAME_COMMON_AREA_OFFSET] = acehex(area).encode('ascii')
common[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET] = b'1' if profile.get_bool('workout_mode') else b'0'
common[self.GAME_COMMON_WEIGHT_OFFSET] = str(float(profile.get_int('weight')) / 10.0).encode('ascii')
common[self.GAME_COMMON_CHARACTER_OFFSET] = acehex(profile.get_int('character')).encode('ascii')
usergamedata[ptype]['strdata'] = b','.join(common)
common = usergamedata[ptype]["strdata"].split(b",")
common[self.GAME_COMMON_NAME_OFFSET] = name.encode("ascii")
common[self.GAME_COMMON_AREA_OFFSET] = acehex(area).encode(
"ascii"
)
common[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET] = (
b"1" if profile.get_bool("workout_mode") else b"0"
)
common[self.GAME_COMMON_WEIGHT_OFFSET] = str(
float(profile.get_int("weight")) / 10.0
).encode("ascii")
common[self.GAME_COMMON_CHARACTER_OFFSET] = acehex(
profile.get_int("character")
).encode("ascii")
usergamedata[ptype]["strdata"] = b",".join(common)
if ptype == "OPTION":
# Return user settings for frontend
option = usergamedata[ptype]['strdata'].split(b',')
option[self.GAME_OPTION_FAST_SLOW_OFFSET] = acehex(profile.get_int('early_late')).encode('ascii')
option[self.GAME_OPTION_COMBO_POSITION_OFFSET] = acehex(profile.get_int('combo')).encode('ascii')
option[self.GAME_OPTION_ARROW_SKIN_OFFSET] = acehex(profile.get_int('arrowskin')).encode('ascii')
option[self.GAME_OPTION_GUIDELINE_OFFSET] = acehex(profile.get_int('guidelines')).encode('ascii')
option[self.GAME_OPTION_FILTER_OFFSET] = acehex(profile.get_int('filter')).encode('ascii')
usergamedata[ptype]['strdata'] = b','.join(option)
option = usergamedata[ptype]["strdata"].split(b",")
option[self.GAME_OPTION_FAST_SLOW_OFFSET] = acehex(
profile.get_int("early_late")
).encode("ascii")
option[self.GAME_OPTION_COMBO_POSITION_OFFSET] = acehex(
profile.get_int("combo")
).encode("ascii")
option[self.GAME_OPTION_ARROW_SKIN_OFFSET] = acehex(
profile.get_int("arrowskin")
).encode("ascii")
option[self.GAME_OPTION_GUIDELINE_OFFSET] = acehex(
profile.get_int("guidelines")
).encode("ascii")
option[self.GAME_OPTION_FILTER_OFFSET] = acehex(
profile.get_int("filter")
).encode("ascii")
usergamedata[ptype]["strdata"] = b",".join(option)
if ptype == "LAST":
# Return the number of calories expended in the last day
workouts = self.data.local.user.get_time_based_achievements(
self.game,
self.version,
userid,
achievementtype='workout',
achievementtype="workout",
since=Time.now() - Time.SECONDS_IN_DAY,
)
total = sum([w.data.get_int('calories') for w in workouts])
total = sum([w.data.get_int("calories") for w in workouts])
last = usergamedata[ptype]['strdata'].split(b',')
last[self.GAME_LAST_CALORIES_OFFSET] = acehex(total).encode('ascii')
usergamedata[ptype]['strdata'] = b','.join(last)
last = usergamedata[ptype]["strdata"].split(b",")
last[self.GAME_LAST_CALORIES_OFFSET] = acehex(total).encode(
"ascii"
)
usergamedata[ptype]["strdata"] = b",".join(last)
if ptype == "RIVAL":
# Fill in the DDR code and active status of the three active
# rivals.
rival = usergamedata[ptype]['strdata'].split(b',')
lastdict = profile.get_dict('last')
rival = usergamedata[ptype]["strdata"].split(b",")
lastdict = profile.get_dict("last")
friends: Dict[int, Optional[Profile]] = {}
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
@ -815,16 +985,16 @@ class DDRAce(
3: self.GAME_RIVAL_SLOT_3_ACTIVE_OFFSET,
}[rivalno]
whichfriend = lastdict.get_int(f'rival{rivalno}') - 1
whichfriend = lastdict.get_int(f"rival{rivalno}") - 1
if whichfriend < 0:
# This rival isn't active
rival[activeslot] = b'0'
rival[activeslot] = b"0"
continue
friendprofile = friends.get(whichfriend)
if friendprofile is None:
# This rival doesn't exist
rival[activeslot] = b'0'
rival[activeslot] = b"0"
continue
ddrcodeslot = {
@ -833,28 +1003,42 @@ class DDRAce(
3: self.GAME_RIVAL_SLOT_3_DDRCODE_OFFSET,
}[rivalno]
rival[activeslot] = acehex(rivalno).encode('ascii')
rival[ddrcodeslot] = acehex(friendprofile.extid).encode('ascii')
rival[activeslot] = acehex(rivalno).encode("ascii")
rival[ddrcodeslot] = acehex(friendprofile.extid).encode(
"ascii"
)
usergamedata[ptype]['strdata'] = b','.join(rival)
usergamedata[ptype]["strdata"] = b",".join(rival)
dnode = Node.string('d', base64.b64encode(usergamedata[ptype]['strdata']).decode('ascii'))
dnode.add_child(Node.string('bin1', base64.b64encode(usergamedata[ptype]['bindata']).decode('ascii')))
dnode = Node.string(
"d",
base64.b64encode(usergamedata[ptype]["strdata"]).decode(
"ascii"
),
)
dnode.add_child(
Node.string(
"bin1",
base64.b64encode(usergamedata[ptype]["bindata"]).decode(
"ascii"
),
)
)
record.add_child(dnode)
player.add_child(Node.u32('record_num', records))
player.add_child(Node.u32("record_num", records))
playerdata.add_child(Node.s32('result', 0))
playerdata.add_child(Node.s32("result", 0))
return playerdata
def handle_system_convcardnumber_request(self, request: Node) -> Node:
cardid = request.child_value('data/card_id')
cardid = request.child_value("data/card_id")
cardnumber = CardCipher.encode(cardid)
system = Node.void('system')
data = Node.void('data')
system = Node.void("system")
data = Node.void("data")
system.add_child(data)
system.add_child(Node.s32('result', 0))
data.add_child(Node.string('card_number', cardnumber))
system.add_child(Node.s32("result", 0))
data.add_child(Node.string("card_number", cardnumber))
return system

View File

@ -43,7 +43,7 @@ class DDRX2(
DDRBase,
):
name: str = 'DanceDanceRevolution X2'
name: str = "DanceDanceRevolution X2"
version: int = VersionConstants.DDR_X2
GAME_STYLE_SINGLE: Final[int] = 0
@ -147,22 +147,24 @@ class DDRX2(
return combo_type
def handle_game_common_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
for flagid in range(256):
flag = Node.void('flag')
flag = Node.void("flag")
game.add_child(flag)
flag.set_attribute('id', str(flagid))
flag.set_attribute('s2', '0')
flag.set_attribute('s1', '0')
flag.set_attribute('t', '0')
flag.set_attribute("id", str(flagid))
flag.set_attribute("s2", "0")
flag.set_attribute("s1", "0")
flag.set_attribute("t", "0")
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS)
hit_chart = self.data.local.music.get_hit_chart(
self.game, self.music_version, self.GAME_MAX_SONGS
)
counts_by_reflink = [0] * self.GAME_MAX_SONGS
for (reflink, plays) in hit_chart:
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
counts_by_reflink[reflink] = plays
game.add_child(Node.u32_array('cnt_music', counts_by_reflink))
game.add_child(Node.u32_array("cnt_music", counts_by_reflink))
return game
@ -179,13 +181,16 @@ class DDRX2(
sortedrecords[score.id] = {}
sortedrecords[score.id][score.chart] = (userid, score)
missing_profiles.append(userid)
users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_profiles)}
users = {
userid: profile
for (userid, profile) in self.get_any_profiles(missing_profiles)
}
game = Node.void('game')
game = Node.void("game")
for song in sortedrecords:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink_num', str(song))
music.set_attribute("reclink_num", str(song))
for chart in sortedrecords[song]:
userid, score = sortedrecords[song][chart]
@ -194,24 +199,28 @@ class DDRX2(
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int('rank'))
combo_type = self.db_to_game_halo(score.data.get_int('halo'))
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.string('name', users[userid].get_str('name')))
typenode.add_child(Node.u32('score', score.points))
typenode.add_child(Node.u16('area', users[userid].get_int('area', self.get_machine_region())))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.string("name", users[userid].get_str("name")))
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(
Node.u16(
"area", users[userid].get_int("area", self.get_machine_region())
)
)
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
return game
def handle_game_load_m_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
refid = request.attribute('refid')
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
if extid is not None:
# Rival score loading
@ -221,7 +230,9 @@ class DDRX2(
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
else:
scores = []
@ -231,11 +242,11 @@ class DDRX2(
sortedscores[score.id] = {}
sortedscores[score.id][score.chart] = score
game = Node.void('game')
game = Node.void("game")
for song in sortedscores:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink', str(song))
music.set_attribute("reclink", str(song))
for chart in sortedscores[song]:
score = sortedscores[song][chart]
@ -244,39 +255,39 @@ class DDRX2(
except KeyError:
# Don't support this chart in this game
continue
gamerank = self.db_to_game_rank(score.data.get_int('rank'))
combo_type = self.db_to_game_halo(score.data.get_int('halo'))
gamerank = self.db_to_game_rank(score.data.get_int("rank"))
combo_type = self.db_to_game_halo(score.data.get_int("halo"))
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.u32('score', score.points))
typenode.add_child(Node.u16('count', score.plays))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.u32("score", score.points))
typenode.add_child(Node.u16("count", score.plays))
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
return game
def handle_game_save_m_request(self, request: Node) -> Node:
refid = request.attribute('refid')
songid = int(request.attribute('mid'))
chart = self.game_to_db_chart(int(request.attribute('mtype')))
refid = request.attribute("refid")
songid = int(request.attribute("mid"))
chart = self.game_to_db_chart(int(request.attribute("mtype")))
# Calculate statistics
data = request.child('data')
points = int(data.attribute('score'))
combo = int(data.attribute('combo'))
rank = self.game_to_db_rank(int(data.attribute('rank')))
if int(data.attribute('full')) == 0:
data = request.child("data")
points = int(data.attribute("score"))
combo = int(data.attribute("combo"))
rank = self.game_to_db_rank(int(data.attribute("rank")))
if int(data.attribute("full")) == 0:
halo = self.HALO_NONE
elif int(data.attribute('perf')) == 0:
elif int(data.attribute("perf")) == 0:
halo = self.HALO_GREAT_FULL_COMBO
elif points < 1000000:
halo = self.HALO_PERFECT_FULL_COMBO
else:
halo = self.HALO_MARVELOUS_FULL_COMBO
trace = request.child_value('trace')
trace = request.child_value("trace")
# Save the score, regardless of whether we have a refid. If we save
# an anonymous score, it only goes into the DB to count against the
@ -294,336 +305,372 @@ class DDRX2(
)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('game')
root = Node.void("game")
# Look up play stats we bridge to every mix
play_stats = self.get_play_statistics(userid)
# Basic game settings
root.add_child(Node.string('seq', ''))
root.add_child(Node.u32('code', profile.extid))
root.add_child(Node.string('name', profile.get_str('name')))
root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region())))
root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays')))
root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays')))
root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess
root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0')))
root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1')))
root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2')))
root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3')))
root.add_child(Node.u32('exp', play_stats.get_int('exp')))
root.add_child(Node.u32('exp_o', profile.get_int('exp_o')))
root.add_child(Node.u32('star', profile.get_int('star')))
root.add_child(Node.u32('star_c', profile.get_int('star_c')))
root.add_child(Node.u8('combo', profile.get_int('combo', 0)))
root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0)))
root.add_child(Node.string("seq", ""))
root.add_child(Node.u32("code", profile.extid))
root.add_child(Node.string("name", profile.get_str("name")))
root.add_child(
Node.u8("area", profile.get_int("area", self.get_machine_region()))
)
root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays")))
root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays")))
root.add_child(
Node.u32("cnt_b", play_stats.get_int("battle_plays"))
) # This could be wrong, its a guess
root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0")))
root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1")))
root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2")))
root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3")))
root.add_child(Node.u32("exp", play_stats.get_int("exp")))
root.add_child(Node.u32("exp_o", profile.get_int("exp_o")))
root.add_child(Node.u32("star", profile.get_int("star")))
root.add_child(Node.u32("star_c", profile.get_int("star_c")))
root.add_child(Node.u8("combo", profile.get_int("combo", 0)))
root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0)))
# Character stuff
chara = Node.void('chara')
chara = Node.void("chara")
root.add_child(chara)
if 'chara' in profile:
chara.set_attribute('my', str(profile.get_int('chara')))
if "chara" in profile:
chara.set_attribute("my", str(profile.get_int("chara")))
root.add_child(Node.u8_array('chara_opt', profile.get_int_array('chara_opt', 96)))
root.add_child(
Node.u8_array("chara_opt", profile.get_int_array("chara_opt", 96))
)
# Drill rankings
if 'title' in profile:
title = Node.void('title')
if "title" in profile:
title = Node.void("title")
root.add_child(title)
titledict = profile.get_dict('title')
if 't' in titledict:
title.set_attribute('t', str(titledict.get_int('t')))
if 's' in titledict:
title.set_attribute('s', str(titledict.get_int('s')))
if 'd' in titledict:
title.set_attribute('d', str(titledict.get_int('d')))
titledict = profile.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if 'title_gr' in profile:
title_gr = Node.void('title_gr')
if "title_gr" in profile:
title_gr = Node.void("title_gr")
root.add_child(title_gr)
title_grdict = profile.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = profile.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Event progrses
if 'event' in profile:
event = Node.void('event')
if "event" in profile:
event = Node.void("event")
root.add_child(event)
event_dict = profile.get_dict('event')
if 'diff_sum' in event_dict:
event.set_attribute('diff_sum', str(event_dict.get_int('diff_sum')))
if 'welcome' in event_dict:
event.set_attribute('welcome', str(event_dict.get_int('welcome')))
if 'e_flags' in event_dict:
event.set_attribute('e_flags', str(event_dict.get_int('e_flags')))
event_dict = profile.get_dict("event")
if "diff_sum" in event_dict:
event.set_attribute("diff_sum", str(event_dict.get_int("diff_sum")))
if "welcome" in event_dict:
event.set_attribute("welcome", str(event_dict.get_int("welcome")))
if "e_flags" in event_dict:
event.set_attribute("e_flags", str(event_dict.get_int("e_flags")))
if 'e_panel' in profile:
e_panel = Node.void('e_panel')
if "e_panel" in profile:
e_panel = Node.void("e_panel")
root.add_child(e_panel)
e_panel_dict = profile.get_dict('e_panel')
if 'play_id' in e_panel_dict:
e_panel.set_attribute('play_id', str(e_panel_dict.get_int('play_id')))
e_panel.add_child(Node.u8_array('cell', e_panel_dict.get_int_array('cell', 24)))
e_panel.add_child(Node.u8_array('panel_state', e_panel_dict.get_int_array('panel_state', 6)))
e_panel_dict = profile.get_dict("e_panel")
if "play_id" in e_panel_dict:
e_panel.set_attribute("play_id", str(e_panel_dict.get_int("play_id")))
e_panel.add_child(
Node.u8_array("cell", e_panel_dict.get_int_array("cell", 24))
)
e_panel.add_child(
Node.u8_array(
"panel_state", e_panel_dict.get_int_array("panel_state", 6)
)
)
if 'e_pix' in profile:
e_pix = Node.void('e_pix')
if "e_pix" in profile:
e_pix = Node.void("e_pix")
root.add_child(e_pix)
e_pix_dict = profile.get_dict('e_pix')
if 'max_distance' in e_pix_dict:
e_pix.set_attribute('max_distance', str(e_pix_dict.get_int('max_distance')))
if 'max_planet' in e_pix_dict:
e_pix.set_attribute('max_planet', str(e_pix_dict.get_int('max_planet')))
if 'total_distance' in e_pix_dict:
e_pix.set_attribute('total_distance', str(e_pix_dict.get_int('total_distance')))
if 'total_planet' in e_pix_dict:
e_pix.set_attribute('total_planet', str(e_pix_dict.get_int('total_planet')))
if 'border_character' in e_pix_dict:
e_pix.set_attribute('border_character', str(e_pix_dict.get_int('border_character')))
if 'border_balloon' in e_pix_dict:
e_pix.set_attribute('border_balloon', str(e_pix_dict.get_int('border_balloon')))
if 'border_music_aftr' in e_pix_dict:
e_pix.set_attribute('border_music_aftr', str(e_pix_dict.get_int('border_music_aftr')))
if 'border_music_meii' in e_pix_dict:
e_pix.set_attribute('border_music_meii', str(e_pix_dict.get_int('border_music_meii')))
if 'border_music_dirt' in e_pix_dict:
e_pix.set_attribute('border_music_dirt', str(e_pix_dict.get_int('border_music_dirt')))
if 'flags' in e_pix_dict:
e_pix.set_attribute('flags', str(e_pix_dict.get_int('flags')))
e_pix_dict = profile.get_dict("e_pix")
if "max_distance" in e_pix_dict:
e_pix.set_attribute(
"max_distance", str(e_pix_dict.get_int("max_distance"))
)
if "max_planet" in e_pix_dict:
e_pix.set_attribute("max_planet", str(e_pix_dict.get_int("max_planet")))
if "total_distance" in e_pix_dict:
e_pix.set_attribute(
"total_distance", str(e_pix_dict.get_int("total_distance"))
)
if "total_planet" in e_pix_dict:
e_pix.set_attribute(
"total_planet", str(e_pix_dict.get_int("total_planet"))
)
if "border_character" in e_pix_dict:
e_pix.set_attribute(
"border_character", str(e_pix_dict.get_int("border_character"))
)
if "border_balloon" in e_pix_dict:
e_pix.set_attribute(
"border_balloon", str(e_pix_dict.get_int("border_balloon"))
)
if "border_music_aftr" in e_pix_dict:
e_pix.set_attribute(
"border_music_aftr", str(e_pix_dict.get_int("border_music_aftr"))
)
if "border_music_meii" in e_pix_dict:
e_pix.set_attribute(
"border_music_meii", str(e_pix_dict.get_int("border_music_meii"))
)
if "border_music_dirt" in e_pix_dict:
e_pix.set_attribute(
"border_music_dirt", str(e_pix_dict.get_int("border_music_dirt"))
)
if "flags" in e_pix_dict:
e_pix.set_attribute("flags", str(e_pix_dict.get_int("flags")))
# Calorie mode
if 'weight' in profile:
if "weight" in profile:
workouts = self.data.local.user.get_time_based_achievements(
self.game,
self.version,
userid,
achievementtype='workout',
achievementtype="workout",
since=Time.now() - Time.SECONDS_IN_DAY,
)
total = sum([w.data.get_int('calories') for w in workouts])
workout = Node.void('workout')
total = sum([w.data.get_int("calories") for w in workouts])
workout = Node.void("workout")
root.add_child(workout)
workout.set_attribute('weight', str(profile.get_int('weight')))
workout.set_attribute('day', str(total))
workout.set_attribute('disp', '1')
workout.set_attribute("weight", str(profile.get_int("weight")))
workout.set_attribute("day", str(total))
workout.set_attribute("disp", "1")
# Last cursor settings
last = Node.void('last')
last = Node.void("last")
root.add_child(last)
lastdict = profile.get_dict('last')
last.set_attribute('fri', str(lastdict.get_int('fri')))
last.set_attribute('style', str(lastdict.get_int('style')))
last.set_attribute('mode', str(lastdict.get_int('mode')))
last.set_attribute('cate', str(lastdict.get_int('cate')))
last.set_attribute('sort', str(lastdict.get_int('sort')))
last.set_attribute('mid', str(lastdict.get_int('mid')))
last.set_attribute('mtype', str(lastdict.get_int('mtype')))
last.set_attribute('cid', str(lastdict.get_int('cid')))
last.set_attribute('ctype', str(lastdict.get_int('ctype')))
last.set_attribute('sid', str(lastdict.get_int('sid')))
lastdict = profile.get_dict("last")
last.set_attribute("fri", str(lastdict.get_int("fri")))
last.set_attribute("style", str(lastdict.get_int("style")))
last.set_attribute("mode", str(lastdict.get_int("mode")))
last.set_attribute("cate", str(lastdict.get_int("cate")))
last.set_attribute("sort", str(lastdict.get_int("sort")))
last.set_attribute("mid", str(lastdict.get_int("mid")))
last.set_attribute("mtype", str(lastdict.get_int("mtype")))
last.set_attribute("cid", str(lastdict.get_int("cid")))
last.set_attribute("ctype", str(lastdict.get_int("ctype")))
last.set_attribute("sid", str(lastdict.get_int("sid")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
root.add_child(gr_s)
index = 1
for entry in profile.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
root.add_child(gr_d)
index = 1
for entry in profile.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Options in menus
root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16)))
root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16)))
root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16)))
root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16)))
# Unlock flags
root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256)))
root.add_child(
Node.u8_array("flag", profile.get_int_array("flag", 256, [1] * 256))
)
# Ranking display?
root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100)))
root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100)))
# Rivals
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
friend = self.get_profile(link.other_userid)
play_stats = self.get_play_statistics(link.other_userid)
if friend is not None:
friendnode = Node.void('friend')
friendnode = Node.void("friend")
root.add_child(friendnode)
friendnode.set_attribute('pos', str(pos))
friendnode.set_attribute('vs', '0')
friendnode.set_attribute('up', '0')
friendnode.add_child(Node.u32('code', friend.extid))
friendnode.add_child(Node.string('name', friend.get_str('name')))
friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region())))
friendnode.add_child(Node.u32('exp', play_stats.get_int('exp')))
friendnode.add_child(Node.u32('star', friend.get_int('star')))
friendnode.set_attribute("pos", str(pos))
friendnode.set_attribute("vs", "0")
friendnode.set_attribute("up", "0")
friendnode.add_child(Node.u32("code", friend.extid))
friendnode.add_child(Node.string("name", friend.get_str("name")))
friendnode.add_child(
Node.u8("area", friend.get_int("area", self.get_machine_region()))
)
friendnode.add_child(Node.u32("exp", play_stats.get_int("exp")))
friendnode.add_child(Node.u32("star", friend.get_int("star")))
# Drill rankings
if 'title' in friend:
title = Node.void('title')
if "title" in friend:
title = Node.void("title")
friendnode.add_child(title)
titledict = friend.get_dict('title')
if 't' in titledict:
title.set_attribute('t', str(titledict.get_int('t')))
if 's' in titledict:
title.set_attribute('s', str(titledict.get_int('s')))
if 'd' in titledict:
title.set_attribute('d', str(titledict.get_int('d')))
titledict = friend.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if 'title_gr' in friend:
title_gr = Node.void('title_gr')
if "title_gr" in friend:
title_gr = Node.void("title_gr")
friendnode.add_child(title_gr)
title_grdict = friend.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = friend.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
friendnode.add_child(gr_s)
index = 1
for entry in friend.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
friendnode.add_child(gr_d)
index = 1
for entry in friend.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
play_stats = self.get_play_statistics(userid)
# Grab last node and accessories so we can make decisions based on type
last = request.child('last')
lastdict = newprofile.get_dict('last')
mode = int(last.attribute('mode'))
style = int(last.attribute('style'))
last = request.child("last")
lastdict = newprofile.get_dict("last")
mode = int(last.attribute("mode"))
style = int(last.attribute("style"))
is_dp = style == self.GAME_STYLE_DOUBLE
# Drill rankings
title = request.child('title')
title_gr = request.child('title_gr')
titledict = newprofile.get_dict('title')
title_grdict = newprofile.get_dict('title_gr')
title = request.child("title")
title_gr = request.child("title_gr")
titledict = newprofile.get_dict("title")
title_grdict = newprofile.get_dict("title_gr")
# Groove radar level ups
gr = request.child('gr')
gr = request.child("gr")
# Set the correct values depending on if we're single or double play
if is_dp:
play_stats.increment_int('double_plays')
play_stats.increment_int("double_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_d',
"gr_d",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('d', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("d", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('d', title.value)
newprofile.replace_dict('title_gr', title_grdict)
title_grdict.replace_int("d", title.value)
newprofile.replace_dict("title_gr", title_grdict)
else:
play_stats.increment_int('single_plays')
play_stats.increment_int("single_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_s',
"gr_s",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('s', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("s", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('s', title.value)
newprofile.replace_dict('title_gr', title_grdict)
play_stats.increment_int(f'cnt_m{mode}')
title_grdict.replace_int("s", title.value)
newprofile.replace_dict("title_gr", title_grdict)
play_stats.increment_int(f"cnt_m{mode}")
# Update last attributes
lastdict.replace_int('fri', intish(last.attribute('fri')))
lastdict.replace_int('style', intish(last.attribute('style')))
lastdict.replace_int('mode', intish(last.attribute('mode')))
lastdict.replace_int('cate', intish(last.attribute('cate')))
lastdict.replace_int('sort', intish(last.attribute('sort')))
lastdict.replace_int('mid', intish(last.attribute('mid')))
lastdict.replace_int('mtype', intish(last.attribute('mtype')))
lastdict.replace_int('cid', intish(last.attribute('cid')))
lastdict.replace_int('ctype', intish(last.attribute('ctype')))
lastdict.replace_int('sid', intish(last.attribute('sid')))
newprofile.replace_dict('last', lastdict)
lastdict.replace_int("fri", intish(last.attribute("fri")))
lastdict.replace_int("style", intish(last.attribute("style")))
lastdict.replace_int("mode", intish(last.attribute("mode")))
lastdict.replace_int("cate", intish(last.attribute("cate")))
lastdict.replace_int("sort", intish(last.attribute("sort")))
lastdict.replace_int("mid", intish(last.attribute("mid")))
lastdict.replace_int("mtype", intish(last.attribute("mtype")))
lastdict.replace_int("cid", intish(last.attribute("cid")))
lastdict.replace_int("ctype", intish(last.attribute("ctype")))
lastdict.replace_int("sid", intish(last.attribute("sid")))
newprofile.replace_dict("last", lastdict)
# Grab character options
chara = request.child('chara')
chara = request.child("chara")
if chara is not None:
newprofile.replace_int('chara', intish(chara.attribute('my')))
newprofile.replace_int_array('chara_opt', 96, request.child_value('chara_opt'))
newprofile.replace_int("chara", intish(chara.attribute("my")))
newprofile.replace_int_array("chara_opt", 96, request.child_value("chara_opt"))
# Track event progress
event = request.child('event')
event = request.child("event")
if event is not None:
event_dict = newprofile.get_dict('event')
event_dict.replace_int('diff_sum', intish(event.attribute('diff_sum')))
event_dict.replace_int('e_flags', intish(event.attribute('e_flags')))
event_dict.replace_int('welcome', intish(event.attribute('welcome')))
newprofile.replace_dict('event', event_dict)
event_dict = newprofile.get_dict("event")
event_dict.replace_int("diff_sum", intish(event.attribute("diff_sum")))
event_dict.replace_int("e_flags", intish(event.attribute("e_flags")))
event_dict.replace_int("welcome", intish(event.attribute("welcome")))
newprofile.replace_dict("event", event_dict)
e_panel = request.child('e_panel')
e_panel = request.child("e_panel")
if e_panel is not None:
e_panel_dict = newprofile.get_dict('e_panel')
e_panel_dict.replace_int('play_id', intish(e_panel.attribute('play_id')))
e_panel_dict.replace_int_array('cell', 24, e_panel.child_value('cell'))
e_panel_dict.replace_int_array('panel_state', 6, e_panel.child_value('panel_state'))
newprofile.replace_dict('e_panel', e_panel_dict)
e_panel_dict = newprofile.get_dict("e_panel")
e_panel_dict.replace_int("play_id", intish(e_panel.attribute("play_id")))
e_panel_dict.replace_int_array("cell", 24, e_panel.child_value("cell"))
e_panel_dict.replace_int_array(
"panel_state", 6, e_panel.child_value("panel_state")
)
newprofile.replace_dict("e_panel", e_panel_dict)
e_pix = request.child('e_pix')
e_pix = request.child("e_pix")
if e_pix is not None:
e_pix_dict = newprofile.get_dict('e_pix')
max_distance = e_pix_dict.get_int('max_distance')
max_planet = e_pix_dict.get_int('max_planet')
total_distance = e_pix_dict.get_int('total_distance')
total_planet = e_pix_dict.get_int('total_planet')
cur_distance = intish(e_pix.attribute('this_distance'))
cur_planet = intish(e_pix.attribute('this_planet'))
e_pix_dict = newprofile.get_dict("e_pix")
max_distance = e_pix_dict.get_int("max_distance")
max_planet = e_pix_dict.get_int("max_planet")
total_distance = e_pix_dict.get_int("total_distance")
total_planet = e_pix_dict.get_int("total_planet")
cur_distance = intish(e_pix.attribute("this_distance"))
cur_planet = intish(e_pix.attribute("this_planet"))
if cur_distance is not None:
max_distance = max(max_distance, cur_distance)
total_distance += cur_distance
@ -631,51 +678,51 @@ class DDRX2(
max_planet = max(max_planet, cur_planet)
total_planet += cur_planet
e_pix_dict.replace_int('max_distance', max_distance)
e_pix_dict.replace_int('max_planet', max_planet)
e_pix_dict.replace_int('total_distance', total_distance)
e_pix_dict.replace_int('total_planet', total_planet)
e_pix_dict.replace_int('flags', intish(e_pix.attribute('flags')))
newprofile.replace_dict('e_pix', e_pix_dict)
e_pix_dict.replace_int("max_distance", max_distance)
e_pix_dict.replace_int("max_planet", max_planet)
e_pix_dict.replace_int("total_distance", total_distance)
e_pix_dict.replace_int("total_planet", total_planet)
e_pix_dict.replace_int("flags", intish(e_pix.attribute("flags")))
newprofile.replace_dict("e_pix", e_pix_dict)
# Options
opt = request.child('opt')
opt = request.child("opt")
if opt is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('opt', 16, opt.value[:16])
newprofile.replace_int_array("opt", 16, opt.value[:16])
# Experience and stars
exp = request.child_value('exp')
exp = request.child_value("exp")
if exp is not None:
play_stats.replace_int('exp', play_stats.get_int('exp') + exp)
star = request.child_value('star')
play_stats.replace_int("exp", play_stats.get_int("exp") + exp)
star = request.child_value("star")
if star is not None:
newprofile.replace_int('star', newprofile.get_int('star') + star)
star_c = request.child_value('star_c')
newprofile.replace_int("star", newprofile.get_int("star") + star)
star_c = request.child_value("star_c")
if star_c is not None:
newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp)
newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp)
# Update game flags
for child in request.children:
if child.name != 'flag':
if child.name != "flag":
continue
try:
value = int(child.attribute('data'))
offset = int(child.attribute('no'))
value = int(child.attribute("data"))
offset = int(child.attribute("no"))
except ValueError:
continue
flags = newprofile.get_int_array('flag', 256, [1] * 256)
flags = newprofile.get_int_array("flag", 256, [1] * 256)
if offset < 0 or offset >= len(flags):
continue
flags[offset] = value
newprofile.replace_int_array('flag', 256, flags)
newprofile.replace_int_array("flag", 256, flags)
# Workout mode support
newweight = -1
oldweight = newprofile.get_int('weight')
oldweight = newprofile.get_int("weight")
for child in request.children:
if child.name != 'weight':
if child.name != "weight":
continue
newweight = child.value
if newweight < 0:
@ -684,14 +731,14 @@ class DDRX2(
# Either update or unset the weight depending on the game
if newweight == 0:
# Weight is unset or we declined to use this feature, remove from profile
if 'weight' in newprofile:
del newprofile['weight']
if "weight" in newprofile:
del newprofile["weight"]
else:
# Weight has been set or previously retrieved, we should save calories
newprofile.replace_int('weight', newweight)
newprofile.replace_int("weight", newweight)
total = 0
for child in request.children:
if child.name != 'calory':
if child.name != "calory":
continue
total += child.value
self.data.local.user.put_time_based_achievement(
@ -699,10 +746,10 @@ class DDRX2(
self.version,
userid,
0,
'workout',
"workout",
{
'calories': total,
'weight': newweight,
"calories": total,
"weight": newweight,
},
)
@ -710,7 +757,7 @@ class DDRX2(
oldfriends: List[Optional[UserID]] = [None] * 10
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
@ -719,11 +766,11 @@ class DDRX2(
# Save any rivals that were added/removed/changed
newfriends = oldfriends[:]
for child in request.children:
if child.name != 'friend':
if child.name != "friend":
continue
code = int(child.attribute('code'))
pos = int(child.attribute('pos'))
code = int(child.attribute("code"))
pos = int(child.attribute("pos"))
if pos >= 0 and pos < 10:
if code == 0:
@ -731,7 +778,9 @@ class DDRX2(
newfriends[pos] = None
else:
# Try looking up the userid
newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code)
newfriends[pos] = self.data.remote.user.from_extid(
self.game, self.version, code
)
# Diff the set of links to determine updates
for i in range(10):
@ -744,7 +793,7 @@ class DDRX2(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
elif oldfriends[i] is None:
@ -753,7 +802,7 @@ class DDRX2(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
@ -763,14 +812,14 @@ class DDRX2(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
self.data.local.user.put_link(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)

View File

@ -49,7 +49,7 @@ class DDRX3(
DDRBase,
):
name: str = 'DanceDanceRevolution X3 VS 2ndMIX'
name: str = "DanceDanceRevolution X3 VS 2ndMIX"
version: int = VersionConstants.DDR_X3_VS_2NDMIX
GAME_STYLE_SINGLE: Final[int] = 0
@ -156,28 +156,30 @@ class DDRX3(
return combo_type
def handle_game_common_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
for flagid in range(256):
flag = Node.void('flag')
flag = Node.void("flag")
game.add_child(flag)
flag.set_attribute('id', str(flagid))
flag.set_attribute('s2', '0')
flag.set_attribute('s1', '0')
flag.set_attribute('t', '0')
flag.set_attribute("id", str(flagid))
flag.set_attribute("s2", "0")
flag.set_attribute("s1", "0")
flag.set_attribute("t", "0")
hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS)
hit_chart = self.data.local.music.get_hit_chart(
self.game, self.music_version, self.GAME_MAX_SONGS
)
counts_by_reflink = [0] * self.GAME_MAX_SONGS
for (reflink, plays) in hit_chart:
if reflink >= 0 and reflink < self.GAME_MAX_SONGS:
counts_by_reflink[reflink] = plays
game.add_child(Node.u32_array('cnt_music', counts_by_reflink))
game.add_child(Node.u32_array("cnt_music", counts_by_reflink))
return game
def handle_game_load_m_request(self, request: Node) -> Node:
extid = intish(request.attribute('code'))
refid = request.attribute('refid')
extid = intish(request.attribute("code"))
refid = request.attribute("refid")
if extid is not None:
# Rival score loading
@ -187,10 +189,15 @@ class DDRX3(
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
old_scores = [
score for score in self.data.local.user.get_achievements(self.game, self.music_version, userid)
if score.type == '2ndmix'
score
for score in self.data.local.user.get_achievements(
self.game, self.music_version, userid
)
if score.type == "2ndmix"
]
else:
scores = []
@ -203,7 +210,7 @@ class DDRX3(
sortedscores[score.id] = {}
if score.chart not in sortedscores[score.id]:
sortedscores[score.id][score.chart] = {}
sortedscores[score.id][score.chart]['score'] = score
sortedscores[score.id][score.chart]["score"] = score
for oldscore in old_scores:
songid = int(oldscore.id / 100)
@ -212,13 +219,13 @@ class DDRX3(
sortedscores[songid] = {}
if chart not in sortedscores[songid]:
sortedscores[songid][chart] = {}
sortedscores[songid][chart]['oldscore'] = oldscore
sortedscores[songid][chart]["oldscore"] = oldscore
game = Node.void('game')
game = Node.void("game")
for song in sortedscores:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('reclink', str(song))
music.set_attribute("reclink", str(song))
for chart in sortedscores[song]:
try:
@ -228,12 +235,16 @@ class DDRX3(
continue
scoredict = sortedscores[song][chart]
if 'score' in scoredict:
if "score" in scoredict:
# We played the normal version of this song
gamerank = self.db_to_game_rank(scoredict['score'].data.get_int('rank'))
combo_type = self.db_to_game_halo(scoredict['score'].data.get_int('halo'))
points = scoredict['score'].points # type: ignore
plays = scoredict['score'].plays # type: ignore
gamerank = self.db_to_game_rank(
scoredict["score"].data.get_int("rank")
)
combo_type = self.db_to_game_halo(
scoredict["score"].data.get_int("halo")
)
points = scoredict["score"].points # type: ignore
plays = scoredict["score"].plays # type: ignore
else:
# We only played 2nd mix version of this song
gamerank = 0
@ -241,45 +252,45 @@ class DDRX3(
points = 0
plays = 0
if 'oldscore' in scoredict:
if "oldscore" in scoredict:
# We played the 2nd mix version of this song
oldpoints = scoredict['oldscore'].data.get_int('points')
oldrank = scoredict['oldscore'].data.get_int('rank')
oldplays = scoredict['oldscore'].data.get_int('plays')
oldpoints = scoredict["oldscore"].data.get_int("points")
oldrank = scoredict["oldscore"].data.get_int("rank")
oldplays = scoredict["oldscore"].data.get_int("plays")
else:
oldpoints = 0
oldrank = 0
oldplays = 0
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('diff', str(gamechart))
typenode.set_attribute("diff", str(gamechart))
typenode.add_child(Node.u32('score', points))
typenode.add_child(Node.u16('count', plays))
typenode.add_child(Node.u8('rank', gamerank))
typenode.add_child(Node.u8('combo_type', combo_type))
typenode.add_child(Node.u32('score_2nd', oldpoints))
typenode.add_child(Node.u8('rank_2nd', oldrank))
typenode.add_child(Node.u16('cnt_2nd', oldplays))
typenode.add_child(Node.u32("score", points))
typenode.add_child(Node.u16("count", plays))
typenode.add_child(Node.u8("rank", gamerank))
typenode.add_child(Node.u8("combo_type", combo_type))
typenode.add_child(Node.u32("score_2nd", oldpoints))
typenode.add_child(Node.u8("rank_2nd", oldrank))
typenode.add_child(Node.u16("cnt_2nd", oldplays))
return game
def handle_game_save_m_request(self, request: Node) -> Node:
refid = request.attribute('refid')
songid = int(request.attribute('mid'))
chart = self.game_to_db_chart(int(request.attribute('mtype')))
refid = request.attribute("refid")
songid = int(request.attribute("mid"))
chart = self.game_to_db_chart(int(request.attribute("mtype")))
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
# Calculate statistics
data = request.child('data')
playmode = int(data.attribute('playmode'))
data = request.child("data")
playmode = int(data.attribute("playmode"))
if playmode == self.GAME_PLAY_MODE_2NDMIX:
if userid is not None:
# These are special cased and treated like achievements
points = int(data.attribute('score_2nd'))
combo = int(data.attribute('combo_2nd'))
rank = int(data.attribute('rank_2nd'))
points = int(data.attribute("score_2nd"))
combo = int(data.attribute("combo_2nd"))
rank = int(data.attribute("rank_2nd"))
# Grab the old 2nd mix score
existingscore = self.data.local.user.get_achievement(
@ -287,17 +298,17 @@ class DDRX3(
self.music_version,
userid,
(songid * 100) + chart,
'2ndmix',
"2ndmix",
)
if existingscore is not None:
highscore = points > existingscore.get_int('points')
highscore = points > existingscore.get_int("points")
plays = existingscore.get_int('plays', 0) + 1
points = max(points, existingscore.get_int('points'))
plays = existingscore.get_int("plays", 0) + 1
points = max(points, existingscore.get_int("points"))
if not highscore:
combo = existingscore.get_int('combo', combo)
rank = existingscore.get_int('rank', rank)
combo = existingscore.get_int("combo", combo)
rank = existingscore.get_int("rank", rank)
else:
plays = 1
@ -306,27 +317,27 @@ class DDRX3(
self.music_version,
userid,
(songid * 100) + chart,
'2ndmix',
"2ndmix",
{
'points': points,
'combo': combo,
'rank': rank,
'plays': plays,
"points": points,
"combo": combo,
"rank": rank,
"plays": plays,
},
)
else:
points = int(data.attribute('score'))
combo = int(data.attribute('combo'))
rank = self.game_to_db_rank(int(data.attribute('rank')))
if int(data.attribute('full')) == 0:
points = int(data.attribute("score"))
combo = int(data.attribute("combo"))
rank = self.game_to_db_rank(int(data.attribute("rank")))
if int(data.attribute("full")) == 0:
halo = self.HALO_NONE
elif int(data.attribute('perf')) == 0:
elif int(data.attribute("perf")) == 0:
halo = self.HALO_GREAT_FULL_COMBO
elif points < 1000000:
halo = self.HALO_PERFECT_FULL_COMBO
else:
halo = self.HALO_MARVELOUS_FULL_COMBO
trace = request.child_value('trace')
trace = request.child_value("trace")
# Save the score, regardless of whether we have a refid. If we save
# an anonymous score, it only goes into the DB to count against the
@ -343,355 +354,371 @@ class DDRX3(
)
# No response needed
game = Node.void('game')
game = Node.void("game")
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('game')
root = Node.void("game")
# Look up play stats we bridge to every mix
play_stats = self.get_play_statistics(userid)
# Basic game settings
root.add_child(Node.string('seq', ''))
root.add_child(Node.u32('code', profile.extid))
root.add_child(Node.string('name', profile.get_str('name')))
root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region())))
root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays')))
root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays')))
root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess
root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0')))
root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1')))
root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2')))
root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3')))
root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4')))
root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5')))
root.add_child(Node.u32('exp', play_stats.get_int('exp')))
root.add_child(Node.u32('exp_o', profile.get_int('exp_o')))
root.add_child(Node.u32('star', profile.get_int('star')))
root.add_child(Node.u32('star_c', profile.get_int('star_c')))
root.add_child(Node.u8('combo', profile.get_int('combo', 0)))
root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0)))
root.add_child(Node.string("seq", ""))
root.add_child(Node.u32("code", profile.extid))
root.add_child(Node.string("name", profile.get_str("name")))
root.add_child(
Node.u8("area", profile.get_int("area", self.get_machine_region()))
)
root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays")))
root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays")))
root.add_child(
Node.u32("cnt_b", play_stats.get_int("battle_plays"))
) # This could be wrong, its a guess
root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0")))
root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1")))
root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2")))
root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3")))
root.add_child(Node.u32("cnt_m4", play_stats.get_int("cnt_m4")))
root.add_child(Node.u32("cnt_m5", play_stats.get_int("cnt_m5")))
root.add_child(Node.u32("exp", play_stats.get_int("exp")))
root.add_child(Node.u32("exp_o", profile.get_int("exp_o")))
root.add_child(Node.u32("star", profile.get_int("star")))
root.add_child(Node.u32("star_c", profile.get_int("star_c")))
root.add_child(Node.u8("combo", profile.get_int("combo", 0)))
root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0)))
# Character stuff
chara = Node.void('chara')
chara = Node.void("chara")
root.add_child(chara)
chara.set_attribute('my', str(profile.get_int('chara', 30)))
root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96)))
chara.set_attribute("my", str(profile.get_int("chara", 30)))
root.add_child(
Node.u16_array(
"chara_opt", profile.get_int_array("chara_opt", 96, [208] * 96)
)
)
# Drill rankings
if 'title_gr' in profile:
title_gr = Node.void('title_gr')
if "title_gr" in profile:
title_gr = Node.void("title_gr")
root.add_child(title_gr)
title_grdict = profile.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = profile.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Calorie mode
if 'weight' in profile:
if "weight" in profile:
workouts = self.data.local.user.get_time_based_achievements(
self.game,
self.version,
userid,
achievementtype='workout',
achievementtype="workout",
since=Time.now() - Time.SECONDS_IN_DAY,
)
total = sum([w.data.get_int('calories') for w in workouts])
workout = Node.void('workout')
total = sum([w.data.get_int("calories") for w in workouts])
workout = Node.void("workout")
root.add_child(workout)
workout.set_attribute('weight', str(profile.get_int('weight')))
workout.set_attribute('day', str(total))
workout.set_attribute('disp', '1')
workout.set_attribute("weight", str(profile.get_int("weight")))
workout.set_attribute("day", str(total))
workout.set_attribute("disp", "1")
# Daily play counts
daycount = Node.void('daycount')
daycount = Node.void("daycount")
root.add_child(daycount)
daycount.set_attribute('playcount', str(play_stats.today_plays))
daycount.set_attribute("playcount", str(play_stats.today_plays))
# Daily combo stuff, unknown how this works
dailycombo = Node.void('dailycombo')
dailycombo = Node.void("dailycombo")
root.add_child(dailycombo)
dailycombo.set_attribute('daily_combo', str(0))
dailycombo.set_attribute('daily_combo_lv', str(0))
dailycombo.set_attribute("daily_combo", str(0))
dailycombo.set_attribute("daily_combo_lv", str(0))
# Last cursor settings
last = Node.void('last')
last = Node.void("last")
root.add_child(last)
lastdict = profile.get_dict('last')
last.set_attribute('rival1', str(lastdict.get_int('rival1', -1)))
last.set_attribute('rival2', str(lastdict.get_int('rival2', -1)))
last.set_attribute('rival3', str(lastdict.get_int('rival3', -1)))
last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in X3
last.set_attribute('style', str(lastdict.get_int('style')))
last.set_attribute('mode', str(lastdict.get_int('mode')))
last.set_attribute('cate', str(lastdict.get_int('cate')))
last.set_attribute('sort', str(lastdict.get_int('sort')))
last.set_attribute('mid', str(lastdict.get_int('mid')))
last.set_attribute('mtype', str(lastdict.get_int('mtype')))
last.set_attribute('cid', str(lastdict.get_int('cid')))
last.set_attribute('ctype', str(lastdict.get_int('ctype')))
last.set_attribute('sid', str(lastdict.get_int('sid')))
lastdict = profile.get_dict("last")
last.set_attribute("rival1", str(lastdict.get_int("rival1", -1)))
last.set_attribute("rival2", str(lastdict.get_int("rival2", -1)))
last.set_attribute("rival3", str(lastdict.get_int("rival3", -1)))
last.set_attribute(
"fri", str(lastdict.get_int("rival1", -1))
) # This literally goes to the same memory in X3
last.set_attribute("style", str(lastdict.get_int("style")))
last.set_attribute("mode", str(lastdict.get_int("mode")))
last.set_attribute("cate", str(lastdict.get_int("cate")))
last.set_attribute("sort", str(lastdict.get_int("sort")))
last.set_attribute("mid", str(lastdict.get_int("mid")))
last.set_attribute("mtype", str(lastdict.get_int("mtype")))
last.set_attribute("cid", str(lastdict.get_int("cid")))
last.set_attribute("ctype", str(lastdict.get_int("ctype")))
last.set_attribute("sid", str(lastdict.get_int("sid")))
# Result stars
result_star = Node.void('result_star')
result_star = Node.void("result_star")
root.add_child(result_star)
result_stars = profile.get_int_array('result_stars', 9)
result_stars = profile.get_int_array("result_stars", 9)
for i in range(9):
result_star.set_attribute(f'slot{i + 1}', str(result_stars[i]))
result_star.set_attribute(f"slot{i + 1}", str(result_stars[i]))
# Target stuff
target = Node.void('target')
target = Node.void("target")
root.add_child(target)
target.set_attribute('flag', str(profile.get_int('target_flag')))
target.set_attribute('setnum', str(profile.get_int('target_setnum')))
target.set_attribute("flag", str(profile.get_int("target_flag")))
target.set_attribute("setnum", str(profile.get_int("target_setnum")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
root.add_child(gr_s)
index = 1
for entry in profile.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
root.add_child(gr_d)
index = 1
for entry in profile.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in profile.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Options in menus
root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16)))
root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16)))
root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16)))
root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16)))
# Unlock flags
root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256)))
root.add_child(
Node.u8_array("flag", profile.get_int_array("flag", 256, [1] * 256))
)
# Ranking display?
root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100)))
root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100)))
# Rivals
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
friend = self.get_profile(link.other_userid)
play_stats = self.get_play_statistics(link.other_userid)
if friend is not None:
friendnode = Node.void('friend')
friendnode = Node.void("friend")
root.add_child(friendnode)
friendnode.set_attribute('pos', str(pos))
friendnode.set_attribute('vs', '0')
friendnode.set_attribute('up', '0')
friendnode.add_child(Node.u32('code', friend.extid))
friendnode.add_child(Node.string('name', friend.get_str('name')))
friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region())))
friendnode.add_child(Node.u32('exp', play_stats.get_int('exp')))
friendnode.add_child(Node.u32('star', friend.get_int('star')))
friendnode.set_attribute("pos", str(pos))
friendnode.set_attribute("vs", "0")
friendnode.set_attribute("up", "0")
friendnode.add_child(Node.u32("code", friend.extid))
friendnode.add_child(Node.string("name", friend.get_str("name")))
friendnode.add_child(
Node.u8("area", friend.get_int("area", self.get_machine_region()))
)
friendnode.add_child(Node.u32("exp", play_stats.get_int("exp")))
friendnode.add_child(Node.u32("star", friend.get_int("star")))
# Drill rankings
if 'title' in friend:
title = Node.void('title')
if "title" in friend:
title = Node.void("title")
friendnode.add_child(title)
titledict = friend.get_dict('title')
if 't' in titledict:
title.set_attribute('t', str(titledict.get_int('t')))
if 's' in titledict:
title.set_attribute('s', str(titledict.get_int('s')))
if 'd' in titledict:
title.set_attribute('d', str(titledict.get_int('d')))
titledict = friend.get_dict("title")
if "t" in titledict:
title.set_attribute("t", str(titledict.get_int("t")))
if "s" in titledict:
title.set_attribute("s", str(titledict.get_int("s")))
if "d" in titledict:
title.set_attribute("d", str(titledict.get_int("d")))
if 'title_gr' in friend:
title_gr = Node.void('title_gr')
if "title_gr" in friend:
title_gr = Node.void("title_gr")
friendnode.add_child(title_gr)
title_grdict = friend.get_dict('title_gr')
if 't' in title_grdict:
title_gr.set_attribute('t', str(title_grdict.get_int('t')))
if 's' in title_grdict:
title_gr.set_attribute('s', str(title_grdict.get_int('s')))
if 'd' in title_grdict:
title_gr.set_attribute('d', str(title_grdict.get_int('d')))
title_grdict = friend.get_dict("title_gr")
if "t" in title_grdict:
title_gr.set_attribute("t", str(title_grdict.get_int("t")))
if "s" in title_grdict:
title_gr.set_attribute("s", str(title_grdict.get_int("s")))
if "d" in title_grdict:
title_gr.set_attribute("d", str(title_grdict.get_int("d")))
# Groove gauge level-ups
gr_s = Node.void('gr_s')
gr_s = Node.void("gr_s")
friendnode.add_child(gr_s)
index = 1
for entry in friend.get_int_array('gr_s', 5):
gr_s.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_s", 5):
gr_s.set_attribute(f"gr{index}", str(entry))
index = index + 1
gr_d = Node.void('gr_d')
gr_d = Node.void("gr_d")
friendnode.add_child(gr_d)
index = 1
for entry in friend.get_int_array('gr_d', 5):
gr_d.set_attribute(f'gr{index}', str(entry))
for entry in friend.get_int_array("gr_d", 5):
gr_d.set_attribute(f"gr{index}", str(entry))
index = index + 1
# Play area
areas = profile.get_int_array('play_area', 55)
play_area = Node.void('play_area')
areas = profile.get_int_array("play_area", 55)
play_area = Node.void("play_area")
root.add_child(play_area)
for i in range(len(areas)):
play_area.set_attribute(f'play_cnt{i}', str(areas[i]))
play_area.set_attribute(f"play_cnt{i}", str(areas[i]))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
play_stats = self.get_play_statistics(userid)
# Grab last node and accessories so we can make decisions based on type
last = request.child('last')
lastdict = newprofile.get_dict('last')
mode = int(last.attribute('mode'))
style = int(last.attribute('style'))
last = request.child("last")
lastdict = newprofile.get_dict("last")
mode = int(last.attribute("mode"))
style = int(last.attribute("style"))
is_dp = style == self.GAME_STYLE_DOUBLE
# Drill rankings
title = request.child('title')
title_gr = request.child('title_gr')
titledict = newprofile.get_dict('title')
title_grdict = newprofile.get_dict('title_gr')
title = request.child("title")
title_gr = request.child("title_gr")
titledict = newprofile.get_dict("title")
title_grdict = newprofile.get_dict("title_gr")
# Groove radar level ups
gr = request.child('gr')
gr = request.child("gr")
# Set the correct values depending on if we're single or double play
if is_dp:
play_stats.increment_int('double_plays')
play_stats.increment_int("double_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_d',
"gr_d",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('d', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("d", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('d', title.value)
newprofile.replace_dict('title_gr', title_grdict)
title_grdict.replace_int("d", title.value)
newprofile.replace_dict("title_gr", title_grdict)
else:
play_stats.increment_int('single_plays')
play_stats.increment_int("single_plays")
if gr is not None:
newprofile.replace_int_array(
'gr_s',
"gr_s",
5,
[
intish(gr.attribute('gr1')),
intish(gr.attribute('gr2')),
intish(gr.attribute('gr3')),
intish(gr.attribute('gr4')),
intish(gr.attribute('gr5')),
intish(gr.attribute("gr1")),
intish(gr.attribute("gr2")),
intish(gr.attribute("gr3")),
intish(gr.attribute("gr4")),
intish(gr.attribute("gr5")),
],
)
if title is not None:
titledict.replace_int('s', title.value)
newprofile.replace_dict('title', titledict)
titledict.replace_int("s", title.value)
newprofile.replace_dict("title", titledict)
if title_gr is not None:
title_grdict.replace_int('s', title.value)
newprofile.replace_dict('title_gr', title_grdict)
play_stats.increment_int(f'cnt_m{mode}')
title_grdict.replace_int("s", title.value)
newprofile.replace_dict("title_gr", title_grdict)
play_stats.increment_int(f"cnt_m{mode}")
# Result stars
result_star = request.child('result_star')
result_star = request.child("result_star")
if result_star is not None:
newprofile.replace_int_array(
'result_stars',
"result_stars",
9,
[
intish(result_star.attribute('slot1')),
intish(result_star.attribute('slot2')),
intish(result_star.attribute('slot3')),
intish(result_star.attribute('slot4')),
intish(result_star.attribute('slot5')),
intish(result_star.attribute('slot6')),
intish(result_star.attribute('slot7')),
intish(result_star.attribute('slot8')),
intish(result_star.attribute('slot9')),
intish(result_star.attribute("slot1")),
intish(result_star.attribute("slot2")),
intish(result_star.attribute("slot3")),
intish(result_star.attribute("slot4")),
intish(result_star.attribute("slot5")),
intish(result_star.attribute("slot6")),
intish(result_star.attribute("slot7")),
intish(result_star.attribute("slot8")),
intish(result_star.attribute("slot9")),
],
)
# Target stuff
target = request.child('target')
target = request.child("target")
if target is not None:
newprofile.replace_int('target_flag', intish(target.attribute('flag')))
newprofile.replace_int('target_setnum', intish(target.attribute('setnum')))
newprofile.replace_int("target_flag", intish(target.attribute("flag")))
newprofile.replace_int("target_setnum", intish(target.attribute("setnum")))
# Update last attributes
lastdict.replace_int('rival1', intish(last.attribute('rival1')))
lastdict.replace_int('rival2', intish(last.attribute('rival2')))
lastdict.replace_int('rival3', intish(last.attribute('rival3')))
lastdict.replace_int('style', intish(last.attribute('style')))
lastdict.replace_int('mode', intish(last.attribute('mode')))
lastdict.replace_int('cate', intish(last.attribute('cate')))
lastdict.replace_int('sort', intish(last.attribute('sort')))
lastdict.replace_int('mid', intish(last.attribute('mid')))
lastdict.replace_int('mtype', intish(last.attribute('mtype')))
lastdict.replace_int('cid', intish(last.attribute('cid')))
lastdict.replace_int('ctype', intish(last.attribute('ctype')))
lastdict.replace_int('sid', intish(last.attribute('sid')))
newprofile.replace_dict('last', lastdict)
lastdict.replace_int("rival1", intish(last.attribute("rival1")))
lastdict.replace_int("rival2", intish(last.attribute("rival2")))
lastdict.replace_int("rival3", intish(last.attribute("rival3")))
lastdict.replace_int("style", intish(last.attribute("style")))
lastdict.replace_int("mode", intish(last.attribute("mode")))
lastdict.replace_int("cate", intish(last.attribute("cate")))
lastdict.replace_int("sort", intish(last.attribute("sort")))
lastdict.replace_int("mid", intish(last.attribute("mid")))
lastdict.replace_int("mtype", intish(last.attribute("mtype")))
lastdict.replace_int("cid", intish(last.attribute("cid")))
lastdict.replace_int("ctype", intish(last.attribute("ctype")))
lastdict.replace_int("sid", intish(last.attribute("sid")))
newprofile.replace_dict("last", lastdict)
# Grab character options
chara = request.child('chara')
chara = request.child("chara")
if chara is not None:
newprofile.replace_int('chara', intish(chara.attribute('my')))
chara_opt = request.child('chara_opt')
newprofile.replace_int("chara", intish(chara.attribute("my")))
chara_opt = request.child("chara_opt")
if chara_opt is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96])
newprofile.replace_int_array("chara_opt", 96, chara_opt.value[:96])
# Options
opt = request.child('opt')
opt = request.child("opt")
if opt is not None:
# A bug in old versions of AVS returns the wrong number for set
newprofile.replace_int_array('opt', 16, opt.value[:16])
newprofile.replace_int_array("opt", 16, opt.value[:16])
# Experience and stars
exp = request.child_value('exp')
exp = request.child_value("exp")
if exp is not None:
play_stats.replace_int('exp', play_stats.get_int('exp') + exp)
star = request.child_value('star')
play_stats.replace_int("exp", play_stats.get_int("exp") + exp)
star = request.child_value("star")
if star is not None:
newprofile.replace_int('star', newprofile.get_int('star') + star)
star_c = request.child_value('star_c')
newprofile.replace_int("star", newprofile.get_int("star") + star)
star_c = request.child_value("star_c")
if star_c is not None:
newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp)
newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp)
# Update game flags
for child in request.children:
if child.name != 'flag':
if child.name != "flag":
continue
try:
value = int(child.attribute('data'))
offset = int(child.attribute('no'))
value = int(child.attribute("data"))
offset = int(child.attribute("no"))
except ValueError:
continue
flags = newprofile.get_int_array('flag', 256, [1] * 256)
flags = newprofile.get_int_array("flag", 256, [1] * 256)
if offset < 0 or offset >= len(flags):
continue
flags[offset] = value
newprofile.replace_int_array('flag', 256, flags)
newprofile.replace_int_array("flag", 256, flags)
# Workout mode support
newweight = -1
oldweight = newprofile.get_int('weight')
oldweight = newprofile.get_int("weight")
for child in request.children:
if child.name != 'weight':
if child.name != "weight":
continue
newweight = child.value
if newweight < 0:
@ -700,14 +727,14 @@ class DDRX3(
# Either update or unset the weight depending on the game
if newweight == 0:
# Weight is unset or we declined to use this feature, remove from profile
if 'weight' in newprofile:
del newprofile['weight']
if "weight" in newprofile:
del newprofile["weight"]
else:
# Weight has been set or previously retrieved, we should save calories
newprofile.replace_int('weight', newweight)
newprofile.replace_int("weight", newweight)
total = 0
for child in request.children:
if child.name != 'calory':
if child.name != "calory":
continue
total += child.value
self.data.local.user.put_time_based_achievement(
@ -715,10 +742,10 @@ class DDRX3(
self.version,
userid,
0,
'workout',
"workout",
{
'calories': total,
'weight': newweight,
"calories": total,
"weight": newweight,
},
)
@ -726,7 +753,7 @@ class DDRX3(
oldfriends: List[Optional[UserID]] = [None] * 10
links = self.data.local.user.get_links(self.game, self.version, userid)
for link in links:
if link.type[:7] != 'friend_':
if link.type[:7] != "friend_":
continue
pos = int(link.type[7:])
@ -735,11 +762,11 @@ class DDRX3(
# Save any rivals that were added/removed/changed
newfriends = oldfriends[:]
for child in request.children:
if child.name != 'friend':
if child.name != "friend":
continue
code = int(child.attribute('code'))
pos = int(child.attribute('pos'))
code = int(child.attribute("code"))
pos = int(child.attribute("pos"))
if pos >= 0 and pos < 10:
if code == 0:
@ -747,7 +774,9 @@ class DDRX3(
newfriends[pos] = None
else:
# Try looking up the userid
newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code)
newfriends[pos] = self.data.remote.user.from_extid(
self.game, self.version, code
)
# Diff the set of links to determine updates
for i in range(10):
@ -760,7 +789,7 @@ class DDRX3(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
elif oldfriends[i] is None:
@ -769,7 +798,7 @@ class DDRX3(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
@ -779,24 +808,24 @@ class DDRX3(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
oldfriends[i],
)
self.data.local.user.put_link(
self.game,
self.version,
userid,
f'friend_{i}',
f"friend_{i}",
newfriends[i],
{},
)
# Play area counter
shop_area = int(request.attribute('shop_area'))
shop_area = int(request.attribute("shop_area"))
if shop_area >= 0 and shop_area < 55:
areas = newprofile.get_int_array('play_area', 55)
areas = newprofile.get_int_array("play_area", 55)
areas[shop_area] = areas[shop_area] + 1
newprofile.replace_int_array('play_area', 55, areas)
newprofile.replace_int_array("play_area", 55, areas)
# Keep track of play statistics
self.update_play_statistics(userid, play_stats)

View File

@ -48,12 +48,17 @@ class DDRFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['HDX', 'JDX', 'KDX', 'MDX']:
for gamecode in ["HDX", "JDX", "KDX", "MDX"]:
Base.register(gamecode, DDRFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
def version_from_date(date: int) -> Optional[int]:
if date < 2014051200:
return VersionConstants.DDR_2013
@ -65,20 +70,20 @@ class DDRFactory(Factory):
return VersionConstants.DDR_A20
return None
if model.gamecode == 'HDX':
if model.gamecode == "HDX":
return DDRX(data, config, model)
if model.gamecode == 'JDX':
if model.gamecode == "JDX":
return DDRX2(data, config, model)
if model.gamecode == 'KDX':
if model.gamecode == "KDX":
return DDRX3(data, config, model)
if model.gamecode == 'MDX':
if model.gamecode == "MDX":
if model.version is None:
if parentmodel is None:
return None
# We have no way to tell apart newer versions. However, we can make
# an educated guess if we happen to be summoned for old profile lookup.
if parentmodel.gamecode not in ['HDX', 'JDX', 'KDX', 'MDX']:
if parentmodel.gamecode not in ["HDX", "JDX", "KDX", "MDX"]:
return None
parentversion = version_from_date(parentmodel.version)

View File

@ -7,7 +7,7 @@ from bemani.common import VersionConstants
class DDRX(DDRBase):
name: str = 'DanceDanceRevolution X'
name: str = "DanceDanceRevolution X"
version: int = VersionConstants.DDR_X
def previous_version(self) -> Optional[DDRBase]:
@ -16,7 +16,7 @@ class DDRX(DDRBase):
class DDRSuperNova2(DDRBase):
name: str = 'DanceDanceRevolution SuperNova 2'
name: str = "DanceDanceRevolution SuperNova 2"
version: int = VersionConstants.DDR_SUPERNOVA_2
def previous_version(self) -> Optional[DDRBase]:
@ -25,7 +25,7 @@ class DDRSuperNova2(DDRBase):
class DDRSuperNova(DDRBase):
name: str = 'DanceDanceRevolution SuperNova'
name: str = "DanceDanceRevolution SuperNova"
version: int = VersionConstants.DDR_SUPERNOVA
def previous_version(self) -> Optional[DDRBase]:
@ -34,7 +34,7 @@ class DDRSuperNova(DDRBase):
class DDRExtreme(DDRBase):
name: str = 'DanceDanceRevolution Extreme'
name: str = "DanceDanceRevolution Extreme"
version: int = VersionConstants.DDR_EXTREME
def previous_version(self) -> Optional[DDRBase]:
@ -43,7 +43,7 @@ class DDRExtreme(DDRBase):
class DDR7thMix(DDRBase):
name: str = 'DanceDanceRevolution 7thMix'
name: str = "DanceDanceRevolution 7thMix"
version: int = VersionConstants.DDR_7THMIX
def previous_version(self) -> Optional[DDRBase]:
@ -52,7 +52,7 @@ class DDR7thMix(DDRBase):
class DDR6thMix(DDRBase):
name: str = 'DanceDanceRevolution 6thMix'
name: str = "DanceDanceRevolution 6thMix"
version: int = VersionConstants.DDR_6THMIX
def previous_version(self) -> Optional[DDRBase]:
@ -61,7 +61,7 @@ class DDR6thMix(DDRBase):
class DDR5thMix(DDRBase):
name: str = 'DanceDanceRevolution 5thMix'
name: str = "DanceDanceRevolution 5thMix"
version: int = VersionConstants.DDR_5THMIX
def previous_version(self) -> Optional[DDRBase]:
@ -70,7 +70,7 @@ class DDR5thMix(DDRBase):
class DDR4thMix(DDRBase):
name: str = 'DanceDanceRevolution 4thMix'
name: str = "DanceDanceRevolution 4thMix"
version: int = VersionConstants.DDR_4THMIX
def previous_version(self) -> Optional[DDRBase]:
@ -79,7 +79,7 @@ class DDR4thMix(DDRBase):
class DDR3rdMix(DDRBase):
name: str = 'DanceDanceRevolution 3rdMix'
name: str = "DanceDanceRevolution 3rdMix"
version: int = VersionConstants.DDR_3RDMIX
def previous_version(self) -> Optional[DDRBase]:
@ -88,7 +88,7 @@ class DDR3rdMix(DDRBase):
class DDR2ndMix(DDRBase):
name: str = 'DanceDanceRevolution 2ndMix'
name: str = "DanceDanceRevolution 2ndMix"
version: int = VersionConstants.DDR_2NDMIX
def previous_version(self) -> Optional[DDRBase]:
@ -97,5 +97,5 @@ class DDR2ndMix(DDRBase):
class DDR1stMix(DDRBase):
name: str = 'DanceDanceRevolution 1stMix'
name: str = "DanceDanceRevolution 1stMix"
version: int = VersionConstants.DDR_1STMIX

View File

@ -61,7 +61,7 @@ class Dispatch:
"""
self.log("Received request:\n{}", tree)
if tree.name != 'call':
if tree.name != "call":
# Invalid request
self.log("Invalid root node {}", tree.name)
return None
@ -71,15 +71,17 @@ class Dispatch:
self.log("Invalid number of children for root node")
return None
modelstring = tree.attribute('model')
modelstring = tree.attribute("model")
model = Model.from_modelstring(modelstring)
pcbid = tree.attribute('srcid')
pcbid = tree.attribute("srcid")
# If we are enforcing, bail out if we don't recognize thie ID
pcb = self.__data.local.machine.get_machine(pcbid)
if self.__config.server.enforce_pcbid and pcb is None:
self.log("Unrecognized PCBID {}", pcbid)
raise UnrecognizedPCBIDException(pcbid, modelstring, self.__config.client.address)
raise UnrecognizedPCBIDException(
pcbid, modelstring, self.__config.client.address
)
# If we don't have a Machine, but we aren't enforcing, we must create it
if pcb is None:
@ -88,9 +90,9 @@ class Dispatch:
request = tree.children[0]
config = self.__config.clone()
config['machine'] = {
'pcbid': pcbid,
'arcade': pcb.arcade,
config["machine"] = {
"pcbid": pcbid,
"arcade": pcb.arcade,
}
# If the machine we looked up is in an arcade, override the global
@ -98,22 +100,29 @@ class Dispatch:
if pcb.arcade is not None:
arcade = self.__data.local.machine.get_arcade(pcb.arcade)
if arcade is not None:
config['paseli']['enabled'] = arcade.data.get_bool('paseli_enabled')
config['paseli']['infinite'] = arcade.data.get_bool('paseli_infinite')
if arcade.data.get_bool('mask_services_url'):
config["paseli"]["enabled"] = arcade.data.get_bool("paseli_enabled")
config["paseli"]["infinite"] = arcade.data.get_bool("paseli_infinite")
if arcade.data.get_bool("mask_services_url"):
# Mask the address, no matter what the server settings are
config['server']['uri'] = None
config["server"]["uri"] = None
game = Base.create(self.__data, config, model)
method = request.attribute('method')
method = request.attribute("method")
response = None
# If we are enforcing, make sure the PCBID isn't specified to be
# game-specific
if config.server.enforce_pcbid and pcb.game is not None:
if pcb.game != game.game:
self.log("PCBID {} assigned to game {}, but connected from game {}", pcbid, pcb.game, game.game)
raise UnrecognizedPCBIDException(pcbid, modelstring, config.client.address)
self.log(
"PCBID {} assigned to game {}, but connected from game {}",
pcbid,
pcb.game,
game.game,
)
raise UnrecognizedPCBIDException(
pcbid, modelstring, config.client.address
)
if pcb.version is not None:
if pcb.version > 0 and pcb.version != game.version:
self.log(
@ -124,7 +133,9 @@ class Dispatch:
game.game,
game.version,
)
raise UnrecognizedPCBIDException(pcbid, modelstring, config.client.address)
raise UnrecognizedPCBIDException(
pcbid, modelstring, config.client.address
)
if pcb.version < 0 and (-pcb.version) < game.version:
self.log(
"PCBID {} assigned to game {} maximum version {}, but connected from game {} version {}",
@ -134,11 +145,13 @@ class Dispatch:
game.game,
game.version,
)
raise UnrecognizedPCBIDException(pcbid, modelstring, config.client.address)
raise UnrecognizedPCBIDException(
pcbid, modelstring, config.client.address
)
# First, try to handle with specific service/method function
try:
handler = getattr(game, f'handle_{request.name}_{method}_request')
handler = getattr(game, f"handle_{request.name}_{method}_request")
except AttributeError:
handler = None
if handler is not None:
@ -147,7 +160,7 @@ class Dispatch:
if response is None:
# Now, try to pass it off to a generic service handler
try:
handler = getattr(game, f'handle_{request.name}_requests')
handler = getattr(game, f"handle_{request.name}_requests")
except AttributeError:
handler = None
if handler is not None:
@ -159,12 +172,12 @@ class Dispatch:
return None
# Make sure we have a status value if one wasn't provided
if 'status' not in response.attributes:
response.set_attribute('status', str(Status.SUCCESS))
if "status" not in response.attributes:
response.set_attribute("status", str(Status.SUCCESS))
root = Node.void('response')
root = Node.void("response")
root.add_child(response)
root.set_attribute('dstid', pcbid)
root.set_attribute("dstid", pcbid)
self.log("Sending response:\n{}", root)

View File

@ -11,13 +11,13 @@ class EventLogHandler(Base):
def handle_eventlog_write_request(self, request: Node) -> Node:
# Just turn off further logging
gamesession = request.child_value('data/gamesession')
gamesession = request.child_value("data/gamesession")
if gamesession < 0:
gamesession = random.randint(1, 1000000)
root = Node.void('eventlog')
root.add_child(Node.s64('gamesession', gamesession))
root.add_child(Node.s32('logsendflg', 0))
root.add_child(Node.s32('logerrlevel', 0))
root.add_child(Node.s32('evtidnosendflg', 0))
root = Node.void("eventlog")
root.add_child(Node.s64("gamesession", gamesession))
root.add_child(Node.s32("logsendflg", 0))
root.add_child(Node.s32("logerrlevel", 0))
root.add_child(Node.s32("evtidnosendflg", 0))
return root

View File

@ -5,7 +5,14 @@ from typing_extensions import Final
from bemani.backend.base import Base
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
from bemani.common import Profile, ValidatedDict, Model, GameConstants, DBConstants, Parallel
from bemani.common import (
Profile,
ValidatedDict,
Model,
GameConstants,
DBConstants,
Parallel,
)
from bemani.data import Config, Data, Score, Machine, UserID
from bemani.protocol import Node
@ -62,8 +69,8 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
DAN_RANK_CHUDEN: Final[int] = DBConstants.IIDX_DAN_RANK_CHUDEN
DAN_RANK_KAIDEN: Final[int] = DBConstants.IIDX_DAN_RANK_KAIDEN
DAN_RANKING_SINGLE: Final[str] = 'sgrade'
DAN_RANKING_DOUBLE: Final[str] = 'dgrade'
DAN_RANKING_SINGLE: Final[str] = "sgrade"
DAN_RANKING_DOUBLE: Final[str] = "dgrade"
GHOST_TYPE_NONE: Final[int] = 0
GHOST_TYPE_RIVAL: Final[int] = 100
@ -78,12 +85,12 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Return the local2 service so that Copula and above will send certain packets.
extra_services: List[str] = [
'local2',
"local2",
]
def __init__(self, data: Data, config: Config, model: Model) -> None:
super().__init__(data, config, model)
if model.rev == 'X':
if model.rev == "X":
self.omnimix = True
else:
self.omnimix = False
@ -94,7 +101,7 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return DBConstants.OMNIMIX_VERSION_BUMP + self.version
return self.version
def previous_version(self) -> Optional['IIDXBase']:
def previous_version(self) -> Optional["IIDXBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -106,9 +113,11 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('pc')
return Node.void("pc")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -136,7 +145,9 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
return self.format_profile(userid, profile)
def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], pid: Optional[int]) -> Profile:
def new_profile_by_refid(
self, refid: Optional[str], name: Optional[str], pid: Optional[int]
) -> Profile:
"""
Given a RefID and an optional name, create a profile and then return
that newly created profile.
@ -145,7 +156,7 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
if name is None:
name = 'なし'
name = "なし"
if pid is None:
pid = self.get_machine_region()
@ -156,10 +167,10 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
refid,
0,
{
'name': name,
'pid': pid,
'settings': {
'flags': 223 # Default to turning on all optional folders
"name": name,
"pid": pid,
"settings": {
"flags": 223 # Default to turning on all optional folders
},
},
)
@ -192,8 +203,8 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
def get_clear_rates(
self,
songid: Optional[int]=None,
songchart: Optional[int]=None,
songid: Optional[int] = None,
songchart: Optional[int] = None,
) -> Dict[int, Dict[int, Dict[str, int]]]:
"""
Returns a dictionary similar to the following:
@ -208,24 +219,26 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
},
}
"""
all_attempts, remote_attempts = Parallel.execute([
lambda: self.data.local.music.get_all_attempts(
game=self.game,
version=self.music_version,
songid=songid,
songchart=songchart,
),
lambda: self.data.remote.music.get_clear_rates(
game=self.game,
version=self.music_version,
songid=songid,
songchart=songchart,
),
])
all_attempts, remote_attempts = Parallel.execute(
[
lambda: self.data.local.music.get_all_attempts(
game=self.game,
version=self.music_version,
songid=songid,
songchart=songchart,
),
lambda: self.data.remote.music.get_clear_rates(
game=self.game,
version=self.music_version,
songid=songid,
songchart=songchart,
),
]
)
attempts: Dict[int, Dict[int, Dict[str, int]]] = {}
for (_, attempt) in all_attempts:
if attempt.data.get_int('clear_status') == self.CLEAR_STATUS_NO_PLAY:
if attempt.data.get_int("clear_status") == self.CLEAR_STATUS_NO_PLAY:
# This attempt was outside of the clear infra, so don't bother with it.
continue
@ -234,24 +247,33 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
attempts[attempt.id] = {}
if attempt.chart not in attempts[attempt.id]:
attempts[attempt.id][attempt.chart] = {
'total': 0,
'clears': 0,
'fcs': 0,
"total": 0,
"clears": 0,
"fcs": 0,
}
# We saw an attempt, keep the total attempts in sync.
attempts[attempt.id][attempt.chart]['total'] = attempts[attempt.id][attempt.chart]['total'] + 1
attempts[attempt.id][attempt.chart]["total"] = (
attempts[attempt.id][attempt.chart]["total"] + 1
)
if attempt.data.get_int('clear_status', self.CLEAR_STATUS_FAILED) == self.CLEAR_STATUS_FAILED:
if (
attempt.data.get_int("clear_status", self.CLEAR_STATUS_FAILED)
== self.CLEAR_STATUS_FAILED
):
# This attempt was a failure, so don't count it against clears of full combos
continue
# It was at least a clear
attempts[attempt.id][attempt.chart]['clears'] = attempts[attempt.id][attempt.chart]['clears'] + 1
attempts[attempt.id][attempt.chart]["clears"] = (
attempts[attempt.id][attempt.chart]["clears"] + 1
)
if attempt.data.get_int('clear_status') == self.CLEAR_STATUS_FULL_COMBO:
if attempt.data.get_int("clear_status") == self.CLEAR_STATUS_FULL_COMBO:
# This was a full combo clear, so it also counts here
attempts[attempt.id][attempt.chart]['fcs'] = attempts[attempt.id][attempt.chart]['fcs'] + 1
attempts[attempt.id][attempt.chart]["fcs"] = (
attempts[attempt.id][attempt.chart]["fcs"] + 1
)
# Merge in remote attempts
for songid in remote_attempts:
@ -261,14 +283,20 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
for songchart in remote_attempts[songid]:
if songchart not in attempts[songid]:
attempts[songid][songchart] = {
'total': 0,
'clears': 0,
'fcs': 0,
"total": 0,
"clears": 0,
"fcs": 0,
}
attempts[songid][songchart]['total'] += remote_attempts[songid][songchart]['plays']
attempts[songid][songchart]['clears'] += remote_attempts[songid][songchart]['clears']
attempts[songid][songchart]['fcs'] += remote_attempts[songid][songchart]['combos']
attempts[songid][songchart]["total"] += remote_attempts[songid][
songchart
]["plays"]
attempts[songid][songchart]["clears"] += remote_attempts[songid][
songchart
]["clears"]
attempts[songid][songchart]["fcs"] += remote_attempts[songid][
songchart
]["combos"]
# If requesting a specific song/chart, make sure its in the dict
if songid is not None:
@ -278,9 +306,9 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
if songchart is not None:
if songchart not in attempts[songid]:
attempts[songid][songchart] = {
'total': 0,
'clears': 0,
'fcs': 0,
"total": 0,
"clears": 0,
"fcs": 0,
}
return attempts
@ -339,26 +367,30 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
oldscore = None
# Score history is verbatum, instead of highest score
history = ValidatedDict({
'clear_status': clear_status,
'miss_count': miss_count,
})
history = ValidatedDict(
{
"clear_status": clear_status,
"miss_count": miss_count,
}
)
old_ex_score = ex_score
if ghost is not None:
history['ghost'] = ghost
history["ghost"] = ghost
if oldscore is None:
# If it is a new score, create a new dictionary to add to
scoredata = ValidatedDict({
'clear_status': clear_status,
'pgreats': pgreats,
'greats': greats,
})
scoredata = ValidatedDict(
{
"clear_status": clear_status,
"pgreats": pgreats,
"greats": greats,
}
)
if miss_count != -1:
scoredata.replace_int('miss_count', miss_count)
scoredata.replace_int("miss_count", miss_count)
if ghost is not None:
scoredata['ghost'] = ghost
scoredata["ghost"] = ghost
raised = True
highscore = True
else:
@ -367,21 +399,25 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
highscore = ex_score >= oldscore.points
ex_score = max(ex_score, oldscore.points)
scoredata = oldscore.data
scoredata.replace_int('clear_status', max(scoredata.get_int('clear_status'), clear_status))
scoredata.replace_int(
"clear_status", max(scoredata.get_int("clear_status"), clear_status)
)
if miss_count != -1:
if scoredata.get_int('miss_count', -1) == -1:
scoredata.replace_int('miss_count', miss_count)
if scoredata.get_int("miss_count", -1) == -1:
scoredata.replace_int("miss_count", miss_count)
else:
scoredata.replace_int('miss_count', min(scoredata.get_int('miss_count'), miss_count))
scoredata.replace_int(
"miss_count", min(scoredata.get_int("miss_count"), miss_count)
)
if raised:
scoredata.replace_int('pgreats', pgreats)
scoredata.replace_int('greats', greats)
scoredata.replace_int("pgreats", pgreats)
scoredata.replace_int("greats", greats)
if ghost is not None:
scoredata.replace_bytes('ghost', ghost)
scoredata.replace_bytes("ghost", ghost)
if shop is not None:
history.replace_int('shop', shop)
scoredata.replace_int('shop', shop)
history.replace_int("shop", shop)
scoredata.replace_int("shop", shop)
# Look up where this score was earned
lid = self.get_machine_id()
@ -477,42 +513,41 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
)
if dan_score is None:
dan_score = ValidatedDict()
dan_score.replace_int('percent', max(percent, dan_score.get_int('percent')))
dan_score.replace_int('stages_cleared', max(stages_cleared, dan_score.get_int('stages_cleared')))
dan_score.replace_int("percent", max(percent, dan_score.get_int("percent")))
dan_score.replace_int(
"stages_cleared", max(stages_cleared, dan_score.get_int("stages_cleared"))
)
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
rank,
dantype,
dan_score
self.game, self.version, userid, rank, dantype, dan_score
)
def db_to_game_status(self, db_status: int) -> int:
"""
Given a DB status, translate to a game clear status.
"""
raise Exception('Implement in specific game class!')
raise Exception("Implement in specific game class!")
def game_to_db_status(self, game_status: int) -> int:
"""
Given a game clear status, translate to DB status.
"""
raise Exception('Implement in specific game class!')
raise Exception("Implement in specific game class!")
def game_to_db_chart(self, game_chart: int) -> int:
"""
Given a game's chart for a song, return the chart as defined above.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def db_to_game_chart(self, db_chart: int) -> int:
"""
Given a chart as defined above, return the game's chart constant.
"""
raise Exception('Implement in sub-class!')
raise Exception("Implement in sub-class!")
def make_score_struct(self, scores: List[Score], cltype: int, index: int) -> List[List[int]]:
def make_score_struct(
self, scores: List[Score], cltype: int, index: int
) -> List[List[int]]:
scorestruct: Dict[int, List[int]] = {}
for score in scores:
@ -560,9 +595,11 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
-1, # Miss count another,
]
scorestruct[musicid][chartindex + 2] = self.db_to_game_status(score.data.get_int('clear_status'))
scorestruct[musicid][chartindex + 2] = self.db_to_game_status(
score.data.get_int("clear_status")
)
scorestruct[musicid][chartindex + 5] = score.points
scorestruct[musicid][chartindex + 8] = score.data.get_int('miss_count', -1)
scorestruct[musicid][chartindex + 8] = score.data.get_int("miss_count", -1)
return [scorestruct[s] for s in scorestruct]
@ -577,10 +614,12 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
if chart != self.CHART_TYPE_B7:
continue
scorelist.append([
musicid,
self.db_to_game_status(score.data.get_int('clear_status')),
])
scorelist.append(
[
musicid,
self.db_to_game_status(score.data.get_int("clear_status")),
]
)
return scorelist
@ -597,7 +636,7 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Sum up for each bucket
for score in scores:
ghost = score.data.get_bytes('ghost')
ghost = score.data.get_bytes("ghost")
for i in range(len(ghost)):
total_ghost[i] = total_ghost[i] + ghost[i]
count = count + 1
@ -624,16 +663,16 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
delta_ghost = [total_ghost[i] - reference_ghost[i] for i in range(ghost_length)]
# Return averages
return new_ex_score, struct.pack('b' * ghost_length, *delta_ghost)
return new_ex_score, struct.pack("b" * ghost_length, *delta_ghost)
def user_joined_arcade(self, machine: Machine, profile: Optional[Profile]) -> bool:
if profile is None:
return False
if 'shop_location' not in profile:
if "shop_location" not in profile:
return False
machineid = profile.get_int('shop_location')
machineid = profile.get_int("shop_location")
if machineid == machine.id:
# We can short-circuit arcade lookup because their machine
# is the current machine.
@ -659,30 +698,39 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
if ghost_type == self.GHOST_TYPE_RIVAL:
rival_extid = int(parameter)
rival_userid = self.data.remote.user.from_extid(self.game, self.version, rival_extid)
rival_userid = self.data.remote.user.from_extid(
self.game, self.version, rival_extid
)
if rival_userid is not None:
rival_profile = self.get_profile(rival_userid)
rival_score = self.data.remote.music.get_score(self.game, self.music_version, rival_userid, musicid, chart)
rival_score = self.data.remote.music.get_score(
self.game, self.music_version, rival_userid, musicid, chart
)
if rival_score is not None and rival_profile is not None:
ghost_score = {
'score': rival_score.points,
'ghost': rival_score.data.get_bytes('ghost'),
'name': rival_profile.get_str('name'),
'pid': rival_profile.get_int('pid'),
"score": rival_score.points,
"ghost": rival_score.data.get_bytes("ghost"),
"name": rival_profile.get_str("name"),
"pid": rival_profile.get_int("pid"),
}
if (
ghost_type == self.GHOST_TYPE_GLOBAL_TOP or
ghost_type == self.GHOST_TYPE_LOCAL_TOP or
ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE or
ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE
ghost_type == self.GHOST_TYPE_GLOBAL_TOP
or ghost_type == self.GHOST_TYPE_LOCAL_TOP
or ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE
or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE
):
if (
ghost_type == self.GHOST_TYPE_LOCAL_TOP or
ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE
ghost_type == self.GHOST_TYPE_LOCAL_TOP
or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE
):
all_scores = sorted(
self.data.local.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart),
self.data.local.music.get_all_scores(
game=self.game,
version=self.music_version,
songid=musicid,
songchart=chart,
),
key=lambda s: s[1].points,
reverse=True,
)
@ -698,30 +746,38 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
0,
)
if 'shop_location' in my_profile:
shop_id = my_profile.get_int('shop_location')
if "shop_location" in my_profile:
shop_id = my_profile.get_int("shop_location")
machine = self.get_machine_by_id(shop_id)
else:
machine = None
if machine is not None:
all_scores = [
score for score in all_scores
if self.user_joined_arcade(machine, self.get_any_profile(score[0]))
score
for score in all_scores
if self.user_joined_arcade(
machine, self.get_any_profile(score[0])
)
]
else:
# Not joined an arcade, so nobody matches our scores
all_scores = []
else:
all_scores = sorted(
self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart),
self.data.remote.music.get_all_scores(
game=self.game,
version=self.music_version,
songid=musicid,
songchart=chart,
),
key=lambda s: s[1].points,
reverse=True,
)
if (
ghost_type == self.GHOST_TYPE_GLOBAL_TOP or
ghost_type == self.GHOST_TYPE_LOCAL_TOP
ghost_type == self.GHOST_TYPE_GLOBAL_TOP
or ghost_type == self.GHOST_TYPE_LOCAL_TOP
):
for potential_top in all_scores:
top_userid = potential_top[0]
@ -729,28 +785,30 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
top_profile = self.get_any_profile(top_userid)
if top_profile is not None:
ghost_score = {
'score': top_score.points,
'ghost': top_score.data.get_bytes('ghost'),
'name': top_profile.get_str('name'),
'pid': top_profile.get_int('pid'),
'extid': top_profile.extid,
"score": top_score.points,
"ghost": top_score.data.get_bytes("ghost"),
"name": top_profile.get_str("name"),
"pid": top_profile.get_int("pid"),
"extid": top_profile.extid,
}
break
if (
ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE or
ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE
ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE
or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE
):
average_score, delta_ghost = self.delta_score([score[1] for score in all_scores], ghost_length)
average_score, delta_ghost = self.delta_score(
[score[1] for score in all_scores], ghost_length
)
if average_score is not None and delta_ghost is not None:
ghost_score = {
'score': average_score,
'ghost': bytes([0] * ghost_length),
"score": average_score,
"ghost": bytes([0] * ghost_length),
}
if (
ghost_type == self.GHOST_TYPE_DAN_TOP or
ghost_type == self.GHOST_TYPE_DAN_AVERAGE
ghost_type == self.GHOST_TYPE_DAN_TOP
or ghost_type == self.GHOST_TYPE_DAN_AVERAGE
):
is_dp = chart not in [
self.CHART_TYPE_N7,
@ -772,18 +830,28 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
if dan_rank != -1:
all_scores = sorted(
self.data.local.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart),
self.data.local.music.get_all_scores(
game=self.game,
version=self.music_version,
songid=musicid,
songchart=chart,
),
key=lambda s: s[1].points,
reverse=True,
)
all_profiles = self.data.local.user.get_all_profiles(self.game, self.version)
all_profiles = self.data.local.user.get_all_profiles(
self.game, self.version
)
relevant_userids = {
profile[0] for profile in all_profiles
if profile[1].get_int(self.DAN_RANKING_DOUBLE if is_dp else self.DAN_RANKING_SINGLE) == dan_rank
profile[0]
for profile in all_profiles
if profile[1].get_int(
self.DAN_RANKING_DOUBLE if is_dp else self.DAN_RANKING_SINGLE
)
== dan_rank
}
relevant_scores = [
score for score in all_scores
if score[0] in relevant_userids
score for score in all_scores if score[0] in relevant_userids
]
if ghost_type == self.GHOST_TYPE_DAN_TOP:
for potential_top in relevant_scores:
@ -792,27 +860,29 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
top_profile = self.get_any_profile(top_userid)
if top_profile is not None:
ghost_score = {
'score': top_score.points,
'ghost': top_score.data.get_bytes('ghost'),
'name': top_profile.get_str('name'),
'pid': top_profile.get_int('pid'),
'extid': top_profile.extid,
"score": top_score.points,
"ghost": top_score.data.get_bytes("ghost"),
"name": top_profile.get_str("name"),
"pid": top_profile.get_int("pid"),
"extid": top_profile.extid,
}
break
if ghost_type == self.GHOST_TYPE_DAN_AVERAGE:
average_score, delta_ghost = self.delta_score([score[1] for score in relevant_scores], ghost_length)
average_score, delta_ghost = self.delta_score(
[score[1] for score in relevant_scores], ghost_length
)
if average_score is not None and delta_ghost is not None:
ghost_score = {
'score': average_score,
'ghost': bytes([0] * ghost_length),
"score": average_score,
"ghost": bytes([0] * ghost_length),
}
if (
ghost_type == self.GHOST_TYPE_RIVAL_TOP or
ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE
ghost_type == self.GHOST_TYPE_RIVAL_TOP
or ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE
):
rival_extids = [int(e[1:-1]) for e in parameter.split(',')]
rival_extids = [int(e[1:-1]) for e in parameter.split(",")]
rival_userids = [
self.data.remote.user.from_extid(self.game, self.version, rival_extid)
for rival_extid in rival_extids
@ -820,8 +890,13 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
all_scores = sorted(
[
score for score in
self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart)
score
for score in self.data.remote.music.get_all_scores(
game=self.game,
version=self.music_version,
songid=musicid,
songchart=chart,
)
if score[0] in rival_userids
],
key=lambda s: s[1].points,
@ -834,20 +909,22 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
top_profile = self.get_any_profile(top_userid)
if top_profile is not None:
ghost_score = {
'score': top_score.points,
'ghost': top_score.data.get_bytes('ghost'),
'name': top_profile.get_str('name'),
'pid': top_profile.get_int('pid'),
'extid': top_profile.extid,
"score": top_score.points,
"ghost": top_score.data.get_bytes("ghost"),
"name": top_profile.get_str("name"),
"pid": top_profile.get_int("pid"),
"extid": top_profile.extid,
}
break
if ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE:
average_score, delta_ghost = self.delta_score([score[1] for score in all_scores], ghost_length)
average_score, delta_ghost = self.delta_score(
[score[1] for score in all_scores], ghost_length
)
if average_score is not None and delta_ghost is not None:
ghost_score = {
'score': average_score,
'ghost': bytes([0] * ghost_length),
"score": average_score,
"ghost": bytes([0] * ghost_length),
}
return ghost_score

View File

@ -8,7 +8,7 @@ from bemani.common import VersionConstants
class IIDXBistrover(IIDXBase):
name: str = 'Beatmania IIDX BISTROVER'
name: str = "Beatmania IIDX BISTROVER"
version: int = VersionConstants.IIDX_BISTROVER
requires_extended_regions: bool = True

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,9 @@ from bemani.data import UserID
class IIDXCourse(IIDXBase):
COURSE_TYPE_SECRET: Final[str] = 'secret_course'
COURSE_TYPE_INTERNET_RANKING: Final[str] = 'ir_course'
COURSE_TYPE_CLASSIC: Final[str] = 'classic_course'
COURSE_TYPE_SECRET: Final[str] = "secret_course"
COURSE_TYPE_INTERNET_RANKING: Final[str] = "ir_course"
COURSE_TYPE_CLASSIC: Final[str] = "classic_course"
def id_and_chart_from_courseid(self, courseid: int) -> Tuple[int, int]:
return (int(courseid / 6), courseid % 6)
@ -57,11 +57,15 @@ class IIDXCourse(IIDXBase):
)
if course_score is None:
course_score = ValidatedDict()
course_score.replace_int('clear_status', max(clear_status, course_score.get_int('clear_status')))
old_ex_score = (course_score.get_int('pgnum') * 2) + course_score.get_int('gnum')
course_score.replace_int(
"clear_status", max(clear_status, course_score.get_int("clear_status"))
)
old_ex_score = (course_score.get_int("pgnum") * 2) + course_score.get_int(
"gnum"
)
if old_ex_score < ((pgreats * 2) + greats):
course_score.replace_int('pgnum', pgreats)
course_score.replace_int('gnum', greats)
course_score.replace_int("pgnum", pgreats)
course_score.replace_int("gnum", greats)
self.data.local.user.put_achievement(
self.game,

View File

@ -70,12 +70,17 @@ class IIDXFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['JDJ', 'JDZ', 'KDZ', 'LDJ']:
for gamecode in ["JDJ", "JDZ", "KDZ", "LDJ"]:
Base.register(gamecode, IIDXFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
def version_from_date(date: int) -> Optional[int]:
if date < 2013100200:
return VersionConstants.IIDX_TRICORO
@ -97,20 +102,20 @@ class IIDXFactory(Factory):
return VersionConstants.IIDX_BISTROVER
return None
if model.gamecode == 'JDJ':
if model.gamecode == "JDJ":
return IIDXSirius(data, config, model)
if model.gamecode == 'JDZ':
if model.gamecode == "JDZ":
return IIDXResortAnthem(data, config, model)
if model.gamecode == 'KDZ':
if model.gamecode == "KDZ":
return IIDXLincle(data, config, model)
if model.gamecode == 'LDJ':
if model.gamecode == "LDJ":
if model.version is None:
if parentmodel is None:
return None
# We have no way to tell apart newer versions. However, we can make
# an educated guess if we happen to be summoned for old profile lookup.
if parentmodel.gamecode not in ['JDJ', 'JDZ', 'KDZ', 'LDJ']:
if parentmodel.gamecode not in ["JDJ", "JDZ", "KDZ", "LDJ"]:
return None
parentversion = version_from_date(parentmodel.version)
if parentversion == VersionConstants.IIDX_SPADA:

View File

@ -8,7 +8,7 @@ from bemani.common import VersionConstants
class IIDXHeroicVerse(IIDXBase):
name: str = 'Beatmania IIDX HEROIC VERSE'
name: str = "Beatmania IIDX HEROIC VERSE"
version: int = VersionConstants.IIDX_HEROIC_VERSE
requires_extended_regions: bool = True

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,13 +7,13 @@ from bemani.common import VersionConstants
class IIDX1stStyle(IIDXBase):
name: str = 'Beatmania IIDX 1st style & substream'
name: str = "Beatmania IIDX 1st style & substream"
version: int = VersionConstants.IIDX
class IIDX2ndStyle(IIDXBase):
name: str = 'Beatmania IIDX 2nd style'
name: str = "Beatmania IIDX 2nd style"
version: int = VersionConstants.IIDX_2ND_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -22,7 +22,7 @@ class IIDX2ndStyle(IIDXBase):
class IIDX3rdStyle(IIDXBase):
name: str = 'Beatmania IIDX 3rd style'
name: str = "Beatmania IIDX 3rd style"
version: int = VersionConstants.IIDX_3RD_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -31,7 +31,7 @@ class IIDX3rdStyle(IIDXBase):
class IIDX4thStyle(IIDXBase):
name: str = 'Beatmania IIDX 4th style'
name: str = "Beatmania IIDX 4th style"
version: int = VersionConstants.IIDX_4TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -40,7 +40,7 @@ class IIDX4thStyle(IIDXBase):
class IIDX5thStyle(IIDXBase):
name: str = 'Beatmania IIDX 5th style'
name: str = "Beatmania IIDX 5th style"
version: int = VersionConstants.IIDX_5TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -49,7 +49,7 @@ class IIDX5thStyle(IIDXBase):
class IIDX6thStyle(IIDXBase):
name: str = 'Beatmania IIDX 6th style'
name: str = "Beatmania IIDX 6th style"
version: int = VersionConstants.IIDX_6TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -58,7 +58,7 @@ class IIDX6thStyle(IIDXBase):
class IIDX7thStyle(IIDXBase):
name: str = 'Beatmania IIDX 7th style'
name: str = "Beatmania IIDX 7th style"
version: int = VersionConstants.IIDX_7TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -67,7 +67,7 @@ class IIDX7thStyle(IIDXBase):
class IIDX8thStyle(IIDXBase):
name: str = 'Beatmania IIDX 8th style'
name: str = "Beatmania IIDX 8th style"
version: int = VersionConstants.IIDX_8TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -76,7 +76,7 @@ class IIDX8thStyle(IIDXBase):
class IIDX9thStyle(IIDXBase):
name: str = 'Beatmania IIDX 9th style'
name: str = "Beatmania IIDX 9th style"
version: int = VersionConstants.IIDX_9TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -85,7 +85,7 @@ class IIDX9thStyle(IIDXBase):
class IIDX10thStyle(IIDXBase):
name: str = 'Beatmania IIDX 10th style'
name: str = "Beatmania IIDX 10th style"
version: int = VersionConstants.IIDX_10TH_STYLE
def previous_version(self) -> Optional[IIDXBase]:
@ -94,7 +94,7 @@ class IIDX10thStyle(IIDXBase):
class IIDXRed(IIDXBase):
name: str = 'Beatmania IIDX RED'
name: str = "Beatmania IIDX RED"
version: int = VersionConstants.IIDX_RED
def previous_version(self) -> Optional[IIDXBase]:
@ -103,7 +103,7 @@ class IIDXRed(IIDXBase):
class IIDXHappySky(IIDXBase):
name: str = 'Beatmania IIDX HAPPY SKY'
name: str = "Beatmania IIDX HAPPY SKY"
version: int = VersionConstants.IIDX_HAPPY_SKY
def previous_version(self) -> Optional[IIDXBase]:
@ -112,7 +112,7 @@ class IIDXHappySky(IIDXBase):
class IIDXDistorted(IIDXBase):
name: str = 'Beatmania IIDX DistorteD'
name: str = "Beatmania IIDX DistorteD"
version: int = VersionConstants.IIDX_DISTORTED
def previous_version(self) -> Optional[IIDXBase]:
@ -121,7 +121,7 @@ class IIDXDistorted(IIDXBase):
class IIDXGold(IIDXBase):
name: str = 'Beatmania IIDX GOLD'
name: str = "Beatmania IIDX GOLD"
version: int = VersionConstants.IIDX_GOLD
def previous_version(self) -> Optional[IIDXBase]:
@ -130,7 +130,7 @@ class IIDXGold(IIDXBase):
class IIDXDJTroopers(IIDXBase):
name: str = 'Beatmania IIDX DJ TROOPERS'
name: str = "Beatmania IIDX DJ TROOPERS"
version: int = VersionConstants.IIDX_DJ_TROOPERS
def previous_version(self) -> Optional[IIDXBase]:
@ -139,7 +139,7 @@ class IIDXDJTroopers(IIDXBase):
class IIDXEmpress(IIDXBase):
name: str = 'Beatmania IIDX EMPRESS'
name: str = "Beatmania IIDX EMPRESS"
version: int = VersionConstants.IIDX_EMPRESS
def previous_version(self) -> Optional[IIDXBase]:
@ -148,7 +148,7 @@ class IIDXEmpress(IIDXBase):
class IIDXSirius(IIDXBase):
name: str = 'Beatmania IIDX SIRIUS'
name: str = "Beatmania IIDX SIRIUS"
version: int = VersionConstants.IIDX_SIRIUS
def previous_version(self) -> Optional[IIDXBase]:
@ -157,7 +157,7 @@ class IIDXSirius(IIDXBase):
class IIDXResortAnthem(IIDXBase):
name: str = 'Beatmania IIDX Resort Anthem'
name: str = "Beatmania IIDX Resort Anthem"
version: int = VersionConstants.IIDX_RESORT_ANTHEM
def previous_version(self) -> Optional[IIDXBase]:
@ -166,7 +166,7 @@ class IIDXResortAnthem(IIDXBase):
class IIDXLincle(IIDXBase):
name: str = 'Beatmania IIDX Lincle'
name: str = "Beatmania IIDX Lincle"
version: int = VersionConstants.IIDX_LINCLE
def previous_version(self) -> Optional[IIDXBase]:

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ from bemani.common import VersionConstants
class JubeatAvenue(JubeatBase):
name: str = 'Jubeat Avenue'
name: str = "Jubeat Avenue"
version: int = VersionConstants.JUBEAT_AVENUE
def previous_version(self) -> Optional[JubeatBase]:

View File

@ -28,9 +28,13 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
PLAY_MEDAL_FAILED: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_FAILED
PLAY_MEDAL_CLEARED: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_CLEARED
PLAY_MEDAL_NEARLY_FULL_COMBO: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO
PLAY_MEDAL_NEARLY_FULL_COMBO: Final[
int
] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO
PLAY_MEDAL_FULL_COMBO: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO
PLAY_MEDAL_NEARLY_EXCELLENT: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT
PLAY_MEDAL_NEARLY_EXCELLENT: Final[
int
] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT
PLAY_MEDAL_EXCELLENT: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT
CHART_TYPE_BASIC: Final[int] = 0
@ -42,7 +46,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
def __init__(self, data: Data, config: Config, model: Model) -> None:
super().__init__(data, config, model)
if model.rev == 'X' or model.rev == 'Y':
if model.rev == "X" or model.rev == "Y":
self.omnimix = True
else:
self.omnimix = False
@ -53,7 +57,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return DBConstants.OMNIMIX_VERSION_BUMP + self.version
return self.version
def previous_version(self) -> Optional['JubeatBase']:
def previous_version(self) -> Optional["JubeatBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -69,8 +73,8 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
userid - The user ID we are saving the profile for.
profile - A dictionary that should be looked up later using get_profile.
"""
if 'has_old_version' in profile:
del profile['has_old_version']
if "has_old_version" in profile:
del profile["has_old_version"]
super().put_profile(userid, profile)
def format_profile(self, userid: UserID, profile: Profile) -> Node:
@ -78,16 +82,20 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('gametop')
return Node.void("gametop")
def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node:
def format_scores(
self, userid: UserID, profile: Profile, scores: List[Score]
) -> Node:
"""
Base handler for a score list. Given a userid, profile and a score list,
return a Node representing a score list. Should be overridden.
"""
return Node.void('gametop')
return Node.void("gametop")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -113,7 +121,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Now try to find out if the profile is new or old
oldversion = self.previous_version()
oldprofile = oldversion.get_profile(userid)
profile['has_old_version'] = oldprofile is not None
profile["has_old_version"] = oldprofile is not None
# Now, return it
return self.format_profile(userid, profile)
@ -127,7 +135,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
if name is None:
name = 'なし'
name = "なし"
# First, create and save the default profile
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -137,7 +145,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
refid,
0,
{
'name': name,
"name": name,
},
)
self.put_profile(userid, profile)
@ -145,7 +153,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Now, reload and format the profile, looking up the has old version flag
oldversion = self.previous_version()
oldprofile = oldversion.get_profile(userid)
profile['has_old_version'] = oldprofile is not None
profile["has_old_version"] = oldprofile is not None
return self.format_profile(userid, profile)
@ -158,7 +166,9 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
if scores is None:
return None
profile = self.get_profile(userid)
@ -175,9 +185,9 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
points: int,
medal: int,
combo: int,
ghost: Optional[List[int]]=None,
stats: Optional[Dict[str, int]]=None,
music_rate: Optional[int]=None,
ghost: Optional[List[int]] = None,
stats: Optional[Dict[str, int]] = None,
music_rate: Optional[int] = None,
) -> None:
"""
Given various pieces of a score, update the user's high score and score
@ -220,38 +230,38 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
scoredata = oldscore.data
# Replace medal with highest value
scoredata.replace_int('medal', max(scoredata.get_int('medal'), medal))
history.replace_int('medal', medal)
scoredata.replace_int("medal", max(scoredata.get_int("medal"), medal))
history.replace_int("medal", medal)
# Increment counters based on medal
if medal == self.PLAY_MEDAL_CLEARED:
scoredata.increment_int('clear_count')
scoredata.increment_int("clear_count")
if medal == self.PLAY_MEDAL_FULL_COMBO:
scoredata.increment_int('full_combo_count')
scoredata.increment_int("full_combo_count")
if medal == self.PLAY_MEDAL_EXCELLENT:
scoredata.increment_int('excellent_count')
scoredata.increment_int("excellent_count")
# If we have a combo, replace it
scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
history.replace_int('combo', combo)
scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo))
history.replace_int("combo", combo)
if stats is not None:
if raised:
# We have stats, and there's a new high score, update the stats
scoredata.replace_dict('stats', stats)
history.replace_dict('stats', stats)
scoredata.replace_dict("stats", stats)
history.replace_dict("stats", stats)
if ghost is not None:
# Update the ghost regardless, but don't bother with it in history
scoredata.replace_int_array('ghost', len(ghost), ghost)
scoredata.replace_int_array("ghost", len(ghost), ghost)
if music_rate is not None:
if oldscore is not None:
if music_rate > oldscore.data.get_int('music_rate'):
scoredata.replace_int('music_rate', music_rate)
if music_rate > oldscore.data.get_int("music_rate"):
scoredata.replace_int("music_rate", music_rate)
else:
scoredata.replace_int('music_rate', music_rate)
history.replace_int('music_rate', music_rate)
scoredata.replace_int("music_rate", music_rate)
history.replace_int("music_rate", music_rate)
# Look up where this score was earned
lid = self.get_machine_id()

File diff suppressed because it is too large Load Diff

View File

@ -5,83 +5,78 @@ from bemani.protocol import Node
class JubeatLoggerReportHandler(JubeatBase):
def handle_logger_report_request(self, request: Node) -> Node:
# Handle this by returning nothing, game doesn't care
root = Node.void('logger')
root = Node.void("logger")
return root
class JubeatDemodataGetNewsHandler(JubeatBase):
def handle_demodata_get_news_request(self, request: Node) -> Node:
demodata = Node.void('demodata')
data = Node.void('data')
demodata = Node.void("demodata")
data = Node.void("data")
demodata.add_child(data)
officialnews = Node.void('officialnews')
officialnews = Node.void("officialnews")
data.add_child(officialnews)
officialnews.set_attribute('count', '0')
officialnews.set_attribute("count", "0")
return demodata
class JubeatDemodataGetHitchartHandler(JubeatBase):
def handle_demodata_get_hitchart_request(self, request: Node) -> Node:
demodata = Node.void('demodata')
data = Node.void('data')
demodata = Node.void("demodata")
data = Node.void("data")
demodata.add_child(data)
# Not sure what this is, maybe date?
data.add_child(Node.string('update', time.strftime("%d/%m/%Y")))
data.add_child(Node.string("update", time.strftime("%d/%m/%Y")))
# No idea which songs are licensed or regular, so only return hit chart
# for all songs on regular mode.
hitchart_lic = Node.void('hitchart_lic')
hitchart_lic = Node.void("hitchart_lic")
data.add_child(hitchart_lic)
hitchart_lic.set_attribute('count', '0')
hitchart_lic.set_attribute("count", "0")
songs = self.data.local.music.get_hit_chart(self.game, self.music_version, 10)
hitchart_org = Node.void('hitchart_org')
hitchart_org = Node.void("hitchart_org")
data.add_child(hitchart_org)
hitchart_org.set_attribute('count', str(len(songs)))
hitchart_org.set_attribute("count", str(len(songs)))
rank = 1
for song in songs:
rankdata = Node.void('rankdata')
rankdata = Node.void("rankdata")
hitchart_org.add_child(rankdata)
rankdata.add_child(Node.s32('music_id', song[0]))
rankdata.add_child(Node.s16('rank', rank))
rankdata.add_child(Node.s16('prev', rank))
rankdata.add_child(Node.s32("music_id", song[0]))
rankdata.add_child(Node.s16("rank", rank))
rankdata.add_child(Node.s16("prev", rank))
rank = rank + 1
return demodata
class JubeatLobbyCheckHandler(JubeatBase):
def handle_lobby_check_request(self, request: Node) -> Node:
root = Node.void('lobby')
data = Node.void('data')
root = Node.void("lobby")
data = Node.void("data")
root.add_child(data)
data.add_child(Node.s16('interval', 0))
data.add_child(Node.s16('entry_timeout', 0))
entrant_nr = Node.u32('entrant_nr', 0)
entrant_nr.set_attribute('time', '0')
data.add_child(Node.s16("interval", 0))
data.add_child(Node.s16("entry_timeout", 0))
entrant_nr = Node.u32("entrant_nr", 0)
entrant_nr.set_attribute("time", "0")
data.add_child(entrant_nr)
return root
class JubeatGamendRegisterHandler(JubeatBase):
def handle_gameend_regist_request(self, request: Node) -> Node:
data = request.child('data')
player = data.child('player')
data = request.child("data")
player = data.child("player")
if player is not None:
refid = player.child_value('refid')
refid = player.child_value("refid")
else:
refid = None
@ -99,32 +94,31 @@ class JubeatGamendRegisterHandler(JubeatBase):
if userid is not None and newprofile is not None:
self.put_profile(userid, newprofile)
gameend = Node.void('gameend')
data = Node.void('data')
gameend = Node.void("gameend")
data = Node.void("data")
gameend.add_child(data)
player = Node.void('player')
player = Node.void("player")
data.add_child(player)
player.add_child(Node.s32('session_id', 1))
player.add_child(Node.s32('end_final_session_id', 1))
player.add_child(Node.s32("session_id", 1))
player.add_child(Node.s32("end_final_session_id", 1))
return gameend
class JubeatGametopGetMeetingHandler(JubeatBase):
def handle_gametop_get_meeting_request(self, request: Node) -> Node:
gametop = Node.void('gametop')
data = Node.void('data')
gametop = Node.void("gametop")
data = Node.void("data")
gametop.add_child(data)
meeting = Node.void('meeting')
meeting = Node.void("meeting")
data.add_child(meeting)
single = Node.void('single')
single = Node.void("single")
meeting.add_child(single)
single.set_attribute('count', '0')
tag = Node.void('tag')
single.set_attribute("count", "0")
tag = Node.void("tag")
meeting.add_child(tag)
tag.set_attribute('count', '0')
reward = Node.void('reward')
tag.set_attribute("count", "0")
reward = Node.void("reward")
data.add_child(reward)
reward.add_child(Node.s32('total', -1))
reward.add_child(Node.s32('point', -1))
reward.add_child(Node.s32("total", -1))
reward.add_child(Node.s32("point", -1))
return gametop

View File

@ -21,462 +21,462 @@ class JubeatCourse(JubeatBase):
# List of base courses for Saucer Fulfill+ from BemaniWiki
return [
{
'id': 1,
'name': '溢れ出した記憶、特別なあなたにありがとう。',
'level': 1,
'music': [
"id": 1,
"name": "溢れ出した記憶、特別なあなたにありがとう。",
"level": 1,
"music": [
(50000241, 2),
(10000052, 2),
(30000042, 2),
(50000085, 2),
(50000144, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000],
self.COURSE_REQUIREMENT_FULL_COMBO: [0, 1, 2],
},
},
{
'id': 2,
'name': 'コースモードが怖いばっかお前TAGがついてるだろ',
'level': 1,
'music': [
"id": 2,
"name": "コースモードが怖いばっかお前TAGがついてるだろ",
"level": 1,
"music": [
(50000121, 1),
(30000122, 1),
(40000159, 1),
(50000089, 1),
(40000051, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [800000, 850000, 900000],
self.COURSE_REQUIREMENT_FULL_COMBO: [0, 1, 2],
},
},
{
'id': 3,
'name': '満月の鐘踊り響くは虚空から成る恋の歌',
'level': 2,
'music': [
"id": 3,
"name": "満月の鐘踊り響くは虚空から成る恋の歌",
"level": 2,
"music": [
(40000121, 2),
(50000188, 2),
(30000047, 2),
(50000237, 2),
(50000176, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000],
self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3],
},
},
{
'id': 4,
'name': 'スミスゼミナール 夏の陣開講記念 基本編',
'level': 2,
'music': [
"id": 4,
"name": "スミスゼミナール 夏の陣開講記念 基本編",
"level": 2,
"music": [
(50000267, 1),
(50000233, 1),
(50000228, 1),
(50000268, 1),
(50000291, 1),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3],
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95],
},
},
{
'id': 5,
'name': 'HARDモードじゃないから、絶対、大丈夫だよっ',
'level': 2,
'music': [
"id": 5,
"name": "HARDモードじゃないから、絶対、大丈夫だよっ",
"level": 2,
"music": [
(50000144, 2),
(50000188, 2),
(50000070, 2),
(50000151, 2),
(50000152, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000],
self.COURSE_REQUIREMENT_FULL_COMBO: [0, 1, 2],
},
},
{
'id': 6,
'name': '星明かりの下、愛という名の日替わりランチを君と',
'level': 3,
'music': [
"id": 6,
"name": "星明かりの下、愛という名の日替わりランチを君と",
"level": 3,
"music": [
(50000196, 1),
(50000151, 2),
(50000060, 1),
(40000048, 2),
(10000051, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [70, 80, 90],
},
},
{
'id': 7,
'name': '輝く北極星と幸せなヒーロー',
'level': 4,
'music': [
"id": 7,
"name": "輝く北極星と幸せなヒーロー",
"level": 4,
"music": [
(50000079, 2),
(20000044, 2),
(50000109, 2),
(10000043, 2),
(10000042, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000],
self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3],
},
},
{
'id': 8,
'name': '花-鳥-藻-夏',
'level': 4,
'music': [
"id": 8,
"name": "花-鳥-藻-夏",
"level": 4,
"music": [
(10000068, 2),
(40000154, 2),
(50000123, 1),
(40000051, 2),
(30000045, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [70, 80, 90],
},
},
{
'id': 9,
'name': 'TAG生誕祭2014 俺の記録を抜いてみろ!',
'level': 4,
'music': [
"id": 9,
"name": "TAG生誕祭2014 俺の記録を抜いてみろ!",
"level": 4,
"music": [
(30000122, 2),
(50000086, 2),
(50000121, 2),
(50000196, 2),
(40000051, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 967252],
self.COURSE_REQUIREMENT_FULL_COMBO: [0, 0, 1],
},
},
{
'id': 10,
'name': 'さよなら、亡くした恋と蝶の舞うヒストリア',
'level': 5,
'music': [
"id": 10,
"name": "さよなら、亡くした恋と蝶の舞うヒストリア",
"level": 5,
"music": [
(20000041, 2),
(30000044, 2),
(50000037, 2),
(20000124, 2),
(50000033, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [80, 85, 90],
},
},
{
'id': 11,
'name': 'きらきらほしふるまぼろしなぎさちゃん',
'level': 5,
'music': [
"id": 11,
"name": "きらきらほしふるまぼろしなぎさちゃん",
"level": 5,
"music": [
(30000050, 2),
(30000049, 2),
(50000235, 2),
(50000157, 2),
(50000038, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [700000, 800000, 900000],
},
},
{
'id': 12,
'name': 'The Memorial Third: 僕みたいに演奏してね',
'level': 5,
'music': [
"id": 12,
"name": "The Memorial Third: 僕みたいに演奏してね",
"level": 5,
"music": [
(10000037, 2),
(20000048, 1),
(50000253, 1),
(20000121, 2),
(50000133, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [75, 80, 85],
},
},
{
'id': 13,
'name': 'Enjoy! 4thKAC ~ Memories of saucer ~',
'level': 5,
'music': [
"id": 13,
"name": "Enjoy! 4thKAC ~ Memories of saucer ~",
"level": 5,
"music": [
(50000206, 1),
(50000023, 1),
(50000078, 1),
(50000203, 1),
(50000323, 1),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000],
self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 4],
},
},
{
'id': 14,
'name': '風に吹かれるキケンなシロクマダンス',
'level': 6,
'music': [
"id": 14,
"name": "風に吹かれるキケンなシロクマダンス",
"level": 6,
"music": [
(50000059, 2),
(50000197, 2),
(30000037, 2),
(50000182, 2),
(20000038, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000],
self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3],
},
},
{
'id': 15,
'name': '君主は視線で友との愛を語るめう',
'level': 6,
'music': [
"id": 15,
"name": "君主は視線で友との愛を語るめう",
"level": 6,
"music": [
(40000052, 2),
(50000152, 2),
(50000090, 2),
(20000040, 2),
(50000184, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95],
},
},
{
'id': 16,
'name': 'スミスゼミナール 夏の陣開講記念 応用編',
'level': 6,
'music': [
"id": 16,
"name": "スミスゼミナール 夏の陣開講記念 応用編",
"level": 6,
"music": [
(50000233, 2),
(50000267, 2),
(50000268, 2),
(50000228, 2),
(50000291, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [750000, 850000, 900000],
},
},
{
'id': 17,
'name': '天から降り注ぐ星はまるで甘いキャンディ',
'level': 7,
'music': [
"id": 17,
"name": "天から降り注ぐ星はまるで甘いキャンディ",
"level": 7,
"music": [
(20000044, 2),
(30000050, 2),
(50000080, 2),
(40000126, 2),
(10000067, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95],
},
},
{
'id': 18,
'name': 'てんとう虫が囁いている「Wow Wow…」',
'level': 7,
'music': [
"id": 18,
"name": "てんとう虫が囁いている「Wow Wow…」",
"level": 7,
"music": [
(50000132, 2),
(40000128, 2),
(10000036, 2),
(50000119, 2),
(50000030, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95],
},
},
{
'id': 19,
'name': 'HARDモードでも大丈夫だよ絶対、大丈夫だよっ',
'level': 7,
'music': [
"id": 19,
"name": "HARDモードでも大丈夫だよ絶対、大丈夫だよっ",
"level": 7,
"music": [
(50000144, 2),
(50000070, 2),
(50000188, 2),
(50000151, 2),
(50000152, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000],
},
},
{
'id': 20,
'name': 'こんなHARDモード、滅べばいい…',
'level': 7,
'music': [
"id": 20,
"name": "こんなHARDモード、滅べばいい…",
"level": 7,
"music": [
(50000294, 2),
(50000295, 2),
(50000234, 2),
(50000245, 2),
(50000282, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000],
},
},
{
'id': 21,
'name': 'Challenge! 4thKAC ~ Memories of saucer ~',
'level': 7,
'music': [
"id": 21,
"name": "Challenge! 4thKAC ~ Memories of saucer ~",
"level": 7,
"music": [
(50000206, 2),
(50000023, 2),
(50000078, 2),
(50000203, 2),
(50000323, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000],
},
},
{
'id': 22,
'name': 'サヨナラ・キングコング ~ 恋のつぼみは愛の虹へ ~',
'level': 8,
'music': [
"id": 22,
"name": "サヨナラ・キングコング ~ 恋のつぼみは愛の虹へ ~",
"level": 8,
"music": [
(50000148, 2),
(50000101, 2),
(10000064, 2),
(50000171, 2),
(50000070, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000],
},
},
{
'id': 23,
'name': '風に舞う白鳥の翼と花弁、さながら万華鏡のよう',
'level': 8,
'music': [
"id": 23,
"name": "風に舞う白鳥の翼と花弁、さながら万華鏡のよう",
"level": 8,
"music": [
(30000036, 2),
(50000122, 2),
(10000062, 2),
(50000199, 2),
(40000153, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [90, 95, 98],
},
},
{
'id': 24,
'name': 'The 小さなおぼろガチョウ♪',
'level': 8,
'music': [
"id": 24,
"name": "The 小さなおぼろガチョウ♪",
"level": 8,
"music": [
(50000049, 2),
(50000071, 2),
(10000041, 2),
(50000031, 2),
(40000129, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [970000, 980000, 990000],
self.COURSE_REQUIREMENT_FULL_COMBO: [2, 3, 4],
},
},
{
'id': 25,
'name': 'TAG生誕祭2014 俺の記録を抜いてみろ!~ HARD編 ~',
'level': 8,
'music': [
"id": 25,
"name": "TAG生誕祭2014 俺の記録を抜いてみろ!~ HARD編 ~",
"level": 8,
"music": [
(50000089, 2),
(50000083, 2),
(50000210, 2),
(50000030, 2),
(40000159, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [800000, 900000, 931463],
},
},
{
'id': 26,
'name': '凍る世界で見る鳳凰の火の花',
'level': 9,
'music': [
"id": 26,
"name": "凍る世界で見る鳳凰の火の花",
"level": 9,
"music": [
(30000043, 2),
(10000039, 2),
(20000048, 2),
(50000096, 2),
(20000038, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000],
},
},
{
'id': 27,
'name': '真実の桜が乱れしとき、キルト纏いし君は修羅となる',
'level': 9,
'music': [
"id": 27,
"name": "真実の桜が乱れしとき、キルト纏いし君は修羅となる",
"level": 9,
"music": [
(50000113, 2),
(50000184, 2),
(50000177, 2),
(30000124, 2),
(50000078, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_PERFECT_PERCENT: [80, 85, 90],
},
},
{
'id': 28,
'name': 'THE FINAL01 ~ 雷光に月、乙女に花散る祝福を ~',
'level': 10,
'music': [
"id": 28,
"name": "THE FINAL01 ~ 雷光に月、乙女に花散る祝福を ~",
"level": 10,
"music": [
(10000038, 2),
(20000051, 2),
(30000048, 2),
(40000060, 2),
(50000023, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000],
},
},
{
'id': 29,
'name': 'The Memorial Third: assimilated all into Nature',
'level': 10,
'music': [
"id": 29,
"name": "The Memorial Third: assimilated all into Nature",
"level": 10,
"music": [
(50000135, 2),
(50000029, 2),
(40000047, 2),
(40000046, 2),
(50000253, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000],
},
},
{
'id': 30,
'name': '4thKAC ~ Memories of saucer ~',
'level': 10,
'music': [
"id": 30,
"name": "4thKAC ~ Memories of saucer ~",
"level": 10,
"music": [
(50000206, 2),
(50000023, 2),
(50000078, 2),
(50000203, 2),
(50000323, 2),
],
'requirements': {
"requirements": {
self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000],
},
},
@ -490,40 +490,40 @@ class JubeatCourse(JubeatBase):
scores: List[int],
) -> None:
if len(scores) != 5:
raise Exception('Invalid course scores list!')
raise Exception("Invalid course scores list!")
if rating not in [
self.COURSE_RATING_FAILED,
self.COURSE_RATING_BRONZE,
self.COURSE_RATING_SILVER,
self.COURSE_RATING_GOLD,
]:
raise Exception('Invalid course rating!')
raise Exception("Invalid course rating!")
# Figure out if we should update the rating/scores or not
oldcourse = self.data.local.game.get_achievement(
self.game,
userid,
courseid,
'course',
"course",
)
if oldcourse is not None:
# Update the rating if the user did better
rating = max(rating, oldcourse.get_int('rating'))
rating = max(rating, oldcourse.get_int("rating"))
# Update the scores if the total score was better
if sum(scores) < sum(oldcourse.get_int_array('scores', 5)):
scores = oldcourse.get_int_array('scores', 5)
if sum(scores) < sum(oldcourse.get_int_array("scores", 5)):
scores = oldcourse.get_int_array("scores", 5)
# Save it as an achievement
self.data.local.game.put_achievement(
self.game,
userid,
courseid,
'course',
"course",
{
'rating': rating,
'scores': scores,
"rating": rating,
"scores": scores,
},
)
@ -534,9 +534,9 @@ class JubeatCourse(JubeatBase):
courses = {}
achievements = self.data.local.game.get_achievements(self.game, userid)
for achievement in achievements:
if achievement.type == 'course':
if achievement.type == "course":
courses[achievement.id] = {
'rating': achievement.data.get_int('rating'),
'scores': achievement.data.get_int_array('scores', 5),
"rating": achievement.data.get_int("rating"),
"scores": achievement.data.get_int_array("scores", 5),
}
return courses

View File

@ -42,29 +42,35 @@ class JubeatFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['H44', 'I44', 'J44', 'K44', 'L44']:
for gamecode in ["H44", "I44", "J44", "K44", "L44"]:
Base.register(gamecode, JubeatFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
if model.gamecode == 'H44':
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
if model.gamecode == "H44":
return Jubeat(data, config, model)
if model.gamecode == 'I44':
if model.gamecode == "I44":
if model.version >= 2010031800:
return JubeatRipplesAppend(data, config, model)
else:
return JubeatRipples(data, config, model)
if model.gamecode == 'J44':
if model.gamecode == "J44":
if model.version >= 2011032300:
return JubeatKnitAppend(data, config, model)
else:
return JubeatKnit(data, config, model)
if model.gamecode == 'K44':
if model.gamecode == "K44":
if model.version >= 2012031400:
return JubeatCopiousAppend(data, config, model)
else:
return JubeatCopious(data, config, model)
if model.gamecode == 'L44':
if model.gamecode == "L44":
if model.version <= 2014022400:
return JubeatSaucer(data, config, model)
if model.version >= 2014030300 and model.version < 2015022000:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,13 +7,13 @@ from bemani.common import VersionConstants
class Jubeat(JubeatBase):
name: str = 'Jubeat'
name: str = "Jubeat"
version: int = VersionConstants.JUBEAT
class JubeatRipples(JubeatBase):
name: str = 'Jubeat Ripples'
name: str = "Jubeat Ripples"
version: int = VersionConstants.JUBEAT_RIPPLES
def previous_version(self) -> Optional[JubeatBase]:
@ -22,7 +22,7 @@ class JubeatRipples(JubeatBase):
class JubeatRipplesAppend(JubeatBase):
name: str = 'Jubeat Ripples Append'
name: str = "Jubeat Ripples Append"
version: int = VersionConstants.JUBEAT_RIPPLES_APPEND
def previous_version(self) -> Optional[JubeatBase]:
@ -31,7 +31,7 @@ class JubeatRipplesAppend(JubeatBase):
class JubeatKnit(JubeatBase):
name: str = 'Jubeat Knit'
name: str = "Jubeat Knit"
version: int = VersionConstants.JUBEAT_KNIT
def previous_version(self) -> Optional[JubeatBase]:
@ -40,7 +40,7 @@ class JubeatKnit(JubeatBase):
class JubeatKnitAppend(JubeatBase):
name: str = 'Jubeat Knit Append'
name: str = "Jubeat Knit Append"
version: int = VersionConstants.JUBEAT_KNIT_APPEND
def previous_version(self) -> Optional[JubeatBase]:
@ -49,7 +49,7 @@ class JubeatKnitAppend(JubeatBase):
class JubeatCopious(JubeatBase):
name: str = 'Jubeat Copious'
name: str = "Jubeat Copious"
version: int = VersionConstants.JUBEAT_COPIOUS
def previous_version(self) -> Optional[JubeatBase]:
@ -58,7 +58,7 @@ class JubeatCopious(JubeatBase):
class JubeatCopiousAppend(JubeatBase):
name: str = 'Jubeat Copious Append'
name: str = "Jubeat Copious Append"
version: int = VersionConstants.JUBEAT_COPIOUS_APPEND
def previous_version(self) -> Optional[JubeatBase]:

View File

@ -13,7 +13,7 @@ class MetalGearArcadeBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
game: GameConstants = GameConstants.MGA
def previous_version(self) -> Optional['MetalGearArcadeBase']:
def previous_version(self) -> Optional["MetalGearArcadeBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.

View File

@ -14,13 +14,19 @@ class MetalGearArcadeFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['I36']:
for gamecode in ["I36"]:
Base.register(gamecode, MetalGearArcadeFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
if model.gamecode == 'I36':
if model.gamecode == "I36":
return MetalGearArcade(data, config, model)
# Unknown game version

View File

@ -19,36 +19,36 @@ class MetalGearArcade(
def __update_shop_name(self, profiledata: bytes) -> None:
# Figure out the profile type
csvs = profiledata.split(b',')
csvs = profiledata.split(b",")
if len(csvs) < 2:
# Not long enough to care about
return
datatype = csvs[1].decode('ascii')
if datatype != 'PLAYDATA':
datatype = csvs[1].decode("ascii")
if datatype != "PLAYDATA":
# Not the right profile type requested
return
# Grab the shop name
try:
shopname = csvs[30].decode('shift-jis')
shopname = csvs[30].decode("shift-jis")
except Exception:
return
self.update_machine_name(shopname)
def handle_system_getmaster_request(self, request: Node) -> Node:
# See if we can grab the request
data = request.child('data')
data = request.child("data")
if not data:
root = Node.void('system')
root.add_child(Node.s32('result', 0))
root = Node.void("system")
root.add_child(Node.s32("result", 0))
return root
# Figure out what type of messsage this is
reqtype = data.child_value('datatype')
reqkey = data.child_value('datakey')
reqtype = data.child_value("datatype")
reqkey = data.child_value("datakey")
# System message
root = Node.void('system')
root = Node.void("system")
if reqtype == "S_SRVMSG" and reqkey == "INFO":
# Generate system message
@ -56,23 +56,35 @@ class MetalGearArcade(
settings2_str = "1,1,1,1,1,1,1,1,1,1,1,1,1,1"
# Send it to the client, making sure to inform the client that it was valid.
root.add_child(Node.string('strdata1', base64.b64encode(settings1_str.encode('ascii')).decode('ascii')))
root.add_child(Node.string('strdata2', base64.b64encode(settings2_str.encode('ascii')).decode('ascii')))
root.add_child(Node.u64('updatedate', Time.now() * 1000))
root.add_child(Node.s32('result', 1))
root.add_child(
Node.string(
"strdata1",
base64.b64encode(settings1_str.encode("ascii")).decode("ascii"),
)
)
root.add_child(
Node.string(
"strdata2",
base64.b64encode(settings2_str.encode("ascii")).decode("ascii"),
)
)
root.add_child(Node.u64("updatedate", Time.now() * 1000))
root.add_child(Node.s32("result", 1))
else:
# Unknown message.
root.add_child(Node.s32('result', 0))
root.add_child(Node.s32("result", 0))
return root
def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node:
# Look up user by refid
refid = request.child_value('data/eaid')
refid = request.child_value("data/eaid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
root = Node.void('playerdata')
root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here.
root = Node.void("playerdata")
root.add_child(
Node.s32("result", 1)
) # Unclear if this is the right thing to do here.
return root
# Extract new profile info from old profile
@ -87,14 +99,14 @@ class MetalGearArcade(
self.put_profile(userid, newprofile)
# Return success!
root = Node.void('playerdata')
root.add_child(Node.s32('result', 0))
root = Node.void("playerdata")
root.add_child(Node.s32("result", 0))
return root
def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node:
# Look up user by refid
refid = request.child_value('data/eaid')
profiletypes = request.child_value('data/recv_csv').split(',')
refid = request.child_value("data/eaid")
profiletypes = request.child_value("data/recv_csv").split(",")
profile = None
userid = None
if refid is not None:
@ -104,58 +116,66 @@ class MetalGearArcade(
if profile is not None:
return self.format_profile(userid, profiletypes, profile)
else:
root = Node.void('playerdata')
root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here.
root = Node.void("playerdata")
root.add_child(
Node.s32("result", 1)
) # Unclear if this is the right thing to do here.
return root
def format_profile(self, userid: UserID, profiletypes: List[str], profile: Profile) -> Node:
root = Node.void('playerdata')
root.add_child(Node.s32('result', 0))
player = Node.void('player')
def format_profile(
self, userid: UserID, profiletypes: List[str], profile: Profile
) -> Node:
root = Node.void("playerdata")
root.add_child(Node.s32("result", 0))
player = Node.void("player")
root.add_child(player)
records = 0
record = Node.void('record')
record = Node.void("record")
player.add_child(record)
for profiletype in profiletypes:
if profiletype == "3fffffffff":
continue
for j in range(len(profile['strdatas'])):
strdata = profile['strdatas'][j]
bindata = profile['bindatas'][j]
for j in range(len(profile["strdatas"])):
strdata = profile["strdatas"][j]
bindata = profile["bindatas"][j]
# Figure out the profile type
csvs = strdata.split(b',')
csvs = strdata.split(b",")
if len(csvs) < 2:
# Not long enough to care about
continue
datatype = csvs[1].decode('ascii')
datatype = csvs[1].decode("ascii")
if datatype != profiletype:
# Not the right profile type requested
continue
# This is a valid profile node for this type, lets return only the profile values
strdata = b','.join(csvs[2:])
d = Node.string('d', base64.b64encode(strdata).decode('ascii'))
strdata = b",".join(csvs[2:])
d = Node.string("d", base64.b64encode(strdata).decode("ascii"))
record.add_child(d)
d.add_child(Node.string('bin1', base64.b64encode(bindata).decode('ascii')))
d.add_child(
Node.string("bin1", base64.b64encode(bindata).decode("ascii"))
)
# Remember that we had this record
records = records + 1
player.add_child(Node.u32('record_num', records))
player.add_child(Node.u32("record_num", records))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool
) -> Profile:
# Profile save request, data values are base64 encoded.
# d is a CSV, and bin1 is binary data.
newprofile = oldprofile.clone()
strdatas: List[bytes] = []
bindatas: List[bytes] = []
record = request.child('data/record')
record = request.child("data/record")
for node in record.children:
if node.name != 'd':
if node.name != "d":
continue
profile = base64.b64decode(node.value)
@ -165,10 +185,10 @@ class MetalGearArcade(
if is_new:
self.__update_shop_name(profile)
strdatas.append(profile)
bindatas.append(base64.b64decode(node.child_value('bin1')))
bindatas.append(base64.b64decode(node.child_value("bin1")))
newprofile['strdatas'] = strdatas
newprofile['bindatas'] = bindatas
newprofile["strdatas"] = strdatas
newprofile["bindatas"] = bindatas
# Keep track of play statistics across all versions
self.update_play_statistics(userid)

View File

@ -4,7 +4,14 @@ from typing_extensions import Final
from bemani.backend.base import Base
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
from bemani.common import Profile, ValidatedDict, GameConstants, DBConstants, Parallel, Model
from bemani.common import (
Profile,
ValidatedDict,
GameConstants,
DBConstants,
Parallel,
Model,
)
from bemani.data import UserID, Config, Data
from bemani.protocol import Node
@ -36,7 +43,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
def __init__(self, data: Data, config: Config, model: Model) -> None:
super().__init__(data, config, model)
if model.rev == 'X':
if model.rev == "X":
self.omnimix = True
else:
self.omnimix = False
@ -47,7 +54,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return DBConstants.OMNIMIX_VERSION_BUMP + self.version
return self.version
def previous_version(self) -> Optional['MusecaBase']:
def previous_version(self) -> Optional["MusecaBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -56,19 +63,19 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
def game_to_db_clear_type(self, clear_type: int) -> int:
# Given a game clear type, return the canonical database identifier.
raise Exception('Implement in subclass!')
raise Exception("Implement in subclass!")
def db_to_game_clear_type(self, clear_type: int) -> int:
# Given a database clear type, return the game's identifier.
raise Exception('Implement in subclass!')
raise Exception("Implement in subclass!")
def game_to_db_grade(self, grade: int) -> int:
# Given a game grade, return the canonical database identifier.
raise Exception('Implement in subclass!')
raise Exception("Implement in subclass!")
def db_to_game_grade(self, grade: int) -> int:
# Given a database grade, return the game's identifier.
raise Exception('Implement in subclass!')
raise Exception("Implement in subclass!")
def get_profile_by_refid(self, refid: Optional[str]) -> Optional[Node]:
"""
@ -88,7 +95,9 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Now, return it
return self.format_profile(userid, profile)
def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], locid: Optional[int]) -> Node:
def new_profile_by_refid(
self, refid: Optional[str], name: Optional[str], locid: Optional[int]
) -> Node:
"""
Given a RefID and an optional name, create a profile and then return
a formatted profile node. Similar rationale to get_profile_by_refid.
@ -97,7 +106,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
if name is None:
name = 'NONAME'
name = "NONAME"
# First, create and save the default profile
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -107,8 +116,8 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
refid,
0,
{
'name': name,
'loc': locid,
"name": name,
"loc": locid,
},
)
self.put_profile(userid, profile)
@ -119,9 +128,11 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('game')
return Node.void("game")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -142,16 +153,18 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
},
}
"""
all_attempts, remote_attempts = Parallel.execute([
lambda: self.data.local.music.get_all_attempts(
game=self.game,
version=self.music_version,
),
lambda: self.data.remote.music.get_clear_rates(
game=self.game,
version=self.music_version,
)
])
all_attempts, remote_attempts = Parallel.execute(
[
lambda: self.data.local.music.get_all_attempts(
game=self.game,
version=self.music_version,
),
lambda: self.data.remote.music.get_clear_rates(
game=self.game,
version=self.music_version,
),
]
)
attempts: Dict[int, Dict[int, Dict[str, int]]] = {}
for (_, attempt) in all_attempts:
# Terrible temporary structure is terrible.
@ -159,19 +172,26 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
attempts[attempt.id] = {}
if attempt.chart not in attempts[attempt.id]:
attempts[attempt.id][attempt.chart] = {
'total': 0,
'clears': 0,
"total": 0,
"clears": 0,
}
# We saw an attempt, keep the total attempts in sync.
attempts[attempt.id][attempt.chart]['total'] = attempts[attempt.id][attempt.chart]['total'] + 1
attempts[attempt.id][attempt.chart]["total"] = (
attempts[attempt.id][attempt.chart]["total"] + 1
)
if attempt.data.get_int('clear_type', self.CLEAR_TYPE_FAILED) != self.CLEAR_TYPE_FAILED:
if (
attempt.data.get_int("clear_type", self.CLEAR_TYPE_FAILED)
!= self.CLEAR_TYPE_FAILED
):
# This attempt was a failure, so don't count it against clears of full combos
continue
# It was at least a clear
attempts[attempt.id][attempt.chart]['clears'] = attempts[attempt.id][attempt.chart]['clears'] + 1
attempts[attempt.id][attempt.chart]["clears"] = (
attempts[attempt.id][attempt.chart]["clears"] + 1
)
# Merge in remote attempts
for songid in remote_attempts:
@ -181,12 +201,16 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
for songchart in remote_attempts[songid]:
if songchart not in attempts[songid]:
attempts[songid][songchart] = {
'total': 0,
'clears': 0,
"total": 0,
"clears": 0,
}
attempts[songid][songchart]['total'] += remote_attempts[songid][songchart]['plays']
attempts[songid][songchart]['clears'] += remote_attempts[songid][songchart]['clears']
attempts[songid][songchart]["total"] += remote_attempts[songid][
songchart
]["plays"]
attempts[songid][songchart]["clears"] += remote_attempts[songid][
songchart
]["clears"]
return attempts
@ -199,7 +223,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
clear_type: int,
grade: int,
combo: int,
stats: Optional[Dict[str, int]]=None,
stats: Optional[Dict[str, int]] = None,
) -> None:
"""
Given various pieces of a score, update the user's high score and score
@ -256,21 +280,23 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
scoredata = oldscore.data
# Replace grade and clear type
scoredata.replace_int('clear_type', max(scoredata.get_int('clear_type'), clear_type))
history.replace_int('clear_type', clear_type)
scoredata.replace_int('grade', max(scoredata.get_int('grade'), grade))
history.replace_int('grade', grade)
scoredata.replace_int(
"clear_type", max(scoredata.get_int("clear_type"), clear_type)
)
history.replace_int("clear_type", clear_type)
scoredata.replace_int("grade", max(scoredata.get_int("grade"), grade))
history.replace_int("grade", grade)
# If we have a combo, replace it
scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
history.replace_int('combo', combo)
scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo))
history.replace_int("combo", combo)
# If we have play stats, replace it
if stats is not None:
if raised:
# We have stats, and there's a new high score, update the stats
scoredata.replace_dict('stats', stats)
history.replace_dict('stats', stats)
scoredata.replace_dict("stats", stats)
history.replace_dict("stats", stats)
# Look up where this score was earned
lid = self.get_machine_id()

View File

@ -4,138 +4,136 @@ from bemani.protocol import Node
class MusecaGameShopHandler(MusecaBase):
def handle_game_3_shop_request(self, request: Node) -> Node:
self.update_machine_name(request.child_value('shopname'))
self.update_machine_name(request.child_value("shopname"))
# Respond with number of milliseconds until next request
game = Node.void('game_3')
game.add_child(Node.u32('nxt_time', 1000 * 5 * 60))
game = Node.void("game_3")
game.add_child(Node.u32("nxt_time", 1000 * 5 * 60))
return game
class MusecaGameHiscoreHandler(MusecaBase):
def handle_game_3_hiscore_request(self, request: Node) -> Node:
# Grab location for local scores
locid = ID.parse_machine_id(request.child_value('locid'))
locid = ID.parse_machine_id(request.child_value("locid"))
# Start the response packet
game = Node.void('game_3')
game = Node.void("game_3")
# First, grab hit chart
playcounts = self.data.local.music.get_hit_chart(self.game, self.version, 1024)
hitchart = Node.void('hitchart')
hitchart = Node.void("hitchart")
game.add_child(hitchart)
for (songid, count) in playcounts:
info = Node.void('info')
info = Node.void("info")
hitchart.add_child(info)
info.add_child(Node.u32('id', songid))
info.add_child(Node.u32('cnt', count))
info.add_child(Node.u32("id", songid))
info.add_child(Node.u32("cnt", count))
# Now, grab user records
records = self.data.remote.music.get_all_records(self.game, self.version)
users = {
uid: prof for (uid, prof) in
self.get_any_profiles([r[0] for r in records])
uid: prof for (uid, prof) in self.get_any_profiles([r[0] for r in records])
}
hiscore_allover = Node.void('hiscore_allover')
hiscore_allover = Node.void("hiscore_allover")
game.add_child(hiscore_allover)
# Output records
for (userid, score) in records:
info = Node.void('info')
info = Node.void("info")
if userid not in users:
raise Exception('Logic error, could not find profile for user!')
raise Exception("Logic error, could not find profile for user!")
profile = users[userid]
info.add_child(Node.u32('id', score.id))
info.add_child(Node.u32('type', score.chart))
info.add_child(Node.string('name', profile.get_str('name')))
info.add_child(Node.string('seq', ID.format_extid(profile.extid)))
info.add_child(Node.u32('score', score.points))
info.add_child(Node.u32("id", score.id))
info.add_child(Node.u32("type", score.chart))
info.add_child(Node.string("name", profile.get_str("name")))
info.add_child(Node.string("seq", ID.format_extid(profile.extid)))
info.add_child(Node.u32("score", score.points))
# Add to global scores
hiscore_allover.add_child(info)
# Now, grab local records
area_users = [
uid for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version)
if prof.get_int('loc', -1) == locid
]
records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users)
missing_players = [
uid for (uid, _) in records
if uid not in users
uid
for (uid, prof) in self.data.local.user.get_all_profiles(
self.game, self.version
)
if prof.get_int("loc", -1) == locid
]
records = self.data.local.music.get_all_records(
self.game, self.version, userlist=area_users
)
missing_players = [uid for (uid, _) in records if uid not in users]
for (uid, prof) in self.get_any_profiles(missing_players):
users[uid] = prof
hiscore_location = Node.void('hiscore_location')
hiscore_location = Node.void("hiscore_location")
game.add_child(hiscore_location)
# Output records
for (userid, score) in records:
info = Node.void('info')
info = Node.void("info")
if userid not in users:
raise Exception('Logic error, could not find profile for user!')
raise Exception("Logic error, could not find profile for user!")
profile = users[userid]
info.add_child(Node.u32('id', score.id))
info.add_child(Node.u32('type', score.chart))
info.add_child(Node.string('name', profile.get_str('name')))
info.add_child(Node.string('seq', ID.format_extid(profile.extid)))
info.add_child(Node.u32('score', score.points))
info.add_child(Node.u32("id", score.id))
info.add_child(Node.u32("type", score.chart))
info.add_child(Node.string("name", profile.get_str("name")))
info.add_child(Node.string("seq", ID.format_extid(profile.extid)))
info.add_child(Node.u32("score", score.points))
# Add to global scores
hiscore_location.add_child(info)
# Now, grab clear rates
clear_rate = Node.void('clear_rate')
clear_rate = Node.void("clear_rate")
game.add_child(clear_rate)
clears = self.get_clear_rates()
for songid in clears:
for chart in clears[songid]:
if clears[songid][chart]['total'] > 0:
rate = float(clears[songid][chart]['clears']) / float(clears[songid][chart]['total'])
dnode = Node.void('d')
if clears[songid][chart]["total"] > 0:
rate = float(clears[songid][chart]["clears"]) / float(
clears[songid][chart]["total"]
)
dnode = Node.void("d")
clear_rate.add_child(dnode)
dnode.add_child(Node.u32('id', songid))
dnode.add_child(Node.u32('type', chart))
dnode.add_child(Node.s16('cr', int(rate * 10000)))
dnode.add_child(Node.u32("id", songid))
dnode.add_child(Node.u32("type", chart))
dnode.add_child(Node.s16("cr", int(rate * 10000)))
return game
class MusecaGameFrozenHandler(MusecaBase):
def handle_game_3_frozen_request(self, request: Node) -> Node:
game = Node.void('game_3')
game.add_child(Node.u8('result', 0))
game = Node.void("game_3")
game.add_child(Node.u8("result", 0))
return game
class MusecaGameNewHandler(MusecaBase):
def handle_game_3_new_request(self, request: Node) -> Node:
refid = request.child_value('refid')
name = request.child_value('name')
loc = ID.parse_machine_id(request.child_value('locid'))
refid = request.child_value("refid")
name = request.child_value("name")
loc = ID.parse_machine_id(request.child_value("locid"))
self.new_profile_by_refid(refid, name, loc)
root = Node.void('game_3')
root = Node.void("game_3")
return root
class MusecaGameSaveMusicHandler(MusecaBase):
def handle_game_3_save_m_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -143,19 +141,19 @@ class MusecaGameSaveMusicHandler(MusecaBase):
userid = None
# Doesn't matter if userid is None here, that's an anonymous score
musicid = request.child_value('music_id')
chart = request.child_value('music_type')
points = request.child_value('score')
combo = request.child_value('max_chain')
clear_type = self.game_to_db_clear_type(request.child_value('clear_type'))
grade = self.game_to_db_grade(request.child_value('score_grade'))
musicid = request.child_value("music_id")
chart = request.child_value("music_type")
points = request.child_value("score")
combo = request.child_value("max_chain")
clear_type = self.game_to_db_clear_type(request.child_value("clear_type"))
grade = self.game_to_db_grade(request.child_value("score_grade"))
stats = {
'btn_rate': request.child_value('btn_rate'),
'long_rate': request.child_value('long_rate'),
'vol_rate': request.child_value('vol_rate'),
'critical': request.child_value('critical'),
'near': request.child_value('near'),
'error': request.child_value('error'),
"btn_rate": request.child_value("btn_rate"),
"long_rate": request.child_value("long_rate"),
"vol_rate": request.child_value("vol_rate"),
"critical": request.child_value("critical"),
"near": request.child_value("near"),
"error": request.child_value("error"),
}
# Save the score
@ -171,19 +169,17 @@ class MusecaGameSaveMusicHandler(MusecaBase):
)
# Return a blank response
return Node.void('game_3')
return Node.void("game_3")
class MusecaGamePlayEndHandler(MusecaBase):
def handle_game_3_play_e_request(self, request: Node) -> Node:
return Node.void('game_3')
return Node.void("game_3")
class MusecaGameSaveHandler(MusecaBase):
def handle_game_3_save_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -199,4 +195,4 @@ class MusecaGameSaveHandler(MusecaBase):
if userid is not None and newprofile is not None:
self.put_profile(userid, newprofile)
return Node.void('game_3')
return Node.void("game_3")

View File

@ -16,12 +16,17 @@ class MusecaFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['PIX']:
for gamecode in ["PIX"]:
Base.register(gamecode, MusecaFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
def version_from_date(date: int) -> Optional[int]:
if date <= 2016072600:
return VersionConstants.MUSECA
@ -29,7 +34,7 @@ class MusecaFactory(Factory):
return VersionConstants.MUSECA_1_PLUS
return None
if model.gamecode == 'PIX':
if model.gamecode == "PIX":
version = version_from_date(model.version)
if version == VersionConstants.MUSECA:
return Museca1(data, config, model)

View File

@ -59,12 +59,12 @@ class Museca1(
Return all of our front-end modifiably settings.
"""
return {
'bools': [
"bools": [
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
],
}
@ -109,34 +109,37 @@ class Museca1(
}[grade]
def handle_game_3_common_request(self, request: Node) -> Node:
game = Node.void('game_3')
limited = Node.void('music_limited')
game = Node.void("game_3")
limited = Node.void("music_limited")
game.add_child(limited)
# Song unlock config
game_config = self.get_game_config()
if game_config.get_bool('force_unlock_songs'):
if game_config.get_bool("force_unlock_songs"):
ids = set()
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
for song in songs:
if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE):
if song.data.get_int("limited") in (
self.GAME_LIMITED_LOCKED,
self.GAME_LIMITED_UNLOCKABLE,
):
ids.add((song.id, song.chart))
for (songid, chart) in ids:
info = Node.void('info')
info = Node.void("info")
limited.add_child(info)
info.add_child(Node.s32('music_id', songid))
info.add_child(Node.u8('music_type', chart))
info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED))
info.add_child(Node.s32("music_id", songid))
info.add_child(Node.u8("music_type", chart))
info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED))
# Event config
event = Node.void('event')
event = Node.void("event")
game.add_child(event)
def enable_event(eid: int) -> None:
evt = Node.void('info')
evt = Node.void("info")
event.add_child(evt)
evt.add_child(Node.u32('event_id', eid))
evt.add_child(Node.u32("event_id", eid))
# Allow PASELI light start
enable_event(83)
@ -153,21 +156,21 @@ class Museca1(
return game
def handle_game_3_exception_request(self, request: Node) -> Node:
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_load_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
root = self.get_profile_by_refid(refid)
if root is not None:
return root
# No data succession, there's nothing older than this!
root = Node.void('game_3')
root.add_child(Node.u8('result', 1))
root = Node.void("game_3")
root.add_child(Node.u8("result", 1))
return root
def handle_game_3_load_m_request(self, request: Node) -> Node:
refid = request.child_value('dataid')
refid = request.child_value("dataid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -175,154 +178,186 @@ class Museca1(
userid = None
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
else:
scores = []
# Output to the game
game = Node.void('game_3')
new = Node.void('new')
game = Node.void("game_3")
new = Node.void("new")
game.add_child(new)
for score in scores:
music = Node.void('music')
music = Node.void("music")
new.add_child(music)
music.add_child(Node.u32('music_id', score.id))
music.add_child(Node.u32('music_type', score.chart))
music.add_child(Node.u32('score', score.points))
music.add_child(Node.u32('cnt', score.plays))
music.add_child(Node.u32('clear_type', self.db_to_game_clear_type(score.data.get_int('clear_type'))))
music.add_child(Node.u32('score_grade', self.db_to_game_grade(score.data.get_int('grade'))))
stats = score.data.get_dict('stats')
music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate')))
music.add_child(Node.u32('long_rate', stats.get_int('long_rate')))
music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate')))
music.add_child(Node.u32("music_id", score.id))
music.add_child(Node.u32("music_type", score.chart))
music.add_child(Node.u32("score", score.points))
music.add_child(Node.u32("cnt", score.plays))
music.add_child(
Node.u32(
"clear_type",
self.db_to_game_clear_type(score.data.get_int("clear_type")),
)
)
music.add_child(
Node.u32(
"score_grade", self.db_to_game_grade(score.data.get_int("grade"))
)
)
stats = score.data.get_dict("stats")
music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate")))
music.add_child(Node.u32("long_rate", stats.get_int("long_rate")))
music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate")))
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
game = Node.void('game_3')
game = Node.void("game_3")
# Generic profile stuff
game.add_child(Node.string('name', profile.get_str('name')))
game.add_child(Node.string('code', ID.format_extid(profile.extid)))
game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet')))
game.add_child(Node.u32('gamecoin_block', profile.get_int('block')))
game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1)))
game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20)))
game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy')))
game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count')))
game.add_child(Node.string("name", profile.get_str("name")))
game.add_child(Node.string("code", ID.format_extid(profile.extid)))
game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet")))
game.add_child(Node.u32("gamecoin_block", profile.get_int("block")))
game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id", -1)))
game.add_child(
Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20))
)
game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy")))
game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count")))
# Play statistics
statistics = self.get_play_statistics(userid)
game.add_child(Node.u32('play_count', statistics.total_plays))
game.add_child(Node.u32('daily_count', statistics.today_plays))
game.add_child(Node.u32('play_chain', statistics.consecutive_days))
game.add_child(Node.u32("play_count", statistics.total_plays))
game.add_child(Node.u32("daily_count", statistics.today_plays))
game.add_child(Node.u32("play_chain", statistics.consecutive_days))
# Last played stuff
if 'last' in profile:
lastdict = profile.get_dict('last')
last = Node.void('last')
if "last" in profile:
lastdict = profile.get_dict("last")
last = Node.void("last")
game.add_child(last)
last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1)))
last.add_child(Node.u8('music_type', lastdict.get_int('music_type')))
last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type')))
last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down')))
last.add_child(Node.u8('headphone', lastdict.get_int('headphone')))
last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001)))
last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id')))
last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option')))
last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1)))
last.add_child(Node.u8("music_type", lastdict.get_int("music_type")))
last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type")))
last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down")))
last.add_child(Node.u8("headphone", lastdict.get_int("headphone")))
last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001)))
last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id")))
last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option")))
# Item unlocks
itemnode = Node.void('item')
itemnode = Node.void("item")
game.add_child(itemnode)
game_config = self.get_game_config()
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
achievements = self.data.local.user.get_achievements(
self.game, self.version, userid
)
for item in achievements:
if item.type[:5] != 'item_':
if item.type[:5] != "item_":
continue
itemtype = int(item.type[5:])
if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG:
if (
game_config.get_bool("force_unlock_songs")
and itemtype == self.GAME_CATALOG_TYPE_SONG
):
# Don't echo unlocked songs, we will add all of them later
continue
info = Node.void('info')
info = Node.void("info")
itemnode.add_child(info)
info.add_child(Node.u8('type', itemtype))
info.add_child(Node.u32('id', item.id))
info.add_child(Node.u32('param', item.data.get_int('param')))
if 'diff_param' in item.data:
info.add_child(Node.s32('diff_param', item.data.get_int('diff_param')))
info.add_child(Node.u8("type", itemtype))
info.add_child(Node.u32("id", item.id))
info.add_child(Node.u32("param", item.data.get_int("param")))
if "diff_param" in item.data:
info.add_child(Node.s32("diff_param", item.data.get_int("diff_param")))
if game_config.get_bool('force_unlock_songs'):
if game_config.get_bool("force_unlock_songs"):
ids: Dict[int, int] = {}
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
for song in songs:
if song.id not in ids:
ids[song.id] = 0
if song.data.get_int('difficulty') > 0:
if song.data.get_int("difficulty") > 0:
ids[song.id] = ids[song.id] | (1 << song.chart)
for itemid in ids:
if ids[itemid] == 0:
continue
info = Node.void('info')
info = Node.void("info")
itemnode.add_child(info)
info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG))
info.add_child(Node.u32('id', itemid))
info.add_child(Node.u32('param', ids[itemid]))
info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG))
info.add_child(Node.u32("id", itemid))
info.add_child(Node.u32("param", ids[itemid]))
return game
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
# Update blaster energy and in-game currencies
earned_gamecoin_packet = request.child_value('earned_gamecoin_packet')
earned_gamecoin_packet = request.child_value("earned_gamecoin_packet")
if earned_gamecoin_packet is not None:
newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet)
earned_gamecoin_block = request.child_value('earned_gamecoin_block')
newprofile.replace_int(
"packet", newprofile.get_int("packet") + earned_gamecoin_packet
)
earned_gamecoin_block = request.child_value("earned_gamecoin_block")
if earned_gamecoin_block is not None:
newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block)
earned_blaster_energy = request.child_value('earned_blaster_energy')
newprofile.replace_int(
"block", newprofile.get_int("block") + earned_gamecoin_block
)
earned_blaster_energy = request.child_value("earned_blaster_energy")
if earned_blaster_energy is not None:
newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy)
newprofile.replace_int(
"blaster_energy",
newprofile.get_int("blaster_energy") + earned_blaster_energy,
)
# Miscelaneous stuff
newprofile.replace_int('blaster_count', request.child_value('blaster_count'))
newprofile.replace_int('skill_name_id', request.child_value('skill_name_id'))
newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param'))
newprofile.replace_int("blaster_count", request.child_value("blaster_count"))
newprofile.replace_int("skill_name_id", request.child_value("skill_name_id"))
newprofile.replace_int_array(
"hidden_param", 20, request.child_value("hidden_param")
)
# Update user's unlock status if we aren't force unlocked
game_config = self.get_game_config()
if request.child('item') is not None:
for child in request.child('item').children:
if child.name != 'info':
if request.child("item") is not None:
for child in request.child("item").children:
if child.name != "info":
continue
item_id = child.child_value('id')
item_type = child.child_value('type')
param = child.child_value('param')
diff_param = child.child_value('diff_param')
item_id = child.child_value("id")
item_type = child.child_value("type")
param = child.child_value("param")
diff_param = child.child_value("diff_param")
if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG:
if (
game_config.get_bool("force_unlock_songs")
and item_type == self.GAME_CATALOG_TYPE_SONG
):
# Don't save back songs, because they were force unlocked
continue
if diff_param is not None:
paramvals = {
'diff_param': diff_param,
'param': param,
"diff_param": diff_param,
"param": param,
}
else:
paramvals = {
'param': param,
"param": param,
}
self.data.local.user.put_achievement(
@ -330,23 +365,23 @@ class Museca1(
self.version,
userid,
item_id,
f'item_{item_type}',
f"item_{item_type}",
paramvals,
)
# Grab last information.
lastdict = newprofile.get_dict('last')
lastdict.replace_int('headphone', request.child_value('headphone'))
lastdict.replace_int('appeal_id', request.child_value('appeal_id'))
lastdict.replace_int('comment_id', request.child_value('comment_id'))
lastdict.replace_int('music_id', request.child_value('music_id'))
lastdict.replace_int('music_type', request.child_value('music_type'))
lastdict.replace_int('sort_type', request.child_value('sort_type'))
lastdict.replace_int('narrow_down', request.child_value('narrow_down'))
lastdict.replace_int('gauge_option', request.child_value('gauge_option'))
lastdict = newprofile.get_dict("last")
lastdict.replace_int("headphone", request.child_value("headphone"))
lastdict.replace_int("appeal_id", request.child_value("appeal_id"))
lastdict.replace_int("comment_id", request.child_value("comment_id"))
lastdict.replace_int("music_id", request.child_value("music_id"))
lastdict.replace_int("music_type", request.child_value("music_type"))
lastdict.replace_int("sort_type", request.child_value("sort_type"))
lastdict.replace_int("narrow_down", request.child_value("narrow_down"))
lastdict.replace_int("gauge_option", request.child_value("gauge_option"))
# Save back last information gleaned from results
newprofile.replace_dict('last', lastdict)
newprofile.replace_dict("last", lastdict)
# Keep track of play statistics
self.update_play_statistics(userid)

View File

@ -64,18 +64,18 @@ class Museca1Plus(
Return all of our front-end modifiably settings.
"""
return {
'bools': [
"bools": [
{
'name': 'Disable Online Matching',
'tip': 'Disable online matching between games.',
'category': 'game_config',
'setting': 'disable_matching',
"name": "Disable Online Matching",
"tip": "Disable online matching between games.",
"category": "game_config",
"setting": "disable_matching",
},
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
],
}
@ -121,36 +121,39 @@ class Museca1Plus(
}[grade]
def handle_game_3_common_request(self, request: Node) -> Node:
game = Node.void('game_3')
limited = Node.void('music_limited')
game = Node.void("game_3")
limited = Node.void("music_limited")
game.add_child(limited)
# Song unlock config
game_config = self.get_game_config()
if game_config.get_bool('force_unlock_songs'):
if game_config.get_bool("force_unlock_songs"):
ids = set()
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
for song in songs:
if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE):
if song.data.get_int("limited") in (
self.GAME_LIMITED_LOCKED,
self.GAME_LIMITED_UNLOCKABLE,
):
ids.add((song.id, song.chart))
for (songid, chart) in ids:
info = Node.void('info')
info = Node.void("info")
limited.add_child(info)
info.add_child(Node.s32('music_id', songid))
info.add_child(Node.u8('music_type', chart))
info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED))
info.add_child(Node.s32("music_id", songid))
info.add_child(Node.u8("music_type", chart))
info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED))
# Event config
event = Node.void('event')
event = Node.void("event")
game.add_child(event)
def enable_event(eid: int) -> None:
evt = Node.void('info')
evt = Node.void("info")
event.add_child(evt)
evt.add_child(Node.u32('event_id', eid))
evt.add_child(Node.u32("event_id", eid))
if not game_config.get_bool('disable_matching'):
if not game_config.get_bool("disable_matching"):
enable_event(143) # Matching enabled
# These events are meant specifically for Museca Plus
@ -160,11 +163,11 @@ class Museca1Plus(
212, # News 2
]
event_ids = [
1, # Extended pedal options (no effect on Museca 1+1/2)
56, # Generator grafica icon <print 1 in musicdb>
83, # Paseli Light Start
86, # Generator grafica icon <print 2 in musicdb>
98, # Caption 2 notice (grs_grafica_caption_2.png)
1, # Extended pedal options (no effect on Museca 1+1/2)
56, # Generator grafica icon <print 1 in musicdb>
83, # Paseli Light Start
86, # Generator grafica icon <print 2 in musicdb>
98, # Caption 2 notice (grs_grafica_caption_2.png)
105, # Makes the "Number of Layers" option visible in game settings
130, # Curator Rank
141, # Coconatsu & Mukipara grafica effects
@ -183,96 +186,96 @@ class Museca1Plus(
enable_event(evtid)
# List of known event IDs:
# 56, # Generator grafica icon <print 1 in musicdb>
# 83, # Paseli Light Start
# 86, # Generator grafica icon <print 2 in musicdb>
# 98, # Caption 2 notice (grs_grafica_caption_2.png)
# 100, # DJ YOSHITAKA EXHIBITION 2016
# 103, # HATSUNE MIKU EXHIBITION 2016 - PART 1
# 104, # HATSUNE MIKU EXHIBITION 2016 - PART 2
# 105, # Makes the "Number of Layers" option visible in game settings
# 106, # HATSUNE MIKU EXHIBITION 2016 - PART 3
# 117, # NEW GENERATION METEOR DIFFUSE FESTA 2016 / RYUSEI FESTA TRIGGER
# 129, # COCONATSU EXHIBITION 2016
# 130, # Curator Rank
# 97, # Agetta Moratta (vmlink_phase 1 in musicdb)
# 114, # Agetta Moratta (vmlink_phase 2 in musicdb)
# 140, # Agetta Moratta (vmlink_phase 3 in musicdb)
# 141, # Coconatsu & Mukipara grafica effects
# 143, # Matching
# 144, # BEMANI ARCHAEOLOGICAL EXHIBITION
# 163, # TUTORIAL SNOW
# 169, # SHIORI FUJISAKI EXHIBITION 2017 - PART 1
# 174, # SHIORI FUJISAKI EXHIBITION 2017 - PART 2
# 182, # Mute illil's voice?
# 192, # GREAT REPRINT FESTIVAL: MIKU + DJ YOSHITAKA
# 194, # Continue
# 195, # Fictional Curator (foot pedal options)
# 211, #News 1
# 212, #News 2
# 213, #News 3
# 214, #News 4
# 217, #News 5
# 218, #News 6
# 219, #News 7
# 220, #News 8
# 221, # GRAFICA PRESENTATION CAMPAIGN “THE PRIMITIVE LIFE EXHIBITION”
# 222, # GRAFICA PRESENTATION CAMPAIGN "NOISE"
# 223, # GRAFICA PRESENTATION CAMPAIGN "PATISSERIE ROUGE"
# 224, # GRAFICA PRESENTATION CAMPAIGN "GUNSLINGER"
# 145, # MUKIPARA UNLOCKS
# 146, # MUKIPARA UNLOCKS
# 147, # MUKIPARA UNLOCKS
# 148, # MUKIPARA UNLOCKS
# 149, # MUKIPARA UNLOCKS
# 56, # Generator grafica icon <print 1 in musicdb>
# 83, # Paseli Light Start
# 86, # Generator grafica icon <print 2 in musicdb>
# 98, # Caption 2 notice (grs_grafica_caption_2.png)
# 100, # DJ YOSHITAKA EXHIBITION 2016
# 103, # HATSUNE MIKU EXHIBITION 2016 - PART 1
# 104, # HATSUNE MIKU EXHIBITION 2016 - PART 2
# 105, # Makes the "Number of Layers" option visible in game settings
# 106, # HATSUNE MIKU EXHIBITION 2016 - PART 3
# 117, # NEW GENERATION METEOR DIFFUSE FESTA 2016 / RYUSEI FESTA TRIGGER
# 129, # COCONATSU EXHIBITION 2016
# 130, # Curator Rank
# 97, # Agetta Moratta (vmlink_phase 1 in musicdb)
# 114, # Agetta Moratta (vmlink_phase 2 in musicdb)
# 140, # Agetta Moratta (vmlink_phase 3 in musicdb)
# 141, # Coconatsu & Mukipara grafica effects
# 143, # Matching
# 144, # BEMANI ARCHAEOLOGICAL EXHIBITION
# 163, # TUTORIAL SNOW
# 169, # SHIORI FUJISAKI EXHIBITION 2017 - PART 1
# 174, # SHIORI FUJISAKI EXHIBITION 2017 - PART 2
# 182, # Mute illil's voice?
# 192, # GREAT REPRINT FESTIVAL: MIKU + DJ YOSHITAKA
# 194, # Continue
# 195, # Fictional Curator (foot pedal options)
# 211, #News 1
# 212, #News 2
# 213, #News 3
# 214, #News 4
# 217, #News 5
# 218, #News 6
# 219, #News 7
# 220, #News 8
# 221, # GRAFICA PRESENTATION CAMPAIGN “THE PRIMITIVE LIFE EXHIBITION”
# 222, # GRAFICA PRESENTATION CAMPAIGN "NOISE"
# 223, # GRAFICA PRESENTATION CAMPAIGN "PATISSERIE ROUGE"
# 224, # GRAFICA PRESENTATION CAMPAIGN "GUNSLINGER"
# 145, # MUKIPARA UNLOCKS
# 146, # MUKIPARA UNLOCKS
# 147, # MUKIPARA UNLOCKS
# 148, # MUKIPARA UNLOCKS
# 149, # MUKIPARA UNLOCKS
# Makes special missions available on grafica that have them.
extend = Node.void('extend')
extend = Node.void("extend")
game.add_child(extend)
info = Node.void('info')
info = Node.void("info")
extend.add_child(info)
info.add_child(Node.u32('extend_id', 1))
info.add_child(Node.u32('extend_type', 9))
info.add_child(Node.s32('param_num_1', 2))
info.add_child(Node.s32('param_num_2', 50))
info.add_child(Node.s32('param_num_3', 59))
info.add_child(Node.s32('param_num_4', 64))
info.add_child(Node.s32('param_num_5', 86))
info.add_child(Node.string('param_str_1', 'available_ex: 1'))
info.add_child(Node.string('param_str_2', 'available_ex: 1'))
info.add_child(Node.string('param_str_3', 'available_ex: 1'))
info.add_child(Node.string('param_str_4', 'available_ex: 1'))
info.add_child(Node.string('param_str_5', 'available_ex: 1'))
info.add_child(Node.u32("extend_id", 1))
info.add_child(Node.u32("extend_type", 9))
info.add_child(Node.s32("param_num_1", 2))
info.add_child(Node.s32("param_num_2", 50))
info.add_child(Node.s32("param_num_3", 59))
info.add_child(Node.s32("param_num_4", 64))
info.add_child(Node.s32("param_num_5", 86))
info.add_child(Node.string("param_str_1", "available_ex: 1"))
info.add_child(Node.string("param_str_2", "available_ex: 1"))
info.add_child(Node.string("param_str_3", "available_ex: 1"))
info.add_child(Node.string("param_str_4", "available_ex: 1"))
info.add_child(Node.string("param_str_5", "available_ex: 1"))
if self.omnimix:
info = Node.void('info')
info = Node.void("info")
extend.add_child(info)
info.add_child(Node.u32('extend_id', 2))
info.add_child(Node.u32('extend_type', 9))
info.add_child(Node.s32('param_num_1', 210))
info.add_child(Node.s32('param_num_2', 0))
info.add_child(Node.s32('param_num_3', 0))
info.add_child(Node.s32('param_num_4', 0))
info.add_child(Node.s32('param_num_5', 0))
info.add_child(Node.string('param_str_1', ''))
info.add_child(Node.string('param_str_2', ''))
info.add_child(Node.string('param_str_3', ''))
info.add_child(Node.string('param_str_4', ''))
info.add_child(Node.string('param_str_5', ''))
info.add_child(Node.u32("extend_id", 2))
info.add_child(Node.u32("extend_type", 9))
info.add_child(Node.s32("param_num_1", 210))
info.add_child(Node.s32("param_num_2", 0))
info.add_child(Node.s32("param_num_3", 0))
info.add_child(Node.s32("param_num_4", 0))
info.add_child(Node.s32("param_num_5", 0))
info.add_child(Node.string("param_str_1", ""))
info.add_child(Node.string("param_str_2", ""))
info.add_child(Node.string("param_str_3", ""))
info.add_child(Node.string("param_str_4", ""))
info.add_child(Node.string("param_str_5", ""))
return game
def handle_game_3_lounge_request(self, request: Node) -> Node:
game = Node.void('game_3')
game = Node.void("game_3")
# Refresh interval in seconds.
game.add_child(Node.u32('interval', 10))
game.add_child(Node.u32("interval", 10))
return game
def handle_game_3_exception_request(self, request: Node) -> Node:
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_load_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
root = self.get_profile_by_refid(refid)
if root is not None:
return root
@ -294,12 +297,12 @@ class Museca1Plus(
# Return the previous formatted profile to the game.
return previous_game.format_profile(userid, profile)
else:
root = Node.void('game_3')
root.add_child(Node.u8('result', 1))
root = Node.void("game_3")
root.add_child(Node.u8("result", 1))
return root
def handle_game_3_load_m_request(self, request: Node) -> Node:
refid = request.child_value('dataid')
refid = request.child_value("dataid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -307,160 +310,192 @@ class Museca1Plus(
userid = None
if userid is not None:
scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
scores = self.data.remote.music.get_scores(
self.game, self.music_version, userid
)
else:
scores = []
# Output to the game
game = Node.void('game_3')
new = Node.void('new')
game = Node.void("game_3")
new = Node.void("new")
game.add_child(new)
for score in scores:
music = Node.void('music')
music = Node.void("music")
new.add_child(music)
music.add_child(Node.u32('music_id', score.id))
music.add_child(Node.u32('music_type', score.chart))
music.add_child(Node.u32('score', score.points))
music.add_child(Node.u32('cnt', score.plays))
music.add_child(Node.u32('combo', score.data.get_int('combo')))
music.add_child(Node.u32('clear_type', self.db_to_game_clear_type(score.data.get_int('clear_type'))))
music.add_child(Node.u32('score_grade', self.db_to_game_grade(score.data.get_int('grade'))))
stats = score.data.get_dict('stats')
music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate')))
music.add_child(Node.u32('long_rate', stats.get_int('long_rate')))
music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate')))
music.add_child(Node.u32("music_id", score.id))
music.add_child(Node.u32("music_type", score.chart))
music.add_child(Node.u32("score", score.points))
music.add_child(Node.u32("cnt", score.plays))
music.add_child(Node.u32("combo", score.data.get_int("combo")))
music.add_child(
Node.u32(
"clear_type",
self.db_to_game_clear_type(score.data.get_int("clear_type")),
)
)
music.add_child(
Node.u32(
"score_grade", self.db_to_game_grade(score.data.get_int("grade"))
)
)
stats = score.data.get_dict("stats")
music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate")))
music.add_child(Node.u32("long_rate", stats.get_int("long_rate")))
music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate")))
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
game = Node.void('game_3')
game = Node.void("game_3")
# Generic profile stuff
game.add_child(Node.string('name', profile.get_str('name')))
game.add_child(Node.string('code', ID.format_extid(profile.extid)))
game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet')))
game.add_child(Node.u32('gamecoin_block', profile.get_int('block')))
game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1)))
game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20)))
game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy')))
game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count')))
game.add_child(Node.string("name", profile.get_str("name")))
game.add_child(Node.string("code", ID.format_extid(profile.extid)))
game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet")))
game.add_child(Node.u32("gamecoin_block", profile.get_int("block")))
game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id", -1)))
game.add_child(
Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20))
)
game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy")))
game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count")))
# Enable Ryusei Festa
ryusei_festa = Node.void('ryusei_festa')
ryusei_festa = Node.void("ryusei_festa")
game.add_child(ryusei_festa)
ryusei_festa.add_child(Node.bool('ryusei_festa_trigger', True))
ryusei_festa.add_child(Node.bool("ryusei_festa_trigger", True))
# Play statistics
statistics = self.get_play_statistics(userid)
game.add_child(Node.u32('play_count', statistics.total_plays))
game.add_child(Node.u32('daily_count', statistics.today_plays))
game.add_child(Node.u32('play_chain', statistics.consecutive_days))
game.add_child(Node.u32("play_count", statistics.total_plays))
game.add_child(Node.u32("daily_count", statistics.today_plays))
game.add_child(Node.u32("play_chain", statistics.consecutive_days))
# Last played stuff
if 'last' in profile:
lastdict = profile.get_dict('last')
last = Node.void('last')
if "last" in profile:
lastdict = profile.get_dict("last")
last = Node.void("last")
game.add_child(last)
last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1)))
last.add_child(Node.u8('music_type', lastdict.get_int('music_type')))
last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type')))
last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down')))
last.add_child(Node.u8('headphone', lastdict.get_int('headphone')))
last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001)))
last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id')))
last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option')))
last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1)))
last.add_child(Node.u8("music_type", lastdict.get_int("music_type")))
last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type")))
last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down")))
last.add_child(Node.u8("headphone", lastdict.get_int("headphone")))
last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001)))
last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id")))
last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option")))
# Item unlocks
itemnode = Node.void('item')
itemnode = Node.void("item")
game.add_child(itemnode)
game_config = self.get_game_config()
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
achievements = self.data.local.user.get_achievements(
self.game, self.version, userid
)
for item in achievements:
if item.type[:5] != 'item_':
if item.type[:5] != "item_":
continue
itemtype = int(item.type[5:])
if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG:
if (
game_config.get_bool("force_unlock_songs")
and itemtype == self.GAME_CATALOG_TYPE_SONG
):
# Don't echo unlocked songs, we will add all of them later
continue
info = Node.void('info')
info = Node.void("info")
itemnode.add_child(info)
info.add_child(Node.u8('type', itemtype))
info.add_child(Node.u32('id', item.id))
info.add_child(Node.u32('param', item.data.get_int('param')))
if 'diff_param' in item.data:
info.add_child(Node.s32('diff_param', item.data.get_int('diff_param')))
info.add_child(Node.u8("type", itemtype))
info.add_child(Node.u32("id", item.id))
info.add_child(Node.u32("param", item.data.get_int("param")))
if "diff_param" in item.data:
info.add_child(Node.s32("diff_param", item.data.get_int("diff_param")))
if game_config.get_bool('force_unlock_songs'):
if game_config.get_bool("force_unlock_songs"):
ids: Dict[int, int] = {}
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
for song in songs:
if song.id not in ids:
ids[song.id] = 0
if song.data.get_int('difficulty') > 0:
if song.data.get_int("difficulty") > 0:
ids[song.id] = ids[song.id] | (1 << song.chart)
for itemid in ids:
if ids[itemid] == 0:
continue
info = Node.void('info')
info = Node.void("info")
itemnode.add_child(info)
info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG))
info.add_child(Node.u32('id', itemid))
info.add_child(Node.u32('param', ids[itemid]))
info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG))
info.add_child(Node.u32("id", itemid))
info.add_child(Node.u32("param", ids[itemid]))
return game
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
# Update blaster energy and in-game currencies
earned_gamecoin_packet = request.child_value('earned_gamecoin_packet')
earned_gamecoin_packet = request.child_value("earned_gamecoin_packet")
if earned_gamecoin_packet is not None:
newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet)
earned_gamecoin_block = request.child_value('earned_gamecoin_block')
newprofile.replace_int(
"packet", newprofile.get_int("packet") + earned_gamecoin_packet
)
earned_gamecoin_block = request.child_value("earned_gamecoin_block")
if earned_gamecoin_block is not None:
newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block)
earned_blaster_energy = request.child_value('earned_blaster_energy')
newprofile.replace_int(
"block", newprofile.get_int("block") + earned_gamecoin_block
)
earned_blaster_energy = request.child_value("earned_blaster_energy")
if earned_blaster_energy is not None:
newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy)
newprofile.replace_int(
"blaster_energy",
newprofile.get_int("blaster_energy") + earned_blaster_energy,
)
# Miscelaneous stuff
newprofile.replace_int('blaster_count', request.child_value('blaster_count'))
newprofile.replace_int('skill_name_id', request.child_value('skill_name_id'))
newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param'))
newprofile.replace_int("blaster_count", request.child_value("blaster_count"))
newprofile.replace_int("skill_name_id", request.child_value("skill_name_id"))
newprofile.replace_int_array(
"hidden_param", 20, request.child_value("hidden_param")
)
# Update user's unlock status if we aren't force unlocked
game_config = self.get_game_config()
if request.child('item') is not None:
for child in request.child('item').children:
if child.name != 'info':
if request.child("item") is not None:
for child in request.child("item").children:
if child.name != "info":
continue
item_id = child.child_value('id')
item_type = child.child_value('type')
param = child.child_value('param')
diff_param = child.child_value('diff_param')
item_id = child.child_value("id")
item_type = child.child_value("type")
param = child.child_value("param")
diff_param = child.child_value("diff_param")
if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG:
if (
game_config.get_bool("force_unlock_songs")
and item_type == self.GAME_CATALOG_TYPE_SONG
):
# Don't save back songs, because they were force unlocked
continue
if diff_param is not None:
paramvals = {
'diff_param': diff_param,
'param': param,
"diff_param": diff_param,
"param": param,
}
else:
paramvals = {
'param': param,
"param": param,
}
self.data.local.user.put_achievement(
@ -468,23 +503,23 @@ class Museca1Plus(
self.version,
userid,
item_id,
f'item_{item_type}',
f"item_{item_type}",
paramvals,
)
# Grab last information.
lastdict = newprofile.get_dict('last')
lastdict.replace_int('headphone', request.child_value('headphone'))
lastdict.replace_int('appeal_id', request.child_value('appeal_id'))
lastdict.replace_int('comment_id', request.child_value('comment_id'))
lastdict.replace_int('music_id', request.child_value('music_id'))
lastdict.replace_int('music_type', request.child_value('music_type'))
lastdict.replace_int('sort_type', request.child_value('sort_type'))
lastdict.replace_int('narrow_down', request.child_value('narrow_down'))
lastdict.replace_int('gauge_option', request.child_value('gauge_option'))
lastdict = newprofile.get_dict("last")
lastdict.replace_int("headphone", request.child_value("headphone"))
lastdict.replace_int("appeal_id", request.child_value("appeal_id"))
lastdict.replace_int("comment_id", request.child_value("comment_id"))
lastdict.replace_int("music_id", request.child_value("music_id"))
lastdict.replace_int("music_type", request.child_value("music_type"))
lastdict.replace_int("sort_type", request.child_value("sort_type"))
lastdict.replace_int("narrow_down", request.child_value("narrow_down"))
lastdict.replace_int("gauge_option", request.child_value("gauge_option"))
# Save back last information gleaned from results
newprofile.replace_dict('last', lastdict)
newprofile.replace_dict("last", lastdict)
# Keep track of play statistics
self.update_play_statistics(userid)

View File

@ -4,7 +4,14 @@ from typing_extensions import Final
from bemani.backend.base import Base
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
from bemani.common import Profile, ValidatedDict, Time, GameConstants, DBConstants, BroadcastConstants
from bemani.common import (
Profile,
ValidatedDict,
Time,
GameConstants,
DBConstants,
BroadcastConstants,
)
from bemani.data import UserID, Achievement, ScoreSaveException
from bemani.protocol import Node
@ -20,16 +27,30 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Play medals, as saved into/loaded from the DB
PLAY_MEDAL_NO_PLAY: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_NO_PLAY
PLAY_MEDAL_CIRCLE_FAILED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED
PLAY_MEDAL_DIAMOND_FAILED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED
PLAY_MEDAL_CIRCLE_FAILED: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED
PLAY_MEDAL_DIAMOND_FAILED: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED
PLAY_MEDAL_STAR_FAILED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED
PLAY_MEDAL_EASY_CLEAR: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR
PLAY_MEDAL_CIRCLE_CLEARED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED
PLAY_MEDAL_DIAMOND_CLEARED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED
PLAY_MEDAL_CIRCLE_CLEARED: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED
PLAY_MEDAL_DIAMOND_CLEARED: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED
PLAY_MEDAL_STAR_CLEARED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED
PLAY_MEDAL_CIRCLE_FULL_COMBO: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO
PLAY_MEDAL_DIAMOND_FULL_COMBO: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO
PLAY_MEDAL_STAR_FULL_COMBO: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO
PLAY_MEDAL_CIRCLE_FULL_COMBO: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO
PLAY_MEDAL_DIAMOND_FULL_COMBO: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO
PLAY_MEDAL_STAR_FULL_COMBO: Final[
int
] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO
PLAY_MEDAL_PERFECT: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT
# Chart type, as saved into/loaded from the DB, and returned to game
@ -47,7 +68,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# properly.
supports_expired_profiles: bool = False
def previous_version(self) -> Optional['PopnMusicBase']:
def previous_version(self) -> Optional["PopnMusicBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -59,7 +80,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('playerdata')
return Node.void("playerdata")
def format_conversion(self, userid: UserID, profile: Profile) -> Node:
"""
@ -70,9 +91,11 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
format_conversion on that previous version to get the profile to
migrate.
"""
return Node.void('playerdata')
return Node.void("playerdata")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -80,7 +103,9 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
"""
return oldprofile
def get_profile_by_refid(self, refid: Optional[str], load_mode: int) -> Optional[Node]:
def get_profile_by_refid(
self, refid: Optional[str], load_mode: int
) -> Optional[Node]:
"""
Given a RefID, return a formatted profile node. Basically every game
needs a profile lookup, even if it handles where that happens in
@ -127,7 +152,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
self,
refid: Optional[str],
name: Optional[str],
chara: Optional[int]=None,
chara: Optional[int] = None,
achievements: Sequence[Achievement] = (),
) -> Node:
"""
@ -138,7 +163,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
if name is None:
name = 'なし'
name = "なし"
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
@ -149,11 +174,11 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
refid,
0,
{
'name': name,
"name": name,
},
)
if chara is not None:
profile.replace_int('chara', chara)
profile.replace_int("chara", chara)
self.put_profile(userid, profile)
for achievement in achievements:
self.data.local.user.put_achievement(
@ -173,8 +198,8 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
chart: int,
points: int,
medal: int,
combo: Optional[int]=None,
stats: Optional[Dict[str, int]]=None,
combo: Optional[int] = None,
stats: Optional[Dict[str, int]] = None,
) -> None:
"""
Given various pieces of a score, update the user's high score and score
@ -227,19 +252,19 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
scoredata = oldscore.data
# Replace medal with highest value
scoredata.replace_int('medal', max(scoredata.get_int('medal'), medal))
history.replace_int('medal', medal)
scoredata.replace_int("medal", max(scoredata.get_int("medal"), medal))
history.replace_int("medal", medal)
if stats is not None:
if raised:
# We have stats, and there's a new high score, update the stats
scoredata.replace_dict('stats', stats)
history.replace_dict('stats', stats)
scoredata.replace_dict("stats", stats)
history.replace_dict("stats", stats)
if combo is not None:
# If we have a combo, replace it
scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
history.replace_int('combo', combo)
scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo))
history.replace_int("combo", combo)
# Look up where this score was earned
lid = self.get_machine_id()
@ -290,44 +315,53 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# We saved successfully
break
def broadcast_score(self, userid: UserID, songid: int, chart: int, medal: int, points: int, combo: int, stats: Dict[str, int]) -> None:
def broadcast_score(
self,
userid: UserID,
songid: int,
chart: int,
medal: int,
points: int,
combo: int,
stats: Dict[str, int],
) -> None:
# Generate scorecard
profile = self.get_profile(userid)
song = self.data.local.music.get_song(self.game, self.version, songid, chart)
card_medal = {
self.PLAY_MEDAL_CIRCLE_FAILED: 'Failed',
self.PLAY_MEDAL_DIAMOND_FAILED: 'Failed',
self.PLAY_MEDAL_STAR_FAILED: 'Failed',
self.PLAY_MEDAL_EASY_CLEAR: 'Cleared',
self.PLAY_MEDAL_CIRCLE_CLEARED: 'Cleared',
self.PLAY_MEDAL_DIAMOND_CLEARED: 'Cleared',
self.PLAY_MEDAL_STAR_CLEARED: 'Cleared',
self.PLAY_MEDAL_CIRCLE_FULL_COMBO: 'Full Combo',
self.PLAY_MEDAL_DIAMOND_FULL_COMBO: 'Full Combo',
self.PLAY_MEDAL_STAR_FULL_COMBO: 'Full Combo',
self.PLAY_MEDAL_PERFECT: 'Perfect',
self.PLAY_MEDAL_CIRCLE_FAILED: "Failed",
self.PLAY_MEDAL_DIAMOND_FAILED: "Failed",
self.PLAY_MEDAL_STAR_FAILED: "Failed",
self.PLAY_MEDAL_EASY_CLEAR: "Cleared",
self.PLAY_MEDAL_CIRCLE_CLEARED: "Cleared",
self.PLAY_MEDAL_DIAMOND_CLEARED: "Cleared",
self.PLAY_MEDAL_STAR_CLEARED: "Cleared",
self.PLAY_MEDAL_CIRCLE_FULL_COMBO: "Full Combo",
self.PLAY_MEDAL_DIAMOND_FULL_COMBO: "Full Combo",
self.PLAY_MEDAL_STAR_FULL_COMBO: "Full Combo",
self.PLAY_MEDAL_PERFECT: "Perfect",
}[medal]
card_chart = {
self.CHART_TYPE_EASY: 'Easy',
self.CHART_TYPE_NORMAL: 'Normal',
self.CHART_TYPE_HYPER: 'Hyper',
self.CHART_TYPE_EX: 'Ex',
self.CHART_TYPE_EASY: "Easy",
self.CHART_TYPE_NORMAL: "Normal",
self.CHART_TYPE_HYPER: "Hyper",
self.CHART_TYPE_EX: "Ex",
}[chart]
# Construct the dictionary for the broadcast
card_data = {
BroadcastConstants.PLAYER_NAME: profile.get_str('name', 'なし'),
BroadcastConstants.PLAYER_NAME: profile.get_str("name", "なし"),
BroadcastConstants.SONG_NAME: song.name,
BroadcastConstants.ARTIST_NAME: song.artist,
BroadcastConstants.DIFFICULTY: card_chart,
BroadcastConstants.SCORE: str(points),
BroadcastConstants.MEDAL: card_medal,
BroadcastConstants.COOLS: str(stats['cool']),
BroadcastConstants.GREATS: str(stats['great']),
BroadcastConstants.GOODS: str(stats['good']),
BroadcastConstants.BADS: str(stats['bad']),
BroadcastConstants.COOLS: str(stats["cool"]),
BroadcastConstants.GREATS: str(stats["great"]),
BroadcastConstants.GOODS: str(stats["good"]),
BroadcastConstants.BADS: str(stats["bad"]),
BroadcastConstants.COMBO: str(combo),
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -66,12 +66,17 @@ class PopnMusicFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['G15', 'H16', 'I17', 'J39', 'K39', 'L39', 'M39']:
for gamecode in ["G15", "H16", "I17", "J39", "K39", "L39", "M39"]:
Base.register(gamecode, PopnMusicFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
def version_from_date(date: int) -> Optional[int]:
if date <= 2014061900:
return VersionConstants.POPN_MUSIC_SUNNY_PARK
@ -87,26 +92,34 @@ class PopnMusicFactory(Factory):
return VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES
return None
if model.gamecode == 'G15':
if model.gamecode == "G15":
return PopnMusicAdventure(data, config, model)
if model.gamecode == 'H16':
if model.gamecode == "H16":
return PopnMusicParty(data, config, model)
if model.gamecode == 'I17':
if model.gamecode == "I17":
return PopnMusicTheMovie(data, config, model)
if model.gamecode == 'J39':
if model.gamecode == "J39":
return PopnMusicSengokuRetsuden(data, config, model)
if model.gamecode == 'K39':
if model.gamecode == "K39":
return PopnMusicTuneStreet(data, config, model)
if model.gamecode == 'L39':
if model.gamecode == "L39":
return PopnMusicFantasia(data, config, model)
if model.gamecode == 'M39':
if model.gamecode == "M39":
if model.version is None:
if parentmodel is None:
return None
# We have no way to tell apart newer versions. However, we can make
# an educated guess if we happen to be summoned for old profile lookup.
if parentmodel.gamecode not in ['G15', 'H16', 'I17', 'J39', 'K39', 'L39', 'M39']:
if parentmodel.gamecode not in [
"G15",
"H16",
"I17",
"J39",
"K39",
"L39",
"M39",
]:
return None
parentversion = version_from_date(parentmodel.version)
if parentversion == VersionConstants.POPN_MUSIC_LAPISTORIA:

View File

@ -52,31 +52,31 @@ class PopnMusicFantasia(PopnMusicBase):
Return all of our front-end modifiably settings.
"""
return {
'ints': [
"ints": [
{
'name': 'Game Phase',
'tip': 'Game unlock phase for all players.',
'category': 'game_config',
'setting': 'game_phase',
'values': {
0: 'NO PHASE',
1: 'SECRET DATA RELEASE',
2: 'MAX: ALL DATA RELEASE',
}
"name": "Game Phase",
"tip": "Game unlock phase for all players.",
"category": "game_config",
"setting": "game_phase",
"values": {
0: "NO PHASE",
1: "SECRET DATA RELEASE",
2: "MAX: ALL DATA RELEASE",
},
},
{
'name': 'Pop\'n Quest Event Phase',
'tip': 'Event phase for all players.',
'category': 'game_config',
'setting': 'event_phase',
'values': {
0: 'No event',
1: 'Phase 1',
2: 'Phase 2',
3: 'Phase 3',
4: 'Phase 4',
5: 'Phase MAX',
}
"name": "Pop'n Quest Event Phase",
"tip": "Event phase for all players.",
"category": "game_config",
"setting": "event_phase",
"values": {
0: "No event",
1: "Phase 1",
2: "Phase 2",
3: "Phase 3",
4: "Phase 4",
5: "Phase MAX",
},
},
]
}
@ -94,7 +94,7 @@ class PopnMusicFantasia(PopnMusicBase):
self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO,
self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO,
self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT,
}[score.data.get_int('medal')]
}[score.data.get_int("medal")]
position = {
self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION,
self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION,
@ -104,67 +104,95 @@ class PopnMusicFantasia(PopnMusicBase):
return medal << (position * 4)
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('playerdata')
root = Node.void("playerdata")
# Set up the base profile
base = Node.void('base')
base = Node.void("base")
root.add_child(base)
base.add_child(Node.string('name', profile.get_str('name', 'なし')))
base.add_child(Node.string('g_pm_id', ID.format_extid(profile.extid)))
base.add_child(Node.u8('mode', profile.get_int('mode', 0)))
base.add_child(Node.s8('button', profile.get_int('button', 0)))
base.add_child(Node.s8('last_play_flag', profile.get_int('last_play_flag', -1)))
base.add_child(Node.u8('medal_and_friend', profile.get_int('medal_and_friend', 0)))
base.add_child(Node.s8('category', profile.get_int('category', -1)))
base.add_child(Node.s8('sub_category', profile.get_int('sub_category', -1)))
base.add_child(Node.s16('chara', profile.get_int('chara', -1)))
base.add_child(Node.s8('chara_category', profile.get_int('chara_category', -1)))
base.add_child(Node.u8('collabo', profile.get_int('collabo', 255)))
base.add_child(Node.u8('sheet', profile.get_int('sheet', 0)))
base.add_child(Node.s8('tutorial', profile.get_int('tutorial', 0)))
base.add_child(Node.s32('music_open_pt', profile.get_int('music_open_pt', 0)))
base.add_child(Node.s8('is_conv', -1))
base.add_child(Node.s32('option', profile.get_int('option', 0)))
base.add_child(Node.s16('music', profile.get_int('music', -1)))
base.add_child(Node.u16('ep', profile.get_int('ep', 0)))
base.add_child(Node.s32_array('sp_color_flg', profile.get_int_array('sp_color_flg', 2)))
base.add_child(Node.s32('read_news', profile.get_int('read_news', 0)))
base.add_child(Node.s16('consecutive_days_coupon', profile.get_int('consecutive_days_coupon', 0)))
base.add_child(Node.s8('staff', 0))
base.add_child(Node.string("name", profile.get_str("name", "なし")))
base.add_child(Node.string("g_pm_id", ID.format_extid(profile.extid)))
base.add_child(Node.u8("mode", profile.get_int("mode", 0)))
base.add_child(Node.s8("button", profile.get_int("button", 0)))
base.add_child(Node.s8("last_play_flag", profile.get_int("last_play_flag", -1)))
base.add_child(
Node.u8("medal_and_friend", profile.get_int("medal_and_friend", 0))
)
base.add_child(Node.s8("category", profile.get_int("category", -1)))
base.add_child(Node.s8("sub_category", profile.get_int("sub_category", -1)))
base.add_child(Node.s16("chara", profile.get_int("chara", -1)))
base.add_child(Node.s8("chara_category", profile.get_int("chara_category", -1)))
base.add_child(Node.u8("collabo", profile.get_int("collabo", 255)))
base.add_child(Node.u8("sheet", profile.get_int("sheet", 0)))
base.add_child(Node.s8("tutorial", profile.get_int("tutorial", 0)))
base.add_child(Node.s32("music_open_pt", profile.get_int("music_open_pt", 0)))
base.add_child(Node.s8("is_conv", -1))
base.add_child(Node.s32("option", profile.get_int("option", 0)))
base.add_child(Node.s16("music", profile.get_int("music", -1)))
base.add_child(Node.u16("ep", profile.get_int("ep", 0)))
base.add_child(
Node.s32_array("sp_color_flg", profile.get_int_array("sp_color_flg", 2))
)
base.add_child(Node.s32("read_news", profile.get_int("read_news", 0)))
base.add_child(
Node.s16(
"consecutive_days_coupon", profile.get_int("consecutive_days_coupon", 0)
)
)
base.add_child(Node.s8("staff", 0))
# Player card section
player_card_dict = profile.get_dict('player_card')
player_card = Node.void('player_card')
player_card_dict = profile.get_dict("player_card")
player_card = Node.void("player_card")
root.add_child(player_card)
player_card.add_child(Node.u8_array('title', player_card_dict.get_int_array('title', 2, [0, 1])))
player_card.add_child(Node.u8('frame', player_card_dict.get_int('frame')))
player_card.add_child(Node.u8('base', player_card_dict.get_int('base')))
player_card.add_child(Node.u8_array('seal', player_card_dict.get_int_array('seal', 2)))
player_card.add_child(Node.s32_array('get_title', player_card_dict.get_int_array('get_title', 4)))
player_card.add_child(Node.s32('get_frame', player_card_dict.get_int('get_frame')))
player_card.add_child(Node.s32('get_base', player_card_dict.get_int('get_base')))
player_card.add_child(Node.s32_array('get_seal', player_card_dict.get_int_array('get_seal', 2)))
player_card.add_child(Node.s8('is_open', 1))
player_card.add_child(
Node.u8_array("title", player_card_dict.get_int_array("title", 2, [0, 1]))
)
player_card.add_child(Node.u8("frame", player_card_dict.get_int("frame")))
player_card.add_child(Node.u8("base", player_card_dict.get_int("base")))
player_card.add_child(
Node.u8_array("seal", player_card_dict.get_int_array("seal", 2))
)
player_card.add_child(
Node.s32_array("get_title", player_card_dict.get_int_array("get_title", 4))
)
player_card.add_child(
Node.s32("get_frame", player_card_dict.get_int("get_frame"))
)
player_card.add_child(
Node.s32("get_base", player_card_dict.get_int("get_base"))
)
player_card.add_child(
Node.s32_array("get_seal", player_card_dict.get_int_array("get_seal", 2))
)
player_card.add_child(Node.s8("is_open", 1))
# Player card EX section
player_card_ex = Node.void('player_card_ex')
player_card_ex = Node.void("player_card_ex")
root.add_child(player_card_ex)
player_card_ex.add_child(Node.s32('get_title_ex', player_card_dict.get_int('get_title_ex')))
player_card_ex.add_child(Node.s32('get_frame_ex', player_card_dict.get_int('get_frame_ex')))
player_card_ex.add_child(Node.s32('get_base_ex', player_card_dict.get_int('get_base_ex')))
player_card_ex.add_child(Node.s32('get_seal_ex', player_card_dict.get_int('get_seal_ex')))
player_card_ex.add_child(
Node.s32("get_title_ex", player_card_dict.get_int("get_title_ex"))
)
player_card_ex.add_child(
Node.s32("get_frame_ex", player_card_dict.get_int("get_frame_ex"))
)
player_card_ex.add_child(
Node.s32("get_base_ex", player_card_dict.get_int("get_base_ex"))
)
player_card_ex.add_child(
Node.s32("get_seal_ex", player_card_dict.get_int("get_seal_ex"))
)
# Statistics section and scores section
statistics = self.get_play_statistics(userid)
base.add_child(Node.s32('total_play_cnt', statistics.total_plays))
base.add_child(Node.s16('today_play_cnt', statistics.today_plays))
base.add_child(Node.s16('consecutive_days', statistics.consecutive_days))
base.add_child(Node.s32("total_play_cnt", statistics.total_plays))
base.add_child(Node.s16("today_play_cnt", statistics.today_plays))
base.add_child(Node.s16("consecutive_days", statistics.consecutive_days))
# Number of rivals that are active for this version.
links = self.data.local.user.get_links(self.game, self.version, userid)
rivalcount = 0
for link in links:
if link.type != 'rival':
if link.type != "rival":
continue
if not self.has_profile(link.other_userid):
@ -172,10 +200,20 @@ class PopnMusicFantasia(PopnMusicBase):
# This profile is valid.
rivalcount += 1
base.add_child(Node.u8('active_fr_num', rivalcount))
base.add_child(Node.u8("active_fr_num", rivalcount))
last_played = [x[0] for x in self.data.local.music.get_last_played(self.game, self.version, userid, 3)]
most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 20)]
last_played = [
x[0]
for x in self.data.local.music.get_last_played(
self.game, self.version, userid, 3
)
]
most_played = [
x[0]
for x in self.data.local.music.get_most_played(
self.game, self.version, userid, 20
)
]
while len(last_played) < 3:
last_played.append(-1)
while len(most_played) < 20:
@ -198,10 +236,12 @@ class PopnMusicFantasia(PopnMusicBase):
self.CHART_TYPE_EX,
]:
continue
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
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)
clear_medal[score.id] = clear_medal[
score.id
] | self.__format_medal_for_score(score)
hiscore_index = (score.id * 4) + {
self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION,
self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION,
@ -211,69 +251,81 @@ class PopnMusicFantasia(PopnMusicBase):
hiscore_byte_pos = int((hiscore_index * 17) / 8)
hiscore_bit_pos = int((hiscore_index * 17) % 8)
hiscore_value = score.points << hiscore_bit_pos
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF)
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (
hiscore_value & 0xFF
)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[
hiscore_byte_pos + 1
] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[
hiscore_byte_pos + 2
] | ((hiscore_value >> 16) & 0xFF)
hiscore = bytes(hiscore_array)
player_card.add_child(Node.s16_array('best_music', most_played[0:3]))
base.add_child(Node.s16_array('my_best', most_played))
base.add_child(Node.s16_array('latest_music', last_played))
base.add_child(Node.u16_array('clear_medal', clear_medal))
base.add_child(Node.u8_array('clear_medal_sub', clear_medal_sub))
player_card.add_child(Node.s16_array("best_music", most_played[0:3]))
base.add_child(Node.s16_array("my_best", most_played))
base.add_child(Node.s16_array("latest_music", last_played))
base.add_child(Node.u16_array("clear_medal", clear_medal))
base.add_child(Node.u8_array("clear_medal_sub", clear_medal_sub))
# Goes outside of base for some reason
root.add_child(Node.binary('hiscore', hiscore))
root.add_child(Node.binary("hiscore", hiscore))
# Net VS section
netvs = Node.void('netvs')
netvs = Node.void("netvs")
root.add_child(netvs)
netvs.add_child(Node.s32_array('get_ojama', [0, 0]))
netvs.add_child(Node.s32('rank_point', 0))
netvs.add_child(Node.s32('play_point', 0))
netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0]))
netvs.add_child(Node.u8('rank', 0))
netvs.add_child(Node.s8_array('ojama_condition', [0] * 74))
netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0]))
netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0]))
netvs.add_child(Node.s8_array('jewelry', [0] * 15))
netvs.add_child(Node.s32_array("get_ojama", [0, 0]))
netvs.add_child(Node.s32("rank_point", 0))
netvs.add_child(Node.s32("play_point", 0))
netvs.add_child(Node.s16_array("record", [0, 0, 0, 0, 0, 0]))
netvs.add_child(Node.u8("rank", 0))
netvs.add_child(Node.s8_array("ojama_condition", [0] * 74))
netvs.add_child(Node.s8_array("set_ojama", [0, 0, 0]))
netvs.add_child(Node.s8_array("set_recommend", [0, 0, 0]))
netvs.add_child(Node.s8_array("jewelry", [0] * 15))
for dialog in [0, 1, 2, 3, 4, 5]:
netvs.add_child(Node.string('dialog', f'dialog#{dialog}'))
netvs.add_child(Node.string("dialog", f"dialog#{dialog}"))
sp_data = Node.void('sp_data')
sp_data = Node.void("sp_data")
root.add_child(sp_data)
sp_data.add_child(Node.s32('sp', profile.get_int('sp', 0)))
sp_data.add_child(Node.s32("sp", profile.get_int("sp", 0)))
reflec_data = Node.void('reflec_data')
reflec_data = Node.void("reflec_data")
root.add_child(reflec_data)
reflec_data.add_child(Node.s8_array('reflec', profile.get_int_array('reflec', 2)))
reflec_data.add_child(
Node.s8_array("reflec", profile.get_int_array("reflec", 2))
)
# Navigate section
for i in range(3):
navigate_dict = profile.get_dict(f'navigate_{i}')
navigate = Node.void('navigate')
navigate_dict = profile.get_dict(f"navigate_{i}")
navigate = Node.void("navigate")
root.add_child(navigate)
navigate.add_child(Node.s8('genre', navigate_dict.get_int('genre', -1)))
navigate.add_child(Node.s8('image', navigate_dict.get_int('image', -1)))
navigate.add_child(Node.s8('level', navigate_dict.get_int('level', -1)))
navigate.add_child(Node.s8('ojama', navigate_dict.get_int('ojama', -1)))
navigate.add_child(Node.s16('limit_num', navigate_dict.get_int('limit_num', -1)))
navigate.add_child(Node.s8('button', navigate_dict.get_int('button', -1)))
navigate.add_child(Node.s8('life', navigate_dict.get_int('life', -1)))
navigate.add_child(Node.s16('progress', navigate_dict.get_int('progress', -1)))
navigate.add_child(Node.s8("genre", navigate_dict.get_int("genre", -1)))
navigate.add_child(Node.s8("image", navigate_dict.get_int("image", -1)))
navigate.add_child(Node.s8("level", navigate_dict.get_int("level", -1)))
navigate.add_child(Node.s8("ojama", navigate_dict.get_int("ojama", -1)))
navigate.add_child(
Node.s16("limit_num", navigate_dict.get_int("limit_num", -1))
)
navigate.add_child(Node.s8("button", navigate_dict.get_int("button", -1)))
navigate.add_child(Node.s8("life", navigate_dict.get_int("life", -1)))
navigate.add_child(
Node.s16("progress", navigate_dict.get_int("progress", -1))
)
return root
def format_conversion(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('playerdata')
root = Node.void("playerdata")
root.add_child(Node.string('name', profile.get_str('name', 'なし')))
root.add_child(Node.s16('chara', profile.get_int('chara', -1)))
root.add_child(Node.s32('option', profile.get_int('option', 0)))
root.add_child(Node.u8('version', 0))
root.add_child(Node.u8('kind', 0))
root.add_child(Node.u8('season', 0))
root.add_child(Node.string("name", profile.get_str("name", "なし")))
root.add_child(Node.s16("chara", profile.get_int("chara", -1)))
root.add_child(Node.s32("option", profile.get_int("option", 0)))
root.add_child(Node.u8("version", 0))
root.add_child(Node.u8("kind", 0))
root.add_child(Node.u8("season", 0))
clear_medal = [0] * self.GAME_MAX_MUSIC_ID
hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)
@ -291,10 +343,12 @@ class PopnMusicFantasia(PopnMusicBase):
self.CHART_TYPE_EX,
]:
continue
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
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)
clear_medal[score.id] = clear_medal[
score.id
] | self.__format_medal_for_score(score)
hiscore_index = (score.id * 4) + {
self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION,
self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION,
@ -304,86 +358,114 @@ class PopnMusicFantasia(PopnMusicBase):
hiscore_byte_pos = int((hiscore_index * 17) / 8)
hiscore_bit_pos = int((hiscore_index * 17) % 8)
hiscore_value = score.points << hiscore_bit_pos
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF)
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (
hiscore_value & 0xFF
)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[
hiscore_byte_pos + 1
] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[
hiscore_byte_pos + 2
] | ((hiscore_value >> 16) & 0xFF)
root.add_child(Node.u16_array('clear_medal', clear_medal))
root.add_child(Node.binary('hiscore', bytes(hiscore_array)))
root.add_child(Node.u16_array("clear_medal", clear_medal))
root.add_child(Node.binary("hiscore", bytes(hiscore_array)))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
# For some reason, Pop'n 20 sends us two profile saves, one with 'not done yet'
# so we only want to process the done yet node. The 'not gameover' save has
# jubeat collabo stuff set in it, but we don't use that so it doesn't matter.
if request.child_value('is_not_gameover') == 1:
if request.child_value("is_not_gameover") == 1:
return oldprofile
newprofile = oldprofile.clone()
newprofile.replace_int('option', request.child_value('option'))
newprofile.replace_int('chara', request.child_value('chara'))
newprofile.replace_int('mode', request.child_value('mode'))
newprofile.replace_int('button', request.child_value('button'))
newprofile.replace_int('music', request.child_value('music'))
newprofile.replace_int('sheet', request.child_value('sheet'))
newprofile.replace_int('last_play_flag', request.child_value('last_play_flag'))
newprofile.replace_int('category', request.child_value('category'))
newprofile.replace_int('sub_category', request.child_value('sub_category'))
newprofile.replace_int('chara_category', request.child_value('chara_category'))
newprofile.replace_int('medal_and_friend', request.child_value('medal_and_friend'))
newprofile.replace_int('ep', request.child_value('ep'))
newprofile.replace_int_array('sp_color_flg', 2, request.child_value('sp_color_flg'))
newprofile.replace_int('read_news', request.child_value('read_news'))
newprofile.replace_int('consecutive_days_coupon', request.child_value('consecutive_days_coupon'))
newprofile.replace_int('tutorial', request.child_value('tutorial'))
newprofile.replace_int('music_open_pt', request.child_value('music_open_pt'))
newprofile.replace_int('collabo', request.child_value('collabo'))
newprofile.replace_int("option", request.child_value("option"))
newprofile.replace_int("chara", request.child_value("chara"))
newprofile.replace_int("mode", request.child_value("mode"))
newprofile.replace_int("button", request.child_value("button"))
newprofile.replace_int("music", request.child_value("music"))
newprofile.replace_int("sheet", request.child_value("sheet"))
newprofile.replace_int("last_play_flag", request.child_value("last_play_flag"))
newprofile.replace_int("category", request.child_value("category"))
newprofile.replace_int("sub_category", request.child_value("sub_category"))
newprofile.replace_int("chara_category", request.child_value("chara_category"))
newprofile.replace_int(
"medal_and_friend", request.child_value("medal_and_friend")
)
newprofile.replace_int("ep", request.child_value("ep"))
newprofile.replace_int_array(
"sp_color_flg", 2, request.child_value("sp_color_flg")
)
newprofile.replace_int("read_news", request.child_value("read_news"))
newprofile.replace_int(
"consecutive_days_coupon", request.child_value("consecutive_days_coupon")
)
newprofile.replace_int("tutorial", request.child_value("tutorial"))
newprofile.replace_int("music_open_pt", request.child_value("music_open_pt"))
newprofile.replace_int("collabo", request.child_value("collabo"))
sp_node = request.child('sp_data')
sp_node = request.child("sp_data")
if sp_node is not None:
newprofile.replace_int('sp', sp_node.child_value('sp'))
newprofile.replace_int("sp", sp_node.child_value("sp"))
reflec_node = request.child('reflec_data')
reflec_node = request.child("reflec_data")
if reflec_node is not None:
newprofile.replace_int_array('reflec', 2, reflec_node.child_value('reflec'))
newprofile.replace_int_array("reflec", 2, reflec_node.child_value("reflec"))
# Keep track of play statistics
self.update_play_statistics(userid)
# Extract player card stuff
player_card_dict = newprofile.get_dict('player_card')
player_card_dict.replace_int_array('title', 2, request.child_value('title'))
player_card_dict.replace_int('frame', request.child_value('frame'))
player_card_dict.replace_int('base', request.child_value('base'))
player_card_dict.replace_int_array('seal', 2, request.child_value('seal'))
player_card_dict.replace_int_array('get_title', 4, request.child_value('get_title'))
player_card_dict.replace_int('get_frame', request.child_value('get_frame'))
player_card_dict.replace_int('get_base', request.child_value('get_base'))
player_card_dict.replace_int_array('get_seal', 2, request.child_value('get_seal'))
player_card_dict = newprofile.get_dict("player_card")
player_card_dict.replace_int_array("title", 2, request.child_value("title"))
player_card_dict.replace_int("frame", request.child_value("frame"))
player_card_dict.replace_int("base", request.child_value("base"))
player_card_dict.replace_int_array("seal", 2, request.child_value("seal"))
player_card_dict.replace_int_array(
"get_title", 4, request.child_value("get_title")
)
player_card_dict.replace_int("get_frame", request.child_value("get_frame"))
player_card_dict.replace_int("get_base", request.child_value("get_base"))
player_card_dict.replace_int_array(
"get_seal", 2, request.child_value("get_seal")
)
player_card_ex = request.child('player_card_ex')
player_card_ex = request.child("player_card_ex")
if player_card_ex is not None:
player_card_dict.replace_int('get_title_ex', player_card_ex.child_value('get_title_ex'))
player_card_dict.replace_int('get_frame_ex', player_card_ex.child_value('get_frame_ex'))
player_card_dict.replace_int('get_base_ex', player_card_ex.child_value('get_base_ex'))
player_card_dict.replace_int('get_seal_ex', player_card_ex.child_value('get_seal_ex'))
newprofile.replace_dict('player_card', player_card_dict)
player_card_dict.replace_int(
"get_title_ex", player_card_ex.child_value("get_title_ex")
)
player_card_dict.replace_int(
"get_frame_ex", player_card_ex.child_value("get_frame_ex")
)
player_card_dict.replace_int(
"get_base_ex", player_card_ex.child_value("get_base_ex")
)
player_card_dict.replace_int(
"get_seal_ex", player_card_ex.child_value("get_seal_ex")
)
newprofile.replace_dict("player_card", player_card_dict)
# Extract navigate stuff
nav_id = 0
for navigate in request.children:
if navigate.name == 'navigate':
navigate_dict = newprofile.get_dict(f'navigate_{nav_id}')
navigate_dict.replace_int('genre', navigate.child_value('genre'))
navigate_dict.replace_int('image', navigate.child_value('image'))
navigate_dict.replace_int('level', navigate.child_value('level'))
navigate_dict.replace_int('ojama', navigate.child_value('ojama'))
navigate_dict.replace_int('limit_num', navigate.child_value('limit_num'))
navigate_dict.replace_int('button', navigate.child_value('button'))
navigate_dict.replace_int('life', navigate.child_value('life'))
navigate_dict.replace_int('progress', navigate.child_value('progress'))
newprofile.replace_dict(f'navigate_{nav_id}', navigate_dict)
if navigate.name == "navigate":
navigate_dict = newprofile.get_dict(f"navigate_{nav_id}")
navigate_dict.replace_int("genre", navigate.child_value("genre"))
navigate_dict.replace_int("image", navigate.child_value("image"))
navigate_dict.replace_int("level", navigate.child_value("level"))
navigate_dict.replace_int("ojama", navigate.child_value("ojama"))
navigate_dict.replace_int(
"limit_num", navigate.child_value("limit_num")
)
navigate_dict.replace_int("button", navigate.child_value("button"))
navigate_dict.replace_int("life", navigate.child_value("life"))
navigate_dict.replace_int("progress", navigate.child_value("progress"))
newprofile.replace_dict(f"navigate_{nav_id}", navigate_dict)
nav_id += 1
if nav_id >= 3:
@ -391,15 +473,15 @@ class PopnMusicFantasia(PopnMusicBase):
# Extract scores
for node in request.children:
if node.name == 'stage':
songid = node.child_value('no')
if node.name == "stage":
songid = node.child_value("no")
chart = {
self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY,
self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL,
self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER,
self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX,
}[node.child_value('sheet')]
medal = (node.child_value('n_data') >> (chart * 4)) & 0x000F
}[node.child_value("sheet")]
medal = (node.child_value("n_data") >> (chart * 4)) & 0x000F
medal = {
self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED,
self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED,
@ -412,116 +494,132 @@ class PopnMusicFantasia(PopnMusicBase):
self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO,
self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT,
}[medal]
points = node.child_value('score')
points = node.child_value("score")
self.update_score(userid, songid, chart, points, medal)
return newprofile
def handle_playerdata_expire_request(self, request: Node) -> Node:
return Node.void('playerdata')
return Node.void("playerdata")
def handle_playerdata_logout_request(self, request: Node) -> Node:
return Node.void('playerdata')
return Node.void("playerdata")
def handle_playerdata_get_request(self, request: Node) -> Node:
modelstring = request.attribute('model')
refid = request.child_value('ref_id')
modelstring = request.attribute("model")
refid = request.child_value("ref_id")
root = self.get_profile_by_refid(
refid,
self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY,
)
if root is None:
root = Node.void('playerdata')
root.set_attribute('status', str(Status.NO_PROFILE))
root = Node.void("playerdata")
root.set_attribute("status", str(Status.NO_PROFILE))
return root
def handle_playerdata_conversion_request(self, request: Node) -> Node:
refid = request.child_value('ref_id')
name = request.child_value('name')
chara = request.child_value('chara')
refid = request.child_value("ref_id")
name = request.child_value("name")
chara = request.child_value("chara")
root = self.new_profile_by_refid(refid, name, chara)
if root is None:
root = Node.void('playerdata')
root.set_attribute('status', str(Status.NO_PROFILE))
root = Node.void("playerdata")
root.set_attribute("status", str(Status.NO_PROFILE))
return root
def handle_playerdata_new_request(self, request: Node) -> Node:
refid = request.child_value('ref_id')
name = request.child_value('name')
refid = request.child_value("ref_id")
name = request.child_value("name")
root = self.new_profile_by_refid(refid, name)
if root is None:
root = Node.void('playerdata')
root.set_attribute('status', str(Status.NO_PROFILE))
root = Node.void("playerdata")
root.set_attribute("status", str(Status.NO_PROFILE))
return root
def handle_playerdata_set_request(self, request: Node) -> Node:
refid = request.attribute('ref_id')
refid = request.attribute("ref_id")
machine = self.get_machine()
root = Node.void('playerdata')
root.add_child(Node.s8('pref', machine.data.get_int('pref', self.get_machine_region())))
root = Node.void("playerdata")
root.add_child(
Node.s8("pref", machine.data.get_int("pref", self.get_machine_region()))
)
if refid is None:
root.add_child(Node.string('name', ''))
root.add_child(Node.s16('chara', -1))
root.add_child(Node.u8('frame', 0))
root.add_child(Node.u8('base', 0))
root.add_child(Node.u8('seal_1', 0))
root.add_child(Node.u8('seal_2', 0))
root.add_child(Node.u8('title_1', 0))
root.add_child(Node.u8('title_2', 0))
root.add_child(Node.s16('recommend_1', -1))
root.add_child(Node.s16('recommend_2', -1))
root.add_child(Node.s16('recommend_3', -1))
root.add_child(Node.string('message', ''))
root.add_child(Node.string("name", ""))
root.add_child(Node.s16("chara", -1))
root.add_child(Node.u8("frame", 0))
root.add_child(Node.u8("base", 0))
root.add_child(Node.u8("seal_1", 0))
root.add_child(Node.u8("seal_2", 0))
root.add_child(Node.u8("title_1", 0))
root.add_child(Node.u8("title_2", 0))
root.add_child(Node.s16("recommend_1", -1))
root.add_child(Node.s16("recommend_2", -1))
root.add_child(Node.s16("recommend_3", -1))
root.add_child(Node.string("message", ""))
return root
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
root.add_child(Node.string('name', ''))
root.add_child(Node.s16('chara', -1))
root.add_child(Node.u8('frame', 0))
root.add_child(Node.u8('base', 0))
root.add_child(Node.u8('seal_1', 0))
root.add_child(Node.u8('seal_2', 0))
root.add_child(Node.u8('title_1', 0))
root.add_child(Node.u8('title_2', 0))
root.add_child(Node.s16('recommend_1', -1))
root.add_child(Node.s16('recommend_2', -1))
root.add_child(Node.s16('recommend_3', -1))
root.add_child(Node.string('message', ''))
root.add_child(Node.string("name", ""))
root.add_child(Node.s16("chara", -1))
root.add_child(Node.u8("frame", 0))
root.add_child(Node.u8("base", 0))
root.add_child(Node.u8("seal_1", 0))
root.add_child(Node.u8("seal_2", 0))
root.add_child(Node.u8("title_1", 0))
root.add_child(Node.u8("title_2", 0))
root.add_child(Node.s16("recommend_1", -1))
root.add_child(Node.s16("recommend_2", -1))
root.add_child(Node.s16("recommend_3", -1))
root.add_child(Node.string("message", ""))
return root
oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0)
oldprofile = self.get_profile(userid) or Profile(
self.game, self.version, refid, 0
)
newprofile = self.unformat_profile(userid, request, oldprofile)
if newprofile is not None:
player_card_dict = newprofile.get_dict('player_card')
player_card_dict = newprofile.get_dict("player_card")
self.put_profile(userid, newprofile)
root.add_child(Node.string('name', newprofile.get_str('name', 'なし')))
root.add_child(Node.s16('chara', newprofile.get_int('chara', -1)))
root.add_child(Node.u8('frame', player_card_dict.get_int('frame')))
root.add_child(Node.u8('base', player_card_dict.get_int('base')))
root.add_child(Node.u8('seal_1', player_card_dict.get_int_array('seal', 2)[0]))
root.add_child(Node.u8('seal_2', player_card_dict.get_int_array('seal', 2)[1]))
root.add_child(Node.u8('title_1', player_card_dict.get_int_array('title', 2, [0, 1])[0]))
root.add_child(Node.u8('title_2', player_card_dict.get_int_array('title', 2, [0, 1])[1]))
root.add_child(Node.s16('recommend_1', -1))
root.add_child(Node.s16('recommend_2', -1))
root.add_child(Node.s16('recommend_3', -1))
root.add_child(Node.string('message', ''))
root.add_child(Node.string("name", newprofile.get_str("name", "なし")))
root.add_child(Node.s16("chara", newprofile.get_int("chara", -1)))
root.add_child(Node.u8("frame", player_card_dict.get_int("frame")))
root.add_child(Node.u8("base", player_card_dict.get_int("base")))
root.add_child(
Node.u8("seal_1", player_card_dict.get_int_array("seal", 2)[0])
)
root.add_child(
Node.u8("seal_2", player_card_dict.get_int_array("seal", 2)[1])
)
root.add_child(
Node.u8(
"title_1", player_card_dict.get_int_array("title", 2, [0, 1])[0]
)
)
root.add_child(
Node.u8(
"title_2", player_card_dict.get_int_array("title", 2, [0, 1])[1]
)
)
root.add_child(Node.s16("recommend_1", -1))
root.add_child(Node.s16("recommend_2", -1))
root.add_child(Node.s16("recommend_3", -1))
root.add_child(Node.string("message", ""))
return root
def handle_playerdata_friend_request(self, request: Node) -> Node:
refid = request.attribute('ref_id')
root = Node.void('playerdata')
refid = request.attribute("ref_id")
root = Node.void("playerdata")
# Look up our own user ID based on the RefID provided.
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
root.set_attribute('status', str(Status.NO_PROFILE))
root.set_attribute("status", str(Status.NO_PROFILE))
return root
# Grab the links that we care about.
@ -529,7 +627,7 @@ class PopnMusicFantasia(PopnMusicBase):
profiles: Dict[UserID, Profile] = {}
rivals: List[Link] = []
for link in links:
if link.type != 'rival':
if link.type != "rival":
continue
other_profile = self.get_profile(link.other_userid)
@ -544,17 +642,19 @@ class PopnMusicFantasia(PopnMusicBase):
scores = self.data.remote.music.get_scores(self.game, self.version, rivalid)
# First, output general profile info.
friend = Node.void('friend')
friend = Node.void("friend")
root.add_child(friend)
# This might be for having non-active or non-confirmed friends, but setting to 0 makes the
# ranking numbers disappear and the player icon show a questionmark.
friend.add_child(Node.s8('open', 1))
friend.add_child(Node.s8("open", 1))
# Set up some sane defaults.
friend.add_child(Node.string('name', rivalprofile.get_str('name', 'なし')))
friend.add_child(Node.string('g_pm_id', ID.format_extid(rivalprofile.extid)))
friend.add_child(Node.s16('chara', rivalprofile.get_int('chara', -1)))
friend.add_child(Node.string("name", rivalprofile.get_str("name", "なし")))
friend.add_child(
Node.string("g_pm_id", ID.format_extid(rivalprofile.extid))
)
friend.add_child(Node.s16("chara", rivalprofile.get_int("chara", -1)))
# Perform hiscore/medal conversion.
hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)
@ -571,10 +671,12 @@ class PopnMusicFantasia(PopnMusicBase):
self.CHART_TYPE_EX,
]:
continue
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
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)
clear_medal[score.id] = clear_medal[
score.id
] | self.__format_medal_for_score(score)
hiscore_index = (score.id * 4) + {
self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION,
self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION,
@ -584,13 +686,19 @@ class PopnMusicFantasia(PopnMusicBase):
hiscore_byte_pos = int((hiscore_index * 17) / 8)
hiscore_bit_pos = int((hiscore_index * 17) % 8)
hiscore_value = score.points << hiscore_bit_pos
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF)
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (
hiscore_value & 0xFF
)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[
hiscore_byte_pos + 1
] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[
hiscore_byte_pos + 2
] | ((hiscore_value >> 16) & 0xFF)
hiscore = bytes(hiscore_array)
friend.add_child(Node.u16_array('clear_medal', clear_medal))
friend.add_child(Node.binary('hiscore', hiscore))
friend.add_child(Node.u16_array("clear_medal", clear_medal))
friend.add_child(Node.binary("hiscore", hiscore))
# Note that if we ever support internet ranking mode, there's an 'ir_hiscore' node here as well.
@ -601,41 +709,45 @@ class PopnMusicFantasia(PopnMusicBase):
def handle_game_get_request(self, request: Node) -> Node:
game_config = self.get_game_config()
game_phase = game_config.get_int('game_phase')
event_phase = game_config.get_int('event_phase')
game_phase = game_config.get_int("game_phase")
event_phase = game_config.get_int("event_phase")
root = Node.void('game')
root.add_child(Node.s32('game_phase', game_phase))
root.add_child(Node.s32('ir_phase', 0))
root.add_child(Node.s32('event_phase', event_phase))
root.add_child(Node.s32('netvs_phase', 0)) # Net taisen mode, we don't support lobbies.
root.add_child(Node.s32('card_phase', 6))
root.add_child(Node.s32('illust_phase', 2))
root.add_child(Node.s32('psp_phase', 5)) # Unlock songs from Pop'n Music Portable.
root.add_child(Node.s32('other_phase', 1))
root.add_child(Node.s32('jubeat_phase', 1))
root.add_child(Node.s32('public_phase', 3))
root.add_child(Node.s32('kac_phase', 2))
root.add_child(Node.s32('local_matching_enable', 1))
root.add_child(Node.s32('n_matching_sec', 60))
root.add_child(Node.s32('l_matching_sec', 60))
root.add_child(Node.s32('is_check_cpu', 0))
root.add_child(Node.s32('week_no', 0))
root.add_child(Node.s32('team_day', 0))
root.add_child(Node.s32_array('ng_illust', [-1] * 64))
root.add_child(Node.s16_array('sel_ranking', [-1] * 10))
root.add_child(Node.s16_array('up_ranking', [-1] * 10))
root = Node.void("game")
root.add_child(Node.s32("game_phase", game_phase))
root.add_child(Node.s32("ir_phase", 0))
root.add_child(Node.s32("event_phase", event_phase))
root.add_child(
Node.s32("netvs_phase", 0)
) # Net taisen mode, we don't support lobbies.
root.add_child(Node.s32("card_phase", 6))
root.add_child(Node.s32("illust_phase", 2))
root.add_child(
Node.s32("psp_phase", 5)
) # Unlock songs from Pop'n Music Portable.
root.add_child(Node.s32("other_phase", 1))
root.add_child(Node.s32("jubeat_phase", 1))
root.add_child(Node.s32("public_phase", 3))
root.add_child(Node.s32("kac_phase", 2))
root.add_child(Node.s32("local_matching_enable", 1))
root.add_child(Node.s32("n_matching_sec", 60))
root.add_child(Node.s32("l_matching_sec", 60))
root.add_child(Node.s32("is_check_cpu", 0))
root.add_child(Node.s32("week_no", 0))
root.add_child(Node.s32("team_day", 0))
root.add_child(Node.s32_array("ng_illust", [-1] * 64))
root.add_child(Node.s16_array("sel_ranking", [-1] * 10))
root.add_child(Node.s16_array("up_ranking", [-1] * 10))
return root
def handle_game_active_request(self, request: Node) -> Node:
# Update the name of this cab for admin purposes. Also store the prefecture.
machine = self.get_machine()
machine.name = request.child_value('shop_name') or machine.name
machine.data.replace_int('pref', request.child_value('pref'))
machine.name = request.child_value("shop_name") or machine.name
machine.data.replace_int("pref", request.child_value("pref"))
self.update_machine(machine)
return Node.void('game')
return Node.void("game")
def handle_lobby_requests(self, request: Node) -> Node:
# Stub out the entire lobby service
return Node.void('lobby')
return Node.void("lobby")

View File

@ -41,164 +41,164 @@ class PopnMusicKaimei(PopnMusicModernBase):
Return all of our front-end modifiably settings.
"""
return {
'ints': [
"ints": [
{
'name': 'Music Open Phase',
'tip': 'Default music phase for all players.',
'category': 'game_config',
'setting': 'music_phase',
'values': {
"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',
}
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'
"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!',
}
"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": "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',
}
"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',
}
"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',
}
"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': [
"bools": [
# We don't currently support lobbies or anything, so this is commented out until
# somebody gets around to implementing it.
# {
@ -208,24 +208,24 @@ class PopnMusicKaimei(PopnMusicModernBase):
# 'setting': 'enable_net_taisen',
# },
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"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')
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')
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 (
@ -384,20 +384,31 @@ class PopnMusicKaimei(PopnMusicModernBase):
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = super().format_profile(userid, profile)
account = root.child('account')
account.add_child(Node.s16('card_again_count', profile.get_int('card_again_count')))
account.add_child(Node.s16('sp_riddles_id', profile.get_int('sp_riddles_id')))
account = root.child("account")
account.add_child(
Node.s16("card_again_count", profile.get_int("card_again_count"))
)
account.add_child(Node.s16("sp_riddles_id", profile.get_int("sp_riddles_id")))
# Kaimei riddles events
event2021 = Node.void('event2021')
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)))
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')
riddles_data = Node.void("riddles_data")
root.add_child(riddles_data)
# Generate Short Riddles for MN tanteisha
@ -413,62 +424,74 @@ class PopnMusicKaimei(PopnMusicModernBase):
randomRiddles.append(riddle)
sh_riddles = Node.void('sh_riddles')
sh_riddles = Node.void("sh_riddles")
riddles_data.add_child(sh_riddles)
sh_riddles.add_child(Node.u32('sh_riddles_id', riddle))
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)
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')
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')
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))
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:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = super().unformat_profile(userid, request, oldprofile)
account = request.child('account')
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'))
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')
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'))
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':
if node.name == "riddles_data":
riddle_id = 0
playedRiddle = request.child('account').child_value('sp_riddles_id')
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')
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):
if riddles_cleared or select_count >= 3:
select_count = 3
elif (playedRiddle == riddle_id):
elif playedRiddle == riddle_id:
select_count += 1
self.data.local.user.put_achievement(
@ -476,13 +499,13 @@ class PopnMusicKaimei(PopnMusicModernBase):
self.version,
userid,
riddle_id,
'riddle',
"riddle",
{
'kaimei_gauge': kaimei_gauge,
'is_cleared': is_cleared,
'riddles_cleared': riddles_cleared,
'select_count': select_count,
'other_count': other_count,
"kaimei_gauge": kaimei_gauge,
"is_cleared": is_cleared,
"riddles_cleared": riddles_cleared,
"select_count": select_count,
"other_count": other_count,
},
)

File diff suppressed because it is too large Load Diff

View File

@ -36,102 +36,102 @@ class PopnMusicPeace(PopnMusicModernBase):
Return all of our front-end modifiably settings.
"""
return {
'ints': [
"ints": [
{
'name': 'Music Open Phase',
'tip': 'Default music phase for all players.',
'category': 'game_config',
'setting': 'music_phase',
'values': {
"name": "Music Open Phase",
"tip": "Default music phase for all players.",
"category": "game_config",
"setting": "music_phase",
"values": {
# The value goes to 23 now, but it starts where usaneko left off at 11
# Unlocks a total of 53 songs
12: 'No music unlocks',
13: 'Phase 1',
14: 'Phase 2',
15: 'Phase 3',
16: 'Phase 4',
17: 'Phase 5',
18: 'Phase 6',
19: 'Phase 7',
20: 'Phase 8',
21: 'Phase 9',
22: 'Phase 10',
23: 'Phase MAX',
}
12: "No music unlocks",
13: "Phase 1",
14: "Phase 2",
15: "Phase 3",
16: "Phase 4",
17: "Phase 5",
18: "Phase 6",
19: "Phase 7",
20: "Phase 8",
21: "Phase 9",
22: "Phase 10",
23: "Phase MAX",
},
},
{
'name': 'NAVI-Kun Event Phase',
'tip': 'NAVI-Kun event phase for all players.',
'category': 'game_config',
'setting': 'navikun_phase',
'values': {
"name": "NAVI-Kun Event Phase",
"tip": "NAVI-Kun event phase for all players.",
"category": "game_config",
"setting": "navikun_phase",
"values": {
# The value goes to 30 now, but it starts where usaneko left off at 15
# Unlocks a total of 89 songs
15: 'Phase 1',
16: 'Phase 2',
17: 'Phase 3',
18: 'Phase 4',
19: 'Phase 5',
20: 'Phase 6',
21: 'Phase 7',
22: 'Phase 8',
23: 'Phase 9',
24: 'Phase 10',
25: 'Phase 11',
26: 'Phase 12',
27: 'Phase 13',
28: 'Phase 14',
29: 'Phase 15',
30: 'Phase MAX',
15: "Phase 1",
16: "Phase 2",
17: "Phase 3",
18: "Phase 4",
19: "Phase 5",
20: "Phase 6",
21: "Phase 7",
22: "Phase 8",
23: "Phase 9",
24: "Phase 10",
25: "Phase 11",
26: "Phase 12",
27: "Phase 13",
28: "Phase 14",
29: "Phase 15",
30: "Phase MAX",
},
},
{
# 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!',
}
"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!",
},
},
{
# The following values control the pop'n music event archive. Setting the flag to the following values has the
# corresponding effect. Each value will include the events above it, for example setting it to 5 gives you the
# pop'n 15 event, as well as SP, 12, and 11 events. Setting it to 0 disabled the event and skips the entire screen,
# setting it to 20 makes all of the events available for selection. Completing the minigame unlocks the associated content.
'name': 'Event Archive Phase',
'tip': 'Event Archive mini-game phase for all players.',
'category': 'game_config',
'setting': 'event_archive_phase',
'values': {
0: 'Event Archive disabled',
1: 'pop\'n music 11 - The Latest Space Station',
2: 'pop\'n music 11 & 12 Iroha - The Southernmost Point of the Universe / Ninja Otasuke Cheat Sheet in Trouble',
3: 'pop\'n music Sunny Park - I Love Walking in Happiness Park',
4: 'pop\'n music 12 Iroha - Ninja Code: April 1st Volume',
5: 'pop\'n music 15 ADVENTURE - Route to Awaken the Soul',
6: 'pop\'n music 20 fantasia - A Braided Fantasy Song',
7: 'EXTRA',
8: 'pop\'n music 15 ADVENTURE - A Route with a Faint Bell Sound',
9: 'pop\'n music 13 Carnival - Bunny Magician Attraction',
10: 'pop\'n music 14 FEVER! - That Burning Special Attack, again!',
11: 'pop\'n music Sunny Park - Festival Nightfall Park',
12: 'pop\'n music 20 fantasia - A Fantasy Song by the Bladed Warrior',
13: 'pop\'n music 19 TUNE STREET - A Town Where the Sound of the Brass Band Rings After School',
14: 'pop\'n music éclale - Fun Rag Hour',
15: 'pop\'n music 13 Carnival - Ghost Piano Attraction',
16: 'pop\'n music 14 FEVER! - That Warrior Defending Peace, again!',
17: 'pop\'n music 18 Sengoku Retsuden - A Territory with a Glamorous Cultural Flavor',
18: 'pop\'n music éclale - Runaway Guitarist in the Starry Sky',
19: 'pop\'n music 17 THE MOVIE - A Blockbuster Uncovering a Conspiracy in the Peaceful City',
20: 'pop\'n music lapistoria - God\'s Forgotten Things',
}
"name": "Event Archive Phase",
"tip": "Event Archive mini-game phase for all players.",
"category": "game_config",
"setting": "event_archive_phase",
"values": {
0: "Event Archive disabled",
1: "pop'n music 11 - The Latest Space Station",
2: "pop'n music 11 & 12 Iroha - The Southernmost Point of the Universe / Ninja Otasuke Cheat Sheet in Trouble",
3: "pop'n music Sunny Park - I Love Walking in Happiness Park",
4: "pop'n music 12 Iroha - Ninja Code: April 1st Volume",
5: "pop'n music 15 ADVENTURE - Route to Awaken the Soul",
6: "pop'n music 20 fantasia - A Braided Fantasy Song",
7: "EXTRA",
8: "pop'n music 15 ADVENTURE - A Route with a Faint Bell Sound",
9: "pop'n music 13 Carnival - Bunny Magician Attraction",
10: "pop'n music 14 FEVER! - That Burning Special Attack, again!",
11: "pop'n music Sunny Park - Festival Nightfall Park",
12: "pop'n music 20 fantasia - A Fantasy Song by the Bladed Warrior",
13: "pop'n music 19 TUNE STREET - A Town Where the Sound of the Brass Band Rings After School",
14: "pop'n music éclale - Fun Rag Hour",
15: "pop'n music 13 Carnival - Ghost Piano Attraction",
16: "pop'n music 14 FEVER! - That Warrior Defending Peace, again!",
17: "pop'n music 18 Sengoku Retsuden - A Territory with a Glamorous Cultural Flavor",
18: "pop'n music éclale - Runaway Guitarist in the Starry Sky",
19: "pop'n music 17 THE MOVIE - A Blockbuster Uncovering a Conspiracy in the Peaceful City",
20: "pop'n music lapistoria - God's Forgotten Things",
},
},
],
'bools': [
"bools": [
# We don't currently support lobbies or anything, so this is commented out until
# somebody gets around to implementing it.
# {
@ -141,21 +141,21 @@ class PopnMusicPeace(PopnMusicModernBase):
# 'setting': 'enable_net_taisen',
# },
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"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')
event_archive_phase = game_config.get_int('event_archive_phase')
holiday_greeting = game_config.get_int('holiday_greeting')
music_phase = game_config.get_int("music_phase")
event_archive_phase = game_config.get_int("event_archive_phase")
holiday_greeting = game_config.get_int("holiday_greeting")
enable_net_taisen = False # game_config.get_bool('enable_net_taisen')
navikun_phase = game_config.get_int('navikun_phase')
navikun_phase = game_config.get_int("navikun_phase")
# Event phases
return (

File diff suppressed because it is too large Load Diff

View File

@ -66,64 +66,64 @@ class PopnMusicTuneStreet(PopnMusicBase):
Return all of our front-end modifiably settings.
"""
return {
'ints': [
"ints": [
{
'name': 'Game Phase',
'tip': 'Game unlock phase for all players.',
'category': 'game_config',
'setting': 'game_phase',
'values': {
0: 'NO PHASE',
1: 'SECRET DATA RELEASE',
2: 'MAX: ALL DATA RELEASE',
}
"name": "Game Phase",
"tip": "Game unlock phase for all players.",
"category": "game_config",
"setting": "game_phase",
"values": {
0: "NO PHASE",
1: "SECRET DATA RELEASE",
2: "MAX: ALL DATA RELEASE",
},
},
{
'name': 'Town Mode Phase',
'tip': 'Town mode phase for all players.',
'category': 'game_config',
'setting': 'town_phase',
'values': {
0: 'town mode disabled',
1: 'town phase 1',
2: 'town phase 2',
3: 'Pop\'n Naan Festival',
"name": "Town Mode Phase",
"tip": "Town mode phase for all players.",
"category": "game_config",
"setting": "town_phase",
"values": {
0: "town mode disabled",
1: "town phase 1",
2: "town phase 2",
3: "Pop'n Naan Festival",
# 4 seems to be a continuation of town phase 2. Intentionally leaving it out.
5: 'town phase 3',
6: 'town phase 4',
7: 'Miracle 4 + 1',
5: "town phase 3",
6: "town phase 4",
7: "Miracle 4 + 1",
# 8 seems to be a continuation of town phase 4. Intentionally leaving it out.
9: 'town phase MAX',
10: 'Find your daughter!',
9: "town phase MAX",
10: "Find your daughter!",
# 11 is a continuation of phase MAX after find your daughter, with Tanabata
# bamboo grass added as well.
11: 'town phase MAX+1',
12: 'Peruri-san visits',
11: "town phase MAX+1",
12: "Peruri-san visits",
# 13 is a continuation of phase MAX+1 after peruri-san visits, with Watermelon
# pattern tank added as well.
13: 'town phase MAX+2',
14: 'Find Deuil!',
13: "town phase MAX+2",
14: "Find Deuil!",
# 15 is a continuation of phase MAX+2 after find deuil, with Tsukimi dumplings
# added as well.
15: 'town phase MAX+3',
16: 'Landmark stamp rally',
15: "town phase MAX+3",
16: "Landmark stamp rally",
# 17 is a continuation of MAX+3 after landmark stamp rally ends, but offering
# no additional stuff.
}
},
},
],
'bools': [
"bools": [
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
{
'name': 'Force Customization Unlock',
'tip': 'Force unlock all theme and menu customizations.',
'category': 'game_config',
'setting': 'force_unlock_customizations',
"name": "Force Customization Unlock",
"tip": "Force unlock all theme and menu customizations.",
"category": "game_config",
"setting": "force_unlock_customizations",
},
],
}
@ -166,11 +166,11 @@ class PopnMusicTuneStreet(PopnMusicBase):
self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_FLAG_FULL_COMBO,
self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_FLAG_FULL_COMBO,
self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_FLAG_PERFECT_COMBO,
}[score.data.get_int('medal')]
}[score.data.get_int("medal")]
return (flags << shift) | playedflag
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('playerdata')
root = Node.void("playerdata")
# Format profile
binary_profile = [0] * 2198
@ -178,7 +178,7 @@ class PopnMusicTuneStreet(PopnMusicBase):
# Copy name. We intentionally leave location 12 alone as it is
# the null termination for the name if it happens to be 12
# characters (6 shift-jis kana).
name_binary = profile.get_str('name', 'なし').encode('shift-jis')[0:12]
name_binary = profile.get_str("name", "なし").encode("shift-jis")[0:12]
for name_pos, byte in enumerate(name_binary):
binary_profile[name_pos] = byte
@ -199,32 +199,32 @@ class PopnMusicTuneStreet(PopnMusicBase):
13: 5,
14: 5,
15: 5,
}[profile.get_int('play_mode')]
}[profile.get_int("play_mode")]
# Copy miscelaneous values
binary_profile[15] = profile.get_int('last_play_flag') & 0xFF
binary_profile[16] = profile.get_int('medal_and_friend') & 0xFF
binary_profile[37] = profile.get_int('read_news') & 0xFF
binary_profile[38] = profile.get_int('skin_tex_note') & 0xFF
binary_profile[39] = profile.get_int('skin_tex_cmn') & 0xFF
binary_profile[40] = profile.get_int('skin_sd_bgm') & 0xFF
binary_profile[41] = profile.get_int('skin_sd_se') & 0xFF
binary_profile[44] = profile.get_int('option') & 0xFF
binary_profile[45] = (profile.get_int('option') >> 8) & 0xFF
binary_profile[46] = (profile.get_int('option') >> 16) & 0xFF
binary_profile[47] = (profile.get_int('option') >> 24) & 0xFF
binary_profile[48] = profile.get_int('jubeat_collabo') & 0xFF
binary_profile[49] = (profile.get_int('jubeat_collabo') >> 8) & 0xFF
binary_profile[15] = profile.get_int("last_play_flag") & 0xFF
binary_profile[16] = profile.get_int("medal_and_friend") & 0xFF
binary_profile[37] = profile.get_int("read_news") & 0xFF
binary_profile[38] = profile.get_int("skin_tex_note") & 0xFF
binary_profile[39] = profile.get_int("skin_tex_cmn") & 0xFF
binary_profile[40] = profile.get_int("skin_sd_bgm") & 0xFF
binary_profile[41] = profile.get_int("skin_sd_se") & 0xFF
binary_profile[44] = profile.get_int("option") & 0xFF
binary_profile[45] = (profile.get_int("option") >> 8) & 0xFF
binary_profile[46] = (profile.get_int("option") >> 16) & 0xFF
binary_profile[47] = (profile.get_int("option") >> 24) & 0xFF
binary_profile[48] = profile.get_int("jubeat_collabo") & 0xFF
binary_profile[49] = (profile.get_int("jubeat_collabo") >> 8) & 0xFF
# 52-56 and 56-60 make up two 32 bit colors found in color_3p_flag.
binary_profile[60] = profile.get_int('chara', -1) & 0xFF
binary_profile[61] = (profile.get_int('chara', -1) >> 8) & 0xFF
binary_profile[62] = profile.get_int('music') & 0xFF
binary_profile[63] = (profile.get_int('music') >> 8) & 0xFF
binary_profile[64] = profile.get_int('sheet') & 0xFF
binary_profile[65] = profile.get_int('category') & 0xFF
binary_profile[66] = profile.get_int('norma_point') & 0xFF
binary_profile[67] = (profile.get_int('norma_point') >> 8) & 0xFF
binary_profile[60] = profile.get_int("chara", -1) & 0xFF
binary_profile[61] = (profile.get_int("chara", -1) >> 8) & 0xFF
binary_profile[62] = profile.get_int("music") & 0xFF
binary_profile[63] = (profile.get_int("music") >> 8) & 0xFF
binary_profile[64] = profile.get_int("sheet") & 0xFF
binary_profile[65] = profile.get_int("category") & 0xFF
binary_profile[66] = profile.get_int("norma_point") & 0xFF
binary_profile[67] = (profile.get_int("norma_point") >> 8) & 0xFF
# Format Scores
hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 7) * 17) + 7) / 8)
@ -238,14 +238,18 @@ class PopnMusicTuneStreet(PopnMusicBase):
self.CHART_TYPE_EASY,
]:
continue
if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY:
if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY:
continue
flags = self.__format_flags_for_score(score)
flags_index = score.id * 2
binary_profile[108 + flags_index] = binary_profile[108 + flags_index] | (flags & 0xFF)
binary_profile[109 + flags_index] = binary_profile[109 + flags_index] | ((flags >> 8) & 0xFF)
binary_profile[108 + flags_index] = binary_profile[108 + flags_index] | (
flags & 0xFF
)
binary_profile[109 + flags_index] = binary_profile[109 + flags_index] | (
(flags >> 8) & 0xFF
)
if score.chart in [
self.CHART_TYPE_ENJOY_5_BUTTON,
@ -267,12 +271,23 @@ class PopnMusicTuneStreet(PopnMusicBase):
hiscore_byte_pos = int((hiscore_index * 17) / 8)
hiscore_bit_pos = int((hiscore_index * 17) % 8)
hiscore_value = score.points << hiscore_bit_pos
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF)
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (
hiscore_value & 0xFF
)
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[
hiscore_byte_pos + 1
] | ((hiscore_value >> 8) & 0xFF)
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[
hiscore_byte_pos + 2
] | ((hiscore_value >> 16) & 0xFF)
# Format most played
most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 20)]
most_played = [
x[0]
for x in self.data.local.music.get_most_played(
self.game, self.version, userid, 20
)
]
while len(most_played) < 20:
most_played.append(-1)
profile_pos = 68
@ -291,22 +306,24 @@ class PopnMusicTuneStreet(PopnMusicBase):
# - 10 appears to be purchased BGMs.
# - 11 appears to be purchased sound effects.
binary_town = [0] * 141
town = profile.get_dict('town')
town = profile.get_dict("town")
# Last play flag, so the selection for 5/9/9+cool sticks.
binary_town[140] = town.get_int('play_type')
binary_town[140] = town.get_int("play_type")
# Fill in basic town points, tracked here and returned in basic profile for some reason.
binary_town[0] = town.get_int('points') & 0xFF
binary_town[1] = (town.get_int('points') >> 8) & 0xFF
binary_town[2] = (town.get_int('points') >> 16) & 0xFF
binary_town[3] = (town.get_int('points') >> 24) & 0xFF
binary_town[0] = town.get_int("points") & 0xFF
binary_town[1] = (town.get_int("points") >> 8) & 0xFF
binary_town[2] = (town.get_int("points") >> 16) & 0xFF
binary_town[3] = (town.get_int("points") >> 24) & 0xFF
# Fill in purchase flags (this is for stuff like BGMs, SEs, Pop-kun customizations, etc).
bought_flg = town.get_int_array('bought_flg', 3)
bought_flg = town.get_int_array("bought_flg", 3)
game_config = self.get_game_config()
force_unlock_songs = game_config.get_bool('force_unlock_songs')
force_unlock_customizations = game_config.get_bool('force_unlock_customizations')
force_unlock_songs = game_config.get_bool("force_unlock_songs")
force_unlock_customizations = game_config.get_bool(
"force_unlock_customizations"
)
if force_unlock_songs:
bought_flg[0] = 0xFFFFFFFF
@ -320,7 +337,7 @@ class PopnMusicTuneStreet(PopnMusicBase):
binary_town[off + 3] = (bought_flg[flg] >> 24) & 0xFF
# Fill in build flags (presumably for what parcels of land have been bought and built on).
build_flg = town.get_int_array('build_flg', 8)
build_flg = town.get_int_array("build_flg", 8)
for flg, off in enumerate([16, 20, 24, 28, 32, 36, 40, 44]):
binary_town[off + 0] = build_flg[flg] & 0xFF
binary_town[off + 1] = (build_flg[flg] >> 8) & 0xFF
@ -328,15 +345,37 @@ class PopnMusicTuneStreet(PopnMusicBase):
binary_town[off + 3] = (build_flg[flg] >> 24) & 0xFF
# Fill in character flags (presumably for character location, orientation, stats, etc).
chara_flg = town.get_int_array('chara_flg', 19)
for flg, off in enumerate([48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120]):
chara_flg = town.get_int_array("chara_flg", 19)
for flg, off in enumerate(
[
48,
52,
56,
60,
64,
68,
72,
76,
80,
84,
88,
92,
96,
100,
104,
108,
112,
116,
120,
]
):
binary_town[off + 0] = chara_flg[flg] & 0xFF
binary_town[off + 1] = (chara_flg[flg] >> 8) & 0xFF
binary_town[off + 2] = (chara_flg[flg] >> 16) & 0xFF
binary_town[off + 3] = (chara_flg[flg] >> 24) & 0xFF
# Fill in miscellaneous event flags.
event_flg = town.get_int_array('event_flg', 4)
event_flg = town.get_int_array("event_flg", 4)
for flg, off in enumerate([124, 128, 132, 136]):
binary_town[off + 0] = event_flg[flg] & 0xFF
binary_town[off + 1] = (event_flg[flg] >> 8) & 0xFF
@ -344,58 +383,72 @@ class PopnMusicTuneStreet(PopnMusicBase):
binary_town[off + 3] = (event_flg[flg] >> 24) & 0xFF
# Construct final profile
root.add_child(Node.binary('b', bytes(binary_profile)))
root.add_child(Node.binary('hiscore', bytes(hiscore_array)))
root.add_child(Node.binary('town', bytes(binary_town)))
root.add_child(Node.binary("b", bytes(binary_profile)))
root.add_child(Node.binary("hiscore", bytes(hiscore_array)))
root.add_child(Node.binary("town", bytes(binary_town)))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
# Extract the playmode, important for scores later
playmode = int(request.attribute('play_mode'))
newprofile.replace_int('play_mode', playmode)
playmode = int(request.attribute("play_mode"))
newprofile.replace_int("play_mode", playmode)
# Extract profile options
newprofile.replace_int('chara', int(request.attribute('chara_num')))
if 'option' in request.attributes:
newprofile.replace_int('option', int(request.attribute('option')))
if 'last_play_flag' in request.attributes:
newprofile.replace_int('last_play_flag', int(request.attribute('last_play_flag')))
if 'medal_and_friend' in request.attributes:
newprofile.replace_int('medal_and_friend', int(request.attribute('medal_and_friend')))
if 'music_num' in request.attributes:
newprofile.replace_int('music', int(request.attribute('music_num')))
if 'sheet_num' in request.attributes:
newprofile.replace_int('sheet', int(request.attribute('sheet_num')))
if 'category_num' in request.attributes:
newprofile.replace_int('category', int(request.attribute('category_num')))
if 'read_news_no_max' in request.attributes:
newprofile.replace_int('read_news', int(request.attribute('read_news_no_max')))
if 'jubeat_collabo' in request.attributes:
newprofile.replace_int('jubeat_collabo', int(request.attribute('jubeat_collabo')))
if 'norma_point' in request.attributes:
newprofile.replace_int('norma_point', int(request.attribute('norma_point')))
if 'skin_tex_note' in request.attributes:
newprofile.replace_int('skin_tex_note', int(request.attribute('skin_tex_note')))
if 'skin_tex_cmn' in request.attributes:
newprofile.replace_int('skin_tex_cmn', int(request.attribute('skin_tex_cmn')))
if 'skin_sd_bgm' in request.attributes:
newprofile.replace_int('skin_sd_bgm', int(request.attribute('skin_sd_bgm')))
if 'skin_sd_se' in request.attributes:
newprofile.replace_int('skin_sd_se', int(request.attribute('skin_sd_se')))
newprofile.replace_int("chara", int(request.attribute("chara_num")))
if "option" in request.attributes:
newprofile.replace_int("option", int(request.attribute("option")))
if "last_play_flag" in request.attributes:
newprofile.replace_int(
"last_play_flag", int(request.attribute("last_play_flag"))
)
if "medal_and_friend" in request.attributes:
newprofile.replace_int(
"medal_and_friend", int(request.attribute("medal_and_friend"))
)
if "music_num" in request.attributes:
newprofile.replace_int("music", int(request.attribute("music_num")))
if "sheet_num" in request.attributes:
newprofile.replace_int("sheet", int(request.attribute("sheet_num")))
if "category_num" in request.attributes:
newprofile.replace_int("category", int(request.attribute("category_num")))
if "read_news_no_max" in request.attributes:
newprofile.replace_int(
"read_news", int(request.attribute("read_news_no_max"))
)
if "jubeat_collabo" in request.attributes:
newprofile.replace_int(
"jubeat_collabo", int(request.attribute("jubeat_collabo"))
)
if "norma_point" in request.attributes:
newprofile.replace_int("norma_point", int(request.attribute("norma_point")))
if "skin_tex_note" in request.attributes:
newprofile.replace_int(
"skin_tex_note", int(request.attribute("skin_tex_note"))
)
if "skin_tex_cmn" in request.attributes:
newprofile.replace_int(
"skin_tex_cmn", int(request.attribute("skin_tex_cmn"))
)
if "skin_sd_bgm" in request.attributes:
newprofile.replace_int("skin_sd_bgm", int(request.attribute("skin_sd_bgm")))
if "skin_sd_se" in request.attributes:
newprofile.replace_int("skin_sd_se", int(request.attribute("skin_sd_se")))
# Keep track of play statistics
self.update_play_statistics(userid)
# Extract scores
for node in request.children:
if node.name == 'music':
songid = int(node.attribute('music_num'))
chart = int(node.attribute('sheet_num'))
points = int(node.attribute('score'))
data = int(node.attribute('data'))
if node.name == "music":
songid = int(node.attribute("music_num"))
chart = int(node.attribute("sheet_num"))
points = int(node.attribute("score"))
data = int(node.attribute("data"))
# We never save battle scores
if chart in [
@ -405,7 +458,10 @@ class PopnMusicTuneStreet(PopnMusicBase):
continue
# Arrange order to be compatible with future mixes
if playmode in {self.GAME_PLAY_MODE_CHO_CHALLENGE, self.GAME_PLAY_MODE_TOWN_CHO_CHALLENGE}:
if playmode in {
self.GAME_PLAY_MODE_CHO_CHALLENGE,
self.GAME_PLAY_MODE_TOWN_CHO_CHALLENGE,
}:
if chart in [
self.GAME_CHART_TYPE_5_BUTTON,
self.GAME_CHART_TYPE_ENJOY_5_BUTTON,
@ -463,26 +519,32 @@ class PopnMusicTuneStreet(PopnMusicBase):
self.update_score(userid, songid, chart, points, medal)
# Update town mode data.
town = newprofile.get_dict('town')
town = newprofile.get_dict("town")
# Basic stuff that's in the base node for no reason?
if 'tp' in request.attributes:
town.replace_int('points', int(request.attribute('tp')))
if "tp" in request.attributes:
town.replace_int("points", int(request.attribute("tp")))
# Stuff that is in the town node
townnode = request.child('town')
townnode = request.child("town")
if townnode is not None:
if 'play_type' in townnode.attributes:
town.replace_int('play_type', int(townnode.attribute('play_type')))
if 'base' in townnode.attributes:
town.replace_int_array('base', 4, [int(x) for x in townnode.attribute('base').split(',')])
if 'bought_flg' in townnode.attributes:
bought_array = [int(x) for x in townnode.attribute('bought_flg').split(',')]
if "play_type" in townnode.attributes:
town.replace_int("play_type", int(townnode.attribute("play_type")))
if "base" in townnode.attributes:
town.replace_int_array(
"base", 4, [int(x) for x in townnode.attribute("base").split(",")]
)
if "bought_flg" in townnode.attributes:
bought_array = [
int(x) for x in townnode.attribute("bought_flg").split(",")
]
if len(bought_array) == 3:
game_config = self.get_game_config()
force_unlock_songs = game_config.get_bool('force_unlock_songs')
force_unlock_customizations = game_config.get_bool('force_unlock_customizations')
old_bought_array = town.get_int_array('bought_flg', 3)
force_unlock_songs = game_config.get_bool("force_unlock_songs")
force_unlock_customizations = game_config.get_bool(
"force_unlock_customizations"
)
old_bought_array = town.get_int_array("bought_flg", 3)
if force_unlock_songs:
# Don't save force unlocked flags, it'll clobber the profile.
@ -491,66 +553,91 @@ class PopnMusicTuneStreet(PopnMusicBase):
# Don't save force unlocked flags, it'll clobber the profile.
bought_array[1] = old_bought_array[1]
town.replace_int_array('bought_flg', 3, bought_array)
if 'build_flg' in townnode.attributes:
town.replace_int_array('build_flg', 8, [int(x) for x in townnode.attribute('build_flg').split(',')])
if 'chara_flg' in townnode.attributes:
town.replace_int_array('chara_flg', 19, [int(x) for x in townnode.attribute('chara_flg').split(',')])
if 'event_flg' in townnode.attributes:
town.replace_int_array('event_flg', 4, [int(x) for x in townnode.attribute('event_flg').split(',')])
town.replace_int_array("bought_flg", 3, bought_array)
if "build_flg" in townnode.attributes:
town.replace_int_array(
"build_flg",
8,
[int(x) for x in townnode.attribute("build_flg").split(",")],
)
if "chara_flg" in townnode.attributes:
town.replace_int_array(
"chara_flg",
19,
[int(x) for x in townnode.attribute("chara_flg").split(",")],
)
if "event_flg" in townnode.attributes:
town.replace_int_array(
"event_flg",
4,
[int(x) for x in townnode.attribute("event_flg").split(",")],
)
for bid in range(8):
if f'building_{bid}' in townnode.attributes:
town.replace_int_array(f'building_{bid}', 8, [int(x) for x in townnode.attribute(f'building_{bid}').split(',')])
if f"building_{bid}" in townnode.attributes:
town.replace_int_array(
f"building_{bid}",
8,
[
int(x)
for x in townnode.attribute(f"building_{bid}").split(",")
],
)
newprofile.replace_dict('town', town)
newprofile.replace_dict("town", town)
return newprofile
def handle_game_get_request(self, request: Node) -> Node:
game_config = self.get_game_config()
game_phase = game_config.get_int('game_phase')
town_phase = game_config.get_int('town_phase')
game_phase = game_config.get_int("game_phase")
town_phase = game_config.get_int("town_phase")
root = Node.void('game')
root.set_attribute('game_phase', str(game_phase)) # Phase unlocks, for song availability.
root.set_attribute('boss_battle_point', '1')
root.set_attribute('boss_diff', '100,100,100,100,100,100,100,100,100,100')
root.set_attribute('card_phase', '3')
root.set_attribute('event_phase', str(town_phase)) # Town mode, for the main event.
root.set_attribute('gfdm_phase', '2')
root.set_attribute('ir_phase', '14')
root.set_attribute('jubeat_phase', '2')
root.set_attribute('local_matching_enable', '1')
root.set_attribute('matching_sec', '120')
root.set_attribute('netvs_phase', '0') # Net taisen mode phase, maximum 18 (no lobby support).
root = Node.void("game")
root.set_attribute(
"game_phase", str(game_phase)
) # Phase unlocks, for song availability.
root.set_attribute("boss_battle_point", "1")
root.set_attribute("boss_diff", "100,100,100,100,100,100,100,100,100,100")
root.set_attribute("card_phase", "3")
root.set_attribute(
"event_phase", str(town_phase)
) # Town mode, for the main event.
root.set_attribute("gfdm_phase", "2")
root.set_attribute("ir_phase", "14")
root.set_attribute("jubeat_phase", "2")
root.set_attribute("local_matching_enable", "1")
root.set_attribute("matching_sec", "120")
root.set_attribute(
"netvs_phase", "0"
) # Net taisen mode phase, maximum 18 (no lobby support).
return root
def handle_game_active_request(self, request: Node) -> Node:
# Update the name of this cab for admin purposes
self.update_machine_name(request.attribute('shop_name'))
return Node.void('game')
self.update_machine_name(request.attribute("shop_name"))
return Node.void("game")
def handle_playerdata_expire_request(self, request: Node) -> Node:
return Node.void('playerdata')
return Node.void("playerdata")
def handle_playerdata_logout_request(self, request: Node) -> Node:
return Node.void('playerdata')
return Node.void("playerdata")
def handle_playerdata_get_request(self, request: Node) -> Node:
modelstring = request.attribute('model')
refid = request.attribute('ref_id')
modelstring = request.attribute("model")
refid = request.attribute("ref_id")
root = self.get_profile_by_refid(
refid,
self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY,
)
if root is None:
root = Node.void('playerdata')
root.set_attribute('status', str(Status.NO_PROFILE))
root = Node.void("playerdata")
root.set_attribute("status", str(Status.NO_PROFILE))
return root
def handle_playerdata_town_request(self, request: Node) -> Node:
refid = request.attribute('ref_id')
root = Node.void('playerdata')
refid = request.attribute("ref_id")
root = Node.void("playerdata")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is None:
@ -560,11 +647,11 @@ class PopnMusicTuneStreet(PopnMusicBase):
if profile is None:
return root
town = profile.get_dict('town')
town = profile.get_dict("town")
residence = Node.void('residence')
residence = Node.void("residence")
root.add_child(residence)
residence.set_attribute('id', str(town.get_int('residence')))
residence.set_attribute("id", str(town.get_int("residence")))
# It appears there can be up to 9 map nodes, not sure why. I'm only returning the
# first one. Perhaps if there's multiple towns, the residence ID lets you choose
@ -572,35 +659,35 @@ class PopnMusicTuneStreet(PopnMusicBase):
mapdata = [0] * 180
# Map over progress for base and buildings. Positions 173-176 are for base flags.
base = town.get_int_array('base', 4)
base = town.get_int_array("base", 4)
for i in range(4):
mapdata[173 + i] = base[i]
# Positions 42-105 are for building flags.
for bid, start in enumerate([42, 50, 58, 66, 74, 82, 90, 98]):
building = town.get_int_array(f'building_{bid}', 8)
building = town.get_int_array(f"building_{bid}", 8)
for i in range(8):
mapdata[start + i] = building[i]
mapnode = Node.binary('map', bytes(mapdata))
mapnode = Node.binary("map", bytes(mapdata))
root.add_child(mapnode)
mapnode.set_attribute('residence', '0')
mapnode.set_attribute("residence", "0")
return root
def handle_playerdata_new_request(self, request: Node) -> Node:
refid = request.attribute('ref_id')
name = request.attribute('name')
refid = request.attribute("ref_id")
name = request.attribute("name")
root = self.new_profile_by_refid(refid, name)
if root is None:
root = Node.void('playerdata')
root.set_attribute('status', str(Status.NO_PROFILE))
root = Node.void("playerdata")
root.set_attribute("status", str(Status.NO_PROFILE))
return root
def handle_playerdata_set_request(self, request: Node) -> Node:
refid = request.attribute('ref_id')
refid = request.attribute("ref_id")
root = Node.void('playerdata')
root = Node.void("playerdata")
if refid is None:
return root
@ -608,7 +695,9 @@ class PopnMusicTuneStreet(PopnMusicBase):
if userid is None:
return root
oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0)
oldprofile = self.get_profile(userid) or Profile(
self.game, self.version, refid, 0
)
newprofile = self.unformat_profile(userid, request, oldprofile)
if newprofile is not None:
@ -618,4 +707,4 @@ class PopnMusicTuneStreet(PopnMusicBase):
def handle_lobby_requests(self, request: Node) -> Node:
# Stub out the entire lobby service
return Node.void('lobby')
return Node.void("lobby")

View File

@ -36,76 +36,76 @@ class PopnMusicUsaNeko(PopnMusicModernBase):
Return all of our front-end modifiably settings.
"""
return {
'ints': [
"ints": [
{
'name': 'Music Open Phase',
'tip': 'Default music phase for all players.',
'category': 'game_config',
'setting': 'music_phase',
'values': {
0: 'No music unlocks',
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 MAX',
}
"name": "Music Open Phase",
"tip": "Default music phase for all players.",
"category": "game_config",
"setting": "music_phase",
"values": {
0: "No music unlocks",
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 MAX",
},
},
{
'name': 'NAVI-Kun Event Phase',
'tip': 'NAVI-Kun event phase for all players.',
'category': 'game_config',
'setting': 'navikun_phase',
'values': {
0: 'Phase 1',
1: 'Phase 2',
2: 'Phase 3',
3: 'Phase 4',
4: 'Phase 5',
5: 'Phase 6',
6: 'Phase 7',
7: 'Phase 8',
8: 'Phase 9',
9: 'Phase 10',
10: 'Phase 11',
11: 'Phase 12',
12: 'Phase 13',
13: 'Phase 14',
14: 'Phase 15',
15: 'Phase MAX',
"name": "NAVI-Kun Event Phase",
"tip": "NAVI-Kun event phase for all players.",
"category": "game_config",
"setting": "navikun_phase",
"values": {
0: "Phase 1",
1: "Phase 2",
2: "Phase 3",
3: "Phase 4",
4: "Phase 5",
5: "Phase 6",
6: "Phase 7",
7: "Phase 8",
8: "Phase 9",
9: "Phase 10",
10: "Phase 11",
11: "Phase 12",
12: "Phase 13",
13: "Phase 14",
14: "Phase 15",
15: "Phase MAX",
},
},
{
# 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!',
}
"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!",
},
},
{
'name': 'Active Event',
'tip': 'Active event for all players.',
'category': 'game_config',
'setting': 'active_event',
'values': {
0: 'No event',
1: 'NAVI-Kun event',
2: 'Daily Mission event',
"name": "Active Event",
"tip": "Active event for all players.",
"category": "game_config",
"setting": "active_event",
"values": {
0: "No event",
1: "NAVI-Kun event",
2: "Daily Mission event",
},
},
],
'bools': [
"bools": [
# We don't currently support lobbies or anything, so this is commented out until
# somebody gets around to implementing it.
# {
@ -115,20 +115,20 @@ class PopnMusicUsaNeko(PopnMusicModernBase):
# 'setting': 'enable_net_taisen',
# },
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"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')
active_event = game_config.get_int('active_event')
navikun_phase = game_config.get_int('navikun_phase')
music_phase = game_config.get_int("music_phase")
holiday_greeting = game_config.get_int("holiday_greeting")
active_event = game_config.get_int("active_event")
navikun_phase = game_config.get_int("navikun_phase")
enable_net_taisen = False # game_config.get_bool('enable_net_taisen')
navikun_enabled = active_event == 1

View File

@ -26,23 +26,31 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
CLEAR_TYPE_NO_PLAY: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY
CLEAR_TYPE_FAILED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED
CLEAR_TYPE_CLEARED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED
CLEAR_TYPE_HARD_CLEARED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED
CLEAR_TYPE_S_HARD_CLEARED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED
CLEAR_TYPE_HARD_CLEARED: Final[
int
] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED
CLEAR_TYPE_S_HARD_CLEARED: Final[
int
] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED
# Combo types, as saved/loaded from the DB
COMBO_TYPE_NONE: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE
COMBO_TYPE_ALMOST_COMBO: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO
COMBO_TYPE_ALMOST_COMBO: Final[
int
] = DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO
COMBO_TYPE_FULL_COMBO: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO
COMBO_TYPE_FULL_COMBO_ALL_JUST: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST
COMBO_TYPE_FULL_COMBO_ALL_JUST: Final[
int
] = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST
# Return the local2 and lobby2 service so that matching will work on newer
# Reflec Beat games.
extra_services: List[str] = [
'local2',
'lobby2',
"local2",
"lobby2",
]
def previous_version(self) -> Optional['ReflecBeatBase']:
def previous_version(self) -> Optional["ReflecBeatBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -54,9 +62,11 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('pc')
return Node.void("pc")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -84,7 +94,9 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
return self.format_profile(userid, profile)
def put_profile_by_refid(self, refid: Optional[str], request: Node) -> Optional[Profile]:
def put_profile_by_refid(
self, refid: Optional[str], request: Node
) -> Optional[Profile]:
"""
Given a RefID and a request node, unformat the profile and save it.
"""
@ -121,10 +133,10 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
clear_type: int,
combo_type: int,
miss_count: int,
combo: Optional[int]=None,
stats: Optional[Dict[str, int]]=None,
param: Optional[int]=None,
kflag: Optional[int]=None,
combo: Optional[int] = None,
stats: Optional[Dict[str, int]] = None,
param: Optional[int] = None,
kflag: Optional[int] = None,
) -> None:
"""
Given various pieces of a score, update the user's high score and score
@ -176,53 +188,66 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
scoredata = oldscore.data
# Update the last played time
scoredata.replace_int('last_played_time', now)
scoredata.replace_int("last_played_time", now)
# Replace clear type with highest value and timestamps
if clear_type >= scoredata.get_int('clear_type'):
scoredata.replace_int('clear_type', max(scoredata.get_int('clear_type'), clear_type))
scoredata.replace_int('best_clear_type_time', now)
history.replace_int('clear_type', clear_type)
if clear_type >= scoredata.get_int("clear_type"):
scoredata.replace_int(
"clear_type", max(scoredata.get_int("clear_type"), clear_type)
)
scoredata.replace_int("best_clear_type_time", now)
history.replace_int("clear_type", clear_type)
# Replace combo type with highest value and timestamps
if combo_type >= scoredata.get_int('combo_type'):
scoredata.replace_int('combo_type', max(scoredata.get_int('combo_type'), combo_type))
scoredata.replace_int('best_clear_type_time', now)
history.replace_int('combo_type', combo_type)
if combo_type >= scoredata.get_int("combo_type"):
scoredata.replace_int(
"combo_type", max(scoredata.get_int("combo_type"), combo_type)
)
scoredata.replace_int("best_clear_type_time", now)
history.replace_int("combo_type", combo_type)
# Update the combo for this song
if combo is not None:
scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
history.replace_int('combo', combo)
scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo))
history.replace_int("combo", combo)
# Update the param for this song
if param is not None:
scoredata.replace_int('param', max(scoredata.get_int('param'), param))
history.replace_int('param', param)
scoredata.replace_int("param", max(scoredata.get_int("param"), param))
history.replace_int("param", param)
# Update the kflag for this song
if kflag is not None:
scoredata.replace_int('kflag', max(scoredata.get_int('kflag'), kflag))
history.replace_int('kflag', kflag)
scoredata.replace_int("kflag", max(scoredata.get_int("kflag"), kflag))
history.replace_int("kflag", kflag)
# Update win/lost/draw stats for this song
if stats is not None:
scoredata.replace_dict('stats', stats)
history.replace_dict('stats', stats)
scoredata.replace_dict("stats", stats)
history.replace_dict("stats", stats)
# Update the achievement rate with timestamps
if achievement_rate >= scoredata.get_int('achievement_rate'):
scoredata.replace_int('achievement_rate', max(scoredata.get_int('achievement_rate'), achievement_rate))
scoredata.replace_int('best_achievement_rate_time', now)
history.replace_int('achievement_rate', achievement_rate)
if achievement_rate >= scoredata.get_int("achievement_rate"):
scoredata.replace_int(
"achievement_rate",
max(scoredata.get_int("achievement_rate"), achievement_rate),
)
scoredata.replace_int("best_achievement_rate_time", now)
history.replace_int("achievement_rate", achievement_rate)
# Update the miss count with timestamps, either if it was lowered, or if the old value was blank.
# If the new value is -1 (we didn't get a miss count this time), never update the old value.
if miss_count >= 0:
if miss_count <= scoredata.get_int('miss_count', 999999) or scoredata.get_int('miss_count') == -1:
scoredata.replace_int('miss_count', min(scoredata.get_int('miss_count', 999999), miss_count))
scoredata.replace_int('best_miss_count_time', now)
history.replace_int('miss_count', miss_count)
if (
miss_count <= scoredata.get_int("miss_count", 999999)
or scoredata.get_int("miss_count") == -1
):
scoredata.replace_int(
"miss_count",
min(scoredata.get_int("miss_count", 999999), miss_count),
)
scoredata.replace_int("best_miss_count_time", now)
history.replace_int("miss_count", miss_count)
# Look up where this score was earned
lid = self.get_machine_id()

File diff suppressed because it is too large Load Diff

View File

@ -24,12 +24,17 @@ class ReflecBeatFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['KBR', 'LBR', 'MBR']:
for gamecode in ["KBR", "LBR", "MBR"]:
Base.register(gamecode, ReflecBeatFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
def version_from_date(date: int) -> Optional[int]:
if date < 2014060400:
return VersionConstants.REFLEC_BEAT_COLETTE
@ -43,16 +48,16 @@ class ReflecBeatFactory(Factory):
return VersionConstants.REFLEC_BEAT_REFLESIA
return None
if model.gamecode == 'KBR':
if model.gamecode == "KBR":
return ReflecBeat(data, config, model)
if model.gamecode == 'LBR':
if model.gamecode == "LBR":
return ReflecBeatLimelight(data, config, model)
if model.gamecode == 'MBR':
if model.gamecode == "MBR":
if model.version is None:
if parentmodel is None:
return None
if parentmodel.gamecode not in ['KBR', 'LBR', 'MBR']:
if parentmodel.gamecode not in ["KBR", "LBR", "MBR"]:
return None
parentversion = version_from_date(parentmodel.version)
if parentversion == VersionConstants.REFLEC_BEAT_COLETTE:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -27,15 +27,15 @@ class ReflecBeat(ReflecBeatBase):
Return all of our front-end modifiably settings.
"""
return {
'bools': [
"bools": [
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
],
'ints': [],
"ints": [],
}
def __db_to_game_clear_type(self, db_clear_type: int, db_combo_type: int) -> int:
@ -59,10 +59,12 @@ class ReflecBeat(ReflecBeatBase):
]:
return self.GAME_CLEAR_TYPE_FULL_COMBO
raise Exception(f'Invalid db_combo_type {db_combo_type}')
raise Exception(f'Invalid db_clear_type {db_clear_type}')
raise Exception(f"Invalid db_combo_type {db_combo_type}")
raise Exception(f"Invalid db_clear_type {db_clear_type}")
def __game_to_db_clear_type(self, game_clear_type: int, game_achievement_rate: int) -> Tuple[int, int]:
def __game_to_db_clear_type(
self, game_clear_type: int, game_achievement_rate: int
) -> Tuple[int, int]:
if game_clear_type == self.GAME_CLEAR_TYPE_NO_PLAY:
return (self.CLEAR_TYPE_NO_PLAY, self.COMBO_TYPE_NONE)
if game_clear_type == self.GAME_CLEAR_TYPE_PLAYED:
@ -73,67 +75,69 @@ class ReflecBeat(ReflecBeatBase):
if game_clear_type == self.GAME_CLEAR_TYPE_FULL_COMBO:
return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_FULL_COMBO)
raise Exception(f'Invalid game_clear_type {game_clear_type}')
raise Exception(f"Invalid game_clear_type {game_clear_type}")
def handle_log_pcb_status_request(self, request: Node) -> Node:
return Node.void('log')
return Node.void("log")
def handle_log_opsetting_request(self, request: Node) -> Node:
return Node.void('log')
return Node.void("log")
def handle_log_play_request(self, request: Node) -> Node:
return Node.void('log')
return Node.void("log")
def handle_pcbinfo_get_request(self, request: Node) -> Node:
shop_id = ID.parse_machine_id(request.child_value('lid'))
shop_id = ID.parse_machine_id(request.child_value("lid"))
machine = self.get_machine_by_id(shop_id)
if machine is not None:
machine_name = machine.name
close = machine.data.get_bool('close')
hour = machine.data.get_int('hour')
minute = machine.data.get_int('minute')
pref = machine.data.get_int('pref', self.get_machine_region())
close = machine.data.get_bool("close")
hour = machine.data.get_int("hour")
minute = machine.data.get_int("minute")
pref = machine.data.get_int("pref", self.get_machine_region())
else:
machine_name = ''
machine_name = ""
close = False
hour = 0
minute = 0
pref = self.get_machine_region()
root = Node.void('pcbinfo')
info = Node.void('info')
root = Node.void("pcbinfo")
info = Node.void("info")
root.add_child(info)
info.add_child(Node.string('name', machine_name))
info.add_child(Node.s16('pref', pref))
info.add_child(Node.bool('close', close))
info.add_child(Node.u8('hour', hour))
info.add_child(Node.u8('min', minute))
info.add_child(Node.string("name", machine_name))
info.add_child(Node.s16("pref", pref))
info.add_child(Node.bool("close", close))
info.add_child(Node.u8("hour", hour))
info.add_child(Node.u8("min", minute))
return root
def handle_pcbinfo_set_request(self, request: Node) -> Node:
self.update_machine_name(request.child_value('info/name'))
self.update_machine_data({
'close': request.child_value('info/close'),
'hour': request.child_value('info/hour'),
'minute': request.child_value('info/min'),
'pref': request.child_value('info/pref'),
})
return Node.void('pcbinfo')
self.update_machine_name(request.child_value("info/name"))
self.update_machine_data(
{
"close": request.child_value("info/close"),
"hour": request.child_value("info/hour"),
"minute": request.child_value("info/min"),
"pref": request.child_value("info/pref"),
}
)
return Node.void("pcbinfo")
def __add_event_info(self, request: Node) -> None:
events: Dict[int, int] = {}
for (_eventid, _phase) in events.items():
data = Node.void('data')
data = Node.void("data")
request.add_child(data)
data.add_child(Node.s32('type', -1))
data.add_child(Node.s32('value', -1))
data.add_child(Node.s32("type", -1))
data.add_child(Node.s32("value", -1))
def handle_sysinfo_get_request(self, request: Node) -> Node:
root = Node.void('sysinfo')
trd = Node.void('trd')
root = Node.void("sysinfo")
trd = Node.void("trd")
root.add_child(trd)
# Add event info
@ -142,16 +146,16 @@ class ReflecBeat(ReflecBeatBase):
return root
def handle_sysinfo_fan_request(self, request: Node) -> Node:
sysinfo = Node.void('sysinfo')
sysinfo.add_child(Node.u8('pref', self.get_machine_region()))
sysinfo.add_child(Node.string('lid', request.child_value('lid')))
sysinfo = Node.void("sysinfo")
sysinfo.add_child(Node.u8("pref", self.get_machine_region()))
sysinfo.add_child(Node.string("lid", request.child_value("lid")))
return sysinfo
def handle_lobby_entry_request(self, request: Node) -> Node:
root = Node.void('lobby')
root = Node.void("lobby")
# Create a lobby entry for this user
extid = request.child_value('e/uid')
extid = request.child_value("e/uid")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
profile = self.get_profile(userid)
@ -160,49 +164,49 @@ class ReflecBeat(ReflecBeatBase):
self.version,
userid,
{
'mid': request.child_value('e/mid'),
'ng': request.child_value('e/ng'),
'lid': request.child_value('e/lid'),
'sn': request.child_value('e/sn'),
'pref': request.child_value('e/pref'),
'ga': request.child_value('e/ga'),
'gp': request.child_value('e/gp'),
'la': request.child_value('e/la'),
}
"mid": request.child_value("e/mid"),
"ng": request.child_value("e/ng"),
"lid": request.child_value("e/lid"),
"sn": request.child_value("e/sn"),
"pref": request.child_value("e/pref"),
"ga": request.child_value("e/ga"),
"gp": request.child_value("e/gp"),
"la": request.child_value("e/la"),
},
)
lobby = self.data.local.lobby.get_lobby(
self.game,
self.version,
userid,
)
root.add_child(Node.s32('eid', lobby.get_int('id')))
e = Node.void('e')
root.add_child(Node.s32("eid", lobby.get_int("id")))
e = Node.void("e")
root.add_child(e)
e.add_child(Node.s32('eid', lobby.get_int('id')))
e.add_child(Node.u16('mid', lobby.get_int('mid')))
e.add_child(Node.u8('ng', lobby.get_int('ng')))
e.add_child(Node.s32('uid', profile.extid))
e.add_child(Node.string('pn', profile.get_str('name')))
e.add_child(Node.s32('exp', profile.get_int('exp')))
e.add_child(Node.u8('mg', profile.get_int('mg')))
e.add_child(Node.s32('tid', lobby.get_int('tid')))
e.add_child(Node.string('tn', lobby.get_str('tn')))
e.add_child(Node.string('lid', lobby.get_str('lid')))
e.add_child(Node.string('sn', lobby.get_str('sn')))
e.add_child(Node.u8('pref', lobby.get_int('pref')))
e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4)))
e.add_child(Node.u16('gp', lobby.get_int('gp')))
e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4)))
e.add_child(Node.s32("eid", lobby.get_int("id")))
e.add_child(Node.u16("mid", lobby.get_int("mid")))
e.add_child(Node.u8("ng", lobby.get_int("ng")))
e.add_child(Node.s32("uid", profile.extid))
e.add_child(Node.string("pn", profile.get_str("name")))
e.add_child(Node.s32("exp", profile.get_int("exp")))
e.add_child(Node.u8("mg", profile.get_int("mg")))
e.add_child(Node.s32("tid", lobby.get_int("tid")))
e.add_child(Node.string("tn", lobby.get_str("tn")))
e.add_child(Node.string("lid", lobby.get_str("lid")))
e.add_child(Node.string("sn", lobby.get_str("sn")))
e.add_child(Node.u8("pref", lobby.get_int("pref")))
e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4)))
e.add_child(Node.u16("gp", lobby.get_int("gp")))
e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4)))
return root
def handle_lobby_read_request(self, request: Node) -> Node:
root = Node.void('lobby')
root = Node.void("lobby")
# Look up all lobbies matching the criteria specified
mg = request.child_value('m_grade') # noqa: F841
extid = request.child_value('uid')
limit = request.child_value('max')
mg = request.child_value("m_grade") # noqa: F841
extid = request.child_value("uid")
limit = request.child_value("max")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version)
@ -219,47 +223,44 @@ class ReflecBeat(ReflecBeatBase):
# No profile info, don't return this lobby
continue
e = Node.void('e')
e = Node.void("e")
root.add_child(e)
e.add_child(Node.s32('eid', lobby.get_int('id')))
e.add_child(Node.u16('mid', lobby.get_int('mid')))
e.add_child(Node.u8('ng', lobby.get_int('ng')))
e.add_child(Node.s32('uid', profile.extid))
e.add_child(Node.string('pn', profile.get_str('name')))
e.add_child(Node.s32('exp', profile.get_int('exp')))
e.add_child(Node.u8('mg', profile.get_int('mg')))
e.add_child(Node.s32('tid', lobby.get_int('tid')))
e.add_child(Node.string('tn', lobby.get_str('tn')))
e.add_child(Node.string('lid', lobby.get_str('lid')))
e.add_child(Node.string('sn', lobby.get_str('sn')))
e.add_child(Node.u8('pref', lobby.get_int('pref')))
e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4)))
e.add_child(Node.u16('gp', lobby.get_int('gp')))
e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4)))
e.add_child(Node.s32("eid", lobby.get_int("id")))
e.add_child(Node.u16("mid", lobby.get_int("mid")))
e.add_child(Node.u8("ng", lobby.get_int("ng")))
e.add_child(Node.s32("uid", profile.extid))
e.add_child(Node.string("pn", profile.get_str("name")))
e.add_child(Node.s32("exp", profile.get_int("exp")))
e.add_child(Node.u8("mg", profile.get_int("mg")))
e.add_child(Node.s32("tid", lobby.get_int("tid")))
e.add_child(Node.string("tn", lobby.get_str("tn")))
e.add_child(Node.string("lid", lobby.get_str("lid")))
e.add_child(Node.string("sn", lobby.get_str("sn")))
e.add_child(Node.u8("pref", lobby.get_int("pref")))
e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4)))
e.add_child(Node.u16("gp", lobby.get_int("gp")))
e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4)))
limit = limit - 1
return root
def handle_lobby_delete_request(self, request: Node) -> Node:
eid = request.child_value('eid')
eid = request.child_value("eid")
self.data.local.lobby.destroy_lobby(eid)
return Node.void('lobby')
return Node.void("lobby")
def handle_player_start_request(self, request: Node) -> Node:
# Add a dummy entry into the lobby setup so we can clean up on end play
refid = request.child_value('rid')
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
self.data.local.lobby.put_play_session_info(
self.game,
self.version,
userid,
{}
self.game, self.version, userid, {}
)
root = Node.void('player')
root.add_child(Node.bool('is_suc', True))
root = Node.void("player")
root.add_child(Node.bool("is_suc", True))
# Add event info
self.__add_event_info(root)
@ -267,11 +268,11 @@ class ReflecBeat(ReflecBeatBase):
return root
def handle_player_delete_request(self, request: Node) -> Node:
return Node.void('player')
return Node.void("player")
def handle_player_end_request(self, request: Node) -> Node:
# Destroy play session based on info from the request
refid = request.child_value('rid')
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
# Kill any lingering lobbies by this user
@ -281,159 +282,182 @@ class ReflecBeat(ReflecBeatBase):
userid,
)
if lobby is not None:
self.data.local.lobby.destroy_lobby(lobby.get_int('id'))
self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid)
self.data.local.lobby.destroy_lobby(lobby.get_int("id"))
self.data.local.lobby.destroy_play_session_info(
self.game, self.version, userid
)
return Node.void('player')
return Node.void("player")
def handle_player_read_request(self, request: Node) -> Node:
refid = request.child_value('rid')
refid = request.child_value("rid")
profile = self.get_profile_by_refid(refid)
if profile:
return profile
return Node.void('player')
return Node.void("player")
def handle_player_write_request(self, request: Node) -> Node:
refid = request.child_value('rid')
refid = request.child_value("rid")
profile = self.put_profile_by_refid(refid, request)
root = Node.void('player')
root = Node.void("player")
if profile is None:
root.add_child(Node.s32('uid', 0))
root.add_child(Node.s32("uid", 0))
else:
root.add_child(Node.s32('uid', profile.extid))
root.add_child(Node.s32('time', Time.now()))
root.add_child(Node.s32("uid", profile.extid))
root.add_child(Node.s32("time", Time.now()))
return root
def format_profile(self, userid: UserID, profile: Profile) -> Node:
statistics = self.get_play_statistics(userid)
game_config = self.get_game_config()
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
achievements = self.data.local.user.get_achievements(
self.game, self.version, userid
)
scores = self.data.remote.music.get_scores(self.game, self.version, userid)
root = Node.void('player')
pdata = Node.void('pdata')
root = Node.void("player")
pdata = Node.void("pdata")
root.add_child(pdata)
base = Node.void('base')
base = Node.void("base")
pdata.add_child(base)
base.add_child(Node.s32('uid', profile.extid))
base.add_child(Node.string('name', profile.get_str('name')))
base.add_child(Node.s16('lv', profile.get_int('lvl')))
base.add_child(Node.s32('exp', profile.get_int('exp')))
base.add_child(Node.s16('mg', profile.get_int('mg')))
base.add_child(Node.s16('ap', profile.get_int('ap')))
base.add_child(Node.s32('flag', profile.get_int('flag')))
base.add_child(Node.s32("uid", profile.extid))
base.add_child(Node.string("name", profile.get_str("name")))
base.add_child(Node.s16("lv", profile.get_int("lvl")))
base.add_child(Node.s32("exp", profile.get_int("exp")))
base.add_child(Node.s16("mg", profile.get_int("mg")))
base.add_child(Node.s16("ap", profile.get_int("ap")))
base.add_child(Node.s32("flag", profile.get_int("flag")))
con = Node.void('con')
con = Node.void("con")
pdata.add_child(con)
con.add_child(Node.s32('day', statistics.today_plays))
con.add_child(Node.s32('cnt', statistics.total_plays))
con.add_child(Node.s32('last', statistics.last_play_timestamp))
con.add_child(Node.s32('now', Time.now()))
con.add_child(Node.s32("day", statistics.today_plays))
con.add_child(Node.s32("cnt", statistics.total_plays))
con.add_child(Node.s32("last", statistics.last_play_timestamp))
con.add_child(Node.s32("now", Time.now()))
team = Node.void('team')
team = Node.void("team")
pdata.add_child(team)
team.add_child(Node.s32('id', -1))
team.add_child(Node.string('name', ''))
team.add_child(Node.s32("id", -1))
team.add_child(Node.string("name", ""))
custom = Node.void('custom')
customdict = profile.get_dict('custom')
custom = Node.void("custom")
customdict = profile.get_dict("custom")
pdata.add_child(custom)
custom.add_child(Node.u8('bgm_m', customdict.get_int('bgm_m')))
custom.add_child(Node.u8('st_f', customdict.get_int('st_f')))
custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg')))
custom.add_child(Node.u8('st_bg_b', customdict.get_int('st_bg_b')))
custom.add_child(Node.u8('eff_e', customdict.get_int('eff_e')))
custom.add_child(Node.u8('se_s', customdict.get_int('se_s')))
custom.add_child(Node.u8('se_s_v', customdict.get_int('se_s_v')))
custom.add_child(Node.u8("bgm_m", customdict.get_int("bgm_m")))
custom.add_child(Node.u8("st_f", customdict.get_int("st_f")))
custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg")))
custom.add_child(Node.u8("st_bg_b", customdict.get_int("st_bg_b")))
custom.add_child(Node.u8("eff_e", customdict.get_int("eff_e")))
custom.add_child(Node.u8("se_s", customdict.get_int("se_s")))
custom.add_child(Node.u8("se_s_v", customdict.get_int("se_s_v")))
released = Node.void('released')
released = Node.void("released")
pdata.add_child(released)
for item in achievements:
if item.type[:5] != 'item_':
if item.type[:5] != "item_":
continue
itemtype = int(item.type[5:])
if game_config.get_bool('force_unlock_songs') and itemtype == 0:
if game_config.get_bool("force_unlock_songs") and itemtype == 0:
# Don't echo unlocks when we're force unlocking, we'll do it later
continue
info = Node.void('info')
info = Node.void("info")
released.add_child(info)
info.add_child(Node.u8('type', itemtype))
info.add_child(Node.u16('id', item.id))
info.add_child(Node.u8("type", itemtype))
info.add_child(Node.u16("id", item.id))
if game_config.get_bool('force_unlock_songs'):
songs = {song.id for song in self.data.local.music.get_all_songs(self.game, self.version)}
if game_config.get_bool("force_unlock_songs"):
songs = {
song.id
for song in self.data.local.music.get_all_songs(self.game, self.version)
}
for songid in songs:
info = Node.void('info')
info = Node.void("info")
released.add_child(info)
info.add_child(Node.u8('type', 0))
info.add_child(Node.u16('id', songid))
info.add_child(Node.u8("type", 0))
info.add_child(Node.u16("id", songid))
# Scores
record = Node.void('record')
record = Node.void("record")
pdata.add_child(record)
for score in scores:
rec = Node.void('rec')
rec = Node.void("rec")
record.add_child(rec)
rec.add_child(Node.u16('mid', score.id))
rec.add_child(Node.u8('ng', score.chart))
rec.add_child(Node.s32('win', score.data.get_dict('stats').get_int('win')))
rec.add_child(Node.s32('lose', score.data.get_dict('stats').get_int('lose')))
rec.add_child(Node.s32('draw', score.data.get_dict('stats').get_int('draw')))
rec.add_child(Node.u8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type'), score.data.get_int('combo_type'))))
rec.add_child(Node.s16('ar', int(score.data.get_int('achievement_rate') / 10)))
rec.add_child(Node.s16('bs', score.points))
rec.add_child(Node.s16('mc', score.data.get_int('combo')))
rec.add_child(Node.s16('bmc', score.data.get_int('miss_count')))
rec.add_child(Node.u16("mid", score.id))
rec.add_child(Node.u8("ng", score.chart))
rec.add_child(Node.s32("win", score.data.get_dict("stats").get_int("win")))
rec.add_child(
Node.s32("lose", score.data.get_dict("stats").get_int("lose"))
)
rec.add_child(
Node.s32("draw", score.data.get_dict("stats").get_int("draw"))
)
rec.add_child(
Node.u8(
"ct",
self.__db_to_game_clear_type(
score.data.get_int("clear_type"),
score.data.get_int("combo_type"),
),
)
)
rec.add_child(
Node.s16("ar", int(score.data.get_int("achievement_rate") / 10))
)
rec.add_child(Node.s16("bs", score.points))
rec.add_child(Node.s16("mc", score.data.get_int("combo")))
rec.add_child(Node.s16("bmc", score.data.get_int("miss_count")))
# In original ReflecBeat, the entire battle log was returned for each battle.
# We don't support storing all of that info, so don't return anything here.
blog = Node.void('blog')
blog = Node.void("blog")
pdata.add_child(blog)
# Comment (seems unused?)
pdata.add_child(Node.string('cmnt', ''))
pdata.add_child(Node.string("cmnt", ""))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
game_config = self.get_game_config()
newprofile = oldprofile.clone()
newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid')))
newprofile.replace_str('name', request.child_value('pdata/base/name'))
newprofile.replace_int('lvl', request.child_value('pdata/base/lv'))
newprofile.replace_int('exp', request.child_value('pdata/base/exp'))
newprofile.replace_int('mg', request.child_value('pdata/base/mg'))
newprofile.replace_int('ap', request.child_value('pdata/base/ap'))
newprofile.replace_int('flag', request.child_value('pdata/base/flag'))
newprofile.replace_int("lid", ID.parse_machine_id(request.child_value("lid")))
newprofile.replace_str("name", request.child_value("pdata/base/name"))
newprofile.replace_int("lvl", request.child_value("pdata/base/lv"))
newprofile.replace_int("exp", request.child_value("pdata/base/exp"))
newprofile.replace_int("mg", request.child_value("pdata/base/mg"))
newprofile.replace_int("ap", request.child_value("pdata/base/ap"))
newprofile.replace_int("flag", request.child_value("pdata/base/flag"))
customdict = newprofile.get_dict('custom')
custom = request.child('pdata/custom')
customdict = newprofile.get_dict("custom")
custom = request.child("pdata/custom")
if custom:
customdict.replace_int('bgm_m', custom.child_value('bgm_m'))
customdict.replace_int('st_f', custom.child_value('st_f'))
customdict.replace_int('st_bg', custom.child_value('st_bg'))
customdict.replace_int('st_bg_b', custom.child_value('st_bg_b'))
customdict.replace_int('eff_e', custom.child_value('eff_e'))
customdict.replace_int('se_s', custom.child_value('se_s'))
customdict.replace_int('se_s_v', custom.child_value('se_s_v'))
newprofile.replace_dict('custom', customdict)
customdict.replace_int("bgm_m", custom.child_value("bgm_m"))
customdict.replace_int("st_f", custom.child_value("st_f"))
customdict.replace_int("st_bg", custom.child_value("st_bg"))
customdict.replace_int("st_bg_b", custom.child_value("st_bg_b"))
customdict.replace_int("eff_e", custom.child_value("eff_e"))
customdict.replace_int("se_s", custom.child_value("se_s"))
customdict.replace_int("se_s_v", custom.child_value("se_s_v"))
newprofile.replace_dict("custom", customdict)
# Music unlocks and other stuff
released = request.child('pdata/released')
released = request.child("pdata/released")
if released:
for child in released.children:
if child.name != 'info':
if child.name != "info":
continue
item_id = child.child_value('id')
item_type = child.child_value('type')
if game_config.get_bool('force_unlock_songs') and item_type == 0:
item_id = child.child_value("id")
item_type = child.child_value("type")
if game_config.get_bool("force_unlock_songs") and item_type == 0:
# Don't save unlocks when we're force unlocking
continue
@ -442,7 +466,7 @@ class ReflecBeat(ReflecBeatBase):
self.version,
userid,
item_id,
f'item_{item_type}',
f"item_{item_type}",
{},
)
@ -453,46 +477,48 @@ class ReflecBeat(ReflecBeatBase):
# the previous try. So, we must also look at the battle log for the actual play scores,
# and combine the data if we can.
savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {}
songplays = request.child('pdata/record')
songplays = request.child("pdata/record")
if songplays:
for child in songplays.children:
if child.name != 'rec':
if child.name != "rec":
continue
songid = child.child_value('mid')
chart = child.child_value('ng')
songid = child.child_value("mid")
chart = child.child_value("ng")
# These don't get sent with the battle logs, so we try to construct
# the values here.
if songid not in savedrecords:
savedrecords[songid] = {}
savedrecords[songid][chart] = {
'achievement_rate': child.child_value('ar') * 10,
'points': child.child_value('bs'),
'combo': child.child_value('mc'),
'miss_count': child.child_value('bmc'),
'win': child.child_value('win'),
'lose': child.child_value('lose'),
'draw': child.child_value('draw'),
"achievement_rate": child.child_value("ar") * 10,
"points": child.child_value("bs"),
"combo": child.child_value("mc"),
"miss_count": child.child_value("bmc"),
"win": child.child_value("win"),
"lose": child.child_value("lose"),
"draw": child.child_value("draw"),
}
# Now, see the actual battles that were played. If we can, unify the data with a record.
# We only do that when the record achievement rate and score matches the battle achievement
# rate and score, so we know for a fact that that record was generated by this battle.
battlelogs = request.child('pdata/blog')
battlelogs = request.child("pdata/blog")
if battlelogs:
for child in battlelogs.children:
if child.name != 'log':
if child.name != "log":
continue
songid = child.child_value('mid')
chart = child.child_value('ng')
songid = child.child_value("mid")
chart = child.child_value("ng")
clear_type = child.child_value('myself/ct')
achievement_rate = child.child_value('myself/ar') * 10
points = child.child_value('myself/s')
clear_type = child.child_value("myself/ct")
achievement_rate = child.child_value("myself/ar") * 10
points = child.child_value("myself/s")
clear_type, combo_type = self.__game_to_db_clear_type(clear_type, achievement_rate)
clear_type, combo_type = self.__game_to_db_clear_type(
clear_type, achievement_rate
)
combo = None
miss_count = -1
@ -503,17 +529,17 @@ class ReflecBeat(ReflecBeatBase):
data = savedrecords[songid][chart]
if (
data['achievement_rate'] == achievement_rate and
data['points'] == points
data["achievement_rate"] == achievement_rate
and data["points"] == points
):
# This is the same record! Use the stats from it to update our
# internal representation.
combo = data['combo']
miss_count = data['miss_count']
combo = data["combo"]
miss_count = data["miss_count"]
stats = {
'win': data['win'],
'lose': data['lose'],
'draw': data['draw'],
"win": data["win"],
"lose": data["lose"],
"draw": data["draw"],
}
self.update_score(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -66,33 +66,37 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
return self.COMBO_TYPE_FULL_COMBO
if game_combo == self.GAME_COMBO_TYPE_FULL_COMBO_ALL_JUST:
return self.COMBO_TYPE_FULL_COMBO_ALL_JUST
raise Exception(f'Invalid game_combo value {game_combo}')
raise Exception(f"Invalid game_combo value {game_combo}")
def _add_event_info(self, root: Node) -> None:
# Overridden in subclasses
pass
def _add_shop_score(self, root: Node) -> None:
shop_score = Node.void('shop_score')
shop_score = Node.void("shop_score")
root.add_child(shop_score)
today = Node.void('today')
today = Node.void("today")
shop_score.add_child(today)
yesterday = Node.void('yesterday')
yesterday = Node.void("yesterday")
shop_score.add_child(yesterday)
all_profiles = self.data.local.user.get_all_profiles(self.game, self.version)
all_attempts = self.data.local.music.get_all_attempts(self.game, self.version, timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY))
all_attempts = self.data.local.music.get_all_attempts(
self.game,
self.version,
timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY),
)
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
if machine.arcade is not None:
lids = [
machine.id for machine in self.data.local.machine.get_all_machines(machine.arcade)
machine.id
for machine in self.data.local.machine.get_all_machines(machine.arcade)
]
else:
lids = [machine.id]
relevant_profiles = [
profile for profile in all_profiles
if profile[1].get_int('lid', -1) in lids
profile for profile in all_profiles if profile[1].get_int("lid", -1) in lids
]
for (rootnode, timeoffset) in [
@ -101,10 +105,11 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
]:
# Grab all attempts made in the relevant day
relevant_attempts = [
attempt for attempt in all_attempts
attempt
for attempt in all_attempts
if (
attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) and
attempt[1].timestamp <= (Time.end_of_today() - timeoffset)
attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset)
and attempt[1].timestamp <= (Time.end_of_today() - timeoffset)
)
]
@ -120,7 +125,10 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
scores_by_user[userid][attempt.id][attempt.chart] = attempt
else:
# If this attempt is better than the stored one, replace it
if scores_by_user[userid][attempt.id][attempt.chart].points < attempt.points:
if (
scores_by_user[userid][attempt.id][attempt.chart].points
< attempt.points
):
scores_by_user[userid][attempt.id][attempt.chart] = attempt
# Calculate points earned by user in the day
@ -129,52 +137,65 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
points_by_user[userid] = 0
for mid in scores_by_user[userid]:
for chart in scores_by_user[userid][mid]:
points_by_user[userid] = points_by_user[userid] + scores_by_user[userid][mid][chart].points
points_by_user[userid] = (
points_by_user[userid]
+ scores_by_user[userid][mid][chart].points
)
# Output that day's earned points
for (userid, profile) in relevant_profiles:
data = Node.void('data')
data = Node.void("data")
rootnode.add_child(data)
data.add_child(Node.s16('day_id', int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY)))
data.add_child(Node.s32('user_id', profile.extid))
data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id')))
data.add_child(Node.s16('point', min(points_by_user.get(userid, 0), 32767)))
data.add_child(Node.s32('update_time', Time.now()))
data.add_child(Node.string('name', profile.get_str('name')))
data.add_child(
Node.s16(
"day_id", int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY)
)
)
data.add_child(Node.s32("user_id", profile.extid))
data.add_child(
Node.s16("icon_id", profile.get_dict("config").get_int("icon_id"))
)
data.add_child(
Node.s16("point", min(points_by_user.get(userid, 0), 32767))
)
data.add_child(Node.s32("update_time", Time.now()))
data.add_child(Node.string("name", profile.get_str("name")))
rootnode.add_child(Node.s32('time', Time.beginning_of_today() - timeoffset))
rootnode.add_child(Node.s32("time", Time.beginning_of_today() - timeoffset))
def handle_info_rb5_info_read_request(self, request: Node) -> Node:
root = Node.void('info')
root = Node.void("info")
self._add_event_info(root)
return root
def handle_info_rb5_info_read_hit_chart_request(self, request: Node) -> Node:
version = request.child_value('ver')
version = request.child_value("ver")
root = Node.void('info')
root.add_child(Node.s32('ver', version))
ranking = Node.void('ranking')
root = Node.void("info")
root.add_child(Node.s32("ver", version))
ranking = Node.void("ranking")
root.add_child(ranking)
def add_hitchart(name: str, start: int, end: int, hitchart: List[Tuple[int, int]]) -> None:
def add_hitchart(
name: str, start: int, end: int, hitchart: List[Tuple[int, int]]
) -> None:
base = Node.void(name)
ranking.add_child(base)
base.add_child(Node.s32('bt', start))
base.add_child(Node.s32('et', end))
new = Node.void('new')
base.add_child(Node.s32("bt", start))
base.add_child(Node.s32("et", end))
new = Node.void("new")
base.add_child(new)
for (mid, plays) in hitchart:
d = Node.void('d')
d = Node.void("d")
new.add_child(d)
d.add_child(Node.s16('mid', mid))
d.add_child(Node.s32('cnt', plays))
d.add_child(Node.s16("mid", mid))
d.add_child(Node.s32("cnt", plays))
# Weekly hit chart
add_hitchart(
'weekly',
"weekly",
Time.now() - Time.SECONDS_IN_WEEK,
Time.now(),
self.data.local.music.get_hit_chart(self.game, self.version, 1024, 7),
@ -182,7 +203,7 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
# Monthly hit chart
add_hitchart(
'monthly',
"monthly",
Time.now() - Time.SECONDS_IN_DAY * 30,
Time.now(),
self.data.local.music.get_hit_chart(self.game, self.version, 1024, 30),
@ -190,7 +211,7 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
# All time hit chart
add_hitchart(
'total',
"total",
Time.now() - Time.SECONDS_IN_DAY * 365,
Time.now(),
self.data.local.music.get_hit_chart(self.game, self.version, 1024, 365),
@ -199,13 +220,13 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
return root
def handle_info_rb5_info_read_shop_ranking_request(self, request: Node) -> Node:
start_music_id = request.child_value('min')
end_music_id = request.child_value('max')
start_music_id = request.child_value("min")
end_music_id = request.child_value("max")
root = Node.void('info')
shop_score = Node.void('shop_score')
root = Node.void("info")
shop_score = Node.void("shop_score")
root.add_child(shop_score)
shop_score.add_child(Node.s32('time', Time.now()))
shop_score.add_child(Node.s32("time", Time.now()))
profiles: Dict[UserID, Profile] = {}
for songid in range(start_music_id, end_music_id + 1):
@ -233,31 +254,44 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
profiles[userid] = self.get_any_profile(userid)
profile = profiles[userid]
data = Node.void('data')
data = Node.void("data")
shop_score.add_child(data)
data.add_child(Node.s32('rank', i + 1))
data.add_child(Node.s16('music_id', songid))
data.add_child(Node.s8('note_grade', score.chart))
data.add_child(Node.s8('clear_type', self._db_to_game_clear_type(score.data.get_int('clear_type'))))
data.add_child(Node.s32('user_id', profile.extid))
data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id')))
data.add_child(Node.s32('score', score.points))
data.add_child(Node.s32('time', score.timestamp))
data.add_child(Node.string('name', profile.get_str('name')))
data.add_child(Node.s32("rank", i + 1))
data.add_child(Node.s16("music_id", songid))
data.add_child(Node.s8("note_grade", score.chart))
data.add_child(
Node.s8(
"clear_type",
self._db_to_game_clear_type(
score.data.get_int("clear_type")
),
)
)
data.add_child(Node.s32("user_id", profile.extid))
data.add_child(
Node.s16(
"icon_id", profile.get_dict("config").get_int("icon_id")
)
)
data.add_child(Node.s32("score", score.points))
data.add_child(Node.s32("time", score.timestamp))
data.add_child(Node.string("name", profile.get_str("name")))
return root
def handle_lobby_rb5_lobby_entry_request(self, request: Node) -> Node:
root = Node.void('lobby')
root.add_child(Node.s32('interval', 120))
root.add_child(Node.s32('interval_p', 120))
root = Node.void("lobby")
root.add_child(Node.s32("interval", 120))
root.add_child(Node.s32("interval_p", 120))
# Create a lobby entry for this user
extid = request.child_value('e/uid')
extid = request.child_value("e/uid")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
profile = self.get_profile(userid)
info = self.data.local.lobby.get_play_session_info(self.game, self.version, userid)
info = self.data.local.lobby.get_play_session_info(
self.game, self.version, userid
)
if profile is None or info is None:
return root
@ -266,61 +300,61 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
self.version,
userid,
{
'mid': request.child_value('e/mid'),
'ng': request.child_value('e/ng'),
'mopt': request.child_value('e/mopt'),
'lid': request.child_value('e/lid'),
'sn': request.child_value('e/sn'),
'pref': request.child_value('e/pref'),
'stg': request.child_value('e/stg'),
'pside': request.child_value('e/pside'),
'eatime': request.child_value('e/eatime'),
'ga': request.child_value('e/ga'),
'gp': request.child_value('e/gp'),
'la': request.child_value('e/la'),
'ver': request.child_value('e/ver'),
}
"mid": request.child_value("e/mid"),
"ng": request.child_value("e/ng"),
"mopt": request.child_value("e/mopt"),
"lid": request.child_value("e/lid"),
"sn": request.child_value("e/sn"),
"pref": request.child_value("e/pref"),
"stg": request.child_value("e/stg"),
"pside": request.child_value("e/pside"),
"eatime": request.child_value("e/eatime"),
"ga": request.child_value("e/ga"),
"gp": request.child_value("e/gp"),
"la": request.child_value("e/la"),
"ver": request.child_value("e/ver"),
},
)
lobby = self.data.local.lobby.get_lobby(
self.game,
self.version,
userid,
)
root.add_child(Node.s32('eid', lobby.get_int('id')))
e = Node.void('e')
root.add_child(Node.s32("eid", lobby.get_int("id")))
e = Node.void("e")
root.add_child(e)
e.add_child(Node.s32('eid', lobby.get_int('id')))
e.add_child(Node.u16('mid', lobby.get_int('mid')))
e.add_child(Node.u8('ng', lobby.get_int('ng')))
e.add_child(Node.s32('uid', profile.extid))
e.add_child(Node.s32('uattr', profile.get_int('uattr')))
e.add_child(Node.string('pn', profile.get_str('name')))
e.add_child(Node.s32('plyid', info.get_int('id')))
e.add_child(Node.s16('mg', profile.get_int('mg')))
e.add_child(Node.s32('mopt', lobby.get_int('mopt')))
e.add_child(Node.string('lid', lobby.get_str('lid')))
e.add_child(Node.string('sn', lobby.get_str('sn')))
e.add_child(Node.u8('pref', lobby.get_int('pref')))
e.add_child(Node.s8('stg', lobby.get_int('stg')))
e.add_child(Node.s8('pside', lobby.get_int('pside')))
e.add_child(Node.s16('eatime', lobby.get_int('eatime')))
e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4)))
e.add_child(Node.u16('gp', lobby.get_int('gp')))
e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4)))
e.add_child(Node.u8('ver', lobby.get_int('ver')))
e.add_child(Node.s32("eid", lobby.get_int("id")))
e.add_child(Node.u16("mid", lobby.get_int("mid")))
e.add_child(Node.u8("ng", lobby.get_int("ng")))
e.add_child(Node.s32("uid", profile.extid))
e.add_child(Node.s32("uattr", profile.get_int("uattr")))
e.add_child(Node.string("pn", profile.get_str("name")))
e.add_child(Node.s32("plyid", info.get_int("id")))
e.add_child(Node.s16("mg", profile.get_int("mg")))
e.add_child(Node.s32("mopt", lobby.get_int("mopt")))
e.add_child(Node.string("lid", lobby.get_str("lid")))
e.add_child(Node.string("sn", lobby.get_str("sn")))
e.add_child(Node.u8("pref", lobby.get_int("pref")))
e.add_child(Node.s8("stg", lobby.get_int("stg")))
e.add_child(Node.s8("pside", lobby.get_int("pside")))
e.add_child(Node.s16("eatime", lobby.get_int("eatime")))
e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4)))
e.add_child(Node.u16("gp", lobby.get_int("gp")))
e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4)))
e.add_child(Node.u8("ver", lobby.get_int("ver")))
return root
def handle_lobby_rb5_lobby_read_request(self, request: Node) -> Node:
root = Node.void('lobby')
root.add_child(Node.s32('interval', 120))
root.add_child(Node.s32('interval_p', 120))
root = Node.void("lobby")
root.add_child(Node.s32("interval", 120))
root.add_child(Node.s32("interval_p", 120))
# Look up all lobbies matching the criteria specified
ver = request.child_value('var')
mg = request.child_value('m_grade') # noqa: F841
extid = request.child_value('uid')
limit = request.child_value('max')
ver = request.child_value("var")
mg = request.child_value("m_grade") # noqa: F841
extid = request.child_value("uid")
limit = request.child_value("max")
userid = self.data.remote.user.from_extid(self.game, self.version, extid)
if userid is not None:
lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version)
@ -331,95 +365,99 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
if user == userid:
# If we have our own lobby, don't return it
continue
if ver != lobby.get_int('ver'):
if ver != lobby.get_int("ver"):
# Don't return lobby data for different versions
continue
profile = self.get_profile(user)
info = self.data.local.lobby.get_play_session_info(self.game, self.version, userid)
info = self.data.local.lobby.get_play_session_info(
self.game, self.version, userid
)
if profile is None or info is None:
# No profile info, don't return this lobby
return root
e = Node.void('e')
e = Node.void("e")
root.add_child(e)
e.add_child(Node.s32('eid', lobby.get_int('id')))
e.add_child(Node.u16('mid', lobby.get_int('mid')))
e.add_child(Node.u8('ng', lobby.get_int('ng')))
e.add_child(Node.s32('uid', profile.extid))
e.add_child(Node.s32('uattr', profile.get_int('uattr')))
e.add_child(Node.string('pn', profile.get_str('name')))
e.add_child(Node.s32('plyid', info.get_int('id')))
e.add_child(Node.s16('mg', profile.get_int('mg')))
e.add_child(Node.s32('mopt', lobby.get_int('mopt')))
e.add_child(Node.string('lid', lobby.get_str('lid')))
e.add_child(Node.string('sn', lobby.get_str('sn')))
e.add_child(Node.u8('pref', lobby.get_int('pref')))
e.add_child(Node.s8('stg', lobby.get_int('stg')))
e.add_child(Node.s8('pside', lobby.get_int('pside')))
e.add_child(Node.s16('eatime', lobby.get_int('eatime')))
e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4)))
e.add_child(Node.u16('gp', lobby.get_int('gp')))
e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4)))
e.add_child(Node.u8('ver', lobby.get_int('ver')))
e.add_child(Node.s32("eid", lobby.get_int("id")))
e.add_child(Node.u16("mid", lobby.get_int("mid")))
e.add_child(Node.u8("ng", lobby.get_int("ng")))
e.add_child(Node.s32("uid", profile.extid))
e.add_child(Node.s32("uattr", profile.get_int("uattr")))
e.add_child(Node.string("pn", profile.get_str("name")))
e.add_child(Node.s32("plyid", info.get_int("id")))
e.add_child(Node.s16("mg", profile.get_int("mg")))
e.add_child(Node.s32("mopt", lobby.get_int("mopt")))
e.add_child(Node.string("lid", lobby.get_str("lid")))
e.add_child(Node.string("sn", lobby.get_str("sn")))
e.add_child(Node.u8("pref", lobby.get_int("pref")))
e.add_child(Node.s8("stg", lobby.get_int("stg")))
e.add_child(Node.s8("pside", lobby.get_int("pside")))
e.add_child(Node.s16("eatime", lobby.get_int("eatime")))
e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4)))
e.add_child(Node.u16("gp", lobby.get_int("gp")))
e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4)))
e.add_child(Node.u8("ver", lobby.get_int("ver")))
limit = limit - 1
return root
def handle_lobby_rb5_lobby_delete_entry_request(self, request: Node) -> Node:
eid = request.child_value('eid')
eid = request.child_value("eid")
self.data.local.lobby.destroy_lobby(eid)
return Node.void('lobby')
return Node.void("lobby")
def handle_pcb_rb5_pcb_boot_request(self, request: Node) -> Node:
shop_id = ID.parse_machine_id(request.child_value('lid'))
shop_id = ID.parse_machine_id(request.child_value("lid"))
machine = self.get_machine_by_id(shop_id)
if machine is not None:
machine_name = machine.name
close = machine.data.get_bool('close')
hour = machine.data.get_int('hour')
minute = machine.data.get_int('minute')
close = machine.data.get_bool("close")
hour = machine.data.get_int("hour")
minute = machine.data.get_int("minute")
else:
machine_name = ''
machine_name = ""
close = False
hour = 0
minute = 0
root = Node.void('pcb')
sinfo = Node.void('sinfo')
root = Node.void("pcb")
sinfo = Node.void("sinfo")
root.add_child(sinfo)
sinfo.add_child(Node.string('nm', machine_name))
sinfo.add_child(Node.bool('cl_enbl', close))
sinfo.add_child(Node.u8('cl_h', hour))
sinfo.add_child(Node.u8('cl_m', minute))
sinfo.add_child(Node.bool('shop_flag', True))
sinfo.add_child(Node.string("nm", machine_name))
sinfo.add_child(Node.bool("cl_enbl", close))
sinfo.add_child(Node.u8("cl_h", hour))
sinfo.add_child(Node.u8("cl_m", minute))
sinfo.add_child(Node.bool("shop_flag", True))
return root
def handle_pcb_rb5_pcb_error_request(self, request: Node) -> Node:
return Node.void('pcb')
return Node.void("pcb")
def handle_pcb_rb5_pcb_update_request(self, request: Node) -> Node:
return Node.void('pcb')
return Node.void("pcb")
def handle_shop_rb5_shop_write_setting_request(self, request: Node) -> Node:
return Node.void('shop')
return Node.void("shop")
def handle_shop_rb5_shop_write_info_request(self, request: Node) -> Node:
self.update_machine_name(request.child_value('sinfo/nm'))
self.update_machine_data({
'close': request.child_value('sinfo/cl_enbl'),
'hour': request.child_value('sinfo/cl_h'),
'minute': request.child_value('sinfo/cl_m'),
'pref': request.child_value('sinfo/prf'),
})
return Node.void('shop')
self.update_machine_name(request.child_value("sinfo/nm"))
self.update_machine_data(
{
"close": request.child_value("sinfo/cl_enbl"),
"hour": request.child_value("sinfo/cl_h"),
"minute": request.child_value("sinfo/cl_m"),
"pref": request.child_value("sinfo/prf"),
}
)
return Node.void("shop")
def handle_player_rb5_player_start_request(self, request: Node) -> Node:
root = Node.void('player')
root = Node.void("player")
# Create a new play session based on info from the request
refid = request.child_value('rid')
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
self.data.local.lobby.put_play_session_info(
@ -427,10 +465,10 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
self.version,
userid,
{
'ga': request.child_value('ga'),
'gp': request.child_value('gp'),
'la': request.child_value('la'),
'pnid': request.child_value('pnid'),
"ga": request.child_value("ga"),
"gp": request.child_value("gp"),
"la": request.child_value("la"),
"pnid": request.child_value("pnid"),
},
)
info = self.data.local.lobby.get_play_session_info(
@ -439,22 +477,22 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
userid,
)
if info is not None:
play_id = info.get_int('id')
play_id = info.get_int("id")
else:
play_id = 0
else:
play_id = 0
# Session stuff, and resend global defaults
root.add_child(Node.s32('plyid', play_id))
root.add_child(Node.u64('start_time', Time.now() * 1000))
root.add_child(Node.s32("plyid", play_id))
root.add_child(Node.u64("start_time", Time.now() * 1000))
self._add_event_info(root)
return root
def handle_player_rb5_player_end_request(self, request: Node) -> Node:
# Destroy play session based on info from the request
refid = request.child_value('rid')
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
# Kill any lingering lobbies by this user
@ -464,16 +502,18 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
userid,
)
if lobby is not None:
self.data.local.lobby.destroy_lobby(lobby.get_int('id'))
self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid)
self.data.local.lobby.destroy_lobby(lobby.get_int("id"))
self.data.local.lobby.destroy_play_session_info(
self.game, self.version, userid
)
return Node.void('player')
return Node.void("player")
def handle_player_rb5_player_delete_request(self, request: Node) -> Node:
return Node.void('player')
return Node.void("player")
def handle_player_rb5_player_succeed_request(self, request: Node) -> Node:
refid = request.child_value('rid')
refid = request.child_value("rid")
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
previous_version = self.previous_version()
@ -481,25 +521,25 @@ class ReflecBeatVolzzaBase(ReflecBeatBase):
else:
profile = None
root = Node.void('player')
root = Node.void("player")
if profile is None:
# Return empty succeed to say this is new
root.add_child(Node.string('name', ''))
root.add_child(Node.s32('grd', -1))
root.add_child(Node.s32('ap', -1))
root.add_child(Node.s32('uattr', 0))
root.add_child(Node.string("name", ""))
root.add_child(Node.s32("grd", -1))
root.add_child(Node.s32("ap", -1))
root.add_child(Node.s32("uattr", 0))
else:
# Return previous profile formatted to say this is data succession
root.add_child(Node.string('name', profile.get_str('name')))
root.add_child(Node.s32('grd', profile.get_int('mg'))) # This is a guess
root.add_child(Node.s32('ap', profile.get_int('ap')))
root.add_child(Node.s32('uattr', profile.get_int('uattr')))
root.add_child(Node.string("name", profile.get_str("name")))
root.add_child(Node.s32("grd", profile.get_int("mg"))) # This is a guess
root.add_child(Node.s32("ap", profile.get_int("ap")))
root.add_child(Node.s32("uattr", profile.get_int("uattr")))
return root
def handle_player_rb5_player_read_request(self, request: Node) -> Node:
refid = request.child_value('rid')
refid = request.child_value("rid")
profile = self.get_profile_by_refid(refid)
if profile:
return profile
return Node.void('player')
return Node.void("player")

View File

@ -21,7 +21,9 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
CLEAR_TYPE_CLEAR: Final[int] = DBConstants.SDVX_CLEAR_TYPE_CLEAR
CLEAR_TYPE_HARD_CLEAR: Final[int] = DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR
CLEAR_TYPE_ULTIMATE_CHAIN: Final[int] = DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN
CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: Final[int] = DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN
CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: Final[
int
] = DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN
GRADE_NO_PLAY: Final[int] = DBConstants.SDVX_GRADE_NO_PLAY
GRADE_D: Final[int] = DBConstants.SDVX_GRADE_D
@ -41,7 +43,7 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
CHART_TYPE_INFINITE: Final[int] = 3
CHART_TYPE_MAXIMUM: Final[int] = 4
def previous_version(self) -> Optional['SoundVoltexBase']:
def previous_version(self) -> Optional["SoundVoltexBase"]:
"""
Returns the previous version of the game, based on this game. Should
be overridden.
@ -66,7 +68,9 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
# Now, return it
return self.format_profile(userid, profile)
def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], locid: Optional[int]) -> Node:
def new_profile_by_refid(
self, refid: Optional[str], name: Optional[str], locid: Optional[int]
) -> Node:
"""
Given a RefID and an optional name, create a profile and then return
a formatted profile node. Similar rationale to get_profile_by_refid.
@ -75,7 +79,7 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
return None
if name is None:
name = 'NONAME'
name = "NONAME"
# First, create and save the default profile
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -85,8 +89,8 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
refid,
0,
{
'name': name,
'loc': locid,
"name": name,
"loc": locid,
},
)
self.put_profile(userid, profile)
@ -97,9 +101,11 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
Base handler for a profile. Given a userid and a profile dictionary,
return a Node representing a profile. Should be overridden.
"""
return Node.void('game')
return Node.void("game")
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
"""
Base handler for profile parsing. Given a request and an old profile,
return a new profile that's been updated with the contents of the request.
@ -121,16 +127,18 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
},
}
"""
all_attempts, remote_attempts = Parallel.execute([
lambda: self.data.local.music.get_all_attempts(
game=self.game,
version=self.version,
),
lambda: self.data.remote.music.get_clear_rates(
game=self.game,
version=self.version,
)
])
all_attempts, remote_attempts = Parallel.execute(
[
lambda: self.data.local.music.get_all_attempts(
game=self.game,
version=self.version,
),
lambda: self.data.remote.music.get_clear_rates(
game=self.game,
version=self.version,
),
]
)
attempts: Dict[int, Dict[int, Dict[str, int]]] = {}
for (_, attempt) in all_attempts:
# Terrible temporary structure is terrible.
@ -138,26 +146,33 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
attempts[attempt.id] = {}
if attempt.chart not in attempts[attempt.id]:
attempts[attempt.id][attempt.chart] = {
'total': 0,
'clears': 0,
'average': 0,
"total": 0,
"clears": 0,
"average": 0,
}
# We saw an attempt, keep the total attempts in sync.
attempts[attempt.id][attempt.chart]['average'] = int(
attempts[attempt.id][attempt.chart]["average"] = int(
(
(attempts[attempt.id][attempt.chart]['average'] * attempts[attempt.id][attempt.chart]['total']) +
attempt.points
) / (attempts[attempt.id][attempt.chart]['total'] + 1)
(
attempts[attempt.id][attempt.chart]["average"]
* attempts[attempt.id][attempt.chart]["total"]
)
+ attempt.points
)
/ (attempts[attempt.id][attempt.chart]["total"] + 1)
)
attempts[attempt.id][attempt.chart]['total'] += 1
attempts[attempt.id][attempt.chart]["total"] += 1
if attempt.data.get_int('clear_type', self.CLEAR_TYPE_NO_PLAY) in [self.CLEAR_TYPE_NO_PLAY, self.CLEAR_TYPE_FAILED]:
if attempt.data.get_int("clear_type", self.CLEAR_TYPE_NO_PLAY) in [
self.CLEAR_TYPE_NO_PLAY,
self.CLEAR_TYPE_FAILED,
]:
# This attempt was a failure, so don't count it against clears of full combos
continue
# It was at least a clear
attempts[attempt.id][attempt.chart]['clears'] += 1
attempts[attempt.id][attempt.chart]["clears"] += 1
# Merge in remote attempts
for songid in remote_attempts:
@ -167,13 +182,17 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
for songchart in remote_attempts[songid]:
if songchart not in attempts[songid]:
attempts[songid][songchart] = {
'total': 0,
'clears': 0,
'average': 0,
"total": 0,
"clears": 0,
"average": 0,
}
attempts[songid][songchart]['total'] += remote_attempts[songid][songchart]['plays']
attempts[songid][songchart]['clears'] += remote_attempts[songid][songchart]['clears']
attempts[songid][songchart]["total"] += remote_attempts[songid][
songchart
]["plays"]
attempts[songid][songchart]["clears"] += remote_attempts[songid][
songchart
]["clears"]
return attempts
@ -186,7 +205,7 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
clear_type: int,
grade: int,
combo: int,
stats: Optional[Dict[str, int]]=None,
stats: Optional[Dict[str, int]] = None,
) -> None:
"""
Given various pieces of a score, update the user's high score and score
@ -248,21 +267,23 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
scoredata = oldscore.data
# Replace clear type and grade
scoredata.replace_int('clear_type', max(scoredata.get_int('clear_type'), clear_type))
history.replace_int('clear_type', clear_type)
scoredata.replace_int('grade', max(scoredata.get_int('grade'), grade))
history.replace_int('grade', grade)
scoredata.replace_int(
"clear_type", max(scoredata.get_int("clear_type"), clear_type)
)
history.replace_int("clear_type", clear_type)
scoredata.replace_int("grade", max(scoredata.get_int("grade"), grade))
history.replace_int("grade", grade)
# If we have a combo, replace it
scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
history.replace_int('combo', combo)
scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo))
history.replace_int("combo", combo)
# If we have play stats, replace it
if stats is not None:
if raised:
# We have stats, and there's a new high score, update the stats
scoredata.replace_dict('stats', stats)
history.replace_dict('stats', stats)
scoredata.replace_dict("stats", stats)
history.replace_dict("stats", stats)
# Look up where this score was earned
lid = self.get_machine_id()

View File

@ -14,7 +14,7 @@ class SoundVoltexBooth(
SoundVoltexBase,
):
name: str = 'SOUND VOLTEX BOOTH'
name: str = "SOUND VOLTEX BOOTH"
version: int = VersionConstants.SDVX_BOOTH
GAME_LIMITED_LOCKED: Final[int] = 1
@ -42,24 +42,24 @@ class SoundVoltexBooth(
Return all of our front-end modifiably settings.
"""
return {
'bools': [
"bools": [
{
'name': 'Disable Online Matching',
'tip': 'Disable online matching between games.',
'category': 'game_config',
'setting': 'disable_matching',
"name": "Disable Online Matching",
"tip": "Disable online matching between games.",
"category": "game_config",
"setting": "disable_matching",
},
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
{
'name': 'Force Appeal Card Unlock',
'tip': 'Force unlock all appeal cards.',
'category': 'game_config',
'setting': 'force_unlock_cards',
"name": "Force Appeal Card Unlock",
"tip": "Force unlock all appeal cards.",
"category": "game_config",
"setting": "force_unlock_cards",
},
],
}
@ -112,108 +112,108 @@ class SoundVoltexBooth(
}[grade]
def handle_game_exception_request(self, request: Node) -> Node:
return Node.void('game')
return Node.void("game")
def handle_game_entry_s_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
# This should be created on the fly for a lobby that we're in.
game.add_child(Node.u32('entry_id', 1))
game.add_child(Node.u32("entry_id", 1))
return game
def handle_game_lounge_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
# Refresh interval in seconds.
game.add_child(Node.u32('interval', 10))
game.add_child(Node.u32("interval", 10))
return game
def handle_game_entry_e_request(self, request: Node) -> Node:
# Lobby destroy method, eid attribute (u32) should be used
# to destroy any open lobbies.
return Node.void('game')
return Node.void("game")
def handle_game_frozen_request(self, request: Node) -> Node:
game = Node.void('game')
game.set_attribute('result', '0')
game = Node.void("game")
game.set_attribute("result", "0")
return game
def handle_game_shop_request(self, request: Node) -> Node:
self.update_machine_name(request.child_value('shopname'))
self.update_machine_name(request.child_value("shopname"))
# Respond with number of milliseconds until next request
game = Node.void('game')
game.add_child(Node.u32('nxt_time', 1000 * 5 * 60))
game = Node.void("game")
game.add_child(Node.u32("nxt_time", 1000 * 5 * 60))
return game
def handle_game_common_request(self, request: Node) -> Node:
game = Node.void('game')
limited = Node.void('limited')
game = Node.void("game")
limited = Node.void("limited")
game.add_child(limited)
game_config = self.get_game_config()
if game_config.get_bool('force_unlock_songs'):
if game_config.get_bool("force_unlock_songs"):
ids = set()
songs = self.data.local.music.get_all_songs(self.game, self.version)
for song in songs:
if song.data.get_int('limited') == self.GAME_LIMITED_LOCKED:
if song.data.get_int("limited") == self.GAME_LIMITED_LOCKED:
ids.add(song.id)
for songid in ids:
music = Node.void('music')
music = Node.void("music")
limited.add_child(music)
music.set_attribute('id', str(songid))
music.set_attribute('flag', str(self.GAME_LIMITED_UNLOCKED))
music.set_attribute("id", str(songid))
music.set_attribute("flag", str(self.GAME_LIMITED_UNLOCKED))
event = Node.void('event')
event = Node.void("event")
game.add_child(event)
def enable_event(eid: int) -> None:
evt = Node.void('info')
evt = Node.void("info")
event.add_child(evt)
evt.set_attribute('id', str(eid))
evt.set_attribute("id", str(eid))
if not game_config.get_bool('disable_matching'):
if not game_config.get_bool("disable_matching"):
enable_event(3) # Matching enabled
enable_event(9) # Rank Soukuu
enable_event(13) # Year-end bonus
catalog = Node.void('catalog')
catalog = Node.void("catalog")
game.add_child(catalog)
songunlocks = self.data.local.game.get_items(self.game, self.version)
for unlock in songunlocks:
if unlock.type != 'song_unlock':
if unlock.type != "song_unlock":
continue
info = Node.void('info')
info = Node.void("info")
catalog.add_child(info)
info.set_attribute('id', str(unlock.id))
info.set_attribute('currency', str(self.GAME_CURRENCY_BLOCKS))
info.set_attribute('price', str(unlock.data.get_int('blocks')))
info.set_attribute("id", str(unlock.id))
info.set_attribute("currency", str(self.GAME_CURRENCY_BLOCKS))
info.set_attribute("price", str(unlock.data.get_int("blocks")))
kacinfo = Node.void('kacinfo')
kacinfo = Node.void("kacinfo")
game.add_child(kacinfo)
kacinfo.add_child(Node.u32('note00', 0))
kacinfo.add_child(Node.u32('note01', 0))
kacinfo.add_child(Node.u32('note02', 0))
kacinfo.add_child(Node.u32('note10', 0))
kacinfo.add_child(Node.u32('note11', 0))
kacinfo.add_child(Node.u32('note12', 0))
kacinfo.add_child(Node.u32('rabbeat0', 0))
kacinfo.add_child(Node.u32('rabbeat1', 0))
kacinfo.add_child(Node.u32("note00", 0))
kacinfo.add_child(Node.u32("note01", 0))
kacinfo.add_child(Node.u32("note02", 0))
kacinfo.add_child(Node.u32("note10", 0))
kacinfo.add_child(Node.u32("note11", 0))
kacinfo.add_child(Node.u32("note12", 0))
kacinfo.add_child(Node.u32("rabbeat0", 0))
kacinfo.add_child(Node.u32("rabbeat1", 0))
return game
def handle_game_hiscore_request(self, request: Node) -> Node:
game = Node.void('game')
game = Node.void("game")
# Ranking system I think?
for i in range(1, 21):
ranking = Node.void('ranking')
ranking = Node.void("ranking")
game.add_child(ranking)
ranking.set_attribute('id', str(i))
ranking.set_attribute("id", str(i))
hiscore = Node.void('hiscore')
hiscore = Node.void("hiscore")
game.add_child(hiscore)
hiscore.set_attribute('type', '1')
hiscore.set_attribute("type", "1")
records = self.data.remote.music.get_all_records(self.game, self.version)
@ -228,44 +228,47 @@ class SoundVoltexBooth(
records_by_id[score.id][score.chart] = record
missing_users.append(userid)
users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_users)}
users = {
userid: profile
for (userid, profile) in self.get_any_profiles(missing_users)
}
# Output records
for songid in records_by_id:
music = Node.void('music')
music = Node.void("music")
hiscore.add_child(music)
music.set_attribute('id', str(songid))
music.set_attribute("id", str(songid))
for chart in records_by_id[songid]:
note = Node.void('note')
note = Node.void("note")
music.add_child(note)
note.set_attribute('type', str(chart))
note.set_attribute("type", str(chart))
userid, score = records_by_id[songid][chart]
note.set_attribute('score', str(score.points))
note.set_attribute('name', users[userid].get_str('name'))
note.set_attribute("score", str(score.points))
note.set_attribute("name", users[userid].get_str("name"))
return game
def handle_game_new_request(self, request: Node) -> Node:
refid = request.attribute('refid')
name = request.attribute('name')
loc = ID.parse_machine_id(request.attribute('locid'))
refid = request.attribute("refid")
name = request.attribute("name")
loc = ID.parse_machine_id(request.attribute("locid"))
self.new_profile_by_refid(refid, name, loc)
root = Node.void('game')
root = Node.void("game")
return root
def handle_game_load_request(self, request: Node) -> Node:
refid = request.attribute('dataid')
refid = request.attribute("dataid")
root = self.get_profile_by_refid(refid)
if root is None:
root = Node.void('game')
root.set_attribute('none', '1')
root = Node.void("game")
root.set_attribute("none", "1")
return root
def handle_game_save_request(self, request: Node) -> Node:
refid = request.attribute('refid')
refid = request.attribute("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -281,10 +284,10 @@ class SoundVoltexBooth(
if userid is not None and newprofile is not None:
self.put_profile(userid, newprofile)
return Node.void('game')
return Node.void("game")
def handle_game_load_m_request(self, request: Node) -> Node:
refid = request.attribute('dataid')
refid = request.attribute("dataid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -305,27 +308,33 @@ class SoundVoltexBooth(
scores_by_id[score.id][score.chart] = score
# Output to the game
game = Node.void('game')
game = Node.void("game")
for songid in scores_by_id:
music = Node.void('music')
music = Node.void("music")
game.add_child(music)
music.set_attribute('music_id', str(songid))
music.set_attribute("music_id", str(songid))
for chart in scores_by_id[songid]:
typenode = Node.void('type')
typenode = Node.void("type")
music.add_child(typenode)
typenode.set_attribute('type_id', str(chart))
typenode.set_attribute("type_id", str(chart))
score = scores_by_id[songid][chart]
typenode.set_attribute('score', str(score.points))
typenode.set_attribute('cnt', str(score.plays))
typenode.set_attribute('clear_type', str(self.__db_to_game_clear_type(score.data.get_int('clear_type'))))
typenode.set_attribute('score_grade', str(self.__db_to_game_grade(score.data.get_int('grade'))))
typenode.set_attribute("score", str(score.points))
typenode.set_attribute("cnt", str(score.plays))
typenode.set_attribute(
"clear_type",
str(self.__db_to_game_clear_type(score.data.get_int("clear_type"))),
)
typenode.set_attribute(
"score_grade",
str(self.__db_to_game_grade(score.data.get_int("grade"))),
)
return game
def handle_game_save_m_request(self, request: Node) -> Node:
refid = request.attribute('dataid')
refid = request.attribute("dataid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -333,14 +342,14 @@ class SoundVoltexBooth(
userid = None
if userid is None:
return Node.void('game')
return Node.void("game")
musicid = int(request.attribute('music_id'))
chart = int(request.attribute('music_type'))
score = int(request.attribute('score'))
combo = int(request.attribute('max_chain'))
grade = self.__game_to_db_grade(int(request.attribute('score_grade')))
clear_type = self.__game_to_db_clear_type(int(request.attribute('clear_type')))
musicid = int(request.attribute("music_id"))
chart = int(request.attribute("music_type"))
score = int(request.attribute("score"))
combo = int(request.attribute("max_chain"))
grade = self.__game_to_db_grade(int(request.attribute("score_grade")))
clear_type = self.__game_to_db_clear_type(int(request.attribute("clear_type")))
# Save the score
self.update_score(
@ -354,10 +363,10 @@ class SoundVoltexBooth(
)
# No response necessary
return Node.void('game')
return Node.void("game")
def handle_game_buy_request(self, request: Node) -> Node:
refid = request.attribute('refid')
refid = request.attribute("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -371,21 +380,26 @@ class SoundVoltexBooth(
if userid is not None and profile is not None:
# Look up packets and blocks
packet = profile.get_int('packet')
block = profile.get_int('block')
packet = profile.get_int("packet")
block = profile.get_int("block")
# Add on any additional we earned this round
packet = packet + (request.child_value('earned_gamecoin_packet') or 0)
block = block + (request.child_value('earned_gamecoin_block') or 0)
packet = packet + (request.child_value("earned_gamecoin_packet") or 0)
block = block + (request.child_value("earned_gamecoin_block") or 0)
# Look up the item to get the actual price and currency used
item = self.data.local.game.get_item(self.game, self.version, request.child_value('catalog_id'), 'song_unlock')
item = self.data.local.game.get_item(
self.game,
self.version,
request.child_value("catalog_id"),
"song_unlock",
)
if item is not None:
currency_type = request.child_value('currency_type')
currency_type = request.child_value("currency_type")
if currency_type == self.GAME_CURRENCY_PACKETS:
if 'packets' in item:
if "packets" in item:
# This is a valid purchase
newpacket = packet - item.get_int('packets')
newpacket = packet - item.get_int("packets")
if newpacket < 0:
result = 1
else:
@ -395,9 +409,9 @@ class SoundVoltexBooth(
# Bad transaction
result = 1
elif currency_type == self.GAME_CURRENCY_BLOCKS:
if 'blocks' in item:
if "blocks" in item:
# This is a valid purchase
newblock = block - item.get_int('blocks')
newblock = block - item.get_int("blocks")
if newblock < 0:
result = 1
else:
@ -412,8 +426,8 @@ class SoundVoltexBooth(
if result == 0:
# Transaction is valid, update the profile with new packets and blocks
profile.replace_int('packet', packet)
profile.replace_int('block', block)
profile.replace_int("packet", packet)
profile.replace_int("block", block)
self.put_profile(userid, profile)
else:
# Bad catalog ID
@ -424,97 +438,117 @@ class SoundVoltexBooth(
block = 0
result = 1
game = Node.void('game')
game.add_child(Node.u32('gamecoin_packet', packet))
game.add_child(Node.u32('gamecoin_block', block))
game.add_child(Node.s8('result', result))
game = Node.void("game")
game.add_child(Node.u32("gamecoin_packet", packet))
game.add_child(Node.u32("gamecoin_block", block))
game.add_child(Node.s8("result", result))
return game
def format_profile(self, userid: UserID, profile: Profile) -> Node:
game = Node.void('game')
game = Node.void("game")
# Generic profile stuff
game.add_child(Node.string('name', profile.get_str('name')))
game.add_child(Node.string('code', ID.format_extid(profile.extid)))
game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet')))
game.add_child(Node.u32('gamecoin_block', profile.get_int('block')))
game.add_child(Node.u32('exp_point', profile.get_int('exp')))
game.add_child(Node.u32('m_user_cnt', profile.get_int('m_user_cnt')))
game.add_child(Node.string("name", profile.get_str("name")))
game.add_child(Node.string("code", ID.format_extid(profile.extid)))
game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet")))
game.add_child(Node.u32("gamecoin_block", profile.get_int("block")))
game.add_child(Node.u32("exp_point", profile.get_int("exp")))
game.add_child(Node.u32("m_user_cnt", profile.get_int("m_user_cnt")))
game_config = self.get_game_config()
if game_config.get_bool('force_unlock_cards'):
game.add_child(Node.bool_array('have_item', [True] * 512))
if game_config.get_bool("force_unlock_cards"):
game.add_child(Node.bool_array("have_item", [True] * 512))
else:
game.add_child(Node.bool_array('have_item', [x > 0 for x in profile.get_int_array('have_item', 512)]))
if game_config.get_bool('force_unlock_songs'):
game.add_child(Node.bool_array('have_note', [True] * 512))
game.add_child(
Node.bool_array(
"have_item",
[x > 0 for x in profile.get_int_array("have_item", 512)],
)
)
if game_config.get_bool("force_unlock_songs"):
game.add_child(Node.bool_array("have_note", [True] * 512))
else:
game.add_child(Node.bool_array('have_note', [x > 0 for x in profile.get_int_array('have_note', 512)]))
game.add_child(
Node.bool_array(
"have_note",
[x > 0 for x in profile.get_int_array("have_note", 512)],
)
)
# Last played stuff
lastdict = profile.get_dict('last')
last = Node.void('last')
lastdict = profile.get_dict("last")
last = Node.void("last")
game.add_child(last)
last.set_attribute('music_id', str(lastdict.get_int('music_id')))
last.set_attribute('music_type', str(lastdict.get_int('music_type')))
last.set_attribute('sort_type', str(lastdict.get_int('sort_type')))
last.set_attribute('headphone', str(lastdict.get_int('headphone')))
last.set_attribute('hispeed', str(lastdict.get_int('hispeed')))
last.set_attribute('appeal_id', str(lastdict.get_int('appeal_id')))
last.set_attribute('frame0', str(lastdict.get_int('frame0')))
last.set_attribute('frame1', str(lastdict.get_int('frame1')))
last.set_attribute('frame2', str(lastdict.get_int('frame2')))
last.set_attribute('frame3', str(lastdict.get_int('frame3')))
last.set_attribute('frame4', str(lastdict.get_int('frame4')))
last.set_attribute("music_id", str(lastdict.get_int("music_id")))
last.set_attribute("music_type", str(lastdict.get_int("music_type")))
last.set_attribute("sort_type", str(lastdict.get_int("sort_type")))
last.set_attribute("headphone", str(lastdict.get_int("headphone")))
last.set_attribute("hispeed", str(lastdict.get_int("hispeed")))
last.set_attribute("appeal_id", str(lastdict.get_int("appeal_id")))
last.set_attribute("frame0", str(lastdict.get_int("frame0")))
last.set_attribute("frame1", str(lastdict.get_int("frame1")))
last.set_attribute("frame2", str(lastdict.get_int("frame2")))
last.set_attribute("frame3", str(lastdict.get_int("frame3")))
last.set_attribute("frame4", str(lastdict.get_int("frame4")))
return game
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile:
def unformat_profile(
self, userid: UserID, request: Node, oldprofile: Profile
) -> Profile:
newprofile = oldprofile.clone()
# Update experience and in-game currencies
earned_gamecoin_packet = request.child_value('earned_gamecoin_packet')
earned_gamecoin_packet = request.child_value("earned_gamecoin_packet")
if earned_gamecoin_packet is not None:
newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet)
earned_gamecoin_block = request.child_value('earned_gamecoin_block')
newprofile.replace_int(
"packet", newprofile.get_int("packet") + earned_gamecoin_packet
)
earned_gamecoin_block = request.child_value("earned_gamecoin_block")
if earned_gamecoin_block is not None:
newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block)
gain_exp = request.child_value('gain_exp')
newprofile.replace_int(
"block", newprofile.get_int("block") + earned_gamecoin_block
)
gain_exp = request.child_value("gain_exp")
if gain_exp is not None:
newprofile.replace_int('exp', newprofile.get_int('exp') + gain_exp)
newprofile.replace_int("exp", newprofile.get_int("exp") + gain_exp)
# Miscelaneous stuff
newprofile.replace_int('m_user_cnt', request.child_value('m_user_cnt'))
newprofile.replace_int("m_user_cnt", request.child_value("m_user_cnt"))
# Update user's unlock status if we aren't force unlocked
game_config = self.get_game_config()
if not game_config.get_bool('force_unlock_cards'):
have_item = request.child_value('have_item')
if not game_config.get_bool("force_unlock_cards"):
have_item = request.child_value("have_item")
if have_item is not None:
newprofile.replace_int_array('have_item', 512, [1 if x else 0 for x in have_item])
if not game_config.get_bool('force_unlock_songs'):
have_note = request.child_value('have_note')
newprofile.replace_int_array(
"have_item", 512, [1 if x else 0 for x in have_item]
)
if not game_config.get_bool("force_unlock_songs"):
have_note = request.child_value("have_note")
if have_note is not None:
newprofile.replace_int_array('have_note', 512, [1 if x else 0 for x in have_note])
newprofile.replace_int_array(
"have_note", 512, [1 if x else 0 for x in have_note]
)
# Grab last information.
lastdict = newprofile.get_dict('last')
lastdict.replace_int('headphone', request.child_value('headphone'))
lastdict.replace_int('hispeed', request.child_value('hispeed'))
lastdict.replace_int('appeal_id', request.child_value('appeal_id'))
lastdict.replace_int('frame0', request.child_value('frame0'))
lastdict.replace_int('frame1', request.child_value('frame1'))
lastdict.replace_int('frame2', request.child_value('frame2'))
lastdict.replace_int('frame3', request.child_value('frame3'))
lastdict.replace_int('frame4', request.child_value('frame4'))
last = request.child('last')
lastdict = newprofile.get_dict("last")
lastdict.replace_int("headphone", request.child_value("headphone"))
lastdict.replace_int("hispeed", request.child_value("hispeed"))
lastdict.replace_int("appeal_id", request.child_value("appeal_id"))
lastdict.replace_int("frame0", request.child_value("frame0"))
lastdict.replace_int("frame1", request.child_value("frame1"))
lastdict.replace_int("frame2", request.child_value("frame2"))
lastdict.replace_int("frame3", request.child_value("frame3"))
lastdict.replace_int("frame4", request.child_value("frame4"))
last = request.child("last")
if last is not None:
lastdict.replace_int('music_id', intish(last.attribute('music_id')))
lastdict.replace_int('music_type', intish(last.attribute('music_type')))
lastdict.replace_int('sort_type', intish(last.attribute('sort_type')))
lastdict.replace_int("music_id", intish(last.attribute("music_id")))
lastdict.replace_int("music_type", intish(last.attribute("music_type")))
lastdict.replace_int("sort_type", intish(last.attribute("sort_type")))
# Save back last information gleaned from results
newprofile.replace_dict('last', lastdict)
newprofile.replace_dict("last", lastdict)
# Keep track of play statistics
self.update_play_statistics(userid)

View File

@ -22,12 +22,17 @@ class SoundVoltexFactory(Factory):
@classmethod
def register_all(cls) -> None:
for gamecode in ['KFC']:
for gamecode in ["KFC"]:
Base.register(gamecode, SoundVoltexFactory)
@classmethod
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
def create(
cls,
data: Data,
config: Config,
model: Model,
parentmodel: Optional[Model] = None,
) -> Optional[Base]:
def version_from_date(date: int) -> Optional[int]:
if date < 2013060500:
return VersionConstants.SDVX_BOOTH
@ -39,14 +44,14 @@ class SoundVoltexFactory(Factory):
return VersionConstants.SDVX_HEAVENLY_HAVEN
return None
if model.gamecode == 'KFC':
if model.gamecode == "KFC":
if model.version is None:
if parentmodel is None:
return None
# We have no way to tell apart newer versions. However, we can make
# an educated guess if we happen to be summoned for old profile lookup.
if parentmodel.gamecode != 'KFC':
if parentmodel.gamecode != "KFC":
return None
parentversion = version_from_date(parentmodel.version)

View File

@ -14,7 +14,7 @@ class SoundVoltexGravityWars(
SoundVoltexBase,
):
name: str = 'SOUND VOLTEX III GRAVITY WARS'
name: str = "SOUND VOLTEX III GRAVITY WARS"
version: int = VersionConstants.SDVX_GRAVITY_WARS
GAME_LIMITED_LOCKED: Final[int] = 1
@ -50,30 +50,30 @@ class SoundVoltexGravityWars(
Return all of our front-end modifiably settings.
"""
return {
'bools': [
"bools": [
{
'name': 'Disable Online Matching',
'tip': 'Disable online matching between games.',
'category': 'game_config',
'setting': 'disable_matching',
"name": "Disable Online Matching",
"tip": "Disable online matching between games.",
"category": "game_config",
"setting": "disable_matching",
},
{
'name': 'Force Song Unlock',
'tip': 'Force unlock all songs.',
'category': 'game_config',
'setting': 'force_unlock_songs',
"name": "Force Song Unlock",
"tip": "Force unlock all songs.",
"category": "game_config",
"setting": "force_unlock_songs",
},
{
'name': 'Force Appeal Card Unlock',
'tip': 'Force unlock all appeal cards.',
'category': 'game_config',
'setting': 'force_unlock_cards',
"name": "Force Appeal Card Unlock",
"tip": "Force unlock all appeal cards.",
"category": "game_config",
"setting": "force_unlock_cards",
},
{
'name': 'Force Crew Card Unlock',
'tip': 'Force unlock all crew and subcrew cards.',
'category': 'game_config',
'setting': 'force_unlock_crew',
"name": "Force Crew Card Unlock",
"tip": "Force unlock all crew and subcrew cards.",
"category": "game_config",
"setting": "force_unlock_crew",
},
],
}
@ -140,51 +140,54 @@ class SoundVoltexGravityWars(
def __get_skill_analyzer_skill_levels(self) -> Dict[int, str]:
return {
0: 'Skill LEVEL 01 岳翔',
1: 'Skill LEVEL 02 流星',
2: 'Skill LEVEL 03 月衝',
3: 'Skill LEVEL 04 瞬光',
4: 'Skill LEVEL 05 天極',
5: 'Skill LEVEL 06 烈風',
6: 'Skill LEVEL 07 雷電',
7: 'Skill LEVEL 08 麗華',
8: 'Skill LEVEL 09 魔騎士',
9: 'Skill LEVEL 10 剛力羅',
10: 'Skill LEVEL 11 或帝滅斗',
11: 'Skill LEVEL ∞(12) 暴龍天',
0: "Skill LEVEL 01 岳翔",
1: "Skill LEVEL 02 流星",
2: "Skill LEVEL 03 月衝",
3: "Skill LEVEL 04 瞬光",
4: "Skill LEVEL 05 天極",
5: "Skill LEVEL 06 烈風",
6: "Skill LEVEL 07 雷電",
7: "Skill LEVEL 08 麗華",
8: "Skill LEVEL 09 魔騎士",
9: "Skill LEVEL 10 剛力羅",
10: "Skill LEVEL 11 或帝滅斗",
11: "Skill LEVEL ∞(12) 暴龍天",
}
def handle_game_3_common_request(self, request: Node) -> Node:
game = Node.void('game_3')
limited = Node.void('music_limited')
game = Node.void("game_3")
limited = Node.void("music_limited")
game.add_child(limited)
# Song unlock config
game_config = self.get_game_config()
if game_config.get_bool('force_unlock_songs'):
if game_config.get_bool("force_unlock_songs"):
ids = set()
songs = self.data.local.music.get_all_songs(self.game, self.version)
for song in songs:
if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE):
if song.data.get_int("limited") in (
self.GAME_LIMITED_LOCKED,
self.GAME_LIMITED_UNLOCKABLE,
):
ids.add((song.id, song.chart))
for (songid, chart) in ids:
info = Node.void('info')
info = Node.void("info")
limited.add_child(info)
info.add_child(Node.s32('music_id', songid))
info.add_child(Node.u8('music_type', chart))
info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED))
info.add_child(Node.s32("music_id", songid))
info.add_child(Node.u8("music_type", chart))
info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED))
# Event config
event = Node.void('event')
event = Node.void("event")
game.add_child(event)
def enable_event(eid: int) -> None:
evt = Node.void('info')
evt = Node.void("info")
event.add_child(evt)
evt.add_child(Node.u32('event_id', eid))
evt.add_child(Node.u32("event_id", eid))
if not game_config.get_bool('disable_matching'):
if not game_config.get_bool("disable_matching"):
enable_event(1) # Matching enabled
enable_event(2) # Floor Infection
enable_event(3) # Policy Break
@ -194,7 +197,7 @@ class SoundVoltexGravityWars(
enable_event(eventid)
# Skill Analyzer config
skill_course = Node.void('skill_course')
skill_course = Node.void("skill_course")
game.add_child(skill_course)
seasons = self._get_skill_analyzer_seasons()
@ -202,74 +205,91 @@ class SoundVoltexGravityWars(
courses = self._get_skill_analyzer_courses()
max_level: Dict[int, int] = {}
for course in courses:
max_level[course['level']] = max(course['season_id'], max_level.get(course['level'], -1))
max_level[course["level"]] = max(
course["season_id"], max_level.get(course["level"], -1)
)
for course in courses:
info = Node.void('info')
info = Node.void("info")
skill_course.add_child(info)
info.add_child(Node.s16('course_id', course.get('id', course['level'])))
info.add_child(Node.s16('level', course['level']))
info.add_child(Node.s32('season_id', course['season_id']))
info.add_child(Node.string('season_name', seasons[course['season_id']]))
info.add_child(Node.bool('season_new_flg', max_level[course['level']] == course['season_id']))
info.add_child(Node.string('course_name', course.get('skill_name', skillnames.get(course['level'], ''))))
info.add_child(Node.s16('course_type', 0))
info.add_child(Node.s16('skill_name_id', course.get('skill_name_id', course['level'])))
info.add_child(Node.bool('matching_assist', course['level'] >= 0 and course['level'] <= 6))
info.add_child(Node.s16('gauge_type', self.GAME_GAUGE_TYPE_SKILL))
info.add_child(Node.s16('paseli_type', 0))
info.add_child(Node.s16("course_id", course.get("id", course["level"])))
info.add_child(Node.s16("level", course["level"]))
info.add_child(Node.s32("season_id", course["season_id"]))
info.add_child(Node.string("season_name", seasons[course["season_id"]]))
info.add_child(
Node.bool(
"season_new_flg", max_level[course["level"]] == course["season_id"]
)
)
info.add_child(
Node.string(
"course_name",
course.get("skill_name", skillnames.get(course["level"], "")),
)
)
info.add_child(Node.s16("course_type", 0))
info.add_child(
Node.s16("skill_name_id", course.get("skill_name_id", course["level"]))
)
info.add_child(
Node.bool(
"matching_assist", course["level"] >= 0 and course["level"] <= 6
)
)
info.add_child(Node.s16("gauge_type", self.GAME_GAUGE_TYPE_SKILL))
info.add_child(Node.s16("paseli_type", 0))
for trackno, trackdata in enumerate(course['tracks']):
track = Node.void('track')
for trackno, trackdata in enumerate(course["tracks"]):
track = Node.void("track")
info.add_child(track)
track.add_child(Node.s16('track_no', trackno))
track.add_child(Node.s32('music_id', trackdata['id']))
track.add_child(Node.s8('music_type', trackdata['type']))
track.add_child(Node.s16("track_no", trackno))
track.add_child(Node.s32("music_id", trackdata["id"]))
track.add_child(Node.s8("music_type", trackdata["type"]))
return game
def handle_game_3_exception_request(self, request: Node) -> Node:
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_shop_request(self, request: Node) -> Node:
self.update_machine_name(request.child_value('shopname'))
self.update_machine_name(request.child_value("shopname"))
# Respond with number of milliseconds until next request
game = Node.void('game_3')
game.add_child(Node.u32('nxt_time', 1000 * 5 * 60))
game = Node.void("game_3")
game.add_child(Node.u32("nxt_time", 1000 * 5 * 60))
return game
def handle_game_3_lounge_request(self, request: Node) -> Node:
game = Node.void('game_3')
game = Node.void("game_3")
# Refresh interval in seconds.
game.add_child(Node.u32('interval', 10))
game.add_child(Node.u32("interval", 10))
return game
def handle_game_3_entry_s_request(self, request: Node) -> Node:
game = Node.void('game_3')
game = Node.void("game_3")
# This should be created on the fly for a lobby that we're in.
game.add_child(Node.u32('entry_id', 1))
game.add_child(Node.u32("entry_id", 1))
return game
def handle_game_3_entry_e_request(self, request: Node) -> Node:
# Lobby destroy method, eid node (u32) should be used
# to destroy any open lobbies.
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_frozen_request(self, request: Node) -> Node:
game = Node.void('game_3')
game.add_child(Node.u8('result', 0))
game = Node.void("game_3")
game.add_child(Node.u8("result", 0))
return game
def handle_game_3_save_e_request(self, request: Node) -> Node:
# This has to do with Policy Break against ReflecBeat and
# floor infection, but we don't implement multi-game support so meh.
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_play_e_request(self, request: Node) -> Node:
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_buy_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -283,15 +303,15 @@ class SoundVoltexGravityWars(
if userid is not None and profile is not None:
# Look up packets and blocks
packet = profile.get_int('packet')
block = profile.get_int('block')
packet = profile.get_int("packet")
block = profile.get_int("block")
# Add on any additional we earned this round
packet = packet + (request.child_value('earned_gamecoin_packet') or 0)
block = block + (request.child_value('earned_gamecoin_block') or 0)
packet = packet + (request.child_value("earned_gamecoin_packet") or 0)
block = block + (request.child_value("earned_gamecoin_block") or 0)
currency_type = request.child_value('currency_type')
price = request.child_value('item/price')
currency_type = request.child_value("currency_type")
price = request.child_value("item/price")
if isinstance(price, list):
# Sometimes we end up buying more than one item at once
price = sum(price)
@ -318,14 +338,14 @@ class SoundVoltexGravityWars(
if result == 0:
# Transaction is valid, update the profile with new packets and blocks
profile.replace_int('packet', packet)
profile.replace_int('block', block)
profile.replace_int("packet", packet)
profile.replace_int("block", block)
self.put_profile(userid, profile)
# If this was a song unlock, we should mark it as unlocked
item_type = request.child_value('item/item_type')
item_id = request.child_value('item/item_id')
param = request.child_value('item/param')
item_type = request.child_value("item/item_type")
item_id = request.child_value("item/item_id")
param = request.child_value("item/param")
if not isinstance(item_type, list):
# Sometimes we buy multiple things at once. Make it easier by always assuming this.
@ -339,9 +359,9 @@ class SoundVoltexGravityWars(
self.version,
userid,
item_id[i],
f'item_{item_type[i]}',
f"item_{item_type[i]}",
{
'param': param[i],
"param": param[i],
},
)
@ -351,23 +371,23 @@ class SoundVoltexGravityWars(
block = 0
result = 1
game = Node.void('game_3')
game.add_child(Node.u32('gamecoin_packet', packet))
game.add_child(Node.u32('gamecoin_block', block))
game.add_child(Node.s8('result', result))
game = Node.void("game_3")
game.add_child(Node.u32("gamecoin_packet", packet))
game.add_child(Node.u32("gamecoin_block", block))
game.add_child(Node.s8("result", result))
return game
def handle_game_3_new_request(self, request: Node) -> Node:
refid = request.child_value('refid')
name = request.child_value('name')
loc = ID.parse_machine_id(request.child_value('locid'))
refid = request.child_value("refid")
name = request.child_value("name")
loc = ID.parse_machine_id(request.child_value("locid"))
self.new_profile_by_refid(refid, name, loc)
root = Node.void('game_3')
root = Node.void("game_3")
return root
def handle_game_3_load_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
root = self.get_profile_by_refid(refid)
if root is not None:
return root
@ -386,17 +406,17 @@ class SoundVoltexGravityWars(
profile = None
if profile is not None:
root = Node.void('game_3')
root.add_child(Node.u8('result', 2))
root.add_child(Node.string('name', profile.get_str('name')))
root = Node.void("game_3")
root.add_child(Node.u8("result", 2))
root.add_child(Node.string("name", profile.get_str("name")))
return root
else:
root = Node.void('game_3')
root.add_child(Node.u8('result', 1))
root = Node.void("game_3")
root.add_child(Node.u8("result", 1))
return root
def handle_game_3_save_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -412,10 +432,10 @@ class SoundVoltexGravityWars(
if userid is not None and newprofile is not None:
self.put_profile(userid, newprofile)
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_load_m_request(self, request: Node) -> Node:
refid = request.child_value('dataid')
refid = request.child_value("dataid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -428,28 +448,37 @@ class SoundVoltexGravityWars(
scores = []
# Output to the game
game = Node.void('game_3')
new = Node.void('new')
game = Node.void("game_3")
new = Node.void("new")
game.add_child(new)
for score in scores:
music = Node.void('music')
music = Node.void("music")
new.add_child(music)
music.add_child(Node.u32('music_id', score.id))
music.add_child(Node.u32('music_type', score.chart))
music.add_child(Node.u32('score', score.points))
music.add_child(Node.u32('cnt', score.plays))
music.add_child(Node.u32('clear_type', self.__db_to_game_clear_type(score.data.get_int('clear_type'))))
music.add_child(Node.u32('score_grade', self.__db_to_game_grade(score.data.get_int('grade'))))
stats = score.data.get_dict('stats')
music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate')))
music.add_child(Node.u32('long_rate', stats.get_int('long_rate')))
music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate')))
music.add_child(Node.u32("music_id", score.id))
music.add_child(Node.u32("music_type", score.chart))
music.add_child(Node.u32("score", score.points))
music.add_child(Node.u32("cnt", score.plays))
music.add_child(
Node.u32(
"clear_type",
self.__db_to_game_clear_type(score.data.get_int("clear_type")),
)
)
music.add_child(
Node.u32(
"score_grade", self.__db_to_game_grade(score.data.get_int("grade"))
)
)
stats = score.data.get_dict("stats")
music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate")))
music.add_child(Node.u32("long_rate", stats.get_int("long_rate")))
music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate")))
return game
def handle_game_3_save_m_request(self, request: Node) -> Node:
refid = request.child_value('refid')
refid = request.child_value("refid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -457,19 +486,19 @@ class SoundVoltexGravityWars(
userid = None
# Doesn't matter if userid is None here, that's an anonymous score
musicid = request.child_value('music_id')
chart = request.child_value('music_type')
points = request.child_value('score')
combo = request.child_value('max_chain')
clear_type = self.__game_to_db_clear_type(request.child_value('clear_type'))
grade = self.__game_to_db_grade(request.child_value('score_grade'))
musicid = request.child_value("music_id")
chart = request.child_value("music_type")
points = request.child_value("score")
combo = request.child_value("max_chain")
clear_type = self.__game_to_db_clear_type(request.child_value("clear_type"))
grade = self.__game_to_db_grade(request.child_value("score_grade"))
stats = {
'btn_rate': request.child_value('btn_rate'),
'long_rate': request.child_value('long_rate'),
'vol_rate': request.child_value('vol_rate'),
'critical': request.child_value('critical'),
'near': request.child_value('near'),
'error': request.child_value('error'),
"btn_rate": request.child_value("btn_rate"),
"long_rate": request.child_value("long_rate"),
"vol_rate": request.child_value("vol_rate"),
"critical": request.child_value("critical"),
"near": request.child_value("near"),
"error": request.child_value("error"),
}
# Save the score
@ -485,10 +514,10 @@ class SoundVoltexGravityWars(
)
# Return a blank response
return Node.void('game_3')
return Node.void("game_3")
def handle_game_3_save_c_request(self, request: Node) -> Node:
refid = request.child_value('dataid')
refid = request.child_value("dataid")
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
@ -496,27 +525,29 @@ class SoundVoltexGravityWars(
userid = None
if userid is not None:
course_id = request.child_value('crsid')
clear_type = request.child_value('ct')
achievement_rate = request.child_value('ar')
season_id = request.child_value('ssnid')
course_id = request.child_value("crsid")
clear_type = request.child_value("ct")
achievement_rate = request.child_value("ar")
season_id = request.child_value("ssnid")
# Do not update the course achievement when old achievement rate is greater.
old = self.data.local.user.get_achievement(self.game, self.version, userid, (season_id * 100) + course_id, 'course')
if old is not None and old.get_int('achievement_rate') > achievement_rate:
return Node.void('game_3')
old = self.data.local.user.get_achievement(
self.game, self.version, userid, (season_id * 100) + course_id, "course"
)
if old is not None and old.get_int("achievement_rate") > achievement_rate:
return Node.void("game_3")
self.data.local.user.put_achievement(
self.game,
self.version,
userid,
(season_id * 100) + course_id,
'course',
"course",
{
'clear_type': clear_type,
'achievement_rate': achievement_rate,
"clear_type": clear_type,
"achievement_rate": achievement_rate,
},
)
# Return a blank response
return Node.void('game_3')
return Node.void("game_3")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,12 @@ class BaseClient:
CARD_BAD_PIN: Final[int] = 116
CARD_NOT_ALLOWED: Final[int] = 110
CORRECT_PASSWORD: Final[str] = '1234'
WRONG_PASSWORD: Final[str] = '4321'
CORRECT_PASSWORD: Final[str] = "1234"
WRONG_PASSWORD: Final[str] = "4321"
def __init__(self, proto: ClientProtocol, pcbid: str, config: Dict[str, Any]) -> None:
def __init__(
self, proto: ClientProtocol, pcbid: str, config: Dict[str, Any]
) -> None:
self.__proto = proto
self.pcbid = pcbid
self.config = config
@ -31,15 +33,15 @@ class BaseClient:
return "E004" + random_hex_string(12, caps=True)
def call_node(self) -> Node:
call = Node.void('call')
call.set_attribute('model', self.config['model'])
call.set_attribute('srcid', self.pcbid)
call.set_attribute('tag', random_hex_string(8))
call = Node.void("call")
call.set_attribute("model", self.config["model"])
call.set_attribute("srcid", self.pcbid)
call.set_attribute("tag", random_hex_string(8))
return call
def exchange(self, path: str, tree: Node) -> Node:
module = tree.children[0].name
method = tree.children[0].attribute('method')
method = tree.children[0].attribute("method")
return self.__proto.exchange(
f'{path}?model={self.config["model"]}&module={module}&method={method}',
@ -47,12 +49,12 @@ class BaseClient:
)
def __assert_path(self, root: Node, path: str) -> bool:
parts = path.split('/')
parts = path.split("/")
children = [root]
node: Optional[Node] = None
for part in parts:
if part[0] == '@':
if part[0] == "@":
# Verify attribute, should be last part in chain so
# assume its the first node
if node is None:
@ -88,77 +90,79 @@ class BaseClient:
"""
if not self.__assert_path(root, path):
raise Exception(f'Path \'{path}\' not found in root node:\n{root}')
raise Exception(f"Path '{path}' not found in root node:\n{root}")
def verify_services_get(self, expected_services: List[str]=[], include_net: bool = False) -> None:
def verify_services_get(
self, expected_services: List[str] = [], include_net: bool = False
) -> None:
call = self.call_node()
# Construct node
services = Node.void('services')
services = Node.void("services")
call.add_child(services)
services.set_attribute('method', 'get')
services.set_attribute("method", "get")
if self.config['avs'] is not None:
if self.config["avs"] is not None:
# Some older games don't include this info
info = Node.void('info')
info = Node.void("info")
services.add_child(info)
info.add_child(Node.string('AVS2', self.config['avs']))
info.add_child(Node.string("AVS2", self.config["avs"]))
if include_net:
net = Node.void('net')
net = Node.void("net")
services.add_child(net)
iface = Node.void('if')
iface = Node.void("if")
net.add_child(iface)
iface.add_child(Node.u8('id', 0))
iface.add_child(Node.bool('valid', True))
iface.add_child(Node.u8('type', 1))
iface.add_child(Node.u8_array('mac', [1, 2, 3, 4, 5, 6]))
iface.add_child(Node.ipv4('addr', '10.0.0.100'))
iface.add_child(Node.ipv4('bcast', '10.0.0.255'))
iface.add_child(Node.ipv4('netmask', '255.255.255.0'))
iface.add_child(Node.ipv4('gateway', '10.0.0.1'))
iface.add_child(Node.ipv4('dhcp', '10.0.0.1'))
iface.add_child(Node.u8("id", 0))
iface.add_child(Node.bool("valid", True))
iface.add_child(Node.u8("type", 1))
iface.add_child(Node.u8_array("mac", [1, 2, 3, 4, 5, 6]))
iface.add_child(Node.ipv4("addr", "10.0.0.100"))
iface.add_child(Node.ipv4("bcast", "10.0.0.255"))
iface.add_child(Node.ipv4("netmask", "255.255.255.0"))
iface.add_child(Node.ipv4("gateway", "10.0.0.1"))
iface.add_child(Node.ipv4("dhcp", "10.0.0.1"))
# Swap with server
resp = self.exchange('core/services', call)
resp = self.exchange("core/services", call)
# Verify that response is correct
self.assert_path(resp, "response/services")
items = resp.child('services').children
items = resp.child("services").children
returned_services = []
for item in items:
# Make sure it is an item with a url component
self.assert_path(item, 'item/@url')
self.assert_path(item, "item/@url")
# Get list of services provided
returned_services.append(item.attribute('name'))
returned_services.append(item.attribute("name"))
for service in expected_services:
if service not in returned_services:
raise Exception(f'Service \'{service}\' expected but not returned')
raise Exception(f"Service '{service}' expected but not returned")
def verify_pcbtracker_alive(self, ecflag: int = 1) -> bool:
call = self.call_node()
# Construct node
pcbtracker = Node.void('pcbtracker')
pcbtracker = Node.void("pcbtracker")
call.add_child(pcbtracker)
pcbtracker.set_attribute('accountid', self.pcbid)
pcbtracker.set_attribute('ecflag', str(ecflag))
pcbtracker.set_attribute('hardid', '01000027584F6D3A')
pcbtracker.set_attribute('method', 'alive')
pcbtracker.set_attribute('softid', '00010203040506070809')
pcbtracker.set_attribute("accountid", self.pcbid)
pcbtracker.set_attribute("ecflag", str(ecflag))
pcbtracker.set_attribute("hardid", "01000027584F6D3A")
pcbtracker.set_attribute("method", "alive")
pcbtracker.set_attribute("softid", "00010203040506070809")
# Swap with server
resp = self.exchange('core/pcbtracker', call)
resp = self.exchange("core/pcbtracker", call)
# Verify that response is correct
self.assert_path(resp, "response/pcbtracker/@ecenable")
# Print out setting
enable = int(resp.child('pcbtracker').attribute('ecenable'))
enable = int(resp.child("pcbtracker").attribute("ecenable"))
if enable != 0:
return True
return False
@ -167,12 +171,12 @@ class BaseClient:
call = self.call_node()
# Construct node
message = Node.void('message')
message = Node.void("message")
call.add_child(message)
message.set_attribute('method', 'get')
message.set_attribute("method", "get")
# Swap with server
resp = self.exchange('core/message', call)
resp = self.exchange("core/message", call)
# Verify that response is correct
self.assert_path(resp, "response/message/@status")
@ -181,13 +185,13 @@ class BaseClient:
call = self.call_node()
# Construct node
dlstatus = Node.void('dlstatus')
dlstatus = Node.void("dlstatus")
call.add_child(dlstatus)
dlstatus.set_attribute('method', 'progress')
dlstatus.add_child(Node.s32('progress', 0))
dlstatus.set_attribute("method", "progress")
dlstatus.add_child(Node.s32("progress", 0))
# Swap with server
resp = self.exchange('core/dlstatus', call)
resp = self.exchange("core/dlstatus", call)
# Verify that response is correct
self.assert_path(resp, "response/dlstatus/@status")
@ -196,28 +200,28 @@ class BaseClient:
call = self.call_node()
# Construct node
package = Node.void('package')
package = Node.void("package")
call.add_child(package)
package.set_attribute('method', 'list')
package.set_attribute('pkgtype', 'all')
package.set_attribute("method", "list")
package.set_attribute("pkgtype", "all")
# Swap with server
resp = self.exchange('core/package', call)
resp = self.exchange("core/package", call)
# Verify that response is correct
self.assert_path(resp, "response/package")
def verify_facility_get(self, encoding: str='SHIFT_JIS') -> str:
def verify_facility_get(self, encoding: str = "SHIFT_JIS") -> str:
call = self.call_node()
# Construct node
facility = Node.void('facility')
facility = Node.void("facility")
call.add_child(facility)
facility.set_attribute('encoding', encoding)
facility.set_attribute('method', 'get')
facility.set_attribute("encoding", encoding)
facility.set_attribute("method", "get")
# Swap with server
resp = self.exchange('core/facility', call)
resp = self.exchange("core/facility", call)
# Verify that response is correct
self.assert_path(resp, "response/facility/location/id")
@ -226,199 +230,220 @@ class BaseClient:
self.assert_path(resp, "response/facility/public")
self.assert_path(resp, "response/facility/share")
return resp.child_value('facility/location/id')
return resp.child_value("facility/location/id")
def verify_pcbevent_put(self) -> None:
call = self.call_node()
# Construct node
pcbevent = Node.void('pcbevent')
pcbevent = Node.void("pcbevent")
call.add_child(pcbevent)
pcbevent.set_attribute('method', 'put')
pcbevent.add_child(Node.time('time', int(time.time())))
pcbevent.add_child(Node.u32('seq', 0))
pcbevent.set_attribute("method", "put")
pcbevent.add_child(Node.time("time", int(time.time())))
pcbevent.add_child(Node.u32("seq", 0))
item = Node.void('item')
item = Node.void("item")
pcbevent.add_child(item)
item.add_child(Node.string('name', 'boot'))
item.add_child(Node.s32('value', 1))
item.add_child(Node.time('time', int(time.time())))
item.add_child(Node.string("name", "boot"))
item.add_child(Node.s32("value", 1))
item.add_child(Node.time("time", int(time.time())))
# Swap with server
resp = self.exchange('core/pcbevent', call)
resp = self.exchange("core/pcbevent", call)
# Verify that response is correct
self.assert_path(resp, "response/pcbevent")
def verify_cardmng_inquire(self, card_id: str, msg_type: str, paseli_enabled: bool) -> Optional[str]:
def verify_cardmng_inquire(
self, card_id: str, msg_type: str, paseli_enabled: bool
) -> Optional[str]:
call = self.call_node()
# Construct node
cardmng = Node.void('cardmng')
cardmng = Node.void("cardmng")
call.add_child(cardmng)
cardmng.set_attribute('cardid', card_id)
cardmng.set_attribute('cardtype', '1')
cardmng.set_attribute('method', 'inquire')
cardmng.set_attribute('update', '0')
if msg_type == 'new' and 'old_profile_model' in self.config:
cardmng.set_attribute('model', self.config['old_profile_model'])
cardmng.set_attribute("cardid", card_id)
cardmng.set_attribute("cardtype", "1")
cardmng.set_attribute("method", "inquire")
cardmng.set_attribute("update", "0")
if msg_type == "new" and "old_profile_model" in self.config:
cardmng.set_attribute("model", self.config["old_profile_model"])
# Swap with server
resp = self.exchange('core/cardmng', call)
resp = self.exchange("core/cardmng", call)
if msg_type == 'unregistered':
if msg_type == "unregistered":
# Verify that response is correct
self.assert_path(resp, "response/cardmng/@status")
# Verify that we weren't found
status = int(resp.child('cardmng').attribute('status'))
status = int(resp.child("cardmng").attribute("status"))
if status != self.CARD_NEW:
raise Exception(f'Card \'{card_id}\' returned invalid status \'{status}\'')
raise Exception(f"Card '{card_id}' returned invalid status '{status}'")
# Nothing to return
return None
elif msg_type == 'new':
elif msg_type == "new":
# Verify that response is correct
self.assert_path(resp, "response/cardmng/@refid")
self.assert_path(resp, "response/cardmng/@binded")
self.assert_path(resp, "response/cardmng/@newflag")
self.assert_path(resp, "response/cardmng/@ecflag")
binded = int(resp.child('cardmng').attribute('binded'))
newflag = int(resp.child('cardmng').attribute('newflag'))
ecflag = int(resp.child('cardmng').attribute('ecflag'))
binded = int(resp.child("cardmng").attribute("binded"))
newflag = int(resp.child("cardmng").attribute("newflag"))
ecflag = int(resp.child("cardmng").attribute("ecflag"))
if binded != 0:
raise Exception(f'Card \'{card_id}\' returned invalid binded value \'{binded}\'')
raise Exception(
f"Card '{card_id}' returned invalid binded value '{binded}'"
)
if newflag != 1:
raise Exception(f'Card \'{card_id}\' returned invalid newflag value \'{newflag}\'')
raise Exception(
f"Card '{card_id}' returned invalid newflag value '{newflag}'"
)
if ecflag != (1 if paseli_enabled else 0):
raise Exception(f'Card \'{card_id}\' returned invalid ecflag value \'{newflag}\'')
raise Exception(
f"Card '{card_id}' returned invalid ecflag value '{newflag}'"
)
# Return the refid
return resp.child('cardmng').attribute('refid')
elif msg_type == 'query':
return resp.child("cardmng").attribute("refid")
elif msg_type == "query":
# Verify that response is correct
self.assert_path(resp, "response/cardmng/@refid")
self.assert_path(resp, "response/cardmng/@binded")
self.assert_path(resp, "response/cardmng/@newflag")
self.assert_path(resp, "response/cardmng/@ecflag")
binded = int(resp.child('cardmng').attribute('binded'))
newflag = int(resp.child('cardmng').attribute('newflag'))
ecflag = int(resp.child('cardmng').attribute('ecflag'))
binded = int(resp.child("cardmng").attribute("binded"))
newflag = int(resp.child("cardmng").attribute("newflag"))
ecflag = int(resp.child("cardmng").attribute("ecflag"))
if binded != 1:
raise Exception(f'Card \'{card_id}\' returned invalid binded value \'{binded}\'')
raise Exception(
f"Card '{card_id}' returned invalid binded value '{binded}'"
)
if newflag != 0:
raise Exception(f'Card \'{card_id}\' returned invalid newflag value \'{newflag}\'')
raise Exception(
f"Card '{card_id}' returned invalid newflag value '{newflag}'"
)
if ecflag != (1 if paseli_enabled else 0):
raise Exception(f'Card \'{card_id}\' returned invalid ecflag value \'{newflag}\'')
raise Exception(
f"Card '{card_id}' returned invalid ecflag value '{newflag}'"
)
# Return the refid
return resp.child('cardmng').attribute('refid')
return resp.child("cardmng").attribute("refid")
else:
raise Exception(f'Unrecognized message type \'{msg_type}\'')
raise Exception(f"Unrecognized message type '{msg_type}'")
def verify_cardmng_getrefid(self, card_id: str) -> str:
call = self.call_node()
# Construct node
cardmng = Node.void('cardmng')
cardmng = Node.void("cardmng")
call.add_child(cardmng)
cardmng.set_attribute('cardid', card_id)
cardmng.set_attribute('cardtype', '1')
cardmng.set_attribute('method', 'getrefid')
cardmng.set_attribute('newflag', '0')
cardmng.set_attribute('passwd', self.CORRECT_PASSWORD)
cardmng.set_attribute("cardid", card_id)
cardmng.set_attribute("cardtype", "1")
cardmng.set_attribute("method", "getrefid")
cardmng.set_attribute("newflag", "0")
cardmng.set_attribute("passwd", self.CORRECT_PASSWORD)
# Swap with server
resp = self.exchange('core/cardmng', call)
resp = self.exchange("core/cardmng", call)
# Verify that response is correct
self.assert_path(resp, "response/cardmng/@refid")
return resp.child('cardmng').attribute('refid')
return resp.child("cardmng").attribute("refid")
def verify_cardmng_authpass(self, ref_id: str, correct: bool) -> None:
call = self.call_node()
# Construct node
cardmng = Node.void('cardmng')
cardmng = Node.void("cardmng")
call.add_child(cardmng)
cardmng.set_attribute('method', 'authpass')
cardmng.set_attribute('pass', self.CORRECT_PASSWORD if correct else self.CORRECT_PASSWORD[::-1])
cardmng.set_attribute('refid', ref_id)
cardmng.set_attribute("method", "authpass")
cardmng.set_attribute(
"pass", self.CORRECT_PASSWORD if correct else self.CORRECT_PASSWORD[::-1]
)
cardmng.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange('core/cardmng', call)
resp = self.exchange("core/cardmng", call)
# Verify that response is correct
self.assert_path(resp, "response/cardmng/@status")
status = int(resp.child('cardmng').attribute('status'))
status = int(resp.child("cardmng").attribute("status"))
if status != (self.CARD_OK if correct else self.CARD_BAD_PIN):
raise Exception(f'Ref ID \'{ref_id}\' returned invalid status \'{status}\'')
raise Exception(f"Ref ID '{ref_id}' returned invalid status '{status}'")
def verify_eacoin_checkin(self, card_id: str) -> Tuple[str, int]:
call = self.call_node()
# Construct node
eacoin = Node.void('eacoin')
eacoin = Node.void("eacoin")
call.add_child(eacoin)
eacoin.set_attribute('method', 'checkin')
eacoin.add_child(Node.string('cardtype', '1'))
eacoin.add_child(Node.string('cardid', card_id))
eacoin.add_child(Node.string('passwd', self.CORRECT_PASSWORD))
eacoin.add_child(Node.string('ectype', '1'))
eacoin.set_attribute("method", "checkin")
eacoin.add_child(Node.string("cardtype", "1"))
eacoin.add_child(Node.string("cardid", card_id))
eacoin.add_child(Node.string("passwd", self.CORRECT_PASSWORD))
eacoin.add_child(Node.string("ectype", "1"))
# Swap with server
resp = self.exchange('core/eacoin', call)
resp = self.exchange("core/eacoin", call)
# Verify that response is correct
self.assert_path(resp, "response/eacoin/sessid")
self.assert_path(resp, "response/eacoin/balance")
return (resp.child('eacoin').child_value('sessid'), resp.child('eacoin').child_value('balance'))
return (
resp.child("eacoin").child_value("sessid"),
resp.child("eacoin").child_value("balance"),
)
def verify_eacoin_consume(self, sessid: str, balance: int, amount: int) -> None:
call = self.call_node()
# Construct node
eacoin = Node.void('eacoin')
eacoin = Node.void("eacoin")
call.add_child(eacoin)
eacoin.set_attribute('method', 'consume')
eacoin.add_child(Node.string('sessid', sessid))
eacoin.add_child(Node.s16('sequence', 0))
eacoin.add_child(Node.s32('payment', amount))
eacoin.add_child(Node.s32('service', 0))
eacoin.add_child(Node.string('itemtype', '0'))
eacoin.add_child(Node.string('detail', '/eacoin/start_pt1'))
eacoin.set_attribute("method", "consume")
eacoin.add_child(Node.string("sessid", sessid))
eacoin.add_child(Node.s16("sequence", 0))
eacoin.add_child(Node.s32("payment", amount))
eacoin.add_child(Node.s32("service", 0))
eacoin.add_child(Node.string("itemtype", "0"))
eacoin.add_child(Node.string("detail", "/eacoin/start_pt1"))
# Swap with server
resp = self.exchange('core/eacoin', call)
resp = self.exchange("core/eacoin", call)
# Verify that response is correct
self.assert_path(resp, "response/eacoin/balance")
newbalance = resp.child('eacoin').child_value('balance')
newbalance = resp.child("eacoin").child_value("balance")
if balance - amount != newbalance:
raise Exception(f"Expected to get back balance {balance - amount} but got {newbalance}")
raise Exception(
f"Expected to get back balance {balance - amount} but got {newbalance}"
)
def verify_eacoin_checkout(self, session: str) -> None:
call = self.call_node()
# Construct node
eacoin = Node.void('eacoin')
eacoin = Node.void("eacoin")
call.add_child(eacoin)
eacoin.set_attribute('method', 'checkout')
eacoin.add_child(Node.string('sessid', session))
eacoin.set_attribute("method", "checkout")
eacoin.add_child(Node.string("sessid", session))
# Swap with server
resp = self.exchange('core/eacoin', call)
resp = self.exchange("core/eacoin", call)
# Verify that response is correct
self.assert_path(resp, "response/eacoin/@status")
def verify(self, cardid: Optional[str]) -> None:
raise Exception('Override in subclass!')
raise Exception("Override in subclass!")

View File

@ -7,30 +7,30 @@ from bemani.protocol import Node
class TheStarBishiBashiClient(BaseClient):
NAME = ''
NAME = ""
def verify_eventlog_write(self, location: str) -> None:
call = self.call_node()
# Construct node
eventlog = Node.void('eventlog')
eventlog = Node.void("eventlog")
call.add_child(eventlog)
eventlog.set_attribute('method', 'write')
eventlog.add_child(Node.u32('retrycnt', 0))
data = Node.void('data')
eventlog.set_attribute("method", "write")
eventlog.add_child(Node.u32("retrycnt", 0))
data = Node.void("data")
eventlog.add_child(data)
data.add_child(Node.string('eventid', 'S_PWRON'))
data.add_child(Node.s32('eventorder', 0))
data.add_child(Node.u64('pcbtime', int(time.time() * 1000)))
data.add_child(Node.s64('gamesession', -1))
data.add_child(Node.string('strdata1', '1.7.6'))
data.add_child(Node.string('strdata2', ''))
data.add_child(Node.s64('numdata1', 1))
data.add_child(Node.s64('numdata2', 0))
data.add_child(Node.string('locationid', location))
data.add_child(Node.string("eventid", "S_PWRON"))
data.add_child(Node.s32("eventorder", 0))
data.add_child(Node.u64("pcbtime", int(time.time() * 1000)))
data.add_child(Node.s64("gamesession", -1))
data.add_child(Node.string("strdata1", "1.7.6"))
data.add_child(Node.string("strdata2", ""))
data.add_child(Node.s64("numdata1", 1))
data.add_child(Node.s64("numdata2", 0))
data.add_child(Node.string("locationid", location))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/eventlog/gamesession")
@ -42,17 +42,17 @@ class TheStarBishiBashiClient(BaseClient):
call = self.call_node()
# Construct node
system = Node.void('system')
system = Node.void("system")
call.add_child(system)
system.set_attribute('method', 'getmaster')
data = Node.void('data')
system.set_attribute("method", "getmaster")
data = Node.void("data")
system.add_child(data)
data.add_child(Node.string('gamekind', 'IBB'))
data.add_child(Node.string('datatype', 'S_SRVMSG'))
data.add_child(Node.string('datakey', 'INFO'))
data.add_child(Node.string("gamekind", "IBB"))
data.add_child(Node.string("datatype", "S_SRVMSG"))
data.add_child(Node.string("datakey", "INFO"))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/system/result")
@ -62,116 +62,116 @@ class TheStarBishiBashiClient(BaseClient):
# Set up profile write
profiledata = [
b'ffffffff',
b'IBBDAT00',
b'1',
b'0',
b'0',
b'0',
b'0',
b'0',
b'e474c1b',
b'0',
b'0',
b'ff',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'',
b'',
b'',
b'\x96\xa2\x90\xdd\x92\xe8',
b'\x8d\x81\x8d`',
b'',
b'',
b'',
b"ffffffff",
b"IBBDAT00",
b"1",
b"0",
b"0",
b"0",
b"0",
b"0",
b"e474c1b",
b"0",
b"0",
b"ff",
b"0",
b"0",
b"0",
b"0",
b"0",
b"0",
b"0",
b"0.000000",
b"0.000000",
b"0.000000",
b"0.000000",
b"0.000000",
b"0.000000",
b"0.000000",
b"0.000000",
b"",
b"",
b"",
b"\x96\xa2\x90\xdd\x92\xe8",
b"\x8d\x81\x8d`",
b"",
b"",
b"",
]
if msg_type == 'new':
if msg_type == "new":
# New profile gets blank name, because we save over it at the end of the round.
profiledata[27] = b''
elif msg_type == 'existing':
profiledata[27] = b""
elif msg_type == "existing":
# Exiting profile gets our hardcoded name saved.
profiledata[27] = self.NAME.encode('shift-jis')
profiledata[27] = self.NAME.encode("shift-jis")
# Construct node
playerdata = Node.void('playerdata')
playerdata = Node.void("playerdata")
call.add_child(playerdata)
playerdata.set_attribute('method', 'usergamedata_send')
playerdata.add_child(Node.u32('retrycnt', 0))
playerdata.set_attribute("method", "usergamedata_send")
playerdata.add_child(Node.u32("retrycnt", 0))
data = Node.void('data')
data = Node.void("data")
playerdata.add_child(data)
data.add_child(Node.string('eaid', ref_id))
data.add_child(Node.string('gamekind', 'IBB'))
data.add_child(Node.u32('datanum', 1))
record = Node.void('record')
data.add_child(Node.string("eaid", ref_id))
data.add_child(Node.string("gamekind", "IBB"))
data.add_child(Node.u32("datanum", 1))
record = Node.void("record")
data.add_child(record)
d = Node.string('d', base64.b64encode(b','.join(profiledata)).decode('ascii'))
d = Node.string("d", base64.b64encode(b",".join(profiledata)).decode("ascii"))
record.add_child(d)
d.add_child(Node.string('bin1', ''))
d.add_child(Node.string("bin1", ""))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
self.assert_path(resp, "response/playerdata/result")
def verify_usergamedata_recv(self, ref_id: str) -> str:
call = self.call_node()
# Construct node
playerdata = Node.void('playerdata')
playerdata = Node.void("playerdata")
call.add_child(playerdata)
playerdata.set_attribute('method', 'usergamedata_recv')
data = Node.void('data')
playerdata.set_attribute("method", "usergamedata_recv")
data = Node.void("data")
playerdata.add_child(data)
data.add_child(Node.string('eaid', ref_id))
data.add_child(Node.string('gamekind', 'IBB'))
data.add_child(Node.u32('recv_num', 1))
data.add_child(Node.string('recv_csv', 'IBBDAT00,3fffffffff'))
data.add_child(Node.string("eaid", ref_id))
data.add_child(Node.string("gamekind", "IBB"))
data.add_child(Node.u32("recv_num", 1))
data.add_child(Node.string("recv_csv", "IBBDAT00,3fffffffff"))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
self.assert_path(resp, "response/playerdata/result")
self.assert_path(resp, "response/playerdata/player/record/d/bin1")
self.assert_path(resp, "response/playerdata/player/record_num")
# Grab binary data, parse out name
bindata = resp.child_value('playerdata/player/record/d')
profiledata = base64.b64decode(bindata).split(b',')
bindata = resp.child_value("playerdata/player/record/d")
profiledata = base64.b64decode(bindata).split(b",")
# We lob off the first two values in returning profile, so the name is offset by two
return profiledata[25].decode('shift-jis')
return profiledata[25].decode("shift-jis")
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'
"pcbtracker",
"pcbevent",
"local",
"message",
"facility",
"cardmng",
"package",
"posevent",
"pkglist",
"dlstatus",
"eacoin",
"lobby",
"ntp",
"keepalive",
]
)
paseli_enabled = self.verify_pcbtracker_alive()
@ -190,33 +190,43 @@ class TheStarBishiBashiClient(BaseClient):
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)
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')
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")
# Bishi doesn't read a new profile, it just writes out CSV for a blank one
self.verify_usergamedata_send(ref_id, msg_type='new')
self.verify_usergamedata_send(ref_id, msg_type="new")
else:
print("Skipping new card checks for existing card")
ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled)
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')
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")
if cardid is None:
# Verify profile saving
name = self.verify_usergamedata_recv(ref_id)
if name != '':
raise Exception('New profile has a name associated with it!')
if name != "":
raise Exception("New profile has a name associated with it!")
self.verify_usergamedata_send(ref_id, msg_type='existing')
self.verify_usergamedata_send(ref_id, msg_type="existing")
name = self.verify_usergamedata_recv(ref_id)
if name != self.NAME:
raise Exception('Existing profile has no name associated with it!')
raise Exception("Existing profile has no name associated with it!")
else:
print("Skipping score checks for existing card")

View File

@ -1,9 +1,9 @@
import random
def random_hex_string(length: int, caps: bool=False) -> str:
def random_hex_string(length: int, caps: bool = False) -> str:
if caps:
string = '0123456789ABCDEF'
string = "0123456789ABCDEF"
else:
string = '0123456789abcdef'
return ''.join([random.choice(string) for x in range(length)])
string = "0123456789abcdef"
return "".join([random.choice(string) for x in range(length)])

File diff suppressed because it is too large Load Diff

View File

@ -7,54 +7,54 @@ from bemani.protocol import Node
class DDR2014Client(BaseClient):
NAME = 'TEST'
NAME = "TEST"
def verify_game_shop(self, loc: str) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'shop')
game.set_attribute('area', '51')
game.set_attribute('boot', '34')
game.set_attribute('close', '0')
game.set_attribute('close_t', '0')
game.set_attribute('coin', '02.01.--.--.01.G')
game.set_attribute('diff', '3')
game.set_attribute('during', '1')
game.set_attribute('edit_cnt', '0')
game.set_attribute('edit_used', '1')
game.set_attribute('first', '1')
game.set_attribute('ip', '1.5.7.3')
game.set_attribute('is_freefirstplay', '1')
game.set_attribute('is_paseli', '1')
game.set_attribute('loc', loc)
game.set_attribute('mac', '00:11:22:33:44:55')
game.set_attribute('machine', '2')
game.set_attribute('name', '')
game.set_attribute('pay', '0')
game.set_attribute('region', '.')
game.set_attribute('soft', self.config['model'])
game.set_attribute('softid', self.pcbid)
game.set_attribute('stage', '1')
game.set_attribute('time', '60')
game.set_attribute('type', '0')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "shop")
game.set_attribute("area", "51")
game.set_attribute("boot", "34")
game.set_attribute("close", "0")
game.set_attribute("close_t", "0")
game.set_attribute("coin", "02.01.--.--.01.G")
game.set_attribute("diff", "3")
game.set_attribute("during", "1")
game.set_attribute("edit_cnt", "0")
game.set_attribute("edit_used", "1")
game.set_attribute("first", "1")
game.set_attribute("ip", "1.5.7.3")
game.set_attribute("is_freefirstplay", "1")
game.set_attribute("is_paseli", "1")
game.set_attribute("loc", loc)
game.set_attribute("mac", "00:11:22:33:44:55")
game.set_attribute("machine", "2")
game.set_attribute("name", "")
game.set_attribute("pay", "0")
game.set_attribute("region", ".")
game.set_attribute("soft", self.config["model"])
game.set_attribute("softid", self.pcbid)
game.set_attribute("stage", "1")
game.set_attribute("time", "60")
game.set_attribute("type", "0")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/@stop")
def verify_game_common(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'common')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "common")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/flag/@id")
@ -67,159 +67,159 @@ class DDR2014Client(BaseClient):
def verify_game_hiscore(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'hiscore')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "hiscore")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
for child in resp.child('game').children:
self.assert_path(child, 'music/@reclink_num')
self.assert_path(child, 'music/type/@diff')
self.assert_path(child, 'music/type/name')
self.assert_path(child, 'music/type/score')
self.assert_path(child, 'music/type/area')
self.assert_path(child, 'music/type/rank')
self.assert_path(child, 'music/type/combo_type')
self.assert_path(child, 'music/type/code')
for child in resp.child("game").children:
self.assert_path(child, "music/@reclink_num")
self.assert_path(child, "music/type/@diff")
self.assert_path(child, "music/type/name")
self.assert_path(child, "music/type/score")
self.assert_path(child, "music/type/area")
self.assert_path(child, "music/type/rank")
self.assert_path(child, "music/type/combo_type")
self.assert_path(child, "music/type/code")
def verify_game_area_hiscore(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'area_hiscore')
game.set_attribute('shop_area', '51')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "area_hiscore")
game.set_attribute("shop_area", "51")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
for child in resp.child('game').children:
self.assert_path(child, 'music/@reclink_num')
self.assert_path(child, 'music/type/@diff')
self.assert_path(child, 'music/type/name')
self.assert_path(child, 'music/type/score')
self.assert_path(child, 'music/type/area')
self.assert_path(child, 'music/type/rank')
self.assert_path(child, 'music/type/combo_type')
self.assert_path(child, 'music/type/code')
for child in resp.child("game").children:
self.assert_path(child, "music/@reclink_num")
self.assert_path(child, "music/type/@diff")
self.assert_path(child, "music/type/name")
self.assert_path(child, "music/type/score")
self.assert_path(child, "music/type/area")
self.assert_path(child, "music/type/rank")
self.assert_path(child, "music/type/combo_type")
self.assert_path(child, "music/type/code")
def verify_game_message(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'message')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "message")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_ranking(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'ranking')
game.set_attribute('max', '10')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "ranking")
game.set_attribute("max", "10")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_log(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'log')
game.set_attribute('type', '0')
game.set_attribute('soft', self.config['model'])
game.set_attribute('softid', self.pcbid)
game.set_attribute('ver', '2014102700')
game.set_attribute('boot', '34')
game.set_attribute('mac', '00:11:22:33:44:55')
clear = Node.void('clear')
game.set_attribute("method", "log")
game.set_attribute("type", "0")
game.set_attribute("soft", self.config["model"])
game.set_attribute("softid", self.pcbid)
game.set_attribute("ver", "2014102700")
game.set_attribute("boot", "34")
game.set_attribute("mac", "00:11:22:33:44:55")
clear = Node.void("clear")
game.add_child(clear)
clear.set_attribute('book', '0')
clear.set_attribute('edit', '0')
clear.set_attribute('rank', '0')
clear.set_attribute('set', '0')
auto = Node.void('auto')
clear.set_attribute("book", "0")
clear.set_attribute("edit", "0")
clear.set_attribute("rank", "0")
clear.set_attribute("set", "0")
auto = Node.void("auto")
game.add_child(auto)
auto.set_attribute('book', '1')
auto.set_attribute('edit', '1')
auto.set_attribute('rank', '1')
auto.set_attribute('set', '1')
auto.set_attribute("book", "1")
auto.set_attribute("edit", "1")
auto.set_attribute("rank", "1")
auto.set_attribute("set", "1")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_tax_info(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'tax_info')
game.set_attribute('ver', '2014102700')
game.set_attribute("method", "tax_info")
game.set_attribute("ver", "2014102700")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/tax_info/@tax_phase")
def verify_game_recorder(self) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'recorder')
game.set_attribute('assert_cnt', '0')
game.set_attribute('assert_info', '')
game.set_attribute('assert_path', '')
game.set_attribute('assert_time', '0')
game.set_attribute('boot_time', '1706151228')
game.set_attribute('cnt_demo', '1')
game.set_attribute('cnt_music', '1')
game.set_attribute('cnt_play', '0')
game.set_attribute('last_mid', '481')
game.set_attribute('last_seq', '36')
game.set_attribute('last_step', '0')
game.set_attribute('last_time', '1706151235')
game.set_attribute('softcode', self.config['model'])
game.set_attribute('temp_seq', '15')
game.set_attribute('temp_step', '8')
game.set_attribute('temp_time', '1706151234')
game.set_attribute('wd_restart', '0')
game.set_attribute("method", "recorder")
game.set_attribute("assert_cnt", "0")
game.set_attribute("assert_info", "")
game.set_attribute("assert_path", "")
game.set_attribute("assert_time", "0")
game.set_attribute("boot_time", "1706151228")
game.set_attribute("cnt_demo", "1")
game.set_attribute("cnt_music", "1")
game.set_attribute("cnt_play", "0")
game.set_attribute("last_mid", "481")
game.set_attribute("last_seq", "36")
game.set_attribute("last_step", "0")
game.set_attribute("last_time", "1706151235")
game.set_attribute("softcode", self.config["model"])
game.set_attribute("temp_seq", "15")
game.set_attribute("temp_step", "8")
game.set_attribute("temp_time", "1706151234")
game.set_attribute("wd_restart", "0")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_lock(self, ref_id: str, play: int) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('refid', ref_id)
game.set_attribute('method', 'lock')
game.set_attribute('ver', '2014102700')
game.set_attribute('play', str(play))
game.set_attribute("refid", ref_id)
game.set_attribute("method", "lock")
game.set_attribute("ver", "2014102700")
game.set_attribute("play", str(play))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game/@now_login")
@ -228,34 +228,34 @@ class DDR2014Client(BaseClient):
# Pad the name to 8 characters
name = self.NAME[:8]
while len(name) < 8:
name = name + ' '
name = name + " "
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'new')
game.set_attribute('ver', '2014102700')
game.set_attribute('name', name)
game.set_attribute('area', '51')
game.set_attribute('old', '0')
game.set_attribute('refid', ref_id)
game.set_attribute("method", "new")
game.set_attribute("ver", "2014102700")
game.set_attribute("name", name)
game.set_attribute("area", "51")
game.set_attribute("old", "0")
game.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_load_daily(self, ref_id: str) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'load_daily')
game.set_attribute('ver', '2014102700')
game.set_attribute('refid', ref_id)
game.set_attribute("method", "load_daily")
game.set_attribute("ver", "2014102700")
game.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
self.assert_path(resp, "response/game/daycount/@playcount")
self.assert_path(resp, "response/game/dailycombo/@daily_combo")
@ -263,20 +263,20 @@ class DDR2014Client(BaseClient):
def verify_game_load(self, ref_id: str, msg_type: str) -> Dict[str, Any]:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'load')
game.set_attribute('ver', '2014102700')
game.set_attribute('refid', ref_id)
game.set_attribute("method", "load")
game.set_attribute("ver", "2014102700")
game.set_attribute("refid", ref_id)
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
if msg_type == 'new':
if msg_type == "new":
# Verify that response is correct
self.assert_path(resp, "response/game/@none")
return {}
if msg_type == 'existing':
if msg_type == "existing":
# Verify existing profile and return info
self.assert_path(resp, "response/game/seq")
self.assert_path(resp, "response/game/code")
@ -345,63 +345,63 @@ class DDR2014Client(BaseClient):
for i in range(55):
self.assert_path(resp, f"response/game/play_area/@play_cnt{i}")
gr_s = resp.child('game/gr_s')
gr_d = resp.child('game/gr_d')
gr_s = resp.child("game/gr_s")
gr_d = resp.child("game/gr_d")
return {
'name': resp.child_value('game/name'),
'ext_id': resp.child_value('game/code'),
'single_plays': resp.child_value('game/cnt_s'),
'double_plays': resp.child_value('game/cnt_d'),
'groove_single': [
int(gr_s.attribute('gr1')),
int(gr_s.attribute('gr2')),
int(gr_s.attribute('gr3')),
int(gr_s.attribute('gr4')),
int(gr_s.attribute('gr5')),
"name": resp.child_value("game/name"),
"ext_id": resp.child_value("game/code"),
"single_plays": resp.child_value("game/cnt_s"),
"double_plays": resp.child_value("game/cnt_d"),
"groove_single": [
int(gr_s.attribute("gr1")),
int(gr_s.attribute("gr2")),
int(gr_s.attribute("gr3")),
int(gr_s.attribute("gr4")),
int(gr_s.attribute("gr5")),
],
'groove_double': [
int(gr_d.attribute('gr1')),
int(gr_d.attribute('gr2')),
int(gr_d.attribute('gr3')),
int(gr_d.attribute('gr4')),
int(gr_d.attribute('gr5')),
"groove_double": [
int(gr_d.attribute("gr1")),
int(gr_d.attribute("gr2")),
int(gr_d.attribute("gr3")),
int(gr_d.attribute("gr4")),
int(gr_d.attribute("gr5")),
],
}
raise Exception('Unknown load type!')
raise Exception("Unknown load type!")
def verify_game_load_m(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('ver', '2014102700')
game.set_attribute('all', '1')
game.set_attribute('refid', ref_id)
game.set_attribute('method', 'load_m')
game.set_attribute("ver", "2014102700")
game.set_attribute("all", "1")
game.set_attribute("refid", ref_id)
game.set_attribute("method", "load_m")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
scores: Dict[int, Dict[int, Dict[str, Any]]] = {}
self.assert_path(resp, "response/game")
for child in resp.child('game').children:
self.assert_path(child, 'music/@reclink')
reclink = int(child.attribute('reclink'))
for child in resp.child("game").children:
self.assert_path(child, "music/@reclink")
reclink = int(child.attribute("reclink"))
for typenode in child.children:
self.assert_path(typenode, 'type/@diff')
self.assert_path(typenode, 'type/score')
self.assert_path(typenode, 'type/count')
self.assert_path(typenode, 'type/rank')
self.assert_path(typenode, 'type/combo_type')
chart = int(typenode.attribute('diff'))
self.assert_path(typenode, "type/@diff")
self.assert_path(typenode, "type/score")
self.assert_path(typenode, "type/count")
self.assert_path(typenode, "type/rank")
self.assert_path(typenode, "type/combo_type")
chart = int(typenode.attribute("diff"))
vals = {
'score': typenode.child_value('score'),
'count': typenode.child_value('count'),
'rank': typenode.child_value('rank'),
'halo': typenode.child_value('combo_type'),
"score": typenode.child_value("score"),
"count": typenode.child_value("count"),
"rank": typenode.child_value("rank"),
"halo": typenode.child_value("combo_type"),
}
if reclink not in scores:
scores[reclink] = {}
@ -410,91 +410,95 @@ class DDR2014Client(BaseClient):
def verify_game_load_edit(self, ref_id: str) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('ver', '2014102700')
game.set_attribute('pid', '0')
game.set_attribute('refid', ref_id)
game.set_attribute('method', 'load_edit')
game.set_attribute("ver", "2014102700")
game.set_attribute("pid", "0")
game.set_attribute("refid", ref_id)
game.set_attribute("method", "load_edit")
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_save(self, ref_id: str, style: int, gauge: Optional[List[int]]=None) -> None:
def verify_game_save(
self, ref_id: str, style: int, gauge: Optional[List[int]] = None
) -> None:
gauge = gauge or [0, 0, 0, 0, 0]
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'save')
game.set_attribute('refid', ref_id)
game.set_attribute('ver', '2014102700')
game.set_attribute('shop_area', '51')
last = Node.void('last')
game.set_attribute("method", "save")
game.set_attribute("refid", ref_id)
game.set_attribute("ver", "2014102700")
game.set_attribute("shop_area", "51")
last = Node.void("last")
game.add_child(last)
last.set_attribute('mode', '1')
last.set_attribute('style', str(style))
gr = Node.void('gr')
last.set_attribute("mode", "1")
last.set_attribute("style", str(style))
gr = Node.void("gr")
game.add_child(gr)
gr.set_attribute('gr1', str(gauge[0]))
gr.set_attribute('gr2', str(gauge[1]))
gr.set_attribute('gr3', str(gauge[2]))
gr.set_attribute('gr4', str(gauge[3]))
gr.set_attribute('gr5', str(gauge[4]))
gr.set_attribute("gr1", str(gauge[0]))
gr.set_attribute("gr2", str(gauge[1]))
gr.set_attribute("gr3", str(gauge[2]))
gr.set_attribute("gr4", str(gauge[3]))
gr.set_attribute("gr5", str(gauge[4]))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
def verify_game_save_m(self, ref_id: str, ext_id: str, score: Dict[str, Any]) -> None:
def verify_game_save_m(
self, ref_id: str, ext_id: str, score: Dict[str, Any]
) -> None:
call = self.call_node()
game = Node.void('game')
game = Node.void("game")
call.add_child(game)
game.set_attribute('method', 'save_m')
game.set_attribute('diff', '12345')
game.set_attribute('mtype', str(score['chart']))
game.set_attribute('mid', str(score['id']))
game.set_attribute('refid', ref_id)
game.set_attribute('ver', '2014102700')
data = Node.void('data')
game.set_attribute("method", "save_m")
game.set_attribute("diff", "12345")
game.set_attribute("mtype", str(score["chart"]))
game.set_attribute("mid", str(score["id"]))
game.set_attribute("refid", ref_id)
game.set_attribute("ver", "2014102700")
data = Node.void("data")
game.add_child(data)
data.set_attribute('score', str(score['score']))
data.set_attribute('rank', str(score['rank']))
data.set_attribute('shop_area', '0')
data.set_attribute('playmode', '1')
data.set_attribute('combo', str(score['combo']))
data.set_attribute('phase', '1')
data.set_attribute('style', '0')
data.set_attribute('full', '1' if score['halo'] >= 1 else '0')
data.set_attribute('great_fc', '1' if score['halo'] == 1 else '0')
data.set_attribute('good_fc', '1' if score['halo'] == 4 else '0')
data.set_attribute('perf_fc', '1' if score['halo'] == 2 else '0')
gauge = Node.void('gauge')
data.set_attribute("score", str(score["score"]))
data.set_attribute("rank", str(score["rank"]))
data.set_attribute("shop_area", "0")
data.set_attribute("playmode", "1")
data.set_attribute("combo", str(score["combo"]))
data.set_attribute("phase", "1")
data.set_attribute("style", "0")
data.set_attribute("full", "1" if score["halo"] >= 1 else "0")
data.set_attribute("great_fc", "1" if score["halo"] == 1 else "0")
data.set_attribute("good_fc", "1" if score["halo"] == 4 else "0")
data.set_attribute("perf_fc", "1" if score["halo"] == 2 else "0")
gauge = Node.void("gauge")
game.add_child(gauge)
gauge.set_attribute('life8', '0')
gauge.set_attribute('assist', '0')
gauge.set_attribute('risky', '0')
gauge.set_attribute('life4', '0')
gauge.set_attribute('hard', '0')
player = Node.void('player')
gauge.set_attribute("life8", "0")
gauge.set_attribute("assist", "0")
gauge.set_attribute("risky", "0")
gauge.set_attribute("life4", "0")
gauge.set_attribute("hard", "0")
player = Node.void("player")
game.add_child(player)
player.set_attribute('playcnt', '123')
player.set_attribute('code', ext_id)
option = Node.void('option_02')
player.set_attribute("playcnt", "123")
player.set_attribute("code", ext_id)
option = Node.void("option_02")
game.add_child(option)
option.set_attribute('opt02_0', '6')
option.set_attribute('opt02_6', '1')
option.set_attribute('opt02_13', '2')
game.add_child(Node.u8_array('trace', [0] * 512))
game.add_child(Node.u32('size', 512))
option.set_attribute("opt02_0", "6")
option.set_attribute("opt02_6", "1")
option.set_attribute("opt02_13", "2")
game.add_child(Node.u8_array("trace", [0] * 512))
game.add_child(Node.u32("size", 512))
# Swap with server
resp = self.exchange('', call)
resp = self.exchange("", call)
# Verify that response is correct
self.assert_path(resp, "response/game")
@ -503,26 +507,26 @@ class DDR2014Client(BaseClient):
# 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'
"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('EUC_JP')
location = self.verify_facility_get("EUC_JP")
self.verify_pcbevent_put()
self.verify_game_recorder()
self.verify_game_tax_info()
@ -542,24 +546,34 @@ class DDR2014Client(BaseClient):
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)
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')
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")
# Bishi doesn't read a new profile, it just writes out CSV for a blank one
self.verify_game_load(ref_id, msg_type='new')
self.verify_game_load(ref_id, msg_type="new")
self.verify_game_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)
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')
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 locking and unlocking profile ability
self.verify_game_lock(ref_id, 1)
@ -567,53 +581,53 @@ class DDR2014Client(BaseClient):
if cardid is None:
# Verify empty profile
profile = self.verify_game_load(ref_id, msg_type='existing')
ext_id = str(profile['ext_id'])
if profile['name'] != self.NAME:
raise Exception('Profile has invalid name associated with it!')
if profile['single_plays'] != 0:
raise Exception('Profile has plays on single already!')
if profile['double_plays'] != 0:
raise Exception('Profile has plays on double already!')
if any([g != 0 for g in profile['groove_single']]):
raise Exception('Profile has single groove gauge values already!')
if any([g != 0 for g in profile['groove_double']]):
raise Exception('Profile has double groove gauge values already!')
profile = self.verify_game_load(ref_id, msg_type="existing")
ext_id = str(profile["ext_id"])
if profile["name"] != self.NAME:
raise Exception("Profile has invalid name associated with it!")
if profile["single_plays"] != 0:
raise Exception("Profile has plays on single already!")
if profile["double_plays"] != 0:
raise Exception("Profile has plays on double already!")
if any([g != 0 for g in profile["groove_single"]]):
raise Exception("Profile has single groove gauge values already!")
if any([g != 0 for g in profile["groove_double"]]):
raise Exception("Profile has double groove gauge values already!")
# Verify empty scores
scores = self.verify_game_load_m(ref_id)
if len(scores) > 0:
raise Exception('Scores exist on new profile!')
raise Exception("Scores exist on new profile!")
self.verify_game_load_edit(ref_id)
self.verify_game_load_daily(ref_id)
# Verify profile saving
self.verify_game_save(ref_id, 0, [1, 2, 3, 4, 5])
profile = self.verify_game_load(ref_id, msg_type='existing')
if profile['name'] != self.NAME:
raise Exception('Profile has invalid name associated with it!')
if profile['single_plays'] != 1:
raise Exception('Profile has invalid plays on single!')
if profile['double_plays'] != 0:
raise Exception('Profile has invalid plays on double!')
if profile['groove_single'] != [1, 2, 3, 4, 5]:
raise Exception('Profile has invalid single groove gauge values!')
if any([g != 0 for g in profile['groove_double']]):
raise Exception('Profile has invalid double groove gauge values!')
profile = self.verify_game_load(ref_id, msg_type="existing")
if profile["name"] != self.NAME:
raise Exception("Profile has invalid name associated with it!")
if profile["single_plays"] != 1:
raise Exception("Profile has invalid plays on single!")
if profile["double_plays"] != 0:
raise Exception("Profile has invalid plays on double!")
if profile["groove_single"] != [1, 2, 3, 4, 5]:
raise Exception("Profile has invalid single groove gauge values!")
if any([g != 0 for g in profile["groove_double"]]):
raise Exception("Profile has invalid double groove gauge values!")
self.verify_game_save(ref_id, 1, [5, 4, 3, 2, 1])
profile = self.verify_game_load(ref_id, msg_type='existing')
if profile['name'] != self.NAME:
raise Exception('Profile has invalid name associated with it!')
if profile['single_plays'] != 1:
raise Exception('Profile has invalid plays on single!')
if profile['double_plays'] != 1:
raise Exception('Profile has invalid plays on double!')
if profile['groove_single'] != [1, 2, 3, 4, 5]:
raise Exception('Profile has invalid single groove gauge values!')
if profile['groove_double'] != [5, 4, 3, 2, 1]:
raise Exception('Profile has invalid double groove gauge values!')
profile = self.verify_game_load(ref_id, msg_type="existing")
if profile["name"] != self.NAME:
raise Exception("Profile has invalid name associated with it!")
if profile["single_plays"] != 1:
raise Exception("Profile has invalid plays on single!")
if profile["double_plays"] != 1:
raise Exception("Profile has invalid plays on double!")
if profile["groove_single"] != [1, 2, 3, 4, 5]:
raise Exception("Profile has invalid single groove gauge values!")
if profile["groove_double"] != [5, 4, 3, 2, 1]:
raise Exception("Profile has invalid double groove gauge values!")
# Now, write some scores and verify saving
for phase in [1, 2]:
@ -621,71 +635,71 @@ class DDR2014Client(BaseClient):
dummyscores = [
# An okay score on a chart
{
'id': 593,
'chart': 3,
'score': 800000,
'combo': 123,
'rank': 4,
'halo': 1,
"id": 593,
"chart": 3,
"score": 800000,
"combo": 123,
"rank": 4,
"halo": 1,
},
# A good score on an easier chart same song
{
'id': 593,
'chart': 2,
'score': 990000,
'combo': 321,
'rank': 2,
'halo': 2,
"id": 593,
"chart": 2,
"score": 990000,
"combo": 321,
"rank": 2,
"halo": 2,
},
# A perfect score
{
'id': 483,
'chart': 3,
'score': 1000000,
'combo': 400,
'rank': 1,
'halo': 3,
"id": 483,
"chart": 3,
"score": 1000000,
"combo": 400,
"rank": 1,
"halo": 3,
},
# A bad score
{
'id': 483,
'chart': 2,
'score': 100000,
'combo': 5,
'rank': 7,
'halo': 0,
"id": 483,
"chart": 2,
"score": 100000,
"combo": 5,
"rank": 7,
"halo": 0,
},
{
'id': 483,
'chart': 1,
'score': 60000,
'combo': 5,
'rank': 6,
'halo': 4,
"id": 483,
"chart": 1,
"score": 60000,
"combo": 5,
"rank": 6,
"halo": 4,
},
]
if phase == 2:
dummyscores = [
# A better score on a chart
{
'id': 593,
'chart': 3,
'score': 850000,
'combo': 234,
'rank': 3,
'halo': 2,
"id": 593,
"chart": 3,
"score": 850000,
"combo": 234,
"rank": 3,
"halo": 2,
},
# A worse score on another chart
{
'id': 593,
'chart': 2,
'score': 980000,
'combo': 300,
'rank': 3,
'halo': 0,
'expected_score': 990000,
'expected_rank': 2,
'expected_halo': 2,
"id": 593,
"chart": 2,
"score": 980000,
"combo": 300,
"rank": 3,
"halo": 0,
"expected_score": 990000,
"expected_rank": 2,
"expected_halo": 2,
},
]
@ -693,21 +707,29 @@ class DDR2014Client(BaseClient):
self.verify_game_save_m(ref_id, ext_id, score)
scores = self.verify_game_load_m(ref_id)
for score in dummyscores:
data = scores.get(score['id'], {}).get(score['chart'], None)
data = scores.get(score["id"], {}).get(score["chart"], None)
if data is None:
raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!')
raise Exception(
f'Expected to get score back for song {score["id"]} chart {score["chart"]}!'
)
# Verify the attributes of the score
expected_score = score.get('expected_score', score['score'])
expected_rank = score.get('expected_rank', score['rank'])
expected_halo = score.get('expected_halo', score['halo'])
expected_score = score.get("expected_score", score["score"])
expected_rank = score.get("expected_rank", score["rank"])
expected_halo = score.get("expected_halo", score["halo"])
if data['score'] != expected_score:
raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'')
if data['rank'] != expected_rank:
raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'')
if data['halo'] != expected_halo:
raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'')
if data["score"] != expected_score:
raise Exception(
f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\''
)
if data["rank"] != expected_rank:
raise Exception(
f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\''
)
if data["halo"] != expected_halo:
raise Exception(
f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\''
)
# Sleep so we don't end up putting in score history on the same second
time.sleep(1)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More