mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-09 01:07:27 +08:00
[Enhance][Bug fix] Fatalis values & Salt skill
- For Arcaea 6.2.3
- Add support for `skill_salt`.
- Add support for dynamic values of "Hikari (Fatalis)", which is depended by world mode total steps.
- Fix a bug that the character "Hikari (Fatalis)" cannot be used in world mode. (due to 3f5281582c)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from .config_manager import Config
|
||||
|
||||
ARCAEA_SERVER_VERSION = 'v2.12.0.3'
|
||||
ARCAEA_DATABASE_VERSION = 'v2.12.0.3'
|
||||
ARCAEA_SERVER_VERSION = 'v2.12.0.4'
|
||||
ARCAEA_DATABASE_VERSION = 'v2.12.0.4'
|
||||
ARCAEA_LOG_DATBASE_VERSION = 'v1.1'
|
||||
|
||||
|
||||
@@ -36,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
|
||||
|
||||
@@ -72,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]
|
||||
|
||||
@@ -372,6 +372,7 @@ 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()
|
||||
@@ -383,16 +384,13 @@ class UserPlay(UserScore):
|
||||
|
||||
elif 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
|
||||
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
|
||||
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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
@@ -730,6 +730,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表的某个属性
|
||||
|
||||
@@ -5,15 +5,22 @@ from random import randint
|
||||
from time import time
|
||||
|
||||
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()
|
||||
|
||||
@@ -471,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,
|
||||
@@ -480,6 +516,7 @@ class WorldSkillMixin:
|
||||
'skill_hikari_vanessa': self._skill_hikari_vanessa,
|
||||
'skill_mithra': self._skill_mithra,
|
||||
'skill_chinatsu': self._skill_chinatsu,
|
||||
'skill_salt': self._skill_salt,
|
||||
}
|
||||
if self.user_play.beyond_gauge == 0 and self.character_used.character_id == 35 and self.character_used.skill_id_displayed:
|
||||
self._special_tempest()
|
||||
@@ -670,6 +707,26 @@ class WorldSkillMixin:
|
||||
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
|
||||
|
||||
self.character_bonus_progress_normalized = 10 * (count / total)
|
||||
|
||||
|
||||
class BaseWorldPlay(WorldSkillMixin):
|
||||
'''
|
||||
@@ -822,6 +879,9 @@ class BaseWorldPlay(WorldSkillMixin):
|
||||
|
||||
self.user.current_map.update()
|
||||
|
||||
# 更新用户完成情况
|
||||
self.user.update_user_world_complete_info()
|
||||
|
||||
def update(self) -> None:
|
||||
'''世界模式更新'''
|
||||
self.before_update()
|
||||
|
||||
Reference in New Issue
Block a user