ad catch
This commit is contained in:
@@ -32,6 +32,7 @@ from .help_mortals import HelpMortals
|
||||
from .talk import Talk
|
||||
from .devour_mortals import DevourMortals
|
||||
from .self_heal import SelfHeal
|
||||
from .catch import Catch
|
||||
|
||||
# 注册到 ActionRegistry(标注是否为实际可执行动作)
|
||||
register_action(actual=False)(Action)
|
||||
@@ -60,6 +61,7 @@ register_action(actual=True)(HelpMortals)
|
||||
register_action(actual=True)(Talk)
|
||||
register_action(actual=True)(DevourMortals)
|
||||
register_action(actual=True)(SelfHeal)
|
||||
register_action(actual=True)(Catch)
|
||||
|
||||
__all__ = [
|
||||
# 基类
|
||||
@@ -90,6 +92,7 @@ __all__ = [
|
||||
"Talk",
|
||||
"DevourMortals",
|
||||
"SelfHeal",
|
||||
"Catch",
|
||||
]
|
||||
|
||||
|
||||
|
||||
75
src/classes/action/catch.py
Normal file
75
src/classes/action/catch.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src.classes.action import TimedAction
|
||||
from src.classes.event import Event
|
||||
from src.classes.region import NormalRegion
|
||||
from src.classes.spirit_animal import SpiritAnimal
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.animal import Animal
|
||||
|
||||
|
||||
class Catch(TimedAction):
|
||||
"""
|
||||
御兽:仅百兽宗弟子可用。
|
||||
条件:
|
||||
- 当前处于普通区域,且该区域有动物分布
|
||||
- 目标动物境界 <= Avatar 境界
|
||||
结果:
|
||||
- 按动物境界映射成功率尝试捕捉,成功则成为灵兽(覆盖旧灵兽)。
|
||||
"""
|
||||
|
||||
COMMENT = "尝试驯服一只灵兽,成为自身灵兽"
|
||||
DOABLES_REQUIREMENTS = "仅百兽宗;在有动物的普通区域;目标动物境界不高于角色"
|
||||
PARAMS = {}
|
||||
|
||||
duration_months = 4
|
||||
|
||||
def _get_available_animals(self) -> list[Animal]:
|
||||
region = self.avatar.tile.region
|
||||
avatar_realm = self.avatar.cultivation_progress.realm
|
||||
return [animal for animal in getattr(region, "animals", []) if avatar_realm >= animal.realm]
|
||||
|
||||
def _calc_success_rate_by_realm(self, animal_realm: Realm) -> float:
|
||||
mapping: dict[Realm, float] = {
|
||||
Realm.Qi_Refinement: 0.8,
|
||||
Realm.Foundation_Establishment: 0.6,
|
||||
Realm.Core_Formation: 0.4,
|
||||
Realm.Nascent_Soul: 0.2,
|
||||
}
|
||||
return mapping.get(animal_realm, 0.1)
|
||||
|
||||
def _execute(self) -> None:
|
||||
animals = self._get_available_animals()
|
||||
if not animals:
|
||||
return
|
||||
target = random.choice(animals)
|
||||
base = self._calc_success_rate_by_realm(target.realm)
|
||||
extra = float(self.avatar.effects.get("extra_catch_success_rate", 0) or 0)
|
||||
rate = max(0.0, min(1.0, base + extra))
|
||||
if random.random() < rate:
|
||||
# 覆盖为新的灵兽
|
||||
self.avatar.spirit_animal = SpiritAnimal(name=target.name, realm=target.realm)
|
||||
|
||||
def can_start(self) -> bool:
|
||||
# 仅百兽宗
|
||||
sect = getattr(self.avatar, "sect", None)
|
||||
if sect is None or getattr(sect, "name", "") != "百兽宗":
|
||||
return False
|
||||
region = self.avatar.tile.region
|
||||
if not isinstance(region, NormalRegion):
|
||||
return False
|
||||
return len(self._get_available_animals()) > 0
|
||||
|
||||
def start(self) -> Event:
|
||||
region = self.avatar.tile.region
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 在 {region.name} 尝试御兽")
|
||||
|
||||
def finish(self) -> list[Event]:
|
||||
return []
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ from src.utils.params import filter_kwargs_for_callable
|
||||
from src.classes.sect import Sect
|
||||
from src.classes.appearance import Appearance, get_random_appearance
|
||||
from src.classes.battle import get_base_strength
|
||||
from src.classes.spirit_animal import SpiritAnimal
|
||||
|
||||
persona_num = CONFIG.avatar.persona_num
|
||||
|
||||
@@ -91,6 +92,8 @@ class Avatar:
|
||||
appearance: Appearance = field(default_factory=get_random_appearance)
|
||||
# 装备的法宝(仅一个)
|
||||
treasure: Optional[Treasure] = None
|
||||
# 灵兽:最多一个;若再次捕捉则覆盖
|
||||
spirit_animal: Optional[SpiritAnimal] = None
|
||||
# 当月/当步新设动作标记:在 commit_next_plan 设为 True,首次 tick_action 后清为 False
|
||||
_new_action_set_this_step: bool = False
|
||||
# 不缓存 effects;实时从宗门与功法合并
|
||||
@@ -142,6 +145,9 @@ class Avatar:
|
||||
# 来自法宝
|
||||
if self.treasure is not None:
|
||||
merged = _merge_effects(merged, self.treasure.effects)
|
||||
# 来自灵兽
|
||||
if self.spirit_animal is not None:
|
||||
merged = _merge_effects(merged, self.spirit_animal.effects)
|
||||
# 评估动态效果表达式:值以 "eval(...)" 形式给出
|
||||
evaluated: dict[str, object] = {}
|
||||
for k, v in merged.items():
|
||||
@@ -177,6 +183,7 @@ class Avatar:
|
||||
personas_info = ", ".join([p.get_detailed_info() for p in self.personas]) if self.personas else "无"
|
||||
items_info = ",".join([f"{item.get_detailed_info()}x{quantity}" for item, quantity in self.items.items()]) if self.items else "无"
|
||||
appearance_info = self.appearance.get_detailed_info(self.gender)
|
||||
spirit_animal_info = self.spirit_animal.get_info() if self.spirit_animal is not None else "无"
|
||||
else:
|
||||
treasure_info = self.treasure.get_info() if self.treasure is not None else "无"
|
||||
# personas和sect一致返回detailed,因为这俩太重要了
|
||||
@@ -189,8 +196,9 @@ class Avatar:
|
||||
personas_info = ", ".join([p.get_detailed_info() for p in self.personas]) if self.personas else "无"
|
||||
items_info = ",".join([f"{item.get_info()}x{quantity}" for item, quantity in self.items.items()]) if self.items else "无"
|
||||
appearance_info = self.appearance.get_info()
|
||||
spirit_animal_info = self.spirit_animal.get_info() if self.spirit_animal is not None else "无"
|
||||
|
||||
return {
|
||||
info_dict = {
|
||||
"id": self.id,
|
||||
"名字": self.name,
|
||||
"性别": str(self.gender),
|
||||
@@ -210,6 +218,10 @@ class Avatar:
|
||||
"外貌": appearance_info,
|
||||
"法宝": treasure_info,
|
||||
}
|
||||
# 灵兽:仅在存在时显示
|
||||
if self.spirit_animal is not None:
|
||||
info_dict["灵兽"] = spirit_animal_info
|
||||
return info_dict
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.get_info(detailed=False))
|
||||
@@ -548,6 +560,10 @@ class Avatar:
|
||||
else:
|
||||
add_kv(lines, "法宝", "无")
|
||||
|
||||
# 灵兽:仅在存在时显示
|
||||
if self.spirit_animal is not None:
|
||||
add_kv(lines, "灵兽", self.spirit_animal.get_info())
|
||||
|
||||
# 关系
|
||||
relations_list = [f"{other.name}({str(relation)})" for other, relation in getattr(self, "relations", {}).items()]
|
||||
if relations_list:
|
||||
|
||||
@@ -249,18 +249,35 @@ class NormalRegion(Region):
|
||||
|
||||
return "; ".join(info_parts) if info_parts else "暂无特色物种"
|
||||
|
||||
def _get_species_brief(self) -> str:
|
||||
"""
|
||||
简要物种信息:仅名字与境界,用于在名称后括号展示。
|
||||
例:"灵兔(练气)、青云鹿(练气)、暗影豹(筑基)"
|
||||
若无物种,返回空串。
|
||||
"""
|
||||
briefs: list[str] = []
|
||||
if self.animals:
|
||||
briefs.extend([f"{a.name}({a.realm.value})" for a in self.animals])
|
||||
if self.plants:
|
||||
briefs.extend([f"{p.name}({p.realm.value})" for p in self.plants])
|
||||
return "、".join(briefs)
|
||||
|
||||
def __str__(self) -> str:
|
||||
species_info = self.get_species_info()
|
||||
return f"普通区域:{self.name} - {self.desc} | 物种分布:{species_info}"
|
||||
|
||||
def get_info(self) -> str:
|
||||
return self.name
|
||||
brief = self._get_species_brief()
|
||||
return f"{self.name}({brief})" if brief else self.name
|
||||
|
||||
def get_detailed_info(self) -> str:
|
||||
# 名称后追加物种简要;正文仍保留原来的详细物种描述
|
||||
brief = self._get_species_brief()
|
||||
name_with_brief = f"{self.name}({brief})" if brief else self.name
|
||||
species_info = self.get_species_info()
|
||||
if not species_info or species_info == "暂无特色物种":
|
||||
return f"{self.name} - {self.desc}"
|
||||
return f"{self.name} - {self.desc} | 物种分布:{species_info}"
|
||||
return f"{name_with_brief} - {self.desc}"
|
||||
return f"{name_with_brief} - {self.desc} | 物种分布:{species_info}"
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
lines = super().get_hover_info()
|
||||
|
||||
@@ -68,6 +68,19 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
|
||||
sects_by_name: dict[str, Sect] = {}
|
||||
|
||||
df = game_configs["sect"]
|
||||
# 读取宗门驻地映射(优先从 sect_region.csv 获取驻地地名/描述)
|
||||
sect_region_df = game_configs.get("sect_region")
|
||||
hq_by_sect_id: dict[int, tuple[str, str]] = {}
|
||||
if sect_region_df is not None:
|
||||
for _, sr in sect_region_df.iterrows():
|
||||
sid_str = str(sr.get("sect_id", "")).strip()
|
||||
# 跳过说明行或空值
|
||||
if not sid_str.isdigit():
|
||||
continue
|
||||
sid = int(sid_str)
|
||||
hq_name = str(sr.get("headquarter_name", "")).strip()
|
||||
hq_desc = str(sr.get("headquarter_desc", "")).strip()
|
||||
hq_by_sect_id[sid] = (hq_name, hq_desc)
|
||||
# 可能不存在 technique 配表或未添加 sect 列,做容错
|
||||
tech_df = game_configs.get("technique")
|
||||
assets_base = Path("assets/sects")
|
||||
@@ -92,6 +105,11 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
|
||||
# 读取 effects(兼容 JSON/单引号字面量/空)
|
||||
effects = load_effect_from_str(row.get("effects", ""))
|
||||
|
||||
# 从 sect_region.csv 中优先取驻地名称/描述;否则兼容旧列或退回宗门名
|
||||
csv_hq = hq_by_sect_id.get(int(row["id"]))
|
||||
hq_name_from_csv = (csv_hq[0] if csv_hq else "").strip() if csv_hq else ""
|
||||
hq_desc_from_csv = (csv_hq[1] if csv_hq else "").strip() if csv_hq else ""
|
||||
|
||||
sect = Sect(
|
||||
id=int(row["id"]),
|
||||
name=str(row["name"]),
|
||||
@@ -101,10 +119,10 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
|
||||
sect_surnames=_split_names(row["sect_surnames"]),
|
||||
male_sect_given_names=male_given_names,
|
||||
female_sect_given_names=female_given_names,
|
||||
# 保留旧字段的兼容读取(如旧csv仍包含headquarter_*列则读入;否则使用宗门名与空描述)
|
||||
# 驻地:优先 sect_region.csv;否则兼容旧列;最终回退宗门名
|
||||
headquarter=SectHeadQuarter(
|
||||
name=(str(row.get("headquarter_name", "")).strip() or str(row["name"])) ,
|
||||
desc=str(row.get("headquarter_desc", "")),
|
||||
name=(hq_name_from_csv or str(row.get("headquarter_name", "")).strip() or str(row["name"])) ,
|
||||
desc=(hq_desc_from_csv or str(row.get("headquarter_desc", ""))),
|
||||
image=image_path,
|
||||
),
|
||||
technique_names=technique_names,
|
||||
|
||||
42
src/classes/spirit_animal.py
Normal file
42
src/classes/spirit_animal.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
|
||||
@dataclass
|
||||
class SpiritAnimal:
|
||||
"""
|
||||
灵兽:附着于 Avatar 的唯一“守护灵兽”。
|
||||
仅记录名称与境界,并据境界提供固定战斗力点数加成。
|
||||
|
||||
规则:
|
||||
- 练气/筑基/金丹/元婴 对应战斗力点数 +2/+4/+6/+8
|
||||
- Avatar 最多只能拥有一个 spirit_animal;新的会覆盖旧的。
|
||||
"""
|
||||
|
||||
name: str
|
||||
realm: Realm
|
||||
|
||||
def get_extra_strength_points(self) -> int:
|
||||
mapping = {
|
||||
Realm.Qi_Refinement: 2,
|
||||
Realm.Foundation_Establishment: 4,
|
||||
Realm.Core_Formation: 6,
|
||||
Realm.Nascent_Soul: 8,
|
||||
}
|
||||
return mapping.get(self.realm, 0)
|
||||
|
||||
def get_info(self) -> str:
|
||||
return f"{self.name}({self.realm.value})"
|
||||
|
||||
@property
|
||||
def effects(self) -> dict[str, object]:
|
||||
"""
|
||||
灵兽提供的效果集合。当前仅包含战斗力点数,后续可扩展其他键。
|
||||
"""
|
||||
pts = self.get_extra_strength_points()
|
||||
return {"extra_battle_strength_points": pts} if pts else {}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ game:
|
||||
init_npc_num: 6
|
||||
sect_num: 2 # init_npc_num大于sect_num时,会随机选择sect_num个宗门
|
||||
npc_birth_rate_per_month: 0.001
|
||||
fortune_probability: 0.01
|
||||
fortune_probability: 0.001
|
||||
|
||||
df:
|
||||
ids_separator: ";"
|
||||
|
||||
@@ -7,7 +7,7 @@ id,name,exclusion_ids,desc,weight,condition
|
||||
5,随性,1;24;25,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。,1,
|
||||
6,贪财,,你对灵石和财富有着强烈的渴望。,1,
|
||||
7,采药,,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。,1,
|
||||
8,猎者,,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。,1,
|
||||
8,猎者,,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌,喜欢捕猎野兽。情况允许也会御兽。,100,
|
||||
9,沉思,2,你总是会深思熟虑,思考问题比较有哲理。,1,
|
||||
10,惜命,4;24;25,你总是会珍惜自己的生命,不会轻易冒险。,1,
|
||||
11,友爱,13;14;15;12;24;25,你重视同伴与和谐,乐于助人,倾向通过协作与沟通化解矛盾。,1,
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
id,name,desc,member_act_style,alignment,sect_surnames,male_sect_given_names,female_sect_given_names,weight,effects
|
||||
,,宗门名称与描述,宗门成员行事风格,阵营(正/中/邪),宗门常用姓氏(分号分隔),男性常用名(分号分隔),女性常用名(分号分隔),权重(默认1),effects(JSON)
|
||||
1,明心剑宗,通玄界东方第一宗,以无上剑道称雄于世。云纹禁制为不传心法。,清明克己,行止如一。重剑与心法并重,讲究明心见性。,正,明;心;剑;霄;玄;霁;衡;孤;徽;肃,澄川;宏石;磐岳;霆岱;寂岚;久安;宸秋;烁离;沧岳;砺锋;炎洲;远歌,采微;霏岚;韶华;绮澜;珠影;远岫;若水;凝香;雪瑶;南絮;轻萝;宛竹,1,
|
||||
2,百兽宗,以驯养灵兽闻名,豢养各种妖兽灵怪为战力。,你言语直接,重视力量与血性,崇尚狩猎与搏斗。,邪,,驼王;飞熊;虎魄;狼行;熊罡;白猿;石坚;山岚;青鬃;玄爪;金瞳;裂爪;破角;狂鬃;赤鬣;苍隼;啸风;裂岩,狐绮;白貂;青翎;雪牙;赤羽;玄狸;灵爪;月狐;银鳞;霜蹄;云貉;绒尾;锦狐;轻蹄,1,
|
||||
2,百兽宗,以驯养灵兽闻名,豢养各种妖兽灵怪为战力。,你言语直接,重视力量与血性,崇尚狩猎与搏斗。,邪,,驼王;飞熊;虎魄;狼行;熊罡;白猿;石坚;山岚;青鬃;玄爪;金瞳;裂爪;破角;狂鬃;赤鬣;苍隼;啸风;裂岩,狐绮;白貂;青翎;雪牙;赤羽;玄狸;灵爪;月狐;银鳞;霜蹄;云貉;绒尾;锦狐;轻蹄,100,
|
||||
3,水镜宗,正道十宗之一,实则严守中立。拥有仙界异宝"彻天水镜"可预知未来。,你处事冷静圆融,喜以柔克刚,擅借力与反制。,中,水;镜;寒;霜;冰;清;沐;澜;渊;泉,涟光;沧浪;泽远;浩川;泊舟;涓石;溪原;涵舟;泠曜;漪岑;淞岳;涔雨,漫霖;洛漪;潋月;涵烟;沁波;翠波;漫葭;汀兰;潭歌;涓玥;澧宁;潇然,1,
|
||||
4,冥王宗,行走幽冥之道,术法阴冷狠厉。,你言辞冷厉少情,敬畏因果而不惧杀伐,偏向效率与结果。,邪,冥;王;玄;幽;夜;白;冷;狱;魇;阴,血燎;焚魄;灰灭;殁川;绝尘;厌离;朔寒;邪风;归墟;朽骨;朔月;止戈,寒绫;霜瑶;凄歌;素鸢;祭宁;黛魂;夙梦;绫雪;凛珑;霁月;旷音;凝岚,1,
|
||||
5,朱勾宗,邪宗大派。以炼器、机关、暗杀闻名于世,素来阴毒冷僻。,你直面欲望与代价,不惧黑暗,以攻伐见长。,邪,朱;绯;刃;戮;蚀;渊;钧;鸦;墨;殷,暗阑;机括;鬼匣;夜禁;幻锁;残锋;暗弦;影栅;幽钩;断线;潜匿;迷踪,玄簪;霜绡;纤罗;碎玉;影裳;轻弦;凝黛;凝烟;冷珥;素纱;凛钗;寒袖,1,
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 73.
|
@@ -9,4 +9,6 @@ id,name,sect_id,desc,effects
|
||||
7,万欲同心结,6,情意同心,双修之道相互映照,修为更精进.,"{""extra_dual_cultivation_exp"": 100}"
|
||||
8,影遁披风,8,融身影界,来去无踪,伏击出其不意.,"{""extra_move_step"": 1, ""extra_observation_radius"": 1}"
|
||||
|
||||
9,百兽驭兽符,2,以兽纹灵符加持,唤引兽心,御兽更易.,"{""extra_catch_success_rate"": 0.1}"
|
||||
|
||||
|
||||
|
||||
|
@@ -20,4 +20,5 @@
|
||||
要求与约束:
|
||||
- thought从侧面体现出角色个性、宗门信息
|
||||
- 只有当前动作空间中的动作是立刻可以做的,其他动作需满足对应条件
|
||||
- 不应过分重复的做相同动作
|
||||
- 不应过分重复的做相同动作
|
||||
- 决定动作前,注意是否可执行
|
||||
Reference in New Issue
Block a user