Skip to content
Open
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
44 changes: 28 additions & 16 deletions tasks/daily/buildtarget.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ def collect(self) -> list[tuple[str, str]]:
if validated:
log.debug(f"副本名称 {validated} 检验通过,加入目标列表")
results.append(validated)
if "饰品提取" in validated[0]:
break

else:
log.warning(f"目标副本识别错误,{instance} 不在任何已知副本列表中")

Expand Down Expand Up @@ -474,26 +473,39 @@ def get_target_instance() -> tuple[str, str] | None:
if not BuildTarget._initialized:
BuildTarget.init_build_targets()

require_ornament = False
only_erosion_and_ornament = False
if BuildTarget._target_instances:
only_erosion_and_ornament = all(("侵蚀隧洞" in instance_type) or ("饰品提取" in instance_type) for instance_type in BuildTarget._target_instances)
if only_erosion_and_ornament:
require_ornament = datetime.date.today().weekday() >= (7 - cfg.build_target_ornament_weekly_count)
target_instances = [
(instance_type, instance_names[0])
for instance_type, instance_names in BuildTarget._target_instances.items()
if instance_names and "历战余响" not in instance_type
]
if not target_instances:
return None

def is_erosion_or_ornament(instance_type: str) -> bool:
return "侵蚀隧洞" in instance_type or "饰品提取" in instance_type

only_erosion_and_ornament = all(is_erosion_or_ornament(instance_type) for instance_type, _ in target_instances)
if only_erosion_and_ornament and cfg.build_target_use_user_instance_when_only_erosion_and_ornament:
log.info("培养目标仅识别到侵蚀隧洞/饰品提取,按配置回退至自定义副本")
return None

for instance_type, instance_names in BuildTarget._target_instances.items():
if "历战余响" in instance_type:
continue
if not require_ornament:
return (instance_type, instance_names[0])
if "饰品提取" in instance_type:
return (instance_type, instance_names[0])
# 游戏界面可能将饰品提取显示在材料副本之前,因此不能依赖识别顺序。
# 只要仍有角色或光锥素材需要刷取,就优先执行相应的材料副本。
for instance_type, instance_name in target_instances:
if not is_erosion_or_ornament(instance_type):
return (instance_type, instance_name)

return None
require_ornament = datetime.date.today().weekday() >= (7 - cfg.build_target_ornament_weekly_count)
if require_ornament:
for instance_type, instance_name in target_instances:
if "饰品提取" in instance_type:
return (instance_type, instance_name)

for instance_type, instance_name in target_instances:
if "侵蚀隧洞" in instance_type:
return (instance_type, instance_name)

return target_instances[0]

@staticmethod
def get_target_echo_instance() -> tuple[str, str] | None:
Expand Down
17 changes: 11 additions & 6 deletions tasks/power/power.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,26 @@ class Power:
def run():
Power.preprocess()

# 优先执行体力计划
Power.execute_power_plan()

log.hr("开始清体力", 0)

instance_type = cfg.instance_type
instance_name = cfg.instance_names[instance_type]
target = None

try:
if cfg.build_target_enable and (target := BuildTarget.get_target_instance()):
instance_type, instance_name = target
log.info(f"使用培养目标副本: {instance_type} - {instance_name}")
if cfg.build_target_enable:
target = BuildTarget.get_target_instance()
except Exception as e:
log.error(f"获取培养目标副本失败: {e}")

if target:
instance_type, instance_name = target
log.info(f"使用培养目标副本: {instance_type} - {instance_name}")
else:
# 未启用培养目标、识别失败,或培养目标按配置回退至自定义副本时,
# 才优先执行用户配置的体力计划。
Power.execute_power_plan()

if not Instance.validate_instance(instance_type, instance_name):
log.hr("完成", 2)
return False
Expand Down
76 changes: 76 additions & 0 deletions tests/test_tasks/test_build_target_priority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from types import SimpleNamespace
from unittest.mock import patch

from tasks.daily import buildtarget
from tasks.daily.buildtarget import BuildTarget, DefaultHandler


def test_default_handler_continues_collecting_after_ornament():
handler = DefaultHandler.__new__(DefaultHandler)
instances = [
("饰品提取", "永恒笑剧"),
("拟造花萼(金)", "回忆之蕾"),
("凝滞虚影", "嗔怒之形"),
]

with (
patch.object(handler, "_iter_scroll_windows", return_value=instances),
patch.object(handler, "_capture_items_in_window"),
patch.object(handler, "_is_valid_instance", side_effect=lambda instance: instance),
):
assert handler.collect() == instances


def test_material_instance_is_prioritized_even_when_ornament_is_first():
targets = {
"饰品提取": ["永恒笑剧"],
"拟造花萼(金)": ["回忆之蕾"],
"凝滞虚影": ["嗔怒之形"],
}
cfg = SimpleNamespace(
build_target_use_user_instance_when_only_erosion_and_ornament=True,
build_target_ornament_weekly_count=7,
)

with (
patch.object(buildtarget, "cfg", cfg),
patch.object(BuildTarget, "_initialized", True),
patch.object(BuildTarget, "_target_instances", targets),
):
assert BuildTarget.get_target_instance() == ("拟造花萼(金)", "回忆之蕾")


def test_only_erosion_and_ornament_returns_to_custom_instance_when_enabled():
targets = {
"饰品提取": ["永恒笑剧"],
"侵蚀隧洞": ["睿治之径"],
}
cfg = SimpleNamespace(
build_target_use_user_instance_when_only_erosion_and_ornament=True,
build_target_ornament_weekly_count=7,
)

with (
patch.object(buildtarget, "cfg", cfg),
patch.object(BuildTarget, "_initialized", True),
patch.object(BuildTarget, "_target_instances", targets),
):
assert BuildTarget.get_target_instance() is None


def test_cavern_is_used_before_ornament_when_weekly_count_is_zero():
targets = {
"饰品提取": ["永恒笑剧"],
"侵蚀隧洞": ["睿治之径"],
}
cfg = SimpleNamespace(
build_target_use_user_instance_when_only_erosion_and_ornament=False,
build_target_ornament_weekly_count=0,
)

with (
patch.object(buildtarget, "cfg", cfg),
patch.object(BuildTarget, "_initialized", True),
patch.object(BuildTarget, "_target_instances", targets),
):
assert BuildTarget.get_target_instance() == ("侵蚀隧洞", "睿治之径")
50 changes: 48 additions & 2 deletions tests/test_tasks/test_power_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@


class FakeConfig:
def __init__(self, power_plan, keep_plan):
def __init__(self, power_plan, keep_plan, build_target_enable=False):
self.values = {
"power_plan": power_plan,
"power_plan_keep": keep_plan,
}
self.build_target_enable = build_target_enable
self.instance_type = "侵蚀隧洞"
self.instance_names = {"侵蚀隧洞": "睿治之径"}
self.writes = []

def get_value(self, key, default=None):
Expand Down Expand Up @@ -50,7 +53,9 @@ def validate_instance(instance_type, instance_name):
return True

class FakeBuildTarget:
pass
@staticmethod
def get_target_instance():
return None

stub_modules = {
"module.screen": _stub_module("module.screen", screen=object()),
Expand Down Expand Up @@ -90,5 +95,46 @@ def test_completed_plan_is_unchanged_when_keep_is_enabled(self):
self.assertEqual(cfg.get_value("power_plan"), plan)


class TestPowerRunPriority(unittest.TestCase):
def test_build_target_runs_before_power_plan(self):
cfg = FakeConfig(
[["侵蚀隧洞", "睿治之径", 2]],
keep_plan=True,
build_target_enable=True,
)
module = _load_power_module(cfg)
target = ("拟造花萼(赤)", "毁灭之蕾•拟造花萼(赤)")

with (
patch.object(module.Power, "preprocess"),
patch.object(module.Power, "execute_power_plan") as execute_power_plan,
patch.object(module.Power, "process") as process,
patch.object(module.BuildTarget, "get_target_instance", return_value=target),
):
module.Power.run()

execute_power_plan.assert_not_called()
process.assert_called_once_with(*target)

def test_power_plan_runs_when_build_target_returns_to_custom_instance(self):
cfg = FakeConfig(
[["侵蚀隧洞", "睿治之径", 2]],
keep_plan=True,
build_target_enable=True,
)
module = _load_power_module(cfg)

with (
patch.object(module.Power, "preprocess"),
patch.object(module.Power, "execute_power_plan") as execute_power_plan,
patch.object(module.Power, "process") as process,
patch.object(module.BuildTarget, "get_target_instance", return_value=None),
):
module.Power.run()

execute_power_plan.assert_called_once_with()
process.assert_called_once_with("侵蚀隧洞", "睿治之径")


if __name__ == "__main__":
unittest.main()