Add BEMAPI support for Dance Evolution. Not that this is useful for much (except catalog for bootstrapping remotely), but still, nice to have.

This commit is contained in:
Jennifer Taylor 2025-10-01 01:52:48 +00:00
parent dbe4fbc19e
commit 989414b981
8 changed files with 115 additions and 9 deletions

View File

@ -172,6 +172,8 @@ Valid game series and their versions are as follows. Clients and servers should
* ``6`` - Jubeat Prop
* ``7`` - Jubeat Qubell
* ``8`` - Jubeat Clan
* ``9`` - Jubeat Festo
* ``10`` - Jubeat Ave
* ``museca``
* ``1`` - MUSECA
* ``1p`` - MUSECA 1+1/2
@ -185,6 +187,9 @@ Valid game series and their versions are as follows. Clients and servers should
* ``22`` - Pop'n Music Lapistoria
* ``23`` - Pop'n Music Eclale
* ``24`` - Pop'n Music うさぎと猫と少年の夢
* ``25`` - Pop'n Music Peace
* ``26`` - Pop'n Music Kaimei Riddles
* ``27`` - Pop'n Music Unilab
* ``reflecbeat``
* ``1`` - REFLEC BEAT
* ``2`` - REFLEC BEAT limelight
@ -242,6 +247,22 @@ Given that a high score, once set, is immutable, records are easily cached on th
* ``since`` A UTC unix timestamp to constrain the range of records looked up and returned. If provided, records with an update time greater than or equal to this time should be returned, and records less than this time should be excluded.
* ``until`` A UTC unix timestamp to constrain the range of records looked up and returned. If provided, records with an update time less than this time should be returned, and records greater than or equal to this time should be excluded.
### Dance Evolution Additional Attributes and Documentation
Valid charts for Dance Evolution according to the game are as follows:
* ``0`` - Light
* ``1`` - Standard
* ``2`` - Extreme
* ``3`` - Stealth
* ``4`` - Master
The following attributes should be returned (if available) for records belonging to the Dance Evolution series.
* ``grade`` - The letter grade ranking the user has earned, as a string enum. Should be one of the following values: "AAA", "AA", "A", "B", "C", "D", "E", "F".
* ``combo`` - The highest combo achieved during play, as an integer.
* ``full_combo`` - Whether the record represents a full combo or not, as a boolean.
### DDR Additional Attributes and Documentation
Valid charts for DDR according to the game are as follows:
@ -457,6 +478,12 @@ The profile request isn't currently meant to allow instantiation of full game pr
* ``exact`` - This profile is for the requested game/version. Additional attributes specified below are for this game/version.
* ``partial`` - This profile is for the requested game, but a different version. If the server is capable of doing so, additional attributes should be converted for correct consumption for the game/version requested by the client. If it is not possible, they should be set to -1 to indicate they are not available for the requested game/version.
### Dance Evolution Additional Attributes and Documentation
The following attributes should be returned (if available) by all profiles belonging to the Dance Evolution series.
* ``area`` - The string area as set on the cabinet when creating a profile on DDR. If unavailable, this should be set to "".
### DDR Additional Attributes and Documentation
The following attributes should be returned (if available) by all profiles belonging to the DDR series.
@ -511,6 +538,15 @@ Each song object found in the "songs" list should at minimum contain the followi
* ``artist`` - A string specifying the song's artist. If this is unavailable for this song, a blank string should be returned.
* ``genre`` - A string specifying the song's genre. If this is unavailable for this song, a blank string should be returned.
### Dance Evolution Additional Attributes and Documentation
The following attributes should be returned (if available) for all songs belonging to the Dance Evolution series.
* ``level`` - An integer representing the in-game level of the song.
* ``bpm_min`` - An integer representing the minimum BPM of the song.
* ``bpm_max`` - An integer representing the maximum BPM of the song.
* ``kcal`` - A float representing the kcal rating of the song.
### DDR Additional Attributes and Documentation
The following attributes should be returned (if available) for all songs belonging to the DDR series.

View File

@ -211,6 +211,7 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str
("popnmusic", GameConstants.POPN_MUSIC),
("reflecbeat", GameConstants.REFLEC_BEAT),
("soundvoltex", GameConstants.SDVX),
("danceevolution", GameConstants.DANCE_EVOLUTION),
]:
if constant in g.config.support:
gamemapping[gameid] = constant
@ -293,6 +294,9 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str
"3": VersionConstants.SDVX_GRAVITY_WARS,
"4": VersionConstants.SDVX_HEAVENLY_HAVEN,
},
GameConstants.DANCE_EVOLUTION: {
"1": VersionConstants.DANCE_EVOLUTION,
},
}
.get(game, {})
.get(requestversion)

View File

@ -7,6 +7,14 @@ from bemani.data import Song
class CatalogObject(BaseObject):
def __format_danevo_song(self, song: Song) -> Dict[str, Any]:
return {
"level": song.data.get_int("level"),
"bpm_min": song.data.get_int("bpm_min"),
"bpm_max": song.data.get_int("bpm_max"),
"kcal": song.data.get_float("kcal"),
}
def __format_ddr_song(self, song: Song) -> Dict[str, Any]:
groove = song.data.get_dict("groove")
return {
@ -122,6 +130,8 @@ class CatalogObject(BaseObject):
base.update(self.__format_reflec_song(song))
if self.game == GameConstants.SDVX:
base.update(self.__format_sdvx_song(song))
if self.game == GameConstants.DANCE_EVOLUTION:
base.update(self.__format_danevo_song(song))
return base
@ -258,6 +268,12 @@ class CatalogObject(BaseObject):
)
)
songs.extend(additions)
# Always a special case, of course. DanEvo has a virtual chart for play statistics
# tracking, so we need to filter that out here.
if self.game == GameConstants.DANCE_EVOLUTION:
songs = [song for song in songs if song.chart in [0, 1, 2, 3, 4]]
retval = {
"songs": [self.__format_song(song) for song in songs],
}

View File

@ -7,6 +7,11 @@ from bemani.data import UserID
class ProfileObject(BaseObject):
def __format_danevo_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
return {
"area": profile.get_str("area", "") if exact else "",
}
def __format_ddr_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]:
return {
"area": profile.get_int("area", -1) if exact else -1,
@ -71,6 +76,8 @@ class ProfileObject(BaseObject):
base.update(self.__format_reflec_profile(profile, exact))
if self.game == GameConstants.SDVX:
base.update(self.__format_sdvx_profile(profile, exact))
if self.game == GameConstants.DANCE_EVOLUTION:
base.update(self.__format_danevo_profile(profile, exact))
return base

View File

@ -7,6 +7,24 @@ from bemani.data import Score, UserID
class RecordsObject(BaseObject):
def __format_danevo_record(self, record: Score) -> Dict[str, Any]:
grade = {
DBConstants.DANEVO_GRADE_AAA: "AAA",
DBConstants.DANEVO_GRADE_AA: "AA",
DBConstants.DANEVO_GRADE_A: "A",
DBConstants.DANEVO_GRADE_B: "B",
DBConstants.DANEVO_GRADE_C: "C",
DBConstants.DANEVO_GRADE_D: "D",
DBConstants.DANEVO_GRADE_E: "E",
DBConstants.DANEVO_GRADE_FAILED: "F",
}.get(record.data.get_int("grade"), "F")
return {
"grade": grade,
"combo": record.data.get_int("combo"),
"full_combo": record.data.get_bool("full_combo"),
}
def __format_ddr_record(self, record: Score) -> Dict[str, Any]:
halo = {
DBConstants.DDR_HALO_NONE: "none",
@ -217,6 +235,8 @@ class RecordsObject(BaseObject):
base.update(self.__format_reflec_record(record))
if self.game == GameConstants.SDVX:
base.update(self.__format_sdvx_record(record))
if self.game == GameConstants.DANCE_EVOLUTION:
base.update(self.__format_danevo_record(record))
return base
@ -310,6 +330,13 @@ class RecordsObject(BaseObject):
if record.update >= until:
continue
# Dance Evolution is a special case where it stores data in a virtual chart
# to keep track of play counts, due to the game not sending chart back with
# attempts.
if self.game == GameConstants.DANCE_EVOLUTION:
if record.chart not in [0, 1, 2, 3, 4]:
continue
if userid not in id_to_cards:
cards = self.data.local.user.get_cards(userid)
if len(cards) == 0:

View File

@ -188,6 +188,13 @@ class StatisticsObject(BaseObject):
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]:
retval: List[Dict[str, Any]] = []
# Special case, Dance Evolution can't track attempts in any meaningful capacity
# because the game does not actually send down information about the chart for
# given attempts. So, we only know that players plays a song, not what chart or
# whether they cleared it or full-combo'd it.
if self.game == GameConstants.DANCE_EVOLUTION:
return []
# Fetch the attempts
if idtype == APIConstants.ID_TYPE_SERVER:
retval = self.__aggregate_global(

View File

@ -4,7 +4,7 @@ from typing_extensions import Final
from bemani.backend.base import Base
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
from bemani.common import GameConstants, ValidatedDict
from bemani.common import DBConstants, GameConstants, ValidatedDict
from bemani.data import UserID
@ -22,14 +22,14 @@ class DanceEvolutionBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
CHART_TYPE_MASTER: Final[int] = 4
CHART_TYPE_PLAYTRACKING: Final[int] = 5
GRADE_FAILED: Final[int] = 100
GRADE_E: Final[int] = 200
GRADE_D: Final[int] = 300
GRADE_C: Final[int] = 400
GRADE_B: Final[int] = 500
GRADE_A: Final[int] = 600
GRADE_AA: Final[int] = 700
GRADE_AAA: Final[int] = 800
GRADE_FAILED: Final[int] = DBConstants.DANEVO_GRADE_FAILED
GRADE_E: Final[int] = DBConstants.DANEVO_GRADE_E
GRADE_D: Final[int] = DBConstants.DANEVO_GRADE_D
GRADE_C: Final[int] = DBConstants.DANEVO_GRADE_C
GRADE_B: Final[int] = DBConstants.DANEVO_GRADE_B
GRADE_A: Final[int] = DBConstants.DANEVO_GRADE_A
GRADE_AA: Final[int] = DBConstants.DANEVO_GRADE_AA
GRADE_AAA: Final[int] = DBConstants.DANEVO_GRADE_AAA
def previous_version(self) -> Optional["DanceEvolutionBase"]:
"""

View File

@ -179,6 +179,15 @@ class DBConstants:
OMNIMIX_VERSION_BUMP: Final[int] = 10000
DANEVO_GRADE_FAILED: Final[int] = 100
DANEVO_GRADE_E: Final[int] = 200
DANEVO_GRADE_D: Final[int] = 300
DANEVO_GRADE_C: Final[int] = 400
DANEVO_GRADE_B: Final[int] = 500
DANEVO_GRADE_A: Final[int] = 600
DANEVO_GRADE_AA: Final[int] = 700
DANEVO_GRADE_AAA: Final[int] = 800
DDR_HALO_NONE: Final[int] = 100
DDR_HALO_GOOD_FULL_COMBO: Final[int] = 200
DDR_HALO_GREAT_FULL_COMBO: Final[int] = 300