mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-08 16:57:26 +08:00
Compare commits
23 Commits
master
...
cf20a2c7cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf20a2c7cb | ||
|
|
bfb6a2ddda | ||
|
|
8180eef856 | ||
|
|
2cc69a1100 | ||
|
|
73f28f38d3 | ||
|
|
707f881bdb | ||
|
|
71b789fbaa | ||
|
|
d11a8435b3 | ||
|
|
17998ee655 | ||
|
|
39e1e52d36 | ||
|
|
52bb834a8a | ||
|
|
65f6d212fe | ||
|
|
9c27b998ef | ||
|
|
a3891a901f | ||
|
|
32b708b7de | ||
|
|
eee34bae80 | ||
|
|
029d43e3f5 | ||
|
|
05bdd42679 | ||
|
|
5b5f544a19 | ||
|
|
d05618049c | ||
|
|
2e43dfbdff | ||
|
|
7f94416189 | ||
|
|
4911511ed7 |
@@ -89,7 +89,7 @@ It is just so interesting. What it can do is under exploration.
|
||||
> 其它小改动请参考各个 commit 信息。
|
||||
> Please refer to the commit messages for other minor changes.
|
||||
|
||||
### Version 2.12.0
|
||||
### Version 2.12.1
|
||||
|
||||
> v2.12.0.1 ~ v2.12.0.10 for Arcaea 5.10.6 ~ 6.7.1
|
||||
>
|
||||
|
||||
@@ -81,7 +81,7 @@ def users_user_get(user, user_id):
|
||||
|
||||
@bp.route('/<int:user_id>', methods=['PUT'])
|
||||
@role_required(request, ['change'])
|
||||
@request_json_handle(request, optional_keys=['name', 'password', 'user_code', 'ticket', 'email'], must_change=True)
|
||||
@request_json_handle(request, optional_keys=['name', 'password', 'user_code', 'ticket', 'email', 'custom_banner'], must_change=True)
|
||||
@api_try
|
||||
def users_user_put(data, user, user_id):
|
||||
'''修改一个用户'''
|
||||
@@ -110,6 +110,11 @@ def users_user_put(data, user, user_id):
|
||||
raise InputError('Ticket must be int')
|
||||
u.ticket = data['ticket']
|
||||
r['ticket'] = u.ticket
|
||||
if 'custom_banner' in data:
|
||||
if not isinstance(data['custom_banner'], str):
|
||||
raise InputError('Value `custom_banner` must be str')
|
||||
u.custom_banner = data['custom_banner']
|
||||
r['custom_banner'] = u.custom_banner
|
||||
u.update_columns(d=r)
|
||||
return success_return(r)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class Config:
|
||||
|
||||
SONG_FILE_HASH_PRE_CALCULATE = True
|
||||
|
||||
GAME_API_PREFIX = ['/coldwind/35', '/'] # str | list[str]
|
||||
GAME_API_PREFIX = ['/apricotduck/38', '/'] # str | list[str]
|
||||
OLD_GAME_API_PREFIX = [] # str | list[str]
|
||||
|
||||
ALLOW_APPVERSION = [] # list[str]
|
||||
@@ -100,6 +100,7 @@ class Config:
|
||||
DATABASE_INIT_PATH = './database/init/'
|
||||
SQLITE_LOG_DATABASE_PATH = './database/arcaea_log.db'
|
||||
SQLITE_DATABASE_DELETED_PATH = './database/arcaea_database_deleted.db'
|
||||
LOG_FOLDER_PATH = './log/'
|
||||
|
||||
GAME_LOGIN_RATE_LIMIT = '30/5 minutes'
|
||||
API_LOGIN_RATE_LIMIT = '10/5 minutes'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from .config_manager import Config
|
||||
|
||||
ARCAEA_SERVER_VERSION = 'v2.12.1'
|
||||
ARCAEA_DATABASE_VERSION = 'v2.12.1'
|
||||
ARCAEA_SERVER_VERSION = 'v2.12.1.8'
|
||||
ARCAEA_DATABASE_VERSION = 'v2.12.1.8'
|
||||
ARCAEA_LOG_DATBASE_VERSION = 'v1.1'
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class Constant:
|
||||
BUNDLE_DOWNLOAD_TIME_GAP_LIMIT = Config.BUNDLE_DOWNLOAD_TIME_GAP_LIMIT
|
||||
BUNDLE_DOWNLOAD_LINK_PREFIX = Config.BUNDLE_DOWNLOAD_LINK_PREFIX
|
||||
|
||||
LINKPLAY_UNLOCK_LENGTH = 512 # Units: bytes
|
||||
LINKPLAY_UNLOCK_LENGTH = 1024 # Units: bytes
|
||||
LINKPLAY_TIMEOUT = 5 # Units: seconds
|
||||
|
||||
LINKPLAY_HOST = '127.0.0.1' if Config.SET_LINKPLAY_SERVER_AS_SUB_PROCESS else Config.LINKPLAY_HOST
|
||||
|
||||
@@ -85,9 +85,12 @@ class SonglistParser:
|
||||
if i['ratingClass'] == 3 and i.get('audioOverride', False):
|
||||
r |= 64
|
||||
r |= 1 << i['ratingClass']
|
||||
else:
|
||||
if any(i['ratingClass'] == 3 for i in song.get('difficulties', [])):
|
||||
r |= 8
|
||||
else: # 針對remote_dl為False時BYD難度強制下載的處理
|
||||
for i in song.get('difficulties', []):
|
||||
if i['ratingClass'] == 3:
|
||||
r |= 8
|
||||
if i.get('audioOverride', False):
|
||||
r |= 64
|
||||
|
||||
for extra_file in song.get('additional_files', []):
|
||||
x = extra_file['file_name']
|
||||
|
||||
@@ -247,11 +247,19 @@ class UserPlay(UserScore):
|
||||
self.combo_interval_bonus: int = None # 不能给 None 以外的默认值
|
||||
self.hp_interval_bonus: int = None # 不能给 None 以外的默认值
|
||||
self.fever_bonus: int = None # 不能给 None 以外的默认值
|
||||
self.rank_bonus: int = None # 不能给 None 以外的默认值
|
||||
self.maya_gauge: int = None # 不能给 None 以外的默认值
|
||||
self.nextstage_bonus: int = None # 不能给 None 以外的默认值
|
||||
self.skill_cytusii_flag: str = None
|
||||
self.skill_chinatsu_flag: str = None
|
||||
self.highest_health: int = None
|
||||
self.lowest_health: int = None
|
||||
|
||||
# room score
|
||||
self.room_code: str = None
|
||||
self.room_total_score: int = None
|
||||
self.room_total_players: int = None
|
||||
|
||||
self.invasion_flag: int = None # 1: invasion_start, 2: invasion_hard
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
@@ -301,6 +309,9 @@ class UserPlay(UserScore):
|
||||
# fever 等级最高为 5
|
||||
return False
|
||||
|
||||
if self.rank_bonus is not None and (self.rank_bonus < 0 or self.rank_bonus > 4):
|
||||
return False
|
||||
|
||||
y = f'{self.user.user_id}{self.song_hash}'
|
||||
checksum = md5(x+md5(y))
|
||||
|
||||
@@ -555,18 +566,16 @@ class Potential:
|
||||
def select_recent_30_tuple(self) -> None:
|
||||
'''获取用户recent30数据'''
|
||||
self.c.execute(
|
||||
'''select r_index, song_id, difficulty, rating from recent30 where user_id = ? order by time_played DESC''', (self.user.user_id,))
|
||||
'''select r_index, song_id, difficulty, rating from recent30 where user_id = ? and song_id != '' order by time_played DESC''', (self.user.user_id,))
|
||||
|
||||
self.r30_tuples = [x for x in self.c.fetchall() if x[1] != '']
|
||||
self.r30_tuples = self.c.fetchall()
|
||||
|
||||
def select_recent_30(self) -> None:
|
||||
self.c.execute(
|
||||
'''select song_id, difficulty, score, shiny_perfect_count, perfect_count, near_count, miss_count, health, modifier, time_played, clear_type, rating from recent30 where user_id = ? order by time_played DESC''', (self.user.user_id,))
|
||||
'''select song_id, difficulty, score, shiny_perfect_count, perfect_count, near_count, miss_count, health, modifier, time_played, clear_type, rating from recent30 where user_id = ? and song_id != '' order by time_played DESC''', (self.user.user_id,))
|
||||
|
||||
self.r30 = []
|
||||
for x in self.c.fetchall():
|
||||
if x[0] == '':
|
||||
continue
|
||||
s = Score()
|
||||
s.song.set_chart(x[0], x[1])
|
||||
s.set_score(*x[2:-1])
|
||||
|
||||
@@ -47,6 +47,8 @@ class User:
|
||||
self.world_rank_score: int = None
|
||||
self.ban_flag = None
|
||||
|
||||
self.is_allow_marketing_email = False
|
||||
|
||||
@property
|
||||
def hash_pwd(self) -> str:
|
||||
'''`password`的SHA-256值'''
|
||||
@@ -162,9 +164,9 @@ class UserRegister(User):
|
||||
self._insert_user_char()
|
||||
|
||||
self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
|
||||
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email)
|
||||
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email)
|
||||
''', {'user_code': self.user_code, 'user_id': self.user_id, 'join_date': now, 'name': self.name, 'password': self.hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': self.email})
|
||||
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email, is_allow_marketing_email)
|
||||
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email, :is_allow_marketing_email)
|
||||
''', {'user_code': self.user_code, 'user_id': self.user_id, 'join_date': now, 'name': self.name, 'password': self.hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': self.email, 'is_allow_marketing_email': self.is_allow_marketing_email})
|
||||
|
||||
|
||||
class UserLogin(User):
|
||||
@@ -520,6 +522,7 @@ class UserInfo(User):
|
||||
"is_hide_rating": self.is_hide_rating,
|
||||
"max_stamina_notification_enabled": self.max_stamina_notification_enabled,
|
||||
"mp_notification_enabled": self.mp_notification_enabled,
|
||||
"is_allow_marketing_email": self.is_allow_marketing_email,
|
||||
},
|
||||
"user_id": self.user_id,
|
||||
"name": self.name,
|
||||
@@ -566,6 +569,8 @@ class UserInfo(User):
|
||||
# "feature": "paymentlink"
|
||||
# }
|
||||
# ],
|
||||
|
||||
'has_email': self.email != '',
|
||||
}
|
||||
|
||||
def from_list(self, x: list) -> 'UserInfo':
|
||||
@@ -613,6 +618,7 @@ class UserInfo(User):
|
||||
self.insight_state = x[38]
|
||||
|
||||
self.custom_banner = x[39] if x[39] is not None else ''
|
||||
self.is_allow_marketing_email = x[40] == 1
|
||||
|
||||
return self
|
||||
|
||||
|
||||
@@ -160,11 +160,12 @@ class Map:
|
||||
self.require_localunlock_challengeid: str = None
|
||||
self.chain_info: dict = None
|
||||
|
||||
# self.requires: list[dict] = None
|
||||
self.requires: 'list[dict]' = None
|
||||
self.requires_any: 'list[dict]' = None
|
||||
|
||||
self.disable_over: bool = None
|
||||
self.new_law: str = None
|
||||
self.is_linkplay_allowed: bool = None
|
||||
|
||||
@property
|
||||
def rewards(self) -> list:
|
||||
@@ -219,6 +220,10 @@ class Map:
|
||||
r['new_law'] = self.new_law
|
||||
if self.requires_any:
|
||||
r['requires_any'] = self.requires_any
|
||||
if self.requires:
|
||||
r['requires'] = self.requires
|
||||
if self.is_linkplay_allowed:
|
||||
r['is_linkplay_allowed'] = self.is_linkplay_allowed
|
||||
return r
|
||||
|
||||
def from_dict(self, raw_dict: dict) -> 'Map':
|
||||
@@ -245,9 +250,11 @@ class Map:
|
||||
self.chain_info = raw_dict.get('chain_info')
|
||||
self.steps = [Step().from_dict(s) for s in raw_dict.get('steps')]
|
||||
|
||||
self.is_linkplay_allowed = raw_dict.get('is_linkplay_allowed', False)
|
||||
self.disable_over = raw_dict.get('disable_over')
|
||||
self.new_law = raw_dict.get('new_law')
|
||||
self.requires_any = raw_dict.get('requires_any')
|
||||
self.requires = raw_dict.get('requires')
|
||||
return self
|
||||
|
||||
def select_map_info(self):
|
||||
@@ -485,7 +492,7 @@ class UserStamina(Stamina):
|
||||
|
||||
def select(self):
|
||||
'''获取用户体力信息'''
|
||||
self.c.execute('''select max_stamina_ts, staminafrom user where user_id = :a''',
|
||||
self.c.execute('''select max_stamina_ts, stamina from user where user_id = :a''',
|
||||
{'a': self.user.user_id})
|
||||
x = self.c.fetchone()
|
||||
if not x:
|
||||
@@ -519,6 +526,9 @@ class WorldSkillMixin:
|
||||
'skill_salt': self._skill_salt,
|
||||
'skill_hikari_selene': self._skill_hikari_selene,
|
||||
'skill_nami_sui': self._skill_nami_sui,
|
||||
'skill_vita_arc': self._skill_vita_arc,
|
||||
'skill_maya_uncap': self._skill_maya_uncap,
|
||||
'skill_hikari_tairitsu_debut': self._skill_hikari_tairitsu_debut,
|
||||
}
|
||||
if self.user_play.beyond_gauge == 0 and self.character_used.character_id == 35 and self.character_used.skill_id_displayed:
|
||||
self._special_tempest()
|
||||
@@ -537,6 +547,7 @@ class WorldSkillMixin:
|
||||
'skill_kanae_uncap': self._skill_kanae_uncap,
|
||||
'skill_eto_hoppe': self._skill_eto_hoppe,
|
||||
'skill_intruder': self._skill_intruder,
|
||||
'skill_nonoka_uncap': self._skill_nonoka_uncap,
|
||||
}
|
||||
if self.character_used.skill_id_displayed in factory_dict:
|
||||
factory_dict[self.character_used.skill_id_displayed]()
|
||||
@@ -750,6 +761,46 @@ class WorldSkillMixin:
|
||||
|
||||
self.character_bonus_progress_normalized = self.user_play.fever_bonus / 1000
|
||||
|
||||
def _skill_nonoka_uncap(self) -> None:
|
||||
'''
|
||||
nonoka 觉醒技能,技能等级 * 10% 的世界进度奖励
|
||||
'''
|
||||
if self.user_play.rank_bonus is None:
|
||||
return
|
||||
|
||||
self.character_bonus_progress_normalized = self.user_play.rank_bonus * \
|
||||
0.1 * self.progress_normalized
|
||||
self.user.current_map.reclimb(self.final_progress)
|
||||
|
||||
def _skill_vita_arc(self) -> None:
|
||||
'''
|
||||
vita 技能 2 far 会减少 1 over
|
||||
'''
|
||||
x = self.user_play.near_count // 2
|
||||
over = self.character_used.overdrive.get_value(
|
||||
self.character_used.level)
|
||||
self.over_skill_increase = -min(x, over)
|
||||
|
||||
def _skill_maya_uncap(self) -> None:
|
||||
'''
|
||||
maya 觉醒技能,歌曲游玩抵达全曲 1/4 进度时,回忆率将全部转化为角色能力加成 stat bonus
|
||||
'''
|
||||
if self.user_play.maya_gauge is None:
|
||||
return
|
||||
if self.user_play.maya_gauge >= 0:
|
||||
self.over_skill_increase = self.user_play.maya_gauge
|
||||
self.prog_skill_increase = self.user_play.maya_gauge
|
||||
|
||||
def _skill_hikari_tairitsu_debut(self) -> None:
|
||||
'''
|
||||
hikari & tairitsu 每日首次游玩 Next Stage 曲目时,角色所有能力数值 +20
|
||||
'''
|
||||
if self.user_play.nextstage_bonus is None:
|
||||
return
|
||||
if self.user_play.nextstage_bonus > 0:
|
||||
self.over_skill_increase = 20
|
||||
self.prog_skill_increase = 20
|
||||
|
||||
|
||||
class BaseWorldPlay(WorldSkillMixin):
|
||||
'''
|
||||
@@ -800,11 +851,13 @@ class BaseWorldPlay(WorldSkillMixin):
|
||||
# 'wpaid': 'helloworld', # world play id ???
|
||||
'progress_before_sub_boost': self.final_progress,
|
||||
'progress_sub_boost_amount': 0,
|
||||
'partner_multiply': self.partner_multiply,
|
||||
# 'subscription_multiply'
|
||||
|
||||
# lephon_final: bool dynamic map info
|
||||
# lephon_active: bool dynamic map info
|
||||
# 'steps_modified': False,
|
||||
|
||||
}
|
||||
|
||||
if self.character_used.skill_id_displayed == 'skill_maya':
|
||||
@@ -817,6 +870,15 @@ class BaseWorldPlay(WorldSkillMixin):
|
||||
if self.user_play.prog_boost_multiply != 0: # 源韵强化
|
||||
r['prog_boost_multiply'] = self.user_play.prog_boost_multiply
|
||||
|
||||
# progress_linkplay_boost_amount
|
||||
# linkplay_boost
|
||||
# progress_before_linkplay_boost
|
||||
if arcmap.is_linkplay_allowed:
|
||||
r['linkplay_boost'] = self.linkplay_boost # 同步率
|
||||
r['progress_before_linkplay_boost'] = self.progress_before_linkplay_boost
|
||||
r['progress_linkplay_boost_amount'] = self.progress_before_linkplay_boost * \
|
||||
(self.linkplay_boost - 1) * self.step_times
|
||||
|
||||
return r
|
||||
|
||||
@property
|
||||
@@ -848,6 +910,32 @@ class BaseWorldPlay(WorldSkillMixin):
|
||||
def final_progress(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def partner_multiply(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def progress_before_linkplay_boost(self) -> float:
|
||||
return self.progress_normalized
|
||||
|
||||
@property
|
||||
def linkplay_boost(self) -> float:
|
||||
if not self.user.current_map.is_linkplay_allowed:
|
||||
return 1.0
|
||||
score = self.user_play.room_total_score
|
||||
player_count = self.user_play.room_total_players
|
||||
if score and player_count:
|
||||
if player_count >= 4:
|
||||
factor = 80_000_000
|
||||
elif player_count == 3:
|
||||
factor = 100_000_000
|
||||
elif player_count == 2:
|
||||
factor = 200_000_000
|
||||
else:
|
||||
return 1.0
|
||||
return 1 + score / factor
|
||||
return 1.0
|
||||
|
||||
def before_update(self) -> None:
|
||||
if self.user_play.prog_boost_multiply != 0:
|
||||
self.user.update_user_one_column('prog_boost', 0)
|
||||
@@ -953,6 +1041,7 @@ class WorldPlay(BaseWorldPlay):
|
||||
|
||||
r["user_map"]["steps"] = [x.to_dict()
|
||||
for x in self.user.current_map.steps_for_climbing]
|
||||
|
||||
return r
|
||||
|
||||
@property
|
||||
@@ -969,7 +1058,11 @@ class WorldPlay(BaseWorldPlay):
|
||||
|
||||
@property
|
||||
def final_progress(self) -> float:
|
||||
return (self.progress_normalized + (self.character_bonus_progress_normalized or 0)) * self.step_times + (self.kanae_added_progress or 0) - (self.kanae_stored_progress or 0)
|
||||
return self.progress_before_linkplay_boost * self.linkplay_boost * self.step_times + (self.kanae_added_progress or 0) - (self.kanae_stored_progress or 0)
|
||||
|
||||
@property
|
||||
def progress_before_linkplay_boost(self) -> float:
|
||||
return self.progress_normalized + (self.character_bonus_progress_normalized or 0)
|
||||
|
||||
@property
|
||||
def partner_adjusted_prog(self) -> float:
|
||||
@@ -981,9 +1074,13 @@ class WorldPlay(BaseWorldPlay):
|
||||
prog += self.prog_skill_increase
|
||||
return prog
|
||||
|
||||
@property
|
||||
def partner_multiply(self) -> float:
|
||||
return self.partner_adjusted_prog / 50
|
||||
|
||||
@property
|
||||
def progress_normalized(self) -> float:
|
||||
return self.base_progress * (self.partner_adjusted_prog / 50)
|
||||
return self.base_progress * self.partner_multiply
|
||||
|
||||
def after_update(self) -> None:
|
||||
'''世界模式更新'''
|
||||
@@ -1033,11 +1130,14 @@ class BeyondWorldPlay(BaseWorldPlay):
|
||||
|
||||
@property
|
||||
def progress_normalized(self) -> float:
|
||||
return self.base_progress * self.partner_multiply * self.affinity_multiplier
|
||||
|
||||
@property
|
||||
def partner_multiply(self) -> float:
|
||||
overdrive = self.character_used.overdrive_value
|
||||
if self.over_skill_increase:
|
||||
overdrive += self.over_skill_increase
|
||||
|
||||
return self.base_progress * (overdrive / 50) * self.affinity_multiplier
|
||||
return overdrive / 50
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
r = super().to_dict()
|
||||
@@ -1046,8 +1146,6 @@ class BeyondWorldPlay(BaseWorldPlay):
|
||||
r['pre_boost_progress'] = self.progress_normalized * \
|
||||
self.user_play.fragment_multiply / 100
|
||||
|
||||
# r['partner_multiply'] = self.affinity_multiplier # ?
|
||||
|
||||
if self.over_skill_increase is not None:
|
||||
r['char_stats']['over_skill_increase'] = self.over_skill_increase
|
||||
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
class InitData:
|
||||
char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', 'hikari&tairitsu(reunion)', 'Tairitsu(Axium)', 'Tairitsu(Grievous Lady)', 'stella', 'Hikari & Fisica', 'ilith', 'eto', 'luna', 'shirabe', 'Hikari(Zero)', 'Hikari(Fracture)', 'Hikari(Summer)', 'Tairitsu(Summer)', 'Tairitsu & Trin',
|
||||
'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)', 'shama(UNiVERSE)', 'milk(UNiVERSE)', 'shikoku', 'mika yurisaki', 'Mithra Tercera', 'Toa Kozukata', 'Nami(Twilight)', 'Ilith & Ivy', 'Hikari & Vanessa', 'Maya', 'Insight(Ascendant - 8th Seeker)', 'Luin', 'Vita(Cadenza)', 'Ai-chan', 'Luna & Ilot', 'Eto & Hoppe', 'Forlorn(Ascendant - 6th Seeker)', 'Chinatsu', 'Tsumugi', 'Nai', 'Selene Sheryl (MIR-203)', 'Salt', 'Acid', 'Hikari & Selene Sheryl (Fracture & MIR-203)', 'Hikari & El Clear', 'Tairitsu & El Fail', 'Nami & Sui (Twilight)', 'Nonoka']
|
||||
'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)', 'shama(UNiVERSE)', 'milk(UNiVERSE)', 'shikoku', 'mika yurisaki', 'Mithra Tercera', 'Toa Kozukata', 'Nami(Twilight)', 'Ilith & Ivy', 'Hikari & Vanessa', 'Maya', 'Insight(Ascendant - 8th Seeker)', 'Luin', 'Vita(Cadenza)', 'Ai-chan', 'Luna & Ilot', 'Eto & Hoppe', 'Forlorn(Ascendant - 6th Seeker)', 'Chinatsu', 'Tsumugi', 'Nai', 'Selene Sheryl (MIR-203)', 'Salt', 'Acid', 'Hikari & Selene Sheryl (Fracture & MIR-203)', 'Hikari & El Clear', 'Tairitsu & El Fail', 'Nami & Sui (Twilight)', 'Nonoka (Azure)', 'Vita (Wanderer)', 'Hikari & Tairitsu[ANS] (Breakthrough)', 'Hikari & Tairitsu[ANS] (Debut)', 'VIIM', 'Helena', 'Yuno', 'Shirahime (Flow)']
|
||||
|
||||
skill_id = ['gauge_easy', '', '', '', 'note_mirror', 'skill_reunion', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
|
||||
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'skill_mithra', 'skill_toa', 'skill_nami_twilight', 'skill_ilith_ivy', 'skill_hikari_vanessa', 'skill_maya', 'skill_intruder', 'skill_luin', '', 'skill_aichan', 'skill_luna_ilot', 'skill_eto_hoppe', 'skill_nell', 'skill_chinatsu', 'skill_tsumugi', 'skill_nai', 'skill_selene', 'skill_salt', 'skill_acid', 'skill_hikari_selene', 'skill_hikari_clear', 'skill_tairitsu_fail', 'skill_nami_sui', 'skill_nonoka']
|
||||
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'skill_mithra', 'skill_toa', 'skill_nami_twilight', 'skill_ilith_ivy', 'skill_hikari_vanessa', 'skill_maya', 'skill_intruder', 'skill_luin', '', 'skill_aichan', 'skill_luna_ilot', 'skill_eto_hoppe', 'skill_nell', 'skill_chinatsu', 'skill_tsumugi', 'skill_nai', 'skill_selene', 'skill_salt', 'skill_acid', 'skill_hikari_selene', 'skill_hikari_clear', 'skill_tairitsu_fail', 'skill_nami_sui', 'skill_nonoka', 'skill_vita_arc', '', 'skill_hikari_tairitsu_debut', 'skill_hp_slow_drain', 'skill_hprate_based_on_hp', 'skill_lost_to_85', 'skill_frag_doubled_after_earning_X']
|
||||
|
||||
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', 'ilith_awakened_skill', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
|
||||
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', 'skill_saya_uncap', '', '', '', '', '', '', 'skill_kanae_uncap', '', '', '', 'skill_doroc_uncap', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'skill_luin_uncap', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
|
||||
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', 'skill_saya_uncap', '', '', '', '', '', '', 'skill_kanae_uncap', '', '', '', 'skill_doroc_uncap', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'skill_maya_uncap', '', 'skill_luin_uncap', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'skill_nonoka_uncap', '', '', '', '', '', '', '']
|
||||
|
||||
skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
frag1 = [55, 55, 60, 50, 47, 79, 47, 57, 41, 22, 50, 54, 60, 56, 78, 42, 41, 61, 52, 50, 52, 32,
|
||||
42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42, 38, 25, 58, 50, 61, 45, 45, 38, 34, 27, 18, 56, 47, 30, 45, 57, 56, 47, 33, 26, 29, 66, 40, 33, 51, 27, 50, 60, 45, 50, 38, 22, 63, 37, 23, 59, 45, 20, 43, 50, 22, 37, 26, 47]
|
||||
42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42, 38, 25, 58, 50, 61, 45, 45, 38, 34, 27, 18, 56, 47, 30, 45, 57, 56, 47, 33, 26, 29, 66, 40, 33, 51, 27, 50, 60, 45, 50, 38, 22, 63, 37, 23, 59, 45, 20, 43, 50, 22, 37, 26, 47, 34, 45, 23, 47, 42, 70, 41]
|
||||
|
||||
prog1 = [35, 55, 47, 50, 60, 70, 60, 70, 58, 45, 70, 45, 42, 46, 61, 67, 49, 44, 28, 45, 24, 46, 52,
|
||||
59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42, 50, 50, 45, 41, 61, 45, 45, 58, 50, 130, 18, 57, 55, 50, 45, 70, 37.5, 29, 44, 26, 26, 35, 40, 33, 58, 31, 40, 50, 45, 41, 12, 31, 72, 40, 16, 33, 35, 23, 24, 46, 26, 49, 32, 35]
|
||||
59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42, 50, 50, 45, 41, 61, 45, 45, 58, 50, 130, 18, 57, 55, 50, 45, 70, 37.5, 29, 44, 26, 26, 35, 40, 33, 58, 31, 40, 50, 45, 41, 12, 31, 72, 40, 16, 33, 35, 23, 24, 46, 26, 49, 32, 35, 43, 45, 62, 77, 42, 44, 33]
|
||||
|
||||
overdrive1 = [35, 55, 25, 50, 47, 70, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18,
|
||||
48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 38, 30, 49, 15, 34, 45, 45, 38, 67, 120, 44, 33, 55, 50, 45, 57, 31, 29, 65, 26, 29, 42.5, 40, 33, 58, 31, 35, 34, 45, 41, 12, 19, 38, 40, 26, 39, 56, 20, 25, 46, 18, 71, 29, 25]
|
||||
48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 38, 30, 49, 15, 34, 45, 45, 38, 67, 120, 44, 33, 55, 50, 45, 57, 31, 29, 65, 26, 29, 42.5, 40, 33, 58, 31, 35, 34, 45, 41, 12, 19, 38, 40, 26, 39, 56, 20, 25, 46, 18, 71, 29, 25, 73, 45, 62, 47, 71, 44, 11]
|
||||
|
||||
frag20 = [78, 80, 90, 75, 70, 79, 70, 79, 65, 40, 50, 80, 90, 82, 0, 61, 67, 92, 85, 50, 86, 52,
|
||||
65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 54, 100, 50, 58, 51, 40, 115, 70, 50, 61.6, 48, 37, 90, 60, 50, 92, 66, 44, 79, 50, 47, 55, 49, 79]
|
||||
65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 54, 100, 50, 58, 51, 40, 115, 70, 50, 61.6, 48, 37, 90, 60, 50, 92, 66, 44, 79, 50, 47, 55, 49, 79, 52, 90, 35, 68, 63, 103, 65]
|
||||
|
||||
prog20 = [61, 80, 70, 75, 90, 70, 90, 102, 84, 78, 105, 67, 63, 68, 0, 99, 80, 66, 46, 83, 40, 73,
|
||||
80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 50, 53, 85, 58, 96, 47, 80, 80, 67, 41, 55, 50, 103, 66, 35, 52, 65, 50, 43, 84, 55, 73, 59, 60]
|
||||
80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 50, 53, 85, 58, 96, 47, 80, 80, 67, 41, 55, 50, 103, 66, 35, 52, 65, 50, 43, 84, 55, 73, 59, 60, 64, 67, 94, 110, 63, 65, 58]
|
||||
|
||||
overdrive20 = [61, 80, 47, 75, 70, 70, 95, 79, 65, 31, 50, 59, 90, 58, 0, 78, 50, 70, 62, 49, 64,
|
||||
46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 54, 65.5, 59.5, 58, 96, 47, 75, 54, 90, 41, 34, 30, 55, 66, 55, 62, 81, 44, 46, 84, 39, 105, 55, 43]
|
||||
46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 54, 65.5, 59.5, 58, 96, 47, 75, 54, 90, 41, 34, 30, 55, 66, 55, 62, 81, 44, 46, 84, 39, 105, 55, 43, 110, 50, 94, 68, 105, 65, 13]
|
||||
|
||||
frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62,
|
||||
65, 95, 67, 88, 74, 0.5, 105, 80, 105, 50, 80, 87, 81, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 64, 100, 50, 58, 51, 40, 115, 80, 50, 61.6, 48, 37, 90, 60, 50, 102, 76, 44, 89, 50, 47, 55, 49, 79]
|
||||
65, 95, 67, 88, 74, 0.5, 105, 80, 105, 50, 80, 87, 81, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 64, 100, 50, 58, 51, 50, 115, 80, 50, 61.6, 48, 37, 90, 60, 50, 102, 76, 44, 89, 50, 47, 55, 49, 89, 52, 90, 35, 68, 63, 103, 65]
|
||||
|
||||
prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
|
||||
80, 100, 93, 50, 96, 88, 99, 108, 85, 80, 50, 64, 65, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 60, 53, 85, 58, 96, 47, 80, 90, 67, 41, 55, 50, 103, 66, 35, 62, 75, 50, 53, 84, 55, 73, 59, 60]
|
||||
80, 100, 93, 50, 96, 88, 99, 108, 85, 80, 50, 64, 65, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 60, 53, 85, 58, 96, 57, 80, 90, 67, 41, 55, 50, 103, 66, 35, 62, 75, 50, 53, 84, 55, 73, 59, 70, 64, 67, 94, 110, 63, 65, 58]
|
||||
|
||||
overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64,
|
||||
56, 73, 105, 67, 84, 80, 88, 79, 80, 60, 80, 80, 63, 35, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 64, 65.5, 59.5, 58, 96, 47, 75, 64, 90, 41, 34, 30, 55, 66, 55, 72, 91, 44, 56, 84, 39, 105, 55, 43]
|
||||
56, 73, 105, 67, 84, 80, 88, 79, 80, 60, 80, 80, 63, 35, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 64, 65.5, 59.5, 58, 96, 57, 75, 64, 90, 41, 34, 30, 55, 66, 55, 72, 91, 44, 56, 84, 39, 105, 55, 53, 110, 50, 94, 68, 105, 65, 13]
|
||||
|
||||
char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 0, 2, 2, 1, 0, 2, 0, 4, 2, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 2, 2, 0, 2, 0, 0]
|
||||
0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 0, 2, 2, 1, 0, 2, 0, 4, 2, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 2, 2, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0]
|
||||
|
||||
char_core = {
|
||||
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
|
||||
@@ -68,13 +68,15 @@ class InitData:
|
||||
81: [{'core_id': 'core_chunithm', 'amount': 15}],
|
||||
82: [{'core_id': 'core_chunithm', 'amount': 15}],
|
||||
84: [{'core_id': 'core_maimai', 'amount': 15}],
|
||||
89: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_alluring', 'amount': 25}],
|
||||
71: [],
|
||||
}
|
||||
|
||||
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
|
||||
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase', 'core_umbral', 'core_wacca', 'core_sunset', 'core_tanoc', 'core_serene', 'core_maimai']
|
||||
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase', 'core_umbral', 'core_wacca', 'core_sunset', 'core_tanoc', 'core_serene', 'core_maimai', 'core_alluring']
|
||||
|
||||
world_songs = ["babaroque", "shadesoflight", "kanagawa", "lucifer", "anokumene", "ignotus", "rabbitintheblackroom", "qualia", "redandblue", "bookmaker", "darakunosono", "espebranch", "blacklotus", "givemeanightmare", "vividtheory", "onefr", "gekka", "vexaria3", "infinityheaven3", "fairytale3", "goodtek3", "suomi", "rugie", "faintlight", "harutopia", "goodtek", "dreaminattraction", "syro", "diode", "freefall", "grimheart", "blaster",
|
||||
"cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger", "bookmaker3", "laqryma3", "kyogenkigo", "hivemind", "seclusion", "quonwacca3", "bluecomet", "energysynergymatrix", "gengaozo", "lastendconductor3", "antithese3", "qualia3", "kanagawa3", "heavensdoor3", "pragmatism3", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote", "sanskia", 'altair', 'mukishitsu', 'trapcrow', 'redandblue3', 'ignotus3', 'singularity3', 'dropdead3', 'arcahv', 'freefall3', 'partyvinyl3', 'tsukinimurakumo', 'mantis', 'worldfragments', 'astrawalkthrough', 'chronicle', 'trappola3', 'letsrock', 'shadesoflight3', 'teriqma3', 'impact3', 'lostemotion', 'gimmick', 'lawlesspoint', 'hybris', 'ultimatetaste', 'rgb', 'matenrou', 'dynitikos', 'amekagura', 'fantasy', 'aloneandlorn', 'felys', 'onandon', 'hotarubinoyuki', 'oblivia3', 'libertas3', 'einherjar3', 'purpleverse3', 'viciousheroism3', 'inkarusi3', 'cyberneciacatharsis3', 'alephzero', 'hellohell', 'ichirin', 'awakeninruins', 'morningloom', 'lethalvoltage', 'leaveallbehind', 'desive', 'oldschoolsalvage', 'distortionhuman', 'epitaxy', 'hailstone', 'furetemitai', 'prayer', 'astralexe', 'trpno', 'blackmirror', 'tau', 'snowwhite3', 'altale3', 'energysynergymatrix3', 'anokumene3', 'nhelv3', 'wontbackdown', 'someday']
|
||||
"cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger", "bookmaker3", "laqryma3", "kyogenkigo", "hivemind", "seclusion", "quonwacca3", "bluecomet", "energysynergymatrix", "gengaozo", "lastendconductor3", "antithese3", "qualia3", "kanagawa3", "heavensdoor3", "pragmatism3", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote", "sanskia", 'altair', 'mukishitsu', 'trapcrow', 'redandblue3', 'ignotus3', 'singularity3', 'dropdead3', 'arcahv', 'freefall3', 'partyvinyl3', 'tsukinimurakumo', 'mantis', 'worldfragments', 'astrawalkthrough', 'chronicle', 'trappola3', 'letsrock', 'shadesoflight3', 'teriqma3', 'impact3', 'lostemotion', 'gimmick', 'lawlesspoint', 'hybris', 'ultimatetaste', 'rgb', 'matenrou', 'dynitikos', 'amekagura', 'fantasy', 'aloneandlorn', 'felys', 'onandon', 'hotarubinoyuki', 'oblivia3', 'libertas3', 'einherjar3', 'purpleverse3', 'viciousheroism3', 'inkarusi3', 'cyberneciacatharsis3', 'alephzero', 'hellohell', 'ichirin', 'awakeninruins', 'morningloom', 'lethalvoltage', 'leaveallbehind', 'desive', 'oldschoolsalvage', 'distortionhuman', 'epitaxy', 'hailstone', 'furetemitai', 'prayer', 'astralexe', 'trpno', 'blackmirror', 'tau', 'snowwhite3', 'altale3', 'energysynergymatrix3', 'anokumene3', 'nhelv3', 'wontbackdown', 'someday', 'disintegration', 'acheron', 'rostpagegene', 'awakening', 'mod', 'particle', 'paradoxpalette', 'xxium', 'tabootears', 'undefined', 'mask']
|
||||
|
||||
world_unlocks = ["scenery_chap1", "scenery_chap2",
|
||||
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7", "scenery_chap8", "scenery_beyond"]
|
||||
|
||||
@@ -1006,5 +1006,95 @@
|
||||
],
|
||||
"orig_price": 500,
|
||||
"price": 500
|
||||
},
|
||||
{
|
||||
"name": "anima_append_1",
|
||||
"items": [
|
||||
{
|
||||
"id": "anima_append_1",
|
||||
"type": "pack",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 5,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 500,
|
||||
"price": 500
|
||||
},
|
||||
{
|
||||
"name": "eclipse",
|
||||
"items": [
|
||||
{
|
||||
"id": "eclipse",
|
||||
"type": "pack",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 5,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 500,
|
||||
"price": 500
|
||||
},
|
||||
{
|
||||
"name": "extend_4",
|
||||
"items": [
|
||||
{
|
||||
"id": "extend_4",
|
||||
"type": "pack",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 7,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 700,
|
||||
"price": 700
|
||||
},
|
||||
{
|
||||
"name": "nextstage",
|
||||
"items": [
|
||||
{
|
||||
"id": "nextstage",
|
||||
"type": "pack",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 5,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 500,
|
||||
"price": 500
|
||||
},
|
||||
{
|
||||
"name": "megarex",
|
||||
"items": [
|
||||
{
|
||||
"id": "megarex",
|
||||
"type": "pack",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 5,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 500,
|
||||
"price": 500
|
||||
}
|
||||
]
|
||||
@@ -2116,5 +2116,131 @@
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "eternitybreak",
|
||||
"items": [
|
||||
{
|
||||
"id": "eternitybreak",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "xterfusion",
|
||||
"items": [
|
||||
{
|
||||
"id": "xterfusion",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "angelsboundary",
|
||||
"items": [
|
||||
{
|
||||
"id": "angelsboundary",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "grandspell",
|
||||
"items": [
|
||||
{
|
||||
"id": "grandspell",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "paranoidarc",
|
||||
"items": [
|
||||
{
|
||||
"id": "paranoidarc",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "subsphere",
|
||||
"items": [
|
||||
{
|
||||
"id": "subsphere",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
},
|
||||
{
|
||||
"name": "theraft",
|
||||
"items": [
|
||||
{
|
||||
"id": "theraft",
|
||||
"type": "single",
|
||||
"is_available": true
|
||||
},
|
||||
{
|
||||
"id": "core_generic",
|
||||
"type": "core",
|
||||
"amount": 1,
|
||||
"is_available": true
|
||||
}
|
||||
],
|
||||
"orig_price": 100,
|
||||
"price": 100
|
||||
}
|
||||
]
|
||||
@@ -38,7 +38,8 @@ beyond_boost_gauge real default 0,
|
||||
kanae_stored_prog real default 0,
|
||||
mp_notification_enabled int default 1,
|
||||
insight_state default 4, -- 这里写 4 是为了避免不能切换状态和无故被侵入
|
||||
custom_banner text
|
||||
custom_banner text,
|
||||
is_allow_marketing_email int default 0
|
||||
);
|
||||
create table if not exists login(access_token text,
|
||||
user_id int,
|
||||
|
||||
@@ -27,7 +27,7 @@ class Config:
|
||||
PLAYER_PRE_TIMEOUT = 3000000
|
||||
PLAYER_TIMEOUT = 15000000
|
||||
|
||||
LINK_PLAY_UNLOCK_LENGTH = 512
|
||||
LINK_PLAY_UNLOCK_LENGTH = 1024
|
||||
|
||||
COUNTDOWN_SONG_READY = 4 * 1000000
|
||||
COUNTDOWN_SONG_START = 6 * 1000000
|
||||
|
||||
@@ -133,7 +133,8 @@ class CommandParser:
|
||||
|
||||
def command_07(self):
|
||||
self.s.random_code = self.command[16:24]
|
||||
self.room.players[self.player_index].song_unlock = self.command[24:536]
|
||||
self.room.players[self.player_index].song_unlock = self.command[24:24 +
|
||||
Config.LINK_PLAY_UNLOCK_LENGTH]
|
||||
self.room.update_song_unlock()
|
||||
|
||||
self.room.command_queue.append(self.s.command_14())
|
||||
|
||||
@@ -10,7 +10,7 @@ PADDING = [b(i) * i for i in range(16)] + [b'']
|
||||
class CommandSender:
|
||||
|
||||
PROTOCOL_NAME = b'\x06\x16'
|
||||
PROTOCOL_VERSION = b'\x0D'
|
||||
PROTOCOL_VERSION = b'\x0E'
|
||||
|
||||
def __init__(self, room: Room = None) -> None:
|
||||
self.room = room
|
||||
|
||||
@@ -7,7 +7,8 @@ from core.config_manager import Config, ConfigManager
|
||||
|
||||
if os.path.exists('config.py') or os.path.exists('config'):
|
||||
# 导入用户自定义配置
|
||||
ConfigManager.load(import_module('config').Config)
|
||||
ConfigManager.load(import_module("config").Config)
|
||||
# TODO: More config file formats
|
||||
|
||||
if Config.DEPLOY_MODE == 'gevent':
|
||||
# 异步
|
||||
@@ -17,7 +18,7 @@ if Config.DEPLOY_MODE == 'gevent':
|
||||
|
||||
import sys
|
||||
from logging.config import dictConfig
|
||||
from multiprocessing import Process, set_start_method
|
||||
from multiprocessing import Process, current_process, set_start_method
|
||||
from traceback import format_exc
|
||||
|
||||
from flask import Flask, make_response, request, send_from_directory
|
||||
@@ -170,7 +171,7 @@ def generate_log_file_dict(level: str, filename: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
def pre_main():
|
||||
log_dict = {
|
||||
'version': 1,
|
||||
'root': {
|
||||
@@ -183,7 +184,7 @@ def main():
|
||||
'stream': 'ext://flask.logging.wsgi_errors_stream',
|
||||
'formatter': 'default'
|
||||
},
|
||||
"error_file": generate_log_file_dict('ERROR', './log/error.log')
|
||||
"error_file": generate_log_file_dict('ERROR', f'{Config.LOG_FOLDER_PATH}/error.log')
|
||||
},
|
||||
'formatters': {
|
||||
'default': {
|
||||
@@ -194,11 +195,11 @@ def main():
|
||||
if Config.ALLOW_INFO_LOG:
|
||||
log_dict['root']['handlers'].append('info_file')
|
||||
log_dict['handlers']['info_file'] = generate_log_file_dict(
|
||||
'INFO', './log/info.log')
|
||||
'INFO', f'{Config.LOG_FOLDER_PATH}/info.log')
|
||||
if Config.ALLOW_WARNING_LOG:
|
||||
log_dict['root']['handlers'].append('warning_file')
|
||||
log_dict['handlers']['warning_file'] = generate_log_file_dict(
|
||||
'WARNING', './log/warning.log')
|
||||
'WARNING', f'{Config.LOG_FOLDER_PATH}/warning.log')
|
||||
|
||||
dictConfig(log_dict)
|
||||
|
||||
@@ -208,6 +209,8 @@ def main():
|
||||
input('Press ENTER key to exit.')
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main():
|
||||
if Config.LINKPLAY_HOST and Config.SET_LINKPLAY_SERVER_AS_SUB_PROCESS:
|
||||
from linkplay_server import link_play
|
||||
process = [Process(target=link_play, args=(
|
||||
@@ -223,6 +226,11 @@ def main():
|
||||
tcp_server_run()
|
||||
|
||||
|
||||
# must run for init
|
||||
# this ensures avoiding duplicate init logs for some reason
|
||||
if current_process().name == 'MainProcess':
|
||||
pre_main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
set_start_method("spawn")
|
||||
main()
|
||||
|
||||
@@ -5,8 +5,9 @@ from flask import Blueprint, jsonify, request
|
||||
from werkzeug.datastructures import ImmutableMultiDict
|
||||
|
||||
from core.bundle import BundleDownload
|
||||
from core.character import UserCharacter
|
||||
from core.download import DownloadList
|
||||
from core.error import RateLimit, ArcError
|
||||
from core.error import ArcError, RateLimit
|
||||
from core.item import ItemCharacter
|
||||
from core.notification import NotificationFactory
|
||||
from core.sql import Connect
|
||||
@@ -123,6 +124,25 @@ def insight_complete(user_id, pack_id):
|
||||
})
|
||||
|
||||
|
||||
@bp.route('/unlock/me/awaken_maya', methods=['POST'])
|
||||
@auth_required(request)
|
||||
@arc_try
|
||||
def awaken_maya(user_id):
|
||||
with Connect() as c:
|
||||
ch = UserCharacter(c, 71, UserOnline(c, user_id))
|
||||
ch.select_character_info()
|
||||
try:
|
||||
ch.character_uncap()
|
||||
# ensure no error raised
|
||||
except ArcError:
|
||||
pass
|
||||
|
||||
return success_return({
|
||||
'user_id': user_id,
|
||||
'updated_characters': [ch.to_dict()]
|
||||
})
|
||||
|
||||
|
||||
@bp.route('/applog/me/log', methods=['POST'])
|
||||
def applog_me():
|
||||
# 异常日志,不处理
|
||||
|
||||
@@ -117,27 +117,30 @@ def score_token_course(user_id):
|
||||
def song_score_post(user_id):
|
||||
with Connect() as c:
|
||||
x = UserPlay(c, UserOnline(c, user_id))
|
||||
x.song_token = request.form['song_token']
|
||||
x.song_hash = request.form['song_hash']
|
||||
x.song.set_chart(
|
||||
request.form['song_id'], request.form['difficulty'])
|
||||
x.set_score(request.form['score'], request.form['shiny_perfect_count'], request.form['perfect_count'], request.form['near_count'],
|
||||
request.form['miss_count'], request.form['health'], request.form['modifier'], int(time() * 1000), request.form['clear_type'])
|
||||
x.beyond_gauge = int(request.form['beyond_gauge'])
|
||||
x.submission_hash = request.form['submission_hash']
|
||||
if 'combo_interval_bonus' in request.form:
|
||||
x.combo_interval_bonus = int(request.form['combo_interval_bonus'])
|
||||
if 'hp_interval_bonus' in request.form:
|
||||
x.hp_interval_bonus = int(request.form['hp_interval_bonus'])
|
||||
f = request.form
|
||||
x.song_token = f['song_token']
|
||||
x.song_hash = f['song_hash']
|
||||
x.song.set_chart(f['song_id'], f['difficulty'])
|
||||
x.set_score(f['score'], f['shiny_perfect_count'], f['perfect_count'], f['near_count'],
|
||||
f['miss_count'], f['health'], f['modifier'], int(time() * 1000), f['clear_type'])
|
||||
x.beyond_gauge = int(f['beyond_gauge'])
|
||||
x.submission_hash = f['submission_hash']
|
||||
x.combo_interval_bonus = f.get('combo_interval_bonus', type=int)
|
||||
x.hp_interval_bonus = f.get('hp_interval_bonus', type=int)
|
||||
# visible_map_count
|
||||
if 'fever_bonus' in request.form:
|
||||
x.fever_bonus = int(request.form['fever_bonus'])
|
||||
x.highest_health = request.form.get("highest_health", type=int)
|
||||
x.lowest_health = request.form.get("lowest_health", type=int)
|
||||
x.fever_bonus = f.get('fever_bonus', type=int)
|
||||
x.rank_bonus = f.get('rank_bonus', type=int)
|
||||
x.maya_gauge = f.get('maya_gauge', type=int)
|
||||
x.nextstage_bonus = f.get('nextstage_bonus', type=int)
|
||||
x.highest_health = f.get("highest_health", type=int)
|
||||
x.lowest_health = f.get("lowest_health", type=int)
|
||||
x.room_code = f.get('room_code')
|
||||
x.room_total_score = f.get('room_total_score', type=int)
|
||||
x.room_total_players = f.get('room_total_players', type=int)
|
||||
|
||||
if not x.is_valid:
|
||||
raise InputError('Invalid score.', 107)
|
||||
x.upload_score()
|
||||
# room_code???
|
||||
return success_return(x.to_dict())
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ def register():
|
||||
device_id = request.form['device_id']
|
||||
else:
|
||||
device_id = 'low_version'
|
||||
if 'is_allow_marketing_email' in request.form:
|
||||
new_user.is_allow_marketing_email = request.form['is_allow_marketing_email'] == 'true'
|
||||
|
||||
ip = request.remote_addr
|
||||
new_user.register(device_id, ip)
|
||||
@@ -170,7 +172,7 @@ def sys_set(user_id, set_arg):
|
||||
user.change_favorite_character(int(value))
|
||||
else:
|
||||
value = 'true' == value
|
||||
if set_arg in ('is_hide_rating', 'max_stamina_notification_enabled', 'mp_notification_enabled'):
|
||||
if set_arg in ('is_hide_rating', 'max_stamina_notification_enabled', 'mp_notification_enabled', 'is_allow_marketing_email'):
|
||||
user.update_user_one_column(set_arg, value)
|
||||
return success_return(user.to_dict())
|
||||
|
||||
|
||||
@@ -443,7 +443,7 @@ def all_character():
|
||||
def change_character():
|
||||
# 修改角色数据
|
||||
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
|
||||
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa', 'skill_nami_twilight', 'skill_ilith_ivy', 'skill_hikari_vanessa', 'skill_maya', 'skill_luin', 'skill_luin_uncap', 'skill_kanae_uncap', 'skill_doroc_uncap', 'skill_saya_uncap', 'skill_luna_ilot', 'skill_eto_hoppe', 'skill_aichan', 'skill_nell', 'skill_chinatsu', 'skill_tsumugi', 'skill_nai', 'skill_selene', 'skill_salt', 'skill_acid', 'skill_hikari_selene', 'skill_hikari_clear', 'skill_tairitsu_fail', 'skill_nami_sui']
|
||||
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa', 'skill_nami_twilight', 'skill_ilith_ivy', 'skill_hikari_vanessa', 'skill_maya', 'skill_luin', 'skill_luin_uncap', 'skill_kanae_uncap', 'skill_doroc_uncap', 'skill_saya_uncap', 'skill_luna_ilot', 'skill_eto_hoppe', 'skill_aichan', 'skill_nell', 'skill_chinatsu', 'skill_tsumugi', 'skill_nai', 'skill_selene', 'skill_salt', 'skill_acid', 'skill_hikari_selene', 'skill_hikari_clear', 'skill_tairitsu_fail', 'skill_nami_sui', 'skill_nonoka', 'skill_nonoka_uncap', 'skill_vita_arc', 'skill_maya_uncap', 'skill_hikari_tairitsu_debut', 'skill_hp_slow_drain', 'skill_lost_to_85', 'skill_hprate_based_on_hp', 'skill_frag_doubled_after_earning_X']
|
||||
return render_template('web/changechar.html', skill_ids=skill_ids)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user