refactor new avatar from mortal

This commit is contained in:
bridge
2025-10-24 01:48:48 +08:00
parent 7c63aba0c7
commit 30d5372608
2 changed files with 161 additions and 21 deletions

View File

@@ -49,6 +49,12 @@ MASTER_LEVEL_EXTRA_MAX: int = 10 # 在最小等级差基础上的额外
PARENT_LEVEL_MIN_DIFF: int = 10 # 父母与子女最小等级差
PARENT_LEVEL_EXTRA_MAX: int = 10 # 在最小等级差基础上的额外浮动
# —— 新凡人(单个)生成相关概率与范围 ——
NEW_MORTAL_PARENT_PROB: float = 0.30 # 有概率是某个既有角色的子女
NEW_MORTAL_SECT_PROB: float = 0.50 # 有概率成为某个“已有宗门”的弟子
NEW_MORTAL_MASTER_PROB: float = 0.40 # 若成为宗门弟子,有概率拜该宗门现有人物为师
NEW_MORTAL_LEVEL_MAX: int = 40 # 新凡人默认偏低等级上限
def random_gender() -> Gender:
return Gender.MALE if random.random() < 0.5 else Gender.FEMALE
@@ -56,26 +62,165 @@ def random_gender() -> Gender:
def get_new_avatar_from_mortal(world: World, current_month_stamp: MonthStamp, name: str, age: Age) -> Avatar:
"""
从凡人中来的新修士:最低境界、随机位置,不分配宗门/法宝。
从凡人中来的新修士:先规划宗门/关系,再生成实际角色;不分配宗门法宝。
"""
avatar_id = get_avatar_id()
# 规划
plan = plan_mortal(world, name=name, age=age)
# 生成
return build_mortal_from_plan(world, current_month_stamp, name=name, age=age, plan=plan)
class MortalPlan:
def __init__(self):
self.gender: Optional[Gender] = None
self.sect: Optional[Sect] = None
self.surname: Optional[str] = None
self.parent_avatar: Optional[Avatar] = None
self.master_avatar: Optional[Avatar] = None
self.level: int = max(LEVEL_MIN, random.randint(LEVEL_MIN, NEW_MORTAL_LEVEL_MAX))
self.pos_x: int = 0
self.pos_y: int = 0
def _pick_any_sect(existed_sects: Optional[List[Sect]]) -> Optional[Sect]:
if not existed_sects:
return None
return random.choice(existed_sects)
def _pick_sects_balanced(existed_sects: List[Sect], k: int) -> list[Optional[Sect]]:
"""
从宗门列表中“均衡”挑选 k 个位置的宗门引用:
- 每次选择当前计数最少的宗门之一;
- 返回长度为 k 的列表;
"""
if not existed_sects or k <= 0:
return []
counts: dict[int, int] = {s.id: 0 for s in existed_sects}
chosen: list[Optional[Sect]] = []
for _ in range(k):
min_count = min(counts.values()) if counts else 0
candidates = [s for s in existed_sects if counts.get(s.id, 0) == min_count]
s = random.choice(candidates)
counts[s.id] = counts.get(s.id, 0) + 1
chosen.append(s)
return chosen
def plan_mortal(world: World, name: str, age: Age, *, existed_sects: Optional[List[Sect]] = None, existing_avatars: Optional[List[Avatar]] = None) -> MortalPlan:
"""
规划新凡人的宗门与关系(父母/师徒),以及取名所需的姓氏等。
"""
plan = MortalPlan()
# 性别与位置
plan.gender = random_gender()
plan.pos_x = random.randint(0, world.map.width - 1)
plan.pos_y = random.randint(0, world.map.height - 1)
# 数据源
if existing_avatars is None:
existing_avatars = list(world.avatar_manager.avatars.values())
if existed_sects is None:
# 若 run 层已抽样,可传入;否则直接从世界可见宗门中随机(此处简单选择)
try:
from src.classes.sect import sects_by_id as _sects_by_id
existed_sects = list(_sects_by_id.values())
except Exception:
existed_sects = []
# 5.b 宗门(先于关系确定,便于后续取名/师徒)
if random.random() < NEW_MORTAL_SECT_PROB:
# 单人场景挑1个宗门复用均衡逻辑的退化形式
picked = _pick_sects_balanced(existed_sects or [], 1)
plan.sect = picked[0] if picked else None
# 5.a 父/母:从现有角色中挑选足够年长者
if random.random() < NEW_MORTAL_PARENT_PROB and existing_avatars:
candidates: list[Avatar] = []
for av in existing_avatars:
if av.age.age >= age.age + PARENT_MIN_DIFF:
candidates.append(av)
if candidates:
parent = random.choice(candidates)
plan.parent_avatar = parent
# 姓氏偏好:父男同姓,母女异姓(仅在 name 为空时影响)
if not name:
if parent.gender is Gender.MALE:
plan.surname = pick_surname_for_sect(plan.sect or parent.sect)
else:
# 母为女:尽量不同姓
mom_surname = pick_surname_for_sect(plan.sect or parent.sect)
# 迭代挑一个不同姓
for _ in range(5):
s = pick_surname_for_sect(plan.sect)
if s != mom_surname:
plan.surname = s
break
# 父母更强的趋势:控制新人的 level 不超过父母太多
if parent.cultivation_progress.level + PARENT_LEVEL_MIN_DIFF > plan.level:
plan.level = max(LEVEL_MIN, min(parent.cultivation_progress.level - 1, NEW_MORTAL_LEVEL_MAX))
# 5.c 师徒(仅当选中了宗门)
if plan.sect is not None and random.random() < NEW_MORTAL_MASTER_PROB and existing_avatars:
same_sect = [av for av in existing_avatars if av.sect is plan.sect]
if same_sect:
stronger = [av for av in same_sect if av.cultivation_progress.level >= plan.level + MASTER_LEVEL_MIN_DIFF]
if stronger:
plan.master_avatar = random.choice(stronger)
return plan
def build_mortal_from_plan(world: World, current_month_stamp: MonthStamp, *, name: str, age: Age, plan: MortalPlan) -> Avatar:
"""
根据规划创建新凡人,并写入父母/师徒关系;不分配宗门法宝。
取名规则:尊重传入 name若为空则按规划的 sect/surname 生成。
"""
# 名称
if name:
final_name = name
else:
if plan.surname:
final_name = get_random_name_with_surname(plan.gender, plan.surname, plan.sect)
else:
final_name = get_random_name_for_sect(plan.gender, plan.sect)
# 出生时间与位置
birth_month_stamp = current_month_stamp - age.age * 12 + random.randint(0, 11)
cultivation_progress = CultivationProgress(0)
pos_x = random.randint(0, world.map.width - 1)
pos_y = random.randint(0, world.map.height - 1)
gender = random.choice(list(Gender))
return Avatar(
# 基础对象
avatar = Avatar(
world=world,
name=name,
id=avatar_id,
name=final_name,
id=get_avatar_id(),
birth_month_stamp=MonthStamp(birth_month_stamp),
age=age,
gender=gender,
cultivation_progress=cultivation_progress,
pos_x=pos_x,
pos_y=pos_y,
gender=plan.gender,
cultivation_progress=CultivationProgress(plan.level),
pos_x=plan.pos_x,
pos_y=plan.pos_y,
sect=plan.sect,
)
# 位置刷新
avatar.tile = world.map.get_tile(avatar.pos_x, avatar.pos_y)
# 写关系(父母/师徒);不发放宗门法宝
if plan.parent_avatar is not None:
plan.parent_avatar.set_relation(avatar, Relation.PARENT)
if plan.master_avatar is not None:
plan.master_avatar.set_relation(avatar, Relation.MASTER)
# 功法将由 __post_init__ 自动基于 sect 设置;灵根映射同 make_avatars 流程
if avatar.technique is not None:
mapped = attribute_to_root(avatar.technique.attribute)
if mapped is not None:
avatar.root = mapped
return avatar
def plan_sects_and_relations(n: int, existed_sects: Optional[List[Sect]]) -> tuple[list[Optional[Sect]], list[Optional[Gender]], list[Optional[str]], dict[tuple[int, int], Relation]]:
"""
@@ -94,13 +239,8 @@ def plan_sects_and_relations(n: int, existed_sects: Optional[List[Sect]]) -> tup
# 宗门均衡分配(约 2/3 成为宗门弟子)
if use_sects and existed_sects:
sect_member_target = int(n * SECT_MEMBER_RATIO) # 目标配额约2/3为宗门弟子其余散修
sect_member_counts_by_id: dict[int, int] = {s.id: 0 for s in existed_sects}
for i in range(sect_member_target):
min_count = min(sect_member_counts_by_id.values()) if sect_member_counts_by_id else 0 # 轮转均衡:优先填充人数最少的宗门
candidates = [s for s in existed_sects if sect_member_counts_by_id.get(s.id, 0) == min_count]
s = random.choice(candidates)
sect_member_counts_by_id[s.id] += 1
planned_sect[i] = s
planned_sect[:sect_member_target] = _pick_sects_balanced(existed_sects, sect_member_target)
# 打散次序,避免前段集中
paired = list(zip(planned_sect, list(range(n))))
random.shuffle(paired)
planned_sect = [p[0] for p in paired]