- Add Ranking Event Support

- Add Technical Challenge Event Support
- Fix Event Enumeration for EVT_TYPES as Enum starts with 1, to be in spec with what game expects, also add missing Max EVT_TYPE
- Add documentation on how to properly configure and run Events for ONGEKI
This commit is contained in:
phantomlan
2023-11-05 18:09:58 +01:00
committed by phantomlan
parent 4bedf71d3d
commit 4da886a083
4 changed files with 171 additions and 21 deletions

View File

@@ -228,7 +228,21 @@ class OngekiBase:
return {"length": 0, "gameSaleList": []}
def handle_get_game_tech_music_api_request(self, data: Dict) -> Dict:
return {"length": 0, "gameTechMusicList": []}
music_list = self.data.item.get_tech_music()
prep_music_list = []
for music in music_list:
tmp = music._asdict()
tmp.pop("id")
prep_music_list.append(tmp)
if prep_music_list is None:
return {"length": 0, "gameTechMusicList": []}
return {
"length": len(prep_music_list),
"gameTechMusicList": prep_music_list,
}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
@@ -283,7 +297,7 @@ class OngekiBase:
"endDate": "2099-12-31 00:00:00.0",
}
)
return {
"type": data["type"],
"length": len(evt_list),
@@ -403,15 +417,24 @@ class OngekiBase:
}
def handle_get_user_tech_event_ranking_api_request(self, data: Dict) -> Dict:
# user_event_ranking_list = self.data.item.get_tech_event_ranking(data["userId"])
# if user_event_ranking_list is None: return {}
user_tech_event_ranks = self.data.item.get_tech_event_ranking(data["userId"])
if user_tech_event_ranks is None:
return {
"userId": data["userId"],
"length": 0,
"userTechEventRankingList": [],
}
# collect the whole table and clear other players, to preserve proper ranking
evt_ranking = []
# for evt in user_event_ranking_list:
# tmp = evt._asdict()
# tmp.pop("id")
# tmp.pop("user")
# evt_ranking.append(tmp)
for evt in user_tech_event_ranks:
tmp = evt._asdict()
if tmp["user"] != data["userId"]:
tmp.clear()
else:
tmp.pop("id")
tmp.pop("user")
evt_ranking.append(tmp)
return {
"userId": data["userId"],
@@ -533,20 +556,26 @@ class OngekiBase:
return {"userId": data["userId"], "userData": user_data}
def handle_get_user_event_ranking_api_request(self, data: Dict) -> Dict:
# user_event_ranking_list = self.data.item.get_event_ranking(data["userId"])
# if user_event_ranking_list is None: return {}
user_event_ranking_list = self.data.item.get_ranking_event_ranks(data["userId"])
if user_event_ranking_list is None:
return {}
evt_ranking = []
# for evt in user_event_ranking_list:
# tmp = evt._asdict()
# tmp.pop("id")
# tmp.pop("user")
# evt_ranking.append(tmp)
# We collect the whole ranking table, and clear out any not needed data, this way we preserve the proper ranking
# In official spec this should be done server side, in maintenance period
prep_event_ranking = []
for evt in user_event_ranking_list:
tmp = evt._asdict()
if tmp["user"] != data["userId"]:
tmp.clear()
else:
tmp.pop("id")
tmp.pop("user")
prep_event_ranking.append(tmp)
return {
"userId": data["userId"],
"length": len(evt_ranking),
"userEventRankingList": evt_ranking,
"length": len(prep_event_ranking),
"userEventRankingList": prep_event_ranking,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
@@ -788,6 +817,7 @@ class OngekiBase:
tmp.pop("user")
mission_point_list.append(tmp)
return {
"userId": data["userId"],
"length": len(mission_point_list),
@@ -804,6 +834,10 @@ class OngekiBase:
tmp = evt_music._asdict()
tmp.pop("id")
tmp.pop("user")
# pop other stuff event_point doesn't want
tmp.pop("rank")
tmp.pop("type")
tmp.pop("date")
event_point_list.append(tmp)
return {
@@ -987,6 +1021,9 @@ class OngekiBase:
for x in upsert["userTechEventList"]:
self.data.item.put_tech_event(user_id, x)
# This should be updated once a day in maintenance window, but for time being we will push the update on each upsert
self.data.item.put_tech_event_ranking(user_id, x)
if "userKopList" in upsert:
for x in upsert["userKopList"]:
self.data.profile.put_kop(user_id, x)

View File

@@ -19,7 +19,6 @@ class OngekiConstants:
EVT_TYPES: Enum = Enum(
"EVT_TYPES",
[
"None",
"Announcement",
"Movie",
"AddMyList",
@@ -39,6 +38,8 @@ class OngekiConstants:
"TechChallengeEvent",
"AcceptTechChallengeEvent",
"SilverJewelEvent",
"Max",
"None",
],
)

View File

@@ -1,3 +1,4 @@
from datetime import date, datetime, timedelta
from typing import Dict, Optional, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
@@ -172,6 +173,9 @@ event_point = Table(
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("eventId", Integer),
Column("point", Integer),
Column("rank", Integer),
Column("type", Integer),
Column("date", String(25)),
Column("isRankingRewarded", Boolean),
UniqueConstraint("user", "eventId", name="ongeki_user_event_point_uk"),
mysql_charset="utf8mb4",
@@ -247,6 +251,31 @@ tech_event = Table(
mysql_charset="utf8mb4",
)
tech_music = Table(
"ongeki_tech_music_list",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("eventId", Integer),
Column("musicId", Integer),
Column("level", Integer),
UniqueConstraint("musicId", name="ongeki_tech_music_list_uk"),
mysql_charset="utf8mb4",
)
tech_ranking = Table(
"ongeki_tech_event_ranking",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("date", String(25)),
Column("eventId", Integer),
Column("rank", Integer),
Column("totalPlatinumScore", Integer),
Column("totalTechScore", Integer),
UniqueConstraint("user", "eventId", name="ongeki_tech_event_ranking_uk"),
mysql_charset="utf8mb4",
)
gacha = Table(
"ongeki_user_gacha",
metadata,
@@ -534,7 +563,12 @@ class OngekiItemData(BaseData):
return result.fetchall()
def put_event_point(self, aime_id: int, event_point_data: Dict) -> Optional[int]:
# We update only the newest (type: 1) entry, in official spec game watches for both latest(type:1) and previous (type:2) entries to give an additional info how many ranks has player moved up or down
# This fully featured is on TODO list, at the moment we just update the tables as data comes and give out rank as request comes
event_point_data["user"] = aime_id
event_point_data["type"] = 1
event_point_time = datetime.now()
event_point_data["date"] = datetime.strftime(event_point_time, "%Y-%m-%d %H:%M")
sql = insert(event_point).values(**event_point_data)
conflict = sql.on_duplicate_key_update(**event_point_data)
@@ -613,6 +647,14 @@ class OngekiItemData(BaseData):
return None
return result.fetchall()
def get_tech_music(self) -> Optional[List[Dict]]:
sql = select(tech_music)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_tech_event(self, aime_id: int, tech_event_data: Dict) -> Optional[int]:
tech_event_data["user"] = aime_id
@@ -625,6 +667,22 @@ class OngekiItemData(BaseData):
return None
return result.lastrowid
def put_tech_event_ranking(self, aime_id: int, tech_event_data: Dict) -> Optional[int]:
tech_event_data["user"] = aime_id
tech_event_data.pop("isRankingRewarded")
tech_event_data.pop("isTotalTechNewRecord")
tech_event_data["date"] = tech_event_data.pop("techRecordDate")
tech_event_data["rank"] = 0
sql = insert(tech_ranking).values(**tech_event_data)
conflict = sql.on_duplicate_key_update(**tech_event_data)
result = self.execute(conflict)
if result is None:
self.logger.warning(f"put_tech_event_ranking: Failed to update ranking! aime_id {aime_id}")
return None
return result.lastrowid
def get_tech_event(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(tech_event).where(tech_event.c.user == aime_id)
result = self.execute(sql)
@@ -714,3 +772,21 @@ class OngekiItemData(BaseData):
)
return None
return result.lastrowid
def get_ranking_event_ranks(self, aime_id: int) -> Optional[List[Dict]]:
# Calculates player rank on GameRequest from server, and sends it back, official spec would rank players in maintenance period, on TODO list
sql = select(event_point.c.id, event_point.c.user, event_point.c.eventId, event_point.c.type, func.row_number().over(partition_by=event_point.c.eventId, order_by=event_point.c.point.desc()).label('rank'), event_point.c.date, event_point.c.point)
result = self.execute(sql)
if result is None:
self.logger.error(f"failed to rank aime_id: {aime_id} ranking event positions")
return None
return result.fetchall()
def get_tech_event_ranking(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(tech_ranking.c.id, tech_ranking.c.user, tech_ranking.c.date, tech_ranking.c.eventId, func.row_number().over(partition_by=tech_ranking.c.eventId, order_by=[tech_ranking.c.totalTechScore.desc(),tech_ranking.c.totalPlatinumScore.desc()]).label('rank'), tech_ranking.c.totalTechScore, tech_ranking.c.totalPlatinumScore)
result = self.execute(sql)
if result is None:
self.logger.warning(f"aime_id: {aime_id} has no tech ranking ranks")
return None
return result.fetchall()