from pathlib import Path import pytest from core.basic.character import createCharacter from core.basic.equipment import get_equipment CHARACTER_ROOT = Path(__file__).resolve().parents[2] / "core" / "character" / "GF" DEFAULT_ALTER = "GF.swordman_male.weapon_master" RESULT_KEYS = {"uuid", "skills", "info", "suits", "buffer"} EQUIP_PARTS = { "武器", "副武器", "称号", "上衣", "头肩", "下装", "腰带", "鞋", "手镯", "项链", "戒指", "辅助装备", "魔法石", "耳环", } KNOWN_BROKEN_ALTERS = { } def _discover_alters() -> list[str]: alters = [] for path in sorted(CHARACTER_ROOT.rglob("*.py")): if path.name == "__init__.py": continue text = path.read_text(encoding="utf-8") if "classChange" not in text: continue relative = path.relative_to(CHARACTER_ROOT).with_suffix("") alters.append("GF." + ".".join(relative.parts)) return alters def _character_params(): params = [] for alter in _discover_alters(): marks = [] if alter in KNOWN_BROKEN_ALTERS: marks.append(pytest.mark.xfail(reason=KNOWN_BROKEN_ALTERS[alter], strict=True)) params.append(pytest.param(alter, id=alter, marks=marks)) return params def _equipment_part(equ) -> str | None: if equ.itemType == "武器": return "武器" if equ.itemDetailType in EQUIP_PARTS: return equ.itemDetailType return None def _equipment_params(): params = [] for equ in get_equipment("0").equs: part = _equipment_part(equ) if part is None: continue params.append(pytest.param(equ, part, id=f"{part}:{equ.id}:{equ.name}")) return params def _assert_calc_result_shape(result: dict): assert isinstance(result, dict) assert RESULT_KEYS.issubset(result) assert isinstance(result["skills"], list) assert isinstance(result["info"], list) @pytest.mark.parametrize("alter", _character_params()) def test_all_gf_characters_can_calculate_with_minimal_config(alter, minimal_calc_config): character = createCharacter(alter, "0") assert character is not None, f"createCharacter returned None for alter={alter}" _assert_calc_result_shape(character.calc(minimal_calc_config)) @pytest.mark.parametrize(("equ", "part"), _equipment_params()) def test_all_equipment_can_calculate_when_equipped_once(equ, part, minimal_calc_config): config = minimal_calc_config.copy() config["equips"] = { part: value.copy() for part, value in minimal_calc_config["equips"].items() } config["equips"][part] = { **config["equips"][part], "id": equ.id, } character = createCharacter(DEFAULT_ALTER, "0") assert character is not None _assert_calc_result_shape(character.calc(config)) @pytest.mark.parametrize('vp', [0, 1, 2]) @pytest.mark.parametrize('alter', _character_params()) def test_all_gf_learned_skill_results_ignore_configuration_order(alter, vp, minimal_calc_config): """Exercise real profession rules, including every VP-dependent effect.""" from copy import deepcopy from random import Random probe = createCharacter(alter, '0') values = [ (str(skill.id), {'lv': skill.calculate_lv(), 'vp': vp, 'up': 0}) for skill in sorted(probe.skills, key=lambda item: item.id) ] shuffled = values.copy() Random(42).shuffle(shuffled) results = [] for ordered in (values, values[::-1], shuffled): config = deepcopy(minimal_calc_config) config['skills'] = dict(ordered) config['skillShortcuts'] = {'combos': [[] for _ in range(6)]} result = createCharacter(alter, '0').calc(config) result.pop('uuid') results.append(result) assert results[0] == results[1] == results[2] @pytest.mark.parametrize('alter', _character_params()) def test_learned_skills_with_weapon_ignore_configuration_order(alter, minimal_calc_config): from copy import deepcopy probe = createCharacter(alter, '0') weapon = next( equ for equ in get_equipment('0').equs if equ.itemType == '武器' and equ.itemDetailType in probe.武器选项 ) values = [(str(skill.id), {'lv': skill.calculate_lv(), 'vp': 2, 'up': 0}) for skill in probe.skills] results = [] for ordered in (values, values[::-1]): config = deepcopy(minimal_calc_config) config['equips']['武器']['id'] = weapon.id config['skills'] = dict(ordered) config['skillShortcuts'] = {'combos': [[] for _ in range(6)]} result = createCharacter(alter, '0').calc(config) result.pop('uuid') results.append(result) assert results[0] == results[1]