diff --git a/src/classes/action/nurture_weapon.py b/src/classes/action/nurture_weapon.py index e015aa4..36db308 100644 --- a/src/classes/action/nurture_weapon.py +++ b/src/classes/action/nurture_weapon.py @@ -18,15 +18,15 @@ class NurtureWeapon(TimedAction): duration_months = 3 def _execute(self) -> None: - from src.classes.equipment_grade import EquipmentGrade - from src.classes.weapon import get_treasure_weapon + from src.classes.cultivation import Realm + from src.classes.weapon import get_random_weapon_by_realm # 温养兵器增加较多熟练度(5-10) proficiency_gain = random.uniform(5.0, 10.0) self.avatar.increase_weapon_proficiency(proficiency_gain) - # 如果是普通兵器,有概率升级为宝物 - if self.avatar.weapon and self.avatar.weapon.grade == EquipmentGrade.COMMON: + # 如果是练气兵器,有概率升级为筑基兵器 + if self.avatar.weapon and self.avatar.weapon.realm == Realm.Qi_Refinement: # 基础5%概率 + 来自effects的额外概率 base_upgrade_chance = 0.05 extra_chance_raw = self.avatar.effects.get("extra_weapon_upgrade_chance", 0.0) @@ -34,12 +34,13 @@ class NurtureWeapon(TimedAction): total_chance = min(1.0, base_upgrade_chance + extra_chance) if random.random() < total_chance: - treasure_weapon = get_treasure_weapon(self.avatar.weapon.weapon_type) + treasure_weapon = get_random_weapon_by_realm(Realm.Foundation_Establishment, self.avatar.weapon.weapon_type) if treasure_weapon: import copy old_weapon_name = self.avatar.weapon.name old_proficiency = self.avatar.weapon_proficiency # 深拷贝宝物兵器并更换(会重新计算长期效果) + # get_random_weapon_by_realm 已经返回了副本,但再次copy也无妨 new_weapon = copy.deepcopy(treasure_weapon) self.avatar.change_weapon(new_weapon) # 恢复熟练度(change_weapon 会归零,需要手动恢复) diff --git a/src/classes/action/switch_weapon.py b/src/classes/action/switch_weapon.py index 6124bb5..3224573 100644 --- a/src/classes/action/switch_weapon.py +++ b/src/classes/action/switch_weapon.py @@ -2,19 +2,20 @@ from __future__ import annotations from src.classes.action import InstantAction from src.classes.event import Event -from src.classes.weapon import get_common_weapon +from src.classes.weapon import get_random_weapon_by_realm from src.classes.weapon_type import WeaponType +from src.classes.cultivation import Realm from src.classes.normalize import normalize_weapon_type class SwitchWeapon(InstantAction): """ - 切换兵器:将当前兵器切换为指定类型的凡品兵器。 + 切换兵器:将当前兵器切换为指定类型的练气兵器。 熟练度重置为0。 """ ACTION_NAME = "切换兵器" - DESC = "切换到指定类型的凡品兵器,或卸下兵器。当前兵器会丧失,熟练度会重置为0。适用于想要更换兵器类型或从头修炼新兵器的情况。" + DESC = "切换到指定类型的练气兵器,或卸下兵器。当前兵器会丧失,熟练度会重置为0。适用于想要更换兵器类型或从头修炼新兵器的情况。" DOABLES_REQUIREMENTS = "无前置条件" PARAMS = {"weapon_type_name": "str"} @@ -37,8 +38,8 @@ class SwitchWeapon(InstantAction): if target_weapon_type is None: return - # 获取凡品兵器 - common_weapon = get_common_weapon(target_weapon_type) + # 获取练气兵器(练气期) + common_weapon = get_random_weapon_by_realm(Realm.Qi_Refinement, target_weapon_type) if common_weapon is None: return @@ -67,16 +68,16 @@ class SwitchWeapon(InstantAction): if target_weapon_type is None: return False, f"未知兵器类型: {weapon_type_name}(支持的类型:剑/刀/枪/棍/扇/鞭/琴/笛/暗器/无)" - # 检查是否已经是该类型的凡品兵器 + # 检查是否已经是该类型的练气兵器 if self.avatar.weapon is not None and \ self.avatar.weapon.weapon_type == target_weapon_type and \ - self.avatar.weapon.name == f"凡品{target_weapon_type.value}": - return False, f"已经装备了凡品{target_weapon_type.value}" + self.avatar.weapon.realm == Realm.Qi_Refinement: + return False, f"已经装备了基础{target_weapon_type.value}" - # 检查凡品兵器是否存在 - common_weapon = get_common_weapon(target_weapon_type) + # 检查练气兵器是否存在 + common_weapon = get_random_weapon_by_realm(Realm.Qi_Refinement, target_weapon_type) if common_weapon is None: - return False, f"系统中不存在凡品{target_weapon_type.value}" + return False, f"系统中不存在练气{target_weapon_type.value}" return True, "" @@ -91,7 +92,7 @@ class SwitchWeapon(InstantAction): normalized_type = normalize_weapon_type(weapon_type_name) return Event( self.world.month_stamp, - f"{self.avatar.name} 切换兵器为凡品{normalized_type}", + f"{self.avatar.name} 切换兵器为练气{normalized_type}", related_avatars=[self.avatar.id] ) diff --git a/src/classes/auxiliary.py b/src/classes/auxiliary.py index 67510cb..b7741bd 100644 --- a/src/classes/auxiliary.py +++ b/src/classes/auxiliary.py @@ -5,8 +5,7 @@ from typing import Optional, Dict from src.utils.df import game_configs, get_str, get_int from src.classes.effect import load_effect_from_str -from src.classes.equipment_grade import EquipmentGrade -from src.classes.sect import Sect, sects_by_id +from src.classes.cultivation import Realm @dataclass @@ -14,114 +13,104 @@ class Auxiliary: """ 辅助装备类:提供各种辅助功能的装备 字段与 static/game_configs/auxiliary.csv 对应: - - grade: 装备等级(普通、宝物、法宝) - - sect_id: 对应宗门ID(见 sect.csv);允许为空表示无特定宗门归属 + - realm: 装备等级(练气/筑基/金丹/元婴) - effects: 解析为 dict,用于与 Avatar.effects 合并 """ id: int name: str - grade: EquipmentGrade - sect_id: Optional[int] + realm: Realm desc: str effects: dict[str, object] = field(default_factory=dict) effect_desc: str = "" - sect: Optional[Sect] = None # 特殊属性(用于存储实例特定数据) special_data: dict = field(default_factory=dict) - def __deepcopy__(self, memo): - """ - 自定义深拷贝: - Sect 对象必须保持单例引用,不能深拷贝,否则会复制整个宗门及其所有成员, - 导致内存浪费和潜在的无限递归/哈希错误。 - """ - import copy - cls = self.__class__ - result = cls.__new__(cls) - memo[id(self)] = result - - for k, v in self.__dict__.items(): - if k == 'sect': - # 浅拷贝引用 - setattr(result, k, v) - else: - # 深拷贝其他属性 - setattr(result, k, copy.deepcopy(v, memo)) - return result - def get_info(self) -> str: """获取简略信息""" - return f"{self.name}" + suffix = "" + # 万魂幡特殊显示 + if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0: + suffix = f"(吞噬魂魄:{self.special_data['devoured_souls']})" + return f"{self.name}{suffix}" def get_detailed_info(self) -> str: """获取详细信息""" + souls = "" + if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0: + souls = f" 吞噬魂魄:{self.special_data['devoured_souls']}" + effect_part = f" 效果:{self.effect_desc}" if self.effect_desc else "" - return f"{self.name}({self.grade},{self.desc}){effect_part}" + return f"{self.name}({self.realm.value},{self.desc}{souls}){effect_part}" def get_colored_info(self) -> str: """获取带颜色标记的信息,供前端渲染使用""" - r, g, b = self.grade.color_rgb + r, g, b = self.realm.color_rgb return f"{self.get_info()}" def get_structured_info(self) -> dict: + full_desc = self.desc + # 特殊数据处理 + souls = 0 + if self.name == "万魂幡": + souls = self.special_data.get("devoured_souls", 0) + if souls > 0: + full_desc = f"{full_desc} (已吞噬魂魄:{souls})" + return { "name": self.name, - "desc": self.desc, - "grade": self.grade.value, - "color": self.grade.color_rgb, + "desc": full_desc, + "grade": self.realm.value, + "color": self.realm.color_rgb, "effect_desc": self.effect_desc, } -def _load_auxiliaries() -> tuple[Dict[int, Auxiliary], Dict[str, Auxiliary], Dict[int, Auxiliary]]: +def _load_auxiliaries() -> tuple[Dict[int, Auxiliary], Dict[str, Auxiliary]]: """从配表加载 auxiliary 数据。 - 返回:(按ID、按名称、按宗门ID 的映射)。 - 若同一宗门配置多个辅助装备,按首次出现保留(每门至多一个法宝级)。 + 返回:(按ID、按名称 的映射)。 """ auxiliaries_by_id: Dict[int, Auxiliary] = {} auxiliaries_by_name: Dict[str, Auxiliary] = {} - auxiliaries_by_sect_id: Dict[int, Auxiliary] = {} df = game_configs.get("auxiliary") if df is None: - return auxiliaries_by_id, auxiliaries_by_name, auxiliaries_by_sect_id + return auxiliaries_by_id, auxiliaries_by_name for row in df: - sect_id = get_int(row, "sect_id", -1) - if sect_id == -1: - sect_id = None - effects = load_effect_from_str(get_str(row, "effects")) from src.utils.effect_desc import format_effects_to_text effect_desc = format_effects_to_text(effects) - sect_obj: Optional[Sect] = sects_by_id.get(sect_id) if sect_id is not None else None - # 解析grade - grade_str = get_str(row, "grade", "普通") - grade = EquipmentGrade.COMMON - for g in EquipmentGrade: - if g.value == grade_str: - grade = g - break + grade_str = get_str(row, "grade", "练气") + try: + realm = next(r for r in Realm if r.value == grade_str) + except StopIteration: + realm = Realm.Qi_Refinement a = Auxiliary( id=get_int(row, "id"), name=get_str(row, "name"), - grade=grade, - sect_id=sect_id, + realm=realm, desc=get_str(row, "desc"), effects=effects, effect_desc=effect_desc, - sect=sect_obj, ) auxiliaries_by_id[a.id] = a auxiliaries_by_name[a.name] = a - if a.sect_id is not None and a.sect_id not in auxiliaries_by_sect_id: - auxiliaries_by_sect_id[a.sect_id] = a - return auxiliaries_by_id, auxiliaries_by_name, auxiliaries_by_sect_id + return auxiliaries_by_id, auxiliaries_by_name -auxiliaries_by_id, auxiliaries_by_name, auxiliaries_by_sect_id = _load_auxiliaries() +auxiliaries_by_id, auxiliaries_by_name = _load_auxiliaries() + + +def get_random_auxiliary_by_realm(realm: Realm) -> Optional[Auxiliary]: + """获取指定境界的随机辅助装备""" + import random + import copy + candidates = [a for a in auxiliaries_by_id.values() if a.realm == realm] + if not candidates: + return None + return copy.deepcopy(random.choice(candidates)) diff --git a/src/classes/cultivation.py b/src/classes/cultivation.py index aa25dc7..b5b7692 100644 --- a/src/classes/cultivation.py +++ b/src/classes/cultivation.py @@ -1,6 +1,8 @@ from enum import Enum from functools import total_ordering +from src.classes.color import Color + @total_ordering class Realm(Enum): Qi_Refinement = "练气" @@ -8,6 +10,17 @@ class Realm(Enum): Core_Formation = "金丹" Nascent_Soul = "元婴" + @property + def color_rgb(self) -> tuple[int, int, int]: + """返回境界对应的RGB颜色值""" + color_map = { + Realm.Qi_Refinement: Color.COMMON_WHITE, + Realm.Foundation_Establishment: Color.UNCOMMON_GREEN, + Realm.Core_Formation: Color.EPIC_PURPLE, + Realm.Nascent_Soul: Color.LEGENDARY_GOLD, + } + return color_map.get(self, Color.COMMON_WHITE) + @classmethod def from_id(cls, realm_id: int) -> "Realm": index = realm_id - 1 diff --git a/src/classes/effect.py b/src/classes/effect.py index c5d71ec..032440f 100644 --- a/src/classes/effect.py +++ b/src/classes/effect.py @@ -103,7 +103,7 @@ def _evaluate_conditional_effect(effect: dict[str, Any] | list[dict[str, Any]], 评估后实际生效的effect dict(合并所有满足条件的effects) """ from src.classes.weapon_type import WeaponType - from src.classes.equipment_grade import EquipmentGrade + from src.classes.cultivation import Realm from src.classes.alignment import Alignment # 构建安全的eval上下文 @@ -111,7 +111,7 @@ def _evaluate_conditional_effect(effect: dict[str, Any] | list[dict[str, Any]], "__builtins__": {}, "avatar": avatar, "WeaponType": WeaponType, - "EquipmentGrade": EquipmentGrade, + "Realm": Realm, "Alignment": Alignment, # 常用内置函数 "any": any, diff --git a/src/classes/equipment_grade.py b/src/classes/equipment_grade.py deleted file mode 100644 index 49b468f..0000000 --- a/src/classes/equipment_grade.py +++ /dev/null @@ -1,25 +0,0 @@ -from enum import Enum -from src.classes.color import Color, EQUIPMENT_GRADE_COLORS - - -class EquipmentGrade(Enum): - """ - 装备等级枚举 - """ - COMMON = "普通" # 无限复制,作为兜底 - TREASURE = "宝物" # 可有多个,无数量限制 - ARTIFACT = "法宝" # 全世界唯一 - - def __str__(self) -> str: - return self.value - - @property - def color_rgb(self) -> tuple[int, int, int]: - """返回装备等级对应的RGB颜色值""" - color_map = { - EquipmentGrade.COMMON: EQUIPMENT_GRADE_COLORS["COMMON"], - EquipmentGrade.TREASURE: EQUIPMENT_GRADE_COLORS["TREASURE"], - EquipmentGrade.ARTIFACT: EQUIPMENT_GRADE_COLORS["ARTIFACT"], - } - return color_map.get(self, Color.COMMON_WHITE) - diff --git a/src/classes/fortune.py b/src/classes/fortune.py index 483930c..c6aa261 100644 --- a/src/classes/fortune.py +++ b/src/classes/fortune.py @@ -17,11 +17,11 @@ from src.classes.technique import ( is_attribute_compatible_with_root, TechniqueAttribute, ) -from src.classes.weapon import Weapon, weapons_by_id -from src.classes.auxiliary import Auxiliary, auxiliaries_by_id -from src.classes.equipment_grade import EquipmentGrade +from src.classes.weapon import Weapon, get_random_weapon_by_realm +from src.classes.auxiliary import Auxiliary, get_random_auxiliary_by_realm from src.classes.relation import Relation from src.classes.alignment import Alignment +from src.classes.cultivation import Realm class FortuneKind(Enum): @@ -150,17 +150,17 @@ def _find_potential_master(avatar: Avatar) -> Optional[Avatar]: def _can_get_weapon(avatar: Avatar) -> bool: - """检查是否可以获得兵器奇遇:当前兵器是普通级时可触发""" + """检查是否可以获得兵器奇遇:当前兵器是练气级(凡品)时可触发""" if avatar.weapon is None: return True - return avatar.weapon.grade == EquipmentGrade.COMMON + return avatar.weapon.realm == Realm.Qi_Refinement def _can_get_auxiliary(avatar: Avatar) -> bool: - """检查是否可以获得辅助装备奇遇:无辅助装备或辅助装备非法宝级时可触发""" + """检查是否可以获得辅助装备奇遇:无辅助装备或辅助装备是练气级时可触发""" if avatar.auxiliary is None: return True - return avatar.auxiliary.grade != EquipmentGrade.ARTIFACT + return avatar.auxiliary.realm == Realm.Qi_Refinement def _can_get_technique(avatar: Avatar) -> bool: @@ -247,56 +247,28 @@ def _pick_theme(kind: FortuneKind) -> str: def _get_weapon_for_avatar(avatar: Avatar) -> Optional[Weapon]: """ - 获取兵器:优先法宝 > 宝物 > 普通 - - 法宝:检查世界唯一性 - - 宝物:可重复 - - 普通:可重复 + 获取兵器: + 奇遇通常提供比当前境界更好的兵器。 + 如果是练气期,提供筑基期兵器。 + 其他境界提供同境界兵器(因为高境界兵器本身就稀有且强)。 """ - # 尝试获取法宝级兵器 - owned_artifact_ids: set[int] = set() - for other in avatar.world.avatar_manager.avatars.values(): - if other.weapon is not None and other.weapon.grade == EquipmentGrade.ARTIFACT: - owned_artifact_ids.add(other.weapon.id) - - artifact_candidates = [w for w in weapons_by_id.values() - if w.grade == EquipmentGrade.ARTIFACT and w.id not in owned_artifact_ids] - if artifact_candidates: - return random.choice(artifact_candidates) - - # 尝试获取宝物级兵器 - treasure_candidates = [w for w in weapons_by_id.values() - if w.grade == EquipmentGrade.TREASURE] - if treasure_candidates: - return random.choice(treasure_candidates) - - # 回退到普通级兵器(理论上不应该走到这里,因为普通级兵器不应该通过奇遇获得) - return None + target_realm = avatar.cultivation_progress.realm + if target_realm == Realm.Qi_Refinement: + target_realm = Realm.Foundation_Establishment + + return get_random_weapon_by_realm(target_realm) def _get_auxiliary_for_avatar(avatar: Avatar) -> Optional[Auxiliary]: """ - 获取辅助装备:优先法宝 > 宝物 - - 法宝:检查世界唯一性 - - 宝物:可重复 + 获取辅助装备: + 规则同兵器。 """ - # 尝试获取法宝级辅助装备 - owned_artifact_ids: set[int] = set() - for other in avatar.world.avatar_manager.avatars.values(): - if other.auxiliary is not None and other.auxiliary.grade == EquipmentGrade.ARTIFACT: - owned_artifact_ids.add(other.auxiliary.id) - - artifact_candidates = [a for a in auxiliaries_by_id.values() - if a.grade == EquipmentGrade.ARTIFACT and a.id not in owned_artifact_ids] - if artifact_candidates: - return random.choice(artifact_candidates) - - # 尝试获取宝物级辅助装备 - treasure_candidates = [a for a in auxiliaries_by_id.values() - if a.grade == EquipmentGrade.TREASURE] - if treasure_candidates: - return random.choice(treasure_candidates) - - return None + target_realm = avatar.cultivation_progress.realm + if target_realm == Realm.Qi_Refinement: + target_realm = Realm.Foundation_Establishment + + return get_random_auxiliary_by_realm(target_realm) def _get_fortune_technique_for_avatar(avatar: Avatar) -> Optional[Technique]: @@ -436,34 +408,34 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]: Returns: (should_replace, result_text) """ new_name = new_obj.name - new_grade = new_obj.grade.value + new_grade_val = getattr(new_obj, "grade", getattr(new_obj, "realm", None)).value if old_obj is None: - return True, f"{avatar.name} 获得{new_grade}{type_label}『{new_name}』" + return True, f"{avatar.name} 获得{new_grade_val}{type_label}『{new_name}』" old_name = old_obj.name - old_grade = old_obj.grade.value + old_grade_val = getattr(old_obj, "grade", getattr(old_obj, "realm", None)).value options = [ { "key": "A", - "desc": f"保留原{type_label}『{old_name}』({old_grade}),放弃新{type_label}『{new_name}』({new_grade})。" + "desc": f"保留原{type_label}『{old_name}』({old_grade_val}),放弃新{type_label}『{new_name}』({new_grade_val})。" }, { "key": "B", - "desc": f"放弃原{type_label},接受新{type_label}『{new_name}』({new_grade})。" + "desc": f"放弃原{type_label},接受新{type_label}『{new_name}』({new_grade_val})。" } ] - base_context = f"你在奇遇中发现了{new_grade}{type_label}『{new_name}』,但你手中已有『{old_name}』。" + base_context = f"你在奇遇中发现了{new_grade_val}{type_label}『{new_name}』,但你手中已有『{old_name}』。" context = f"{base_context} {extra_context}".strip() choice = await make_decision(avatar, context, options) if choice == "A": - return False, f"{avatar.name} 放弃了{new_grade}{type_label}『{new_name}』,保留了『{old_name}』" + return False, f"{avatar.name} 放弃了{new_grade_val}{type_label}『{new_name}』,保留了『{old_name}』" else: - return True, f"{avatar.name} 获得了{new_grade}{type_label}『{new_name}』,替换了『{old_name}』" + return True, f"{avatar.name} 获得了{new_grade_val}{type_label}『{new_name}』,替换了『{old_name}』" if kind == FortuneKind.WEAPON: weapon = _get_weapon_for_avatar(avatar) diff --git a/src/classes/kill_and_grab.py b/src/classes/kill_and_grab.py index 65ae898..bf4a952 100644 --- a/src/classes/kill_and_grab.py +++ b/src/classes/kill_and_grab.py @@ -2,7 +2,6 @@ from __future__ import annotations from typing import TYPE_CHECKING import random -from src.classes.equipment_grade import EquipmentGrade from src.classes.single_choice import make_decision if TYPE_CHECKING: @@ -23,37 +22,37 @@ async def kill_and_grab(winner: Avatar, loser: Avatar) -> str: loot_candidates = [] # 检查兵器 - if loser.weapon and loser.weapon.grade != EquipmentGrade.COMMON: + if loser.weapon: loot_candidates.append(("weapon", loser.weapon)) # 检查辅助装备 - if loser.auxiliary and loser.auxiliary.grade != EquipmentGrade.COMMON: + if loser.auxiliary: loot_candidates.append(("auxiliary", loser.auxiliary)) if not loot_candidates: return "" - # 优先选法宝,其次宝物;如果有多个同级,随机选一个 - loot_candidates.sort(key=lambda x: 1 if x[1].grade == EquipmentGrade.ARTIFACT else 0, reverse=True) + # 优先高境界 + loot_candidates.sort(key=lambda x: x[1].realm, reverse=True) # 筛选出最高优先级的那些 - best_grade = loot_candidates[0][1].grade - best_candidates = [c for c in loot_candidates if c[1].grade == best_grade] + best_realm = loot_candidates[0][1].realm + best_candidates = [c for c in loot_candidates if c[1].realm == best_realm] loot_type, loot_item = random.choice(best_candidates) should_loot = False # 判定是否夺取 - # 1. 如果winner当前部位为空或为凡品,直接夺取 + # 1. 如果winner当前部位为空,直接夺取 winner_current = getattr(winner, loot_type) - if winner_current is None or winner_current.grade == EquipmentGrade.COMMON: + if winner_current is None : should_loot = True else: - # 2. 否则让 AI 决策 + # 其他情况下都让 AI 决策 # 构建详细描述,包含效果 item_desc = loot_item.get_detailed_info() current_desc = winner_current.get_detailed_info() - context = f"战斗胜利,{loser.name} 身死道消,留下了一件{loot_item.grade.value}{'兵器' if loot_type == 'weapon' else '辅助装备'}『{item_desc}』。" + context = f"战斗胜利,{loser.name} 身死道消,留下了一件{loot_item.realm.value}{'兵器' if loot_type == 'weapon' else '辅助装备'}『{item_desc}』。" options = [ { "key": "A", @@ -71,13 +70,12 @@ async def kill_and_grab(winner: Avatar, loser: Avatar) -> str: if should_loot: if loot_type == "weapon": winner.change_weapon(loot_item) - from src.classes.weapon import get_common_weapon - loser.change_weapon(get_common_weapon(loot_item.weapon_type)) # 给死者塞个凡品防止空指针 + loser.change_weapon(None) else: winner.change_auxiliary(loot_item) loser.change_auxiliary(None) - return f"{winner.name}夺取了对方的{loot_item.grade.value}『{loot_item.name}』!" + return f"{winner.name}夺取了对方的{loot_item.realm.value}『{loot_item.name}』!" return "" diff --git a/src/classes/weapon.py b/src/classes/weapon.py index b2078f1..ce8ecd2 100644 --- a/src/classes/weapon.py +++ b/src/classes/weapon.py @@ -1,13 +1,14 @@ from __future__ import annotations +import copy +import random from dataclasses import dataclass, field from typing import Optional, Dict from src.utils.df import game_configs, get_str, get_int from src.classes.effect import load_effect_from_str -from src.classes.equipment_grade import EquipmentGrade +from src.classes.cultivation import Realm from src.classes.weapon_type import WeaponType -from src.classes.sect import Sect, sects_by_id @dataclass @@ -16,110 +17,60 @@ class Weapon: 兵器类:用于战斗的装备 字段与 static/game_configs/weapon.csv 对应: - weapon_type: 兵器类型(剑、刀、枪等) - - grade: 装备等级(普通、宝物、法宝) - - sect_id: 对应宗门ID(见 sect.csv);允许为空表示无特定宗门归属 + - realm: 装备等级(练气/筑基/金丹/元婴) - effects: 解析为 dict,用于与 Avatar.effects 合并 """ id: int name: str weapon_type: WeaponType - grade: EquipmentGrade - sect_id: Optional[int] + realm: Realm desc: str effects: dict[str, object] = field(default_factory=dict) effect_desc: str = "" - sect: Optional[Sect] = None # 特殊属性(如万魂幡的吞噬魂魄计数) special_data: dict = field(default_factory=dict) - def __deepcopy__(self, memo): - """ - 自定义深拷贝: - Sect 对象必须保持单例引用,不能深拷贝,否则会复制整个宗门及其所有成员, - 导致内存浪费和潜在的无限递归/哈希错误。 - """ - import copy - cls = self.__class__ - result = cls.__new__(cls) - memo[id(self)] = result - - for k, v in self.__dict__.items(): - if k == 'sect': - # 浅拷贝引用 - setattr(result, k, v) - else: - # 深拷贝其他属性 - setattr(result, k, copy.deepcopy(v, memo)) - return result - def get_info(self) -> str: """获取简略信息""" - suffix = "" - # 万魂幡特殊显示 - if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0: - suffix = f"(吞噬魂魄:{self.special_data['devoured_souls']})" - return f"{self.name}{suffix}" + return f"{self.name}" def get_detailed_info(self) -> str: """获取详细信息""" - souls = "" - if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0: - souls = f" 吞噬魂魄:{self.special_data['devoured_souls']}" - effect_part = f" 效果:{self.effect_desc}" if self.effect_desc else "" - return f"{self.name}({self.weapon_type}·{self.grade},{self.desc}{souls}){effect_part}" + return f"{self.name}({self.weapon_type}·{self.realm.value},{self.desc}){effect_part}" def get_colored_info(self) -> str: """获取带颜色标记的信息,供前端渲染使用""" - r, g, b = self.grade.color_rgb + r, g, b = self.realm.color_rgb return f"{self.get_info()}" def get_structured_info(self) -> dict: - - # 基础描述 - full_desc = self.desc - - # 特殊数据处理 - souls = 0 - if self.name == "万魂幡": - souls = self.special_data.get("devoured_souls", 0) - if souls > 0: - full_desc = f"{full_desc} (已吞噬魂魄:{souls})" - return { "name": self.name, - "desc": full_desc, - "grade": self.grade.value, - "color": self.grade.color_rgb, + "desc": self.desc, + "grade": self.realm.value, + "color": self.realm.color_rgb, "type": self.weapon_type.value, "effect_desc": self.effect_desc, } -def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon], Dict[int, Weapon]]: +def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon]]: """从配表加载 weapon 数据。 - 返回:(按ID、按名称、按宗门ID 的映射)。 - 若同一宗门配置多个兵器,按首次出现保留(每门至多一个法宝级)。 + 返回:(按ID、按名称 的映射)。 """ weapons_by_id: Dict[int, Weapon] = {} weapons_by_name: Dict[str, Weapon] = {} - weapons_by_sect_id: Dict[int, Weapon] = {} df = game_configs.get("weapon") if df is None: - return weapons_by_id, weapons_by_name, weapons_by_sect_id + return weapons_by_id, weapons_by_name for row in df: - sect_id = get_int(row, "sect_id", -1) - if sect_id == -1: - sect_id = None - effects = load_effect_from_str(get_str(row, "effects")) from src.utils.effect_desc import format_effects_to_text effect_desc = format_effects_to_text(effects) - sect_obj: Optional[Sect] = sects_by_id.get(sect_id) if sect_id is not None else None - # 解析weapon_type weapon_type_str = get_str(row, "weapon_type") weapon_type = None @@ -129,51 +80,40 @@ def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon], Dict[int, Wea break if weapon_type is None: - # 如果找不到对应类型,可以决定是跳过还是抛错 - # 这里保持原有逻辑,如果是空或者非法,抛错提示配置问题 raise ValueError(f"武器 {get_str(row, 'name')} 的weapon_type '{weapon_type_str}' 无效,必须是有效的兵器类型") # 解析grade - grade_str = get_str(row, "grade", "普通") - grade = EquipmentGrade.COMMON - for g in EquipmentGrade: - if g.value == grade_str: - grade = g - break + grade_str = get_str(row, "grade", "练气") + try: + realm = next(r for r in Realm if r.value == grade_str) + except StopIteration: + realm = Realm.Qi_Refinement w = Weapon( id=get_int(row, "id"), name=get_str(row, "name"), weapon_type=weapon_type, - grade=grade, - sect_id=sect_id, + realm=realm, desc=get_str(row, "desc"), effects=effects, effect_desc=effect_desc, - sect=sect_obj, ) weapons_by_id[w.id] = w weapons_by_name[w.name] = w - if w.sect_id is not None and w.sect_id not in weapons_by_sect_id: - weapons_by_sect_id[w.sect_id] = w - return weapons_by_id, weapons_by_name, weapons_by_sect_id + return weapons_by_id, weapons_by_name -weapons_by_id, weapons_by_name, weapons_by_sect_id = _load_weapons() +weapons_by_id, weapons_by_name = _load_weapons() -def get_common_weapon(weapon_type: WeaponType) -> Optional[Weapon]: - """获取指定类型的凡品兵器(用于兜底)""" - weapon_name = f"凡品{weapon_type.value}" - return weapons_by_name.get(weapon_name) - - -def get_treasure_weapon(weapon_type: WeaponType) -> Optional[Weapon]: - """获取指定类型的宝物级兵器""" - from src.classes.equipment_grade import EquipmentGrade - for weapon in weapons_by_id.values(): - if weapon.weapon_type == weapon_type and weapon.grade == EquipmentGrade.TREASURE: - return weapon - return None +def get_random_weapon_by_realm(realm: Realm, weapon_type: Optional[WeaponType] = None) -> Optional[Weapon]: + """获取指定境界(及可选类型)的随机兵器""" + candidates = [w for w in weapons_by_id.values() if w.realm == realm] + if weapon_type is not None: + candidates = [w for w in candidates if w.weapon_type == weapon_type] + + if not candidates: + return None + return copy.deepcopy(random.choice(candidates)) diff --git a/src/server/main.py b/src/server/main.py index b3cc077..616fbeb 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -869,8 +869,7 @@ def get_game_data(): "id": w.id, "name": w.name, "type": w.weapon_type.value, - "grade": w.grade.value, - "sect_id": w.sect_id + "grade": w.realm.value, } for w in weapons_by_id.values() ] @@ -879,8 +878,7 @@ def get_game_data(): { "id": a.id, "name": a.name, - "grade": a.grade.value, - "sect_id": a.sect_id + "grade": a.realm.value, } for a in auxiliaries_by_id.values() ] diff --git a/src/sim/new_avatar.py b/src/sim/new_avatar.py index f1e2fef..dcb487e 100644 --- a/src/sim/new_avatar.py +++ b/src/sim/new_avatar.py @@ -61,32 +61,6 @@ def random_gender() -> Gender: return Gender.MALE if random.random() < 0.5 else Gender.FEMALE -def _get_equipment_probabilities_by_realm(realm, equipment_type: str) -> tuple[float, float]: - """ - 根据境界和装备类型获取法宝、宝物的概率 - - Args: - realm: 角色境界 - equipment_type: 'weapon' 或 'auxiliary' - - Returns: - (artifact_prob, treasure_prob): 法宝概率和宝物概率 - """ - if equipment_type == 'weapon': - return (0.05, 0.25) # 武器:5%法宝,25%宝物 - - # 辅助装备:根据境界分层 - auxiliary_probs = { - 'Qi_Refinement': (0.05, 0.20), - 'Foundation_Establishment': (0.10, 0.35), - 'Core_Formation': (0.20, 0.50), - 'Nascent_Soul': (0.35, 0.60), - 'default': (0.05, 0.20), - } - realm_name = realm.name if hasattr(realm, 'name') else 'default' - return auxiliary_probs.get(realm_name, auxiliary_probs['default']) - - class EquipmentAllocator: """ 负责所有初始装备分配逻辑,提供兵器与辅助装备的统一接口。 @@ -97,10 +71,9 @@ class EquipmentAllocator: """ 初始兵器逻辑: - 80% 继承宗门偏好兵器类型,否则完全随机 - - 先按境界概率抽高品质(约 1% 法宝、5% 宝物),优先宗门池 - - 若高品质落空,再发一把普通兵器 + - 根据境界随机生成一把兵器 """ - from src.classes.weapon import get_common_weapon, weapons_by_id, weapons_by_sect_id + from src.classes.weapon import get_random_weapon_by_realm from src.classes.weapon_type import WeaponType weapon_type = None @@ -110,103 +83,18 @@ class EquipmentAllocator: if wt.value == avatar.sect.preferred_weapon: weapon_type = wt break - if weapon_type is None: - weapon_type = random.choice(list(WeaponType)) - - high_grade_weapon = EquipmentAllocator._assign_generic( - avatar, - 'weapon', - weapons_by_id, - weapons_by_sect_id, - weapon_type - ) - - if high_grade_weapon is not None: - avatar.weapon = high_grade_weapon - return - - avatar.weapon = get_common_weapon(weapon_type) + + avatar.weapon = get_random_weapon_by_realm(avatar.cultivation_progress.realm, weapon_type) @staticmethod def assign_auxiliary(avatar: Avatar) -> None: """ - 初始辅助装备逻辑(境界越高概率越高): - - 练气:约 5% 宝物 - - 筑基:≈ 2% 法宝 + 8% 宝物 - - 金丹:≈ 5% 法宝 + 15% 宝物 - - 元婴:≈ 10% 法宝 + 20% 宝物 - 同样优先宗门专属辅助装备。 + 初始辅助装备逻辑: + - 根据境界随机生成一件辅助装备 """ - from src.classes.auxiliary import auxiliaries_by_id, auxiliaries_by_sect_id - - auxiliary = EquipmentAllocator._assign_generic( - avatar, - 'auxiliary', - auxiliaries_by_id, - auxiliaries_by_sect_id, - None - ) - - if auxiliary is not None: - avatar.auxiliary = auxiliary - - @staticmethod - def _find_by_grade( - avatar: Avatar, - grade, - equipment_pool_by_id: dict, - equipment_pool_by_sect_id: dict, - weapon_type_filter = None, - ): - if avatar.sect is not None and avatar.sect.id in equipment_pool_by_sect_id: - candidate = equipment_pool_by_sect_id[avatar.sect.id] - if candidate.grade == grade: - return candidate - - candidates = [ - e for e in equipment_pool_by_id.values() - if e.grade == grade - and (weapon_type_filter is None or getattr(e, "weapon_type", None) == weapon_type_filter) - ] - return random.choice(candidates) if candidates else None - - @staticmethod - def _assign_generic( - avatar: Avatar, - equipment_type: str, - equipment_pool_by_id: dict, - equipment_pool_by_sect_id: dict, - weapon_type_filter = None, - ) -> Optional[object]: - from src.classes.equipment_grade import EquipmentGrade - import copy - - artifact_prob, treasure_prob = _get_equipment_probabilities_by_realm( - avatar.cultivation_progress.realm, - equipment_type - ) - - roll = random.random() - - if roll < artifact_prob: - equipment = EquipmentAllocator._find_by_grade( - avatar, EquipmentGrade.ARTIFACT, - equipment_pool_by_id, equipment_pool_by_sect_id, - weapon_type_filter - ) - if equipment: - return copy.deepcopy(equipment) - - if roll < artifact_prob + treasure_prob: - equipment = EquipmentAllocator._find_by_grade( - avatar, EquipmentGrade.TREASURE, - equipment_pool_by_id, equipment_pool_by_sect_id, - weapon_type_filter - ) - if equipment: - return copy.deepcopy(equipment) - - return None + from src.classes.auxiliary import get_random_auxiliary_by_realm + + avatar.auxiliary = get_random_auxiliary_by_realm(avatar.cultivation_progress.realm) @dataclass diff --git a/static/game_configs/auxiliary.csv b/static/game_configs/auxiliary.csv index c0de526..eaa985d 100644 --- a/static/game_configs/auxiliary.csv +++ b/static/game_configs/auxiliary.csv @@ -1,27 +1,27 @@ -id,name,grade,sect_id,desc,effects -,名称,等级(普通/宝物/法宝),所属宗门ID(见sect.csv),描述/提示词,"JSON形式(支持宽松格式,见effects.py说明)" -2001,木灵甲,宝物,7,以灵木纤维编织,生机盎然,防御尚可.,{damage_reduction: 0.08} -2002,血菩提,宝物,,生于火麒麟滴血之处,重伤必治,无伤增功.,{extra_hp_recovery_rate: 0.3} -2003,人参果(伪),宝物,,虽不及真品,闻一闻也能活三百六十岁.,{extra_max_lifespan: 50} -2004,万宝令,宝物,9,千帆城通用的最高级商令,见令如见城主.,{extra_item_sell_price_multiplier: 0.2} -2005,铁精,宝物,5,提炼百斤凡铁才得一两,提升法器品质的基础材料.,{extra_weapon_upgrade_chance: 0.05} -2006,源天神眼,宝物,3,可勘破虚妄,直视本源,寻龙定穴.,"{extra_observation_radius: 1, extra_fortune_probability: 0.005}" -2007,踏云靴,宝物,8,步履生云,身轻如燕,赶路必备.,"{extra_escape_success_rate: 0.15, extra_move_step: 1}" -2008,菩提子,宝物,7,佛门至宝,持之可灵台清明,悟性大增.,{extra_breakthrough_success_rate: 0.1} -2009,金丝软甲,宝物,1,刀枪不入,水火不侵,贴身软甲.,"{extra_max_hp: 100, damage_reduction: 0.05}" -2010,寒玉床,宝物,,古墓派至宝,在此床上修炼,事半功倍,且无心魔之虞.,{cultivate_duration_reduction: 0.2} -2011,无相面具,宝物,,千人千面,不仅掩盖容貌,更能掩盖气息.,{extra_plunder_multiplier: 0.5} -3001,神风舟,法宝,9,飞行法器,速度极快,居家旅行杀人越货必备.,{extra_move_step: 1} -3002,昆仑镜,法宝,3,上古神器,拥有穿梭时空之力,此为仿品,仅能窥探远方.,{extra_observation_radius: 2} -3003,掌天瓶,法宝,5,夺天地之造化,聚日月之精华,催熟万物.,{extra_cultivate_exp: 50} -3004,龙凤呈祥戒,法宝,6,龙凤和鸣,心意相通,双修进境一日千里.,{extra_dual_cultivation_exp: 100} -3005,隐形披风,法宝,8,披上后身形尽隐,连神识也难以察觉.,"{extra_move_step: 1, extra_escape_success_rate: 0.2}" -3006,御兽环,法宝,2,套在妖兽颈上,任其凶焰滔天,也要乖乖听命.,{extra_catch_success_rate: 0.1} -3007,太虚甲,法宝,7,太虚幻境所化,万法不沾,防御力惊人.,"{damage_reduction: 0.2, extra_max_hp: 200}" -3008,八卦炉,法宝,4,仿太上老君炼丹炉,丹成九转,药效倍增.,"{extra_max_lifespan: 150, extra_hp_recovery_rate: 0.5}" -3009,聚宝盆,法宝,9,沈万三的宝物,放入一文,取出万贯,财源滚滚.,"{extra_item_sell_price_multiplier: 0.5, extra_fortune_probability: 0.01}" -3010,天地烘炉,法宝,5,以天地为炉,造化为工,炼制万物.,"{extra_weapon_upgrade_chance: 0.15, extra_weapon_proficiency_gain: 0.5}" -3011,六道剑匣,法宝,1,背负六道,剑出轮回,悟剑良伴.,"{extra_breakthrough_success_rate: 0.2, extra_cultivate_exp: 100}" -3012,传国玉玺,法宝,,受命于天,既寿永昌。皇威浩荡,震慑宵小.,{realm_suppression_bonus: 0.15} -3013,灵眼之泉,法宝,5,可移动的灵气之源,随身携带的洞天福地.,"{cultivate_duration_reduction: 0.3, extra_cultivate_exp: 50}" -3014,万魂幡,法宝,,血海不枯,冥河不死。极度邪恶的掠夺法宝.,"{legal_actions: ['DevourMortals'], extra_plunder_multiplier: 1.0, realm_suppression_bonus: 0.15, extra_battle_strength_points: 'avatar.auxiliary.special_data.get(""devoured_souls"", 0) // 100 * 0.1'}" +id,name,grade,desc,effects +,名称,等级(练气/筑基/金丹/元婴),描述/提示词,JSON形式(支持宽松格式,见effects.py说明) +2001,木灵甲,筑基,以灵木纤维编织,生机盎然,防御尚可.,{damage_reduction: 0.08} +2002,血菩提,筑基,生于火麒麟滴血之处,重伤必治,无伤增功.,{extra_hp_recovery_rate: 0.3} +2003,人参果(伪),筑基,虽不及真品,闻一闻也能活三百六十岁.,{extra_max_lifespan: 50} +2004,万宝令,筑基,千帆城通用的最高级商令,见令如见城主.,{extra_item_sell_price_multiplier: 0.2} +2005,铁精,筑基,提炼百斤凡铁才得一两,提升法器品质的基础材料.,{extra_weapon_upgrade_chance: 0.05} +2006,源天神眼,筑基,可勘破虚妄,直视本源,寻龙定穴.,"{extra_observation_radius: 1, extra_fortune_probability: 0.005}" +2007,踏云靴,筑基,步履生云,身轻如燕,赶路必备.,"{extra_escape_success_rate: 0.15, extra_move_step: 1}" +2008,菩提子,筑基,佛门至宝,持之可灵台清明,悟性大增.,{extra_breakthrough_success_rate: 0.1} +2009,金丝软甲,筑基,刀枪不入,水火不侵,贴身软甲.,"{extra_max_hp: 100, damage_reduction: 0.05}" +2010,寒玉床,筑基,古墓派至宝,在此床上修炼,事半功倍,且无心魔之虞.,{cultivate_duration_reduction: 0.2} +2011,无相面具,筑基,千人千面,不仅掩盖容貌,更能掩盖气息.,{extra_plunder_multiplier: 0.5} +3001,神风舟,金丹,飞行法器,速度极快,居家旅行杀人越货必备.,{extra_move_step: 1} +3002,昆仑镜,金丹,上古神器,拥有穿梭时空之力,此为仿品,仅能窥探远方.,{extra_observation_radius: 2} +3003,掌天瓶,元婴,夺天地之造化,聚日月之精华,催熟万物.,{extra_cultivate_exp: 50} +3004,龙凤呈祥戒,金丹,龙凤和鸣,心意相通,双修进境一日千里.,{extra_dual_cultivation_exp: 100} +3005,隐形披风,金丹,披上后身形尽隐,连神识也难以察觉.,"{extra_move_step: 1, extra_escape_success_rate: 0.2}" +3006,御兽环,金丹,套在妖兽颈上,任其凶焰滔天,也要乖乖听命.,{extra_catch_success_rate: 0.1} +3007,太虚甲,金丹,太虚幻境所化,万法不沾,防御力惊人.,"{damage_reduction: 0.2, extra_max_hp: 200}" +3008,八卦炉,金丹,仿太上老君炼丹炉,丹成九转,药效倍增.,"{extra_max_lifespan: 150, extra_hp_recovery_rate: 0.5}" +3009,聚宝盆,金丹,沈万三的宝物,放入一文,取出万贯,财源滚滚.,"{extra_item_sell_price_multiplier: 0.5, extra_fortune_probability: 0.01}" +3010,天地烘炉,金丹,以天地为炉,造化为工,炼制万物.,"{extra_weapon_upgrade_chance: 0.15, extra_weapon_proficiency_gain: 0.5}" +3011,六道剑匣,金丹,背负六道,剑出轮回,悟剑良伴.,"{extra_breakthrough_success_rate: 0.2, extra_cultivate_exp: 100}" +3012,传国玉玺,元婴,受命于天,既寿永昌。皇威浩荡,震慑宵小.,{realm_suppression_bonus: 0.15} +3013,灵眼之泉,金丹,可移动的灵气之源,随身携带的洞天福地.,"{cultivate_duration_reduction: 0.3, extra_cultivate_exp: 50}" +3014,万魂幡,元婴,血海不枯,冥河不死。极度邪恶的掠夺法宝.,"{legal_actions: ['DevourMortals'], extra_plunder_multiplier: 1.0, realm_suppression_bonus: 0.15, extra_battle_strength_points: 'avatar.auxiliary.special_data.get(""devoured_souls"", 0) // 100 * 0.1'}" diff --git a/static/game_configs/weapon.csv b/static/game_configs/weapon.csv index e2fcc36..e9d617a 100644 --- a/static/game_configs/weapon.csv +++ b/static/game_configs/weapon.csv @@ -1,35 +1,35 @@ -id,name,weapon_type,grade,sect_id,desc,effects -,名称,兵器类型,等级(普通/宝物/法宝),所属宗门ID(见sect.csv),描述/提示词,"JSON形式(支持宽松格式,见effects.py说明)" -1001,凡品剑,剑,普通,,市井铁匠铺打造的铁剑,勉强能用。,{extra_battle_strength_points: 1} -1002,凡品刀,刀,普通,,厚背砍刀,寻常武夫的最爱。,{extra_battle_strength_points: 1} -1003,凡品枪,枪,普通,,白蜡杆制成,枪头挂着红缨。,{extra_battle_strength_points: 1} -1004,凡品棍,棍,普通,,普通的硬木棍,长与眉齐。,{extra_battle_strength_points: 1} -1005,凡品扇,扇,普通,,文人雅士常用的折扇,附庸风雅。,{extra_battle_strength_points: 1} -1006,凡品鞭,鞭,普通,,普通的皮鞭,多用于赶车或刑罚。,{extra_battle_strength_points: 1} -1007,凡品琴,琴,普通,,桐木制成的古琴,音色尚可。,{extra_battle_strength_points: 1} -1008,凡品笛,笛,普通,,紫竹削制,声音清脆。,{extra_battle_strength_points: 1} -1009,凡品暗器,暗器,普通,,随处可见的铁制暗器。,{extra_battle_strength_points: 1} -2001,青霜剑,剑,宝物,,剑气森寒,据说曾斩过白蛇。,{extra_battle_strength_points: 2} -2002,井中月,刀,宝物,,刀光如井中映月,虚实难测。,{extra_battle_strength_points: 2} -2003,火尖枪,枪,宝物,,枪头隐有火光,能引动凡火。,"{extra_battle_strength_points: 2, extra_max_hp: 50}" -2004,囚龙棒,棍,宝物,,精铁铸造,重达千斤,力大者可用。,"{extra_battle_strength_points: 2, extra_max_hp: 50}" -2005,桃花扇,扇,宝物,,桃花影落飞神剑,扇面绘有桃花,暗藏杀机。,"{extra_battle_strength_points: 2, extra_observation_radius: 1}" -2006,打神鞭,鞭,宝物,,仿品,虽无打神之能,却可伤人神魂。,{extra_battle_strength_points: 2} -2007,天魔琴,琴,宝物,,琴音魔性,扰乱心智。,"{extra_battle_strength_points: 2, cultivate_duration_reduction: 0.1}" -2008,鬼笛陈情,笛,宝物,,据说能以此笛操控尸傀。,"{extra_battle_strength_points: 2, cultivate_duration_reduction: 0.1}" -2009,无影针,暗器,宝物,,细若牛毛,来去无影,防不胜防。,{extra_battle_strength_points: 2} -2010,万兽笛,笛,宝物,2,百兽宗秘制,笛声可安抚或激怒野兽。,"[{extra_battle_strength_points: 1}, {when: 'avatar.spirit_animal is not None', extra_battle_strength_points: 2}]" -2011,血饮狂刀,刀,宝物,,刀身赤红,似渴望饮血。,{extra_battle_strength_points: 2} -2012,玄铁重剑,剑,宝物,,重剑无锋,大巧不工,独孤求败。,"{extra_battle_strength_points: 2, extra_max_hp: 100}" -2013,紫薇软剑,剑,宝物,,软若束带,动若灵蛇。,"{extra_battle_strength_points: 2, extra_escape_success_rate: 0.1}" -2014,水火囚龙棍,棍,宝物,7,镇魂宗制式兵器,水火相济,困敌锁魂。,"{extra_battle_strength_points: 2}" -3001,青竹蜂云剑,剑,法宝,1,成套飞剑,内蕴辟邪神雷,克制天下邪祟。,{extra_battle_strength_points: 3} -3002,随心铁杆兵,棍,法宝,7,六耳猕猴的兵器,随心变化,大小如意。,"{extra_battle_strength_points: 2, extra_observation_radius: 1}" -3003,五火七禽扇,扇,法宝,4,扇面有空中火、石中火、木中火等五火,一扇灰飞烟灭。,"{extra_battle_strength_points: 3, extra_observation_radius: 2}" -3004,弑神枪,枪,法宝,9,杀气滔天,曾染魔神之血,枪出无回。,"{extra_battle_strength_points: '2 + avatar.weapon_proficiency * 0.01'}" -3005,赤锋矛,枪,法宝,,赤锋矛,不朽盾,斩尽仙王灭九天。,"[{extra_battle_strength_points: 2}, {when: 'avatar.cultivation_progress.level >= 30', extra_battle_strength_points: 2, realm_suppression_bonus: 0.1}]" -3006,斩仙飞刀,暗器,法宝,5,红葫芦内藏一线毫光,有眉有目。请宝贝转身,神鬼难逃。,"{extra_battle_strength_points: 3, extra_observation_radius: 1, extra_escape_success_rate: 0.15}" -3007,诛仙剑,剑,法宝,1,非铜非铁亦非钢,曾在须弥山下藏。利气直透九重天。,{extra_battle_strength_points: 3} -3008,干将莫邪,剑,法宝,,挚情之剑,一雌一雄,分则为杀,合则为情。,"{extra_battle_strength_points: 2, extra_max_hp: 100}" -3009,芭蕉扇,扇,法宝,,太阴之精叶,一扇息火,二扇生风,三扇下雨。,"{extra_battle_strength_points: 2, extra_observation_radius: 2, extra_move_step: 1}" -3010,紫金降魔杵,棍,法宝,,韦护的兵器,虽然轻灵,打在人身却重如泰山。,"{extra_battle_strength_points: 3, damage_reduction: 0.1}" +id,name,weapon_type,grade,desc,effects +,名称,兵器类型,等级(练气/筑基/金丹/元婴),描述/提示词,JSON形式(支持宽松格式,见effects.py说明) +1001,凡品剑,剑,练气,市井铁匠铺打造的铁剑,勉强能用。,{extra_battle_strength_points: 1} +1002,凡品刀,刀,练气,厚背砍刀,寻常武夫的最爱。,{extra_battle_strength_points: 1} +1003,凡品枪,枪,练气,白蜡杆制成,枪头挂着红缨。,{extra_battle_strength_points: 1} +1004,凡品棍,棍,练气,普通的硬木棍,长与眉齐。,{extra_battle_strength_points: 1} +1005,凡品扇,扇,练气,文人雅士常用的折扇,附庸风雅。,{extra_battle_strength_points: 1} +1006,凡品鞭,鞭,练气,普通的皮鞭,多用于赶车或刑罚。,{extra_battle_strength_points: 1} +1007,凡品琴,琴,练气,桐木制成的古琴,音色尚可。,{extra_battle_strength_points: 1} +1008,凡品笛,笛,练气,紫竹削制,声音清脆。,{extra_battle_strength_points: 1} +1009,凡品暗器,暗器,练气,随处可见的铁制暗器。,{extra_battle_strength_points: 1} +2001,青霜剑,剑,筑基,剑气森寒。,{extra_battle_strength_points: 2} +2002,井中月,刀,筑基,刀光如井中映月,虚实难测。,{extra_battle_strength_points: 2} +2003,火尖枪,枪,筑基,枪头隐有火光,能引动凡火。,"{extra_battle_strength_points: 2, extra_max_hp: 50}" +2004,囚龙棒,棍,筑基,精铁铸造,重达千斤,力大者可用。,"{extra_battle_strength_points: 2, extra_max_hp: 50}" +2005,桃花扇,扇,筑基,桃花影落飞神剑,扇面绘有桃花,暗藏杀机。,"{extra_battle_strength_points: 2, extra_observation_radius: 1}" +2006,打神鞭,鞭,筑基,仿品,虽无打神之能,却可伤人神魂。,{extra_battle_strength_points: 2} +2007,天魔琴,琴,筑基,琴音魔性,扰乱心智。,"{extra_battle_strength_points: 2, cultivate_duration_reduction: 0.1}" +2008,鬼笛陈情,笛,筑基,据说能以此笛操控尸傀。,"{extra_battle_strength_points: 2, cultivate_duration_reduction: 0.1}" +2009,无影针,暗器,筑基,细若牛毛,来去无影,防不胜防。,{extra_battle_strength_points: 2} +2010,万兽笛,笛,筑基,百兽宗秘制,笛声可安抚或激怒野兽。,"[{extra_battle_strength_points: 1}, {when: 'avatar.spirit_animal is not None', extra_battle_strength_points: 2}]" +2011,血饮狂刀,刀,筑基,刀身赤红,似渴望饮血。,{extra_battle_strength_points: 2} +2012,玄铁重剑,剑,筑基,重剑无锋,大巧不工,独孤求败。,"{extra_battle_strength_points: 2, extra_max_hp: 100}" +2013,紫薇软剑,剑,筑基,软若束带,动若灵蛇。,"{extra_battle_strength_points: 2, extra_escape_success_rate: 0.1}" +2014,水火囚龙棍,棍,筑基,镇魂宗制式兵器,水火相济,困敌锁魂。,{extra_battle_strength_points: 2} +3001,青竹蜂云剑,剑,金丹,成套飞剑,内蕴辟邪神雷,克制天下邪祟。,{extra_battle_strength_points: 3} +3002,随心铁杆兵,棍,金丹,六耳猕猴的兵器,随心变化,大小如意。,"{extra_battle_strength_points: 2, extra_observation_radius: 1}" +3003,五火七禽扇,扇,金丹,扇面有空中火、石中火、木中火等五火,一扇灰飞烟灭。,"{extra_battle_strength_points: 3, extra_observation_radius: 2}" +3004,弑神枪,枪,元婴,杀气滔天,曾染魔神之血,枪出无回。,{extra_battle_strength_points: '2 + avatar.weapon_proficiency * 0.01'} +3005,赤锋矛,枪,元婴,赤锋矛,不朽盾,斩尽仙王灭九天。,"[{extra_battle_strength_points: 2}, {when: 'avatar.cultivation_progress.level >= 30', extra_battle_strength_points: 2, realm_suppression_bonus: 0.1}]" +3006,斩仙飞刀,暗器,金丹,红葫芦内藏一线毫光,有眉有目。请宝贝转身,神鬼难逃。,"{extra_battle_strength_points: 3, extra_observation_radius: 1, extra_escape_success_rate: 0.15}" +3007,诛仙剑,剑,元婴,非铜非铁亦非钢,曾在须弥山下藏。利气直透九重天。,{extra_battle_strength_points: 3} +3008,干将莫邪,剑,金丹,挚情之剑,一雌一雄,分则为杀,合则为情。,"{extra_battle_strength_points: 2, extra_max_hp: 100}" +3009,芭蕉扇,扇,金丹,太阴之精叶,一扇息火,二扇生风,三扇下雨。,"{extra_battle_strength_points: 2, extra_observation_radius: 2, extra_move_step: 1}" +3010,紫金降魔杵,棍,金丹,韦护的兵器,虽然轻灵,打在人身却重如泰山。,"{extra_battle_strength_points: 3, damage_reduction: 0.1}" diff --git a/tests/test_equipment.py b/tests/test_equipment.py new file mode 100644 index 0000000..bd4a2f9 --- /dev/null +++ b/tests/test_equipment.py @@ -0,0 +1,124 @@ +import pytest +from src.classes.weapon import Weapon, get_random_weapon_by_realm, weapons_by_id +from src.classes.auxiliary import Auxiliary, get_random_auxiliary_by_realm, auxiliaries_by_id +from src.classes.cultivation import Realm +from src.classes.weapon_type import WeaponType + +class TestEquipment: + def test_weapon_structure(self): + """测试兵器数据结构和加载""" + assert len(weapons_by_id) > 0 + + # 检查任意一个兵器 + weapon = next(iter(weapons_by_id.values())) + assert isinstance(weapon, Weapon) + assert isinstance(weapon.realm, Realm) + assert isinstance(weapon.weapon_type, WeaponType) + assert weapon.name + + # 测试 info 方法 + info = weapon.get_info() + assert info == weapon.name + + detailed = weapon.get_detailed_info() + assert weapon.name in detailed + assert weapon.realm.value in detailed + + def test_weapon_random_generation(self): + """测试按境界随机生成兵器""" + # 测试练气期 + w_qi = get_random_weapon_by_realm(Realm.Qi_Refinement) + if w_qi: + assert w_qi.realm == Realm.Qi_Refinement + + # 测试筑基期 + w_found = get_random_weapon_by_realm(Realm.Foundation_Establishment) + if w_found: + assert w_found.realm == Realm.Foundation_Establishment + + # 测试金丹期 + w_core = get_random_weapon_by_realm(Realm.Core_Formation) + if w_core: + assert w_core.realm == Realm.Core_Formation + + def test_weapon_deepcopy(self): + """测试兵器深拷贝独立性""" + w1 = get_random_weapon_by_realm(Realm.Qi_Refinement) + if not w1: + pytest.skip("No Qi Refinement weapons available") + + w2 = get_random_weapon_by_realm(Realm.Qi_Refinement) + # 即使随机到同一个原型,它们也应该是不同的对象 + # 注意:get_random_weapon_by_realm 内部已经做了 deepcopy + + # 为了确保测试有效,我们手动获取同一个原型并 deepcopy + prototype = weapons_by_id[w1.id] + import copy + w_copy1 = copy.deepcopy(prototype) + w_copy2 = copy.deepcopy(prototype) + + assert w_copy1 is not w_copy2 + assert w_copy1.id == w_copy2.id + + # 修改一个不影响另一个 + w_copy1.special_data["test"] = 123 + assert "test" not in w_copy2.special_data + + def test_auxiliary_structure(self): + """测试辅助装备数据结构和加载""" + assert len(auxiliaries_by_id) > 0 + + # 检查任意一个 + aux = next(iter(auxiliaries_by_id.values())) + assert isinstance(aux, Auxiliary) + assert isinstance(aux.realm, Realm) + assert aux.name + + # 测试 info 方法 + info = aux.get_info() + assert info == aux.name + + detailed = aux.get_detailed_info() + assert aux.name in detailed + assert aux.realm.value in detailed + + def test_soul_banner(self): + """测试万魂幡特殊逻辑""" + # 查找万魂幡 + soul_banner = None + for a in auxiliaries_by_id.values(): + if a.name == "万魂幡": + soul_banner = a + break + + if not soul_banner: + pytest.skip("万魂幡 not found in config") + + import copy + banner = copy.deepcopy(soul_banner) + + # 初始状态 + assert "吞噬魂魄" not in banner.get_info() + + # 增加魂魄 + banner.special_data["devoured_souls"] = 100 + + # 检查显示更新 + assert "吞噬魂魄:100" in banner.get_info() + assert "吞噬魂魄:100" in banner.get_detailed_info() + + # 检查结构化信息 + struct = banner.get_structured_info() + assert "已吞噬魂魄:100" in struct["desc"] + + def test_grade_renaming_compatibility(self): + """测试 realm 改名后的兼容性(如果有必要)""" + # 确保 weapon 和 auxiliary 确实有 realm 属性 + weapon = next(iter(weapons_by_id.values())) + assert hasattr(weapon, 'realm') + assert not hasattr(weapon, 'grade') # 确保改名彻底 + + aux = next(iter(auxiliaries_by_id.values())) + assert hasattr(aux, 'realm') + assert not hasattr(aux, 'grade') # 确保改名彻底 + diff --git a/web/src/api/game.ts b/web/src/api/game.ts index 3f3d206..cf36742 100644 --- a/web/src/api/game.ts +++ b/web/src/api/game.ts @@ -19,8 +19,8 @@ export interface GameDataDTO { personas: Array<{ id: number; name: string; desc: string; rarity: string }>; realms: string[]; techniques: Array<{ id: number; name: string; grade: string; attribute: string; sect: string | null }>; - weapons: Array<{ id: number; name: string; grade: string; type: string; sect_id: number | null }>; - auxiliaries: Array<{ id: number; name: string; grade: string; sect_id: number | null }>; + weapons: Array<{ id: number; name: string; grade: string; type: string }>; + auxiliaries: Array<{ id: number; name: string; grade: string }>; alignments: Array<{ value: string; label: string }>; } diff --git a/web/src/components/game/panels/info/components/EntityRow.vue b/web/src/components/game/panels/info/components/EntityRow.vue index f48cc94..1614b4a 100644 --- a/web/src/components/game/panels/info/components/EntityRow.vue +++ b/web/src/components/game/panels/info/components/EntityRow.vue @@ -4,7 +4,7 @@ import type { EffectEntity } from '@/types/core'; defineProps<{ item: EffectEntity; - meta?: string; // e.g. "Rank 3" or "Count: 5" + meta?: string; // e.g. "熟练度 50%" compact?: boolean; }>(); @@ -20,8 +20,9 @@ defineEmits(['click']); {{ item.name }} - - {{ meta || item.grade }} + + {{ meta }} + {{ item.grade }} @@ -48,9 +49,23 @@ defineEmits(['click']); font-size: 12px; } +.info { + display: flex; + align-items: center; + gap: 8px; +} + +.grade { + font-size: 11px; + padding: 1px 5px; + background: rgba(255, 215, 0, 0.15); + border: 1px solid rgba(255, 215, 0, 0.3); + border-radius: 3px; + color: #daa520; +} + .meta { font-size: 11px; color: #888; } - diff --git a/web/src/components/game/panels/info/components/SecondaryPopup.vue b/web/src/components/game/panels/info/components/SecondaryPopup.vue index 7c4725b..4de7d7c 100644 --- a/web/src/components/game/panels/info/components/SecondaryPopup.vue +++ b/web/src/components/game/panels/info/components/SecondaryPopup.vue @@ -1,16 +1,12 @@