Merge branch 'refs/heads/develop' into prism_plus_support

# Conflicts:
#	core/data/alembic/versions/16f34bf7b968_mai2_kaleidx_scope_support.py
#	core/data/alembic/versions/5cf98cfe52ad_mai2_prism_support.py
#	core/data/alembic/versions/5d7b38996e67_mai2_prism_support.py
#	core/data/alembic/versions/bdf710616ba4_mai2_add_prism_playlog_support.py
#	titles/mai2/index.py
#	titles/mai2/prism.py
#	titles/mai2/read.py
#	titles/mai2/schema/static.py
This commit is contained in:
SoulGateKey
2025-05-15 02:41:55 +08:00
27 changed files with 1495 additions and 296 deletions

View File

@@ -728,10 +728,11 @@ class Mai2ItemData(BaseData):
# Do an anti-join with the mai2_item_item table to exclude any
# items the users have already owned.
if exclude_owned:
sql = sql.join(
sql = sql.outerjoin(
item,
(present.c.itemKind == item.c.itemKind)
& (present.c.itemId == item.c.itemId)
& (item.c.user == user_id)
)
condition &= (item.c.itemKind.is_(None) & item.c.itemId.is_(None))

View File

@@ -2,13 +2,28 @@ from core.data.schema.base import BaseData, metadata
from typing import Optional, Dict, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, BIGINT, Float, INTEGER, BOOLEAN, VARCHAR
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.sql.functions import coalesce
from datetime import datetime
opts = Table(
"mai2_static_opt",
metadata,
Column("id", BIGINT, primary_key=True, nullable=False),
Column("version", INTEGER, nullable=False),
Column("name", VARCHAR(4), nullable=False), # Axxx
Column("sequence", INTEGER, nullable=False), # release in DataConfig.xml
Column("cmReleaseVer", INTEGER, nullable=False),
Column("whenRead", TIMESTAMP, nullable=False, server_default=func.now()),
Column("isEnable", BOOLEAN, nullable=False, server_default="1"),
UniqueConstraint("version", "name", name="mai2_static_opt_uk"),
mysql_charset="utf8mb4",
)
event = Table(
"mai2_static_event",
metadata,
@@ -19,6 +34,7 @@ event = Table(
Column("name", String(255)),
Column("startDate", TIMESTAMP, server_default=func.now()),
Column("enabled", Boolean, server_default="1"),
Column("opt", ForeignKey("mai2_static_opt.id", ondelete="SET NULL", onupdate="cascade")),
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
mysql_charset="utf8mb4",
)
@@ -37,6 +53,7 @@ music = Table(
Column("addedVersion", String(255)),
Column("difficulty", Float),
Column("noteDesigner", String(255)),
Column("opt", ForeignKey("mai2_static_opt.id", ondelete="SET NULL", onupdate="cascade")),
UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"),
mysql_charset="utf8mb4",
)
@@ -51,6 +68,7 @@ ticket = Table(
Column("name", String(255)),
Column("price", Integer, server_default="1"),
Column("enabled", Boolean, server_default="1"),
Column("opt", ForeignKey("mai2_static_opt.id", ondelete="SET NULL", onupdate="cascade")),
UniqueConstraint("version", "ticketId", name="mai2_static_ticket_uk"),
mysql_charset="utf8mb4",
)
@@ -67,34 +85,25 @@ cards = Table(
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
Column("enabled", Boolean, server_default="1"),
Column("opt", ForeignKey("cm_static_opts.id", ondelete="SET NULL", onupdate="cascade")),
UniqueConstraint("version", "cardId", "cardName", name="mai2_static_cards_uk"),
mysql_charset="utf8mb4",
)
kaleidxscope_condition = Table(
"mai2_static_kaleidxscope_condition",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("conditionId", Integer),
Column("conditionName", String(255)),
Column("songId", Integer),
Column("songName", String(255)),
UniqueConstraint("conditionId", "conditionName", "songId", "songName", name="mai2_static_kaleidxscope_uk"),
mysql_charset="utf8mb4",
)
class Mai2StaticData(BaseData):
async def put_game_event(
self, version: int, type: int, event_id: int, name: str
self, version: int, type: int, event_id: int, name: str, opt_id: int = None
) -> Optional[int]:
sql = insert(event).values(
version=version,
type=type,
eventId=event_id,
name=name,
opt=coalesce(event.c.opt, opt_id)
)
conflict = sql.on_duplicate_key_update(eventId=event_id)
conflict = sql.on_duplicate_key_update(eventId=event_id, opt=coalesce(event.c.opt, opt_id))
result = await self.execute(conflict)
if result is None:
@@ -147,6 +156,7 @@ class Mai2StaticData(BaseData):
added_version: str,
difficulty: float,
note_designer: str,
opt_id: int = None
) -> None:
sql = insert(music).values(
version=version,
@@ -159,6 +169,7 @@ class Mai2StaticData(BaseData):
addedVersion=added_version,
difficulty=difficulty,
noteDesigner=note_designer,
opt=coalesce(music.c.opt, opt_id)
)
conflict = sql.on_duplicate_key_update(
@@ -169,6 +180,7 @@ class Mai2StaticData(BaseData):
addedVersion=added_version,
difficulty=difficulty,
noteDesigner=note_designer,
opt=coalesce(music.c.opt, opt_id)
)
result = await self.execute(conflict)
@@ -184,6 +196,7 @@ class Mai2StaticData(BaseData):
ticket_type: int,
ticket_price: int,
name: str,
opt_id: int = None
) -> Optional[int]:
sql = insert(ticket).values(
version=version,
@@ -191,11 +204,10 @@ class Mai2StaticData(BaseData):
kind=ticket_type,
price=ticket_price,
name=name,
opt=coalesce(ticket.c.opt, opt_id)
)
conflict = sql.on_duplicate_key_update(price=ticket_price)
conflict = sql.on_duplicate_key_update(price=ticket_price)
conflict = sql.on_duplicate_key_update(price=ticket_price, opt=coalesce(ticket.c.opt, opt_id))
result = await self.execute(conflict)
if result is None:
@@ -240,12 +252,12 @@ class Mai2StaticData(BaseData):
return None
return result.fetchone()
async def put_card(self, version: int, card_id: int, card_name: str, **card_data) -> int:
async def put_card(self, version: int, card_id: int, card_name: str, opt_id: int = None, **card_data) -> int:
sql = insert(cards).values(
version=version, cardId=card_id, cardName=card_name, **card_data
version=version, cardId=card_id, cardName=card_name, opt=coalesce(cards.c.opt, opt_id) **card_data
)
conflict = sql.on_duplicate_key_update(**card_data)
conflict = sql.on_duplicate_key_update(opt=coalesce(cards.c.opt, opt_id), **card_data)
result = await self.execute(conflict)
if result is None:
@@ -276,34 +288,84 @@ class Mai2StaticData(BaseData):
if not result:
self.logger.error(f"Failed to update event {table_id} - {is_enable} {start_date}")
# new in prism
async def put_kaleidxscope_condition(
self,
condition_id: int,
condition_name: str,
music_id: int,
music_name: str
) -> Optional[int]:
sql = insert(kaleidxscope_condition).values(
conditionId = condition_id,
conditionName = condition_name,
songId = music_id,
songName = music_name,
)
conflict = sql.on_duplicate_key_update(conditionName=condition_name, songName=music_name)
async def put_opt(self, version: int, folder: str, sequence: int, cm_seq: int = None) -> Optional[int]:
sql = insert(opts).values(version=version, name=folder, sequence=sequence, cmReleaseVer=cm_seq)
conflict = sql.on_duplicate_key_update(sequence=sequence, whenRead=datetime.now())
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_kaleidxscope_condition: Failed to insert kaleidxScope Key Condition! conditionID {condition_id} songId {music_id}"
)
self.logger.warning(f"Failed to insert opt! version {version} folder {folder} sequence {sequence}")
return None
return result.lastrowid
async def get_kaleidxscope_condition(self, condition_id: int) -> None:
sql = kaleidxscope_condition.select(kaleidxscope_condition.c.conditionId == condition_id)
result = await self.execute(sql)
async def get_opt_by_version_folder(self, version: int, folder: str) -> Optional[Row]:
result = await self.execute(opts.select(and_(
opts.c.version == version,
opts.c.name == folder,
)))
if result is None:
return None
return result.fetchall()
return result.fetchone()
async def get_opt_by_version_sequence(self, version: int, sequence: str) -> Optional[Row]:
result = await self.execute(opts.select(and_(
opts.c.version == version,
opts.c.sequence == sequence,
)))
if result is None:
return None
return result.fetchone()
async def get_opts_by_version(self, version: int) -> Optional[List[Row]]:
result = await self.execute(opts.select(opts.c.version == version))
if result is None:
return None
return result.fetchall()
async def get_opts_enabled_by_version(self, version: int) -> Optional[List[Row]]:
result = await self.execute(opts.select(and_(
opts.c.version == version,
opts.c.isEnable == True,
)))
if result is None:
return None
return result.fetchall()
async def get_latest_enabled_opt_by_version(self, version: int) -> Optional[Row]:
result = await self.execute(
opts.select(and_(
opts.c.version == version,
opts.c.isEnable == True,
)).order_by(opts.c.sequence.desc())
)
if result is None:
return None
return result.fetchone()
async def get_opts(self) -> Optional[List[Row]]:
result = await self.execute(opts.select())
if result is None:
return None
return result.fetchall()
async def get_opts(self) -> Optional[List[Row]]:
result = await self.execute(opts.select())
if result is None:
return None
return result.fetchall()
async def set_opt_enabled(self, opt_id: int, enabled: bool) -> bool:
result = await self.execute(opts.update(opts.c.id == opt_id).values(isEnable=enabled))
if result is None:
self.logger.error(f"Failed to set opt enabled status to {enabled} for opt {opt_id}")
return False
return True