mai2: add present support

This commit is contained in:
Hay1tsme 2024-06-28 15:48:27 -04:00
parent fef527d61f
commit 4446ff1f21
6 changed files with 160 additions and 14 deletions

View File

@ -0,0 +1,41 @@
"""mai2_presents
Revision ID: 5ea363686347
Revises: 680789dabab3
Create Date: 2024-06-28 14:49:07.666879
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '5ea363686347'
down_revision = '680789dabab3'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('mai2_item_present',
sa.Column('id', sa.BIGINT(), nullable=False),
sa.Column('version', sa.INTEGER(), nullable=True),
sa.Column('user', sa.Integer(), nullable=True),
sa.Column('itemKind', sa.INTEGER(), nullable=False),
sa.Column('itemId', sa.INTEGER(), nullable=False),
sa.Column('stock', sa.INTEGER(), server_default='1', nullable=False),
sa.Column('startDate', sa.TIMESTAMP(), nullable=True),
sa.Column('endDate', sa.TIMESTAMP(), nullable=True),
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('version', 'user', 'itemKind', 'itemId', name='mai2_item_present_uk'),
mysql_charset='utf8mb4'
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('mai2_item_present')
# ### end Alembic commands ###

View File

@ -182,6 +182,14 @@ Config file is located in `config/cxb.yaml`.
## maimai DX ## maimai DX
### 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 |
### Versions ### Versions
| Game Code | Version ID | Version Name | | Game Code | Version ID | Version Name |

View File

@ -471,7 +471,25 @@ class Mai2Base:
} }
async def handle_get_user_present_api_request(self, data: Dict) -> Dict: async def handle_get_user_present_api_request(self, data: Dict) -> Dict:
return { "userId": data.get("userId", 0), "length": 0, "userPresentList": []} items: List[Dict[str, Any]] = []
user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"])
if user_pres_list:
for present in user_pres_list:
if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()):
self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})")
continue # present period hasn't started yet, move onto the next one
if (present['endDate'] and present['endDate'].timestamp() < datetime.now().timestamp()):
self.logger.warn(f"Present {present['id']} ended on {present['endDate']} and should be removed")
continue # present period ended, move onto the next one
test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId'])
if not test: # Don't send presents for items the user already has
items.append({"itemId": present['itemId'], "itemKind": present['itemKind'], "stock": present['stock'], "isValid": True})
self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present")
await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True)
return { "userId": data.get("userId", 0), "length": len(items), "userPresentList": items}
async def handle_get_transfer_friend_api_request(self, data: Dict) -> Dict: async def handle_get_transfer_friend_api_request(self, data: Dict) -> Dict:
return {} return {}

View File

@ -327,16 +327,35 @@ class Mai2DX(Mai2Base):
async def handle_get_user_item_api_request(self, data: Dict) -> Dict: async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(data["nextIndex"] / 10000000000) kind = int(data["nextIndex"] / 10000000000)
next_idx = int(data["nextIndex"] % 10000000000) next_idx = int(data["nextIndex"] % 10000000000)
user_item_list = await self.data.item.get_items(data["userId"], kind)
items: List[Dict[str, Any]] = [] items: List[Dict[str, Any]] = []
for i in range(next_idx, len(user_item_list)):
tmp = user_item_list[i]._asdict() if kind == 4: # presents
tmp.pop("user") user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"])
tmp.pop("id") if user_pres_list:
items.append(tmp) for present in user_pres_list:
if len(items) >= int(data["maxCount"]): if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()):
break self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})")
continue # present period hasn't started yet, move onto the next one
if (present['endDate'] and present['endDate'].timestamp() < datetime.now().timestamp()):
self.logger.warn(f"Present {present['id']} ended on {present['endDate']} and should be removed")
continue # present period ended, move onto the next one
test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId'])
if not test: # Don't send presents for items the user already has
items.append({"itemId": present['itemId'], "itemKind": present['itemKind'], "stock": present['stock'], "isValid": True})
self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present")
await self.data.item.put_item(data["userId"], present['itemKind'], present['itemId'], present['stock'], True)
else:
user_item_list = await self.data.item.get_items(data["userId"], kind)
for i in range(next_idx, len(user_item_list)):
tmp = user_item_list[i]._asdict()
tmp.pop("user")
tmp.pop("id")
items.append(tmp)
if len(items) >= int(data["maxCount"]):
break
xout = kind * 10000000000 + next_idx + len(items) xout = kind * 10000000000 + next_idx + len(items)

View File

@ -2,8 +2,8 @@ from core.data.schema import BaseData, metadata
from datetime import datetime from datetime import datetime
from typing import Optional, Dict, List from typing import Optional, Dict, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, or_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BIGINT, INTEGER
from sqlalchemy.schema import ForeignKey from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert from sqlalchemy.dialects.mysql import insert
@ -198,6 +198,20 @@ print_detail = Table(
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
present = Table(
"mai2_item_present",
metadata,
Column('id', BIGINT, primary_key=True, nullable=False),
Column('version', INTEGER),
Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("itemKind", INTEGER, nullable=False),
Column("itemId", INTEGER, nullable=False),
Column("stock", INTEGER, nullable=False, server_default="1"),
Column("startDate", TIMESTAMP),
Column("endDate", TIMESTAMP),
UniqueConstraint("version", "user", "itemKind", "itemId", name="mai2_item_present_uk"),
mysql_charset="utf8mb4",
)
class Mai2ItemData(BaseData): class Mai2ItemData(BaseData):
async def put_item( async def put_item(
@ -476,7 +490,7 @@ class Mai2ItemData(BaseData):
musicId = music_id musicId = music_id
) )
conflict = sql.on_duplicate_key_do_nothing() conflict = sql.on_duplicate_key_update(musicId = music_id)
result = await self.execute(conflict) result = await self.execute(conflict)
if result: if result:
@ -586,3 +600,49 @@ class Mai2ItemData(BaseData):
) )
return None return None
return result.lastrowid return result.lastrowid
async def put_present(self, item_kind: int, item_id: int, version: int = None, user_id: int = None, start_date: datetime = None, end_date: datetime = None) -> Optional[int]:
sql = insert(present).values(
version = version,
user = user_id,
itemKind = item_kind,
itemId = item_id,
startDate = start_date,
endDate = end_date
)
conflict = sql.on_duplicate_key_update(
startDate = start_date,
endDate = end_date
)
result = await self.execute(conflict)
if result:
return result.lastrowid
self.logger.error(f"Failed to add present item {item_id}!")
async def get_presents_by_user(self, user_id: int = None) -> Optional[List[Row]]:
result = await self.execute(present.select(or_(present.c.user == user_id, present.c.user is None)))
if result:
return result.fetchall()
async def get_presents_by_version(self, ver: int = None) -> Optional[List[Row]]:
result = await self.execute(present.select(or_(present.c.version == ver, present.c.version is None)))
if result:
return result.fetchall()
async def get_presents_by_version_user(self, ver: int = None, user_id: int = None) -> Optional[List[Row]]:
result = await self.execute(present.select(
and_(
or_(present.c.user == user_id, present.c.user is None)),
or_(present.c.version == ver, present.c.version is None)
)
)
if result:
return result.fetchall()
async def get_present_by_id(self, present_id: int) -> Optional[Row]:
result = await self.execute(present.select(present.c.id == present_id))
if result:
return result.fetchone()

View File

@ -892,7 +892,7 @@ class Mai2ProfileData(BaseData):
rival = rival_id rival = rival_id
) )
conflict = sql.on_duplicate_key_do_nothing() conflict = sql.on_duplicate_key_update(rival = rival_id)
result = await self.execute(conflict) result = await self.execute(conflict)
if result: if result: