Merge branch 'develop' into fork_develop

This commit is contained in:
Dniel97 2023-10-15 18:06:07 +02:00
commit 346b74e51b
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
17 changed files with 424 additions and 48 deletions

View File

@ -150,6 +150,12 @@ class DatabaseConfig:
default=10000,
)
@property
def enable_memcached(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "database", "enable_memcached", default=True
)
@property
def memcached_host(self) -> str:
return CoreConfig.get_config_field(

View File

@ -17,7 +17,7 @@ except ModuleNotFoundError:
def cached(lifetime: int = 10, extra_key: Any = None) -> Callable:
def _cached(func: Callable) -> Callable:
if has_mc:
if has_mc and (cfg and cfg.database.enable_memcached):
hostname = "127.0.0.1"
if cfg:
hostname = cfg.database.memcached_host

View File

@ -0,0 +1,2 @@
ALTER TABLE diva_profile
DROP skn_eqp;

View File

@ -0,0 +1,2 @@
ALTER TABLE diva_profile
ADD skn_eqp INT NOT NULL DEFAULT 0;

View File

@ -59,6 +59,28 @@ python read.py --game SDBT --version <version ID> --binfolder /path/to/game/fold
The importer for Chunithm will import: Events, Music, Charge Items and Avatar Accesories.
### Config
Config file is located in `config/chuni.yaml`.
| Option | Info |
|------------------|----------------------------------------------------------------------------------------------------------------|
| `news_msg` | If this is set, the news at the top of the main screen will be displayed (up to Chunithm Paradise Lost) |
| `name` | If this is set, all players that are not on a team will use this one by default. |
| `rank_scale` | Scales the in-game ranking based on the number of teams within the database |
| `use_login_bonus`| This is used to enable the login bonuses |
| `crypto` | This option is used to enable the TLS Encryption |
**If you would like to use network encryption, the following will be required underneath but key, iv and hash are required:**
```yaml
crypto:
encrypted_only: False
keys:
13: ["0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000", "0000000000000000"]
```
### Database upgrade
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see
@ -88,6 +110,36 @@ After a failed Online Battle the room will be deleted. The host is used for the
- Timer countdown should be handled globally and not by one user
- Game can freeze or can crash if someone (especially the host) leaves the matchmaking
### Rivals
You can configure up to 4 rivals in Chunithm on a per-user basis. There is no UI to do this currently, so in the database, you can do this:
```sql
INSERT INTO aime.chuni_item_favorite (user, version, favId, favKind) VALUES (<user1>, <version>, <user2>, 2);
INSERT INTO aime.chuni_item_favorite (user, version, favId, favKind) VALUES (<user2>, <version>, <user1>, 2);
```
Note that the version **must match**, otherwise song lookup may not work.
### Teams
You can also configure teams for users to be on. There is no UI to do this currently, so in the database, you can do this:
```sql
INSERT INTO aime.chuni_profile_team (teamName) VALUES (<teamName>);
```
Team names can be regular ASCII, and they will be displayed ingame.
On smaller installations, you may also wish to enable scaled team rankings. By default, Chunithm determines team ranking within the first 100 teams. This can be configured in the YAML:
```yaml
team:
rank_scale: True # Scales the in-game ranking based on the number of teams within the database, rather than the default scale of ~100 that the game normally uses.
```
### Favorite songs
You can set the songs that will be in a user's Favorite Songs category using the following SQL entries:
```sql
INSERT INTO aime.chuni_item_favorite (user, version, favId, favKind) VALUES (<user>, <version>, <songId>, 1);
```
The songId is based on the actual ID within your version of Chunithm.
## crossbeats REV.
@ -211,6 +263,9 @@ Config file is located in `config/diva.yaml`.
| `unlock_all_modules` | Unlocks all modules (costumes) by default, if set to `False` all modules need to be purchased |
| `unlock_all_items` | Unlocks all items (customizations) by default, if set to `False` all items need to be purchased |
### Custom PV Lists (databanks)
In order to use custom PV Lists, simply drop in your .dat files inside of /titles/diva/data/ and make sure they are called PvList0.dat, PvList1.dat, PvList2.dat, PvList3.dat and PvList4.dat exactly.
### Database upgrade
@ -257,9 +312,19 @@ Config file is located in `config/ongeki.yaml`.
| Option | Info |
|------------------|----------------------------------------------------------------------------------------------------------------|
| `enabled_gachas` | Enter all gacha IDs for Card Maker to work, other than default may not work due to missing cards added to them |
| `crypto` | This option is used to enable the TLS Encryption |
Note: 1149 and higher are only for Card Maker 1.35 and higher and will be ignored on lower versions.
**If you would like to use network encryption, the following will be required underneath but key, iv and hash are required:**
```yaml
crypto:
encrypted_only: False
keys:
7: ["0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000", "0000000000000000"]
```
### Database upgrade
Always make sure your database (tables) are up-to-date, to do so go to the `core/data/schema/versions` folder and see
@ -509,6 +574,8 @@ python dbutils.py --game SDEW upgrade
- Player title is currently static and cannot be changed in-game
- QR Card Scanning currently only load a static hero
**Network hashing in GssSite.dll must be disabled**
### Credits for SAO support:
- Midorica - Limited Network Support

View File

@ -1,9 +1,11 @@
server:
enable: True
loglevel: "info"
news_msg: ""
team:
name: ARTEMiS
name: ARTEMiS # If this is set, all players that are not on a team will use this one by default.
rank_scale: True # Scales the in-game ranking based on the number of teams within the database, rather than the default scale of ~100 that the game normally uses.
mods:
use_login_bonus: True

View File

@ -24,6 +24,7 @@ database:
sha2_password: False
loglevel: "warn"
user_table_autoincrement_start: 10000
enable_memcached: True
memcached_host: "localhost"
frontend:

View File

@ -181,7 +181,17 @@ class ChuniBase:
return {"type": data["type"], "length": 0, "gameIdlistList": []}
def handle_get_game_message_api_request(self, data: Dict) -> Dict:
return {"type": data["type"], "length": "0", "gameMessageList": []}
return {
"type": data["type"],
"length": 1,
"gameMessageList": [{
"id": 1,
"type": 1,
"message": f"Welcome to {self.core_cfg.server.name} network!" if not self.game_cfg.server.news_msg else self.game_cfg.server.news_msg,
"startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0"
}]
}
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
return {"type": data["type"], "gameRankingList": []}
@ -361,11 +371,98 @@ class ChuniBase:
"userDuelList": duel_list,
}
def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_rival(data["rivalId"])
if p is None:
return {}
userRivalData = {
"rivalId": p.user,
"rivalName": p.userName
}
return {
"userId": data["userId"],
"userRivalData": userRivalData
}
def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
m = self.data.score.get_rival_music(data["rivalId"], data["nextIndex"], data["maxCount"])
if m is None:
return {}
user_rival_music_list = []
for music in m:
actual_music_id = self.data.static.get_song(music["musicId"])
if actual_music_id is None:
music_id = music["musicId"]
else:
music_id = actual_music_id["songId"]
level = music["level"]
score = music["score"]
rank = music["rank"]
# Find the existing entry for the current musicId in the user_rival_music_list
music_entry = next((entry for entry in user_rival_music_list if entry["musicId"] == music_id), None)
if music_entry is None:
# If the entry doesn't exist, create a new entry
music_entry = {
"musicId": music_id,
"length": 0,
"userRivalMusicDetailList": []
}
user_rival_music_list.append(music_entry)
# Check if the current score is higher than the previous highest score for the level
level_entry = next(
(
entry
for entry in music_entry["userRivalMusicDetailList"]
if entry["level"] == level
),
None,
)
if level_entry is None or score > level_entry["scoreMax"]:
# If the level entry doesn't exist or the score is higher, update or add the entry
level_entry = {
"level": level,
"scoreMax": score,
"scoreRank": rank
}
if level_entry not in music_entry["userRivalMusicDetailList"]:
music_entry["userRivalMusicDetailList"].append(level_entry)
music_entry["length"] = len(music_entry["userRivalMusicDetailList"])
result = {
"userId": data["userId"],
"rivalId": data["rivalId"],
"nextIndex": -1,
"userRivalMusicList": user_rival_music_list
}
return result
def handle_get_user_rival_music_api_requestded(self, data: Dict) -> Dict:
m = self.data.score.get_rival_music(data["rivalId"], data["nextIndex"], data["maxCount"])
if m is None:
return {}
userRivalMusicList = []
for music in m:
self.logger.debug(music["point"])
return {
"userId": data["userId"],
"rivalId": data["rivalId"],
"nextIndex": -1
}
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
user_fav_item_list = []
# still needs to be implemented on WebUI
# 1: Music, 3: Character
# 1: Music, 2: User, 3: Character
fav_list = self.data.item.get_all_favorites(
data["userId"], self.version, fav_kind=int(data["kind"])
)
@ -501,7 +598,7 @@ class ChuniBase:
if len(song_list) >= max_ct:
break
if len(song_list) >= next_idx + max_ct:
if len(music_detail) >= next_idx + max_ct:
next_idx += max_ct
else:
next_idx = -1
@ -600,25 +697,43 @@ class ChuniBase:
}
def handle_get_user_team_api_request(self, data: Dict) -> Dict:
# TODO: use the database "chuni_profile_team" with a GUI
# Default values
team_id = 65535
team_name = self.game_cfg.team.team_name
if team_name == "":
team_rank = 0
# Get user profile
profile = self.data.profile.get_profile_data(data["userId"], self.version)
if profile and profile["teamId"]:
# Get team by id
team = self.data.profile.get_team_by_id(profile["teamId"])
if team:
team_id = team["id"]
team_name = team["teamName"]
# Determine whether to use scaled ranks, or original system
if self.game_cfg.team.rank_scale:
team_rank = self.data.profile.get_team_rank(team["id"])
else:
team_rank = self.data.profile.get_team_rank_actual(team["id"])
# Don't return anything if no team name has been defined for defaults and there is no team set for the player
if not profile["teamId"] and team_name == "":
return {"userId": data["userId"], "teamId": 0}
return {
"userId": data["userId"],
"teamId": 1,
"teamRank": 1,
"teamId": team_id,
"teamRank": team_rank,
"teamName": team_name,
"userTeamPoint": {
"userId": data["userId"],
"teamId": 1,
"teamId": team_id,
"orderId": 1,
"teamPoint": 1,
"aggrDate": data["playDate"],
},
}
def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
return {
"userId": data["userId"],
@ -709,9 +824,25 @@ class ChuniBase:
self.data.score.put_playlog(user_id, playlog)
if "userTeamPoint" in upsert:
# TODO: team stuff
pass
team_points = upsert["userTeamPoint"]
try:
for tp in team_points:
if tp["teamId"] != '65535':
# Fetch the current team data
current_team = self.data.profile.get_team_by_id(tp["teamId"])
# Calculate the new teamPoint
new_team_point = int(tp["teamPoint"]) + current_team["teamPoint"]
# Prepare the data to update
team_data = {
"teamPoint": new_team_point
}
# Update the team data
self.data.profile.update_team(tp["teamId"], team_data)
except:
pass # Probably a better way to catch if the team is not set yet (new profiles), but let's just pass
if "userMapAreaList" in upsert:
for map_area in upsert["userMapAreaList"]:
self.data.item.put_map_area(user_id, map_area)
@ -757,4 +888,4 @@ class ChuniBase:
return {
"userId": data["userId"],
"userNetBattleData": {"recentNBSelectMusicList": []},
}
}

View File

@ -19,6 +19,12 @@ class ChuniServerConfig:
self.__config, "chuni", "server", "loglevel", default="info"
)
)
@property
def news_msg(self) -> str:
return CoreConfig.get_config_field(
self.__config, "chuni", "server", "news_msg", default=""
)
class ChuniTeamConfig:
@ -30,6 +36,11 @@ class ChuniTeamConfig:
return CoreConfig.get_config_field(
self.__config, "chuni", "team", "name", default=""
)
@property
def rank_scale(self) -> str:
return CoreConfig.get_config_field(
self.__config, "chuni", "team", "rank_scale", default="False"
)
class ChuniModsConfig:

View File

@ -637,3 +637,103 @@ class ChuniProfileData(BaseData):
if result is None:
return None
return result.fetchall()
def get_team_by_id(self, team_id: int) -> Optional[Row]:
sql = select(team).where(team.c.id == team_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_team_rank_actual(self, team_id: int) -> int:
# Normal ranking system, likely the one used in the real servers
# Query all teams sorted by 'teamPoint'
result = self.execute(
select(team.c.id).order_by(team.c.teamPoint.desc())
)
# Get the rank of the team with the given team_id
rank = None
for i, row in enumerate(result, start=1):
if row.id == team_id:
rank = i
break
# Return the rank if found, or a default rank otherwise
return rank if rank is not None else 0
def get_team_rank(self, team_id: int) -> int:
# Scaled ranking system, designed for smaller instances.
# Query all teams sorted by 'teamPoint'
result = self.execute(
select(team.c.id).order_by(team.c.teamPoint.desc())
)
# Count total number of teams
total_teams = self.execute(select(func.count()).select_from(team)).scalar()
# Get the rank of the team with the given team_id
rank = None
for i, row in enumerate(result, start=1):
if row.id == team_id:
rank = i
break
# If the team is not found, return default rank
if rank is None:
return 0
# Define rank tiers
tiers = {
1: range(1, int(total_teams * 0.1) + 1), # Rainbow
2: range(int(total_teams * 0.1) + 1, int(total_teams * 0.4) + 1), # Gold
3: range(int(total_teams * 0.4) + 1, int(total_teams * 0.7) + 1), # Silver
4: range(int(total_teams * 0.7) + 1, total_teams + 1), # Grey
}
# Assign rank based on tier
for tier_rank, tier_range in tiers.items():
if rank in tier_range:
return tier_rank
# Return default rank if not found in any tier
return 0
def update_team(self, team_id: int, team_data: Dict) -> bool:
team_data["id"] = team_id
sql = insert(team).values(**team_data)
conflict = sql.on_duplicate_key_update(**team_data)
result = self.execute(conflict)
if result is None:
self.logger.warn(
f"update_team: Failed to update team! team id: {team_id}"
)
return False
return True
def get_rival(self, rival_id: int) -> Optional[Row]:
sql = select(profile).where(profile.c.user == rival_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_overview(self) -> Dict:
# Fetch and add up all the playcounts
playcount_sql = self.execute(select(profile.c.playCount))
if playcount_sql is None:
self.logger.warn(
f"get_overview: Couldn't pull playcounts"
)
return 0
total_play_count = 0;
for row in playcount_sql:
total_play_count += row[0]
return {
"total_play_count": total_play_count
}

View File

@ -200,3 +200,10 @@ class ChuniScoreData(BaseData):
if result is None:
return None
return result.lastrowid
def get_rival_music(self, rival_id: int, index: int, max_count: int) -> Optional[List[Dict]]:
sql = select(playlog).where(playlog.c.user == rival_id).limit(max_count).offset(index)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()

View File

@ -453,6 +453,15 @@ class ChuniStaticData(BaseData):
return None
return result.fetchone()
def get_song(self, music_id: int) -> Optional[Row]:
sql = music.select(music.c.id == music_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def put_avatar(
self,
version: int,
@ -587,4 +596,4 @@ class ChuniStaticData(BaseData):
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
return result.fetchone()

View File

@ -7,4 +7,4 @@ index = DivaServlet
database = DivaData
reader = DivaReader
game_codes = [DivaConstants.GAME_CODE]
current_schema_version = 5
current_schema_version = 6

View File

@ -402,7 +402,7 @@ class DivaBase:
response += f"&lv_num={profile['lv_num']}"
response += f"&lv_pnt={profile['lv_pnt']}"
response += f"&vcld_pts={profile['vcld_pts']}"
response += f"&skn_eqp={profile['use_pv_skn_eqp']}"
response += f"&skn_eqp={profile['skn_eqp']}"
response += f"&btn_se_eqp={profile['btn_se_eqp']}"
response += f"&sld_se_eqp={profile['sld_se_eqp']}"
response += f"&chn_sld_se_eqp={profile['chn_sld_se_eqp']}"

View File

@ -51,6 +51,7 @@ profile = Table(
Column("rgo_sts", Integer, nullable=False, server_default="1"),
Column("lv_efct_id", Integer, nullable=False, server_default="0"),
Column("lv_plt_id", Integer, nullable=False, server_default="1"),
Column("skn_eqp", Integer, nullable=False, server_default="0"),
Column("passwd_stat", Integer, nullable=False, server_default="0"),
Column("passwd", String(12), nullable=False, server_default="**********"),
Column(

View File

@ -157,7 +157,6 @@ class PokkenBase:
support_set_3
aid_skill_list
achievement_flag
pokemon_data
event_achievement_flag
event_achievement_param
"""
@ -200,7 +199,7 @@ class PokkenBase:
load_usr.home_loc_name = profile_dict.get("home_loc_name", "")
load_usr.pref_code = profile_dict.get("pref_code", 0)
load_usr.trainer_name = profile_dict.get(
"trainer_name", "Newb" + str(random.randint(1111, 999999))
"trainer_name", f"Newb{str(user_id).zfill(4)}"
)
load_usr.trainer_rank_point = profile_dict.get("trainer_rank_point", 0)
load_usr.wallet = profile_dict.get("wallet", 0)
@ -262,6 +261,29 @@ class PokkenBase:
load_usr.sp_bonus_key_value_2 = profile_dict.get("sp_bonus_key_value_2", 0)
load_usr.last_play_event_id = profile_dict.get("last_play_event_id", 0)
if pokemon_data is not None:
for pkmn in pokemon_data:
pkmn_d = pkmn._asdict()
pkm = jackal_pb2.LoadUserResponseData.PokemonData()
pkm.char_id = pkmn_d.get('char_id', 0)
pkm.illustration_book_no = pkmn_d.get('illustration_book_no', 0)
pkm.pokemon_exp = pkmn_d.get('pokemon_exp', 0)
pkm.battle_num_vs_wan = pkmn_d.get('battle_num_vs_wan', 0)
pkm.win_vs_wan = pkmn_d.get('win_vs_wan', 0)
pkm.battle_num_vs_lan = pkmn_d.get('battle_num_vs_lan', 0)
pkm.win_vs_lan = pkmn_d.get('win_vs_lan', 0)
pkm.battle_num_vs_cpu = pkmn_d.get('battle_num_vs_cpu', 0)
pkm.win_cpu = pkmn_d.get('win_cpu', 0)
pkm.battle_all_num_tutorial = pkmn_d.get('battle_all_num_tutorial', 0)
pkm.battle_num_tutorial = pkmn_d.get('battle_num_tutorial', 0)
pkm.bp_point_atk = pkmn_d.get('bp_point_atk', 0)
pkm.bp_point_res = pkmn_d.get('bp_point_res', 0)
pkm.bp_point_def = pkmn_d.get('bp_point_def', 0)
pkm.bp_point_sp = pkmn_d.get('bp_point_sp', 0)
load_usr.pokemon_data.append(pkm)
res.load_user.CopyFrom(load_usr)
return res.SerializeToString()
@ -325,7 +347,7 @@ class PokkenBase:
for evt_param in req.event_achievement_param:
evt_params.append(evt_param)
self.data.profile.update_profile_event(user_id, evt_state, evt_flgs, evt_params, )
self.data.profile.update_profile_event(user_id, evt_state, evt_flgs, evt_params, req.last_play_event_id)
for reward in req.reward_data:
self.data.item.add_reward(user_id, reward.get_category_id, reward.get_content_id, reward.get_type_id)

View File

@ -3,6 +3,7 @@ from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, an
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select, update, delete
from sqlalchemy.sql.functions import coalesce
from sqlalchemy.engine import Row
from sqlalchemy.dialects.mysql import insert
@ -257,18 +258,27 @@ class PokkenProfileData(BaseData):
user=user_id,
char_id=pokemon_id,
illustration_book_no=illust_no,
bp_point_atk=atk,
bp_point_res=res,
bp_point_def=defe,
bp_point_sp=sp,
pokemon_exp=0,
battle_num_vs_wan=0,
win_vs_wan=0,
battle_num_vs_lan=0,
win_vs_lan=0,
battle_num_vs_cpu=0,
win_cpu=0,
battle_all_num_tutorial=0,
battle_num_tutorial=0,
bp_point_atk=1+atk,
bp_point_res=1+res,
bp_point_def=1+defe,
bp_point_sp=1+sp,
)
conflict = sql.on_duplicate_key_update(
illustration_book_no=illust_no,
bp_point_atk=atk,
bp_point_res=res,
bp_point_def=defe,
bp_point_sp=sp,
bp_point_atk=pokemon_data.c.bp_point_atk + atk,
bp_point_res=pokemon_data.c.bp_point_res + res,
bp_point_def=pokemon_data.c.bp_point_def + defe,
bp_point_sp=pokemon_data.c.bp_point_sp + sp,
)
result = self.execute(conflict)
@ -284,7 +294,7 @@ class PokkenProfileData(BaseData):
xp: int
) -> None:
sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values(
pokemon_exp=pokemon_data.c.pokemon_exp + xp
pokemon_exp=coalesce(pokemon_data.c.pokemon_exp, 0) + xp
)
result = self.execute(sql)
@ -292,29 +302,38 @@ class PokkenProfileData(BaseData):
self.logger.warning(f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}")
def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]:
pass
sql = pokemon_data.select(and_(pokemon_data.c.user == user_id, pokemon_data.c.char_id == pokemon_id))
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_all_pokemon_data(self, user_id: int) -> Optional[List[Row]]:
pass
sql = pokemon_data.select(pokemon_data.c.user == user_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_pokemon_battle_result(
self, user_id: int, pokemon_id: int, match_type: PokkenConstants.BATTLE_TYPE, match_result: PokkenConstants.BATTLE_RESULT
) -> None:
"""
Records the match stats (type and win/loss) for the pokemon and profile
coalesce(pokemon_data.c.win_vs_wan, 0)
"""
sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values(
battle_num_tutorial=pokemon_data.c.battle_num_tutorial + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else pokemon_data.c.battle_num_tutorial,
battle_all_num_tutorial=pokemon_data.c.battle_all_num_tutorial + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else pokemon_data.c.battle_all_num_tutorial,
battle_num_tutorial=coalesce(pokemon_data.c.battle_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_num_tutorial, 0),
battle_all_num_tutorial=coalesce(pokemon_data.c.battle_all_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_all_num_tutorial, 0),
battle_num_vs_cpu=pokemon_data.c.battle_num_vs_cpu + 1 if match_type==PokkenConstants.BATTLE_TYPE.AI else pokemon_data.c.battle_num_vs_cpu,
win_cpu=pokemon_data.c.win_cpu + 1 if match_type==PokkenConstants.BATTLE_TYPE.AI and match_result==PokkenConstants.BATTLE_RESULT.WIN else pokemon_data.c.win_cpu,
battle_num_vs_cpu=coalesce(pokemon_data.c.battle_num_vs_cpu, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.AI else coalesce(pokemon_data.c.battle_num_vs_cpu, 0),
win_cpu=coalesce(pokemon_data.c.win_cpu, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.AI and match_result==PokkenConstants.BATTLE_RESULT.WIN else coalesce(pokemon_data.c.win_cpu, 0),
battle_num_vs_lan=pokemon_data.c.battle_num_vs_lan + 1 if match_type==PokkenConstants.BATTLE_TYPE.LAN else pokemon_data.c.battle_num_vs_lan,
win_vs_lan=pokemon_data.c.win_vs_lan + 1 if match_type==PokkenConstants.BATTLE_TYPE.LAN and match_result==PokkenConstants.BATTLE_RESULT.WIN else pokemon_data.c.win_vs_lan,
battle_num_vs_lan=coalesce(pokemon_data.c.battle_num_vs_lan, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.LAN else coalesce(pokemon_data.c.battle_num_vs_lan, 0),
win_vs_lan=coalesce(pokemon_data.c.win_vs_lan, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.LAN and match_result==PokkenConstants.BATTLE_RESULT.WIN else coalesce(pokemon_data.c.win_vs_lan, 0),
battle_num_vs_wan=pokemon_data.c.battle_num_vs_wan + 1 if match_type==PokkenConstants.BATTLE_TYPE.WAN else pokemon_data.c.battle_num_vs_wan,
win_vs_wan=pokemon_data.c.win_vs_wan + 1 if match_type==PokkenConstants.BATTLE_TYPE.WAN and match_result==PokkenConstants.BATTLE_RESULT.WIN else pokemon_data.c.win_vs_wan,
battle_num_vs_wan=coalesce(pokemon_data.c.battle_num_vs_wan, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.WAN else coalesce(pokemon_data.c.battle_num_vs_wan, 0),
win_vs_wan=coalesce(pokemon_data.c.win_vs_wan, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.WAN and match_result==PokkenConstants.BATTLE_RESULT.WIN else coalesce(pokemon_data.c.win_vs_wan, 0),
)
result = self.execute(sql)
@ -335,11 +354,11 @@ class PokkenProfileData(BaseData):
Records profile stats
"""
sql = update(profile).where(profile.c.user==user_id).values(
ex_ko_num=profile.c.ex_ko_num + exkos,
wko_num=profile.c.wko_num + wkos,
timeup_win_num=profile.c.timeup_win_num + timeout_wins,
cool_ko_num=profile.c.cool_ko_num + cool_kos,
perfect_ko_num=profile.c.perfect_ko_num + perfects,
ex_ko_num=coalesce(profile.c.ex_ko_num, 0) + exkos,
wko_num=coalesce(profile.c.wko_num, 0) + wkos,
timeup_win_num=coalesce(profile.c.timeup_win_num, 0) + timeout_wins,
cool_ko_num=coalesce(profile.c.cool_ko_num, 0) + cool_kos,
perfect_ko_num=coalesce(profile.c.perfect_ko_num, 0) + perfects,
continue_num=continues,
)
@ -348,10 +367,6 @@ class PokkenProfileData(BaseData):
self.logger.warning(f"Failed to update stats for user {user_id}")
def update_support_team(self, user_id: int, support_id: int, support1: int = None, support2: int = None) -> None:
if support1 == 4294967295:
support1 = None
if support2 == 4294967295:
support2 = None
sql = update(profile).where(profile.c.user==user_id).values(
support_set_1_1=support1 if support_id == 1 else profile.c.support_set_1_1,
support_set_1_2=support2 if support_id == 1 else profile.c.support_set_1_2,