Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
606ac649a0
|
@ -10,7 +10,6 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
||||
revision = '5cf98cfe52ad'
|
||||
down_revision = '263884e774cc'
|
||||
branch_labels = None
|
||||
|
@ -1,29 +0,0 @@
|
||||
"""Mai2 add PRiSM+ playlog support
|
||||
|
||||
Revision ID: bdf710616ba4
|
||||
Revises: 16f34bf7b968
|
||||
Create Date: 2025-04-02 12:42:08.981516
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'bdf710616ba4'
|
||||
|
||||
down_revision = '49c295e89cd4'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('mai2_playlog', sa.Column('extBool3', 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', 'extBool3')
|
||||
# ### end Alembic commands ###
|
@ -205,32 +205,32 @@ Presents are items given to the user when they login, with a little animation (f
|
||||
### Versions
|
||||
|
||||
| Game Code | Version ID | Version Name |
|
||||
|----------|------------|-------------------------|
|
||||
| SBXL | 0 | maimai |
|
||||
| SBXL | 1 | maimai PLUS |
|
||||
| SBZF | 2 | maimai GreeN |
|
||||
| SBZF | 3 | maimai GreeN PLUS |
|
||||
| SDBM | 4 | maimai ORANGE |
|
||||
| SDBM | 5 | maimai ORANGE PLUS |
|
||||
| SDCQ | 6 | maimai PiNK |
|
||||
| SDCQ | 7 | maimai PiNK PLUS |
|
||||
| SDDK | 8 | maimai MURASAKi |
|
||||
| SDDK | 9 | maimai MURASAKi PLUS |
|
||||
| SDDZ | 10 | maimai MiLK |
|
||||
| SDDZ | 11 | maimai MiLK PLUS |
|
||||
| SDEY | 12 | maimai FiNALE |
|
||||
| SDEZ | 13 | maimai DX |
|
||||
| SDEZ | 14 | maimai DX PLUS |
|
||||
| SDEZ | 15 | maimai DX Splash |
|
||||
| SDEZ | 16 | maimai DX Splash PLUS |
|
||||
| SDEZ | 17 | maimai DX UNiVERSE |
|
||||
| SDEZ | 18 | maimai DX UNiVERSE PLUS |
|
||||
| SDEZ | 19 | maimai DX FESTiVAL |
|
||||
| SDEZ | 20 | maimai DX FESTiVAL PLUS |
|
||||
| SDEZ | 21 | maimai DX BUDDiES |
|
||||
| SDEZ | 22 | maimai DX BUDDiES PLUS |
|
||||
| SDEZ | 23 | maimai DX PRiSM |
|
||||
| SDEZ | 24 | maimai DX PRiSM PLUS |
|
||||
|-----------|------------|-------------------------|
|
||||
| SBXL | 0 | maimai |
|
||||
| SBXL | 1 | maimai PLUS |
|
||||
| SBZF | 2 | maimai GreeN |
|
||||
| SBZF | 3 | maimai GreeN PLUS |
|
||||
| SDBM | 4 | maimai ORANGE |
|
||||
| SDBM | 5 | maimai ORANGE PLUS |
|
||||
| SDCQ | 6 | maimai PiNK |
|
||||
| SDCQ | 7 | maimai PiNK PLUS |
|
||||
| SDDK | 8 | maimai MURASAKi |
|
||||
| SDDK | 9 | maimai MURASAKi PLUS |
|
||||
| SDDZ | 10 | maimai MiLK |
|
||||
| SDDZ | 11 | maimai MiLK PLUS |
|
||||
| SDEY | 12 | maimai FiNALE |
|
||||
| SDEZ | 13 | maimai DX |
|
||||
| SDEZ | 14 | maimai DX PLUS |
|
||||
| SDEZ | 15 | maimai DX Splash |
|
||||
| SDEZ | 16 | maimai DX Splash PLUS |
|
||||
| SDEZ | 17 | maimai DX UNiVERSE |
|
||||
| SDEZ | 18 | maimai DX UNiVERSE PLUS |
|
||||
| SDEZ | 19 | maimai DX FESTiVAL |
|
||||
| SDEZ | 20 | maimai DX FESTiVAL PLUS |
|
||||
| SDEZ | 21 | maimai DX BUDDiES |
|
||||
| SDEZ | 22 | maimai DX BUDDiES PLUS |
|
||||
| SDEZ | 23 | maimai DX PRiSM |
|
||||
|
||||
|
||||
### Importer
|
||||
|
||||
|
@ -83,7 +83,6 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
||||
+ BUDDiES
|
||||
+ BUDDiES PLUS
|
||||
+ PRiSM
|
||||
+ PRiSM PLUS
|
||||
|
||||
+ O.N.G.E.K.I.
|
||||
+ SUMMER
|
||||
|
@ -13,7 +13,6 @@ from core.config import CoreConfig
|
||||
from .database import ChuniData
|
||||
from .config import ChuniConfig
|
||||
from .const import ChuniConstants, AvatarCategory, ItemKind
|
||||
from .read import ChuniReader
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
@ -92,9 +91,6 @@ class ChuniFrontend(FE_Base):
|
||||
self.data = ChuniData(cfg, self.game_cfg)
|
||||
self.nav_name = "Chunithm"
|
||||
|
||||
# Convert any old assets created with a previous version of the importer
|
||||
ChuniReader.ConvertOldAssets(self.logger)
|
||||
|
||||
def get_routes(self) -> List[Route]:
|
||||
return [
|
||||
Route("/", self.render_GET, methods=['GET']),
|
||||
@ -256,12 +252,12 @@ class ChuniFrontend(FE_Base):
|
||||
artist=music_chart.artist
|
||||
title=music_chart.title
|
||||
(jacket, ext) = path.splitext(music_chart.jacketPath)
|
||||
jacket += ".webp"
|
||||
jacket += ".png"
|
||||
else:
|
||||
difficultyNum=0
|
||||
artist="unknown"
|
||||
title="musicid: " + str(record.musicId)
|
||||
jacket = "unknown.webp"
|
||||
jacket = "unknown.png"
|
||||
|
||||
# Check if this song is a favorite so we can populate the add/remove button
|
||||
is_favorite = await self.data.item.is_favorite(user_id, version, record.musicId)
|
||||
@ -317,12 +313,12 @@ class ChuniFrontend(FE_Base):
|
||||
title=song.title
|
||||
genre=song.genre
|
||||
(jacket, ext) = path.splitext(song.jacketPath)
|
||||
jacket += ".webp"
|
||||
jacket += ".png"
|
||||
else:
|
||||
artist="unknown"
|
||||
title="musicid: " + str(favorite.favId)
|
||||
genre="unknown"
|
||||
jacket = "unknown.webp"
|
||||
jacket = "unknown.png"
|
||||
|
||||
# add a new collection for the genre if this is our first time seeing it
|
||||
if genre not in favorites_by_genre:
|
||||
@ -374,7 +370,7 @@ class ChuniFrontend(FE_Base):
|
||||
item = dict()
|
||||
item["id"] = row["mapIconId"]
|
||||
item["name"] = row["name"]
|
||||
item["iconPath"] = path.splitext(row["iconPath"])[0] + ".webp"
|
||||
item["iconPath"] = path.splitext(row["iconPath"])[0] + ".png"
|
||||
items[row["mapIconId"]] = item
|
||||
|
||||
return (items, len(rows))
|
||||
@ -399,7 +395,7 @@ class ChuniFrontend(FE_Base):
|
||||
item = dict()
|
||||
item["id"] = row["voiceId"]
|
||||
item["name"] = row["name"]
|
||||
item["imagePath"] = path.splitext(row["imagePath"])[0] + ".webp"
|
||||
item["imagePath"] = path.splitext(row["imagePath"])[0] + ".png"
|
||||
items[row["voiceId"]] = item
|
||||
|
||||
return (items, len(rows))
|
||||
@ -422,7 +418,7 @@ class ChuniFrontend(FE_Base):
|
||||
item = dict()
|
||||
item["id"] = row["nameplateId"]
|
||||
item["name"] = row["name"]
|
||||
item["texturePath"] = path.splitext(row["texturePath"])[0] + ".webp"
|
||||
item["texturePath"] = path.splitext(row["texturePath"])[0] + ".png"
|
||||
items[row["nameplateId"]] = item
|
||||
|
||||
return (items, len(rows))
|
||||
@ -468,7 +464,7 @@ class ChuniFrontend(FE_Base):
|
||||
item = dict()
|
||||
item["id"] = row["characterId"]
|
||||
item["name"] = row["name"]
|
||||
item["iconPath"] = path.splitext(row["imagePath3"])[0] + ".webp"
|
||||
item["iconPath"] = path.splitext(row["imagePath3"])[0] + ".png"
|
||||
items[row["characterId"]] = item
|
||||
|
||||
return (items, len(rows))
|
||||
@ -486,8 +482,8 @@ class ChuniFrontend(FE_Base):
|
||||
item = dict()
|
||||
item["id"] = row["avatarAccessoryId"]
|
||||
item["name"] = row["name"]
|
||||
item["iconPath"] = path.splitext(row["iconPath"])[0] + ".webp"
|
||||
item["texturePath"] = path.splitext(row["texturePath"])[0] + ".webp"
|
||||
item["iconPath"] = path.splitext(row["iconPath"])[0] + ".png"
|
||||
item["texturePath"] = path.splitext(row["texturePath"])[0] + ".png"
|
||||
items[row["avatarAccessoryId"]] = item
|
||||
|
||||
return (items, len(rows))
|
||||
|
BIN
titles/chuni/img/avatar-common.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 6.9 KiB |
BIN
titles/chuni/img/avatar-platform.png
Normal file
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 3.5 KiB |
BIN
titles/chuni/img/character-bg.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 1.6 KiB |
2
titles/chuni/img/jacket/.gitignore
vendored
@ -2,4 +2,4 @@
|
||||
*
|
||||
# Except this file and default unknown
|
||||
!.gitignore
|
||||
!unknown.webp
|
||||
!unknown.png
|
BIN
titles/chuni/img/jacket/unknown.png
Normal file
After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 6.4 KiB |
BIN
titles/chuni/img/rank/rank0.png
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 2.1 KiB |
BIN
titles/chuni/img/rank/rank1.png
Normal file
After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 4.9 KiB |
BIN
titles/chuni/img/rank/rank10.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.9 KiB |
BIN
titles/chuni/img/rank/rank11.png
Normal file
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 3.6 KiB |
BIN
titles/chuni/img/rank/rank2.png
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 2.4 KiB |
BIN
titles/chuni/img/rank/rank3.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.7 KiB |
BIN
titles/chuni/img/rank/rank4.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.7 KiB |
BIN
titles/chuni/img/rank/rank5.png
Normal file
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.5 KiB |
BIN
titles/chuni/img/rank/rank6.png
Normal file
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.5 KiB |
BIN
titles/chuni/img/rank/rank7.png
Normal file
After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 5.0 KiB |
BIN
titles/chuni/img/rank/rank8.png
Normal file
After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 5.0 KiB |
BIN
titles/chuni/img/rank/rank9.png
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 2.3 KiB |
BIN
titles/chuni/img/rank/rating0.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 570 B |
BIN
titles/chuni/img/rank/team3.png
Normal file
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 4.1 KiB |
@ -1,11 +1,9 @@
|
||||
from logging import Logger
|
||||
from typing import Optional
|
||||
from os import walk, path, remove
|
||||
from os import walk, path
|
||||
import xml.etree.ElementTree as ET
|
||||
from read import BaseReader
|
||||
from PIL import Image
|
||||
import configparser
|
||||
import glob
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.chuni.database import ChuniData
|
||||
@ -45,9 +43,6 @@ class ChuniReader(BaseReader):
|
||||
if self.version >= ChuniConstants.VER_CHUNITHM_NEW:
|
||||
we_diff = "5"
|
||||
|
||||
# Convert any old assets created with a previous version of the importer
|
||||
ChuniReader.ConvertOldAssets(self.logger)
|
||||
|
||||
# character images could be stored anywhere across all the data dirs. Map them first
|
||||
self.logger.info(f"Mapping DDS image files...")
|
||||
dds_images = dict()
|
||||
@ -538,40 +533,17 @@ class ChuniReader(BaseReader):
|
||||
self.logger.warning(f"Failed to unlock challenge {id}")
|
||||
|
||||
def copy_image(self, filename: str, src_dir: str, dst_dir: str) -> None:
|
||||
# Convert the image to webp so we can easily display it in the frontend
|
||||
# Convert the image to png so we can easily display it in the frontend
|
||||
file_src = path.join(src_dir, filename)
|
||||
(basename, ext) = path.splitext(filename)
|
||||
file_dst = path.join(dst_dir, basename) + ".webp"
|
||||
file_dst = path.join(dst_dir, basename) + ".png"
|
||||
|
||||
if path.exists(file_src) and not path.exists(file_dst):
|
||||
try:
|
||||
im = Image.open(file_src)
|
||||
im.save(file_dst)
|
||||
except Exception:
|
||||
self.logger.warning(f"Failed to convert {filename} to webp")
|
||||
|
||||
def ConvertOldAssets(logger: Logger):
|
||||
"""
|
||||
Converts any previously-imported png files to webp.
|
||||
In the initial version of the userbox/avatar frontend support, png images were used, scraped via read.py.
|
||||
The amount of data pushed once a lot of stuff was unlocked was noticeable so the frontend now uses webp format
|
||||
for these assets. If any png files are present, convert them to webp now.
|
||||
"""
|
||||
# Find all pngs under the /img directory
|
||||
png_files = glob.glob(f'titles/chuni/img/**/*.png', recursive=True)
|
||||
if len(png_files) > 0:
|
||||
logger.info(f'Found {len(png_files)} old assets. Converting to webp... (may take a few minutes)')
|
||||
for img_png in png_files:
|
||||
img_webp = path.splitext(img_png)[0] + '.webp'
|
||||
try:
|
||||
# convert to webp
|
||||
im = Image.open(img_png)
|
||||
im.save(img_webp)
|
||||
# delete the original file
|
||||
remove(img_png)
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to convert {img_png} to webp')
|
||||
logger.info(f'Conversion complete')
|
||||
self.logger.warning(f"Failed to convert {filename} to png")
|
||||
|
||||
def map_dds_images(self, image_dict: dict, dds_dir: str) -> None:
|
||||
for root, dirs, files in walk(dds_dir):
|
||||
|
@ -14,13 +14,13 @@
|
||||
<table class="table-large table-rowdistinct">
|
||||
<caption align="top">AVATAR</caption>
|
||||
<tr><td style="height:340px; width:50%" rowspan=8>
|
||||
<img class="avatar-preview avatar-preview-platform" src="img/avatar-platform.webp">
|
||||
<img class="avatar-preview avatar-preview-platform" src="img/avatar-platform.png">
|
||||
<img id="preview1_back" class="avatar-preview avatar-preview-back" src="">
|
||||
<img id="preview1_skin" class="avatar-preview avatar-preview-skin-rightfoot" src="">
|
||||
<img id="preview2_skin" class="avatar-preview avatar-preview-skin-leftfoot" src="">
|
||||
<img id="preview3_skin" class="avatar-preview avatar-preview-skin-body" src="">
|
||||
<img id="preview1_wear" class="avatar-preview avatar-preview-wear" src="">
|
||||
<img class="avatar-preview avatar-preview-common" src="img/avatar-common.webp">
|
||||
<img class="avatar-preview avatar-preview-common" src="img/avatar-common.png">
|
||||
<img id="preview1_head" class="avatar-preview avatar-preview-head" src="">
|
||||
<img id="preview1_face" class="avatar-preview avatar-preview-face" src="">
|
||||
<img id="preview1_item" class="avatar-preview avatar-preview-item-righthand" src="">
|
||||
@ -36,7 +36,7 @@
|
||||
<tr><td>Front:</td><td><div id="name_front"></div></td></tr>
|
||||
<tr><td>Back:</td><td><div id="name_back"></div></td></tr>
|
||||
|
||||
<tr><td colspan=3 style="padding:8px 0px; text-align: center; position: relative;">
|
||||
<tr><td colspan=3 style="padding:8px 0px; text-align: center;">
|
||||
<button id="save-btn" class="btn btn-primary" style="width:140px;" onClick="saveAvatar()">SAVE</button>
|
||||
<button id="reset-btn" class="btn btn-danger" style="width:140px;" onClick="resetAvatar()">RESET</button>
|
||||
</td></tr>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<img id="preview_nameplate" class="userbox userbox-nameplate" src="">
|
||||
|
||||
<!-- TEAM -->
|
||||
<img class="userbox userbox-teamframe" src="img/rank/team3.webp">
|
||||
<img class="userbox userbox-teamframe" src="img/rank/team3.png">
|
||||
<div class="userbox userbox-teamname">{{team_name}}</div>
|
||||
|
||||
<!-- TROPHY/TITLE -->
|
||||
@ -26,7 +26,7 @@
|
||||
<div id="preview_trophy_name" class="userbox userbox-trophy userbox-trophy-name"></div>
|
||||
|
||||
<!-- NAME/RATING -->
|
||||
<img class="userbox userbox-ratingframe" src="img/rank/rating0.webp">
|
||||
<img class="userbox userbox-ratingframe" src="img/rank/rating0.png">
|
||||
<div class="userbox userbox-name">
|
||||
<span class="userbox-name-level-label">Lv.</span>
|
||||
{{ profile.level }} {{ profile.userName }}
|
||||
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
|
||||
<!-- CHARACTER -->
|
||||
<img class="userbox userbox-charaframe" src="img/character-bg.webp">
|
||||
<img class="userbox userbox-charaframe" src="img/character-bg.png">
|
||||
<img id="preview_character" class="userbox userbox-chara" src="">
|
||||
</td></tr>
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div></td></tr>
|
||||
{% if cur_version >= 17 %} <!-- SubTrophies introduced in VERSE -->
|
||||
|
||||
<tr><td>Trophy Sub 1:</td><td><div id="name_trophy">
|
||||
<select name="trophy-sub-1" id="trophy-sub-1" onclick="changeTrophySub1()" style="width:100%;">
|
||||
<option value="-1"></option>
|
||||
@ -68,8 +68,7 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div></td></tr>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<tr><td>Character:</td><td><div id="name_character"></div></td></tr>
|
||||
|
||||
<tr><td colspan=2 style="padding:8px 0px; text-align: center;">
|
||||
@ -181,10 +180,10 @@ function changeItem(type, id, name, img) {
|
||||
function getRankImage(selected_rank) {
|
||||
for (const x of Array(12).keys()) {
|
||||
if (selected_rank.classList.contains("trophy-rank" + x.toString())) {
|
||||
return "rank" + x.toString() + ".webp";
|
||||
return "rank" + x.toString() + ".png";
|
||||
}
|
||||
}
|
||||
return "rank0.webp"; // shouldnt ever happen
|
||||
return "rank0.png"; // shouldnt ever happen
|
||||
}
|
||||
|
||||
function changeTrophy() {
|
||||
|
@ -208,8 +208,7 @@ class CardMakerReader(BaseReader):
|
||||
"1.35": Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS,
|
||||
"1.40": Mai2Constants.VER_MAIMAI_DX_BUDDIES,
|
||||
"1.45": Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS,
|
||||
"1.50": Mai2Constants.VER_MAIMAI_DX_PRISM,
|
||||
"1.55": Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS
|
||||
"1.50": Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
}
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
|
@ -61,7 +61,6 @@ class Mai2Constants:
|
||||
VER_MAIMAI_DX_BUDDIES = 21
|
||||
VER_MAIMAI_DX_BUDDIES_PLUS = 22
|
||||
VER_MAIMAI_DX_PRISM = 23
|
||||
VER_MAIMAI_DX_PRISM_PLUS = 24
|
||||
|
||||
VERSION_STRING = (
|
||||
"maimai",
|
||||
@ -87,8 +86,7 @@ class Mai2Constants:
|
||||
"maimai DX FESTiVAL PLUS",
|
||||
"maimai DX BUDDiES",
|
||||
"maimai DX BUDDiES PLUS",
|
||||
"maimai DX PRiSM",
|
||||
"maimai DX PRiSM 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],
|
||||
@ -96,21 +94,9 @@ class Mai2Constants:
|
||||
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: [],
|
||||
#紫の扉: JP: need to enter redeem code 51090942171709440000
|
||||
#紫の扉: need to enter redeem code 51090942171709440000
|
||||
4: [11023, 11106, 11221, 11222, 11300, 11374, 11458, 11523, 11619, 11663, 11746],
|
||||
#黑の扉: Played 11 songs
|
||||
5: [11003, 11095, 11152, 11224, 11296, 11375, 11452, 11529, 11608, 11669, 11736, 11806],
|
||||
#黄の扉: Use random selection to play one of the songs
|
||||
6: [212, 213, 337, 270, 271, 11504, 339, 453, 11336, 11852],
|
||||
#赤の扉: Played 10 songs
|
||||
7: [],
|
||||
#PRISM TOWER: Get the key after clearing six doors.
|
||||
8: [],
|
||||
#KALEIDXSCOPE_FIRST_STAGE: Clear Prism Tower
|
||||
9: [],
|
||||
#希望の扉: CLEAR KALEIDXSCOPE_FIRST_STAGE
|
||||
10: []
|
||||
#KALEIDXSCOPE_SECOND_STAGE: JP: scan the DXPASS of 希望の鍵, will automatically unlock after clearing 希望の扉 in artemis
|
||||
#青の扉: Played 11 songs
|
||||
}
|
||||
MAI_VERSION_LUT = {
|
||||
"100": VER_MAIMAI,
|
||||
@ -139,8 +125,7 @@ class Mai2Constants:
|
||||
"135": VER_MAIMAI_DX_FESTIVAL_PLUS,
|
||||
"140": VER_MAIMAI_DX_BUDDIES,
|
||||
"145": VER_MAIMAI_DX_BUDDIES_PLUS,
|
||||
"150": VER_MAIMAI_DX_PRISM,
|
||||
"155": VER_MAIMAI_DX_PRISM_PLUS
|
||||
"150": VER_MAIMAI_DX_PRISM
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -32,7 +32,6 @@ from .festivalplus import Mai2FestivalPlus
|
||||
from .buddies import Mai2Buddies
|
||||
from .buddiesplus import Mai2BuddiesPlus
|
||||
from .prism import Mai2Prism
|
||||
from .prismplus import Mai2PrismPlus
|
||||
|
||||
|
||||
class Mai2Servlet(BaseServlet):
|
||||
@ -69,8 +68,7 @@ class Mai2Servlet(BaseServlet):
|
||||
Mai2FestivalPlus,
|
||||
Mai2Buddies,
|
||||
Mai2BuddiesPlus,
|
||||
Mai2Prism,
|
||||
Mai2PrismPlus
|
||||
Mai2Prism
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("mai2")
|
||||
@ -197,7 +195,7 @@ class Mai2Servlet(BaseServlet):
|
||||
|
||||
if proto == "" or proto == "https://":
|
||||
t_port = f":{title_port_ssl_int}" if title_port_ssl_int != 443 else ""
|
||||
else:
|
||||
else:
|
||||
t_port = f":{title_port_int}" if title_port_int != 80 else ""
|
||||
|
||||
return (
|
||||
@ -345,12 +343,10 @@ 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 and version < 150: # BUDDiES PLUS
|
||||
elif version >= 145 and version <150: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
elif version >= 150 and version < 155:
|
||||
elif version >= 150: # PRiSM
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
elif version >= 155:
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS
|
||||
|
||||
elif game_code == "SDGA": # Int
|
||||
if version < 105: # 1.0
|
||||
@ -371,12 +367,10 @@ 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 and version < 150: # BUDDiES PLUS
|
||||
elif version >= 145 and version <150: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
elif version >= 150 and version < 155:
|
||||
elif version >= 150: # PRiSM
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
elif version >= 155:
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS
|
||||
|
||||
elif game_code == "SDGB": # Chn
|
||||
if version < 110: # Muji
|
||||
@ -392,7 +386,6 @@ class Mai2Servlet(BaseServlet):
|
||||
elif version >= 150: # PRiSM
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM
|
||||
|
||||
|
||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||
|
||||
if game_code == "SDGA":
|
||||
|
@ -1,141 +0,0 @@
|
||||
from typing import Dict
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.prism import Mai2Prism
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
|
||||
|
||||
|
||||
class Mai2PrismPlus(Mai2Prism):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS
|
||||
|
||||
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.55.00"
|
||||
return user_data
|
||||
|
||||
async def handle_upsert_client_play_time_api_request(self, data: Dict) -> Dict:
|
||||
return{
|
||||
"returnCode": 1,
|
||||
"apiName": "UpsertClientPlayTimeApi"
|
||||
}
|
||||
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},
|
||||
{"gateId": 5, "phaseId": 6},
|
||||
{"gateId": 6, "phaseId": 6},
|
||||
{"gateId": 7, "phaseId": 6},
|
||||
{"gateId": 8, "phaseId": 6},
|
||||
{"gateId": 9, "phaseId": 6},
|
||||
{"gateId": 10, "phaseId": 13}
|
||||
]
|
||||
}
|
||||
|
||||
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,11):
|
||||
if gate == 1 or gate == 4 or gate == 6:
|
||||
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)
|
||||
|
||||
|
||||
elif gate == 5:
|
||||
|
||||
playlogs = await self.data.score.get_playlogs(user_id=data["userId"], idx=0, limit=0)
|
||||
allowed_music = set(Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[5])
|
||||
valid_playlogs = []
|
||||
|
||||
for playlog in playlogs:
|
||||
if playlog["extBool2"] == 1 and playlog["musicId"] in allowed_music:
|
||||
valid_playlogs.append(playlog["playlogId"]) # 直接记录 playlogId
|
||||
if valid_playlogs:
|
||||
new_kaleidxscope = {'gateId': 5, "isKeyFound": True}
|
||||
await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope)
|
||||
|
||||
elif gate == 7:
|
||||
|
||||
played_kaleidxscope_list = await self.data.score.get_user_kaleidxscope_list(data["userId"])
|
||||
check_results = {}
|
||||
for i in range(1,7):
|
||||
check_results[i] = False
|
||||
for played_kaleidxscope in played_kaleidxscope_list:
|
||||
if played_kaleidxscope[2] == i and played_kaleidxscope[5] == True:
|
||||
check_results[i] = True
|
||||
break
|
||||
all_true = all(check_results.values())
|
||||
|
||||
if all_true:
|
||||
new_kaleidxscope = {'gateId': 7, "isKeyFound": True}
|
||||
await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope)
|
||||
|
||||
elif gate == 10:
|
||||
|
||||
played_kaleidxscope_list = await self.data.score.get_user_kaleidxscope_list(data["userId"])
|
||||
for played_kaleidxscope in played_kaleidxscope_list:
|
||||
if played_kaleidxscope[2] == 9 and played_kaleidxscope[5] == True:
|
||||
new_kaleidxscope = {'gateId': 10, "isGateFound": True, "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
|
||||
}
|
@ -148,8 +148,7 @@ playlog = Table(
|
||||
Column("extNum2", Integer),
|
||||
Column("extNum4", Integer),
|
||||
Column("extBool1", Boolean), # new with buddies
|
||||
Column("extBool2", Boolean), # new with prism IsRandomSelect
|
||||
Column("extBool3", Boolean), # new with prism+ IsTrackSkip
|
||||
Column("extBool2", Boolean), # new with prism
|
||||
Column("trialPlayAchievement", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|