From 8e3b414094d1f896855995a572508c889ba51651 Mon Sep 17 00:00:00 2001 From: TommrraraSnow Date: Sat, 22 Nov 2025 17:07:29 +0800 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=B3=BB=E7=BB=9F=E4=BD=BF=E7=94=A8Pydantic=20Setting?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用pydantic-settings替换传统JSON配置管理 - 添加类型安全的配置模型类 - 支持环境变量和配置文件的双重重载 - 改进配置文件的初始化和验证逻辑 - 添加详细的类型注解提升代码质量 - 统一配置路径引用(config_path vs CONFIG_PATH) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- pyproject.toml | 1 + uv.lock | 16 ++ zsim/define.py | 301 ++++++++++++++++++++------- zsim/lib_webui/process_simulator.py | 20 +- zsim/page_simulator.py | 22 +- zsim/sim_progress/Buff/buff_class.py | 10 +- 6 files changed, 267 insertions(+), 103 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0ef2170f..fedc4613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ dependencies = [ "sqlalchemy>=2.0.43", "alembic>=1.16.5", "greenlet>=3.0.3", + "pydantic-settings>=2.12.0", ] [tool.ruff] diff --git a/uv.lock b/uv.lock index 9a87e9cc..873f05d6 100644 --- a/uv.lock +++ b/uv.lock @@ -1321,6 +1321,20 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload-time = "2025-04-02T09:48:17.97Z" }, ] +[[package]] +name = "pydantic-settings" +version = "2.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, +] + [[package]] name = "pydeck" version = "0.9.1" @@ -2162,6 +2176,7 @@ dependencies = [ { name = "polars" }, { name = "psutil" }, { name = "pydantic" }, + { name = "pydantic-settings" }, { name = "pywebview" }, { name = "setuptools" }, { name = "sqlalchemy" }, @@ -2209,6 +2224,7 @@ requires-dist = [ { name = "polars", specifier = ">=1.28.1" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pydantic", specifier = ">=2.11.3" }, + { name = "pydantic-settings", specifier = ">=2.12.0" }, { name = "pywebview", specifier = ">=5.4" }, { name = "pywin32", marker = "extra == 'windows'", specifier = ">=308" }, { name = "setuptools", specifier = "~=75.1.0" }, diff --git a/zsim/define.py b/zsim/define.py index 4345a207..76839d93 100644 --- a/zsim/define.py +++ b/zsim/define.py @@ -3,7 +3,16 @@ import sys import tomllib from pathlib import Path -from typing import Callable, Literal +from typing import Any, Callable, ClassVar, Literal, cast + +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_pascal +from pydantic_settings import ( + BaseSettings, + JsonConfigSettingsSource, + PydanticBaseSettingsSource, + SettingsConfigDict, +) # 属性类型: ElementType = Literal[0, 1, 2, 3, 4, 5, 6] @@ -15,18 +24,155 @@ results_dir = "results/" -# 加载角色配置 -# 处理打包后的资源路径 +data_dir = Path("./zsim/data") +config_path = Path("./zsim/config.json") + + +class DebugConfig(BaseModel): + enabled: bool = True + level: int = 4 + check_skill_mul: bool = False + check_skill_mul_tag: list[str] = Field(default_factory=list) + + +class WatchdogConfig(BaseModel): + enabled: bool = False + level: int = 4 + + +class CharacterConfig(BaseModel): + crit_balancing: bool = True + back_attack_rate: float = 1.0 + + +class EnemyConfig(BaseModel): + index_id: int = Field(alias="index_ID") + adjust_id: int = Field(alias="adjust_ID") + difficulty: float + + +class AplModeConfig(BaseModel): + enabled: bool = True + na_order: str + enemy_random_attack: bool = False + enemy_regular_attack: bool = False + enemy_attack_response: bool = False + enemy_attack_method_config: str + enemy_attack_action_data: str + enemy_attack_report: bool = True + player_level: int = 5 + default_apl_dir: str + custom_apl_dir: str + yanagi: str + hugo: str + alice: str + seed: str + perfect_player: bool = True + apl_thought_check: bool = False + apl_thought_check_window: list[int] = [0, 1] + + model_config = ConfigDict(populate_by_name=True, alias_generator=to_pascal) + + +class SwapCancelModeConfig(BaseModel): + enabled: bool = True + completion_coefficient: float + lag_time: float + debug: bool = False + debug_target_skill: str + + +class DatabaseConfig(BaseModel): + sqlite_path: str + character_data_path: str + weapon_data_path: str + equip_2pc_data_path: str + skill_data_path: str + enemy_data_path: str + enemy_adjustment_path: str + default_skill_path: str + judge_file_path: str + effect_file_path: str + exist_file_path: str + apl_file_path: str + + model_config = ConfigDict(populate_by_name=True, alias_generator=str.upper) + + +class Buff0ReportConfig(BaseModel): + enabled: bool = False + + model_config = ConfigDict() + + +class CharReportConfig(BaseModel): + vivian: bool + astra_yao: bool + hugo: bool + yixuan: bool + trigger: bool + jufufu: bool + yuzuha: bool + alice: bool + seed: bool + + model_config = ConfigDict(populate_by_name=True, alias_generator=to_pascal) + + +class NaModeLevelConfig(BaseModel): + hugo: int + + model_config = ConfigDict(populate_by_name=True, alias_generator=to_pascal) + + +class DevConfig(BaseModel): + new_sim_boot: bool = True + + +class Config(BaseSettings): + model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict( + json_file=config_path, + json_file_encoding="utf-8", + env_file=".env", + env_ignore_empty=True, + env_file_encoding="utf-8", + env_nested_delimiter="__", + env_prefix="ZSIM_", + populate_by_name=True, + ) + debug: DebugConfig + stop_tick: int = 10800 + watchdog: WatchdogConfig + character: CharacterConfig + enemy: EnemyConfig + apl_mode: AplModeConfig + swap_cancel_mode: SwapCancelModeConfig + database: DatabaseConfig + translate: dict[str, str] = {} + buff_0_report: Buff0ReportConfig + char_report: CharReportConfig + na_mode_level: NaModeLevelConfig + parallel_mode: dict[str, Any] = {} + dev: DevConfig = DevConfig() + + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> tuple[PydanticBaseSettingsSource, ...]: + return ( + init_settings, + JsonConfigSettingsSource(settings_cls), + env_settings, + dotenv_settings, + file_secret_settings, + ) -if getattr(sys, "frozen", False): - # 如果是打包后的可执行文件,直接从当前目录读取 - data_dir = Path("./zsim/data") - CONFIG_PATH = Path("./zsim/config.json") -else: - # 如果是开发环境 - data_dir = Path("./zsim/data") - CONFIG_PATH = Path("zsim/config.json") # 确保数据目录存在 data_dir.mkdir(exist_ok=True, parents=True) @@ -35,7 +181,7 @@ # 修复:将char_config_file作为参数传递给initialize_config_files -def initialize_config_files_with_paths(char_file, data_dir, config_path): +def initialize_config_files_with_paths(char_file: Path, data_dir: Path, config_path: Path): """ 初始化配置文件。 如果配置文件不存在,则从 _example 文件复制生成。 @@ -50,7 +196,7 @@ def initialize_config_files_with_paths(char_file, data_dir, config_path): print(f"已生成配置文件:{char_file}") # JSON config - def update_json_config(template: dict, user: dict) -> bool: + def update_json_config(template: dict[str, Any], user: dict[str, Any]) -> bool: """递归更新用户配置,返回是否被更新""" updated = False for key, value in template.items(): @@ -58,7 +204,7 @@ def update_json_config(template: dict, user: dict) -> bool: user[key] = value updated = True elif isinstance(value, dict) and isinstance(user.get(key), dict): - if update_json_config(value, user[key]): + if update_json_config(cast(dict[str, Any], value), user[key]): updated = True return updated @@ -78,7 +224,7 @@ def update_json_config(template: dict, user: dict) -> bool: # 使用新的函数 -initialize_config_files_with_paths(char_config_file, data_dir, CONFIG_PATH) +initialize_config_files_with_paths(char_config_file, data_dir, config_path) if char_config_file.exists(): with open(char_config_file, "rb") as f: saved_char_config = tomllib.load(f) @@ -86,33 +232,33 @@ def update_json_config(template: dict, user: dict) -> bool: raise FileNotFoundError(f"Character config file {char_config_file} not found.") # 确保配置文件目录存在 -CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True) -_config = json.load(open(CONFIG_PATH, encoding="utf-8-sig")) +config_path.parent.mkdir(exist_ok=True, parents=True) +config = Config() # type:ignore # 敌人配置 -ENEMY_INDEX_ID: int = _config["enemy"]["index_ID"] -ENEMY_ADJUST_ID: int = _config["enemy"]["adjust_ID"] -ENEMY_DIFFICULTY: float = _config["enemy"]["difficulty"] +ENEMY_INDEX_ID: int = config.enemy.index_id +ENEMY_ADJUST_ID: int = config.enemy.adjust_id +ENEMY_DIFFICULTY: float = config.enemy.difficulty # APL模式配置 -APL_MODE: bool = _config["apl_mode"]["enabled"] -SWAP_CANCEL: bool = _config["swap_cancel_mode"]["enabled"] -APL_PATH: str = _config["database"]["APL_FILE_PATH"] -APL_NA_ORDER_PATH: str = _config["apl_mode"]["na_order"] -ENEMY_RANDOM_ATTACK: bool = _config["apl_mode"]["enemy_random_attack"] -ENEMY_REGULAR_ATTACK: bool = _config["apl_mode"]["enemy_regular_attack"] +APL_MODE: bool = config.apl_mode.enabled +SWAP_CANCEL: bool = config.swap_cancel_mode.enabled +APL_PATH: str = config.database.apl_file_path +APL_NA_ORDER_PATH: str = config.apl_mode.na_order +ENEMY_RANDOM_ATTACK: bool = config.apl_mode.enemy_random_attack +ENEMY_REGULAR_ATTACK: bool = config.apl_mode.enemy_regular_attack if ENEMY_RANDOM_ATTACK and ENEMY_REGULAR_ATTACK: raise ValueError("不能同时开启“敌人随机进攻”与“敌人规律进攻”参数。") -ENEMY_ATTACK_RESPONSE: bool = _config["apl_mode"]["enemy_attack_response"] -ENEMY_ATTACK_METHOD_CONFIG: str = _config["apl_mode"]["enemy_attack_method_config"] -ENEMY_ATTACK_ACTION: str = _config["apl_mode"]["enemy_attack_action_data"] -ENEMY_ATTACK_REPORT: bool = _config["apl_mode"]["enemy_attack_report"] +ENEMY_ATTACK_RESPONSE: bool = config.apl_mode.enemy_attack_response +ENEMY_ATTACK_METHOD_CONFIG: str = config.apl_mode.enemy_attack_method_config +ENEMY_ATTACK_ACTION: str = config.apl_mode.enemy_attack_action_data +ENEMY_ATTACK_REPORT: bool = config.apl_mode.enemy_attack_report ENEMY_ATK_PARAMETER_DICT: dict[str, int | float | bool] = { "Taction": 30, # 角色弹刀与闪避动作的持续时间,不开放给用户更改。 "Tbase": 273, # 人类反应时间大数据中位数,单位ms,不可更改! - "PlayerLevel": _config["apl_mode"]["player_level"], # 玩家水平系数,由用户自己填写。 - "PerfectPlayer": _config["apl_mode"].get("perfect_player", True), # 是否是完美玩家(默认是) + "PlayerLevel": config.apl_mode.player_level, # 玩家水平系数,由用户自己填写。 + "PerfectPlayer": config.apl_mode.perfect_player, # 是否是完美玩家(默认是) "theta": 90, # θ,人类胜利最小反应时间(神经传导极限),为90ms,不可更改! "c": 0.5, # 波动调节系数,暂取0.5,不开放给用户更改。 "delta": 30, # 玩家水平系数所导致的中位数波动单位,暂时取30ms,不开放给用户更改。 @@ -125,71 +271,69 @@ def update_json_config(template: dict, user: dict) -> bool: # 该字典的key为CID,value为招架动作的skill_tag # 注意,不同的招架策略有时候存在着影画或是其他的限制条件, # 所以若是在不满足这些条件的情况下强行使用这些招架策略,那么character中的审查函数会报错而中断程序运行。 -CHAR_PARRY_STRATEGY_MAP: dict = {1411: "1411_Assault_Aid_A"} +CHAR_PARRY_STRATEGY_MAP: dict[int, str] = {1411: "1411_Assault_Aid_A"} # debug参数,用于检查APL在窗口期间的想法 -APL_THOUGHT_CHECK: bool = _config["apl_mode"].get("apl_thought_check", False) -APL_THOUGHT_CHECK_WINDOW: list[int] = _config["apl_mode"].get("apl_thought_check_window", [0, 1]) +APL_THOUGHT_CHECK: bool = config.apl_mode.apl_thought_check +APL_THOUGHT_CHECK_WINDOW: list[int] = config.apl_mode.apl_thought_check_window -DEFAULT_APL_DIR: str = _config["apl_mode"]["default_apl_dir"] -COSTOM_APL_DIR: str = _config["apl_mode"]["custom_apl_dir"] -YANAGI_NA_ORDER: str = _config["apl_mode"]["Yanagi"] -HUGO_NA_ORDER: str = _config["apl_mode"]["Hugo"] -HUGO_NA_MODE_LEVEL: int = _config["na_mode_level"]["Hugo"] -ALICE_NA_ORDER: str = _config["apl_mode"]["Alice"] -SEED_NA_ORDER: str = _config["apl_mode"]["Seed"] +DEFAULT_APL_DIR: str = config.apl_mode.default_apl_dir +COSTOM_APL_DIR: str = config.apl_mode.custom_apl_dir +YANAGI_NA_ORDER: str = config.apl_mode.yanagi +HUGO_NA_ORDER: str = config.apl_mode.hugo +HUGO_NA_MODE_LEVEL: int = config.na_mode_level.hugo +ALICE_NA_ORDER: str = config.apl_mode.alice +SEED_NA_ORDER: str = config.apl_mode.seed #: 合轴操作完成度系数->根据前一个技能帧数的某个比例来延后合轴 -SWAP_CANCEL_MODE_COMPLETION_COEFFICIENT: float = _config["swap_cancel_mode"][ - "completion_coefficient" -] +SWAP_CANCEL_MODE_COMPLETION_COEFFICIENT: float = config.swap_cancel_mode.completion_coefficient #: 操作滞后系数->合轴操作延后的另一种迟滞方案,即固定值延后。 -SWAP_CANCEL_MODE_LAG_TIME: float = _config["swap_cancel_mode"]["lag_time"] -SWAP_CANCEL_MODE_DEBUG: bool = _config["swap_cancel_mode"]["debug"] -SWAP_CANCEL_DEBUG_TARGET_SKILL: str = _config["swap_cancel_mode"]["debug_target_skill"] +SWAP_CANCEL_MODE_LAG_TIME: float = config.swap_cancel_mode.lag_time +SWAP_CANCEL_MODE_DEBUG: bool = config.swap_cancel_mode.debug +SWAP_CANCEL_DEBUG_TARGET_SKILL: str = config.swap_cancel_mode.debug_target_skill # 数据库配置 -SQLITE_PATH: str = _config["database"]["SQLITE_PATH"] -CHARACTER_DATA_PATH: str = _config["database"]["CHARACTER_DATA_PATH"] -WEAPON_DATA_PATH: str = _config["database"]["WEAPON_DATA_PATH"] -EQUIP_2PC_DATA_PATH: str = _config["database"]["EQUIP_2PC_DATA_PATH"] -SKILL_DATA_PATH: str = _config["database"]["SKILL_DATA_PATH"] -ENEMY_DATA_PATH: str = _config["database"]["ENEMY_DATA_PATH"] -ENEMY_ADJUSTMENT_PATH: str = _config["database"]["ENEMY_ADJUSTMENT_PATH"] -DEFAULT_SKILL_PATH: str = _config["database"]["DEFAULT_SKILL_PATH"] -CRIT_BALANCING: bool = _config["character"]["crit_balancing"] -BACK_ATTACK_RATE: bool = _config["character"]["back_attack_rate"] +SQLITE_PATH: str = config.database.sqlite_path +CHARACTER_DATA_PATH: str = config.database.character_data_path +WEAPON_DATA_PATH: str = config.database.weapon_data_path +EQUIP_2PC_DATA_PATH: str = config.database.equip_2pc_data_path +SKILL_DATA_PATH: str = config.database.skill_data_path +ENEMY_DATA_PATH: str = config.database.enemy_data_path +ENEMY_ADJUSTMENT_PATH: str = config.database.enemy_adjustment_path +DEFAULT_SKILL_PATH: str = config.database.default_skill_path +CRIT_BALANCING: bool = config.character.crit_balancing +BACK_ATTACK_RATE: float = config.character.back_attack_rate # FIXME:背击暂时用几率控制。 -DEBUG: bool = _config["debug"]["enabled"] -DEBUG_LEVEL: int = _config["debug"]["level"] -JUDGE_FILE_PATH: str = _config["database"]["JUDGE_FILE_PATH"] -EFFECT_FILE_PATH: str = _config["database"]["EFFECT_FILE_PATH"] -EXIST_FILE_PATH: str = _config["database"]["EXIST_FILE_PATH"] -BUFF_LOADING_CONDITION_TRANSLATION_DICT: dict = _config["translate"] -ENABLE_WATCHDOG: bool = _config["watchdog"]["enabled"] -WATCHDOG_LEVEL: int = _config["watchdog"]["level"] +DEBUG: bool = config.debug.enabled +DEBUG_LEVEL: int = config.debug.level +JUDGE_FILE_PATH: str = config.database.judge_file_path +EFFECT_FILE_PATH: str = config.database.effect_file_path +EXIST_FILE_PATH: str = config.database.exist_file_path +BUFF_LOADING_CONDITION_TRANSLATION_DICT = config.translate +ENABLE_WATCHDOG: bool = config.watchdog.enabled +WATCHDOG_LEVEL: int = config.watchdog.level INPUT_ACTION_LIST = "" # 半废弃 # 初始化Buff的报告: -BUFF_0_REPORT: bool = _config["buff_0_report"]["enabled"] +BUFF_0_REPORT: bool = config.buff_0_report.enabled # 角色特殊机制报告: -VIVIAN_REPORT: bool = _config["char_report"]["Vivian"] -ASTRAYAO_REPORT: bool = _config["char_report"]["AstraYao"] -HUGO_REPORT: bool = _config["char_report"]["Hugo"] -YIXUAN_REPORT: bool = _config["char_report"]["Yixuan"] -TRIGGER_REPORT: bool = _config["char_report"]["Trigger"] -YUZUHA_REPORT: bool = _config["char_report"]["Yuzuha"] -ALICE_REPORT: bool = _config["char_report"]["Alice"] -SEED_REPORT: bool = _config["char_report"]["Seed"] +VIVIAN_REPORT: bool = config.char_report.vivian +ASTRAYAO_REPORT: bool = config.char_report.astra_yao +HUGO_REPORT: bool = config.char_report.hugo +YIXUAN_REPORT: bool = config.char_report.yixuan +TRIGGER_REPORT: bool = config.char_report.trigger +YUZUHA_REPORT: bool = config.char_report.yuzuha +ALICE_REPORT: bool = config.char_report.alice +SEED_REPORT: bool = config.char_report.seed # Cal计算debug -CHECK_SKILL_MUL: bool = _config["debug"]["check_skill_mul"] -CHECK_SKILL_MUL_TAG: list[str] = _config["debug"]["check_skill_mul_tag"] +CHECK_SKILL_MUL: bool = config.debug.check_skill_mul +CHECK_SKILL_MUL_TAG: list[str] = config.debug.check_skill_mul_tag # 开发变量 -NEW_SIM_BOOT: bool = _config.get("dev", {}).get("new_sim_boot", True) +NEW_SIM_BOOT: bool = config.dev.new_sim_boot compare_methods_mapping: dict[str, Callable[[float | int, float | int], bool]] = { "<": lambda a, b: a < b, @@ -297,3 +441,4 @@ def print_constant_names_and_values(): print(f"{name}: {value}") print_constant_names_and_values() + print(config.model_dump_json(indent=2, by_alias=True)) diff --git a/zsim/lib_webui/process_simulator.py b/zsim/lib_webui/process_simulator.py index 65ada8ef..434b6651 100644 --- a/zsim/lib_webui/process_simulator.py +++ b/zsim/lib_webui/process_simulator.py @@ -6,7 +6,7 @@ import polars as pl import streamlit as st -from zsim.define import CONFIG_PATH +from zsim.define import config_path from zsim.lib_webui.process_apl_editor import APLArchive, APLJudgeTool from zsim.simulator.config_classes import ( ExecAttrCurveCfg, @@ -78,7 +78,7 @@ def generate_parallel_args( def apl_selecter(): - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) default_apl_path = config["database"]["APL_FILE_PATH"] @@ -108,10 +108,10 @@ def save_apl_selection(selected_title: str): """ apl_archive = APLArchive() original_path = apl_archive.get_origin_relative_path(selected_title) - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) config["database"]["APL_FILE_PATH"] = original_path - with open(CONFIG_PATH, "w", encoding="utf-8") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=4) @@ -121,7 +121,7 @@ def get_default_apl_tile() -> str | None: Returns: str: 默认APL的标题。 """ - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) default_apl_path = config["database"]["APL_FILE_PATH"] @@ -170,7 +170,7 @@ def show_apl_judge_result(selected_title: str | None = None) -> bool: def enemy_selector() -> None: """敌人配置选择器界面。""" # 从enemy.csv获取所有唯一的IndexID和CN_enemy_ID,并按IndexID排序 - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) saved_index = config["enemy"]["index_ID"] saved_adjust = config["enemy"]["adjust_ID"] @@ -246,12 +246,12 @@ def save_enemy_selection(index_id: int, adjust_id: int): """ # 创建配置文件临时备份 - backup_path = CONFIG_PATH + ".bak" - shutil.copy(CONFIG_PATH, backup_path) + backup_path = config_path + ".bak" + shutil.copy(config_path, backup_path) try: # 部分更新配置文件 - with open(CONFIG_PATH, "r+", encoding="utf-8") as f: + with open(config_path, "r+", encoding="utf-8") as f: config = json.load(f) # 只更新需要的部分 @@ -266,7 +266,7 @@ def save_enemy_selection(index_id: int, adjust_id: int): except Exception as e: # 出错时恢复备份 print(f"保存配置出错: {e}") - shutil.move(backup_path, CONFIG_PATH) + shutil.move(backup_path, config_path) raise finally: # 清理备份 diff --git a/zsim/page_simulator.py b/zsim/page_simulator.py index e5b344ed..b003f936 100644 --- a/zsim/page_simulator.py +++ b/zsim/page_simulator.py @@ -61,7 +61,7 @@ def page_simulator(): """模拟器页面函数""" st.title("ZZZ Simulator - 模拟器") - from zsim.define import CONFIG_PATH + from zsim.define import config_path # 获取当前计算机的物理核心数量 MAX_WORKERS = psutil.cpu_count(logical=False) @@ -71,7 +71,7 @@ def get_executor(): """获取进程池执行器""" return concurrent.futures.ProcessPoolExecutor(max_workers=MAX_WORKERS) - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) default_stop_tick = config["stop_tick"] @@ -99,7 +99,7 @@ def go_simulator(): label_visibility="collapsed", ) if stop_tick != default_stop_tick: - with open(CONFIG_PATH, "r+", encoding="utf-8") as f: + with open(config_path, "r+", encoding="utf-8") as f: config = json.load(f) config["stop_tick"] = stop_tick f.seek(0) @@ -150,7 +150,7 @@ def go_apl_select(): @st.dialog("模拟器配置", width="large") def go_config(): """模拟器配置对话框""" - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) parallel_cfg = config["parallel_mode"] default_mode = parallel_cfg["enabled"] @@ -275,9 +275,11 @@ def go_config(): selected_weapon = st.selectbox( f"音擎 {i + 1}", weapon_options, - index=weapon_options.index(default_weapon_name) - if default_weapon_name in weapon_options - else 0, + index=( + weapon_options.index(default_weapon_name) + if default_weapon_name in weapon_options + else 0 + ), key=f"weapon_select_{i}", label_visibility="collapsed", ) @@ -307,7 +309,7 @@ def go_config(): if st.button("保存配置"): mode_bool = True if mode == RUN_MODES[1] else False # 多进程 if mode_bool: - with open(CONFIG_PATH, "r+", encoding="utf-8") as f: + with open(config_path, "r+", encoding="utf-8") as f: config = json.load(f) config["parallel_mode"]["enabled"] = mode_bool config["parallel_mode"]["adjust_char"] = int(adjust_char.split("号")[0]) @@ -347,7 +349,7 @@ def go_config(): f.truncate() else: # 单进程模式 - with open(CONFIG_PATH, "r+", encoding="utf-8") as f: + with open(config_path, "r+", encoding="utf-8") as f: config = json.load(f) config["parallel_mode"]["enabled"] = mode_bool f.seek(0) @@ -365,7 +367,7 @@ def go_config(): col1, col2 = st.columns([8, 1]) # 加载config - with open(CONFIG_PATH, "r", encoding="utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) parallel_cfg = config["parallel_mode"] if not parallel_cfg["enabled"]: diff --git a/zsim/sim_progress/Buff/buff_class.py b/zsim/sim_progress/Buff/buff_class.py index c030de57..9c1750c7 100644 --- a/zsim/sim_progress/Buff/buff_class.py +++ b/zsim/sim_progress/Buff/buff_class.py @@ -7,7 +7,7 @@ import numpy as np import pandas as pd -from zsim.define import CONFIG_PATH, EFFECT_FILE_PATH, EXIST_FILE_PATH, JUDGE_FILE_PATH +from zsim.define import EFFECT_FILE_PATH, EXIST_FILE_PATH, JUDGE_FILE_PATH, config_path from zsim.sim_progress.Report import report_to_log from .BuffXLogic._buff_record_base_class import BuffRecordBaseClass as BRBC @@ -16,7 +16,7 @@ from zsim.simulator.simulator_class import Simulator -with open(CONFIG_PATH, "r", encoding="utf-8") as file: +with open(config_path, "r", encoding="utf-8") as file: config = json.load(file) debug = config.get("debug") with open("./zsim/sim_progress/Buff/buff_config.json", "r", encoding="utf-8") as f: @@ -207,7 +207,7 @@ def __init__(self, meta_config: pd.Series): 编写beneficiary属性的原因: 目前,在Buff循环逻辑中,find_buff_0函数返回的结果只能是equipper的buff_0,这对于大部分buff来说是没有影响的, 但是如果一个Buff加给自己的和加给他人的buff情况不同、而我们有需要去找受益者获取buff_0时,beneficiary属性就派上用场了。 - ——启发自 Buff 静听嘉音 + ——启发自 Buff 静听嘉音 """ """Buff标签""" @@ -630,7 +630,7 @@ def update( 则意味着buff只执行添加,而不执行更新。 只有操作者才有资格更新buff。而在外部,更新、轮询char的顺序,来自于select_character, 该列表是按照操作者作为第一视角的,所以,我们总能保证操作者的更新buff在前,而受益者的被动添加在后。 - + 但是,如果char_name是enemy,则意味着这是要给敌人添加buff。这是单向行为,所以不在这一层屏蔽的范围内。 """ self.dy.is_changed = True @@ -748,7 +748,7 @@ def update_cause_start(self, timenow, timecost, exist_buff_dict: dict, beneficia self.dy.count = min(buff_0.dy.count + self.ft.step, self.ft.maxcount) self.dy.ready = False self.dy.is_changed = True - """ + """ 所有因start标签而更新的buff,它们的底层逻辑往往和Hit更新互斥, 它们的层数计算往往非常直接,就是当前层数 + 步长; 而想要做到所谓的“层数叠加了”,那么当前层数应该从buff_0处获取(这是通用步骤,其他类型的层数更新也是这个流程) From 28f0d95165656ab21e0dc8cd712f0b937982fd82 Mon Sep 17 00:00:00 2001 From: TommrraraSnow Date: Sat, 22 Nov 2025 17:16:34 +0800 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E5=A4=87=E4=BB=BD=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=92=8C=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zsim/define.py | 10 ++++++---- zsim/lib_webui/process_simulator.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/zsim/define.py b/zsim/define.py index 76839d93..c9350bc9 100644 --- a/zsim/define.py +++ b/zsim/define.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any, Callable, ClassVar, Literal, cast -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_pascal from pydantic_settings import ( BaseSettings, @@ -33,7 +33,7 @@ class DebugConfig(BaseModel): enabled: bool = True level: int = 4 check_skill_mul: bool = False - check_skill_mul_tag: list[str] = Field(default_factory=list) + check_skill_mul_tag: list[str] = [] class WatchdogConfig(BaseModel): @@ -47,10 +47,12 @@ class CharacterConfig(BaseModel): class EnemyConfig(BaseModel): - index_id: int = Field(alias="index_ID") - adjust_id: int = Field(alias="adjust_ID") + index_id: int + adjust_id: int difficulty: float + model_config = ConfigDict(alias_generator=lambda x: x.replace("id", "ID")) + class AplModeConfig(BaseModel): enabled: bool = True diff --git a/zsim/lib_webui/process_simulator.py b/zsim/lib_webui/process_simulator.py index 434b6651..9f29909d 100644 --- a/zsim/lib_webui/process_simulator.py +++ b/zsim/lib_webui/process_simulator.py @@ -246,7 +246,7 @@ def save_enemy_selection(index_id: int, adjust_id: int): """ # 创建配置文件临时备份 - backup_path = config_path + ".bak" + backup_path = config_path.parent / "config.json.bak" shutil.copy(config_path, backup_path) try: From e5fbd60fb29821e9dab70b6353795c898dbabcc9 Mon Sep 17 00:00:00 2001 From: TommrraraSnow Date: Sat, 22 Nov 2025 17:35:02 +0800 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4Buff0ReportCo?= =?UTF-8?q?nfig=E7=B1=BB=E4=B8=AD=E7=9A=84model=5Fconfig=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zsim/define.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/zsim/define.py b/zsim/define.py index c9350bc9..0451fd4d 100644 --- a/zsim/define.py +++ b/zsim/define.py @@ -105,8 +105,6 @@ class DatabaseConfig(BaseModel): class Buff0ReportConfig(BaseModel): enabled: bool = False - model_config = ConfigDict() - class CharReportConfig(BaseModel): vivian: bool From a19cac5151133c71423f20702a10177f6486cd90 Mon Sep 17 00:00:00 2001 From: TommrraraSnow Date: Sat, 22 Nov 2025 19:47:10 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=E4=BD=BF=E7=94=A8config?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3APL=5FMODE=E5=92=8C=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zsim/sim_progress/Preload/SkillsQueue.py | 4 ++-- zsim/simulator/simulator_class.py | 21 +++++++++------------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/zsim/sim_progress/Preload/SkillsQueue.py b/zsim/sim_progress/Preload/SkillsQueue.py index d4a48307..60b88183 100644 --- a/zsim/sim_progress/Preload/SkillsQueue.py +++ b/zsim/sim_progress/Preload/SkillsQueue.py @@ -4,8 +4,8 @@ import pandas as pd -from zsim.define import APL_MODE, ElementType from zsim.define import ELEMENT_TYPE_MAPPING as ETM +from zsim.define import ElementType, config from zsim.sim_progress.Character.skill_class import Skill from zsim.sim_progress.data_struct.LinkedList import LinkedList from zsim.sim_progress.Report import report_to_log @@ -213,7 +213,7 @@ def get_skills_queue( raise ValueError("预加载序技能列表为空") preload_tick_stamps = {skill.CID: 0 for skill in skills} - if not APL_MODE: + if not config.apl_mode.enabled: for tag in preload_skills_list: cid = int(tag[:4]) # 提取tag的前四个字符作为key if cid not in preload_tick_stamps: diff --git a/zsim/simulator/simulator_class.py b/zsim/simulator/simulator_class.py index 6a519cc1..d018121b 100644 --- a/zsim/simulator/simulator_class.py +++ b/zsim/simulator/simulator_class.py @@ -4,13 +4,7 @@ from pydantic import BaseModel -from zsim.define import ( - APL_MODE, - APL_PATH, - ENEMY_ADJUST_ID, - ENEMY_DIFFICULTY, - ENEMY_INDEX_ID, -) +from zsim.define import config from zsim.sim_progress.Buff import ( BuffLoadLoop, buff_add, @@ -96,9 +90,9 @@ def cli_init_simulator(self, sim_cfg: SimCfg | None): self.__detect_parallel_mode(sim_cfg) self.init_data = InitData(common_cfg=None, sim_cfg=sim_cfg) self.enemy = Enemy( - index_id=ENEMY_INDEX_ID, - adjustment_id=ENEMY_ADJUST_ID, - difficulty=ENEMY_DIFFICULTY, + index_id=config.enemy.index_id, + adjustment_id=config.enemy.adjust_id, + difficulty=config.enemy.difficulty, sim_instance=self, ) self.__init_data_struct(sim_cfg) @@ -180,7 +174,7 @@ def __init_data_struct(self, sim_cfg, *, api_apl_path: str | None = None): self.preload = PreloadClass( skills, load_data=self.load_data, - apl_path=APL_PATH if api_apl_path is None else api_apl_path, + apl_path=config.apl_mode.enabled if api_apl_path is None else api_apl_path, sim_instance=self, ) self.game_state: dict[str, Any] = { @@ -227,7 +221,10 @@ def main_loop( preload_list = self.preload.preload_data.preload_action if stop_tick is None: - if not APL_MODE and self.preload.preload_data.skills_queue.head is None: + if ( + not config.apl_mode.enabled + and self.preload.preload_data.skills_queue.head is None + ): # Old Sequence mode left, not compatible with APL mode now stop_tick = self.tick + 120 elif self.tick >= stop_tick: