From e30a46d55c797f0bfa4a18bd0a59232664ee1d0a Mon Sep 17 00:00:00 2001 From: bridge Date: Sat, 25 Oct 2025 01:26:24 +0800 Subject: [PATCH] add define action func --- src/classes/appearance.py | 15 +++ src/sim/new_avatar.py | 241 ++++++++++++++++++++++++++++++++++++-- static/config.yml | 9 +- 3 files changed, 257 insertions(+), 8 deletions(-) diff --git a/src/classes/appearance.py b/src/classes/appearance.py index de73b86..a2f0500 100644 --- a/src/classes/appearance.py +++ b/src/classes/appearance.py @@ -62,9 +62,24 @@ def get_random_appearance() -> Appearance: return Appearance(level=base.level, name=base.name, desc_male=base.desc_male, desc_female=base.desc_female) +def get_appearance_by_level(level: int) -> Appearance: + """ + 按等级(1~10)返回外貌实例;越界时夹在范围内。 + 返回新实例,避免外部持有池中引用。 + """ + lv = int(level) + if lv < 1: + lv = 1 + if lv > 10: + lv = 10 + base = next((a for a in _APPEARANCE_POOL if a.level == lv), _APPEARANCE_POOL[-1]) + return Appearance(level=base.level, name=base.name, desc_male=base.desc_male, desc_female=base.desc_female) + + __all__ = [ "Appearance", "get_random_appearance", + "get_appearance_by_level", ] diff --git a/src/sim/new_avatar.py b/src/sim/new_avatar.py index 6d6d860..bdfb962 100644 --- a/src/sim/new_avatar.py +++ b/src/sim/new_avatar.py @@ -1,21 +1,23 @@ import random -from typing import List, Optional, Dict +from typing import List, Optional, Dict, Tuple, Union from src.classes.world import World from src.classes.map import Map from src.classes.tile import TileType from src.classes.avatar import Avatar, Gender +from src.classes.appearance import get_appearance_by_level from src.classes.calendar import MonthStamp from src.classes.cultivation import CultivationProgress from src.classes.root import Root from src.classes.age import Age from src.utils.names import get_random_name_for_sect, pick_surname_for_sect, get_random_name_with_surname from src.utils.id_generator import get_avatar_id -from src.classes.sect import Sect +from src.classes.sect import Sect, sects_by_id, sects_by_name from src.classes.alignment import Alignment from src.classes.relation import Relation -from src.classes.technique import get_technique_by_sect, attribute_to_root -from src.classes.treasure import treasures_by_sect_id +from src.classes.technique import get_technique_by_sect, attribute_to_root, Technique, techniques_by_id, techniques_by_name +from src.classes.treasure import treasures_by_sect_id, Treasure, treasures_by_id, treasures_by_name +from src.classes.persona import Persona, personas_by_id, personas_by_name # —— 参数常量(便于调参)—— @@ -460,11 +462,236 @@ def make_avatars( current_month_stamp: MonthStamp = MonthStamp(100 * 12), existed_sects: Optional[List[Sect]] = None, ) -> dict[str, Avatar]: + from src.utils.config import CONFIG + n = int(max(0, count)) if n == 0: return {} - # 只负责编排:先规划,再生成 - planned_sect, planned_gender, planned_surname, planned_relations = plan_sects_and_relations(n, existed_sects) - return build_avatars_from_plan(world, current_month_stamp, planned_sect, planned_gender, planned_surname, planned_relations) + + avatars: dict[str, Avatar] = {} + + # 先生成一个“defined_avatar”(若配置存在) + defined = getattr(CONFIG, "defined_avatar", None) + used = 0 + if defined is not None: + try: + da = get_new_avatar_with_config( + world, + current_month_stamp, + name=str(getattr(defined, "name", "") or ""), + age=int(getattr(defined, "age", 0) or 0) if str(getattr(defined, "age", "")).strip() else None, + gender=str(getattr(defined, "gender", "")).strip() or None, + sect=getattr(defined, "sect", None), + appearance=int(getattr(defined, "appearance", 0) or 0) if str(getattr(defined, "appearance", "")).strip() else None, + ) + avatars[da.id] = da + used = 1 + except Exception: + # 配置异常则忽略定义的角色,回退为纯随机 + used = 0 + + # 剩余随机编排 + rest = max(0, n - used) + if rest > 0: + planned_sect, planned_gender, planned_surname, planned_relations = plan_sects_and_relations(rest, existed_sects) + random_avatars = build_avatars_from_plan(world, current_month_stamp, planned_sect, planned_gender, planned_surname, planned_relations) + avatars.update(random_avatars) + + return avatars + +# —— 指定参数创建:支持传入字符串并解析为对象 —— +def _parse_gender(value: Union[str, Gender, None]) -> Optional[Gender]: + if value is None: + return None + if isinstance(value, Gender): + return value + s = str(value).strip() + if s == "男": + return Gender.MALE + if s == "女": + return Gender.FEMALE + return None + + +def _parse_sect(value: Union[str, int, Sect, None]) -> Optional[Sect]: + if value is None: + return None + if isinstance(value, Sect): + return value + # 纯数字视为 id + if isinstance(value, int): + return sects_by_id.get(value) + s = str(value).strip() + if not s: + return None + if s.isdigit(): + return sects_by_id.get(int(s)) + return sects_by_name.get(s) + + +def _parse_technique(value: Union[str, int, Technique, None]) -> Optional[Technique]: + if value is None: + return None + if isinstance(value, Technique): + return value + if isinstance(value, int): + return techniques_by_id.get(value) + s = str(value).strip() + if not s: + return None + if s.isdigit(): + return techniques_by_id.get(int(s)) + return techniques_by_name.get(s) + + +def _parse_treasure(value: Union[str, int, Treasure, None]) -> Optional[Treasure]: + if value is None: + return None + if isinstance(value, Treasure): + return value + if isinstance(value, int): + return treasures_by_id.get(value) + s = str(value).strip() + if not s: + return None + if s.isdigit(): + return treasures_by_id.get(int(s)) + return treasures_by_name.get(s) + + +def _parse_personas(value: Union[str, int, Persona, List[Union[str, int, Persona]], None]) -> Optional[List[Persona]]: + if value is None: + return None + values: List[Union[str, int, Persona]] + if isinstance(value, list): + values = value + else: + values = [value] + result: List[Persona] = [] + for v in values: + if isinstance(v, Persona): + result.append(v) + continue + if isinstance(v, int): + p = personas_by_id.get(v) + if p is not None: + result.append(p) + continue + s = str(v).strip() + if not s: + continue + if s.isdigit(): + p = personas_by_id.get(int(s)) + if p is not None: + result.append(p) + else: + p = personas_by_name.get(s) + if p is not None: + result.append(p) + # 去重,保持顺序 + seen: set[int] = set() + unique: List[Persona] = [] + for p in result: + if p.id in seen: + continue + seen.add(p.id) + unique.append(p) + return unique if unique else None + + +def get_new_avatar_with_config( + world: World, + current_month_stamp: MonthStamp, + *, + name: Optional[str] = None, + age: Union[int, Age, None] = None, + gender: Union[str, Gender, None] = None, + sect: Union[str, int, Sect, None] = None, + level: Optional[int] = None, + pos: Optional[Tuple[int, int]] = None, + technique: Union[str, int, Technique, None] = None, + treasure: Union[str, int, Treasure, None] = None, + personas: Union[str, int, Persona, List[Union[str, int, Persona]], None] = None, + appearance: Optional[int] = None, +) -> Avatar: + """ + 创建一个可配置的新角色: + - 若未提供参数,则复用 get_new_avatar_from_mortal 的随机策略(通过 plan_mortal 实现)。 + - 支持字符串参数:gender 仅支持 "男/女";sect/technique/treasure/persona 可用名称或数字ID。 + + 参数: + - name: 角色名;为空则根据宗门与姓氏自动生成 + - age: 年龄(int)或 Age;未提供时随机 + - gender: 性别(Gender 或 字符串) + - sect: 宗门(Sect 或 名称/ID) + - level: 等级(0~120);未提供时随机 + - pos: 初始坐标 (x, y);未提供时随机 + - technique: 指定功法 + - treasure: 指定法宝 + - personas: 指定个性(单个或列表) + """ + # 年龄(先取整数年龄,规划阶段只用到 age.age,不依赖 realm) + if isinstance(age, Age): + age_years = age.age + elif isinstance(age, int): + age_years = max(AGE_MIN, age) + else: + age_years = random.randint(AGE_MIN, AGE_MAX) + + # 先做一次规划,之后用传入参数覆盖 + tmp_age_for_plan = Age(age_years, CultivationProgress(LEVEL_MIN).realm) + plan = plan_mortal(world, name=name or "", age=tmp_age_for_plan) + + # 覆盖:性别 + g = _parse_gender(gender) + if g is not None: + plan.gender = g + + # 覆盖:宗门 + s = _parse_sect(sect) + if s is not None: + plan.sect = s + + # 覆盖:等级 + if isinstance(level, int): + plan.level = max(LEVEL_MIN, min(LEVEL_MAX, level)) + + # 覆盖:坐标 + if isinstance(pos, tuple) and len(pos) == 2: + x, y = int(pos[0]), int(pos[1]) + # 夹在地图范围内 + x = max(0, min(world.map.width - 1, x)) + y = max(0, min(world.map.height - 1, y)) + plan.pos_x, plan.pos_y = x, y + + # 根据最终等级推导境界,再构造 Age + final_realm = CultivationProgress(plan.level).realm + final_age = Age(age_years, final_realm) + + # 生成 + avatar = build_mortal_from_plan(world, current_month_stamp, name=name or "", age=final_age, plan=plan) + + # 覆盖:功法/法宝/个性 + tech_obj = _parse_technique(technique) + if tech_obj is not None: + avatar.technique = tech_obj + mapped = attribute_to_root(tech_obj.attribute) + if mapped is not None: + avatar.root = mapped + + tre_obj = _parse_treasure(treasure) + if tre_obj is not None: + avatar.treasure = tre_obj + + pers_list = _parse_personas(personas) + if pers_list is not None and len(pers_list) > 0: + avatar.personas = pers_list + + # 覆盖:外貌/颜值 + if isinstance(appearance, int): + avatar.appearance = get_appearance_by_level(appearance) + + return avatar + diff --git a/static/config.yml b/static/config.yml index 366babd..0de1a28 100644 --- a/static/config.yml +++ b/static/config.yml @@ -28,4 +28,11 @@ avatar: social: talk_into_relation_probability: 0.1 - event_context_num: 6 \ No newline at end of file + event_context_num: 6 + +defined_avatar: + name: "张三" + age: 18 + gender: "男" + sect: "合欢宗" + appearance: 10