update treasure to weapon and auxiliary

This commit is contained in:
bridge
2025-11-13 01:16:26 +08:00
parent 2ded449ade
commit e760ba107d
13 changed files with 487 additions and 76 deletions

View File

@@ -18,10 +18,11 @@ class DevourMortals(TimedAction):
def _execute(self) -> None:
# 若持有万魂幡累积吞噬魂魄10~100上限10000
tr = getattr(self.avatar, "treasure", None)
if tr is not None and tr.name == "万魂幡":
weapon = self.avatar.weapon
if weapon is not None and weapon.name == "万魂幡":
gain = random.randint(10, 100)
tr.devoured_souls = min(10000, int(tr.devoured_souls) + gain)
current_souls = weapon.special_data.get("devoured_souls", 0)
weapon.special_data["devoured_souls"] = min(10000, int(current_souls) + gain)
def can_start(self) -> tuple[bool, str]:
legal = self.avatar.effects.get("legal_actions", [])

89
src/classes/auxiliary.py Normal file
View File

@@ -0,0 +1,89 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional, Dict
from src.utils.df import game_configs
from src.classes.effect import load_effect_from_str
from src.classes.equipment_grade import EquipmentGrade
from src.classes.sect import Sect, sects_by_id
@dataclass
class Auxiliary:
"""
辅助装备类:提供各种辅助功能的装备
字段与 static/game_configs/auxiliary.csv 对应:
- grade: 装备等级(普通、宝物、法宝)
- sect_id: 对应宗门ID见 sect.csv允许为空表示无特定宗门归属
- effects: 解析为 dict用于与 Avatar.effects 合并
"""
id: int
name: str
grade: EquipmentGrade
sect_id: Optional[int]
desc: str
effects: dict[str, object] = field(default_factory=dict)
sect: Optional[Sect] = None
def get_info(self) -> str:
"""获取简略信息"""
return f"{self.name}"
def get_detailed_info(self) -> str:
"""获取详细信息"""
return f"{self.name}{self.grade}{self.desc}"
def _load_auxiliaries() -> tuple[Dict[int, Auxiliary], Dict[str, Auxiliary], Dict[int, Auxiliary]]:
"""从配表加载 auxiliary 数据。
返回:(按ID、按名称、按宗门ID 的映射)。
若同一宗门配置多个辅助装备,按首次出现保留(每门至多一个法宝级)。
"""
auxiliaries_by_id: Dict[int, Auxiliary] = {}
auxiliaries_by_name: Dict[str, Auxiliary] = {}
auxiliaries_by_sect_id: Dict[int, Auxiliary] = {}
df = game_configs.get("auxiliary")
if df is None:
return auxiliaries_by_id, auxiliaries_by_name, auxiliaries_by_sect_id
for _, row in df.iterrows():
raw_sect = row.get("sect_id")
sect_id: Optional[int] = None
if raw_sect is not None and str(raw_sect).strip() and str(raw_sect).strip() != "nan":
sect_id = int(float(raw_sect))
raw_effects_val = row.get("effects", "")
effects = load_effect_from_str(raw_effects_val)
sect_obj: Optional[Sect] = sects_by_id.get(int(sect_id)) if sect_id is not None else None
# 解析grade
grade_str = str(row.get("grade", "普通"))
grade = EquipmentGrade.COMMON
for g in EquipmentGrade:
if g.value == grade_str:
grade = g
break
a = Auxiliary(
id=int(row["id"]),
name=str(row["name"]),
grade=grade,
sect_id=sect_id,
desc=str(row.get("desc", "")),
effects=effects,
sect=sect_obj,
)
auxiliaries_by_id[a.id] = a
auxiliaries_by_name[a.name] = a
if a.sect_id is not None and a.sect_id not in auxiliaries_by_sect_id:
auxiliaries_by_sect_id[a.sect_id] = a
return auxiliaries_by_id, auxiliaries_by_name, auxiliaries_by_sect_id
auxiliaries_by_id, auxiliaries_by_name, auxiliaries_by_sect_id = _load_auxiliaries()

View File

@@ -28,7 +28,10 @@ from src.classes.effect import _merge_effects
from src.classes.alignment import Alignment
from src.classes.persona import Persona, personas_by_id, get_random_compatible_personas
from src.classes.item import Item
from src.classes.treasure import Treasure
from src.classes.weapon import Weapon, get_common_weapon
from src.classes.auxiliary import Auxiliary
from src.classes.weapon_type import WeaponType
from src.classes.equipment_grade import EquipmentGrade
from src.classes.magic_stone import MagicStone
from src.classes.hp_and_mp import HP, MP, HP_MAX_BY_REALM, MP_MAX_BY_REALM
from src.utils.id_generator import get_avatar_id
@@ -97,8 +100,10 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
sect_rank: "SectRank | None" = None
# 外貌1~10级创建时随机生成
appearance: Appearance = field(default_factory=get_random_appearance)
# 装备的法宝(仅一个
treasure: Optional[Treasure] = None
# 兵器(必有,无则分配普通兵器
weapon: Optional[Weapon] = None
# 辅助装备(可选)
auxiliary: Optional[Auxiliary] = None
# 灵兽:最多一个;若再次捕捉则覆盖
spirit_animal: Optional[SpiritAnimal] = None
# 当月/当步新设动作标记:在 commit_next_plan 设为 True首次 tick_action 后清为 False
@@ -139,6 +144,11 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
from src.classes.alignment import Alignment as _Alignment
self.alignment = random.choice(list(_Alignment))
# 兵器初始化:如果无兵器,分配一个随机的普通兵器
if self.weapon is None:
weapon_type = random.choice(list(WeaponType))
self.weapon = get_common_weapon(weapon_type)
# effects 改为实时属性,不在此初始化
@property
@@ -154,9 +164,12 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
# 来自特质persona
for persona in self.personas:
merged = _merge_effects(merged, persona.effects)
# 来自法宝
if self.treasure is not None:
merged = _merge_effects(merged, self.treasure.effects)
# 来自兵器
if self.weapon is not None:
merged = _merge_effects(merged, self.weapon.effects)
# 来自辅助装备
if self.auxiliary is not None:
merged = _merge_effects(merged, self.auxiliary.effects)
# 来自灵兽
if self.spirit_animal is not None:
merged = _merge_effects(merged, self.spirit_animal.effects)
@@ -189,7 +202,8 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
from src.classes.sect import get_sect_info_with_rank
if detailed:
treasure_info = self.treasure.get_detailed_info() if self.treasure is not None else ""
weapon_info = self.weapon.get_detailed_info() if self.weapon is not None else ""
auxiliary_info = self.auxiliary.get_detailed_info() if self.auxiliary is not None else ""
sect_info = get_sect_info_with_rank(self, detailed=True)
alignment_info = self.alignment.get_detailed_info() if self.alignment is not None else "未知"
region_info = region.get_detailed_info() if region is not None else ""
@@ -201,7 +215,8 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
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 ""
weapon_info = self.weapon.get_info() if self.weapon is not None else ""
auxiliary_info = self.auxiliary.get_info() if self.auxiliary is not None else ""
# 宗门信息:非详细模式下只显示"宗门名+职位"
sect_info = get_sect_info_with_rank(self, detailed=False)
region_info = region.get_info() if region is not None else ""
@@ -232,7 +247,8 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
"特质": personas_info,
"物品": items_info,
"外貌": appearance_info,
"法宝": treasure_info,
"兵器": weapon_info,
"辅助装备": auxiliary_info,
}
# 灵兽:仅在存在时显示
if self.spirit_animal is not None:
@@ -588,11 +604,19 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
from src.utils.text_wrap import wrap_text
add_section(lines, "目标", wrap_text(self.objective, 28))
# 法宝(仅名字
if self.treasure is not None:
add_section(lines, "法宝", [self.treasure.get_info()])
# 兵器(必有,使用颜色标记等级
if self.weapon is not None:
r, g, b = self.weapon.grade.color_rgb
weapon_text = f"<color:{r},{g},{b}>{self.weapon.get_info()}</color>"
add_kv(lines, "兵器", weapon_text)
# 辅助装备(可选,使用颜色标记等级)
if self.auxiliary is not None:
r, g, b = self.auxiliary.grade.color_rgb
auxiliary_text = f"<color:{r},{g},{b}>{self.auxiliary.get_info()}</color>"
add_kv(lines, "辅助装备", auxiliary_text)
else:
add_kv(lines, "法宝", "")
add_kv(lines, "辅助装备", "")
# 灵兽:仅在存在时显示
if self.spirit_animal is not None:
@@ -672,13 +696,14 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
def get_other_avatar_info(self, other_avatar: "Avatar") -> str:
"""
仅显示几个字段:名字、境界、关系、宗门、阵营、外貌。
仅显示几个字段:名字、境界、关系、宗门、阵营、外貌、装备
"""
relation = self.get_relation(other_avatar)
relation_str = str(relation)
sect_str = other_avatar.sect.name if other_avatar.sect is not None else "散修"
tr_str = other_avatar.treasure.get_info() if other_avatar.treasure is not None else ""
return f"{other_avatar.name},境界:{other_avatar.cultivation_progress.get_info()},关系:{relation_str},阵营:{other_avatar.alignment},宗门:{sect_str},法宝:{tr_str},外貌:{other_avatar.appearance.get_info()}"
weapon_str = other_avatar.weapon.get_info() if other_avatar.weapon is not None else ""
auxiliary_str = other_avatar.auxiliary.get_info() if other_avatar.auxiliary is not None else ""
return f"{other_avatar.name},境界:{other_avatar.cultivation_progress.get_info()},关系:{relation_str},阵营:{other_avatar.alignment},宗门:{sect_str},兵器:{weapon_str},辅助:{auxiliary_str},外貌:{other_avatar.appearance.get_info()}"
def update_time_effect(self) -> None:
"""

View File

@@ -0,0 +1,27 @@
from enum import Enum
class EquipmentGrade(Enum):
"""
装备等级枚举
"""
COMMON = "普通" # 无限复制,作为兜底
TREASURE = "宝物" # 可有多个,无数量限制
ARTIFACT = "法宝" # 全世界唯一
def __str__(self) -> str:
return self.value
@property
def color_rgb(self) -> tuple[int, int, int]:
"""返回装备等级对应的RGB颜色值"""
return _grade_colors.get(self, (200, 200, 200))
# 装备等级颜色映射
_grade_colors = {
EquipmentGrade.COMMON: (150, 150, 150), # 灰色
EquipmentGrade.TREASURE: (138, 43, 226), # 紫色
EquipmentGrade.ARTIFACT: (255, 215, 0), # 金色
}

View File

@@ -17,21 +17,32 @@ from src.classes.technique import (
is_attribute_compatible_with_root,
TechniqueAttribute,
)
from src.classes.treasure import Treasure, treasures_by_id
from src.classes.weapon import Weapon, weapons_by_id
from src.classes.auxiliary import Auxiliary, auxiliaries_by_id
from src.classes.equipment_grade import EquipmentGrade
from src.classes.relation import Relation
from src.classes.alignment import Alignment
class FortuneKind(Enum):
"""奇遇类型"""
TREASURE = "treasure"
WEAPON = "weapon" # 兵器奇遇
AUXILIARY = "auxiliary" # 辅助装备奇遇
TECHNIQUE = "technique"
FIND_MASTER = "find_master"
SPIRIT_STONE = "spirit_stone" # 灵石奇遇
SPIRIT_STONE = "spirit_stone" # 灵石奇遇
CULTIVATION = "cultivation" # 修为奇遇
F_TREASURE_THEMES: list[str] = [
F_WEAPON_THEMES: list[str] = [
"误入洞府",
"巧捡神兵",
"误入试炼",
"异象出世",
"高人赠予",
]
F_AUXILIARY_THEMES: list[str] = [
"误入洞府",
"巧捡奇物",
"误入试炼",
@@ -138,9 +149,18 @@ def _find_potential_master(avatar: Avatar) -> Optional[Avatar]:
return None
def _can_get_treasure(avatar: Avatar) -> bool:
"""检查是否可以获得法宝奇遇"""
return avatar.treasure is None
def _can_get_weapon(avatar: Avatar) -> bool:
"""检查是否可以获得兵器奇遇:当前兵器是普通级时可触发"""
if avatar.weapon is None:
return True
return avatar.weapon.grade == EquipmentGrade.COMMON
def _can_get_auxiliary(avatar: Avatar) -> bool:
"""检查是否可以获得辅助装备奇遇:无辅助装备或辅助装备非法宝级时可触发"""
if avatar.auxiliary is None:
return True
return avatar.auxiliary.grade != EquipmentGrade.ARTIFACT
def _can_get_technique(avatar: Avatar) -> bool:
@@ -179,9 +199,13 @@ def _choose_kind(avatar: Avatar) -> FortuneKind:
"""
possible_kinds: list[FortuneKind] = []
# 法宝奇遇:任何人无法宝都可以
if _can_get_treasure(avatar):
possible_kinds.append(FortuneKind.TREASURE)
# 兵器奇遇:当前兵器是普通级时可触发
if _can_get_weapon(avatar):
possible_kinds.append(FortuneKind.WEAPON)
# 辅助装备奇遇:无辅助装备或辅助装备非法宝级时可触发
if _can_get_auxiliary(avatar):
possible_kinds.append(FortuneKind.AUXILIARY)
# 功法奇遇:任何人功法非上品都可以(实际获得时会有限制)
if _can_get_technique(avatar):
@@ -206,8 +230,10 @@ def _choose_kind(avatar: Avatar) -> FortuneKind:
def _pick_theme(kind: FortuneKind) -> str:
if kind == FortuneKind.TREASURE:
return random.choice(F_TREASURE_THEMES)
if kind == FortuneKind.WEAPON:
return random.choice(F_WEAPON_THEMES)
elif kind == FortuneKind.AUXILIARY:
return random.choice(F_AUXILIARY_THEMES)
elif kind == FortuneKind.TECHNIQUE:
return random.choice(F_TECHNIQUE_THEMES)
elif kind == FortuneKind.FIND_MASTER:
@@ -219,16 +245,58 @@ def _pick_theme(kind: FortuneKind) -> str:
return ""
def _get_unique_treasure_for_world(avatar: Avatar) -> Optional[Treasure]:
"""获取世界唯一法宝:从全量里挑选一个未被任何人持有的"""
owned_ids: set[int] = set()
def _get_weapon_for_avatar(avatar: Avatar) -> Optional[Weapon]:
"""
获取兵器:优先法宝 > 宝物 > 普通
- 法宝:检查世界唯一性
- 宝物:可重复
- 普通:可重复
"""
# 尝试获取法宝级兵器
owned_artifact_ids: set[int] = set()
for other in avatar.world.avatar_manager.avatars.values():
if other.treasure is not None:
owned_ids.add(other.treasure.id)
candidates = [t for t in treasures_by_id.values() if t.id not in owned_ids]
if not candidates:
return None
return random.choice(candidates)
if other.weapon is not None and other.weapon.grade == EquipmentGrade.ARTIFACT:
owned_artifact_ids.add(other.weapon.id)
artifact_candidates = [w for w in weapons_by_id.values()
if w.grade == EquipmentGrade.ARTIFACT and w.id not in owned_artifact_ids]
if artifact_candidates:
return random.choice(artifact_candidates)
# 尝试获取宝物级兵器
treasure_candidates = [w for w in weapons_by_id.values()
if w.grade == EquipmentGrade.TREASURE]
if treasure_candidates:
return random.choice(treasure_candidates)
# 回退到普通级兵器(理论上不应该走到这里,因为普通级兵器不应该通过奇遇获得)
return None
def _get_auxiliary_for_avatar(avatar: Avatar) -> Optional[Auxiliary]:
"""
获取辅助装备:优先法宝 > 宝物
- 法宝:检查世界唯一性
- 宝物:可重复
"""
# 尝试获取法宝级辅助装备
owned_artifact_ids: set[int] = set()
for other in avatar.world.avatar_manager.avatars.values():
if other.auxiliary is not None and other.auxiliary.grade == EquipmentGrade.ARTIFACT:
owned_artifact_ids.add(other.auxiliary.id)
artifact_candidates = [a for a in auxiliaries_by_id.values()
if a.grade == EquipmentGrade.ARTIFACT and a.id not in owned_artifact_ids]
if artifact_candidates:
return random.choice(artifact_candidates)
# 尝试获取宝物级辅助装备
treasure_candidates = [a for a in auxiliaries_by_id.values()
if a.grade == EquipmentGrade.TREASURE]
if treasure_candidates:
return random.choice(treasure_candidates)
return None
def _get_fortune_technique_for_avatar(avatar: Avatar) -> Optional[Technique]:
@@ -319,13 +387,15 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
规则:
- 奇遇不是一个 action仅在条件满足时以概率触发。
- 触发条件:
* 法宝奇遇:无法宝(不限散修/宗门)
* 兵器奇遇:当前兵器是普通级
* 辅助装备奇遇:无辅助装备或辅助装备非法宝级
* 功法奇遇:功法非上品(不限散修/宗门,但宗门弟子只能获得本宗门或无宗门功法)
* 拜师奇遇:无师傅且世界中有合适的师傅(优先同宗门,不能拜敌对阵营)
* 灵石奇遇:任何人都可以触发
* 修为奇遇:未达到瓶颈的人可以触发
- 结果:
* 法宝世界唯一且不可重复
* 兵器:优先法宝世界唯一> 宝物(可重复
* 辅助装备:优先法宝(世界唯一)> 宝物(可重复)
* 功法:可重复,优先上品,需与灵根兼容,宗门弟子受宗门限制
* 拜师:建立师徒关系
* 灵石:根据境界获得灵石(相当于一年狩猎售卖收入)
@@ -351,15 +421,25 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
related_avatars = [avatar.id]
actors_for_story = [avatar] # 用于生成故事的角色列表
if kind == FortuneKind.TREASURE:
tr = _get_unique_treasure_for_world(avatar)
if tr is None:
if kind == FortuneKind.WEAPON:
weapon = _get_weapon_for_avatar(avatar)
if weapon is None:
# 回退到功法
kind = FortuneKind.TECHNIQUE
theme = _pick_theme(kind)
else:
avatar.treasure = tr
res_text = f"{avatar.name} 获得法宝『{tr.name}"
avatar.weapon = weapon
res_text = f"{avatar.name} 获得{weapon.grade}兵器『{weapon.name}"
if kind == FortuneKind.AUXILIARY:
auxiliary = _get_auxiliary_for_avatar(avatar)
if auxiliary is None:
# 回退到功法
kind = FortuneKind.TECHNIQUE
theme = _pick_theme(kind)
else:
avatar.auxiliary = auxiliary
res_text = f"{avatar.name} 获得{auxiliary.grade}辅助装备『{auxiliary.name}"
if kind == FortuneKind.TECHNIQUE:
tech = _get_fortune_technique_for_avatar(avatar)

116
src/classes/weapon.py Normal file
View File

@@ -0,0 +1,116 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional, Dict
from src.utils.df import game_configs
from src.classes.effect import load_effect_from_str
from src.classes.equipment_grade import EquipmentGrade
from src.classes.weapon_type import WeaponType
from src.classes.sect import Sect, sects_by_id
@dataclass
class Weapon:
"""
兵器类:用于战斗的装备
字段与 static/game_configs/weapon.csv 对应:
- weapon_type: 兵器类型(剑、刀、枪等)
- grade: 装备等级(普通、宝物、法宝)
- sect_id: 对应宗门ID见 sect.csv允许为空表示无特定宗门归属
- effects: 解析为 dict用于与 Avatar.effects 合并
"""
id: int
name: str
weapon_type: WeaponType
grade: EquipmentGrade
sect_id: Optional[int]
desc: str
effects: dict[str, object] = field(default_factory=dict)
sect: Optional[Sect] = None
# 特殊属性(如万魂幡的吞噬魂魄计数)
special_data: dict = field(default_factory=dict)
def get_info(self) -> str:
"""获取简略信息"""
suffix = ""
# 万魂幡特殊显示
if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0:
suffix = f"(吞噬魂魄:{self.special_data['devoured_souls']}"
return f"{self.name}{suffix}"
def get_detailed_info(self) -> str:
"""获取详细信息"""
souls = ""
if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0:
souls = f" 吞噬魂魄:{self.special_data['devoured_souls']}"
return f"{self.name}{self.weapon_type}·{self.grade}{self.desc}{souls}"
def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon], Dict[int, Weapon]]:
"""从配表加载 weapon 数据。
返回:(按ID、按名称、按宗门ID 的映射)。
若同一宗门配置多个兵器,按首次出现保留(每门至多一个法宝级)。
"""
weapons_by_id: Dict[int, Weapon] = {}
weapons_by_name: Dict[str, Weapon] = {}
weapons_by_sect_id: Dict[int, Weapon] = {}
df = game_configs.get("weapon")
if df is None:
return weapons_by_id, weapons_by_name, weapons_by_sect_id
for _, row in df.iterrows():
raw_sect = row.get("sect_id")
sect_id: Optional[int] = None
if raw_sect is not None and str(raw_sect).strip() and str(raw_sect).strip() != "nan":
sect_id = int(float(raw_sect))
raw_effects_val = row.get("effects", "")
effects = load_effect_from_str(raw_effects_val)
sect_obj: Optional[Sect] = sects_by_id.get(int(sect_id)) if sect_id is not None else None
# 解析weapon_type
weapon_type_str = str(row.get("weapon_type", "其他"))
weapon_type = WeaponType.OTHER
for wt in WeaponType:
if wt.value == weapon_type_str:
weapon_type = wt
break
# 解析grade
grade_str = str(row.get("grade", "普通"))
grade = EquipmentGrade.COMMON
for g in EquipmentGrade:
if g.value == grade_str:
grade = g
break
w = Weapon(
id=int(row["id"]),
name=str(row["name"]),
weapon_type=weapon_type,
grade=grade,
sect_id=sect_id,
desc=str(row.get("desc", "")),
effects=effects,
sect=sect_obj,
)
weapons_by_id[w.id] = w
weapons_by_name[w.name] = w
if w.sect_id is not None and w.sect_id not in weapons_by_sect_id:
weapons_by_sect_id[w.sect_id] = w
return weapons_by_id, weapons_by_name, weapons_by_sect_id
weapons_by_id, weapons_by_name, weapons_by_sect_id = _load_weapons()
def get_common_weapon(weapon_type: WeaponType) -> Optional[Weapon]:
"""获取指定类型的普通兵器(用于兜底)"""
weapon_name = f"普通{weapon_type.value}"
return weapons_by_name.get(weapon_name)

View File

@@ -0,0 +1,19 @@
from enum import Enum
class WeaponType(Enum):
"""
兵器类型枚举
"""
SWORD = "" # 包括剑匣等
SABER = ""
SPEAR = "" # 包括矛、戟
STAFF = "" # 包括杖、棒
FAN = ""
WHIP = ""
ZITHER = "" # 音律武器
FLUTE = "" # 包括箫
def __str__(self) -> str:
return self.value

View File

@@ -6,7 +6,7 @@ Avatar读档反序列化Mixin
读档策略:
- 两阶段加载先加载所有Avatarrelations留空再重建relations网络
- 引用对象通过id从全局字典获取如techniques_by_id
- treasure深拷贝后恢复devoured_souls
- weapon/auxiliary深拷贝后恢复special_data
- 错误容错:缺失的引用对象会跳过而不是崩溃
"""
from typing import TYPE_CHECKING
@@ -41,7 +41,8 @@ class AvatarLoadMixin:
from src.classes.hp_and_mp import HP, MP
from src.classes.technique import techniques_by_id
from src.classes.item import items_by_id
from src.classes.treasure import treasures_by_id
from src.classes.weapon import weapons_by_id
from src.classes.auxiliary import auxiliaries_by_id
from src.classes.sect import sects_by_id
from src.classes.sect_ranks import SectRank
from src.classes.root import Root
@@ -98,12 +99,19 @@ class AvatarLoadMixin:
if item_id in items_by_id:
avatar.items[items_by_id[item_id]] = quantity
# 重建treasure深拷贝因为devoured_souls是实例特有的)
treasure_id = data.get("treasure_id")
if treasure_id is not None and treasure_id in treasures_by_id:
# 重建weapon深拷贝因为special_data是实例特有的)
weapon_id = data.get("weapon_id")
if weapon_id is not None and weapon_id in weapons_by_id:
import copy
avatar.treasure = copy.deepcopy(treasures_by_id[treasure_id])
avatar.treasure.devoured_souls = data.get("treasure_devoured_souls", 0)
avatar.weapon = copy.deepcopy(weapons_by_id[weapon_id])
avatar.weapon.special_data = data.get("weapon_special_data", {})
# 重建auxiliary深拷贝因为special_data是实例特有的
auxiliary_id = data.get("auxiliary_id")
if auxiliary_id is not None and auxiliary_id in auxiliaries_by_id:
import copy
avatar.auxiliary = copy.deepcopy(auxiliaries_by_id[auxiliary_id])
avatar.auxiliary.special_data = data.get("auxiliary_special_data", {})
# 重建spirit_animal
spirit_animal_data = data.get("spirit_animal")

View File

@@ -16,7 +16,8 @@ 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, 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.weapon import Weapon, weapons_by_id, weapons_by_name
from src.classes.auxiliary import Auxiliary, auxiliaries_by_id, auxiliaries_by_name
from src.classes.persona import Persona, personas_by_id, personas_by_name
@@ -436,10 +437,8 @@ def build_avatars_from_plan(
if sect is not None:
avatar.alignment = sect.alignment
avatar.technique = get_technique_by_sect(sect)
treasure = treasures_by_sect_id.get(sect.id)
if treasure is not None and not sect_treasure_assigned.get(sect.id, False): # 宗门仅发放一次所属法宝
avatar.treasure = treasure
sect_treasure_assigned[sect.id] = True
# 每个宗门只分配一个法宝级兵器给最强者(但不在这里分配,而是让奇遇系统处理)
# 宗门成员初始都是普通兵器
if avatar.technique is not None:
mapped = attribute_to_root(avatar.technique.attribute)
@@ -493,7 +492,8 @@ def make_avatars(
level=int(getattr(defined, "level", 0) or 0) if str(getattr(defined, "level", "")).strip() else None,
appearance=int(getattr(defined, "appearance", 0) or 0) if str(getattr(defined, "appearance", "")).strip() else None,
technique=getattr(defined, "technique", None),
treasure=getattr(defined, "treasure", None),
weapon=getattr(defined, "weapon", None),
auxiliary=getattr(defined, "auxiliary", None),
personas=getattr(defined, "personas", None),
)
avatars[da.id] = da
@@ -556,19 +556,34 @@ def _parse_technique(value: Union[str, int, Technique, None]) -> Optional[Techni
return techniques_by_name.get(s)
def _parse_treasure(value: Union[str, int, Treasure, None]) -> Optional[Treasure]:
def _parse_weapon(value: Union[str, int, Weapon, None]) -> Optional[Weapon]:
if value is None:
return None
if isinstance(value, Treasure):
if isinstance(value, Weapon):
return value
if isinstance(value, int):
return treasures_by_id.get(value)
return weapons_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)
return weapons_by_id.get(int(s))
return weapons_by_name.get(s)
def _parse_auxiliary(value: Union[str, int, Auxiliary, None]) -> Optional[Auxiliary]:
if value is None:
return None
if isinstance(value, Auxiliary):
return value
if isinstance(value, int):
return auxiliaries_by_id.get(value)
s = str(value).strip()
if not s:
return None
if s.isdigit():
return auxiliaries_by_id.get(int(s))
return auxiliaries_by_name.get(s)
def _parse_personas(value: Union[str, int, Persona, List[Union[str, int, Persona]], None]) -> Optional[List[Persona]]:
@@ -643,14 +658,15 @@ def get_new_avatar_with_config(
level: Optional[int] = None,
pos: Optional[Tuple[int, int]] = None,
technique: Union[str, int, Technique, None] = None,
treasure: Union[str, int, Treasure, None] = None,
weapon: Union[str, int, Weapon, None] = None,
auxiliary: Union[str, int, Auxiliary, 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。
- 支持字符串参数gender 仅支持 "男/女"sect/technique/weapon/auxiliary/persona 可用名称或数字ID。
参数:
- name: 角色名;为空则根据宗门与姓氏自动生成
@@ -660,7 +676,8 @@ def get_new_avatar_with_config(
- level: 等级0~120未提供时随机
- pos: 初始坐标 (x, y);未提供时随机
- technique: 指定功法
- treasure: 指定法宝
- weapon: 指定兵器
- auxiliary: 指定辅助装备
- personas: 指定个性(单个或列表)
"""
# 年龄(先取整数年龄,规划阶段只用到 age.age不依赖 realm
@@ -704,7 +721,7 @@ def get_new_avatar_with_config(
# 生成
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
@@ -712,9 +729,13 @@ def get_new_avatar_with_config(
if mapped is not None:
avatar.root = mapped
tre_obj = _parse_treasure(treasure)
if tre_obj is not None:
avatar.treasure = tre_obj
weapon_obj = _parse_weapon(weapon)
if weapon_obj is not None:
avatar.weapon = weapon_obj
auxiliary_obj = _parse_auxiliary(auxiliary)
if auxiliary_obj is not None:
avatar.auxiliary = auxiliary_obj
pers_list = _parse_personas(personas)
if pers_list is not None and len(pers_list) > 0:

View File

@@ -8,7 +8,7 @@ Avatar存档序列化Mixin
- relations转换为dict[str, str]avatar_id -> relation_value
- items转换为dict[int, int]item_id -> quantity
- current_action保存动作类名和参数
- treasure需要深拷贝因为devoured_souls是实例特有的)
- weapon/auxiliary需要深拷贝因为special_data是实例特有的)
"""
@@ -76,8 +76,10 @@ class AvatarSaveMixin:
# 物品与资源
"magic_stone": self.magic_stone.value,
"items": items_dict,
"treasure_id": self.treasure.id if self.treasure else None,
"treasure_devoured_souls": self.treasure.devoured_souls if self.treasure else 0,
"weapon_id": self.weapon.id if self.weapon else None,
"weapon_special_data": self.weapon.special_data if self.weapon else {},
"auxiliary_id": self.auxiliary.id if self.auxiliary else None,
"auxiliary_special_data": self.auxiliary.special_data if self.auxiliary else {},
"spirit_animal": spirit_animal_dict,
# 社交与状态

View File

@@ -0,0 +1,9 @@
id,name,grade,sect_id,desc,effects
,名称,等级(普通/宝物/法宝),所属宗门ID(见sect.csv),描述/提示词,"JSON形式"
2,灵舟,法宝,9,小舟承灵气,御风渡海,千里一瞬.,"{""extra_move_step"": 1}"
3,千里镜,法宝,3,澄澈如镜,观千里之外,洞察先机.,"{""extra_observation_radius"": 2}"
5,聚灵阵盘,法宝,5,刻阵成盘,聚纳灵机,修行事半功倍.,"{""extra_cultivate_exp"": 50}"
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}"
1 id name grade sect_id desc effects
2 名称 等级(普通/宝物/法宝) 所属宗门ID(见sect.csv) 描述/提示词 JSON形式
3 2 灵舟 法宝 9 小舟承灵气,御风渡海,千里一瞬. {"extra_move_step": 1}
4 3 千里镜 法宝 3 澄澈如镜,观千里之外,洞察先机. {"extra_observation_radius": 2}
5 5 聚灵阵盘 法宝 5 刻阵成盘,聚纳灵机,修行事半功倍. {"extra_cultivate_exp": 50}
6 7 万欲同心结 法宝 6 情意同心,双修之道相互映照,修为更精进. {"extra_dual_cultivation_exp": 100}
7 8 影遁披风 法宝 8 融身影界,来去无踪,伏击出其不意. {"extra_move_step": 1, "extra_observation_radius": 1}
8 9 百兽驭兽符 法宝 2 以兽纹灵符加持,唤引兽心,御兽更易. {"extra_catch_success_rate": 0.1}

View File

@@ -0,0 +1,14 @@
id,name,weapon_type,grade,sect_id,desc,effects
,名称,兵器类型,等级(普通/宝物/法宝),所属宗门ID(见sect.csv),描述/提示词,"JSON形式"
1,本命剑匣,,法宝,1,以心御剑,匣启如霆,剑意随心破万法.,"{""extra_battle_strength_points"": 3}"
4,镇魂钟,,法宝,7,钟鸣摄魄,定魂镇邪,护心安魂.,"{""extra_battle_strength_points"": 2, ""extra_observation_radius"": 1}"
6,万魂幡,,法宝,4,幡起万魂啾啾,阴风过境.,"{""legal_actions"": [""DevourMortals""], ""extra_battle_strength_points"": ""eval(avatar.weapon.special_data.get('devoured_souls', 0) // 100 * 0.1)""}"
1001,普通剑,,普通,,平凡无奇的剑,修仙者人手一把。,"{}"
1002,普通刀,,普通,,平凡无奇的刀。,"{}"
1003,普通枪,,普通,,平凡无奇的枪。,"{}"
1004,普通棍,,普通,,平凡无奇的棍。,"{}"
1005,普通扇,,普通,,平凡无奇的扇。,"{}"
1006,普通鞭,,普通,,平凡无奇的鞭。,"{}"
1007,普通琴,,普通,,平凡无奇的琴。,"{}"
1008,普通笛,,普通,,平凡无奇的笛。,"{}"
1 id name weapon_type grade sect_id desc effects
2 名称 兵器类型 等级(普通/宝物/法宝) 所属宗门ID(见sect.csv) 描述/提示词 JSON形式
3 1 本命剑匣 法宝 1 以心御剑,匣启如霆,剑意随心破万法. {"extra_battle_strength_points": 3}
4 4 镇魂钟 法宝 7 钟鸣摄魄,定魂镇邪,护心安魂. {"extra_battle_strength_points": 2, "extra_observation_radius": 1}
5 6 万魂幡 法宝 4 幡起万魂啾啾,阴风过境. {"legal_actions": ["DevourMortals"], "extra_battle_strength_points": "eval(avatar.weapon.special_data.get('devoured_souls', 0) // 100 * 0.1)"}
6 1001 普通剑 普通 平凡无奇的剑,修仙者人手一把。 {}
7 1002 普通刀 普通 平凡无奇的刀。 {}
8 1003 普通枪 普通 平凡无奇的枪。 {}
9 1004 普通棍 普通 平凡无奇的棍。 {}
10 1005 普通扇 普通 平凡无奇的扇。 {}
11 1006 普通鞭 普通 平凡无奇的鞭。 {}
12 1007 普通琴 普通 平凡无奇的琴。 {}
13 1008 普通笛 普通 平凡无奇的笛。 {}

View File

@@ -1,4 +1,4 @@
你是一个故事讲述者,这是一个仙侠世界,你需要把一个事件扩展为一个约150字的小故事。
你是一个故事讲述者,这是一个仙侠世界,你需要把一个事件扩展为一个约200~250字的小故事。
写作风格提示:{style}
额外主题提示:{story_prompt}