update fortune
This commit is contained in:
@@ -11,9 +11,17 @@ from src.classes.event import Event
|
||||
from src.classes.story_teller import StoryTeller
|
||||
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.technique import (
|
||||
TechniqueGrade,
|
||||
get_random_upper_technique_for_avatar,
|
||||
techniques_by_id,
|
||||
Technique,
|
||||
is_attribute_compatible_with_root,
|
||||
TechniqueAttribute,
|
||||
)
|
||||
from src.classes.treasure import Treasure, treasures_by_id
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.alignment import Alignment
|
||||
|
||||
|
||||
class FortuneKind(Enum):
|
||||
@@ -48,39 +56,89 @@ F_FIND_MASTER_THEMES: list[str] = [
|
||||
]
|
||||
|
||||
|
||||
def _is_rogue_and_under_equipped(avatar: Avatar) -> bool:
|
||||
# 必须散修;法宝为空 或 功法非上品
|
||||
if avatar.sect is not None:
|
||||
return False
|
||||
has_no_treasure = avatar.treasure is None
|
||||
is_tech_lower = (avatar.technique is None) or (avatar.technique.grade is not TechniqueGrade.UPPER)
|
||||
return has_no_treasure or is_tech_lower
|
||||
|
||||
|
||||
def _has_master(avatar: Avatar) -> bool:
|
||||
"""检查是否已有师傅"""
|
||||
for other, rel in avatar.relations.items():
|
||||
if rel == Relation.MASTER:
|
||||
if rel == Relation.APPRENTICE:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_alignment_compatible(avatar: Avatar, other: Avatar) -> bool:
|
||||
"""检查两个角色的阵营是否兼容(不是敌对关系)"""
|
||||
from src.classes.alignment import Alignment
|
||||
if avatar.alignment is None or other.alignment is None:
|
||||
return True
|
||||
# 正邪不相容
|
||||
if avatar.alignment == Alignment.RIGHTEOUS and other.alignment == Alignment.EVIL:
|
||||
return False
|
||||
if avatar.alignment == Alignment.EVIL and other.alignment == Alignment.RIGHTEOUS:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _find_potential_master(avatar: Avatar) -> Optional[Avatar]:
|
||||
"""
|
||||
在世界中寻找潜在的师傅。
|
||||
条件:等级 > avatar.level + 20
|
||||
规则:
|
||||
1. 等级 > avatar.level + 20
|
||||
2. 优先选择同宗门的高手
|
||||
3. 如果没有同宗门的,选择阵营兼容的其他人
|
||||
4. 不能拜敌对阵营的人为师
|
||||
"""
|
||||
candidates: list[Avatar] = []
|
||||
same_sect_candidates: list[Avatar] = []
|
||||
other_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 level_diff < 20:
|
||||
continue
|
||||
|
||||
# 阵营兼容性检查
|
||||
if not _is_alignment_compatible(avatar, other):
|
||||
continue
|
||||
|
||||
# 同宗门优先
|
||||
if avatar.sect is not None and other.sect == avatar.sect:
|
||||
same_sect_candidates.append(other)
|
||||
else:
|
||||
other_candidates.append(other)
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
return random.choice(candidates)
|
||||
# 优先从同宗门选择
|
||||
if same_sect_candidates:
|
||||
return random.choice(same_sect_candidates)
|
||||
|
||||
# 没有同宗门的,从其他候选中选择
|
||||
if other_candidates:
|
||||
return random.choice(other_candidates)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _can_get_treasure(avatar: Avatar) -> bool:
|
||||
"""检查是否可以获得法宝奇遇"""
|
||||
return avatar.treasure is None
|
||||
|
||||
|
||||
def _can_get_technique(avatar: Avatar) -> bool:
|
||||
"""
|
||||
检查是否可以获得功法奇遇
|
||||
- 任何人功法非上品都可以触发
|
||||
- 但实际能否获得功法,在获取时会有额外检查(宗门弟子有限制)
|
||||
"""
|
||||
tech_not_upper = (avatar.technique is None) or (avatar.technique.grade is not TechniqueGrade.UPPER)
|
||||
return tech_not_upper
|
||||
|
||||
|
||||
def _can_get_master(avatar: Avatar) -> bool:
|
||||
"""检查是否可以获得拜师奇遇"""
|
||||
if _has_master(avatar):
|
||||
return False
|
||||
return _find_potential_master(avatar) is not None
|
||||
|
||||
|
||||
def _choose_kind(avatar: Avatar) -> FortuneKind:
|
||||
@@ -90,20 +148,17 @@ def _choose_kind(avatar: Avatar) -> FortuneKind:
|
||||
"""
|
||||
possible_kinds: list[FortuneKind] = []
|
||||
|
||||
# 法宝奇遇:散修且无法宝
|
||||
if avatar.sect is None and avatar.treasure is None:
|
||||
# 法宝奇遇:任何人无法宝都可以
|
||||
if _can_get_treasure(avatar):
|
||||
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 _can_get_technique(avatar):
|
||||
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 _can_get_master(avatar):
|
||||
possible_kinds.append(FortuneKind.FIND_MASTER)
|
||||
|
||||
if not possible_kinds:
|
||||
return None
|
||||
@@ -122,7 +177,7 @@ def _pick_theme(kind: FortuneKind) -> str:
|
||||
|
||||
|
||||
def _get_unique_treasure_for_world(avatar: Avatar) -> Optional[Treasure]:
|
||||
# 世界唯一法宝:从全量里挑选一个未被任何人持有的
|
||||
"""获取世界唯一法宝:从全量里挑选一个未被任何人持有的"""
|
||||
owned_ids: set[int] = set()
|
||||
for other in avatar.world.avatar_manager.avatars.values():
|
||||
if other.treasure is not None:
|
||||
@@ -133,13 +188,68 @@ def _get_unique_treasure_for_world(avatar: Avatar) -> Optional[Treasure]:
|
||||
return random.choice(candidates)
|
||||
|
||||
|
||||
def _get_fortune_technique_for_avatar(avatar: Avatar) -> Optional[Technique]:
|
||||
"""
|
||||
为奇遇获取功法。
|
||||
规则:
|
||||
1. 散修:可以获得任何上品功法(与灵根/阵营/condition兼容)
|
||||
2. 宗门弟子:只能获得本宗门或无宗门的上品功法
|
||||
"""
|
||||
candidates: list[Technique] = []
|
||||
|
||||
# 确定允许的宗门范围
|
||||
allowed_sects: set[Optional[str]] = {None, ""}
|
||||
if avatar.sect is not None:
|
||||
sect_name = avatar.sect.name.strip() if avatar.sect.name else None
|
||||
if sect_name:
|
||||
allowed_sects.add(sect_name)
|
||||
|
||||
# 筛选功法
|
||||
for t in techniques_by_id.values():
|
||||
# 必须是上品
|
||||
if t.grade != TechniqueGrade.UPPER:
|
||||
continue
|
||||
|
||||
# 宗门限制:宗门弟子只能获得本宗门或无宗门的功法
|
||||
tech_sect = t.sect.strip() if t.sect else None
|
||||
if tech_sect not in allowed_sects:
|
||||
continue
|
||||
|
||||
# condition 检查
|
||||
if not t.is_allowed_for(avatar):
|
||||
continue
|
||||
|
||||
# 邪功法只能邪道修士修炼
|
||||
if t.attribute == TechniqueAttribute.EVIL and avatar.alignment != Alignment.EVIL:
|
||||
continue
|
||||
|
||||
# 灵根兼容性
|
||||
if not is_attribute_compatible_with_root(t.attribute, avatar.root):
|
||||
continue
|
||||
|
||||
candidates.append(t)
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
# 按权重随机选择
|
||||
weights = [max(0.0, t.weight) for t in candidates]
|
||||
return random.choices(candidates, weights=weights, k=1)[0]
|
||||
|
||||
|
||||
def try_trigger_fortune(avatar: Avatar) -> list[Event]:
|
||||
"""
|
||||
在月度结算阶段尝试触发奇遇。
|
||||
规则:
|
||||
- 奇遇不是一个 action;仅在条件满足时以概率触发。
|
||||
- 触发条件:散修且(无法宝 或 功法非上品),或者无师傅。
|
||||
- 结果:先决定奖励类型(法宝/功法/拜师),法宝世界唯一且不可重复;功法可重复但优先上品且需与灵根兼容;拜师建立师徒关系。
|
||||
- 触发条件:
|
||||
* 法宝奇遇:无法宝(不限散修/宗门)
|
||||
* 功法奇遇:功法非上品(不限散修/宗门,但宗门弟子只能获得本宗门或无宗门功法)
|
||||
* 拜师奇遇:无师傅且世界中有合适的师傅(优先同宗门,不能拜敌对阵营)
|
||||
- 结果:
|
||||
* 法宝:世界唯一且不可重复
|
||||
* 功法:可重复,优先上品,需与灵根兼容,宗门弟子受宗门限制
|
||||
* 拜师:建立师徒关系
|
||||
- 故事:仅给出主旨主题,由 LLM 自由发挥生成短故事。
|
||||
"""
|
||||
prob = float(getattr(CONFIG.game, "fortune_probability", 0.0))
|
||||
@@ -170,9 +280,9 @@ def try_trigger_fortune(avatar: Avatar) -> list[Event]:
|
||||
res_text = f"{avatar.name} 获得法宝『{tr.name}』"
|
||||
|
||||
if kind == FortuneKind.TECHNIQUE:
|
||||
tech = get_random_upper_technique_for_avatar(avatar)
|
||||
tech = _get_fortune_technique_for_avatar(avatar)
|
||||
if tech is None:
|
||||
# 若无可用上品,则不奖励
|
||||
# 若无可用上品功法(宗门弟子可能因宗门限制而找不到),则不奖励
|
||||
return []
|
||||
avatar.technique = tech
|
||||
res_text = f"{avatar.name} 得到上品功法『{tech.name}』"
|
||||
@@ -182,8 +292,8 @@ def try_trigger_fortune(avatar: Avatar) -> list[Event]:
|
||||
if master is None:
|
||||
# 找不到合适的师傅
|
||||
return []
|
||||
# 建立师徒关系
|
||||
avatar.set_relation(master, Relation.MASTER)
|
||||
# 建立师徒关系:avatar 是徒弟,master 是师傅
|
||||
avatar.set_relation(master, Relation.APPRENTICE)
|
||||
res_text = f"{avatar.name} 拜 {master.name} 为师"
|
||||
related_avatars.append(master.id)
|
||||
actors_for_story = [avatar, master] # 拜师奇遇需要两个人的信息
|
||||
|
||||
@@ -38,9 +38,9 @@ class Impart(MutualAction):
|
||||
|
||||
def _can_start(self, target: "Avatar") -> tuple[bool, str]:
|
||||
"""检查传道特有的启动条件"""
|
||||
# 检查是否是师徒关系
|
||||
# 检查是否是师徒关系:师傅对徒弟的关系应该是 MASTER
|
||||
relation = self.avatar.get_relation(target)
|
||||
if relation != Relation.APPRENTICE:
|
||||
if relation != Relation.MASTER:
|
||||
return False, "目标不是自己的徒弟"
|
||||
|
||||
# 检查等级差
|
||||
|
||||
Reference in New Issue
Block a user