From 5241f70ef3f99226da8603cbda622a89058ff4ac Mon Sep 17 00:00:00 2001 From: bridge Date: Sun, 11 Jan 2026 23:12:48 +0800 Subject: [PATCH] refactor item ids --- src/classes/auxiliary.py | 6 +- src/classes/elixir.py | 8 +- src/classes/item_registry.py | 24 +++++ src/classes/region.py | 12 +-- src/classes/store.py | 24 +++-- src/classes/weapon.py | 6 +- src/run/load_map.py | 13 ++- static/game_configs/auxiliary.csv | 32 +++--- static/game_configs/city_region.csv | 12 +-- static/game_configs/elixir.csv | 32 +++--- static/game_configs/weapon.csv | 50 ++++----- tests/test_item_registry.py | 151 ++++++++++++++++++++++++++++ 12 files changed, 281 insertions(+), 89 deletions(-) create mode 100644 src/classes/item_registry.py create mode 100644 tests/test_item_registry.py diff --git a/src/classes/auxiliary.py b/src/classes/auxiliary.py index 534df54..5bb8f25 100644 --- a/src/classes/auxiliary.py +++ b/src/classes/auxiliary.py @@ -89,7 +89,7 @@ def _load_auxiliaries() -> tuple[Dict[int, Auxiliary], Dict[str, Auxiliary]]: realm = Realm.Qi_Refinement a = Auxiliary( - id=get_int(row, "id"), + id=get_int(row, "item_id"), name=get_str(row, "name"), realm=realm, desc=get_str(row, "desc"), @@ -99,6 +99,10 @@ def _load_auxiliaries() -> tuple[Dict[int, Auxiliary], Dict[str, Auxiliary]]: auxiliaries_by_id[a.id] = a auxiliaries_by_name[a.name] = a + + # 注册到全局注册表 + from src.classes.item_registry import ItemRegistry + ItemRegistry.register(a.id, a) return auxiliaries_by_id, auxiliaries_by_name diff --git a/src/classes/elixir.py b/src/classes/elixir.py index 4c9ad28..f08b164 100644 --- a/src/classes/elixir.py +++ b/src/classes/elixir.py @@ -166,7 +166,7 @@ def _load_elixirs() -> tuple[Dict[int, Elixir], Dict[str, List[Elixir]]]: effect_desc = format_effects_to_text(effects) elixir = Elixir( - id=elixir_id, + id=get_int(row, "item_id"), name=name, realm=realm, type=elixir_type, @@ -176,11 +176,15 @@ def _load_elixirs() -> tuple[Dict[int, Elixir], Dict[str, List[Elixir]]]: effect_desc=effect_desc ) - elixirs_by_id[elixir_id] = elixir + elixirs_by_id[elixir.id] = elixir if name not in elixirs_by_name: elixirs_by_name[name] = [] elixirs_by_name[name].append(elixir) + + # 注册到全局注册表 + from src.classes.item_registry import ItemRegistry + ItemRegistry.register(elixir.id, elixir) return elixirs_by_id, elixirs_by_name diff --git a/src/classes/item_registry.py b/src/classes/item_registry.py new file mode 100644 index 0000000..fcf8909 --- /dev/null +++ b/src/classes/item_registry.py @@ -0,0 +1,24 @@ +from typing import Dict, Type, TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from src.classes.item import Item + +class ItemRegistry: + """全局物品注册表""" + + _items_by_id: Dict[int, "Item"] = {} + + @classmethod + def register(cls, item_id: int, item: "Item"): + if item_id in cls._items_by_id: + # 允许重复注册(覆盖),但在开发环境中最好打印警告 + pass + cls._items_by_id[item_id] = item + + @classmethod + def get(cls, item_id: int) -> Optional["Item"]: + return cls._items_by_id.get(item_id) + + @classmethod + def get_all(cls) -> Dict[int, "Item"]: + return cls._items_by_id diff --git a/src/classes/region.py b/src/classes/region.py index 0c857c3..8db4ed8 100644 --- a/src/classes/region.py +++ b/src/classes/region.py @@ -215,19 +215,11 @@ class CultivateRegion(Region): @dataclass(eq=False) class CityRegion(Region, StoreMixin): """城市区域""" - sell_items: str = field(default="[]") + sell_item_ids: list[int] = field(default_factory=list) def __post_init__(self): super().__post_init__() - try: - import ast - items_list = ast.literal_eval(self.sell_items) - if isinstance(items_list, list): - self.init_store(items_list) - else: - self.init_store([]) - except Exception: - self.init_store([]) + self.init_store(self.sell_item_ids) def get_region_type(self) -> str: return "city" diff --git a/src/classes/store.py b/src/classes/store.py index 3565301..4b7fc90 100644 --- a/src/classes/store.py +++ b/src/classes/store.py @@ -1,11 +1,12 @@ from collections import defaultdict -from typing import Any, List +from typing import Any, List, Union from src.utils.resolution import resolve_query from src.classes.elixir import Elixir from src.classes.weapon import Weapon from src.classes.auxiliary import Auxiliary from src.classes.prices import prices +from src.classes.item_registry import ItemRegistry class StoreMixin: """ @@ -13,20 +14,25 @@ class StoreMixin: 赋予区域售卖物品的能力 """ - def init_store(self, item_names: list[str]): + def init_store(self, item_ids: list[int]): """ 初始化商店物品 - :param item_names: 物品名称列表 + :param item_ids: 物品ID列表 """ self.store_items = [] - if not item_names: + if not item_ids: return - for name in item_names: - # 期望类型:丹药、武器、辅助 - res = resolve_query(name, expected_types=[Elixir, Weapon, Auxiliary]) - if res.is_valid and res.obj: - self.store_items.append(res.obj) + for item_id in item_ids: + item = ItemRegistry.get(item_id) + if item: + self.store_items.append(item) + else: + # 兼容旧逻辑:如果列表中混入了字符串名称(虽然根据迁移脚本不应该发生) + if isinstance(item_id, str): + res = resolve_query(item_id, expected_types=[Elixir, Weapon, Auxiliary]) + if res.is_valid and res.obj: + self.store_items.append(res.obj) def get_store_info(self) -> str: """ diff --git a/src/classes/weapon.py b/src/classes/weapon.py index eaa65a1..9a333b3 100644 --- a/src/classes/weapon.py +++ b/src/classes/weapon.py @@ -93,7 +93,7 @@ def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon]]: realm = Realm.Qi_Refinement w = Weapon( - id=get_int(row, "id"), + id=get_int(row, "item_id"), name=get_str(row, "name"), weapon_type=weapon_type, realm=realm, @@ -104,6 +104,10 @@ def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon]]: weapons_by_id[w.id] = w weapons_by_name[w.name] = w + + # 注册到全局注册表 + from src.classes.item_registry import ItemRegistry + ItemRegistry.register(w.id, w) return weapons_by_id, weapons_by_name diff --git a/src/run/load_map.py b/src/run/load_map.py index 2022621..b660129 100644 --- a/src/run/load_map.py +++ b/src/run/load_map.py @@ -105,9 +105,16 @@ def _load_and_assign_regions(game_map: Map, region_coords: dict[int, list[tuple[ params["essence_type"] = EssenceType.from_str(get_str(row, "root_type")) params["essence_density"] = get_int(row, "root_density") elif type_tag == "city": - sell_items_str = get_str(row, "sell_items") - if sell_items_str: - params["sell_items"] = sell_items_str + sell_ids_str = get_str(row, "sell_item_ids") + if sell_ids_str: + try: + import ast + ids = ast.literal_eval(sell_ids_str) + if isinstance(ids, list): + params["sell_item_ids"] = ids + except Exception as e: + print(f"Error parsing sell_item_ids for city {rid}: {e}") + elif type_tag == "sect": sect_id = get_int(row, "sect_id") params["sect_id"] = sect_id diff --git a/static/game_configs/auxiliary.csv b/static/game_configs/auxiliary.csv index 21bb1bf..f952f3f 100644 --- a/static/game_configs/auxiliary.csv +++ b/static/game_configs/auxiliary.csv @@ -1,4 +1,4 @@ -id,name,grade,desc,effects +item_id,name,grade,desc,effects ,名称,等级(练气/筑基/金丹/元婴),描述/提示词,JSON形式(支持宽松格式,见effects.py说明) 2001,木灵甲,筑基,以灵木纤维编织,生机盎然,防御尚可.,{damage_reduction: 0.08} 2002,血菩提,筑基,生于火麒麟滴血之处,重伤必治,无伤增功.,{extra_hp_recovery_rate: 0.3} @@ -11,18 +11,18 @@ id,name,grade,desc,effects 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: 2, extra_escape_success_rate: 0.15}" -3002,昆仑镜,金丹,上古神器,拥有穿梭时空之力,此为仿品,仅能窥探远方.,{extra_observation_radius: 2} -3003,掌天瓶,元婴,夺天地之造化,聚日月之精华,催熟万物.,"{extra_cultivate_exp: 150, cultivate_duration_reduction: 0.2}" -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_hp_recovery_rate: 0.5, extra_refine_success_rate: 0.15}" -3009,聚宝盆,金丹,放入一文,取出万贯,财源滚滚.,"{extra_item_sell_price_multiplier: 0.2, extra_fortune_probability: 0.01, shop_buy_price_reduction: 0.2}" -3010,天地烘炉,金丹,以天地为炉,造化为工,炼制万物.,"{extra_weapon_upgrade_chance: 0.15, extra_cast_success_rate: 0.1, extra_refine_success_rate: 0.1}" -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'}" -3015,神木王鼎,元婴,木系至宝,不仅可炼制神丹,鼎中更自成一界,可催熟灵药。,"{extra_refine_success_rate: 0.25, cultivate_duration_reduction: 0.15}" +2051,神风舟,金丹,飞行法器,速度极快,居家旅行杀人越货必备.,"{extra_move_step: 2, extra_escape_success_rate: 0.15}" +2052,昆仑镜,金丹,上古神器,拥有穿梭时空之力,此为仿品,仅能窥探远方.,{extra_observation_radius: 2} +2053,掌天瓶,元婴,夺天地之造化,聚日月之精华,催熟万物.,"{extra_cultivate_exp: 150, cultivate_duration_reduction: 0.2}" +2054,龙凤呈祥戒,金丹,龙凤和鸣,心意相通,双修进境一日千里.,{extra_dual_cultivation_exp: 100} +2055,隐形披风,金丹,披上后身形尽隐,连神识也难以察觉.,"{extra_move_step: 1, extra_escape_success_rate: 0.2}" +2056,御兽环,金丹,套在妖兽颈上,任其凶焰滔天,也要乖乖听命.,{extra_catch_success_rate: 0.1} +2057,太虚甲,金丹,太虚幻境所化,万法不沾,防御力惊人.,"{damage_reduction: 0.2, extra_max_hp: 200}" +2058,八卦炉,金丹,仿太上老君炼丹炉,丹成九转,药效倍增.,"{extra_hp_recovery_rate: 0.5, extra_refine_success_rate: 0.15}" +2059,聚宝盆,金丹,放入一文,取出万贯,财源滚滚.,"{extra_item_sell_price_multiplier: 0.2, extra_fortune_probability: 0.01, shop_buy_price_reduction: 0.2}" +2060,天地烘炉,金丹,以天地为炉,造化为工,炼制万物.,"{extra_weapon_upgrade_chance: 0.15, extra_cast_success_rate: 0.1, extra_refine_success_rate: 0.1}" +2061,六道剑匣,金丹,背负六道,剑出轮回,悟剑良伴.,"{extra_breakthrough_success_rate: 0.2, extra_cultivate_exp: 100}" +2062,传国玉玺,元婴,受命于天,既寿永昌。皇威浩荡,震慑宵小.,{realm_suppression_bonus: 0.15} +2063,灵眼之泉,金丹,可移动的灵气之源,随身携带的洞天福地.,"{cultivate_duration_reduction: 0.3, extra_cultivate_exp: 50}" +2064,万魂幡,元婴,血海不枯,冥河不死。极度邪恶的掠夺法宝.,"{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'}" +2065,神木王鼎,元婴,木系至宝,不仅可炼制神丹,鼎中更自成一界,可催熟灵药。,"{extra_refine_success_rate: 0.25, cultivate_duration_reduction: 0.15}" diff --git a/static/game_configs/city_region.csv b/static/game_configs/city_region.csv index 303923a..7876d2b 100644 --- a/static/game_configs/city_region.csv +++ b/static/game_configs/city_region.csv @@ -1,7 +1,7 @@ -id,name,desc,sell_items +id,name,desc,sell_item_ids ID必须以3开头,,, -301,青云城,繁华都市,人烟稠密,商贾云集。此地是交易天材地宝、寻找机缘的重要场所。,"['练气破境丹', '练气长生丹']" -302,沙月城,沙漠绿洲中的贸易重镇,各路商队在此集结,是修士补给和交流的重要据点。,"['练气燃血丹', '练气回春丹']" -303,翠林城,森林深处的修仙重镇,众多修士在此栖居,是修炼和炼宝的理想之地。,"['练气剑', '练气刀', '练气枪']" -304,沧澜城,坐落于大河入海口的三角洲,百川归海,水运昌隆,是水系修士往来最为频繁的宝地。,"['练气棍', '练气扇', '练气鞭']" -305,揽月城,屹立于连绵群山之巅,终年云雾缭绕,灵气纯净,是苦修之士感悟天道的绝佳场所。,"['练气琴', '练气笛', '练气暗器']" +301,青云城,繁华都市,人烟稠密,商贾云集。此地是交易天材地宝、寻找机缘的重要场所。,"[3001, 3005]" +302,沙月城,沙漠绿洲中的贸易重镇,各路商队在此集结,是修士补给和交流的重要据点。,"[3009, 3013]" +303,翠林城,森林深处的修仙重镇,众多修士在此栖居,是修炼和炼宝的理想之地。,"[1001, 1002, 1003]" +304,沧澜城,坐落于大河入海口的三角洲,百川归海,水运昌隆,是水系修士往来最为频繁的宝地。,"[1004, 1005, 1006]" +305,揽月城,屹立于连绵群山之巅,终年云雾缭绕,灵气纯净,是苦修之士感悟天道的绝佳场所。,"[1007, 1008, 1009]" diff --git a/static/game_configs/elixir.csv b/static/game_configs/elixir.csv index afe76d9..b8c7bf6 100644 --- a/static/game_configs/elixir.csv +++ b/static/game_configs/elixir.csv @@ -1,17 +1,17 @@ -id,name,realm,type,desc,price,effects +item_id,name,realm,type,desc,price,effects ,名称,境界(练气/筑基/金丹/元婴),类型(Breakthrough/Lifespan/BurnBlood/Heal),描述,价格,JSON形式Effects -1,练气破境丹,练气,Breakthrough,凝聚灵气,辅助练气期修士突破瓶颈的丹药(药效5年)。,50,"{duration_month: 60, extra_breakthrough_success_rate: 0.1}" -2,筑基破境丹,筑基,Breakthrough,蕴含筑基真意,辅助筑基期修士突破瓶颈的灵丹(药效5年)。,200,"{duration_month: 60, extra_breakthrough_success_rate: 0.1}" -3,金丹破境丹,金丹,Breakthrough,凝结金丹之气,辅助金丹期修士碎丹成婴(药效5年)。,500,"{duration_month: 60, extra_breakthrough_success_rate: 0.1}" -5,练气长生丹,练气,Lifespan,采用凡间珍草炼制,略微延缓衰老(限服一颗)。,100,"{extra_max_lifespan: 5}" -6,筑基长生丹,筑基,Lifespan,取天地灵草炼制,可延寿十载(限服一颗)。,500,"{extra_max_lifespan: 10}" -7,金丹长生丹,金丹,Lifespan,夺天地造化,凡人服之立毙,金丹修士服之延寿半甲子(限服一颗)。,200,"{extra_max_lifespan: 30}" -8,元婴长生丹,元婴,Lifespan,蕴含一丝长生之气,元婴老怪以此续命(限服一颗)。,5000,"{extra_max_lifespan: 100}" -9,练气燃血丹,练气,BurnBlood,燃烧精血换取短暂爆发。3年内战力提升,但10年内经脉受损战力下降。,50,"[{duration_month: 36, extra_battle_strength_points: 3}, {duration_month: 120, extra_battle_strength_points: -1}]" -10,筑基燃血丹,筑基,BurnBlood,激发潜能的猛药。3年内战力大增,但10年内虚弱。,100,"[{duration_month: 36, extra_battle_strength_points: 5}, {duration_month: 120, extra_battle_strength_points: -2}]" -11,金丹燃血丹,金丹,BurnBlood,金丹修士拼命时的选择。3年内战力暴涨,但10年内重伤。,200,"[{duration_month: 36, extra_battle_strength_points: 7}, {duration_month: 120, extra_battle_strength_points: -3}]" -12,元婴燃血丹,元婴,BurnBlood,燃烧元婴本源。3年内获得毁天灭地的力量,但10年内几乎废人。,300,"[{duration_month: 36, extra_battle_strength_points: 10}, {duration_month: 120, extra_battle_strength_points: -5}]" -13,练气回春丹,练气,Heal,普通的疗伤丹药,可恢复练气期修士的伤势(持续5年)。,20,"{duration_month: 60, extra_hp_recovery_rate: 0.5}" -14,筑基回春丹,筑基,Heal,药力温和醇厚,能快速愈合筑基期修士的肉身损伤(持续5年)。,50,"{duration_month: 60, extra_hp_recovery_rate: 1.0}" -15,金丹回春丹,金丹,Heal,蕴含生机之力,疗伤圣药(持续5年)。,100,"{duration_month: 60, extra_hp_recovery_rate: 2.0}" -16,元婴回春丹,元婴,Heal,蕴含造化生机,肉身修复极快(持续5年)。,200,"{duration_month: 60, extra_hp_recovery_rate: 5.0}" +3001,练气破境丹,练气,Breakthrough,凝聚灵气,辅助练气期修士突破瓶颈的丹药(药效5年)。,50,"{duration_month: 60, extra_breakthrough_success_rate: 0.1}" +3002,筑基破境丹,筑基,Breakthrough,蕴含筑基真意,辅助筑基期修士突破瓶颈的灵丹(药效5年)。,200,"{duration_month: 60, extra_breakthrough_success_rate: 0.1}" +3003,金丹破境丹,金丹,Breakthrough,凝结金丹之气,辅助金丹期修士碎丹成婴(药效5年)。,500,"{duration_month: 60, extra_breakthrough_success_rate: 0.1}" +3005,练气长生丹,练气,Lifespan,采用凡间珍草炼制,略微延缓衰老(限服一颗)。,100,{extra_max_lifespan: 5} +3006,筑基长生丹,筑基,Lifespan,取天地灵草炼制,可延寿十载(限服一颗)。,500,{extra_max_lifespan: 10} +3007,金丹长生丹,金丹,Lifespan,夺天地造化,凡人服之立毙,金丹修士服之延寿半甲子(限服一颗)。,200,{extra_max_lifespan: 30} +3008,元婴长生丹,元婴,Lifespan,蕴含一丝长生之气,元婴老怪以此续命(限服一颗)。,5000,{extra_max_lifespan: 100} +3009,练气燃血丹,练气,BurnBlood,燃烧精血换取短暂爆发。3年内战力提升,但10年内经脉受损战力下降。,50,"[{duration_month: 36, extra_battle_strength_points: 3}, {duration_month: 120, extra_battle_strength_points: -1}]" +3010,筑基燃血丹,筑基,BurnBlood,激发潜能的猛药。3年内战力大增,但10年内虚弱。,100,"[{duration_month: 36, extra_battle_strength_points: 5}, {duration_month: 120, extra_battle_strength_points: -2}]" +3011,金丹燃血丹,金丹,BurnBlood,金丹修士拼命时的选择。3年内战力暴涨,但10年内重伤。,200,"[{duration_month: 36, extra_battle_strength_points: 7}, {duration_month: 120, extra_battle_strength_points: -3}]" +3012,元婴燃血丹,元婴,BurnBlood,燃烧元婴本源。3年内获得毁天灭地的力量,但10年内几乎废人。,300,"[{duration_month: 36, extra_battle_strength_points: 10}, {duration_month: 120, extra_battle_strength_points: -5}]" +3013,练气回春丹,练气,Heal,普通的疗伤丹药,可恢复练气期修士的伤势(持续5年)。,20,"{duration_month: 60, extra_hp_recovery_rate: 0.5}" +3014,筑基回春丹,筑基,Heal,药力温和醇厚,能快速愈合筑基期修士的肉身损伤(持续5年)。,50,"{duration_month: 60, extra_hp_recovery_rate: 1.0}" +3015,金丹回春丹,金丹,Heal,蕴含生机之力,疗伤圣药(持续5年)。,100,"{duration_month: 60, extra_hp_recovery_rate: 2.0}" +3016,元婴回春丹,元婴,Heal,蕴含造化生机,肉身修复极快(持续5年)。,200,"{duration_month: 60, extra_hp_recovery_rate: 5.0}" diff --git a/static/game_configs/weapon.csv b/static/game_configs/weapon.csv index 957cdf4..4f27576 100644 --- a/static/game_configs/weapon.csv +++ b/static/game_configs/weapon.csv @@ -1,4 +1,4 @@ -id,name,weapon_type,grade,desc,effects +item_id,name,weapon_type,grade,desc,effects ,名称,兵器类型,等级(练气/筑基/金丹/元婴),描述/提示词,JSON形式(支持宽松格式,见effects.py说明) 1001,练气剑,剑,练气,市井铁匠铺打造的铁剑,勉强能用。,{extra_battle_strength_points: 1} 1002,练气刀,刀,练气,厚背砍刀,寻常武夫的最爱。,{extra_battle_strength_points: 1} @@ -9,27 +9,27 @@ id,name,weapon_type,grade,desc,effects 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: 3, extra_observation_radius: 1}" -3003,五火七禽扇,扇,金丹,扇面有空中火、石中火、木中火等五火,一扇灰飞烟灭。,"{extra_battle_strength_points: 3, extra_observation_radius: 2}" -3004,弑神枪,枪,元婴,杀气滔天,曾染魔神之血,枪出无回。,{extra_battle_strength_points: '3 + avatar.weapon_proficiency * 0.02'} -3005,赤锋矛,枪,元婴,赤锋矛,不朽盾,斩尽仙王灭九天。,"[{extra_battle_strength_points: 3}, {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: 4, realm_suppression_bonus: 0.1}" -3008,干将莫邪,剑,金丹,挚情之剑,一雌一雄,分则为杀,合则为情。,"{extra_battle_strength_points: 3, extra_max_hp: 150}" -3009,芭蕉扇,扇,金丹,太阴之精叶,一扇息火,二扇生风,三扇下雨。,"{extra_battle_strength_points: 3, extra_observation_radius: 2, extra_move_step: 1}" -3010,紫金降魔杵,棍,金丹,韦护的兵器,虽然轻灵,打在人身却重如泰山。,"{extra_battle_strength_points: 3, damage_reduction: 0.1}" +1021,青霜剑,剑,筑基,剑气森寒。,{extra_battle_strength_points: 2} +1022,井中月,刀,筑基,刀光如井中映月,虚实难测。,{extra_battle_strength_points: 2} +1023,火尖枪,枪,筑基,枪头隐有火光,能引动凡火。,"{extra_battle_strength_points: 2, extra_max_hp: 50}" +1024,囚龙棒,棍,筑基,精铁铸造,重达千斤,力大者可用。,"{extra_battle_strength_points: 2, extra_max_hp: 50}" +1025,桃花扇,扇,筑基,桃花影落飞神剑,扇面绘有桃花,暗藏杀机。,"{extra_battle_strength_points: 2, extra_observation_radius: 1}" +1026,打神鞭,鞭,筑基,仿品,虽无打神之能,却可伤人神魂。,{extra_battle_strength_points: 2} +1027,天魔琴,琴,筑基,琴音魔性,扰乱心智。,"{extra_battle_strength_points: 2, cultivate_duration_reduction: 0.1}" +1028,鬼笛陈情,笛,筑基,据说能以此笛操控尸傀。,"{extra_battle_strength_points: 2, cultivate_duration_reduction: 0.1}" +1029,无影针,暗器,筑基,细若牛毛,来去无影,防不胜防。,{extra_battle_strength_points: 2} +1030,万兽笛,笛,筑基,百兽宗秘制,笛声可安抚或激怒野兽。,"[{extra_battle_strength_points: 1}, {when: 'avatar.spirit_animal is not None', extra_battle_strength_points: 2}]" +1031,血饮狂刀,刀,筑基,刀身赤红,似渴望饮血。,{extra_battle_strength_points: 2} +1032,玄铁重剑,剑,筑基,重剑无锋,大巧不工,独孤求败。,"{extra_battle_strength_points: 2, extra_max_hp: 100}" +1033,紫薇软剑,剑,筑基,软若束带,动若灵蛇。,"{extra_battle_strength_points: 2, extra_escape_success_rate: 0.1}" +1034,水火囚龙棍,棍,筑基,镇魂宗制式兵器,水火相济,困敌锁魂。,{extra_battle_strength_points: 2} +1051,青竹蜂云剑,剑,金丹,成套飞剑,内蕴辟邪神雷,克制天下邪祟。,{extra_battle_strength_points: 3} +1052,随心铁杆兵,棍,金丹,六耳猕猴的兵器,随心变化,大小如意。,"{extra_battle_strength_points: 3, extra_observation_radius: 1}" +1053,五火七禽扇,扇,金丹,扇面有空中火、石中火、木中火等五火,一扇灰飞烟灭。,"{extra_battle_strength_points: 3, extra_observation_radius: 2}" +1054,弑神枪,枪,元婴,杀气滔天,曾染魔神之血,枪出无回。,{extra_battle_strength_points: '3 + avatar.weapon_proficiency * 0.02'} +1055,赤锋矛,枪,元婴,赤锋矛,不朽盾,斩尽仙王灭九天。,"[{extra_battle_strength_points: 3}, {when: 'avatar.cultivation_progress.level >= 30', extra_battle_strength_points: 2, realm_suppression_bonus: 0.1}]" +1056,斩仙飞刀,暗器,金丹,红葫芦内藏一线毫光,有眉有目。请宝贝转身,神鬼难逃。,"{extra_battle_strength_points: 3, extra_observation_radius: 1, extra_escape_success_rate: 0.15}" +1057,诛仙剑,剑,元婴,非铜非铁亦非钢,曾在须弥山下藏。利气直透九重天。,"{extra_battle_strength_points: 4, realm_suppression_bonus: 0.1}" +1058,干将莫邪,剑,金丹,挚情之剑,一雌一雄,分则为杀,合则为情。,"{extra_battle_strength_points: 3, extra_max_hp: 150}" +1059,芭蕉扇,扇,金丹,太阴之精叶,一扇息火,二扇生风,三扇下雨。,"{extra_battle_strength_points: 3, extra_observation_radius: 2, extra_move_step: 1}" +1060,紫金降魔杵,棍,金丹,韦护的兵器,虽然轻灵,打在人身却重如泰山。,"{extra_battle_strength_points: 3, damage_reduction: 0.1}" diff --git a/tests/test_item_registry.py b/tests/test_item_registry.py new file mode 100644 index 0000000..6fa8a43 --- /dev/null +++ b/tests/test_item_registry.py @@ -0,0 +1,151 @@ +import pytest +from src.classes.item import Item +from src.classes.item_registry import ItemRegistry +from src.classes.weapon import Weapon +from src.classes.auxiliary import Auxiliary +from src.classes.elixir import Elixir +from src.classes.store import StoreMixin +from src.classes.cultivation import Realm +from src.classes.weapon_type import WeaponType +from src.classes.elixir import ElixirType + +# Mock concrete implementations for testing +class MockItem(Item): + def __init__(self, id, name): + self.id = id + self.name = name + +# --- Test Item Registry --- + +def test_registry_registration(): + """Test that items can be registered and retrieved.""" + # Clear registry for test isolation (though usually not exposed, we access _items_by_id) + original_registry = ItemRegistry._items_by_id.copy() + ItemRegistry._items_by_id.clear() + + try: + item1 = MockItem(1001, "TestItem1") + item2 = MockItem(1002, "TestItem2") + + ItemRegistry.register(item1.id, item1) + ItemRegistry.register(item2.id, item2) + + assert ItemRegistry.get(1001) == item1 + assert ItemRegistry.get(1002) == item2 + assert ItemRegistry.get(9999) is None + + all_items = ItemRegistry.get_all() + assert len(all_items) == 2 + assert 1001 in all_items + assert 1002 in all_items + + finally: + # Restore registry + ItemRegistry._items_by_id = original_registry + +def test_registry_overwrite(): + """Test that registering an ID again overwrites the previous item.""" + original_registry = ItemRegistry._items_by_id.copy() + ItemRegistry._items_by_id.clear() + + try: + item1 = MockItem(1001, "ItemV1") + item2 = MockItem(1001, "ItemV2") + + ItemRegistry.register(1001, item1) + assert ItemRegistry.get(1001).name == "ItemV1" + + ItemRegistry.register(1001, item2) + assert ItemRegistry.get(1001).name == "ItemV2" + + finally: + ItemRegistry._items_by_id = original_registry + +# --- Test Store Mixin with Item IDs --- + +class MockShop(StoreMixin): + def __init__(self): + self.name = "TestShop" + +def test_store_init_with_ids(mock_item_data): + """Test initializing a store with item IDs.""" + original_registry = ItemRegistry._items_by_id.copy() + ItemRegistry._items_by_id.clear() + + try: + # Setup registry + weapon = mock_item_data["obj_weapon"] # ID 201 + elixir = mock_item_data["obj_elixir"] # ID 1 + + ItemRegistry.register(weapon.id, weapon) + ItemRegistry.register(elixir.id, elixir) + + # Init shop + shop = MockShop() + shop.init_store([weapon.id, elixir.id]) + + assert len(shop.store_items) == 2 + assert any(i.name == weapon.name for i in shop.store_items) + assert any(i.name == elixir.name for i in shop.store_items) + + # Test store info + info = shop.get_store_info() + assert "出售:" in info + assert weapon.name in info + assert elixir.name in info + + finally: + ItemRegistry._items_by_id = original_registry + +def test_store_init_mixed_ids_and_names(mock_item_data): + """Test backward compatibility: store init with both IDs and names.""" + original_registry = ItemRegistry._items_by_id.copy() + ItemRegistry._items_by_id.clear() + + try: + # We need `resolve_query` to work for names, which relies on + # weapons_by_name etc. populated in conftest or manually here. + # Since we are mocking, we might need to patch resolve_query or ensure global dicts are populated. + # For simplicity, we assume `resolve_query` logic works if global dicts are set. + # But `mock_item_data` objects are fresh and NOT in global dicts by default unless we put them there. + + weapon = mock_item_data["obj_weapon"] + weapon_id = weapon.id + + # Register weapon by ID + ItemRegistry.register(weapon_id, weapon) + + # Prepare an item for Name lookup (not in Registry) + # We need to mock `src.utils.resolution.resolve_query` or make `resolve_query` find it. + # Ideally, `StoreMixin` uses `resolve_query` for strings. + + from src.classes.weapon import weapons_by_name + weapons_by_name[weapon.name] = weapon + + shop = MockShop() + # Init with ID for weapon, and Name for weapon (redundant but testing logic) + shop.init_store([weapon_id, weapon.name]) + + # Should contain two instances of the same weapon (one found by ID, one by Name) + assert len(shop.store_items) == 2 + assert shop.store_items[0] == weapon + assert shop.store_items[1] == weapon + + finally: + ItemRegistry._items_by_id = original_registry + +# --- Test Item Instantiation --- + +def test_item_instantiation(): + """Test the Item.instantiate() method.""" + original = MockItem(1, "Original") + original.some_attr = [1, 2, 3] + + clone = original.instantiate() + + assert clone is not original + assert clone.id == original.id + assert clone.name == original.name + assert clone.some_attr == original.some_attr + assert clone.some_attr is not original.some_attr # Deep copy check +