1
0
Fork 0

- 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

@ -336,6 +336,42 @@ perform all previous updates as well:
python dbutils.py --game SDDT upgrade
```
### Controlling Events (Ranking Event, Technical Challenge Event, Mission Event)
Events are controlled by 2 types of enabled events:
- RankingEvent (type 6), TechChallengeEvent (type 17)
- AcceptRankingEvent (type 7), AcceptTechChallengeEvent (type 18)
Both Ranking and Accept must be enabled for event to function properly
Event will run for the time specified in startDate and endDate
AcceptRankingEvent and AcceptTechChallengeEvent are reward period for events, which specify from what startDate until endDate you can collect the rewards for attending the event, so the reward period must start in the future, e.g. :
- RankingEvent startDate 2023-12-01 - endDate 2023-12-30 - period in which whole event is running
- AcceptRankingEvent startDate 2023-12-23 - endDate 2023-12-30 - period in which you can collect rewards for the event
If player misses the AcceptRankingEvent period - ranking will be invalidated and receive lowest reward from the event (typically 500x money)
Technical Challenge Song List:
Songs that are used for Technical Challenge are not stored anywhere in data files, so you need to fill the database table by yourself, you can gather all songs that should be in Technical Challenges from ONGEKI japanese wikis, or, you can create your own sets:
Database table : `ongeki_tech_music_list`
```
id: Id in table, just increment for each entry
eventId: Id of the event in ongeki_static_events, insert the Id of the TechChallengeEvent (type 17) you want the song be assigned to
musicId: Id of the song you want to add, use songId from ongeki_static_music table
level: Difficulty of the song you want to track during the event, from 0(basic) to 3(master)
```
Current implementation of Ranking and Technical Challenge Events are updated on every profile save to the Network, and Ranked on each player login, in official specification, calculation for current rank on the network should be done in the maintenance window
Mission Event (type 13) is a monthly type of event, which is used when another event doesn't have it's own Ranking or Technical Challenge Event running, only one Mission Event should be running at a time, so enable only the specific Mission you want to run currently on the Network
If you're often trying fresh cards, registering new profiles etc., you can also consider disabling all Announcement Events (type 1), as it will disable all the banners that pop up on login (they show up only once though, so if you click through them once they won't show again)
## Card Maker
### SDED
@ -587,4 +623,4 @@ python dbutils.py --game SDEW upgrade
- Midorica - Limited Network Support
- Dniel97 - Helping with network base
- tungnotpunk - Source
- tungnotpunk - Source

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()