From 2a68f352bce09508a3662aba44d2ca11fff04f20 Mon Sep 17 00:00:00 2001 From: bridge Date: Mon, 5 Jan 2026 22:26:16 +0800 Subject: [PATCH] add elixir --- src/classes/avatar/core.py | 20 +++++ src/classes/effect/mixin.py | 10 ++- src/classes/elixir.py | 139 ++++++++++++++++++++++++++++++ src/sim/load/avatar_load_mixin.py | 11 +++ src/sim/save/avatar_save_mixin.py | 9 ++ static/game_configs/elixir.csv | 17 ++++ static/templates/ai.txt | 2 +- 7 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 static/game_configs/elixir.csv diff --git a/src/classes/avatar/core.py b/src/classes/avatar/core.py index 186c20c..c540de1 100644 --- a/src/classes/avatar/core.py +++ b/src/classes/avatar/core.py @@ -39,6 +39,7 @@ from src.classes.long_term_objective import LongTermObjective from src.classes.nickname_data import Nickname from src.classes.emotions import EmotionType from src.utils.config import CONFIG +from src.classes.elixir import ConsumedElixir, Elixir # Mixin 导入 from src.classes.effect import EffectsMixin @@ -110,6 +111,8 @@ class Avatar( emotion: EmotionType = EmotionType.CALM custom_pic_id: Optional[int] = None + elixirs: List[ConsumedElixir] = field(default_factory=list) + is_dead: bool = False death_info: Optional[dict] = None @@ -122,6 +125,23 @@ class Avatar( relation_interaction_states: dict[str, dict[str, int]] = field(default_factory=lambda: defaultdict(lambda: {"count": 0, "checked_times": 0})) # ========== 宗门相关 ========== + + def consume_elixir(self, elixir: Elixir) -> bool: + """ + 服用丹药 + :return: 是否成功服用 + """ + # 1. 境界校验:只能服用境界等于或者小于当前境界的丹药 + if elixir.realm > self.cultivation_progress.realm: + return False + + # 2. 记录服用状态 + self.elixirs.append(ConsumedElixir(elixir, int(self.world.month_stamp))) + + # 3. 立即触发属性重算(因为可能有立即生效的数值变化,或者MaxHP/Lifespan改变) + self.recalc_effects() + + return True def join_sect(self, sect: Sect, rank: "SectRank") -> None: """加入宗门""" diff --git a/src/classes/effect/mixin.py b/src/classes/effect/mixin.py index aa64cc1..ce16772 100644 --- a/src/classes/effect/mixin.py +++ b/src/classes/effect/mixin.py @@ -60,7 +60,7 @@ class EffectsMixin: @property def effects(self: "Avatar") -> dict[str, object]: """ - 合并所有来源的效果:宗门、功法、灵根、特质、兵器、辅助装备、灵兽、天地灵机 + 合并所有来源的效果:宗门、功法、灵根、特质、兵器、辅助装备、灵兽、天地灵机、丹药 """ merged: dict[str, object] = {} @@ -107,6 +107,11 @@ class EffectsMixin: if self.world.current_phenomenon is not None: _process_source(self.world.current_phenomenon) + # 来自已服用的丹药 + # 简化逻辑:直接 merge 所有丹药的效果 + for consumed in self.elixirs: + _process_source(consumed.elixir) + return merged def get_effect_breakdown(self: "Avatar") -> list[tuple[str, dict[str, Any]]]: @@ -152,6 +157,9 @@ class EffectsMixin: if self.world.current_phenomenon: _collect("天地灵机", self.world.current_phenomenon) + for consumed in self.elixirs: + _collect(f"丹药【{consumed.elixir.name}】", consumed.elixir) + return breakdown def recalc_effects(self: "Avatar") -> None: diff --git a/src/classes/elixir.py b/src/classes/elixir.py index e69de29..6cd065f 100644 --- a/src/classes/elixir.py +++ b/src/classes/elixir.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, List + +from src.utils.df import game_configs, get_str, get_int +from src.classes.effect import load_effect_from_str, format_effects_to_text +from src.classes.cultivation import Realm + + +class ElixirType(Enum): + """丹药类型""" + Breakthrough = "Breakthrough" # 破境 + Lifespan = "Lifespan" # 延寿 + BurnBlood = "BurnBlood" # 燃血 + Heal = "Heal" # 疗伤 + Unknown = "Unknown" + + +@dataclass +class Elixir: + """ + 丹药类 + 字段与 static/game_configs/elixir.csv 对应 + """ + id: int + name: str + realm: Realm + type: ElixirType + desc: str + price: int + effects: dict[str, object] = field(default_factory=dict) + effect_desc: str = "" + + def get_info(self, detailed: bool = False) -> str: + """获取信息""" + if detailed: + return self.get_detailed_info() + return f"{self.name}" + + def get_detailed_info(self) -> str: + """获取详细信息""" + effect_part = f" 效果:{self.effect_desc}" if self.effect_desc else "" + return f"{self.name}({self.realm.value}·{self._get_type_name()},{self.desc}){effect_part}" + + def _get_type_name(self) -> str: + type_names = { + ElixirType.Breakthrough: "破境", + ElixirType.Lifespan: "延寿", + ElixirType.BurnBlood: "燃血", + ElixirType.Heal: "疗伤", + } + return type_names.get(self.type, "未知") + + def get_colored_info(self) -> str: + """获取带颜色标记的信息,供前端渲染使用""" + # 使用对应境界的颜色 + r, g, b = self.realm.color_rgb + return f"{self.get_info()}" + + def get_structured_info(self) -> dict: + return { + "name": self.name, + "desc": self.desc, + "grade": self.realm.value, + "type": self.type.value, + "type_name": self._get_type_name(), + "price": self.price, + "color": self.realm.color_rgb, + "effect_desc": self.effect_desc, + } + + +@dataclass +class ConsumedElixir: + """ + 已服用的丹药记录 + """ + elixir: Elixir + consume_time: int # 服用时的 MonthStamp + + +def _load_elixirs() -> tuple[Dict[int, Elixir], Dict[str, List[Elixir]]]: + """ + 加载丹药配置 + :return: (id索引字典, name索引字典(值为list)) + """ + elixirs_by_id: Dict[int, Elixir] = {} + elixirs_by_name: Dict[str, List[Elixir]] = {} + + if "elixir" not in game_configs: + return elixirs_by_id, elixirs_by_name + + df = game_configs["elixir"] + for row in df: + elixir_id = get_int(row, "id") + name = get_str(row, "name") + desc = get_str(row, "desc") + price = get_int(row, "price") + + # 解析境界 + realm_str = get_str(row, "realm") + # 尝试匹配 Realm 枚举 + realm = Realm.Qi_Refinement # 默认 + for r in Realm: + if r.value == realm_str or r.name == realm_str: + realm = r + break + + # 解析类型 + elixir_type = ElixirType(get_str(row, "type")) + + # 解析 effects + effects = load_effect_from_str(get_str(row, "effects")) + effect_desc = format_effects_to_text(effects) + + elixir = Elixir( + id=elixir_id, + name=name, + realm=realm, + type=elixir_type, + desc=desc, + price=price, + effects=effects, + effect_desc=effect_desc + ) + + elixirs_by_id[elixir_id] = elixir + + if name not in elixirs_by_name: + elixirs_by_name[name] = [] + elixirs_by_name[name].append(elixir) + + return elixirs_by_id, elixirs_by_name + + +# 导出全局变量 +elixirs_by_id, elixirs_by_name = _load_elixirs() diff --git a/src/sim/load/avatar_load_mixin.py b/src/sim/load/avatar_load_mixin.py index e1cf7b4..0d9ce13 100644 --- a/src/sim/load/avatar_load_mixin.py +++ b/src/sim/load/avatar_load_mixin.py @@ -52,6 +52,7 @@ class AvatarLoadMixin: from src.classes.appearance import get_appearance_by_level from src.classes.magic_stone import MagicStone from src.classes.action_runtime import ActionPlan + from src.classes.elixir import elixirs_by_id, ConsumedElixir # 重建基本对象 gender = Gender(data["gender"]) @@ -212,6 +213,16 @@ class AvatarLoadMixin: # relations需要在外部单独重建(因为需要所有avatar都加载完成) avatar.relations = {} + # 恢复丹药记录 + elixir_list_data = data.get("elixirs", []) + avatar.elixirs = [] + for elixir_data in elixir_list_data: + elixir_id = elixir_data["id"] + if elixir_id in elixirs_by_id: + elixir_obj = elixirs_by_id[elixir_id] + consume_time = elixir_data["time"] + avatar.elixirs.append(ConsumedElixir(elixir_obj, consume_time)) + # 加载完成后重新计算effects(确保数值正确) avatar.recalc_effects() diff --git a/src/sim/save/avatar_save_mixin.py b/src/sim/save/avatar_save_mixin.py index b233f79..508e1b3 100644 --- a/src/sim/save/avatar_save_mixin.py +++ b/src/sim/save/avatar_save_mixin.py @@ -107,5 +107,14 @@ class AvatarSaveMixin: } if self.long_term_objective else None, "_action_cd_last_months": self._action_cd_last_months, "known_regions": list(self.known_regions), + + # 丹药 + "elixirs": [ + { + "id": consumed.elixir.id, + "time": consumed.consume_time + } + for consumed in self.elixirs + ], } diff --git a/static/game_configs/elixir.csv b/static/game_configs/elixir.csv new file mode 100644 index 0000000..b2f6604 --- /dev/null +++ b/static/game_configs/elixir.csv @@ -0,0 +1,17 @@ +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,采用凡间珍草炼制,略微延缓衰老(药效50年)。,100,"{duration_month: 600, extra_max_lifespan: 5}" +6,长生丹,筑基,Lifespan,取天地灵草炼制,可延寿十载(药效50年)。,500,"{duration_month: 1200, extra_max_lifespan: 10}" +7,长生丹,金丹,Lifespan,夺天地造化,凡人服之立毙,金丹修士服之延寿半甲子(药效50年)。,200,"{duration_month: 2400, extra_max_lifespan: 30}" +8,长生丹,元婴,Lifespan,蕴含一丝长生之气,元婴老怪以此续命(药效50年)。,5000,"{duration_month: 6000, 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}" diff --git a/static/templates/ai.txt b/static/templates/ai.txt index 9072014..06eb93a 100644 --- a/static/templates/ai.txt +++ b/static/templates/ai.txt @@ -14,7 +14,7 @@ "avatar_thinking": ... // 从角色角度,以第一人称视角,简单清晰的描述想法 "current_emotion": ... // 从以下列表中选择一个最符合当前心情的词:平静、开心、愤怒、悲伤、恐惧、惊讶、期待、厌恶、疑惑、疲惫 "short_term_objective": ..., // 角色接下来一段时间的短期目标 - "action_name_params_pairs": list[Tuple[action_name, action_params]] // 一次性决定未来的5~10个动作,按顺序执行。action_params 必须是字典 {{}},不能是 null。 + "action_name_params_pairs": list[Tuple[action_name, action_params]] // 一次性决定未来的5~10个动作,按顺序执行。action_params 必须是字典 {{}}。如果为空则返回空字典,不能返回null。 }} }}