23 Commits

Author SHA1 Message Date
Lost-MSth
cf20a2c7cb #234 2026-01-29 23:35:56 +08:00
Lost-MSth
bfb6a2ddda [Bug fix][Enhance] Link Play Unlock Bug
- For Arcaea 6.12.0
- Support for "is_allow_marketing_email"
- Support for config "LOG_FOLDER_PATH" (Thanks to PR #234)
- Fix a bug about one song unlock bug in Link Play, due to wrong length of song unlock bytes.
2026-01-29 23:33:00 +08:00
Lost-MSth
8180eef856 Merge pull request #234 from A-random-githuber/dev 2026-01-29 23:29:30 +08:00
A-random-githuber
2cc69a1100 oop 2025-12-26 00:35:34 +07:00
A-random-githuber
73f28f38d3 Cache song 2025-12-26 00:33:28 +07:00
A-random-githuber
707f881bdb Cache song 2025-12-26 00:27:29 +07:00
A-random-githuber
71b789fbaa Song cache 2025-12-26 00:26:22 +07:00
Lost-MSth
d11a8435b3 [Enhance] World Parallel
- For Arcaea 6.11.3
- Add partial support for World Parallel Mode.
- Add some new partners, including "VIIM", "Helena" and "Yuno".

Note: Link Play server has updated and do not support old clients anymore. World Parallel Mode and Link Play may have bugs, because I haven't check out all things carefully.
2025-12-25 01:24:35 +08:00
Lost-MSth
17998ee655 [Bug Fix] Fix /unlock/me/awaken_maya endpoint
- For Arcaea 6.11.0
- Fix a bug that custom errors in `/unlock/me/awaken_maya` cause client errors. #233
2025-11-27 22:49:21 +08:00
Lost-MSth
39e1e52d36 [Enhance] Support skill_hikari_tairitsu_debut skill
- For Arcaea 6.10.8
- Add the new character "Hikari & Tairitsu[ANS] (Debut)", and add support for its skill.
2025-11-23 01:34:40 +08:00
Lost-MSth
52bb834a8a [Enhance] Support Maya's Awaken
- For Arcaea 6.10.1
- Add an endpoint for Maya's awaken. #229
- Add the new character "Hikari & Tairitsu[ANS] (Breakthrough)". (values maybe incorrect)
2025-10-30 23:31:58 +08:00
Lost-MSth
65f6d212fe [Enhance] Maya uncap skill
- Add support for `skill_maya_uncap`
- For Arcaea 6.9.1
2025-10-02 17:45:20 +08:00
Lost-MSth
9c27b998ef [Enhance][Bug fix] API for custom banner
- For Arcaea 6.8.2
- Add support for "skill_vita_arc".
- Now `/users/<user_id> PUT` API endpoint can change the user's custom banner. #223
- Fix a bug that the playing result of beyond world map shows zero value at partner multiply term.
2025-09-04 23:39:00 +08:00
Lost-MSth
a3891a901f Merge pull request #222 from rlarhsid/dev
Adding `core_alluring`
2025-09-04 21:07:29 +08:00
Lost-MSth
32b708b7de Merge pull request #225 from YinMo19/dev
[enhance] sql efficiency
2025-09-04 21:07:01 +08:00
YinMo19
eee34bae80 [enhance] sql efficiency 2025-08-31 16:14:02 +08:00
ceed
029d43e3f5 Bug Fix for core_alluring
- Added `core_alluring` in arc_data.py.
2025-08-24 23:03:46 +09:00
Lost-MSth
05bdd42679 [Enhance] Support skill_nonoka_uncap
- Add support for `skill_nonoka_uncap`.
- For Arcaea 6.8.0
2025-08-21 16:13:43 +08:00
Lost-MSth
5b5f544a19 Merge pull request #220 from YinMo19/dev
[fix] fix sql syntax
2025-08-21 16:10:28 +08:00
YinMo19
d05618049c [fix] fix sql syntax 2025-08-21 13:48:10 +08:00
Lost-MSth
2e43dfbdff Merge pull request #219 from c8763yee/feat/audioOverride-without-remote_dl
- Make server serve audio files for songs with both `audioOverride=True` and `remote_dl=False` flags, when specifying `songlist` file in server.
2025-08-19 17:20:03 +08:00
c8763yee
7f94416189 fix: 3.ogg not download when audioOverride but remote_dl is false 2025-08-15 19:42:58 +08:00
Lost-MSth
4911511ed7 [Bug fix] Import way to run
- Fix a bug that if you import the main app, especially when using gunicorn, you will miss some necessary initial operations. #216
2025-08-06 17:29:05 +08:00
20 changed files with 446 additions and 71 deletions

View File

@@ -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
>

View File

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

View File

@@ -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'

View File

@@ -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

View File

@@ -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']

View File

@@ -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])

View File

@@ -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

View File

@@ -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

View File

@@ -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"]

View File

@@ -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
}
]

View File

@@ -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
}
]

View File

@@ -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,

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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():
# 异常日志,不处理

View File

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

View File

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

View File

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