Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions config/weekly_list.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 各脚本支持的周常声明(静态配置,进 git)。
# 结构:每脚本一个列表,每项:
# name: 周常展示名(与 dungeon_list.yml 一致用中文名)
# dungeons?: 可选副本名清单(存在且有内容即表示该周常需选副本;含「无」表示跳过)
# 周常「周几起」另存于 config/weekly_start.yml({脚本标识: 1~7}),不在此文件。
# 用户已选副本(运行时状态)不在此文件,存于 gui_state.json 的 weekly_dungeons。
# 副本名清单取自各脚本自身配置(如 M7A 的 assets/config/instance_names.json),
# 在此静态声明,避免运行时依赖游戏脚本已安装。
ok-ww:
- name: 幻梦游园
OneDragon-Launcher:
- name: 迷失之地
March7th-Assistant:
- name: 货币战争
- name: 历战余响
dungeons:
- 无
- 铁骸的锈冢
- 晨昏的回眸
- 心兽的战场
- 尘梦的赞礼
- 蛀星的旧靥
- 不死的神实
- 寒潮的落幕
- 毁灭的开端
6 changes: 6 additions & 0 deletions config/weekly_start.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 各脚本的周常起始日(周几起)持久化配置,进 git。
# 结构:{脚本标识: 1~7},1=周一 ~ 7=周日;缺省即「不设置」。
# 与周常声明(weekly_list.yml)、周常超时(weekly_timeouts.yml)平级,三套各自独立。
ok-ww: 3
OneDragon-Launcher: 1
March7th-Assistant: 2
19 changes: 5 additions & 14 deletions src/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,9 @@ def _run_generate_chain(args) -> int:
ui_state[script_name] = {}
ui_state[script_name]["sequence"] = sequence_overrides[script_name]

# 命令行覆盖:--weekly-start 合并到内存 ui_state,并持久化(周几跑是长期配置,
# 同 GUI 改周几起 _set_weekly 语义;与 --dungeon/--sequence 的临时覆盖不同)
# 命令行覆盖:--weekly-start 持久化到 weekly_start.yml
# (周几跑是长期配置,同 GUI 改周几起语义;与 --dungeon/--sequence 的临时覆盖不同)。
# 校验失败(未知脚本/未支持周常/非整数/越界)在写盘前拦截并返回 1。
try:
weekly_overrides = _parse_overrides(args.weekly_start)
except ValueError as exc:
Expand Down Expand Up @@ -404,18 +405,8 @@ def _run_generate_chain(args) -> int:
f"--weekly-start 中 {script_name} 的值越界: {start_day}(应为 1~7)",
)
return 1
if script_name not in ui_state:
ui_state[script_name] = {}
ui_state[script_name]["weekly_start"] = start_day

if weekly_overrides:
# 基于持久化状态(重新读取)合并周几起再落盘,
# 避免把 --dungeon / --sequence 临时覆盖写回 gui_state.json
persist_state = service.load_ui_state()
for script_name, raw in weekly_overrides.items():
entry = persist_state.setdefault(script_name, {})
entry["weekly_start"] = int(raw)
service.save_ui_state(persist_state)
# 仅做持久化,不并入内存 ui_state(ui_state 只承载 --dungeon/--sequence 临时覆盖)
service.set_weekly_start(script_name, start_day)

out = args.out
if out:
Expand Down
2 changes: 1 addition & 1 deletion src/config/set_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

- 基类 `ScriptConfig` 提供通用能力:`_load` / `_save` / `_verify_saved` / `_update_task` / `_update_sequence` / `_init_config` / `_is_aligned` / `set_dungeon` / `safe_update`。
- 子类声明 `_script_name`、`display_name` 与路径类属性:`_config_rel_path` 必填;声明了 `_game_path_keys` 则 `_game_config_rel_path` 必填;需模板初始化才设 `_template_rel_path`。`_task_key` / `_task_map` 按需覆盖。
- 注册表 `_CONFIGS: dict[str, type[ScriptConfig]]` 由 `@register` 装饰器显式填充,key 为 `_script_name`;路径声明不完整会在 import 时 assert 暴露。
- 注册表 `_CONFIGS: dict[str, type[ScriptConfig]]` 由 `@register` 装饰器显式填充,key 为 `_script_name`;路径声明不完整会在 import 时 assert 暴露。**注册表为模块私有,不对外 import**:外部只经模块级公开函数访问(`is_adapted` / `supports_weekly` / `get_config_path` / `get_game_exe_path` / `get_background_rel_path` / `set_config` / `set_weekly_dungeon`)。

## 三个独立流程

Expand Down
52 changes: 52 additions & 0 deletions src/config/set_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,26 @@ def _write_weekly(self, enabled: bool) -> None:
safe_update(config, self._weekly_task_name, enabled, self.display_name)
self._save(config)

def set_weekly_dungeon(self, weekly_name: str, dungeon_name: str) -> None:
"""写入某周常当前选中的副本名到 config.yaml 的 instance_names。

副本名清单的展示与下拉选项由 OneDragon-Helper 的 weekly_list.yml 声明
(dungeons 字段)负责,本方法只承担把用户所选写回 M7A 游戏配置的本分。

Args:
weekly_name: 周常名(如「历战余响」);即 instance_names 的键。
dungeon_name: 选中的副本名(来自 weekly_list.yml 声明)。
"""
config = self._load()
# instance_names 是 M7A 约定键名({周常名: 副本名} 的 dict),保持不动;
# 缺字段则新建。
instance_names = config.get("instance_names")
if not isinstance(instance_names, dict):
instance_names = {}
config["instance_names"] = instance_names
instance_names[weekly_name] = dungeon_name
self._save(config)


# ---- 异环 Neverness to Everness (NTE) ----
@register
Expand Down Expand Up @@ -1033,3 +1053,35 @@ def supports_weekly(script_name: str) -> bool:
if script_name not in _CONFIGS:
return False
return bool(_CONFIGS[script_name]._weekly_task_name)


def get_background_rel_path(script_name: str) -> str:
"""读脚本默认背景图相对路径(相对脚本根目录,供 GUI 背景控制器)。

Args:
script_name: 脚本标识名。

Returns:
背景图相对路径;未适配或未声明背景图时返回空字符串。
"""
if script_name not in _CONFIGS:
return ""
return _CONFIGS[script_name].background


def set_weekly_dungeon(script_name: str, weekly_name: str, dungeon_name: str) -> None:
"""适配器接口:写某周常当前选中的副本名到脚本自身 config。

未适配或该脚本无「周常选副本」概念(子类未实现)时优雅跳过。

Args:
script_name: 脚本标识名。
weekly_name: 周常名(如「历战余响」)。
dungeon_name: 选中的副本名。
"""
if script_name not in _CONFIGS:
return
cfg_cls = _CONFIGS[script_name]
if not hasattr(cfg_cls, "set_weekly_dungeon"):
return
cfg_cls().set_weekly_dungeon(weekly_name, dungeon_name)
37 changes: 0 additions & 37 deletions src/config/subscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
提供脚本根目录解析、config 路径推导、配置文件读写等功能。
"""

import contextlib
import json
import logging
import os
import re
import urllib.error
import urllib.request

import yaml

Expand Down Expand Up @@ -298,37 +295,3 @@ def generate_config_from_example() -> None:
data = yaml.safe_load(f)
with open(config_path, "w", encoding="utf-8") as f:
yaml.dump(data, f, allow_unicode=True, sort_keys=False)


# ============================================================
# 网络下载
# ============================================================


def download_file(url: str, out_path: str, timeout: int = 10) -> str | None:
"""下载文件到本地(通用工具,供各脚本拉取远程资源,如背景图)。

Args:
url: 下载地址。
out_path: 输出路径(自动创建父目录)。
timeout: 网络超时秒数。

Returns:
输出路径;下载失败(网络/磁盘)→ None,调用方自行降级。
"""
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
tmp_path = f"{out_path}.part"
try: # 网络/磁盘操作,失败可恢复,调用方降级处理
with (
urllib.request.urlopen(url, timeout=timeout) as resp, # noqa: S310
open(tmp_path, "wb") as f,
):
f.write(resp.read())
os.replace(tmp_path, out_path)
except (urllib.error.URLError, OSError) as exc:
logger.warning(f"[subscript] 下载失败 {url}: {exc}")
with contextlib.suppress(OSError):
os.remove(tmp_path)
return None
logger.info(f"[subscript] 下载完成: {out_path}")
return out_path
6 changes: 2 additions & 4 deletions src/gui/controllers/background.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from PySide6.QtGui import QImage
from PySide6.QtWidgets import QFileDialog

from src.config.set_config import _CONFIGS
from src.config.set_config import get_background_rel_path
from src.config.subscript import _get_script_root_dir_soft, resolve_script_path

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -90,9 +90,7 @@ def _script_background(self, script_name: str) -> str:
Returns:
背景图绝对路径;未适配/未声明/文件缺失 → 空字符串(交 DEFAULT_BG 兜底)。
"""
if script_name not in _CONFIGS:
return ""
rel = _CONFIGS[script_name].background
rel = get_background_rel_path(script_name)
if not rel:
return ""
root = _get_script_root_dir_soft(script_name)
Expand Down
Loading
Loading