diff --git a/src/classes/avatar.py b/src/classes/avatar.py index f43c99d..e739424 100644 --- a/src/classes/avatar.py +++ b/src/classes/avatar.py @@ -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: diff --git a/src/classes/sect.py b/src/classes/sect.py index 9ad312b..4dc3163 100644 --- a/src/classes/sect.py +++ b/src/classes/sect.py @@ -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 diff --git a/src/classes/technique.py b/src/classes/technique.py index aa84324..599f221 100644 --- a/src/classes/technique.py +++ b/src/classes/technique.py @@ -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) diff --git a/src/run/run.py b/src/run/run.py index ce0287c..5a25f73 100644 --- a/src/run/run.py +++ b/src/run/run.py @@ -26,6 +26,7 @@ from src.classes.sect import sects_by_id from src.classes.alignment import Alignment from src.run.log import get_logger from src.classes.relation import Relation +from src.classes.technique import get_technique_by_sect, attribute_to_root def clamp(value: int, lo: int, hi: int) -> int: @@ -113,6 +114,13 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp # 依据宗门设定阵营(若有宗门则与宗门阵营一致,否则保留默认随机) if assigned_sect is not None: avatar.alignment = assigned_sect.alignment + # 宗门弟子:按宗门功法随机 + t = get_technique_by_sect(assigned_sect) + avatar.technique = t + # 将灵根改为功法对应灵根(邪功法不变) + mapped_root = attribute_to_root(avatar.technique.attribute) + if mapped_root is not None: + avatar.root = mapped_root avatars[avatar.id] = avatar # # —— 为演示添加少量示例关系 —— avatar_list = list(avatars.values()) diff --git a/static/game_configs/technique.csv b/static/game_configs/technique.csv index 81f796f..e123773 100644 --- a/static/game_configs/technique.csv +++ b/static/game_configs/technique.csv @@ -1,33 +1,50 @@ -id,name,technique_root,grade,prompt,weight,condition -,名称,属性,品阶(上中下),提示词,抽样权重,条件 -1,太乙金光锻体诀,金,下品,运太乙金光淬骨炼筋,金罡护体,刀兵难侵。,1, -2,金阳破甲术,金,中品,融金阳之炽与锋,一往破敌,破甲断魄。,1, -3,太乙金光经,金,上品,凝太乙金光成罡,身外结铠,破邪辟秽。,1, -4,青木长春诀,木,下品,借青木长春之机,涓涓养元,稳固根基。,1, -5,碧林长生法,木,中品,汲林海生机,法源不竭,柔而不折。,1, -6,太上长生经,木,上品,执太上长生之理,枝叶化护,源远绵长。,1, -7,玄冥敛息诀,水,下品,以玄冥静息收摄心神,润物无声。,1, -8,玄水化海术,水,中品,以玄水化百川,绵延不绝,攻守随势。,1, -9,太玄水灵经,水,上品,驭水灵真意,掌覆沧海,周天轮转。,1, -10,南明离火心诀,火,下品,承南明离火之炽,鼓荡心魄,炼魂淬魄。,1, -11,赤炎炼真诀,火,中品,以赤炎炼真元,去伪存真,攻势如潮。,1, -12,南明离火经,火,上品,承离火至阳,万焰归一,一念焚天。,1, -13,厚土镇魄诀,土,下品,以厚土安神定魄,稳如山嶽,不动如山。,1, -14,玄黄定岳法,土,中品,以玄黄凝岳势,千钧一力,镇压四方。,1, -15,玄黄镇山经,土,上品,执玄黄大道,山河为印,守中制外。,1, -16,玄冰凝真诀,冰,下品,以玄冰凝灵,清寒护体,心湖如镜。,1, -17,冰魄玄寒法,冰,中品,御玄寒之魄,封江冻海,以静制动。,1, -18,太阴玄冰经,冰,上品,执太阴寒意,万里冰封,寒透经脉。,1, -19,御风术,风,下品,以清风导息,轻身迅行,形如掠影。,1, -20,清风御行法,风,中品,驭风行空,攻守兼备,灵动飘逸。,1, -21,太清御风经,风,上品,执太清之风,来去无踪,剑气乘风。,1, -22,幽冥潜形术,暗,下品,以幽冥藏形,锋芒尽敛,伺机而动。,1, -23,玄夜无相法,暗,中品,夜行无相,出入无痕,摄人心魄。,1, -24,幽冥鬼道经,暗,上品,执幽冥鬼道,虚实互转,影灭形生。,1, -25,九天淬体雷诀,雷,下品,引九天霆罡击体,筋骨再生,爆发如雷。,1, -26,九霄养雷法,雷,中品,引九霄真雷,养气通窍,威势赫然。,1, -27,太清神霄经,雷,上品,执神霄正雷,镇压不祥,一念霹雳。,1, -28,血神噬魂大法,邪,下品,以血神邪念淬炼,杀伐狠辣,损阴德。,1,avatar.alignment == "邪" -29,冥狱吞灵大法,邪,中品,循冥狱之道吞噬灵魄,逆乱阴阳,速成之途。,1,avatar.alignment == "邪" -30,太阴噬天魔经,邪,上品,挟太阴极恶之力,噬天吞地,万邪朝宗。,1,avatar.alignment == "邪" +id,name,technique_root,grade,prompt,weight,condition,sect +,名称,属性,品阶(上中下),提示词,抽样权重,条件,所属宗门 +1,太乙金光锻体诀,金,下品,运太乙金光淬骨炼筋,金罡护体,刀兵难侵。,1,, +2,金阳破甲术,金,中品,融金阳之炽与锋,一往破敌,破甲断魄。,1,, +4,青木长春诀,木,下品,借青木长春之机,涓涓养元,稳固根基。,1,, +5,碧林长生法,木,中品,汲林海生机,法源不竭,柔而不折。,1,, +7,玄冥敛息诀,水,下品,以玄冥静息收摄心神,润物无声。,1,, +8,玄水化海术,水,中品,以玄水化百川,绵延不绝,攻守随势。,1,, +10,南明离火心诀,火,下品,承南明离火之炽,鼓荡心魄,炼魂淬魄。,1,, +11,赤炎炼真诀,火,中品,以赤炎炼真元,去伪存真,攻势如潮。,1,, +13,厚土镇魄诀,土,下品,以厚土安神定魄,稳如山嶽,不动如山。,1,, +14,玄黄定岳法,土,中品,以玄黄凝岳势,千钧一力,镇压四方。,1,, +16,玄冰凝真诀,冰,下品,以玄冰凝灵,清寒护体,心湖如镜。,1,, +17,冰魄玄寒法,冰,中品,御玄寒之魄,封江冻海,以静制动。,1,, +19,御风术,风,下品,以清风导息,轻身迅行,形如掠影。,1,, +20,清风御行法,风,中品,驭风行空,攻守兼备,灵动飘逸。,1,, +22,幽冥潜形术,暗,下品,以幽冥藏形,锋芒尽敛,伺机而动。,1,, +23,玄夜无相法,暗,中品,夜行无相,出入无痕,摄人心魄。,1,, +25,九天淬体雷诀,雷,下品,引九天霆罡击体,筋骨再生,爆发如雷。,1,, +26,九霄养雷法,雷,中品,引九霄真雷,养气通窍,威势赫然。,1,, +28,血神噬魂大法,邪,下品,以血神邪念淬炼,杀伐狠辣,损阴德。,1,, +29,冥狱吞灵大法,邪,中品,循冥狱之道吞噬灵魄,逆乱阴阳,速成之途。,1,, +30,明心归一剑典,金,上品,以剑返本归一,明镜止水,一剑断妄。,10,,明心剑宗 +31,霆光明志剑,雷,上品,以霆光贯剑意,电掣雷行,破虚斩障。,10,,明心剑宗 +32,离火明心诀,火,上品,以离火照见自心,去蔽存真,剑随心行。,10,,明心剑宗 +33,御兽玄纲,木,上品,以木灵沟通群兽,血脉契合,驱策如臂。,10,,百兽宗 +34,蛮荒镇骨体,土,上品,以厚土淬锻筋骨,躯壳如山,血勇不挫。,10,,百兽宗 +35,血啸狩魂诀,邪,上品,以嗜血啸魄摄魂,狩猎杀伐,越战越猛。,10,,百兽宗 +36,彻天水镜观,水,上品,以彻天水镜观机,借势化力,先机在握。,10,,水镜宗 +37,寒漪反斩法,冰,上品,以寒漪回折之势,四两拨千斤,刚柔相济。,10,,水镜宗 +38,镜花游身步,风,上品,以轻风流转为引,身化水月,进退自如。,10,,水镜宗 +39,幽冥镇狱经,暗,上品,引幽冥阴煞镇压万灵,寒魄凝骨,万劫不侵。,10,,冥王宗 +40,冥焰噬神法,邪,上品,以冥焰噬神,逆夺生机,吞灵化魄。,10,,冥王宗 +41,黄泉引魄步,冰,上品,以玄寒引魄入泉,步步生霜,摄魂夺魄。,10,,冥王宗 +42,赤狱炼锋诀,火,上品,以赤狱真火炼锋,法器通灵,杀机更盛。,10,,朱勾宗 +43,魇丝摄魂术,暗,上品,以魇丝无声摄魄,藏形夺命,来去无迹。,10,,朱勾宗 +44,噬心傀儡经,金,上品,以金罡刻阵,傀儡载魄,攻守如一。,10,,朱勾宗 +45,双修合和典,水,上品,以清润相济,合气生生,周天圆满。,10,,合欢宗 +46,魅月摄情术,暗,上品,以魅月摄心,惑而不乱,借情化力。,10,,合欢宗 +47,绮罗化影步,风,上品,以轻纱化影,形随意转,柔中带锋。,10,,合欢宗 +48,镇岳定魂经,土,上品,以玄黄定岳势,安神定魄,万邪难侵。,10,,镇魂宗 +49,霆狱伏邪法,雷,上品,引九霄霆威压邪,刚猛无俦,镇压不返。,10,,镇魂宗 +50,金章封煞术,金,上品,以金章为印,封煞束灵,斩乱镇邪。,10,,镇魂宗 +51,幽冥无相诀,暗,上品,隐形无相,出没幽明,摄心夺魄于无声。,10,,幽魂噬影宗 +52,噬影穿行步,风,上品,以风掠影,穿行缝隙,先至而不留痕。,10,,幽魂噬影宗 +53,冥狱吞魄经,邪,上品,循幽冥吞魄之道,化敌为养,速成险绝。,10,,幽魂噬影宗 +54,天罗机巧篇,金,上品,精研机巧阵枢,百变应敌,器术合一。,10,,千帆城 +55,蓝星定魂阵,土,上品,以厚土定魂为基,阵锁心神,稳如磐石。,10,,千帆城 +56,飞翼乘风术,风,上品,乘风而行,纵横千里,御器齐驱。,10,,千帆城