Merge pull request #212 from Lost-MSth/dev

Update to v2.12.1
This commit is contained in:
Lost-MSth
2025-07-24 23:05:15 +08:00
committed by GitHub
26 changed files with 840 additions and 137 deletions

View File

@@ -91,73 +91,24 @@ It is just so interesting. What it can do is under exploration.
### Version 2.12.0
> v2.11.3.1 ~ v2.11.3.20 for Arcaea 5.2.0 ~ 5.10.4
> v2.12.0.1 ~ v2.12.0.10 for Arcaea 5.10.6 ~ 6.7.1
>
> Here are not some bug fixes.
>
> 注意Link Play 2.0 无法兼容旧版本客户端。 Note: Link Play 2.0 is not compatible with older client versions.
> Here are not all bug fixes and features.
- 适用于 Arcaea 5.10.4 版本
For Arcaea 5.10.4
- 适用于 Arcaea 6.7.1 版本
For Arcaea 6.7.1
- 添加一些新搭档和搭档的觉醒形态,并支持他们的技能
Add some new partners, uncap some others, and add support for their skills.
- 支持 Link Play 2.0 的几乎所有功能
Add almost whole support for Link Play 2.0.
- 支持新谱面难度 ETR
Adapt to the new difficulty ETR.
- 支持内容捆绑包(热更新),包含两种更新模式
Add support for content bundles (hot update), including two update modes.
- 支持新手任务系统
Add support for missions.
- 更新 Recent 30 机制,修改其表结构
Update Recent 30 mechanism. Alter Recent 30 table structure.
- PTT 机制更新:添加了推分保护
PTT mechanism: Change first play protection to new best protection.
- 调整世界排名机制使其更接近于官服
Adjust world rank mechanism to be closer to the official one.
- 重构世界模式,并调整了一些搭档的技能效果和进度计算逻辑
Code refactor for World Mode, and adjust some skills and the logic of progress calculation.
- 支持世界模式的陷落梯子
Add support for Breached World Map.
- 添加了一个陷落梯子例子(#148
Add an example breached map. (#148)
- 变更残片购买体力的恢复时间为 23 小时
Change the recover time of using fragments buying stamina to 23 hours.
- 支持设置多个可使用的和旧的游戏 API 前缀,其中旧的前缀会通知用户更新客户端
Add some endpoints for old API prefixes to notify users to update the client; add support for multiple game prefixes.
- 支持用户自销毁账号(默认不开启)
Add support for users destroy their own accounts. (default unable)
- 添加对“单曲兑换券”的不完整支持
Incomplete support for "pick_ticket".
- 世界模式地图文件夹中可以包含子文件夹了
Make the world maps' folder can have sub folders.
- 支持后台和 API 刷新 Recent 30 的定数评分
Add support for refreshing ratings of Recent 30 via API and webpage.
- 添加对 IP 及设备的用户注册频率限制
Add the IP and the device rate limiters for user register.
- 修复当用户再次通过已经通过的段位时无法正常上传分数的问题by Guzi422
Fix the bug that the player cannot upload the score when completing a course again. (by Guzi422)
- 修复段位模式最高分在用户未完整完成挑战时不更新的逻辑问题
Fix a logical bug that the course's high score will not update if the user does not complete the whole course challenge.
- 修复 Link Play 相关 API 接口报错的问题
Fix a bug that API for Link Play cannot work.
- 修复依赖问题cryptography >= 35.0.0
Fix requirements: cryptography >= 35.0.0
- 修复 `songlist` 解析问题(#156
Fix a `songlist` parser problem. (#156)
- 修复技能 skill_amane 在世界地图台阶类型为空时报错的问题
Fix a bug that "skill_amane" may arise error when the step type of world map is null.
- 支持自动添加搭档“光 & 对立 (Reunion)”和“光 (Fatalis)”,以尝试解决最终章的解锁问题(#110 #164
Add support for automatically adding partner "Hikari & Tairitsu (Reunion)" and "Hikari (Fatalis)", to try to unlock Finale stories correctly. (#110 #164)
- 修复 `songlist` 文件存在时视频文件无法下载的问题(#177
Fix a bug that the video files cannot be downloaded when
the `songlist` file exists. (#177)
- 修复 Link Play 中玩家全部返回房间后上一首曲子成绩消失的问题
Fix a bug that the last song's scores will disappear when all players return to room in Link Play.
- 工具 `update_song.py` 支持 ETR 难度
Add support for ETR difficulties in the `update_song.py` tool.
- 添加发送错误信息的小工具测试服务端
Add a small tool test server to send error message.
- 支持“光 (Fatalis)”的由世界模式总台阶数决定的动态搭档数值
Add support for dynamic values of "Hikari (Fatalis)", which is depended by world mode total steps.
- 支持新版本客户端的账户注册和登录接口
Adapt to new account registration and login endpoints of the new version client.
- 修复 Link Play 房间中玩家历史分数异常的问题
Fix a bug that if players do not start the first play in one room, their scores will be wrong.
- 修复因为歌曲 idx 数值过大导致服务端错误,致使 Link Play 模块无法工作的问题
Fix a bug that if song's idx is too big, the server will catch the error, which makes Link Play cannot work.
- 修复谱面难度 ETR 相关问题
Fix some bugs about the ETR difficulty.
## 运行环境与依赖 Running environment and requirements

View File

@@ -2,6 +2,7 @@ from .config_manager import Config
from .constant import Constant
from .error import ArcError, InputError, ItemNotEnough, NoData
from .item import CollectionItemMixin, ItemCore
from .sql import UserKVTable
class Level:
@@ -55,6 +56,7 @@ class Skill:
class CharacterValue:
def __init__(self, start: float = 0, mid: float = 0, end: float = 0) -> None:
self.set_parameter(start, mid, end)
self.addition: float = 0
@staticmethod
def _calc_char_value_20_math(level: int, value_1: float, value_20: float) -> float:
@@ -87,9 +89,9 @@ class CharacterValue:
def get_value(self, level: Level):
if level.min_level <= level.level <= level.mid_level:
return self._calc_char_value_20_math(level.level, self.start, self.mid)
return self._calc_char_value_20_math(level.level, self.start, self.mid) + self.addition
if level.mid_level < level.level <= level.max_level:
return self._calc_char_value_30(level.level, self.mid, self.end)
return self._calc_char_value_30(level.level, self.mid, self.end) + self.addition
return 0
@@ -231,6 +233,8 @@ class UserCharacter(Character):
self.skill_flag: bool = None
self.fatalis_is_limited: bool = False
@property
def skill_id_displayed(self) -> str:
'''对外显示的技能id'''
@@ -295,6 +299,22 @@ class UserCharacter(Character):
if self.character_id in (21, 46):
self.voice = [0, 1, 2, 3, 100, 1000, 1001]
if self.character_id == 55:
# fatalis 提升数值
# prog & overdrive += 世界模式中完成的所有非无限地图的台阶数之和 / 30
if Config.CHARACTER_FULL_UNLOCK:
addition = Constant.FATALIS_MAX_VALUE
self.fatalis_is_limited = True
else:
kvd = UserKVTable(self.c, self.user.user_id, 'world')
steps = kvd['total_step_count'] or 0
addition = steps / 30
if addition >= Constant.FATALIS_MAX_VALUE:
addition = Constant.FATALIS_MAX_VALUE
self.fatalis_is_limited = True
self.prog.addition = addition
self.overdrive.addition = addition
self.select_character_core()
def to_dict(self) -> dict:
@@ -321,7 +341,7 @@ class UserCharacter(Character):
if self.voice:
r['voice'] = self.voice
if self.character_id == 55:
r['fatalis_is_limited'] = False # emmmmmmm
r['fatalis_is_limited'] = self.fatalis_is_limited
if self.character_id in [1, 6, 7, 17, 18, 24, 32, 35, 52]:
r['base_character_id'] = 1

View File

@@ -12,7 +12,7 @@ class Config:
SONG_FILE_HASH_PRE_CALCULATE = True
GAME_API_PREFIX = '/autumnequinox/33' # str | list[str]
GAME_API_PREFIX = ['/coldwind/35', '/'] # str | list[str]
OLD_GAME_API_PREFIX = [] # str | list[str]
ALLOW_APPVERSION = [] # list[str]
@@ -86,6 +86,9 @@ class Config:
BEST30_WEIGHT = 1 / 40
RECENT10_WEIGHT = 1 / 40
INVASION_START_WEIGHT = 0.1
INVASION_HARD_WEIGHT = 0.1
MAX_FRIEND_COUNT = 50
WORLD_MAP_FOLDER_PATH = './database/map/'
@@ -103,9 +106,7 @@ class Config:
GAME_REGISTER_IP_RATE_LIMIT = '10/1 day'
GAME_REGISTER_DEVICE_RATE_LIMIT = '3/1 day'
NOTIFICATION_EXPIRE_TIME = 3 * 60 * 1000
class ConfigManager:

View File

@@ -1,7 +1,7 @@
from .config_manager import Config
ARCAEA_SERVER_VERSION = 'v2.11.3.20'
ARCAEA_DATABASE_VERSION = 'v2.11.3.19'
ARCAEA_SERVER_VERSION = 'v2.12.1'
ARCAEA_DATABASE_VERSION = 'v2.12.1'
ARCAEA_LOG_DATBASE_VERSION = 'v1.1'
@@ -11,6 +11,10 @@ class Constant:
MAX_STAMINA = 12
# INSIGHT_STATES = [x for x in range(7)]
INSIGHT_TOGGLE_STATES = [3, 4, 5, 6]
# DEFAULT_INSIGHT_STATE = Config.DEFAULT_INSIGHT_STATE
STAMINA_RECOVER_TICK = 1800000
FRAGSTAM_RECOVER_TICK = 23 * 3600 * 1000
@@ -32,6 +36,7 @@ class Constant:
SKILL_FATALIS_WORLD_LOCKED_TIME = 3600000
SKILL_MIKA_SONGS = ['aprilshowers', 'seventhsense', 'oshamascramble',
'amazingmightyyyy', 'cycles', 'maxrage', 'infinity', 'temptation']
FATALIS_MAX_VALUE = 100
MAX_FRIEND_COUNT = Config.MAX_FRIEND_COUNT
@@ -40,6 +45,8 @@ class Constant:
BEST30_WEIGHT = Config.BEST30_WEIGHT
RECENT10_WEIGHT = Config.RECENT10_WEIGHT
INVASION_START_WEIGHT = Config.INVASION_START_WEIGHT
INVASION_HARD_WEIGHT = Config.INVASION_HARD_WEIGHT
WORLD_MAP_FOLDER_PATH = Config.WORLD_MAP_FOLDER_PATH
SONG_FILE_FOLDER_PATH = Config.SONG_FILE_FOLDER_PATH
@@ -66,7 +73,6 @@ class Constant:
LINKPLAY_TCP_SECRET_KEY = Config.LINKPLAY_TCP_SECRET_KEY
LINKPLAY_TCP_MAX_LENGTH = 0x0FFFFFFF
LINKPLAY_MATCH_GET_ROOMS_INTERVAL = 4 # Units: seconds
LINKPLAY_MATCH_PTT_ABS = [5, 20, 50, 100, 200, 500, 1000, 2000]
LINKPLAY_MATCH_UNLOCK_MIN = [1000, 800, 500, 300, 200, 100, 50, 1]

View File

@@ -23,6 +23,8 @@ def get_song_unlock(client_song_map: 'dict[str, list]') -> bytes:
if not v[i]:
continue
index = int(k) * 5 + i
if index >= Constant.LINKPLAY_UNLOCK_LENGTH * 8:
continue
user_song_unlock[index // 8] |= 1 << (index % 8)
return bytes(user_song_unlock)

View File

@@ -171,9 +171,9 @@ class SaveUpdateScore(BaseOperation):
song_id = list(set(song_id_1 + song_id_2))
c.execute(
f'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart where song_id in ({','.join(['?']*len(song_id))})''', song_id)
f'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn, rating_etr from chart where song_id in ({','.join(['?']*len(song_id))})''', song_id)
x = c.fetchall()
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4]]
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4], i[5]]
for i in x} # chart const * 10
new_scores = []
@@ -204,8 +204,8 @@ class SaveUpdateScore(BaseOperation):
def _all_update(self):
with Connect() as c:
c.execute(
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4]]
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn, rating_etr from chart''')
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4], i[5]]
for i in c.fetchall()} # chart const * 10
c.execute('''select user_id from user_save''')
for y in c.fetchall():

View File

@@ -1,5 +1,6 @@
from base64 import b64encode
from os import urandom
from random import choices
from time import time
from .bgtask import BGTask, logdb_execute
@@ -244,10 +245,15 @@ class UserPlay(UserScore):
self.course_play: 'CoursePlay' = None
self.combo_interval_bonus: int = None # 不能给 None 以外的默认值
self.hp_interval_bonus: int = None # 不能给 None 以外的默认值
self.fever_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
self.invasion_flag: int = None # 1: invasion_start, 2: invasion_hard
def to_dict(self) -> dict:
# 不能super
if self.is_world_mode is None or self.course_play_state is None:
@@ -288,6 +294,13 @@ class UserPlay(UserScore):
return False
x = x + str(self.combo_interval_bonus)
if self.hp_interval_bonus is not None and self.hp_interval_bonus < 0:
return False
if self.fever_bonus is not None and (self.fever_bonus < 0 or self.fever_bonus > self.perfect_count * 5):
# fever 等级最高为 5
return False
y = f'{self.user.user_id}{self.song_hash}'
checksum = md5(x+md5(y))
@@ -326,16 +339,27 @@ class UserPlay(UserScore):
self.prog_boost_multiply = int(x[10])
self.beyond_boost_gauge_usage = int(x[11])
self.skill_cytusii_flag = x[12]
self.skill_chinatsu_flag = x[13]
self.invasion_flag = x[14]
self.is_world_mode = True
self.course_play_state = -1
def set_play_state_for_world(self, stamina_multiply: int = 1, fragment_multiply: int = 100, prog_boost_multiply: int = 0, beyond_boost_gauge_usage: int = 0, skill_cytusii_flag: str = None) -> None:
def set_play_state_for_world(
self,
stamina_multiply: int = 1,
fragment_multiply: int = 100,
prog_boost_multiply: int = 0,
beyond_boost_gauge_usage: int = 0,
skill_cytusii_flag: str = None,
skill_chinatsu_flag: str = None
) -> None:
self.song_token = b64encode(urandom(64)).decode()
self.stamina_multiply = int(stamina_multiply)
self.fragment_multiply = int(fragment_multiply)
self.prog_boost_multiply = int(prog_boost_multiply)
self.beyond_boost_gauge_usage = int(beyond_boost_gauge_usage)
self.skill_cytusii_flag = skill_cytusii_flag
self.skill_chinatsu_flag = skill_chinatsu_flag
if self.prog_boost_multiply != 0 or self.beyond_boost_gauge_usage != 0:
self.c.execute('''select prog_boost, beyond_boost_gauge from user where user_id=:a''', {
'a': self.user.user_id})
@@ -346,10 +370,6 @@ class UserPlay(UserScore):
# 注意偷懒了没判断是否是beyond图
self.beyond_boost_gauge_usage = 0
self.clear_play_state()
self.c.execute('''insert into songplay_token values(:t,:a,:b,:c,'',-1,0,0,:d,:e,:f,:g,:h)''', {
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.stamina_multiply, 'e': self.fragment_multiply, 'f': self.prog_boost_multiply, 'g': self.beyond_boost_gauge_usage, 'h': self.skill_cytusii_flag, 't': self.song_token})
self.user.select_user_about_current_map()
self.user.current_map.select_map_info()
@@ -357,17 +377,26 @@ class UserPlay(UserScore):
if self.user.stamina.stamina < self.user.current_map.stamina_cost * self.stamina_multiply:
raise StaminaNotEnough('Stamina is not enough.')
fatalis_stamina_multiply = 1
self.user.select_user_about_character()
if not self.user.is_skill_sealed:
self.user.character.select_character_info()
if self.user.character.skill_id_displayed == 'skill_fatalis':
# 特殊判断hikari fatalis的双倍体力消耗
self.user.stamina.stamina -= self.user.current_map.stamina_cost * \
self.stamina_multiply * 2
self.user.stamina.update()
return None
# invasion 扔骰子
_flag = choices([0, 1, 2], [
max(1 - Constant.INVASION_START_WEIGHT - Constant.INVASION_HARD_WEIGHT, 0), Constant.INVASION_START_WEIGHT, Constant.INVASION_HARD_WEIGHT])[0]
if self.user.is_insight_enabled and _flag != 0:
self.invasion_flag = _flag
self.user.stamina.stamina -= self.user.current_map.stamina_cost * self.stamina_multiply
elif self.user.character.skill_id_displayed == 'skill_fatalis':
# 特殊判断hikari fatalis的双倍体力消耗
fatalis_stamina_multiply = 2
self.clear_play_state()
self.c.execute('''insert into songplay_token values(:t,:a,:b,:c,'',-1,0,0,:d,:e,:f,:g,:h,:i,:j)''', {
'a': self.user.user_id, 'b': self.song.song_id, 'c': self.song.difficulty, 'd': self.stamina_multiply, 'e': self.fragment_multiply, 'f': self.prog_boost_multiply, 'g': self.beyond_boost_gauge_usage, 'h': self.skill_cytusii_flag, 'i': self.skill_chinatsu_flag, 'j': self.invasion_flag, 't': self.song_token})
self.user.stamina.stamina -= self.user.current_map.stamina_cost * \
self.stamina_multiply * fatalis_stamina_multiply
self.user.stamina.update()
def set_play_state_for_course(self, use_course_skip_purchase: bool, course_id: str = None) -> None:
@@ -380,8 +409,8 @@ class UserPlay(UserScore):
self.course_play.score = 0
self.course_play.clear_type = 3 # 设置为PM即最大值
self.c.execute('''insert into songplay_token values(?,?,?,?,?,?,?,?,1,100,0,0,"")''', (self.song_token, self.user.user_id, self.song.song_id,
self.song.difficulty, self.course_play.course_id, self.course_play_state, self.course_play.score, self.course_play.clear_type))
self.c.execute('''insert into songplay_token values(?,?,?,?,?,?,?,?,1,100,0,0,"","",0)''', (self.song_token, self.user.user_id, self.song.song_id,
self.song.difficulty, self.course_play.course_id, self.course_play_state, self.course_play.score, self.course_play.clear_type))
self.user.select_user_about_stamina()
if use_course_skip_purchase:
x = ItemCore(self.c)

View File

@@ -525,3 +525,35 @@ class MemoryDatabase:
@register
def atexit():
MemoryDatabase.conn.close()
class UserKVTable:
'''用户键值对表'''
def __init__(self, c=None, user_id: int = None, class_name: str = None) -> None:
self.c = c
self.user_id = user_id
self.class_name = class_name
def get(self, key: str, idx: int = 0):
'''获取键值对'''
x = self.c.execute(
'''select value from user_kvdata where user_id = ? and class = ? and key = ? and idx = ?''', (self.user_id, self.class_name, key, idx)).fetchone()
return x[0] if x else None
def set(self, key: str, value, idx: int = 0) -> None:
'''设置键值对'''
self.c.execute('''insert or replace into user_kvdata values(?,?,?,?,?)''',
(self.user_id, self.class_name, key, idx, value))
def __getitem__(self, args):
if isinstance(args, tuple):
return self.get(*args)
else:
return self.get(args)
def __setitem__(self, args, value):
if isinstance(args, tuple):
self.set(args[0], value, args[1])
else:
self.set(args, value)

View File

@@ -13,8 +13,8 @@ from .item import UserItemList
from .limiter import ArcLimiter
from .mission import UserMissionList
from .score import Score
from .sql import Query, Sql
from .world import Map, UserMap, UserStamina
from .sql import Query, Sql, UserKVTable
from .world import Map, MapParser, UserMap, UserStamina
def code_get_id(c, user_code: str) -> int:
@@ -326,6 +326,10 @@ class UserInfo(User):
self.current_map: 'Map' = None
self.stamina: 'UserStamina' = None
self.insight_state: int = None
self.custom_banner = None
self.__cores: list = None
self.__packs: list = None
self.__singles: list = None
@@ -336,6 +340,12 @@ class UserInfo(User):
self.curr_available_maps: list = None
self.__course_banners: list = None
@property
def is_insight_enabled(self) -> bool:
if self.insight_state is None:
self.select_user_one_column('insight_state', 4, int)
return self.insight_state == 3 or self.insight_state == 5
@property
def cores(self) -> list:
if self.__cores is None:
@@ -545,8 +555,17 @@ class UserInfo(User):
'pick_ticket': self.pick_ticket,
# 'custom_banner': 'online_banner_2024_06',
'custom_banner': self.custom_banner,
# 'subscription_multiplier': 114,
# 'memory_boost_ticket': 5,
'insight_state': self.insight_state, # 0~2 不可选3 技能激活4 未激活5 激活可选6 未激活可选
# 'enabled_features': [
# {
# "metadata": ["USA"],
# "feature": "paymentlink"
# }
# ],
}
def from_list(self, x: list) -> 'UserInfo':
@@ -591,6 +610,10 @@ class UserInfo(User):
self.mp_notification_enabled = x[37] == 1
self.insight_state = x[38]
self.custom_banner = x[39] if x[39] is not None else ''
return self
def select_user(self) -> None:
@@ -719,6 +742,35 @@ class UserInfo(User):
'''update user set world_rank_score = ? where user_id = ?''', (x[0], self.user_id))
self.world_rank_score = x[0]
def update_user_world_complete_info(self) -> None:
'''
更新用户的世界模式完成信息,包括两个部分
1. 每个章节的完成地图数量,为了 salt 技能
2. 全世界模式完成台阶数之和,为了 fatalis 技能
'''
kvd = UserKVTable(self.c, self.user_id, 'world')
for chapter_id, map_ids in MapParser.chapter_info_without_repeatable.items():
self.c.execute(
f'''select map_id, curr_position from user_world where user_id = ? and map_id in ({','.join(['?']*len(map_ids))})''',
(self.user_id, *map_ids)
)
x = self.c.fetchall()
n = 0
for map_id, curr_position in x:
step_count = MapParser.world_info[map_id]['step_count']
if curr_position == step_count - 1:
n += 1
kvd['chapter_complete_count', chapter_id] = n
self.c.execute(
'''select sum(curr_position) + count(*) from user_world where user_id = ?''', (self.user_id,)
)
x = self.c.fetchone()
if x is not None:
kvd['total_step_count'] = x[0] or 0
def select_user_one_column(self, column_name: str, default_value=None, data_type=None) -> None:
'''
查询user表的某个属性
@@ -795,6 +847,19 @@ class UserOnline(UserInfo):
self.c.execute('''update user set favorite_character = :a where user_id = :b''',
{'a': self.favorite_character.character_id, 'b': self.user_id})
def toggle_invasion(self) -> None:
self.c.execute(
'''select insight_state from user where user_id = ?''', (self.user_id,))
x = self.c.fetchone()
if not x:
raise NoData('No user.', 108, -3)
self.insight_state = x[0]
rq = Constant.INSIGHT_TOGGLE_STATES
self.insight_state = rq[(rq.index(self.insight_state) + 1) % len(rq)]
self.c.execute(
'''update user set insight_state = ? where user_id = ?''', (self.insight_state, self.user_id))
class UserChanger(UserInfo, UserRegister):

View File

@@ -4,16 +4,23 @@ from json import load
from random import randint
from time import time
from .character import Character
from .character import Character, UserCharacter
from .config_manager import Config
from .constant import Constant
from .error import InputError, MapLocked, NoData
from .item import ItemFactory
from .sql import UserKVTable
class MapParser:
map_id_path: 'dict[str, str]' = {}
world_info: 'dict[str, dict]' = {} # 简要记录地图信息
chapter_info: 'dict[int, list[str]]' = {} # 章节包含的地图
# 章节包含的地图(不包含可重复地图)
chapter_info_without_repeatable: 'dict[int, list[str]]' = {}
def __init__(self) -> None:
if not self.map_id_path:
self.parse()
@@ -25,10 +32,31 @@ class MapParser:
continue
path = os.path.join(root, file)
self.map_id_path[file[:-5]] = path
map_id = file[:-5]
self.map_id_path[map_id] = path
map_data = self.get_world_info(map_id)
chapter = map_data.get('chapter', None)
if chapter is None:
continue
self.chapter_info.setdefault(chapter, []).append(map_id)
is_repeatable = map_data.get('is_repeatable', False)
if not is_repeatable:
self.chapter_info_without_repeatable.setdefault(
chapter, []).append(map_id)
self.world_info[map_id] = {
'chapter': chapter,
'is_repeatable': is_repeatable,
'is_beyond': map_data.get('is_beyond', False),
'is_legacy': map_data.get('is_legacy', False),
'step_count': len(map_data.get('steps', [])),
}
def re_init(self) -> None:
self.map_id_path.clear()
self.world_info.clear()
self.chapter_info.clear()
self.chapter_info_without_repeatable.clear()
self.get_world_info.cache_clear()
self.parse()
@@ -133,6 +161,7 @@ class Map:
self.chain_info: dict = None
# self.requires: list[dict] = None
self.requires_any: 'list[dict]' = None
self.disable_over: bool = None
self.new_law: str = None
@@ -188,6 +217,8 @@ class Map:
r['disable_over'] = self.disable_over
if self.new_law is not None and self.new_law != '':
r['new_law'] = self.new_law
if self.requires_any:
r['requires_any'] = self.requires_any
return r
def from_dict(self, raw_dict: dict) -> 'Map':
@@ -216,6 +247,7 @@ class Map:
self.disable_over = raw_dict.get('disable_over')
self.new_law = raw_dict.get('new_law')
self.requires_any = raw_dict.get('requires_any')
return self
def select_map_info(self):
@@ -467,6 +499,14 @@ class UserStamina(Stamina):
class WorldSkillMixin:
'''
不可实例化
self.c = c
self.user = user
self.user_play = user_play
'''
def before_calculate(self) -> None:
factory_dict = {
'skill_vita': self._skill_vita,
@@ -474,7 +514,11 @@ class WorldSkillMixin:
'skill_ilith_ivy': self._skill_ilith_ivy,
'ilith_awakened_skill': self._ilith_awakened_skill,
'skill_hikari_vanessa': self._skill_hikari_vanessa,
'skill_mithra': self._skill_mithra
'skill_mithra': self._skill_mithra,
'skill_chinatsu': self._skill_chinatsu,
'skill_salt': self._skill_salt,
'skill_hikari_selene': self._skill_hikari_selene,
'skill_nami_sui': self._skill_nami_sui,
}
if self.user_play.beyond_gauge == 0 and self.character_used.character_id == 35 and self.character_used.skill_id_displayed:
self._special_tempest()
@@ -492,6 +536,7 @@ class WorldSkillMixin:
'luna_uncap': self._luna_uncap,
'skill_kanae_uncap': self._skill_kanae_uncap,
'skill_eto_hoppe': self._skill_eto_hoppe,
'skill_intruder': self._skill_intruder,
}
if self.character_used.skill_id_displayed in factory_dict:
factory_dict[self.character_used.skill_id_displayed]()
@@ -630,9 +675,11 @@ class WorldSkillMixin:
kanae 觉醒技能,保存世界模式 progress 并在下次结算
直接加减在 progress 最后
技能存储 base_progress * PROG / 50下一次消耗全部存储值无视技能和搭档但需要非技能隐藏状态
6.0 更新:需要体力消耗才存
'''
self.kanae_stored_progress = self.progress_normalized
self.user.current_map.reclimb(self.final_progress)
if self.user.current_map.stamina_cost > 0:
self.kanae_stored_progress = self.progress_normalized
self.user.current_map.reclimb(self.final_progress)
def _skill_eto_hoppe(self) -> None:
'''
@@ -642,6 +689,67 @@ class WorldSkillMixin:
self.character_bonus_progress_normalized = self.progress_normalized
self.user.current_map.reclimb(self.final_progress)
def _skill_chinatsu(self) -> None:
'''
chinatsu 技能hp 超过时提高搭档能力值
'''
_flag = self.user_play.skill_chinatsu_flag
if not self.user_play.hp_interval_bonus or not _flag:
return
x = _flag[:min(len(_flag), self.user_play.hp_interval_bonus)]
self.over_skill_increase = x.count('2') * 5
self.prog_skill_increase = x.count('1') * 5
def _skill_intruder(self) -> None:
'''
intruder 技能,夺舍后世界进度翻倍
'''
if self.user_play.invasion_flag:
self.character_bonus_progress_normalized = self.progress_normalized
self.user.current_map.reclimb(self.final_progress)
def _skill_salt(self) -> None:
'''
salt 技能,根据单个章节地图的完成情况额外获得最高 10 的世界模式进度
当前章节完成地图数 / 本章节总地图数(不含无限图)* 10
'''
if Config.CHARACTER_FULL_UNLOCK:
self.character_bonus_progress_normalized = 10
return
kvd = UserKVTable(self.c, self.user.user_id, 'world')
chapter_id = self.user.current_map.chapter
count = kvd['chapter_complete_count', chapter_id] or 0
total = len(MapParser.chapter_info_without_repeatable[chapter_id])
if count > total:
count = total
radio = count / total if total else 1
self.character_bonus_progress_normalized = 10 * radio
def _skill_hikari_selene(self) -> None:
'''
hikari_selene 技能,曲目结算时每满一格收集条增加 2 step 与 2 overdrive
'''
self.over_skill_increase = 0
self.prog_skill_increase = 0
if 0 < self.user_play.health <= 100:
self.over_skill_increase = int(self.user_play.health / 10) * 2
self.prog_skill_increase = int(self.user_play.health / 10) * 2
def _skill_nami_sui(self) -> None:
'''
nami & sui 技能,根据纯粹音符数与 FEVER 等级提高世界模式进度
'''
if self.user_play.fever_bonus is None:
return
self.character_bonus_progress_normalized = self.user_play.fever_bonus / 1000
class BaseWorldPlay(WorldSkillMixin):
'''
@@ -690,9 +798,13 @@ class BaseWorldPlay(WorldSkillMixin):
'world_mode_locked_end_ts': self.user.world_mode_locked_end_ts,
'beyond_boost_gauge': self.user.beyond_boost_gauge,
# 'wpaid': 'helloworld', # world play id ???
# progress_before_sub_boost
# progress_sub_boost_amount
# subscription_multiply
'progress_before_sub_boost': self.final_progress,
'progress_sub_boost_amount': 0,
# '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':
@@ -751,6 +863,11 @@ class BaseWorldPlay(WorldSkillMixin):
if self.user_play.beyond_gauge == 0 and self.user.kanae_stored_prog > 0:
# 实在不想拆开了,在这里判断一下,注意这段不会在 BeyondWorldPlay 中执行
self.kanae_added_progress = self.user.kanae_stored_prog
if self.user_play.invasion_flag == 1 or (self.user_play.invasion_flag == 2 and self.user_play.health <= 0):
# 这里硬编码了搭档 id 72
self.character_used = UserCharacter(self.c, 72, self.user)
self.character_used.select_character_info()
else:
self.character_used.character_id = self.user.character.character_id
self.character_used.level.level = self.user.character.level.level
@@ -785,6 +902,9 @@ class BaseWorldPlay(WorldSkillMixin):
self.user.current_map.update()
# 更新用户完成情况
self.user.update_user_world_complete_info()
def update(self) -> None:
'''世界模式更新'''
self.before_update()
@@ -932,6 +1052,7 @@ class BeyondWorldPlay(BaseWorldPlay):
r['char_stats']['over_skill_increase'] = self.over_skill_increase
r["user_map"]["steps"] = len(self.user.current_map.steps_for_climbing)
r['affinity_multiply'] = self.affinity_multiplier
if self.user_play.beyond_boost_gauge_usage != 0:
r['beyond_boost_gauge_usage'] = self.user_play.beyond_boost_gauge_usage

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']
'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']
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']
'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_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_luin_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, 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]
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]
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]
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, 50, 50, 45, 41, 12, 31]
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]
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, 50, 34, 45, 41, 12, 19]
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]
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, 50, 70, 50, 61.6, 48, 37]
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]
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, 50, 80, 67, 41, 55, 50]
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]
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, 50, 54, 90, 41, 34, 30]
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]
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, 50, 80, 50, 61.6, 48, 37]
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]
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, 50, 90, 67, 41, 55, 50]
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]
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, 50, 64, 90, 41, 34, 30]
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]
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, 0, 2, 0, 0, 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]
char_core = {
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
@@ -65,18 +65,22 @@ class InitData:
30: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_sunset', 'amount': 25}],
34: [{'core_id': 'core_tanoc', 'amount': 15}],
23: [{'core_id': 'core_desolate', 'amount': 5}, {'core_id': 'core_serene', 'amount': 25}],
81: [{'core_id': 'core_chunithm', 'amount': 15}],
82: [{'core_id': 'core_chunithm', 'amount': 15}],
84: [{'core_id': 'core_maimai', 'amount': 15}],
}
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_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']
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']
"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']
world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7", "scenery_beyond"]
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7", "scenery_chap8", "scenery_beyond"]
course_banners = ['course_banner_' + str(i) for i in range(1, 12)]
# TODO: online_banners
role = ['system', 'admin', 'user', 'selecter']
role_caption = ['系统', '管理员', '用户', '查询接口']

View File

@@ -445,5 +445,50 @@
"core_generic_20",
"course_banner_11"
]
},
{
"course_id": "6.0-dan-12",
"style": 12,
"songs": [
{
"flag_as_hidden": true,
"difficulty": 2,
"id": "abstrusedilemma"
},
{
"flag_as_hidden": true,
"difficulty": 3,
"id": "worldender"
},
{
"flag_as_hidden": true,
"difficulty": 4,
"id": "alterego"
},
{
"flag_as_hidden": true,
"difficulty": 3,
"id": "designant"
}
],
"gauge_requirement": "default",
"requirements": [
{
"value": "4.0-dan-11",
"type": "course"
}
],
"flag_as_hidden_when_requirements_not_met": true,
"dan_name": "Phase 12",
"course_name": "侵蚀万物的色彩",
"can_start": true,
"is_completed": false,
"high_score": 0,
"best_clear_type": -1,
"rewards": [
"fragment2000",
"core_generic_20",
"course_banner_12"
]
}
]

View File

@@ -862,5 +862,149 @@
],
"orig_price": 500,
"price": 500
},
{
"name": "ongeki_append_2",
"items": [
{
"type": "pack",
"id": "ongeki_append_2",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "lephon",
"items": [
{
"type": "pack",
"id": "lephon",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "chunithm_append_3",
"items": [
{
"type": "pack",
"id": "chunithm_append_3",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "maimai_append_2",
"items": [
{
"type": "pack",
"id": "maimai_append_2",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "undertale",
"items": [
{
"type": "pack",
"id": "undertale",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "djmax",
"items": [
{
"type": "pack",
"id": "djmax",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "djmax_append_1",
"items": [
{
"type": "pack",
"id": "djmax_append_1",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "anima",
"items": [
{
"id": "anima",
"type": "pack",
"is_available": true
},
{
"id": "core_generic",
"type": "core",
"amount": 5,
"is_available": true
}
],
"orig_price": 500,
"price": 500
}
]

View File

@@ -1918,5 +1918,203 @@
],
"orig_price": 100,
"price": 100
},
{
"name": "thirdsun",
"items": [
{
"type": "single",
"id": "thirdsun",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "crimsonquartz",
"items": [
{
"type": "single",
"id": "crimsonquartz",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "diesirae",
"items": [
{
"type": "single",
"id": "diesirae",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "spiritdauntless",
"items": [
{
"type": "single",
"id": "spiritdauntless",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "placebobattler",
"items": [
{
"type": "single",
"id": "placebobattler",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "megalovaniarmx",
"items": [
{
"type": "single",
"id": "megalovaniarmx",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "shinchoku",
"items": [
{
"type": "single",
"id": "shinchoku",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "dullblade",
"items": [
{
"type": "single",
"id": "dullblade",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "tictactoe",
"items": [
{
"type": "single",
"id": "tictactoe",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "dontdie",
"items": [
{
"type": "single",
"id": "dontdie",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "miles",
"items": [
{
"type": "single",
"id": "miles",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
}
]

View File

@@ -36,7 +36,9 @@ stamina int,
world_mode_locked_end_ts int,
beyond_boost_gauge real default 0,
kanae_stored_prog real default 0,
mp_notification_enabled int default 1
mp_notification_enabled int default 1,
insight_state default 4, -- 这里写 4 是为了避免不能切换状态和无故被侵入
custom_banner text
);
create table if not exists login(access_token text,
user_id int,
@@ -145,7 +147,9 @@ stamina_multiply int,
fragment_multiply int,
prog_boost_multiply int,
beyond_boost_gauge_usage int,
skill_cytusii_flag text
skill_cytusii_flag text,
skill_chinatsu_flag text,
invasion_flag int
);
create table if not exists item(item_id text,
type text,
@@ -276,6 +280,17 @@ status int,
primary key(user_id, mission_id)
);
-- value 无类型
create table if not exists user_kvdata(
user_id int,
class text,
key text,
idx int,
value,
primary key(user_id, class, key, idx)
);
create index if not exists best_score_1 on best_score (song_id, difficulty);
PRAGMA journal_mode = WAL;

View File

@@ -247,6 +247,10 @@ class CommandParser:
self.room.make_round()
logging.info(f'Room `{self.room.room_code}` starts playing')
for p in self.room.players:
# 防止提前结算
p.finish_flag = 0
if self.room.state == 4:
# 这好像会误判
# if player.download_percent < 99:

View File

@@ -23,7 +23,7 @@ def get_bps():
return s
bp = Blueprint('server', __name__)
list(map(bp.register_blueprint, [user.bp, auth.bp, friend.bp, score.bp,
list(map(bp.register_blueprint, [user.bp2, user.bp, auth.bp, friend.bp, score.bp,
world.bp, purchase.bp, present.bp, others.bp, multiplayer.bp, course.bp, mission.bp]))
bps = [Blueprint(x, __name__, url_prefix=x)

View File

@@ -36,12 +36,6 @@ def login():
return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token})
@bp.route('/verify', methods=['POST']) # 邮箱验证进度查询
@arc_try
def email_verify():
raise ArcError('Email verification unavailable.', 151, status=404)
def auth_required(req):
# arcaea登录验证写成了修饰器
def decorator(view):

View File

@@ -6,7 +6,7 @@ from werkzeug.datastructures import ImmutableMultiDict
from core.bundle import BundleDownload
from core.download import DownloadList
from core.error import RateLimit
from core.error import RateLimit, ArcError
from core.item import ItemCharacter
from core.notification import NotificationFactory
from core.sql import Connect
@@ -101,6 +101,28 @@ def finale_end(user_id):
return success_return({})
@bp.route('/insight/me/complete/<string:pack_id>', methods=['POST'])
@auth_required(request)
@arc_try
def insight_complete(user_id, pack_id):
# insight state change
with Connect() as c:
u = UserOnline(c, user_id)
if pack_id == 'eden_append_1':
item = ItemCharacter(c)
item.set_id('72') # Insight (Ascendant - 8th Seeker)
item.user_claim_item(u)
u.update_user_one_column('insight_state', 1)
elif pack_id == 'lephon':
u.update_user_one_column('insight_state', 3)
else:
raise ArcError('Invalid pack_id', 151, status=404)
return success_return({
'insight_state': u.insight_state
})
@bp.route('/applog/me/log', methods=['POST'])
def applog_me():
# 异常日志,不处理

View File

@@ -29,11 +29,12 @@ def score_token_world(user_id):
d = request.args.get
stamina_multiply = int(d('stamina_multiply', 1))
fragment_multiply = int(d('fragment_multiply', 100))
prog_boost_multiply = int(d('prog_boost_multiply', 0))
beyond_boost_gauge_use = int(d('beyond_boost_gauge_use', 0))
stamina_multiply = d('stamina_multiply', 1, type=int)
fragment_multiply = d('fragment_multiply', 100, type=int)
prog_boost_multiply = d('prog_boost_multiply', 0, type=int)
beyond_boost_gauge_use = d('beyond_boost_gauge_use', 0, type=int)
skill_cytusii_flag = None
skill_chinatsu_flag = None
skill_id = d('skill_id')
if (skill_id == 'skill_ilith_ivy' or skill_id == 'skill_hikari_vanessa') and d('is_skill_sealed') == 'false':
@@ -41,23 +42,34 @@ def score_token_world(user_id):
# TODO: 需要重构整个 user_play世界模式 / 课题模式,所以现在临时 work 一下
skill_cytusii_flag = ''.join([str(randint(0, 2)) for _ in range(5)])
if skill_id == 'skill_chinatsu' and d('is_skill_sealed') == 'false':
skill_chinatsu_flag = ''.join([str(randint(0, 2)) for _ in range(7)])
skill_flag = skill_cytusii_flag or skill_chinatsu_flag
with Connect() as c:
x = UserPlay(c, UserOnline(c, user_id))
x.song.set_chart(request.args['song_id'], int(
request.args['difficulty']))
x.song.set_chart(d('song_id'), d('difficulty', type=int))
x.set_play_state_for_world(
stamina_multiply, fragment_multiply, prog_boost_multiply, beyond_boost_gauge_use, skill_cytusii_flag)
stamina_multiply, fragment_multiply, prog_boost_multiply, beyond_boost_gauge_use, skill_cytusii_flag, skill_chinatsu_flag)
r = {
"stamina": x.user.stamina.stamina,
"max_stamina_ts": x.user.stamina.max_stamina_ts,
"token": x.song_token,
'play_parameters': {},
}
if skill_cytusii_flag and skill_id:
if skill_flag and skill_id:
r['play_parameters'] = {
skill_id: list(
map(lambda x: Constant.WORLD_VALUE_NAME_ENUM[int(x)], skill_cytusii_flag))
map(lambda x: Constant.WORLD_VALUE_NAME_ENUM[int(x)], skill_flag)),
}
if x.invasion_flag == 1:
r['play_parameters']['invasion_start'] = True
elif x.invasion_flag == 2:
r['play_parameters']['invasion_hard'] = True
return success_return(r)
@@ -115,11 +127,17 @@ def song_score_post(user_id):
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'])
# 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)
if not x.is_valid:
raise InputError('Invalid score.', 107)
x.upload_score()
# room_code???
return success_return(x.to_dict())

View File

@@ -14,8 +14,11 @@ from .func import arc_try, header_check, success_return
bp = Blueprint('user', __name__, url_prefix='/user')
bp2 = Blueprint('account', __name__, url_prefix='/account')
@bp.route('', methods=['POST']) # 注册接口
@bp2.route('', methods=['POST'])
@arc_try
def register():
error = header_check(request)
@@ -50,6 +53,16 @@ def user_me(user_id):
return success_return(UserOnline(c, user_id).to_dict())
@bp.route('/me/toggle_invasion', methods=['POST']) # insight skill
@auth_required(request)
@arc_try
def toggle_invasion(user_id):
with Connect() as c:
user = UserOnline(c, user_id)
user.toggle_invasion()
return success_return({'user_id': user.user_id, 'insight_state': user.insight_state})
@bp.route('/me/character', methods=['POST']) # 角色切换
@auth_required(request)
@arc_try
@@ -163,6 +176,7 @@ def sys_set(user_id, set_arg):
@bp.route('/me/request_delete', methods=['POST']) # 删除账号
@bp2.route('/me/request_delete', methods=['POST'])
@auth_required(request)
@arc_try
def user_delete(user_id):
@@ -173,6 +187,14 @@ def user_delete(user_id):
@bp.route('/email/resend_verify', methods=['POST']) # 邮箱验证重发
@bp2.route('/email/resend_verify', methods=['POST'])
@arc_try
def email_resend_verify():
raise ArcError('Email verification unavailable.', 151, status=404)
@bp.route('/verify', methods=['POST']) # 邮箱验证状态查询
@bp2.route('/verify', methods=['POST'])
@arc_try
def email_verify():
raise ArcError('Email verification unavailable.', 151, status=404)

View File

@@ -16,6 +16,7 @@
<option value="1">Present</option>
<option value="2">Future</option>
<option value="3">Beyond</option>
<option value="4">Eternal</option>
</select>
<br />

View File

@@ -15,6 +15,7 @@
<option value="1">Present</option>
<option value="2">Future</option>
<option value="3">Beyond</option>
<option value="4">Eternal</option>
</select>
<br />
<input type="submit" value="Find">
@@ -33,6 +34,8 @@
<span class="difficulty_prs">PRS</span>
{% elif difficulty == 2 %}
<span class="difficulty_ftr">FTR</span>
{% elif difficulty == 4 %}
<span class="difficulty_etr">ETR</span>
{% else %}
<span class="difficulty_byd">BYD</span>

View File

@@ -25,6 +25,8 @@
<span class="difficulty_prs">PRS</span>
{% elif post['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% elif post['difficulty'] == 4 %}
<span class="difficulty_etr">ETR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}

View File

@@ -44,6 +44,8 @@
<span class="difficulty_prs">PRS</span>
{% elif user['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% elif user['difficulty'] == 4 %}
<span class="difficulty_etr">ETR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}
@@ -176,6 +178,8 @@
<span class="difficulty_prs">PRS</span>
{% elif i['difficulty'] == 2 %}
<span class="difficulty_ftr">FTR</span>
{% elif i['difficulty'] == 4 %}
<span class="difficulty_etr">ETR</span>
{% else %}
<span class="difficulty_byd">BYD</span>
{% endif %}

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']
'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']
return render_template('web/changechar.html', skill_ids=skill_ids)