[Refactor][Bug fix] World mode & skills

- Code refactor for world mode
- Try to fix some skills to keep it same with the official server (especially `ayu_uncap`), and adjust the order of World Mode progress calculation (maybe still not right).
- Uncap Kanae

Note: not ready for Kanae's uncapping skill (I do not know its actual effect.)
This commit is contained in:
Lost-MSth
2024-01-13 15:14:54 +08:00
parent de701b4d1b
commit 746712718b
8 changed files with 379 additions and 264 deletions

View File

@@ -12,7 +12,7 @@ class Config:
SONG_FILE_HASH_PRE_CALCULATE = True SONG_FILE_HASH_PRE_CALCULATE = True
GAME_API_PREFIX = '/evolution/23' GAME_API_PREFIX = '/akeome/24'
ALLOW_APPVERSION = [] # list[str] ALLOW_APPVERSION = [] # list[str]

View File

@@ -1,6 +1,6 @@
from .config_manager import Config from .config_manager import Config
ARCAEA_SERVER_VERSION = 'v2.11.2.7' ARCAEA_SERVER_VERSION = 'v2.11.3.2.test'
ARCAEA_LOG_DATBASE_VERSION = 'v1.1' ARCAEA_LOG_DATBASE_VERSION = 'v1.1'

View File

@@ -11,7 +11,7 @@ from .item import ItemCore
from .song import Chart from .song import Chart
from .sql import Connect, Query, Sql from .sql import Connect, Query, Sql
from .util import get_today_timestamp, md5 from .util import get_today_timestamp, md5
from .world import WorldPlay from .world import WorldPlay, BeyondWorldPlay
class Score: class Score:
@@ -500,7 +500,8 @@ class UserPlay(UserScore):
# 世界模式判断 # 世界模式判断
if self.is_world_mode: if self.is_world_mode:
self.world_play = WorldPlay(self.c, self.user, self) self.world_play = WorldPlay(
self.c, self.user, self) if self.beyond_gauge == 0 else BeyondWorldPlay(self.c, self.user, self)
self.world_play.update() self.world_play.update()
# 课题模式判断 # 课题模式判断

View File

@@ -1,7 +1,7 @@
import os import os
from functools import lru_cache from functools import lru_cache
from json import load from json import load
from random import random from random import randint
from time import time from time import time
from .character import Character from .character import Character
@@ -438,226 +438,15 @@ class UserStamina(Stamina):
'a': self.stamina, 'b': self.max_stamina_ts, 'c': self.user.user_id}) 'a': self.stamina, 'b': self.max_stamina_ts, 'c': self.user.user_id})
class WorldPlay: class WorldSkillMixin:
'''
世界模式打歌类处理特殊角色技能联动UserMap和UserPlay
parameter: `user` - `UserOnline`类或子类的实例
'user_play` - `UserPlay`类的实例
'''
def __init__(self, c=None, user=None, user_play=None) -> None:
self.c = c
self.user = user
self.user_play = user_play
self.character_used = None
self.base_step_value: float = None
self.step_value: float = None
self.prog_tempest: float = None
self.character_bonus_progress: float = None
self.prog_skill_increase: float = None
self.over_skill_increase: float = None
def to_dict(self) -> dict:
arcmap: 'UserMap' = self.user.current_map
r = {
"rewards": arcmap.rewards_for_climbing_to_dict(),
"exp": self.character_used.level.exp,
"level": self.character_used.level.level,
"base_progress": self.base_step_value,
"progress": self.step_value,
"user_map": {
"user_id": self.user.user_id,
"curr_position": arcmap.curr_position,
"curr_capture": arcmap.curr_capture,
"is_locked": arcmap.is_locked,
"map_id": arcmap.map_id,
"prev_capture": arcmap.prev_capture,
"prev_position": arcmap.prev_position,
"beyond_health": arcmap.beyond_health
},
"char_stats": {
"character_id": self.character_used.character_id,
"frag": self.character_used.frag.get_value(self.character_used.level),
"prog": self.character_used.prog.get_value(self.character_used.level),
"overdrive": self.character_used.overdrive.get_value(self.character_used.level)
},
"current_stamina": self.user.stamina.stamina,
"max_stamina_ts": self.user.stamina.max_stamina_ts,
'world_mode_locked_end_ts': self.user.world_mode_locked_end_ts,
'beyond_boost_gauge': self.user.beyond_boost_gauge
}
if self.over_skill_increase is not None:
r['char_stats']['over_skill_increase'] = self.over_skill_increase
if self.prog_skill_increase is not None:
r['char_stats']['prog_skill_increase'] = self.prog_skill_increase
if self.prog_tempest is not None:
r['char_stats']['prog'] += self.prog_tempest # 没试过要不要这样
r['char_stats']['prog_tempest'] = self.prog_tempest
if self.character_bonus_progress is not None:
# 猜的,为了让客户端正确显示
r['progress'] -= self.character_bonus_progress
r['character_bonus_progress'] = self.character_bonus_progress
if self.character_used.skill_id_displayed == 'skill_maya':
r['char_stats']['skill_state'] = self.character_used.skill_state
if self.user_play.beyond_gauge == 0:
r["user_map"]["steps"] = [
x.to_dict() for x in arcmap.steps_for_climbing]
else:
r["user_map"]["steps"] = len(arcmap.steps_for_climbing)
if self.user_play.stamina_multiply != 1:
r['stamina_multiply'] = self.user_play.stamina_multiply
if self.user_play.fragment_multiply != 100:
r['fragment_multiply'] = self.user_play.fragment_multiply
if self.user_play.prog_boost_multiply != 0:
r['prog_boost_multiply'] = self.user_play.prog_boost_multiply
if self.user_play.beyond_boost_gauge_usage != 0:
r['beyond_boost_gauge_usage'] = self.user_play.beyond_boost_gauge_usage
return r
@property
def beyond_boost_gauge_addition(self) -> float:
# guessed by Lost-MSth
return 2.45 * self.user_play.rating ** 0.5 + 27
@property
def step_times(self) -> float:
prog_boost_multiply = self.user_play.prog_boost_multiply + 100
beyond_boost_times = 1
if self.user_play.beyond_gauge == 1:
if prog_boost_multiply > 100:
prog_boost_multiply -= 100
if self.user_play.beyond_boost_gauge_usage == 100:
beyond_boost_times = 2
elif self.user_play.beyond_boost_gauge_usage == 200:
beyond_boost_times = 3
return self.user_play.stamina_multiply * self.user_play.fragment_multiply / 100 * prog_boost_multiply / 100 * beyond_boost_times
@property
def exp_times(self) -> float:
prog_boost_multiply = self.user_play.prog_boost_multiply + 100
beyond_boost_times = 1
if self.user_play.beyond_gauge == 1:
if prog_boost_multiply > 100:
prog_boost_multiply -= 100
if self.user_play.beyond_boost_gauge_usage == 100:
beyond_boost_times = 2
elif self.user_play.beyond_boost_gauge_usage == 200:
beyond_boost_times = 3
return self.user_play.stamina_multiply * prog_boost_multiply / 100 * beyond_boost_times
def get_step(self) -> None:
if self.user_play.beyond_gauge == 0:
self.base_step_value = 2.5 + 2.45 * self.user_play.rating**0.5
prog = self.character_used.prog.get_value(
self.character_used.level)
if self.prog_tempest:
prog += self.prog_tempest
if self.prog_skill_increase:
prog += self.prog_skill_increase
self.step_value = self.base_step_value * prog / 50 * self.step_times
else:
if self.user_play.clear_type == 0:
self.base_step_value = 25/28 + \
(self.user_play.rating)**0.5 * 0.43
else:
self.base_step_value = 75/28 + \
(self.user_play.rating)**0.5 * 0.43
if self.character_used.character_id in self.user.current_map.character_affinity:
affinity_multiplier = self.user.current_map.affinity_multiplier[self.user.current_map.character_affinity.index(
self.character_used.character_id)]
else:
affinity_multiplier = 1
overdrive = self.character_used.overdrive.get_value(
self.character_used.level)
if self.over_skill_increase:
overdrive += self.over_skill_increase
self.step_value = self.base_step_value * overdrive / \
50 * self.step_times * affinity_multiplier
def update(self) -> None:
'''世界模式更新'''
if self.user_play.prog_boost_multiply != 0:
self.user.update_user_one_column('prog_boost', 0)
self.user_play.clear_play_state()
self.user.select_user_about_world_play()
if self.user_play.beyond_gauge == 0:
# 更新byd大招蓄力条
self.user.beyond_boost_gauge += self.beyond_boost_gauge_addition
self.user.beyond_boost_gauge = min(
self.user.beyond_boost_gauge, 200)
self.user.update_user_one_column(
'beyond_boost_gauge', self.user.beyond_boost_gauge)
elif self.user_play.beyond_boost_gauge_usage != 0 and self.user_play.beyond_boost_gauge_usage <= self.user.beyond_boost_gauge:
self.user.beyond_boost_gauge -= self.user_play.beyond_boost_gauge_usage
if abs(self.user.beyond_boost_gauge) <= 1e-5:
self.user.beyond_boost_gauge = 0
self.user.update_user_one_column(
'beyond_boost_gauge', self.user.beyond_boost_gauge)
self.character_used = Character()
self.user.character.select_character_info()
if not self.user.is_skill_sealed:
self.character_used = self.user.character
else:
self.character_used.character_id = self.user.character.character_id
self.character_used.level.level = self.user.character.level.level
self.character_used.level.exp = self.user.character.level.exp
self.character_used.frag.set_parameter(50, 50, 50)
self.character_used.prog.set_parameter(50, 50, 50)
self.character_used.overdrive.set_parameter(50, 50, 50)
self.user.current_map.select_map_info()
self.before_calculate()
self.get_step()
self.user.current_map.climb(self.step_value)
self.after_climb()
for i in self.user.current_map.rewards_for_climbing: # 物品分发
for j in i['items']:
j.c = self.c
j.user_claim_item(self.user)
x: 'Step' = self.user.current_map.steps_for_climbing[-1]
if x.step_type:
if 'plusstamina' in x.step_type and x.plus_stamina_value:
# 体力格子
self.user.stamina.stamina += x.plus_stamina_value
self.user.stamina.update()
# 角色升级
if self.character_used.database_table_name == 'user_char':
self.character_used.upgrade(
self.user, self.exp_times*self.user_play.rating*6)
if self.user.current_map.curr_position == self.user.current_map.step_count-1 and self.user.current_map.is_repeatable: # 循环图判断
self.user.current_map.curr_position = 0
self.user.current_map.update()
def before_calculate(self) -> None: def before_calculate(self) -> None:
factory_dict = {'skill_vita': self._skill_vita, 'skill_mika': self._skill_mika, 'skill_ilith_ivy': self._skill_ilith_ivy, factory_dict = {'skill_vita': self._skill_vita,
'ilith_awakened_skill': self._ilith_awakened_skill, 'skill_hikari_vanessa': self._skill_hikari_vanessa} 'skill_mika': self._skill_mika,
'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
}
if self.user_play.beyond_gauge == 0 and self.character_used.character_id == 35 and self.character_used.skill_id_displayed: if self.user_play.beyond_gauge == 0 and self.character_used.character_id == 35 and self.character_used.skill_id_displayed:
self._special_tempest() self._special_tempest()
@@ -665,8 +454,14 @@ class WorldPlay:
factory_dict[self.character_used.skill_id_displayed]() factory_dict[self.character_used.skill_id_displayed]()
def after_climb(self) -> None: def after_climb(self) -> None:
factory_dict = {'eto_uncap': self._eto_uncap, 'ayu_uncap': self._ayu_uncap, factory_dict = {'eto_uncap': self._eto_uncap,
'luna_uncap': self._luna_uncap, 'skill_fatalis': self._skill_fatalis, 'skill_amane': self._skill_amane, 'skill_maya': self._skill_maya, 'skill_mithra': self._skill_mithra} 'ayu_uncap': self._ayu_uncap,
'skill_fatalis': self._skill_fatalis,
'skill_amane': self._skill_amane,
'skill_maya': self._skill_maya,
'luna_uncap': self._luna_uncap,
'skill_kanae_uncap': self._skill_kanae_uncap
}
if self.character_used.skill_id_displayed in factory_dict: if self.character_used.skill_id_displayed in factory_dict:
factory_dict[self.character_used.skill_id_displayed]() factory_dict[self.character_used.skill_id_displayed]()
@@ -706,32 +501,27 @@ class WorldPlay:
break break
if fragment_flag: if fragment_flag:
self.character_bonus_progress = Constant.ETO_UNCAP_BONUS_PROGRESS self.character_bonus_progress_normalized = Constant.ETO_UNCAP_BONUS_PROGRESS
self.step_value += self.character_bonus_progress
self.user.current_map.reclimb(self.step_value) self.user.current_map.reclimb(self.final_progress)
def _luna_uncap(self) -> None: def _luna_uncap(self) -> None:
'''luna觉醒技能限制格开始时世界模式进度加7''' '''luna觉醒技能限制格开始时世界模式进度加 7偷懒重爬因为 map 信息还未获取)'''
x: 'Step' = self.user.current_map.steps_for_climbing[0] x: 'Step' = self.user.current_map.steps_for_climbing[0]
if x.restrict_id and x.restrict_type: if x.restrict_id and x.restrict_type:
self.character_bonus_progress = Constant.LUNA_UNCAP_BONUS_PROGRESS self.self.character_bonus_progress_normalized = Constant.LUNA_UNCAP_BONUS_PROGRESS
self.step_value += self.character_bonus_progress self.user.current_map.reclimb(self.final_progress)
self.user.current_map.reclimb(self.step_value)
def _ayu_uncap(self) -> None: def _ayu_uncap(self) -> None:
'''ayu觉醒技能世界模式进度+5或-5但不会小于0''' '''ayu 觉醒技能,世界模式进度随机变动 [-5, -5],但不会小于 0'''
self.character_bonus_progress = Constant.AYU_UNCAP_BONUS_PROGRESS if random( self.character_bonus_progress_normalized = randint(
) >= 0.5 else -Constant.AYU_UNCAP_BONUS_PROGRESS -Constant.AYU_UNCAP_BONUS_PROGRESS, Constant.AYU_UNCAP_BONUS_PROGRESS)
self.step_value += self.character_bonus_progress if self.progress_normalized + self.character_bonus_progress_normalized < 0:
if self.step_value < 0: self.character_bonus_progress_normalized = -self.progress_normalized
self.character_bonus_progress += self.step_value
self.step_value = 0
self.user.current_map.reclimb(self.step_value) self.user.current_map.reclimb(self.final_progress)
def _skill_fatalis(self) -> None: def _skill_fatalis(self) -> None:
'''hikari fatalis技能世界模式超载打完休息60分钟''' '''hikari fatalis技能世界模式超载打完休息60分钟'''
@@ -743,13 +533,11 @@ class WorldPlay:
def _skill_amane(self) -> None: def _skill_amane(self) -> None:
''' '''
amane技能起始格为限速或随机成绩小于EX时世界模式进度减半 amane技能起始格为限速或随机成绩小于EX时世界模式进度减半
偷懒在after_climb里面需要重爬一次
''' '''
x: 'Step' = self.user.current_map.steps_for_climbing[0] x: 'Step' = self.user.current_map.steps_for_climbing[0]
if ('randomsong' in x.step_type or 'speedlimit' in x.step_type) and self.user_play.song_grade < 5: if ('randomsong' in x.step_type or 'speedlimit' in x.step_type) and self.user_play.song_grade < 5:
self.character_bonus_progress = -self.step_value / 2 self.character_bonus_progress_normalized = -self.progress_normalized / 2
self.step_value = self.step_value / 2 self.user.current_map.reclimb(self.final_progress)
self.user.current_map.reclimb(self.step_value)
def _ilith_awakened_skill(self) -> None: def _ilith_awakened_skill(self) -> None:
''' '''
@@ -773,9 +561,7 @@ class WorldPlay:
mithra 技能,每 150 combo 增加世界模式进度+1 mithra 技能,每 150 combo 增加世界模式进度+1
''' '''
if self.user_play.combo_interval_bonus: if self.user_play.combo_interval_bonus:
self.character_bonus_progress = self.user_play.combo_interval_bonus self.character_bonus_progress_normalized = self.user_play.combo_interval_bonus
self.step_value += self.character_bonus_progress
self.user.current_map.reclimb(self.step_value)
def _skill_ilith_ivy(self) -> None: def _skill_ilith_ivy(self) -> None:
''' '''
@@ -804,7 +590,292 @@ class WorldPlay:
maya 技能skill_flag 为 1 时,世界模式进度翻倍 maya 技能skill_flag 为 1 时,世界模式进度翻倍
''' '''
if self.character_used.skill_flag: if self.character_used.skill_flag:
self.character_bonus_progress = self.step_value self.character_bonus_progress_normalized = self.progress_normalized
self.step_value += self.character_bonus_progress self.user.current_map.reclimb(self.final_progress)
self.user.current_map.reclimb(self.step_value)
self.character_used.change_skill_state() self.character_used.change_skill_state()
def _skill_kanae_uncap(self) -> None:
'''
kanae 觉醒技能,保存世界模式 progress 并在下次结算
'''
pass
class BaseWorldPlay(WorldSkillMixin):
'''
世界模式打歌类处理特殊角色技能联动UserMap和UserPlay
parameter: `user` - `UserOnline`类或子类的实例
'user_play` - `UserPlay`类的实例
'''
def __init__(self, c=None, user=None, user_play=None) -> None:
self.c = c
self.user = user
self.user_play = user_play
self.character_used = None
self.progress_normalized: float = None
self.character_bonus_progress_normalized: float = None
# kanae_stored_progress: float
# kanae_added_progress: float
# partner_multiply: float
# wpaid: str
def to_dict(self) -> dict:
arcmap: 'UserMap' = self.user.current_map
r = {
"rewards": arcmap.rewards_for_climbing_to_dict(),
"exp": self.character_used.level.exp,
"level": self.character_used.level.level,
"base_progress": self.base_progress,
"progress": self.final_progress,
"user_map": {
"user_id": self.user.user_id,
"curr_position": arcmap.curr_position,
"curr_capture": arcmap.curr_capture,
"is_locked": arcmap.is_locked,
"map_id": arcmap.map_id,
"prev_capture": arcmap.prev_capture,
"prev_position": arcmap.prev_position,
"beyond_health": arcmap.beyond_health
},
"char_stats": {
"character_id": self.character_used.character_id,
"frag": self.character_used.frag.get_value(self.character_used.level),
"prog": self.character_used.prog.get_value(self.character_used.level),
"overdrive": self.character_used.overdrive.get_value(self.character_used.level)
},
"current_stamina": self.user.stamina.stamina,
"max_stamina_ts": self.user.stamina.max_stamina_ts,
'world_mode_locked_end_ts': self.user.world_mode_locked_end_ts,
'beyond_boost_gauge': self.user.beyond_boost_gauge,
# 'kanae_stored_progress': 7114000, # 往群愿里塞
# 'kanae_added_progress': 514000, # 群愿往外拿
# 'wpaid': 'helloworld', # world play id ???
# 'partner_multiply': 456, #
}
if self.character_used.skill_id_displayed == 'skill_maya':
r['char_stats']['skill_state'] = self.character_used.skill_state
if self.user_play.stamina_multiply != 1:
r['stamina_multiply'] = self.user_play.stamina_multiply
if self.user_play.fragment_multiply != 100:
r['fragment_multiply'] = self.user_play.fragment_multiply
if self.user_play.prog_boost_multiply != 0: # 源韵强化
r['prog_boost_multiply'] = self.user_play.prog_boost_multiply
return r
@property
def beyond_boost_gauge_addition(self) -> float:
# guessed by Lost-MSth
return 2.45 * self.user_play.rating ** 0.5 + 27
@property
def step_times(self) -> float:
raise NotImplementedError
@property
def exp_times(self) -> float:
return self.user_play.stamina_multiply * (self.user_play.prog_boost_multiply / 100 + 1)
@property
def character_bonus_progress(self) -> float:
return self.character_bonus_progress_normalized * self.step_times
@property
def base_progress(self) -> float:
raise NotImplementedError
@property
def final_progress(self) -> float:
raise NotImplementedError
def get_step(self) -> None:
# to get self.progress_normalized
raise NotImplementedError
def update(self) -> None:
'''世界模式更新'''
if self.user_play.prog_boost_multiply != 0:
self.user.update_user_one_column('prog_boost', 0)
self.user_play.clear_play_state()
self.user.select_user_about_world_play()
self.character_used = Character()
self.user.character.select_character_info()
if not self.user.is_skill_sealed:
self.character_used = self.user.character
else:
self.character_used.character_id = self.user.character.character_id
self.character_used.level.level = self.user.character.level.level
self.character_used.level.exp = self.user.character.level.exp
self.character_used.frag.set_parameter(50, 50, 50)
self.character_used.prog.set_parameter(50, 50, 50)
self.character_used.overdrive.set_parameter(50, 50, 50)
self.user.current_map.select_map_info()
self.before_calculate()
self.get_step()
self.user.current_map.climb(self.final_progress)
self.after_climb()
for i in self.user.current_map.rewards_for_climbing: # 物品分发
for j in i['items']:
j.c = self.c
j.user_claim_item(self.user)
x: 'Step' = self.user.current_map.steps_for_climbing[-1]
if x.step_type:
if 'plusstamina' in x.step_type and x.plus_stamina_value:
# 体力格子
self.user.stamina.stamina += x.plus_stamina_value
self.user.stamina.update()
# 角色升级
if self.character_used.database_table_name == 'user_char':
self.character_used.upgrade(
self.user, self.exp_times*self.user_play.rating*6)
if self.user.current_map.curr_position == self.user.current_map.step_count-1 and self.user.current_map.is_repeatable: # 循环图判断
self.user.current_map.curr_position = 0
self.user.current_map.update()
class WorldPlay(BaseWorldPlay):
def __init__(self, c=None, user=None, user_play=None) -> None:
super().__init__(c, user, user_play)
self.prog_tempest: float = None
self.prog_skill_increase: float = None
def to_dict(self) -> dict:
r = super().to_dict()
# 基础进度加上搭档倍数 不带 character_bonus_progress 但是带 kanae 技能
r['progress_partial_after_stat'] = self.progress_normalized
if self.character_bonus_progress_normalized is not None:
r['character_bonus_progress'] = self.character_bonus_progress_normalized
# 不懂为什么两个玩意一样
r['character_bonus_progress_normalized'] = self.character_bonus_progress_normalized
if self.prog_skill_increase is not None:
r['char_stats']['prog_skill_increase'] = self.prog_skill_increase
if self.prog_tempest is not None:
r['char_stats']['prog'] += self.prog_tempest # 没试过要不要这样
r['char_stats']['prog_tempest'] = self.prog_tempest
r['partner_adjusted_prog'] = self.partner_adjusted_prog
r["user_map"]["steps"] = [x.to_dict()
for x in self.user.current_map.steps_for_climbing]
return r
@property
def step_times(self) -> float:
return self.user_play.stamina_multiply * self.user_play.fragment_multiply / 100 * (self.user_play.prog_boost_multiply / 100 + 1)
@property
def character_bonus_progress(self) -> float:
return self.character_bonus_progress_normalized * self.step_times
@property
def base_progress(self) -> float:
return 2.5 + 2.45 * self.user_play.rating**0.5
@property
def final_progress(self) -> float:
return (self.progress_normalized + (self.character_bonus_progress_normalized or 0)) * self.step_times
@property
def partner_adjusted_prog(self) -> float:
prog = self.character_used.prog.get_value(
self.character_used.level)
if self.prog_tempest:
prog += self.prog_tempest
if self.prog_skill_increase:
prog += self.prog_skill_increase
return prog
def get_step(self) -> None:
self.progress_normalized = self.base_progress * \
(self.partner_adjusted_prog / 50)
def update(self) -> None:
'''世界模式更新'''
super().update()
# 更新byd大招蓄力条
self.user.beyond_boost_gauge += self.beyond_boost_gauge_addition
self.user.beyond_boost_gauge = min(self.user.beyond_boost_gauge, 200)
self.user.update_user_one_column(
'beyond_boost_gauge', self.user.beyond_boost_gauge)
class BeyondWorldPlay(BaseWorldPlay):
def __init__(self, c=None, user=None, user_play=None) -> None:
super().__init__(c, user, user_play)
self.over_skill_increase: float = None
@property
def step_times(self) -> float:
return self.user_play.stamina_multiply * self.user_play.fragment_multiply / 100 * (1 + self.user_play.prog_boost_multiply / 100 + self.user_play.beyond_boost_gauge_usage / 100)
@property
def affinity_multiplier(self) -> float:
if self.user.current_map.character_affinity is not None and self.character_used.character_id is not None and self.character_used.character_id in self.user.current_map.character_affinity:
return self.user.current_map.affinity_multiplier[self.user.current_map.character_affinity.index(self.character_used.character_id)]
return 1
@property
def base_progress(self) -> float:
return self.user_play.rating**0.5 * 0.43 + (25/28 if self.user_play.clear_type == 0 else 75/28)
@property
def final_progress(self) -> float:
return self.progress_normalized * self.step_times
def to_dict(self) -> dict:
r = super().to_dict()
# byd 进度 没有加上源韵强化 和 boost 的数值
r['pre_boost_progress'] = self.progress_normalized * \
self.user_play.fragment_multiply / 100
if self.over_skill_increase is not None:
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
return r
def update(self) -> None:
super().update()
if self.user_play.beyond_boost_gauge_usage != 0 and self.user_play.beyond_boost_gauge_usage <= self.user.beyond_boost_gauge:
self.user.beyond_boost_gauge -= self.user_play.beyond_boost_gauge_usage
if abs(self.user.beyond_boost_gauge) <= 1e-5:
self.user.beyond_boost_gauge = 0
self.user.update_user_one_column(
'beyond_boost_gauge', self.user.beyond_boost_gauge)
def get_step(self) -> None:
overdrive = self.character_used.overdrive.get_value(
self.character_used.level)
if self.over_skill_increase:
overdrive += self.over_skill_increase
self.progress_normalized = self.base_progress * \
(overdrive / 50) * self.affinity_multiplier

View File

@@ -6,7 +6,7 @@ class InitData:
'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'] '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_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', 'ilith_awakened_skill', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee', skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', 'ilith_awakened_skill', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'skill_luin_uncap'] '', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', 'skill_kanae_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, 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, 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]
@@ -30,13 +30,13 @@ class InitData:
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] 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]
frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62, frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62,
65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 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] 65, 85, 67, 88, 74, 0.5, 105, 80, 105, 50, 80, 87, 71, 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]
prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83, prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 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] 80, 90, 93, 50, 96, 88, 99, 108, 85, 80, 50, 64, 55, 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]
overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64, overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64,
56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 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] 56, 73, 95, 67, 84, 80, 88, 79, 80, 60, 80, 80, 63, 25, 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]
char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1, 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, 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]
@@ -61,14 +61,15 @@ class InitData:
10: [{'core_id': 'core_umbral', 'amount': 30}], 10: [{'core_id': 'core_umbral', 'amount': 30}],
66: [{'core_id': 'core_chunithm', 'amount': 15}], 66: [{'core_id': 'core_chunithm', 'amount': 15}],
5: [{'core_id': 'core_hollow', 'amount': 0}], 5: [{'core_id': 'core_hollow', 'amount': 0}],
73: [{'core_id': 'core_wacca', 'amount': 15}] 73: [{'core_id': 'core_wacca', 'amount': 15}],
30: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_sunset', 'amount': 25}]
} }
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson', 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_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase', 'core_umbral', 'core_wacca', 'core_sunset']
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", 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'] "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']
world_unlocks = ["scenery_chap1", "scenery_chap2", world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"] "scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]

View File

@@ -1558,5 +1558,41 @@
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100
},
{
"name": "overdrive",
"items": [
{
"type": "single",
"id": "overdrive",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "lightmyway",
"items": [
{
"type": "single",
"id": "lightmyway",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
} }
] ]

View File

@@ -60,6 +60,12 @@ def finale_end():
return success_return({}) return success_return({})
@bp.route('/applog/me', methods=['POST'])
def applog_me():
# 异常日志,不处理
return success_return({})
map_dict = {'/user/me': user_me, map_dict = {'/user/me': user_me,
'/purchase/bundle/pack': bundle_pack, '/purchase/bundle/pack': bundle_pack,
'/serve/download/me/song': download_song, '/serve/download/me/song': download_song,
@@ -74,9 +80,9 @@ map_dict = {'/user/me': user_me,
@bp.route('/compose/aggregate', methods=['GET']) # 集成式请求 @bp.route('/compose/aggregate', methods=['GET']) # 集成式请求
def aggregate(): def aggregate():
try: try:
#global request # global request
finally_response = {'success': True, 'value': []} finally_response = {'success': True, 'value': []}
#request_ = request # request_ = request
get_list = json.loads(request.args.get('calls')) get_list = json.loads(request.args.get('calls'))
if len(get_list) > 10: if len(get_list) > 10:
# 请求太多驳回 # 请求太多驳回
@@ -101,13 +107,13 @@ def aggregate():
'error_code'), 'id': i['id']} 'error_code'), 'id': i['id']}
if "extra" in resp: if "extra" in resp:
finally_response['extra'] = resp['extra'] finally_response['extra'] = resp['extra']
#request = request_ # request = request_
return jsonify(finally_response) return jsonify(finally_response)
finally_response['value'].append( finally_response['value'].append(
{'id': i.get('id'), 'value': resp['value'] if hasattr(resp, 'get') else resp}) {'id': i.get('id'), 'value': resp['value'] if hasattr(resp, 'get') else resp})
#request = request_ # request = request_
return jsonify(finally_response) return jsonify(finally_response)
except KeyError: except KeyError:
return error_return() return error_return()

View File

@@ -422,7 +422,7 @@ def all_character():
def change_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', 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'] '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']
return render_template('web/changechar.html', skill_ids=skill_ids) return render_template('web/changechar.html', skill_ids=skill_ids)