diff --git a/backend/core/character/GF/gunner_female/spitfire_female.py b/backend/core/character/GF/gunner_female/spitfire_female.py index 71fcd3a7..0044b956 100644 --- a/backend/core/character/GF/gunner_female/spitfire_female.py +++ b/backend/core/character/GF/gunner_female/spitfire_female.py @@ -874,9 +874,19 @@ class Skill27(PassiveSkill): data0 = get_data(f'{prefix}/{uuid}', 0) associate = [ - {'type': '*skillDamage', 'data': data0, 'skills': ['G-14手雷', 'G-35L感电手雷', 'G-18C冰冻手雷']}, + {'type': 'grenadeMastery', 'data': data0, 'skills': ['G-14手雷', 'G-35L感电手雷', 'G-18C冰冻手雷']}, ] + def grenadeMastery(self, old, new, data, skills, exceptSkills, ratio, weapon): + passive = self.char.GetSkillByName('单兵推进器-02X') + bonus = 1 + self.get_data_value(passive.data1, passive.lv) / 100 + old_ratio = 1 + self.get_data_value(data, old) * bonus / ratio + new_ratio = 1 + self.get_data_value(data, new) * bonus / ratio + for name in skills: + skill = self.char.GetSkillByName(name) + if skill is not None and name not in exceptSkills: + skill.skillDamage *= new_ratio / old_ratio + # 暴击 # gunner_female/spitfire_female/fc1262c19f3d0477ee8eda47b8db8696 # 944b9aab492c15a8474f96947ceeb9e4/fc1262c19f3d0477ee8eda47b8db8696 @@ -1846,7 +1856,21 @@ class Skill49(PassiveSkill): # 强化手雷攻击力 : 手雷攻击力的{value2}% data2 = get_data(f'{prefix}/{uuid}', 2) - associate = [{'type': '*skillDamage', 'data': data0}] + associate = [ + {'type': '*skillDamage', 'data': data0}, + {'type': 'grenadeMastery', 'data': data1, 'skills': ['G-14手雷', 'G-35L感电手雷', 'G-18C冰冻手雷']}, + ] + + def grenadeMastery(self, old, new, data, skills, exceptSkills, ratio, weapon): + mastery = self.char.GetSkillByName('手雷精通') + value = self.get_data_value(mastery.data0, mastery.lv) / 100 + # 三觉强化的是手雷精通的增加率,不是直接乘算手雷最终伤害。 + old_ratio = 1 + value * (1 + self.get_data_value(data, old) / ratio) + new_ratio = 1 + value * (1 + self.get_data_value(data, new) / ratio) + for name in skills: + skill = self.char.GetSkillByName(name) + if skill is not None and name not in exceptSkills: + skill.skillDamage *= new_ratio / old_ratio # 空袭战略 # gunner_female/spitfire_female/c591d1827cfbe224031c704e21d07932 diff --git a/backend/tests/unit/test_spitfire_female_grenade_mastery.py b/backend/tests/unit/test_spitfire_female_grenade_mastery.py new file mode 100644 index 00000000..ceb24fad --- /dev/null +++ b/backend/tests/unit/test_spitfire_female_grenade_mastery.py @@ -0,0 +1,97 @@ +import importlib.util +import sys +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from core.basic import skill as skill_base +from core.equipment.version_0.jade import jade_5 + + +@pytest.fixture +def character(monkeypatch): + def get_data(prefix, key): + if key == 'custom': + return 1.0 + if key == 'vps': + return [] + uuid = prefix.rsplit('/', 1)[-1] + if uuid == '0113c8b1306ca76d208f83f2d093dd62' and key == 0: + return [0] + [10 * level for level in range(1, 71)] + if uuid == '34e1bda921012f6018709f1c1a12b04d': + if key == 0: + return [0] + [18 + 2 * level for level in range(1, 71)] + if key == 1: + return [0] + [20] * 70 + return [0] + [100 * level for level in range(1, 71)] + + monkeypatch.setattr(skill_base, 'get_data', get_data) + path = Path(__file__).resolve().parents[2] / 'core/character/GF/gunner_female/spitfire_female.py' + spec = importlib.util.spec_from_file_location('spitfire_grenade_test', path) + module = importlib.util.module_from_spec(spec) + monkeypatch.setitem(sys.modules, spec.name, module) + spec.loader.exec_module(module) + + def create(mastery=11, awakening=10, awakening_first=False): + char = module.classChange(0) + char.charEquipInfo = {'武器': SimpleNamespace(equInfo=None)} + char.setInfo = {} + char.SetSkills({str(sid): {'lv': 40} for sid in (24, 33, 36, 50)}) + levels = [(27, mastery), (49, awakening)] + for sid, level in reversed(levels) if awakening_first else levels: + char.GetSkillByID(sid).lv = level + return char + + return create + + +@pytest.mark.parametrize('mastery', [0, 11]) +@pytest.mark.parametrize('awakening', [0, 10]) +@pytest.mark.parametrize('awakening_first', [False, True]) +def test_grenade_mastery_applies_awakening_bonus(character, mastery, awakening, awakening_first): + char = character(mastery, awakening, awakening_first) + common = 1.38 if awakening else 1 + mastery_ratio = 1 + mastery * (0.12 if awakening else 0.1) + for sid in (24, 33, 36): + assert char.GetSkillByID(sid).skillDamage == pytest.approx(common * mastery_ratio) + assert char.GetSkillByID(50).skillDamage == pytest.approx(common) + + +@pytest.mark.parametrize('count', [1, 2]) +def test_level_jades_update_normal_and_enhanced_grenades(character, count): + char = character() + before = {(sid, mode): char.GetSkillByID(sid).skillInfo(mode) + for sid in (24, 33, 36) for mode in ('普通', '强化')} + for _ in range(count): + jade_5(char, 1) + assert char.GetSkillByID(27).lv == 11 + count + for (sid, mode), old in before.items(): + skill = char.GetSkillByID(sid) + new = skill.skillInfo(mode) + expected_level = 40 + count if sid == 33 else 40 + assert skill.lv == expected_level + expected = (1 + 0.12 * (11 + count)) / 2.32 * expected_level / 40 + assert new[0] * new[1] / (old[0] * old[1]) == pytest.approx(expected) + + +def test_changing_awakening_level_does_not_stack_grenade_bonus(character): + char = character() + awakening = char.GetSkillByID(49) + for level, common, mastery_ratio in [(11, 1.4, 2.32), (0, 1, 2.1), (10, 1.38, 2.32)]: + awakening.lv = level + for sid in (24, 33, 36): + assert char.GetSkillByID(sid).skillDamage == pytest.approx(common * mastery_ratio) + assert char.GetSkillByID(50).skillDamage == pytest.approx(common) + + +def test_mastery_level_changes_preserve_shared_data(character): + char = character() + mastery = char.GetSkillByID(27) + original = mastery.data0.copy() + for requested, actual in [(13, 13), (0, 0), (20, 20), (21, 20), (11, 11)]: + mastery.lv = requested + assert mastery.lv == actual + assert char.GetSkillByID(36).skillDamage == pytest.approx(1.38 * (1 + actual * 0.12)) + assert mastery.data0 == original + assert character().GetSkillByID(36).skillDamage == pytest.approx(1.38 * 2.32)