diff --git a/core/character.py b/core/character.py index 77f7f09..d706503 100644 --- a/core/character.py +++ b/core/character.py @@ -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() if self.character_id == 72: self.update_insight_state() @@ -323,7 +343,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 diff --git a/core/constant.py b/core/constant.py index ffdbe70..a553359 100644 --- a/core/constant.py +++ b/core/constant.py @@ -1,6 +1,6 @@ from .config_manager import Config -ARCAEA_SERVER_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' @@ -32,6 +32,7 @@ class Constant: SKILL_FATALIS_WORLD_LOCKED_TIME = 3600000 SKILL_MIKA_SONGS = ['aprilshowers', 'seventhsense', 'oshamascramble', 'breakbreak', 'straightintolights', 'virtus', 'yomibitoshirazu', 'amazingmightyyyy', 'cycles', 'maxrage', 'infinity', 'temptation'] + FATALIS_MAX_VALUE = 100 MAX_FRIEND_COUNT = Config.MAX_FRIEND_COUNT @@ -68,7 +69,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] diff --git a/core/score.py b/core/score.py index 4772ccb..fd91636 100644 --- a/core/score.py +++ b/core/score.py @@ -377,6 +377,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() @@ -387,16 +388,13 @@ class UserPlay(UserScore): self.invasion_flag = _flag 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: diff --git a/core/sql.py b/core/sql.py index 6852427..a0a2dbf 100644 --- a/core/sql.py +++ b/core/sql.py @@ -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) diff --git a/core/user.py b/core/user.py index dfc6bc6..c2e3d67 100644 --- a/core/user.py +++ b/core/user.py @@ -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: @@ -741,6 +741,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表的某个属性 diff --git a/database/init/arc_data.py b/database/init/arc_data.py index 589930b..c3c28d0 100644 --- a/database/init/arc_data.py +++ b/database/init/arc_data.py @@ -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, 75, 54, 90, 41, 34, 30, 55, 66, 55, 62, 81, 44, 46] frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62, - 65, 95, 67, 88, 74, 0.5, 105, 80, 105, 50, 80, 87, 81, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 64, 100, 50, 58, 51, 40, 115, 80, 50, 61.6, 48, 37, 90, 60, 50, 102, 76, 0, 89] + 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] prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83, - 80, 100, 93, 50, 96, 88, 99, 108, 85, 80, 50, 64, 65, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 60, 53, 85, 58, 96, 47, 80, 90, 67, 41, 55, 50, 103, 66, 35, 62, 75, 0, 53] + 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] overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64, - 56, 73, 105, 67, 84, 80, 88, 79, 80, 60, 80, 80, 63, 35, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 64, 65.5, 59.5, 58, 96, 47, 75, 64, 90, 41, 34, 30, 55, 66, 55, 72, 91, 0, 56] + 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] char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 0, 2, 2, 1, 0, 2, 0, 4, 2, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 2] diff --git a/database/init/tables.sql b/database/init/tables.sql index a9af045..e5c74aa 100644 --- a/database/init/tables.sql +++ b/database/init/tables.sql @@ -283,6 +283,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; diff --git a/web/index.py b/web/index.py index 5a679a8..565efc8 100644 --- a/web/index.py +++ b/web/index.py @@ -443,7 +443,7 @@ def all_character(): def change_character(): # 修改角色数据 skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere', - 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa', 'skill_nami_twilight', 'skill_ilith_ivy', 'skill_hikari_vanessa', 'skill_maya', 'skill_luin', 'skill_luin_uncap', 'skill_kanae_uncap', 'skill_doroc_uncap', 'skill_saya_uncap', 'skill_luna_ilot', 'skill_eto_hoppe', 'skill_aichan', 'skill_nell', 'skill_chinatsu', 'skill_tsumugi', 'skill_nai', 'skill_selene'] + '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'] return render_template('web/changechar.html', skill_ids=skill_ids)