update technique by sect

This commit is contained in:
bridge
2025-10-09 23:32:18 +08:00
parent 9b54822a56
commit 55145850ca
5 changed files with 125 additions and 35 deletions

View File

@@ -13,7 +13,7 @@ from src.classes.tile import Tile
from src.classes.region import Region
from src.classes.cultivation import CultivationProgress
from src.classes.root import Root
from src.classes.technique import Technique, get_random_technique_for_avatar
from src.classes.technique import Technique, get_random_technique_for_avatar, get_technique_by_sect
from src.classes.age import Age
from src.classes.event import NULL_EVENT, Event
from src.classes.typings import ACTION_NAME, ACTION_PARAMS, ACTION_NAME_PARAMS_PAIRS, ACTION_NAME_PARAMS_PAIR
@@ -102,9 +102,11 @@ class Avatar:
if not self.personas:
self.personas = get_random_compatible_personas(persona_num, avatar=self)
# 出生即随机赋予功法(与灵根/阵营/条件兼容)
# 出生即按宗门分配功法:
# - 散修:仅从无宗门功法抽样
# - 有宗门:从“无宗门 + 本宗门”集合抽样
if self.technique is None:
self.technique = get_random_technique_for_avatar(self)
self.technique = get_technique_by_sect(self.sect)
# 若未设定阵营,则依据宗门/无门无派规则设置,避免后续为 None
if self.alignment is None:

View File

@@ -36,6 +36,8 @@ class Sect:
male_sect_given_names: list[str]
female_sect_given_names: list[str]
headquarter: SectHeadQuarter
# 本宗关联的功法名称(来自 technique.csv 的 sect 列)
technique_names: list[str]
# 功法在technique.csv中配置
# TODO法宝
# TODO宗内等级和称谓
@@ -52,12 +54,23 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
sects_by_name: dict[str, Sect] = {}
df = game_configs["sect"]
# 可能不存在 technique 配表或未添加 sect 列,做容错
tech_df = game_configs.get("technique")
assets_base = Path("assets/sects")
for _, row in df.iterrows():
image_path = assets_base / f"{row['name']}.png"
male_given_names = _split_names(row["male_sect_given_names"])
female_given_names = _split_names(row["female_sect_given_names"])
# 收集该宗门下配置的功法名称
technique_names: list[str] = []
if tech_df is not None and "sect" in tech_df.columns:
technique_names = [
str(tname).strip()
for tname in tech_df.loc[tech_df["sect"] == row["name"], "name"].tolist()
if str(tname).strip()
]
sect = Sect(
id=int(row["id"]),
name=str(row["name"]),
@@ -73,6 +86,7 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
desc=str(row.get("headquarter_desc", "")),
image=image_path,
),
technique_names=technique_names,
)
sects_by_id[sect.id] = sect
sects_by_name[sect.name] = sect

View File

@@ -49,6 +49,8 @@ class Technique:
prompt: str
weight: float
condition: str
# 归属宗门名称None/空表示无宗门要求(散修可修)
sect: Optional[str] = None
def is_allowed_for(self, avatar) -> bool:
if not self.condition:
@@ -85,6 +87,8 @@ def loads() -> tuple[dict[int, Technique], dict[str, Technique]]:
condition = "" if str(cond_val) == "nan" else str(cond_val).strip()
weight_val = row.get("weight", 1)
weight = float(str(weight_val)) if str(weight_val) != "nan" else 1.0
sect_val = row.get("sect", "")
sect = None if str(sect_val) == "nan" or str(sect_val).strip() == "" else str(sect_val).strip()
t = Technique(
id=int(row["id"]),
name=name,
@@ -93,6 +97,7 @@ def loads() -> tuple[dict[int, Technique], dict[str, Technique]]:
prompt=str(row.get("prompt", "")),
weight=weight,
condition=condition,
sect=sect,
)
techniques_by_id[t.id] = t
techniques_by_name[t.name] = t
@@ -157,6 +162,35 @@ def get_random_technique_for_avatar(avatar) -> Technique:
return random.choices(candidates, weights=weights, k=1)[0]
def get_technique_by_sect(sect) -> Technique:
"""
简化版:仅按宗门筛选并按权重抽样,不考虑灵根与 condition。
- 散修sect 为 None/空只从无宗门要求sect 为空)的功法中抽样;
- 有宗门:从“无宗门 + 该宗门”的功法中抽样;
若集合为空,则退回全量功法。
"""
import random
sect_name: Optional[str] = None
if sect is not None:
sect_name = getattr(sect, "name", sect)
if isinstance(sect_name, str):
sect_name = sect_name.strip() or None
allowed_sects: set[Optional[str]] = {None, ""}
if sect_name is not None:
allowed_sects.add(sect_name)
def _in_allowed_sect(t: Technique) -> bool:
return (t.sect in allowed_sects) or (t.sect is None) or (t.sect == "")
candidates: List[Technique] = [t for t in techniques_by_id.values() if _in_allowed_sect(t)]
if not candidates:
candidates = list(techniques_by_id.values())
weights = [max(0.0, t.weight) for t in candidates]
return random.choices(candidates, weights=weights, k=1)[0]
def get_grade_bonus(grade: TechniqueGrade) -> float:
if grade is TechniqueGrade.UPPER:
return 0.10
@@ -198,3 +232,18 @@ def get_grade_advantage_bonus(attacker_grade: Optional[TechniqueGrade], defender
return bonus
# 将功法属性映射为默认的灵根(邪功法不返回)
def attribute_to_root(attr: TechniqueAttribute) -> Optional[Root]:
mapping: dict[TechniqueAttribute, Root] = {
TechniqueAttribute.GOLD: Root.GOLD,
TechniqueAttribute.WOOD: Root.WOOD,
TechniqueAttribute.WATER: Root.WATER,
TechniqueAttribute.FIRE: Root.FIRE,
TechniqueAttribute.EARTH: Root.EARTH,
TechniqueAttribute.THUNDER: Root.THUNDER,
TechniqueAttribute.ICE: Root.ICE,
TechniqueAttribute.WIND: Root.WIND,
TechniqueAttribute.DARK: Root.DARK,
}
return mapping.get(attr)