chuni: added login bonus (+importer), fixed config strings

This commit is contained in:
Dniel97 2023-03-28 18:28:57 +02:00
parent 541fe76a7c
commit 1aa92458f4
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
8 changed files with 424 additions and 44 deletions

View File

@ -5,11 +5,14 @@ server:
team: team:
name: ARTEMiS name: ARTEMiS
mods:
use_login_bonus: True
version: version:
"11": 11:
rom: 2.00.00 rom: 2.00.00
data: 2.00.00 data: 2.00.00
"12": 12:
rom: 2.05.00 rom: 2.05.00
data: 2.05.00 data: 2.05.00

View File

@ -23,7 +23,92 @@ class ChuniBase:
self.version = ChuniConstants.VER_CHUNITHM self.version = ChuniConstants.VER_CHUNITHM
def handle_game_login_api_request(self, data: Dict) -> Dict: def handle_game_login_api_request(self, data: Dict) -> Dict:
# self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]}) """
Handles the login bonus logic, required for the game because
getUserLoginBonus gets called after getUserItem and therefore the
items needs to be inserted in the database before they get requested.
Adds a bonusCount after a user logged in after 24 hours, makes sure
loginBonus 30 gets looped, only show the login banner every 24 hours,
adds the bonus to items (itemKind 6)
"""
# ignore the login bonus if disabled in config
if not self.game_cfg.mods.use_login_bonus:
return {"returnCode": 1}
user_id = data["userId"]
login_bonus_presets = self.data.static.get_login_bonus_presets(self.version)
for preset in login_bonus_presets:
# check if a user already has some pogress and if not add the
# login bonus entry
user_login_bonus = self.data.item.get_login_bonus(
user_id, self.version, preset["id"]
)
if user_login_bonus is None:
self.data.item.put_login_bonus(user_id, self.version, preset["id"])
# yeah i'm lazy
user_login_bonus = self.data.item.get_login_bonus(
user_id, self.version, preset["id"]
)
# skip the login bonus entirely if its already finished
if user_login_bonus["isFinished"]:
continue
# make sure the last login is more than 24 hours ago
if user_login_bonus["lastUpdateDate"] < datetime.now() - timedelta(
hours=24
):
# increase the login day counter and update the last login date
bonus_count = user_login_bonus["bonusCount"] + 1
last_update_date = datetime.now()
all_login_boni = self.data.static.get_login_bonus(
self.version, preset["id"]
)
# assume its not None
max_needed_days = all_login_boni[0]["needLoginDayCount"]
# make sure to not show login boni after all days got redeemed
is_finished = False
if bonus_count > max_needed_days:
# assume that all login preset ids under 3000 needs to be
# looped, like 30 and 40 are looped, 40 does not work?
if preset["id"] < 3000:
bonus_count = 1
else:
is_finished = True
# grab the item for the corresponding day
login_item = self.data.static.get_login_bonus_by_required_days(
self.version, preset["id"], bonus_count
)
if login_item is not None:
# now add the present to the database so the
# handle_get_user_item_api_request can grab them
self.data.item.put_item(
user_id,
{
"itemId": login_item["presentId"],
"itemKind": 6,
"stock": login_item["itemNum"],
"isValid": True,
},
)
self.data.item.put_login_bonus(
user_id,
self.version,
preset["id"],
bonusCount=bonus_count,
lastUpdateDate=last_update_date,
isWatched=False,
isFinished=is_finished,
)
return {"returnCode": 1} return {"returnCode": 1}
def handle_game_logout_api_request(self, data: Dict) -> Dict: def handle_game_logout_api_request(self, data: Dict) -> Dict:
@ -309,26 +394,28 @@ class ChuniBase:
} }
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
""" user_id = data["userId"]
Unsure how to get this to trigger... user_login_bonus = self.data.item.get_all_login_bonus(user_id, self.version)
""" if user_login_bonus is None:
return {"userId": user_id, "length": 0, "userLoginBonusList": []}
user_login_list = []
for bonus in user_login_bonus:
user_login_list.append(
{
"presetId": bonus["presetId"],
"bonusCount": bonus["bonusCount"],
"lastUpdateDate": datetime.strftime(
bonus["lastUpdateDate"], "%Y-%m-%d %H:%M:%S"
),
"isWatched": bonus["isWatched"],
}
)
return { return {
"userId": data["userId"], "userId": user_id,
"length": 2, "length": len(user_login_list),
"userLoginBonusList": [ "userLoginBonusList": user_login_list,
{
"presetId": "10",
"bonusCount": "0",
"lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true",
},
{
"presetId": "20",
"bonusCount": "0",
"lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true",
},
],
} }
def handle_get_user_map_api_request(self, data: Dict) -> Dict: def handle_get_user_map_api_request(self, data: Dict) -> Dict:
@ -596,6 +683,12 @@ class ChuniBase:
for emoney in upsert["userEmoneyList"]: for emoney in upsert["userEmoneyList"]:
self.data.profile.put_profile_emoney(user_id, emoney) self.data.profile.put_profile_emoney(user_id, emoney)
if "userLoginBonusList" in upsert:
for login in upsert["userLoginBonusList"]:
self.data.item.put_login_bonus(
user_id, self.version, login["presetId"], isWatched=True
)
return {"returnCode": "1"} return {"returnCode": "1"}
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict: def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:

View File

@ -32,19 +32,29 @@ class ChuniTeamConfig:
) )
class ChuniModsConfig:
def __init__(self, parent_config: "ChuniConfig") -> None:
self.__config = parent_config
@property
def use_login_bonus(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "chuni", "mods", "use_login_bonus", default=True
)
class ChuniVersionConfig: class ChuniVersionConfig:
def __init__(self, parent_config: "ChuniConfig") -> None: def __init__(self, parent_config: "ChuniConfig") -> None:
self.__config = parent_config self.__config = parent_config
def version_rom(self, version: int) -> str: def version(self, version: int) -> Dict:
"""
in the form of:
11: {"rom": 2.00.00, "data": 2.00.00}
"""
return CoreConfig.get_config_field( return CoreConfig.get_config_field(
self.__config, "chuni", "version", f"{version}", "rom", default="2.00.00" self.__config, "chuni", "version", default={}
) )[version]
def version_data(self, version: int) -> str:
return CoreConfig.get_config_field(
self.__config, "chuni", "version", f"{version}", "data", default="2.00.00"
)
class ChuniCryptoConfig: class ChuniCryptoConfig:
@ -73,5 +83,6 @@ class ChuniConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:
self.server = ChuniServerConfig(self) self.server = ChuniServerConfig(self)
self.team = ChuniTeamConfig(self) self.team = ChuniTeamConfig(self)
self.mods = ChuniModsConfig(self)
self.version = ChuniVersionConfig(self) self.version = ChuniVersionConfig(self)
self.crypto = ChuniCryptoConfig(self) self.crypto = ChuniCryptoConfig(self)

View File

@ -49,8 +49,8 @@ class ChuniNew(ChuniBase):
"matchEndTime": match_end, "matchEndTime": match_end,
"matchTimeLimit": 99, "matchTimeLimit": 99,
"matchErrorLimit": 9999, "matchErrorLimit": 9999,
"romVersion": self.game_cfg.version.version_rom(self.version), "romVersion": self.game_cfg.version.version(self.version)["rom"],
"dataVersion": self.game_cfg.version.version_data(self.version), "dataVersion": self.game_cfg.version.version(self.version)["data"],
"matchingUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", "matchingUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
"matchingUriX": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", "matchingUriX": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
"udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", "udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",

View File

@ -13,12 +13,8 @@ class ChuniNewPlus(ChuniNew):
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version_rom( ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)["rom"]
self.version ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"]
)
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version_data(
self.version
)
ret["gameSetting"][ ret["gameSetting"][
"matchingUri" "matchingUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"

View File

@ -42,6 +42,80 @@ class ChuniReader(BaseReader):
self.read_music(f"{dir}/music") self.read_music(f"{dir}/music")
self.read_charges(f"{dir}/chargeItem") self.read_charges(f"{dir}/chargeItem")
self.read_avatar(f"{dir}/avatarAccessory") self.read_avatar(f"{dir}/avatarAccessory")
self.read_login_bonus(f"{dir}/")
def read_login_bonus(self, root_dir: str) -> None:
for root, dirs, files in walk(f"{root_dir}loginBonusPreset"):
for dir in dirs:
if path.exists(f"{root}/{dir}/LoginBonusPreset.xml"):
with open(f"{root}/{dir}/LoginBonusPreset.xml", "rb") as fp:
bytedata = fp.read()
strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata)
for name in xml_root.findall("name"):
id = name.find("id").text
name = name.find("str").text
is_enabled = (
True if xml_root.find("disableFlag").text == "false" else False
)
result = self.data.static.put_login_bonus_preset(
self.version, id, name, is_enabled
)
if result is not None:
self.logger.info(f"Inserted login bonus preset {id}")
else:
self.logger.warn(f"Failed to insert login bonus preset {id}")
for bonus in xml_root.find("infos").findall("LoginBonusDataInfo"):
for name in bonus.findall("loginBonusName"):
bonus_id = name.find("id").text
bonus_name = name.find("str").text
if path.exists(
f"{root_dir}/loginBonus/loginBonus{bonus_id}/LoginBonus.xml"
):
with open(
f"{root_dir}/loginBonus/loginBonus{bonus_id}/LoginBonus.xml",
"rb",
) as fp:
bytedata = fp.read()
strdata = bytedata.decode("UTF-8")
bonus_root = ET.fromstring(strdata)
for present in bonus_root.findall("present"):
present_id = present.find("id").text
present_name = present.find("str").text
item_num = int(bonus_root.find("itemNum").text)
need_login_day_count = int(
bonus_root.find("needLoginDayCount").text
)
login_bonus_category_type = int(
bonus_root.find("loginBonusCategoryType").text
)
result = self.data.static.put_login_bonus(
self.version,
id,
bonus_id,
bonus_name,
present_id,
present_name,
item_num,
need_login_day_count,
login_bonus_category_type,
)
if result is not None:
self.logger.info(f"Inserted login bonus {bonus_id}")
else:
self.logger.warn(
f"Failed to insert login bonus {bonus_id}"
)
def read_events(self, evt_dir: str) -> None: def read_events(self, evt_dir: str) -> None:
for root, dirs, files in walk(evt_dir): for root, dirs, files in walk(evt_dir):

View File

@ -184,8 +184,73 @@ print_detail = Table(
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
login_bonus = Table(
"chuni_item_login_bonus",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False),
Column("presetId", Integer, nullable=False),
Column("bonusCount", Integer, nullable=False, server_default="0"),
Column("lastUpdateDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
Column("isWatched", Boolean, server_default="0"),
Column("isFinished", Boolean, server_default="0"),
UniqueConstraint("version", "user", "presetId", name="chuni_item_login_bonus_uk"),
mysql_charset="utf8mb4",
)
class ChuniItemData(BaseData): class ChuniItemData(BaseData):
def put_login_bonus(
self, user_id: int, version: int, preset_id: int, **login_bonus_data
) -> Optional[int]:
sql = insert(login_bonus).values(
version=version, user=user_id, presetId=preset_id, **login_bonus_data
)
conflict = sql.on_duplicate_key_update(presetId=preset_id, **login_bonus_data)
result = self.execute(conflict)
if result is None:
return None
return result.lastrowid
def get_all_login_bonus(
self, user_id: int, version: int, is_finished: bool = False
) -> Optional[List[Row]]:
sql = login_bonus.select(
and_(
login_bonus.c.version == version,
login_bonus.c.user == user_id,
login_bonus.c.isFinished == is_finished,
)
)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def get_login_bonus(
self, user_id: int, version: int, preset_id: int
) -> Optional[Row]:
sql = login_bonus.select(
and_(
login_bonus.c.version == version,
login_bonus.c.user == user_id,
login_bonus.c.presetId == preset_id,
)
)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]: def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
character_data["user"] = user_id character_data["user"] = user_id
@ -335,7 +400,7 @@ class ChuniItemData(BaseData):
sql = print_state.select( sql = print_state.select(
and_( and_(
print_state.c.user == aime_id, print_state.c.user == aime_id,
print_state.c.hasCompleted == has_completed print_state.c.hasCompleted == has_completed,
) )
) )
@ -351,7 +416,7 @@ class ChuniItemData(BaseData):
and_( and_(
print_state.c.user == aime_id, print_state.c.user == aime_id,
print_state.c.gachaId == gacha_id, print_state.c.gachaId == gacha_id,
print_state.c.hasCompleted == has_completed print_state.c.hasCompleted == has_completed,
) )
) )
@ -380,9 +445,7 @@ class ChuniItemData(BaseData):
user=aime_id, serialId=serial_id, **user_print_data user=aime_id, serialId=serial_id, **user_print_data
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(user=aime_id, **user_print_data)
user=aime_id, **user_print_data
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:

View File

@ -122,8 +122,148 @@ gacha_cards = Table(
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
login_bonus_preset = Table(
"chuni_static_login_bonus_preset",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer, nullable=False),
Column("presetName", String(255), nullable=False),
Column("isEnabled", Boolean, server_default="1"),
UniqueConstraint("version", "id", name="chuni_static_login_bonus_preset_uk"),
mysql_charset="utf8mb4",
)
login_bonus = Table(
"chuni_static_login_bonus",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer, nullable=False),
Column(
"presetId",
ForeignKey(
"chuni_static_login_bonus_preset.id",
ondelete="cascade",
onupdate="cascade",
),
nullable=False,
),
Column("loginBonusId", Integer, nullable=False),
Column("loginBonusName", String(255), nullable=False),
Column("presentId", Integer, nullable=False),
Column("presentName", String(255), nullable=False),
Column("itemNum", Integer, nullable=False),
Column("needLoginDayCount", Integer, nullable=False),
Column("loginBonusCategoryType", Integer, nullable=False),
UniqueConstraint(
"version", "presetId", "loginBonusId", name="chuni_static_login_bonus_uk"
),
mysql_charset="utf8mb4",
)
class ChuniStaticData(BaseData): class ChuniStaticData(BaseData):
def put_login_bonus(
self,
version: int,
preset_id: int,
login_bonus_id: int,
login_bonus_name: str,
present_id: int,
present_ame: str,
item_num: int,
need_login_day_count: int,
login_bonus_category_type: int,
) -> Optional[int]:
sql = insert(login_bonus).values(
version=version,
presetId=preset_id,
loginBonusId=login_bonus_id,
loginBonusName=login_bonus_name,
presentId=present_id,
presentName=present_ame,
itemNum=item_num,
needLoginDayCount=need_login_day_count,
loginBonusCategoryType=login_bonus_category_type,
)
conflict = sql.on_duplicate_key_update(
loginBonusName=login_bonus_name,
presentName=present_ame,
itemNum=item_num,
needLoginDayCount=need_login_day_count,
loginBonusCategoryType=login_bonus_category_type,
)
result = self.execute(conflict)
if result is None:
return None
return result.lastrowid
def get_login_bonus(
self, version: int, preset_id: int,
) -> Optional[List[Row]]:
sql = login_bonus.select(
and_(
login_bonus.c.version == version,
login_bonus.c.presetId == preset_id,
)
).order_by(login_bonus.c.needLoginDayCount.desc())
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def get_login_bonus_by_required_days(
self, version: int, preset_id: int, need_login_day_count: int
) -> Optional[Row]:
sql = login_bonus.select(
and_(
login_bonus.c.version == version,
login_bonus.c.presetId == preset_id,
login_bonus.c.needLoginDayCount == need_login_day_count,
)
)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def put_login_bonus_preset(
self, version: int, preset_id: int, preset_name: str, is_enabled: bool
) -> Optional[int]:
sql = insert(login_bonus_preset).values(
id=preset_id,
version=version,
presetName=preset_name,
isEnabled=is_enabled,
)
conflict = sql.on_duplicate_key_update(
presetName=preset_name, isEnabled=is_enabled
)
result = self.execute(conflict)
if result is None:
return None
return result.lastrowid
def get_login_bonus_presets(
self, version: int, is_enabled: bool = True
) -> Optional[List[Row]]:
sql = login_bonus_preset.select(
and_(
login_bonus_preset.c.version == version,
login_bonus_preset.c.isEnabled == is_enabled,
)
)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_event( def put_event(
self, version: int, event_id: int, type: int, name: str self, version: int, event_id: int, type: int, name: str
) -> Optional[int]: ) -> Optional[int]: