add misfortune

This commit is contained in:
bridge
2025-12-30 23:09:29 +08:00
parent e1f5e5e92a
commit 17ca0cbbce
5 changed files with 163 additions and 19 deletions

View File

@@ -42,18 +42,6 @@ EXTRA_MAX_HP = "extra_max_hp"
- 大量: 200+
"""
EXTRA_MAX_MP = "extra_max_mp"
"""
额外最大灵力值
类型: int
结算: src/classes/avatar.py (__post_init__)
说明: 增加角色的最大灵力值上限。
数值参考:
- 微量: 10~30
- 中量: 50~100 (练气期基础MP约100)
- 大量: 200+
"""
EXTRA_OBSERVATION_RADIUS = "extra_observation_radius"
"""
额外观察半径
@@ -372,7 +360,6 @@ ALL_EFFECTS = [
# 战斗相关
"extra_battle_strength_points", # int - 额外战斗力
"extra_max_hp", # int - 额外最大生命值
"extra_max_mp", # int - 额外最大灵力值
"extra_observation_radius", # int - 额外观察半径
"damage_reduction", # float - 伤害减免
"realm_suppression_bonus", # float - 境界压制加成
@@ -403,6 +390,7 @@ ALL_EFFECTS = [
# 奇遇相关
"extra_fortune_probability", # float - 额外奇遇概率
"extra_misfortune_probability", # float - 额外霉运概率
# 兵器相关
"extra_weapon_proficiency_gain", # float - 额外兵器熟练度增长倍率

View File

@@ -365,7 +365,7 @@ def _get_spirit_stone_amount(avatar: Avatar) -> int:
return random.randint(*range_tuple)
def _get_cultivation_exp(avatar: Avatar) -> int:
def get_cultivation_exp_reward(avatar: Avatar) -> int:
"""根据境界返回修为经验(相当于一年修炼的收益)"""
from src.classes.cultivation import Realm
@@ -523,13 +523,13 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
res_text = f"{avatar.name} 获得灵石 {amount}"
elif kind == FortuneKind.CULTIVATION:
exp_gain = _get_cultivation_exp(avatar)
exp_gain = get_cultivation_exp_reward(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字小故事。"
story_prompt = "请据此写100~300字小故事。"
month_at_finish = avatar.world.month_stamp
base_event = Event(month_at_finish, event_text, related_avatars=related_avatars, is_major=True)
@@ -545,4 +545,5 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
__all__ = [
"try_trigger_fortune",
"get_cultivation_exp_reward",
]

152
src/classes/misfortune.py Normal file
View File

@@ -0,0 +1,152 @@
from __future__ import annotations
import random
from enum import Enum
from typing import Optional
from src.utils.config import CONFIG
from src.classes.avatar import Avatar
from src.classes.event import Event
from src.classes.story_teller import StoryTeller
from src.classes.fortune import get_cultivation_exp_reward
class MisfortuneKind(Enum):
"""霉运类型"""
LOSS_SPIRIT_STONE = "loss_spirit_stone" # 破财
INJURY = "injury" # 受伤
CULTIVATION_BACKLASH = "backlash" # 修为倒退
MF_LOSS_SPIRIT_STONE_THEMES: list[str] = [
"遭遇扒手",
"误买假货",
"遭人勒索",
"洞府失窃",
"赌石惨败",
"投资失败",
]
MF_INJURY_THEMES: list[str] = [
"修炼岔气",
"出门摔伤",
"妖兽偷袭",
"仇家闷棍",
"误触机关",
"天降横祸",
]
MF_BACKLASH_THEMES: list[str] = [
"心魔滋生",
"灵气逆行",
"感悟错乱",
"急火攻心",
]
def _choose_misfortune_kind(avatar: Avatar) -> Optional[MisfortuneKind]:
"""选择霉运类型"""
candidates = []
# 破财:必须有灵石
if avatar.magic_stone.value > 0:
candidates.append(MisfortuneKind.LOSS_SPIRIT_STONE)
# 受伤:任何人都可以受伤
candidates.append(MisfortuneKind.INJURY)
# 修为倒退:只有修炼者(有修为且未满?)或者任何人都可以?
# 简单处理:任何人都可以走火入魔
candidates.append(MisfortuneKind.CULTIVATION_BACKLASH)
if not candidates:
return None
return random.choice(candidates)
def _pick_misfortune_theme(kind: MisfortuneKind) -> str:
if kind == MisfortuneKind.LOSS_SPIRIT_STONE:
return random.choice(MF_LOSS_SPIRIT_STONE_THEMES)
elif kind == MisfortuneKind.INJURY:
return random.choice(MF_INJURY_THEMES)
elif kind == MisfortuneKind.CULTIVATION_BACKLASH:
return random.choice(MF_BACKLASH_THEMES)
return ""
async def try_trigger_misfortune(avatar: Avatar) -> list[Event]:
"""
触发霉运
规则:
- 概率config + effects
- 类型:破财、受伤、修为倒退
- 破财:随机数,不超过总量
- 受伤扣减HP可能致死由simulator结算
- 修为倒退:扣减经验,不降级(经验值可为负?)-> 此处逻辑扣减当前经验最小为0
"""
base_prob = float(getattr(CONFIG.game, "misfortune_probability", 0.0))
extra_prob = float(avatar.effects.get("extra_misfortune_probability", 0.0))
prob = base_prob + extra_prob
if prob <= 0.0:
return []
if random.random() >= prob:
return []
kind = _choose_misfortune_kind(avatar)
if kind is None:
return []
theme = _pick_misfortune_theme(kind)
res_text: str = ""
if kind == MisfortuneKind.LOSS_SPIRIT_STONE:
# 破财:随机数,不超过总量
max_loss = avatar.magic_stone.value
# 设定一个随机范围,例如 10~500但受 max_loss 限制
# 或者完全随机
loss = random.randint(50, 300)
loss = min(loss, max_loss)
avatar.magic_stone.value -= loss
res_text = f"{avatar.name} 损失灵石 {loss}"
elif kind == MisfortuneKind.INJURY:
# 受伤扣减HP
# 扣减量:最大生命值的 10%~80% + 固定值
max_hp = avatar.hp.max
ratio = random.uniform(0.1, 0.8)
damage = int(max_hp * ratio) + random.randint(10, 50)
avatar.hp.cur -= damage
# 注意这里可能扣成负数simulator 会在 _phase_resolve_death 中处理
res_text = f"{avatar.name} 受到伤害 {damage}剩余HP {avatar.hp.cur}/{max_hp}"
elif kind == MisfortuneKind.CULTIVATION_BACKLASH:
# 修为倒退
# 扣减量100~500
loss = random.randint(100, 500)
# 确保不扣到负数(或者允许负数?通常经验不为负)
# 这里只扣减当前经验,不掉级
current_exp = avatar.cultivation_progress.exp
actual_loss = min(current_exp, loss)
avatar.cultivation_progress.exp -= actual_loss
res_text = f"{avatar.name} 修为倒退"
# 生成故事
event_text = f"遭遇霉运({theme}{res_text}"
story_prompt = "请据此写100~300字小故事。只描述倒霉事件本身不要描述角色的心理活动或者愈挫愈勇。"
month_at_finish = avatar.world.month_stamp
base_event = Event(month_at_finish, event_text, related_avatars=[avatar.id], is_major=True)
story = await StoryTeller.tell_story(
event_text, res_text, avatar,
prompt=story_prompt,
allow_relation_changes=False
)
story_event = Event(month_at_finish, story, related_avatars=[avatar.id], is_story=True)
return [base_event, story_event]

View File

@@ -13,6 +13,7 @@ from src.classes.name import get_random_name
from src.utils.config import CONFIG
from src.run.log import get_logger
from src.classes.fortune import try_trigger_fortune
from src.classes.misfortune import try_trigger_misfortune
from src.classes.celestial_phenomenon import get_random_celestial_phenomenon
from src.classes.long_term_objective import process_avatar_long_term_objective
from src.classes.death import handle_death
@@ -203,9 +204,10 @@ class Simulator:
for avatar in living_avatars:
avatar.update_time_effect()
# 使用 gather 并行触发奇遇
tasks = [try_trigger_fortune(avatar) for avatar in living_avatars]
results = await asyncio.gather(*tasks)
# 使用 gather 并行触发奇遇和霉运
tasks_fortune = [try_trigger_fortune(avatar) for avatar in living_avatars]
tasks_misfortune = [try_trigger_misfortune(avatar) for avatar in living_avatars]
results = await asyncio.gather(*(tasks_fortune + tasks_misfortune))
events.extend([e for res in results if res for e in res])