From fbd9a60415375ffd0fe17a1ad4f0471f8e3c9102 Mon Sep 17 00:00:00 2001 From: bridge Date: Sat, 8 Nov 2025 02:53:12 +0800 Subject: [PATCH] refactor tribulation --- src/classes/action/breakthrough.py | 48 ++------ src/classes/tribulation.py | 172 +++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 src/classes/tribulation.py diff --git a/src/classes/action/breakthrough.py b/src/classes/action/breakthrough.py index b3f8b06..0e5e311 100644 --- a/src/classes/action/breakthrough.py +++ b/src/classes/action/breakthrough.py @@ -6,28 +6,16 @@ from src.classes.action.cooldown import cooldown_action from src.classes.event import Event from src.classes.cultivation import Realm from src.classes.story_teller import StoryTeller -from src.classes.relation import Relation +from src.classes.tribulation import TribulationSelector +from src.classes.hp_and_mp import HP_MAX_BY_REALM, MP_MAX_BY_REALM +from src.classes.effect import _merge_effects -# —— 配置:哪些“出发境界”会生成突破小故事(global var)—— +# —— 配置:哪些"出发境界"会生成突破小故事(global var)—— ALLOW_STORY_FROM_REALMS: list[Realm] = [ Realm.Foundation_Establishment, # 筑基 Realm.Core_Formation, # 金丹 ] -# 故事提示词(global var) -STORY_PROMPT_BASE = "以古风、凝练、不炫技的笔触,描绘修士历经{calamity}劫时的心境与取舍,篇幅60~120字。" - -# 劫难说明(global var) -CALAMITY_DESCRIPTIONS: dict[str, str] = { - "心魔": "心念起伏,自我否定与执念缠斗,外寂而内喧。", - "雷劫": "天威如潮,电光凝成纹理,压迫骨血与神识。", - "肉身": "筋骨皮膜重塑,真气磨砺经脉,疼痛与新生并至。", - "寻仇": "仇人旧怨不散,刀光在心底回响,一念之差改写因果。", - "情劫": "柔情即刃,难舍难分,念头被拉回人间烟火。", -} -from src.classes.hp_and_mp import HP_MAX_BY_REALM, MP_MAX_BY_REALM -from src.classes.effect import _merge_effects - @cooldown_action class Breakthrough(TimedAction): @@ -120,8 +108,8 @@ class Breakthrough(TimedAction): # 仅基于出发境界判断是否生成故事 self._gen_story = old_realm in ALLOW_STORY_FROM_REALMS if self._gen_story: - self._calamity = self._choose_calamity() - self._calamity_other = self._choose_related_avatar(self._calamity) + self._calamity = TribulationSelector.choose_tribulation(self.avatar) + self._calamity_other = TribulationSelector.choose_related_avatar(self.avatar, self._calamity) else: self._calamity = None self._calamity_other = None @@ -152,31 +140,9 @@ class Breakthrough(TimedAction): if True: # 故事参与者:本体 +(可选)相关角色 - desc = CALAMITY_DESCRIPTIONS.get(str(calamity), "") - prompt = (STORY_PROMPT_BASE.format(calamity=str(calamity)) + (" " + desc if desc else "")).strip() + prompt = TribulationSelector.get_story_prompt(str(calamity)) story = StoryTeller.tell_from_actors(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, getattr(self, "_calamity_other", None), prompt=prompt) events.append(Event(self.world.month_stamp, story, related_avatars=rel_ids)) return events - # ——— 内部:劫难选择与关联角色 ——— - def _choose_calamity(self) -> str: - base = ["心魔", "雷劫", "肉身"] - rels = getattr(self.avatar, "relations", {}) - has_enemy = any(rel is Relation.ENEMY for rel in rels.values()) - has_lover = any(rel is Relation.LOVERS for rel in rels.values()) - if has_enemy: - base.append("寻仇") - if has_lover: - base.append("情劫") - return random.choice(base) - - def _choose_related_avatar(self, calamity: str): - if calamity not in ("寻仇", "情劫"): - return None - target_rel = Relation.ENEMY if calamity == "寻仇" else Relation.LOVERS - candidates = [other for other, rel in self.avatar.relations.items() if rel is target_rel] - if not candidates: - return None - return random.choice(candidates) - diff --git a/src/classes/tribulation.py b/src/classes/tribulation.py new file mode 100644 index 0000000..4d011ed --- /dev/null +++ b/src/classes/tribulation.py @@ -0,0 +1,172 @@ +""" +天劫系统 +负责管理突破时的各种劫难类型、描述和选择逻辑 +""" +from __future__ import annotations +import random +from typing import TYPE_CHECKING, Optional +from dataclasses import dataclass + +if TYPE_CHECKING: + from src.classes.avatar import Avatar + +from src.classes.relation import Relation + + +@dataclass +class TribulationType: + """天劫类型定义""" + name: str # 劫难名称 + description: str # 劫难描述(用于生成故事的提示) + requires_other_avatar: bool = False # 是否需要关联其他角色 + relation_type: Optional[Relation] = None # 需要的关系类型 + + +# 天劫类型定义 +TRIBULATION_TYPES = { + "心魔": TribulationType( + name="心魔", + description="心念起伏,自我否定与执念缠斗,外寂而内喧。道心深处,旧日疑惑与未来迷茫交织,一念之差即可坠入心魔深渊。", + requires_other_avatar=False + ), + "雷劫": TribulationType( + name="雷劫", + description="天威如潮,电光凝成纹理,压迫骨血与神识。紫霄雷霆自九天垂落,每一道雷光都是天地对修士的考校,稍有不慎便是形神俱灭。", + requires_other_avatar=False + ), + "肉身": TribulationType( + name="肉身", + description="筋骨皮膜重塑,真气磨砺经脉,疼痛与新生并至。肉身如熔炉,每一寸血肉都在烈火中淬炼,破而后立,痛极方能重生。", + requires_other_avatar=False + ), + "寻仇": TribulationType( + name="寻仇", + description="仇人旧怨不散,刀光在心底回响,一念之差改写因果。因果纠缠,宿怨难消,突破之际正是因果清算之时。", + requires_other_avatar=True, + relation_type=Relation.ENEMY + ), + "情劫": TribulationType( + name="情劫", + description="柔情即刃,难舍难分,念头被拉回人间烟火。情丝缠绕,斩不断理还乱,一念执着便是万劫不复。", + requires_other_avatar=True, + relation_type=Relation.LOVERS + ), + "阳罡": TribulationType( + name="阳罡", + description="日轮灼烈,天地赤白两色,阳罡刀风如白金烈焰。正气涤秽,刚猛直击,阳盛则裂。午时最烈,光杀心识,经脉燥爆之险时刻相随。", + requires_other_avatar=False + ), + "雷火": TribulationType( + name="雷火", + description="云海层叠,紫电潜伏,雷中生火、火中孕雷。外冲与内炼同调,神魂与经络双线考校。雷海覆顶,业劫天火焚因果,一瞬之间便是生死两重天。", + requires_other_avatar=False + ), + "风灾": TribulationType( + name="风灾", + description="无风之地先起丝音,罡风断骨,真空撕扯,噬灵风蚀。风洞共鸣如锯切,位移与姿态稍有不慎便是肉身裂口、护体法相被剥。", + requires_other_avatar=False + ), + "阴狱": TribulationType( + name="阴狱", + description="温度突降,黑霜上爬,冤魂压境,寒狱锁身。六道幻刑,阴火烛骨,以业、怖、悔、执为四柱。善恶因果入场,心神判定重于肉身。", + requires_other_avatar=False + ), + "魔劫": TribulationType( + name="魔劫", + description="心跳与灵识节奏错位,旧欲旧恨自启,识海生魔胎影。心魔境中,欲、嗔、痴、慢、疑轮番试探,道心完整度与自洽度为核,一念之差即可走火入魔。", + requires_other_avatar=False + ), +} + + +# 故事生成的基础提示词 +STORY_PROMPT_BASE = "以古风、凝练、不炫技的笔触,描绘修士历经{calamity}劫时的心境与取舍,篇幅100~150字。" + + +class TribulationSelector: + """天劫选择器,负责根据修士状态选择合适的劫难""" + + @staticmethod + def choose_tribulation(avatar: Avatar) -> str: + """ + 根据修士的境界、关系等选择劫难类型 + + Args: + avatar: 渡劫的修士 + + Returns: + 劫难名称 + """ + # 基础劫难池(不需要其他角色) + base_tribulations = ["心魔", "雷劫", "肉身", "阳罡", "雷火", "风灾", "阴狱", "魔劫"] + + # 检查是否有特殊关系,可以触发关系型劫难 + available_tribulations = base_tribulations.copy() + + rels = getattr(avatar, "relations", {}) + has_enemy = any(rel is Relation.ENEMY for rel in rels.values()) + has_lover = any(rel is Relation.LOVERS for rel in rels.values()) + + if has_enemy: + available_tribulations.append("寻仇") + if has_lover: + available_tribulations.append("情劫") + + return random.choice(available_tribulations) + + @staticmethod + def choose_related_avatar(avatar: Avatar, tribulation_name: str) -> Optional[Avatar]: + """ + 根据劫难类型选择相关的角色(如果需要) + + Args: + avatar: 渡劫的修士 + tribulation_name: 劫难名称 + + Returns: + 相关角色,如果不需要则返回 None + """ + tribulation = TRIBULATION_TYPES.get(tribulation_name) + if not tribulation or not tribulation.requires_other_avatar: + return None + + target_rel = tribulation.relation_type + candidates = [ + other for other, rel in avatar.relations.items() + if rel is target_rel + ] + + if not candidates: + return None + + return random.choice(candidates) + + @staticmethod + def get_description(tribulation_name: str) -> str: + """ + 获取劫难的描述文本 + + Args: + tribulation_name: 劫难名称 + + Returns: + 劫难描述 + """ + tribulation = TRIBULATION_TYPES.get(tribulation_name) + return tribulation.description if tribulation else "" + + @staticmethod + def get_story_prompt(tribulation_name: str) -> str: + """ + 获取生成故事的提示词 + + Args: + tribulation_name: 劫难名称 + + Returns: + 完整的故事生成提示词 + """ + desc = TribulationSelector.get_description(tribulation_name) + prompt = STORY_PROMPT_BASE.format(calamity=tribulation_name) + return (prompt + (" " + desc if desc else "")).strip() +