forked from Hay1tsme/artemis
Merge pull request 'mai2:prism_support' (#205) from SoulGateKey/artemis:prism_support into develop
Reviewed-on: Hay1tsme/artemis#205
This commit is contained in:
@ -0,0 +1,52 @@
|
||||
"""Mai2 PRiSM support
|
||||
|
||||
Revision ID: 5cf98cfe52ad
|
||||
Revises: 263884e774cc
|
||||
Create Date: 2025-04-08 08:00:51.243089
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5cf98cfe52ad'
|
||||
down_revision = '263884e774cc'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('mai2_score_kaleidxscope',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user', sa.Integer(), nullable=False),
|
||||
sa.Column('gateId', sa.Integer(), nullable=True),
|
||||
sa.Column('isGateFound', sa.Boolean(), nullable=True),
|
||||
sa.Column('isKeyFound', sa.Boolean(), nullable=True),
|
||||
sa.Column('isClear', sa.Boolean(), nullable=True),
|
||||
sa.Column('totalRestLife', sa.Integer(), nullable=True),
|
||||
sa.Column('totalAchievement', sa.Integer(), nullable=True),
|
||||
sa.Column('totalDeluxscore', sa.Integer(), nullable=True),
|
||||
sa.Column('bestAchievement', sa.Integer(), nullable=True),
|
||||
sa.Column('bestDeluxscore', sa.Integer(), nullable=True),
|
||||
sa.Column('bestAchievementDate', sa.String(length=25), nullable=True),
|
||||
sa.Column('bestDeluxscoreDate', sa.String(length=25), nullable=True),
|
||||
sa.Column('playCount', sa.Integer(), nullable=True),
|
||||
sa.Column('clearDate', sa.String(length=25), nullable=True),
|
||||
sa.Column('lastPlayDate', sa.String(length=25), nullable=True),
|
||||
sa.Column('isInfoWatched', sa.Boolean(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('user', 'gateId', name='mai2_score_best_uk'),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
op.add_column('mai2_playlog', sa.Column('extBool2', sa.Boolean(), nullable=True, server_default=sa.text("NULL")))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('mai2_playlog', 'extBool2')
|
||||
op.drop_table('mai2_score_kaleidxscope')
|
||||
# ### end Alembic commands ###
|
@ -195,15 +195,15 @@ Config file is located in `config/cxb.yaml`.
|
||||
### Presents
|
||||
Presents are items given to the user when they login, with a little animation (for example, the KOP song was given to the finalists as a present). To add a present, you must insert it into the `mai2_item_present` table. In that table, a NULL version means any version, a NULL user means any user, a NULL start date means always open, and a NULL end date means it never expires. Below is a list of presents one might wish to add:
|
||||
|
||||
| Game Version | Item ID | Item Kind | Item Description | Present Description |
|
||||
|--------------|---------|-----------|-------------------------------------------------|------------------------------------------------|
|
||||
| BUDDiES (21) | 409505 | Icon (3) | 旅行スタンプ(月面基地) (Travel Stamp - Moon Base) | Officially obtained on the webui with a serial |
|
||||
| | | | | number, for project raputa |
|
||||
| Game Version | Item ID | Item Kind | Item Description | Present Description |
|
||||
|--------------|---------|----------------------|--------------------------------------------|----------------------------------------------------------------------------|
|
||||
| BUDDiES (21) | 409505 | Icon (3) | 旅行スタンプ(月面基地) (Travel Stamp - Moon Base) | Officially obtained on the webui with a serial number, for project raputa |
|
||||
| PRiSM (23) | 3 | KaleidxScopeKey (15) | 紫の鍵 (Purple Key) | Officially obtained on the webui with a serial number, for KaleidxScope |
|
||||
|
||||
### Versions
|
||||
|
||||
| Game Code | Version ID | Version Name |
|
||||
| --------- | ---------- | ----------------------- |
|
||||
|-----------|------------|-------------------------|
|
||||
| SBXL | 0 | maimai |
|
||||
| SBXL | 1 | maimai PLUS |
|
||||
| SBZF | 2 | maimai GreeN |
|
||||
@ -227,6 +227,8 @@ Presents are items given to the user when they login, with a little animation (f
|
||||
| SDEZ | 20 | maimai DX FESTiVAL PLUS |
|
||||
| SDEZ | 21 | maimai DX BUDDiES |
|
||||
| SDEZ | 22 | maimai DX BUDDiES PLUS |
|
||||
| SDEZ | 23 | maimai DX PRiSM |
|
||||
|
||||
|
||||
### Importer
|
||||
|
||||
|
@ -52,6 +52,7 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
||||
+ FESTiVAL PLUS
|
||||
+ BUDDiES
|
||||
+ BUDDiES PLUS
|
||||
+ PRiSM
|
||||
|
||||
+ O.N.G.E.K.I.
|
||||
+ SUMMER
|
||||
|
@ -207,7 +207,8 @@ class CardMakerReader(BaseReader):
|
||||
"1.30": Mai2Constants.VER_MAIMAI_DX_FESTIVAL,
|
||||
"1.35": Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS,
|
||||
"1.40": Mai2Constants.VER_MAIMAI_DX_BUDDIES,
|
||||
"1.45": Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
"1.45": Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS,
|
||||
"1.50": Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
}
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
|
@ -59,6 +59,7 @@ class Mai2Constants:
|
||||
VER_MAIMAI_DX_FESTIVAL_PLUS = 20
|
||||
VER_MAIMAI_DX_BUDDIES = 21
|
||||
VER_MAIMAI_DX_BUDDIES_PLUS = 22
|
||||
VER_MAIMAI_DX_PRISM = 23
|
||||
|
||||
VERSION_STRING = (
|
||||
"maimai",
|
||||
@ -83,9 +84,19 @@ class Mai2Constants:
|
||||
"maimai DX FESTiVAL",
|
||||
"maimai DX FESTiVAL PLUS",
|
||||
"maimai DX BUDDiES",
|
||||
"maimai DX BUDDiES PLUS"
|
||||
"maimai DX BUDDiES PLUS",
|
||||
"maimai DX PRiSM"
|
||||
)
|
||||
|
||||
KALEIDXSCOPE_KEY_CONDITION={
|
||||
1: [11009, 11008, 11100, 11097, 11098, 11099, 11163, 11162, 11161, 11228, 11229, 11231, 11463, 11464, 11465, 11538, 11539, 11541, 11620, 11622, 11623, 11737, 11738, 11164, 11230, 11466, 11540, 11621, 11739],
|
||||
#青の扉: Played 29 songs
|
||||
2: [11102, 11234, 11300, 11529, 11542, 11612],
|
||||
#白の扉: set Frame as "Latent Kingdom" (459504), play 3 or 4 songs by the composer 大国奏音 in 1 pc
|
||||
3: [],
|
||||
#紫の扉: need to enter redeem code 51090942171709440000
|
||||
4: [11023, 11106, 11221, 11222, 11300, 11374, 11458, 11523, 11619, 11663, 11746],
|
||||
#青の扉: Played 11 songs
|
||||
}
|
||||
MAI_VERSION_LUT = {
|
||||
"100": VER_MAIMAI,
|
||||
"110": VER_MAIMAI_PLUS,
|
||||
|
@ -112,6 +112,17 @@ class Mai2DX(Mai2Base):
|
||||
|
||||
return {"returnCode": 1, "apiName": "UploadUserPlaylogApi"}
|
||||
|
||||
# Exp version use this instead of UploadUserPlaylogApi in 1.50
|
||||
async def handle_upload_user_playlog_list_api_request(self, data: Dict) -> Dict:
|
||||
user_id = data["userId"]
|
||||
playlog_list = data["userPlaylogList"]
|
||||
|
||||
for playlog in playlog_list:
|
||||
await self.data.score.put_playlog(user_id, playlog)
|
||||
|
||||
return {"returnCode": 1, "apiName": "UploadUserPlaylogApi"}
|
||||
|
||||
|
||||
async def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
|
||||
user_id = data["userId"]
|
||||
charge = data["userCharge"]
|
||||
@ -271,6 +282,12 @@ class Mai2DX(Mai2Base):
|
||||
for intimate in upsert["userIntimateList"]:
|
||||
await self.data.profile.put_intimacy(user_id, intimate["partnerId"], intimate["intimateLevel"], intimate["intimateCountRewarded"])
|
||||
|
||||
# added in PRiSM
|
||||
if "userKaleidxScopeList" in upsert and len(upsert["userKaleidxScopeList"]) > 0:
|
||||
for kaleidxscope in upsert["userKaleidxScopeList"]:
|
||||
await self.data.score.put_user_kaleidxscope(user_id, kaleidxscope)
|
||||
|
||||
|
||||
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
||||
|
||||
async def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||
|
@ -31,6 +31,7 @@ from .festival import Mai2Festival
|
||||
from .festivalplus import Mai2FestivalPlus
|
||||
from .buddies import Mai2Buddies
|
||||
from .buddiesplus import Mai2BuddiesPlus
|
||||
from .prism import Mai2Prism
|
||||
|
||||
|
||||
class Mai2Servlet(BaseServlet):
|
||||
@ -66,7 +67,8 @@ class Mai2Servlet(BaseServlet):
|
||||
Mai2Festival,
|
||||
Mai2FestivalPlus,
|
||||
Mai2Buddies,
|
||||
Mai2BuddiesPlus
|
||||
Mai2BuddiesPlus,
|
||||
Mai2Prism
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("mai2")
|
||||
@ -306,8 +308,11 @@ class Mai2Servlet(BaseServlet):
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
|
||||
elif version >= 140 and version < 145: # BUDDiES
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES
|
||||
elif version >= 145: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
elif version >= 145 and version <150: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS,
|
||||
elif version >=150:
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
elif game_code == "SDGA": # Int
|
||||
if version < 105: # 1.0
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||
@ -325,6 +330,12 @@ class Mai2Servlet(BaseServlet):
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||
elif version >= 135 and version < 140: # FESTiVAL PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
|
||||
elif version >= 140 and version < 145: # BUDDiES
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES
|
||||
elif version >= 145 and version <150: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS,
|
||||
elif version >=150:
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||
# If we get a 32 character long hex string, it's a hash and we're
|
||||
|
107
titles/mai2/prism.py
Normal file
107
titles/mai2/prism.py
Normal file
@ -0,0 +1,107 @@
|
||||
from typing import Dict
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.buddiesplus import Mai2BuddiesPlus
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
|
||||
|
||||
class Mai2Prism(Mai2BuddiesPlus):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||
user_data = await super().handle_cm_get_user_preview_api_request(data)
|
||||
|
||||
# hardcode lastDataVersion for CardMaker
|
||||
user_data["lastDataVersion"] = "1.50.00"
|
||||
return user_data
|
||||
|
||||
|
||||
async def handle_get_user_new_item_list_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"user_id": data["userId"],
|
||||
"userItemList": []
|
||||
}
|
||||
|
||||
#seems to be used for downloading music scores online
|
||||
async def handle_get_game_music_score_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"gameMusicScore": {
|
||||
"musicId": data["musicId"],
|
||||
"level": data["level"],
|
||||
"type": data["type"],
|
||||
"scoreData": ""
|
||||
}
|
||||
}
|
||||
|
||||
async def handle_get_game_kaleidx_scope_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"gameKaleidxScopeList": [
|
||||
{"gateId": 1, "phaseId": 6},
|
||||
{"gateId": 2, "phaseId": 6},
|
||||
{"gateId": 3, "phaseId": 6},
|
||||
{"gateId": 4, "phaseId": 6},
|
||||
]
|
||||
}
|
||||
|
||||
async def handle_get_user_kaleidx_scope_api_request(self, data: Dict) -> Dict:
|
||||
# kaleidxscope keyget condition judgement
|
||||
# player may get key before GateFound
|
||||
for gate in range(1,5):
|
||||
if gate == 1 or gate == 4:
|
||||
condition_satisfy = 0
|
||||
for condition in Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[gate]:
|
||||
score_list = await self.data.score.get_best_scores(user_id=data["userId"], song_id=condition)
|
||||
if score_list:
|
||||
condition_satisfy = condition_satisfy + 1
|
||||
if len(Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[gate]) == condition_satisfy:
|
||||
new_kaleidxscope = {'gateId': gate, "isKeyFound": True}
|
||||
await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope)
|
||||
|
||||
elif gate == 2:
|
||||
user_profile = await self.data.profile.get_profile_detail(user_id=data["userId"], version=self.version)
|
||||
user_frame = user_profile["frameId"]
|
||||
if user_frame == 459504:
|
||||
playlogs = await self.data.score.get_playlogs(user_id=data["userId"], idx=0, limit=0)
|
||||
|
||||
playlog_dict = {}
|
||||
for playlog in playlogs:
|
||||
playlog_id = playlog["playlogId"]
|
||||
if playlog_id not in playlog_dict:
|
||||
playlog_dict[playlog_id] = []
|
||||
playlog_dict[playlog_id].append(playlog["musicId"])
|
||||
valid_playlogs = []
|
||||
allowed_music = set(Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[2])
|
||||
for playlog_id, music_ids in playlog_dict.items():
|
||||
|
||||
if len(music_ids) != len(set(music_ids)):
|
||||
continue
|
||||
all_valid = True
|
||||
for mid in music_ids:
|
||||
if mid not in allowed_music:
|
||||
all_valid = False
|
||||
break
|
||||
if all_valid:
|
||||
valid_playlogs.append(playlog_id)
|
||||
|
||||
if valid_playlogs:
|
||||
new_kaleidxscope = {'gateId': 2, "isKeyFound": True}
|
||||
await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope)
|
||||
|
||||
kaleidxscope = await self.data.score.get_user_kaleidxscope_list(data["userId"])
|
||||
|
||||
if kaleidxscope is None:
|
||||
return {"userId": data["userId"], "userKaleidxScopeList":[]}
|
||||
|
||||
kaleidxscope_list = []
|
||||
for kaleidxscope_data in kaleidxscope:
|
||||
tmp = kaleidxscope_data._asdict()
|
||||
tmp.pop("user")
|
||||
tmp.pop("id")
|
||||
kaleidxscope_list.append(tmp)
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userKaleidxScopeList": kaleidxscope_list
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
from configparser import Interpolation
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy import Column, Table, UniqueConstraint, and_
|
||||
@ -147,6 +148,7 @@ playlog = Table(
|
||||
Column("extNum2", Integer),
|
||||
Column("extNum4", Integer),
|
||||
Column("extBool1", Boolean), # new with buddies
|
||||
Column("extBool2", Boolean), # new with prism
|
||||
Column("trialPlayAchievement", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
@ -173,6 +175,34 @@ playlog_2p = Table(
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
kaleidxscope = Table(
|
||||
"mai2_score_kaleidxscope",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("gateId", Integer),
|
||||
Column("isGateFound", Boolean),
|
||||
Column("isKeyFound", Boolean),
|
||||
Column("isClear", Boolean),
|
||||
Column("totalRestLife", Integer),
|
||||
Column("totalAchievement", Integer),
|
||||
Column("totalDeluxscore", Integer),
|
||||
Column("bestAchievement", Integer),
|
||||
Column("bestDeluxscore", Integer),
|
||||
Column("bestAchievementDate", String(25)),
|
||||
Column("bestDeluxscoreDate", String(25)),
|
||||
Column("playCount", Integer),
|
||||
Column("clearDate", String(25)),
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("isInfoWatched", Boolean),
|
||||
UniqueConstraint("user", "gateId", name="mai2_score_best_uk"),
|
||||
mysql_charset="utf8mb4"
|
||||
)
|
||||
|
||||
course = Table(
|
||||
"mai2_score_course",
|
||||
metadata,
|
||||
@ -450,3 +480,22 @@ class Mai2ScoreData(BaseData):
|
||||
self.logger.warning(f"aime_id {aime_id} has no playlog ")
|
||||
return None
|
||||
return result.scalar()
|
||||
|
||||
async def get_user_kaleidxscope_list(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = kaleidxscope.select(kaleidxscope.c.user == user_id)
|
||||
result = await self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
async def put_user_kaleidxscope(self, user_id: int, user_kaleidxscope_data: Dict) -> Optional[int]:
|
||||
user_kaleidxscope_data["user"] = user_id
|
||||
sql = insert(kaleidxscope).values(**user_kaleidxscope_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**user_kaleidxscope_data)
|
||||
|
||||
result = await self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_user_kaleidxscope: Failed to insert! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
Reference in New Issue
Block a user