add more fortunes

This commit is contained in:
bridge
2025-11-11 18:58:40 +08:00
parent ed4174d5ed
commit 0cb7eacee7
3 changed files with 219 additions and 0 deletions

View File

@@ -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字小故事。"

View File

@@ -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)

View File

@@ -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