diff --git a/src/classes/fortune.py b/src/classes/fortune.py index 6c5ad2e..dd7d733 100644 --- a/src/classes/fortune.py +++ b/src/classes/fortune.py @@ -27,6 +27,8 @@ class FortuneKind(Enum): TREASURE = "treasure" TECHNIQUE = "technique" FIND_MASTER = "find_master" + SPIRIT_STONE = "spirit_stone" # 灵石奇遇 + CULTIVATION = "cultivation" # 修为奇遇 F_TREASURE_THEMES: list[str] = [ @@ -53,6 +55,25 @@ F_FIND_MASTER_THEMES: list[str] = [ "通过考验", ] +F_SPIRIT_STONE_THEMES: list[str] = [ + "偶遇灵矿", + "洞府遗财", + "击杀妖兽", + "交易获利", + "赌石得宝", + "拾遗藏宝", +] + +F_CULTIVATION_THEMES: list[str] = [ + "顿悟玄机", + "古碑感悟", + "服食灵药", + "秘境修炼", + "前辈灌顶", + "灵泉淬体", + "传承记忆", +] + def _has_master(avatar: Avatar) -> bool: """检查是否已有师傅""" @@ -139,6 +160,18 @@ def _can_get_master(avatar: Avatar) -> bool: return _find_potential_master(avatar) is not None +def _can_get_spirit_stone(avatar: Avatar) -> bool: + """检查是否可以获得灵石奇遇""" + # 任何人都可以获得灵石 + return True + + +def _can_get_cultivation(avatar: Avatar) -> bool: + """检查是否可以获得修为奇遇""" + # 只有未达到瓶颈的人才能获得修为 + return not avatar.cultivation_progress.is_in_bottleneck() + + def _choose_kind(avatar: Avatar) -> FortuneKind: """ 从所有可能的奇遇中随机选择一个。 @@ -158,6 +191,14 @@ def _choose_kind(avatar: Avatar) -> FortuneKind: if _can_get_master(avatar): possible_kinds.append(FortuneKind.FIND_MASTER) + # 灵石奇遇:任何人都可以 + if _can_get_spirit_stone(avatar): + possible_kinds.append(FortuneKind.SPIRIT_STONE) + + # 修为奇遇:未达到瓶颈的人可以 + if _can_get_cultivation(avatar): + possible_kinds.append(FortuneKind.CULTIVATION) + if not possible_kinds: return None @@ -171,6 +212,10 @@ def _pick_theme(kind: FortuneKind) -> str: return random.choice(F_TECHNIQUE_THEMES) elif kind == FortuneKind.FIND_MASTER: return random.choice(F_FIND_MASTER_THEMES) + elif kind == FortuneKind.SPIRIT_STONE: + return random.choice(F_SPIRIT_STONE_THEMES) + elif kind == FortuneKind.CULTIVATION: + return random.choice(F_CULTIVATION_THEMES) return "" @@ -235,6 +280,39 @@ def _get_fortune_technique_for_avatar(avatar: Avatar) -> Optional[Technique]: return random.choices(candidates, weights=weights, k=1)[0] +def _get_spirit_stone_amount(avatar: Avatar) -> int: + """根据境界返回灵石数量(相当于一年狩猎售卖的收入)""" + from src.classes.cultivation import Realm + + realm_ranges = { + Realm.Qi_Refinement: (20, 30), + Realm.Foundation_Establishment: (100, 150), + Realm.Core_Formation: (200, 300), + Realm.Nascent_Soul: (400, 600), + } + range_tuple = realm_ranges.get( + avatar.cultivation_progress.realm, + (20, 30) # 默认值 + ) + return random.randint(*range_tuple) + + +def _get_cultivation_exp(avatar: Avatar) -> int: + """根据境界返回修为经验(相当于一年修炼的收益)""" + from src.classes.cultivation import Realm + + realm_exp = { + Realm.Qi_Refinement: 600, + Realm.Foundation_Establishment: 800, + Realm.Core_Formation: 1000, + Realm.Nascent_Soul: 1200, + } + return realm_exp.get( + avatar.cultivation_progress.realm, + 600 # 默认值 + ) + + async def try_trigger_fortune(avatar: Avatar) -> list[Event]: """ 在月度结算阶段尝试触发奇遇。 @@ -244,10 +322,14 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]: * 法宝奇遇:无法宝(不限散修/宗门) * 功法奇遇:功法非上品(不限散修/宗门,但宗门弟子只能获得本宗门或无宗门功法) * 拜师奇遇:无师傅且世界中有合适的师傅(优先同宗门,不能拜敌对阵营) + * 灵石奇遇:任何人都可以触发 + * 修为奇遇:未达到瓶颈的人可以触发 - 结果: * 法宝:世界唯一且不可重复 * 功法:可重复,优先上品,需与灵根兼容,宗门弟子受宗门限制 * 拜师:建立师徒关系 + * 灵石:根据境界获得灵石(相当于一年狩猎售卖收入) + * 修为:根据境界增加修为经验(相当于一年修炼收益) - 故事:仅给出主旨主题,由 LLM 自由发挥生成短故事。 """ base_prob = float(getattr(CONFIG.game, "fortune_probability", 0.0)) @@ -298,6 +380,16 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]: related_avatars.append(master.id) actors_for_story = [avatar, master] # 拜师奇遇需要两个人的信息 + elif kind == FortuneKind.SPIRIT_STONE: + amount = _get_spirit_stone_amount(avatar) + avatar.magic_stone.value += amount + res_text = f"{avatar.name} 获得灵石 {amount} 枚" + + elif kind == FortuneKind.CULTIVATION: + exp_gain = _get_cultivation_exp(avatar) + avatar.cultivation_progress.add_exp(exp_gain) + res_text = f"{avatar.name} 修为增长 {exp_gain} 点" + # 生成故事(异步,等待完成) event_text = f"遭遇奇遇({theme}),{res_text}" story_prompt = "请据此写100~150字小故事。" diff --git a/src/classes/mutual_action/__init__.py b/src/classes/mutual_action/__init__.py index 04d9efd..8ca412d 100644 --- a/src/classes/mutual_action/__init__.py +++ b/src/classes/mutual_action/__init__.py @@ -7,6 +7,7 @@ from .conversation import Conversation from .dual_cultivation import DualCultivation from .talk import Talk from .impart import Impart +from .gift_spirit_stone import GiftSpiritStone from src.classes.action.registry import register_action __all__ = [ @@ -17,6 +18,7 @@ __all__ = [ "DualCultivation", "Talk", "Impart", + "GiftSpiritStone", ] # 注册 mutual actions(均为实际动作) @@ -26,5 +28,6 @@ register_action(actual=True)(Conversation) register_action(actual=True)(DualCultivation) register_action(actual=True)(Talk) register_action(actual=True)(Impart) +register_action(actual=True)(GiftSpiritStone) diff --git a/src/classes/mutual_action/gift_spirit_stone.py b/src/classes/mutual_action/gift_spirit_stone.py new file mode 100644 index 0000000..2fe073d --- /dev/null +++ b/src/classes/mutual_action/gift_spirit_stone.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from .mutual_action import MutualAction +from src.classes.event import Event +from src.utils.config import CONFIG + +if TYPE_CHECKING: + from src.classes.avatar import Avatar + + +class GiftSpiritStone(MutualAction): + """赠送灵石:向目标赠送灵石。 + + - 发起方灵石必须足够(至少100灵石) + - 目标在交互范围内 + - 目标可以选择 接受 或 拒绝 + - 若接受:发起方扣除100灵石,目标获得100灵石 + """ + + ACTION_NAME = "赠送灵石" + COMMENT = "向对方赠送灵石,一次赠送100灵石" + DOABLES_REQUIREMENTS = "发起者至少有100灵石;目标在交互范围内" + PARAMS = {"target_avatar": "AvatarName"} + FEEDBACK_ACTIONS = ["Accept", "Reject"] + STORY_PROMPT: str | None = "描绘一段赠送灵石的场景,体现赠送者的慷慨和接受者的反应。80~120字。" + + # 默认赠送数量 + GIFT_AMOUNT = 100 + + def _get_template_path(self) -> Path: + return CONFIG.paths.templates / "mutual_action.txt" + + def _can_start(self, target: "Avatar") -> tuple[bool, str]: + """检查赠送灵石的启动条件""" + # 检查发起者的灵石是否足够 + if self.avatar.magic_stone < self.GIFT_AMOUNT: + return False, f"灵石不足(当前:{self.avatar.magic_stone},需要:{self.GIFT_AMOUNT})" + + return True, "" + + def start(self, target_avatar: "Avatar|str") -> Event: + target = self._get_target_avatar(target_avatar) + target_name = target.name if target is not None else str(target_avatar) + rel_ids = [self.avatar.id] + if target is not None: + rel_ids.append(target.id) + event = Event( + self.world.month_stamp, + f"{self.avatar.name} 向 {target_name} 赠送 {self.GIFT_AMOUNT} 灵石", + related_avatars=rel_ids + ) + # 仅写入历史 + self.avatar.add_event(event, to_sidebar=False) + if target is not None: + target.add_event(event, to_sidebar=False) + # 记录开始文本用于故事生成 + self._start_event_content = event.content + # 初始化内部标记 + self._gift_success = False + return event + + def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None: + fb = str(feedback_name).strip() + if fb == "Accept": + # 接受则当场结算灵石转移 + self._apply_gift(target_avatar) + self._gift_success = True + else: + # 拒绝 + self._gift_success = False + + def _apply_gift(self, target: "Avatar") -> None: + """执行灵石转移""" + # 从发起者扣除灵石 + self.avatar.magic_stone -= self.GIFT_AMOUNT + # 目标获得灵石 + target.magic_stone += self.GIFT_AMOUNT + + def finish(self, target_avatar: "Avatar|str") -> list[Event]: + target = self._get_target_avatar(target_avatar) + events: list[Event] = [] + success = self._gift_success + if target is None: + return events + + if success: + result_text = f"{self.avatar.name} 赠送了 {self.GIFT_AMOUNT} 灵石给 {target.name}({self.avatar.name} 灵石:{self.avatar.magic_stone + self.GIFT_AMOUNT} → {self.avatar.magic_stone},{target.name} 灵石:{target.magic_stone - self.GIFT_AMOUNT} → {target.magic_stone})" + result_event = Event( + self.world.month_stamp, + result_text, + related_avatars=[self.avatar.id, target.id] + ) + events.append(result_event) + + # 生成赠送小故事 + from src.classes.story_teller import StoryTeller + start_text = self._start_event_content or result_event.content + story = StoryTeller.tell_from_actors( + start_text, + result_text, + self.avatar, + target, + prompt=self.STORY_PROMPT + ) + story_event = Event( + self.world.month_stamp, + story, + related_avatars=[self.avatar.id, target.id] + ) + events.append(story_event) + else: + result_text = f"{target.name} 婉拒了 {self.avatar.name} 的灵石赠送" + result_event = Event( + self.world.month_stamp, + result_text, + related_avatars=[self.avatar.id, target.id] + ) + events.append(result_event) + + return events +