update fortune

This commit is contained in:
bridge
2025-11-02 22:38:33 +08:00
parent fa13bd6cac
commit 82a9fbc024
3 changed files with 256 additions and 32 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import random
from enum import Enum
from typing import Optional
import asyncio
@@ -12,6 +13,14 @@ from src.classes.action.event_helper import EventHelper
from src.utils.asyncio_utils import schedule_background
from src.classes.technique import TechniqueGrade, get_random_upper_technique_for_avatar
from src.classes.treasure import Treasure, treasures_by_id
from src.classes.relation import Relation
class FortuneKind(Enum):
"""奇遇类型"""
TREASURE = "treasure"
TECHNIQUE = "technique"
FIND_MASTER = "find_master"
F_TREASURE_THEMES: list[str] = [
@@ -30,6 +39,14 @@ F_TECHNIQUE_THEMES: list[str] = [
"玄妙感悟",
]
F_FIND_MASTER_THEMES: list[str] = [
"危难相救",
"品行打动",
"展露天赋",
"机缘巧合",
"通过考验",
]
def _is_rogue_and_under_equipped(avatar: Avatar) -> bool:
# 必须散修;法宝为空 或 功法非上品
@@ -40,23 +57,68 @@ def _is_rogue_and_under_equipped(avatar: Avatar) -> bool:
return has_no_treasure or is_tech_lower
def _choose_kind(avatar: Avatar) -> str:
# 如果无法宝,偏向法宝;否则若功法非上品,偏向功法;否则随机
no_treasure = avatar.treasure is None
tech_not_upper = (avatar.technique is None) or (avatar.technique.grade is not TechniqueGrade.UPPER)
if no_treasure and tech_not_upper:
return random.choice(["treasure", "technique"]) # 两者都缺,随机其一
if no_treasure:
return "treasure"
if tech_not_upper:
return "technique"
return random.choice(["treasure", "technique"])
def _has_master(avatar: Avatar) -> bool:
"""检查是否已有师傅"""
for other, rel in avatar.relations.items():
if rel == Relation.MASTER:
return True
return False
def _pick_theme(kind: str) -> str:
if kind == "treasure":
def _find_potential_master(avatar: Avatar) -> Optional[Avatar]:
"""
在世界中寻找潜在的师傅。
条件:等级 > avatar.level + 20
"""
candidates: list[Avatar] = []
for other in avatar.world.avatar_manager.avatars.values():
if other is avatar:
continue
level_diff = other.cultivation_progress.level - avatar.cultivation_progress.level
if level_diff >= 20:
candidates.append(other)
if not candidates:
return None
return random.choice(candidates)
def _choose_kind(avatar: Avatar) -> FortuneKind:
"""
从所有可能的奇遇中随机选择一个。
可能的奇遇取决于角色当前状态。
"""
possible_kinds: list[FortuneKind] = []
# 法宝奇遇:散修且无法宝
if avatar.sect is None and avatar.treasure is None:
possible_kinds.append(FortuneKind.TREASURE)
# 功法奇遇:散修且功法非上品
if avatar.sect is None:
tech_not_upper = (avatar.technique is None) or (avatar.technique.grade is not TechniqueGrade.UPPER)
if tech_not_upper:
possible_kinds.append(FortuneKind.TECHNIQUE)
# 拜师奇遇:无师傅且世界中有合适的师傅
if not _has_master(avatar):
if _find_potential_master(avatar) is not None:
possible_kinds.append(FortuneKind.FIND_MASTER)
if not possible_kinds:
return None
return random.choice(possible_kinds)
def _pick_theme(kind: FortuneKind) -> str:
if kind == FortuneKind.TREASURE:
return random.choice(F_TREASURE_THEMES)
return random.choice(F_TECHNIQUE_THEMES)
elif kind == FortuneKind.TECHNIQUE:
return random.choice(F_TECHNIQUE_THEMES)
elif kind == FortuneKind.FIND_MASTER:
return random.choice(F_FIND_MASTER_THEMES)
return ""
def _get_unique_treasure_for_world(avatar: Avatar) -> Optional[Treasure]:
@@ -76,33 +138,38 @@ def try_trigger_fortune(avatar: Avatar) -> list[Event]:
在月度结算阶段尝试触发奇遇。
规则:
- 奇遇不是一个 action仅在条件满足时以概率触发。
- 触发条件:散修且(无法宝 或 功法非上品)。
- 结果:先决定奖励类型(法宝/功法),法宝世界唯一且不可重复;功法可重复但优先上品且需与灵根兼容。
- 触发条件:散修且(无法宝 或 功法非上品),或者无师傅
- 结果:先决定奖励类型(法宝/功法/拜师),法宝世界唯一且不可重复;功法可重复但优先上品且需与灵根兼容;拜师建立师徒关系
- 故事:仅给出主旨主题,由 LLM 自由发挥生成短故事。
"""
prob = float(getattr(CONFIG.game, "fortune_probability", 0.0))
if prob <= 0.0:
return []
if not _is_rogue_and_under_equipped(avatar):
return []
if random.random() >= prob:
return []
# 从所有可能的奇遇中选择
kind = _choose_kind(avatar)
if kind is None:
return []
theme = _pick_theme(kind)
res_text: str = ""
related_avatars = [avatar.id]
actors_for_story = [avatar] # 用于生成故事的角色列表
if kind == "treasure":
if kind == FortuneKind.TREASURE:
tr = _get_unique_treasure_for_world(avatar)
if tr is None:
# 回退到功法
kind = "technique"
kind = FortuneKind.TECHNIQUE
theme = _pick_theme(kind)
else:
avatar.treasure = tr
res_text = f"{avatar.name} 获得法宝『{tr.name}"
if kind == "technique":
if kind == FortuneKind.TECHNIQUE:
tech = get_random_upper_technique_for_avatar(avatar)
if tech is None:
# 若无可用上品,则不奖励
@@ -110,24 +177,42 @@ def try_trigger_fortune(avatar: Avatar) -> list[Event]:
avatar.technique = tech
res_text = f"{avatar.name} 得到上品功法『{tech.name}"
elif kind == FortuneKind.FIND_MASTER:
master = _find_potential_master(avatar)
if master is None:
# 找不到合适的师傅
return []
# 建立师徒关系
avatar.set_relation(master, Relation.MASTER)
res_text = f"{avatar.name}{master.name} 为师"
related_avatars.append(master.id)
actors_for_story = [avatar, master] # 拜师奇遇需要两个人的信息
# 生成故事(异步避免阻塞)
event_text = f"遭遇奇遇({theme}{res_text}"
story_prompt = (
f"请据此写100~150字小故事。"
)
story_prompt = "请据此写100~150字小故事。"
month_at_finish = avatar.world.month_stamp
base_event = Event(month_at_finish, event_text, related_avatars=[avatar.id])
base_event = Event(month_at_finish, event_text, related_avatars=related_avatars)
async def _gen_and_push_story():
story = await StoryTeller.tell_from_actors_async(event_text, res_text, avatar, prompt=story_prompt)
story_event = Event(month_at_finish, story, related_avatars=[avatar.id])
EventHelper.push_self(story_event, avatar, to_sidebar=True)
# 拜师奇遇传入两个角色,其他奇遇传入一个角色
story = await StoryTeller.tell_from_actors_async(event_text, res_text, *actors_for_story, prompt=story_prompt)
story_event = Event(month_at_finish, story, related_avatars=related_avatars)
# 根据涉及角色数量推送事件
if len(actors_for_story) == 1:
EventHelper.push_self(story_event, avatar, to_sidebar=True)
else:
# 拜师奇遇涉及两个角色
EventHelper.push_pair(story_event, initiator=avatar, target=actors_for_story[1], to_sidebar_once=True)
def _fallback_sync():
story = StoryTeller.tell_from_actors(event_text, res_text, avatar, prompt=story_prompt)
story_event = Event(month_at_finish, story, related_avatars=[avatar.id])
EventHelper.push_self(story_event, avatar, to_sidebar=True)
story = StoryTeller.tell_from_actors(event_text, res_text, *actors_for_story, prompt=story_prompt)
story_event = Event(month_at_finish, story, related_avatars=related_avatars)
if len(actors_for_story) == 1:
EventHelper.push_self(story_event, avatar, to_sidebar=True)
else:
EventHelper.push_pair(story_event, initiator=avatar, target=actors_for_story[1], to_sidebar_once=True)
schedule_background(_gen_and_push_story(), fallback=_fallback_sync)