refactor effect system
This commit is contained in:
@@ -76,7 +76,7 @@ def _load_auxiliaries() -> tuple[Dict[int, Auxiliary], Dict[str, Auxiliary]]:
|
||||
|
||||
for row in df:
|
||||
effects = load_effect_from_str(get_str(row, "effects"))
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
effect_desc = format_effects_to_text(effects)
|
||||
|
||||
# 解析grade
|
||||
|
||||
@@ -40,7 +40,7 @@ from src.classes.nickname_data import Nickname
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
# Mixin 导入
|
||||
from src.classes.avatar.effects_mixin import EffectsMixin
|
||||
from src.classes.effect import EffectsMixin
|
||||
from src.classes.avatar.inventory_mixin import InventoryMixin
|
||||
from src.classes.avatar.action_mixin import ActionMixin
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from src.utils.config import CONFIG
|
||||
|
||||
def _get_effects_text(avatar: "Avatar") -> str:
|
||||
"""获取格式化的效果文本"""
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
breakdown = avatar.get_effect_breakdown()
|
||||
effect_parts = []
|
||||
for source_name, effects in breakdown:
|
||||
@@ -383,7 +383,7 @@ def get_avatar_desc(avatar: "Avatar", detailed: bool = False) -> str:
|
||||
lines.append("\n--- 当前效果明细 ---")
|
||||
breakdown = avatar.get_effect_breakdown()
|
||||
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
|
||||
if not breakdown:
|
||||
lines.append("无额外效果")
|
||||
|
||||
@@ -78,7 +78,7 @@ def _load_celestial_phenomena() -> dict[int, CelestialPhenomenon]:
|
||||
|
||||
# 解析effects
|
||||
effects = load_effect_from_str(get_str(row, "effects"))
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
effect_desc = format_effects_to_text(effects)
|
||||
|
||||
phenomenon = CelestialPhenomenon(
|
||||
|
||||
38
src/classes/effect/__init__.py
Normal file
38
src/classes/effect/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from .consts import (
|
||||
ALL_EFFECTS,
|
||||
EXTRA_BATTLE_STRENGTH_POINTS,
|
||||
EXTRA_MAX_HP,
|
||||
EXTRA_OBSERVATION_RADIUS,
|
||||
EXTRA_CULTIVATE_EXP,
|
||||
CULTIVATE_DURATION_REDUCTION,
|
||||
EXTRA_BREAKTHROUGH_SUCCESS_RATE,
|
||||
EXTRA_DUAL_CULTIVATION_EXP,
|
||||
EXTRA_HARVEST_ITEMS,
|
||||
EXTRA_HUNT_ITEMS,
|
||||
EXTRA_MOVE_STEP,
|
||||
EXTRA_CATCH_SUCCESS_RATE,
|
||||
EXTRA_ESCAPE_SUCCESS_RATE,
|
||||
EXTRA_ASSASSINATE_SUCCESS_RATE,
|
||||
EXTRA_FORTUNE_PROBABILITY,
|
||||
EXTRA_MISFORTUNE_PROBABILITY,
|
||||
EXTRA_CAST_SUCCESS_RATE,
|
||||
EXTRA_WEAPON_PROFICIENCY_GAIN,
|
||||
EXTRA_WEAPON_UPGRADE_CHANCE,
|
||||
EXTRA_MAX_LIFESPAN,
|
||||
EXTRA_HP_RECOVERY_RATE,
|
||||
DAMAGE_REDUCTION,
|
||||
REALM_SUPPRESSION_BONUS,
|
||||
EXTRA_ITEM_SELL_PRICE_MULTIPLIER,
|
||||
SHOP_BUY_PRICE_REDUCTION,
|
||||
EXTRA_PLUNDER_MULTIPLIER,
|
||||
LEGAL_ACTIONS,
|
||||
)
|
||||
from .process import (
|
||||
load_effect_from_str,
|
||||
build_effects_map_from_df,
|
||||
_evaluate_conditional_effect,
|
||||
_merge_effects,
|
||||
)
|
||||
from .mixin import EffectsMixin
|
||||
from .desc import format_effects_to_text, translate_condition, EFFECT_DESC_MAP
|
||||
|
||||
@@ -370,7 +370,7 @@ CSV 中 effects 列的写法(支持宽松JSON格式):
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
Effects 通过 src/classes/effect.py 中的 _merge_effects() 函数合并。
|
||||
Effects 通过 src/classes/effect/process.py 中的 _merge_effects() 函数合并。
|
||||
|
||||
合并规则:
|
||||
1. 列表类型 (如 legal_actions): 取并集(去重)
|
||||
@@ -447,3 +447,4 @@ ALL_EFFECTS = [
|
||||
# 特殊权限
|
||||
"legal_actions", # list[str] - 合法动作列表
|
||||
]
|
||||
|
||||
191
src/classes/effect/desc.py
Normal file
191
src/classes/effect/desc.py
Normal file
@@ -0,0 +1,191 @@
|
||||
from typing import Any
|
||||
import re
|
||||
|
||||
EFFECT_DESC_MAP = {
|
||||
"extra_hp_recovery_rate": "生命恢复速率",
|
||||
"extra_max_hp": "最大生命值",
|
||||
"extra_max_lifespan": "最大寿元",
|
||||
"extra_weapon_proficiency_gain": "兵器熟练度获取",
|
||||
"extra_dual_cultivation_exp": "双修经验",
|
||||
"extra_breakthrough_success_rate": "突破成功率",
|
||||
"extra_fortune_probability": "奇遇概率",
|
||||
"extra_misfortune_probability": "霉运概率",
|
||||
"extra_harvest_items": "采集获取物品",
|
||||
"extra_hunt_items": "狩猎获取物品",
|
||||
"extra_item_sell_price_multiplier": "物品出售价格",
|
||||
"shop_buy_price_reduction": "购买折扣",
|
||||
"extra_weapon_upgrade_chance": "兵器升级概率",
|
||||
"extra_plunder_multiplier": "搜刮收益",
|
||||
"extra_catch_success_rate": "捕捉灵兽成功率",
|
||||
"extra_cultivate_exp": "修炼经验",
|
||||
"extra_battle_strength_points": "战力点数",
|
||||
"extra_escape_success_rate": "逃跑成功率",
|
||||
"extra_assassinate_success_rate": "暗杀成功率",
|
||||
"extra_observation_radius": "感知范围",
|
||||
"extra_move_step": "移动步长",
|
||||
"legal_actions": "特殊能力",
|
||||
"damage_reduction": "伤害减免",
|
||||
"realm_suppression_bonus": "境界压制",
|
||||
"cultivate_duration_reduction": "修炼时长缩减",
|
||||
"extra_cast_success_rate": "铸造成功率",
|
||||
}
|
||||
|
||||
ACTION_DESC_MAP = {
|
||||
"DualCultivation": "双修",
|
||||
"DevourMortals": "吞噬凡人",
|
||||
}
|
||||
|
||||
def format_value(key: str, value: Any) -> str:
|
||||
"""
|
||||
格式化效果数值
|
||||
"""
|
||||
if key == "legal_actions" and isinstance(value, list):
|
||||
actions = [ACTION_DESC_MAP.get(str(a), str(a)) for a in value]
|
||||
return "、".join(actions)
|
||||
|
||||
if isinstance(value, (int, float)):
|
||||
# 处理百分比类型的字段
|
||||
if "rate" in key or "probability" in key or "chance" in key or "multiplier" in key or "gain" in key or "reduction" in key or "bonus" in key:
|
||||
# 如果是小数,转为百分比。通常 0.1 表示 +10%
|
||||
# 但有些可能是直接的倍率?代码里 1.0 + value,所以 value 是增量
|
||||
if isinstance(value, float):
|
||||
percent = value * 100
|
||||
sign = "+" if percent > 0 else ""
|
||||
return f"{sign}{percent:.1f}%"
|
||||
|
||||
# 处理数值类型的字段
|
||||
sign = "+" if value > 0 else ""
|
||||
return f"{sign}{value}"
|
||||
|
||||
return str(value)
|
||||
|
||||
def translate_condition(condition: str) -> str:
|
||||
"""
|
||||
将代码形式的条件表达式转换为易读的中文描述。
|
||||
"""
|
||||
if not condition:
|
||||
return "条件触发"
|
||||
|
||||
# 特殊复杂模式:any(p.name == "xxx" for p in avatar.personas)
|
||||
if "avatar.personas" in condition and "any" in condition:
|
||||
m = re.search(r'p\.name\s*==\s*["\'](.*?)["\']', condition)
|
||||
if m:
|
||||
return f"拥有【{m.group(1)}】特质"
|
||||
|
||||
s = condition
|
||||
|
||||
# 1. 变量映射
|
||||
vars_map = {
|
||||
"avatar.weapon.type": "武器类型",
|
||||
"avatar.weapon.weapon_type.value": "武器类型",
|
||||
"avatar.weapon.proficiency": "兵器熟练度",
|
||||
"avatar.weapon": "兵器",
|
||||
"avatar.cultivation_progress.realm.value": "境界等级",
|
||||
"avatar.cultivation_progress.level": "修为等级",
|
||||
"avatar.alignment": "立场",
|
||||
"avatar.age": "年龄",
|
||||
"avatar.spirit_animal": "灵兽",
|
||||
"avatar.sect": "宗门",
|
||||
"avatar.auxiliary": "辅助装备",
|
||||
}
|
||||
|
||||
# 2. 枚举值映射
|
||||
enums_map = {
|
||||
"WeaponType.SWORD": "剑",
|
||||
"WeaponType.SABER": "刀",
|
||||
"WeaponType.SPEAR": "枪",
|
||||
"WeaponType.STAFF": "棍",
|
||||
"WeaponType.FAN": "扇",
|
||||
"WeaponType.WHIP": "鞭",
|
||||
"WeaponType.ZITHER": "琴",
|
||||
"WeaponType.FLUTE": "笛",
|
||||
"WeaponType.HIDDEN_WEAPON": "暗器",
|
||||
|
||||
"Alignment.RIGHTEOUS": "正道",
|
||||
"Alignment.GOOD": "正道",
|
||||
"Alignment.EVIL": "魔道",
|
||||
"Alignment.NEUTRAL": "中立",
|
||||
}
|
||||
|
||||
# 执行变量和枚举替换
|
||||
for k, v in vars_map.items():
|
||||
s = s.replace(k, v)
|
||||
|
||||
for k, v in enums_map.items():
|
||||
s = s.replace(k, v)
|
||||
|
||||
# 3. 特殊语法处理 (is not None, is None)
|
||||
# 必须在变量替换之后,普通运算符替换之前
|
||||
if " is not None" in s:
|
||||
s = re.sub(r'([^\s]+)\s+is\s+not\s+None', r'拥有\1', s)
|
||||
if " is None" in s:
|
||||
s = re.sub(r'([^\s]+)\s+is\s+None', r'未拥有\1', s)
|
||||
|
||||
# 4. 运算符映射
|
||||
ops_map = {
|
||||
"==": "为",
|
||||
"!=": "非",
|
||||
">=": "≥",
|
||||
"<=": "≤",
|
||||
">": ">",
|
||||
"<": "<",
|
||||
" and ": " 且 ",
|
||||
" or ": " 或 ",
|
||||
" in ": " 属于 ",
|
||||
" not ": "非",
|
||||
}
|
||||
|
||||
for k, v in ops_map.items():
|
||||
s = s.replace(k, v)
|
||||
|
||||
# 清理符号
|
||||
s = s.replace('"', '').replace("'", "")
|
||||
s = s.replace('[', '【').replace(']', '】')
|
||||
|
||||
return f"当{s.strip()}"
|
||||
|
||||
def format_effects_to_text(effects: dict[str, Any] | list[dict[str, Any]]) -> str:
|
||||
"""
|
||||
将 effects 字典转换为易读的文本描述。
|
||||
例如:{"extra_max_hp": 100} -> "最大生命值 +100"
|
||||
"""
|
||||
if not effects:
|
||||
return ""
|
||||
|
||||
if isinstance(effects, list):
|
||||
parts = []
|
||||
for eff in effects:
|
||||
text = format_effects_to_text(eff)
|
||||
if text:
|
||||
parts.append(text)
|
||||
return "\n".join(parts)
|
||||
|
||||
desc_list = []
|
||||
for k, v in effects.items():
|
||||
if k == "when":
|
||||
continue
|
||||
|
||||
# 跳过 eval 表达式或者无法解析的 key,或者直接显示 key
|
||||
name = EFFECT_DESC_MAP.get(k, k)
|
||||
|
||||
# 如果是 eval 表达式(字符串形式)或者看起来像代码
|
||||
if isinstance(v, str):
|
||||
if v.startswith("eval(") or "avatar." in v or "//" in v:
|
||||
# 尝试提取简单的描述,或者显示"特殊效果"
|
||||
val_str = "特殊效果(动态)"
|
||||
else:
|
||||
val_str = format_value(k, v)
|
||||
else:
|
||||
val_str = format_value(k, v)
|
||||
|
||||
desc_list.append(f"{name} {val_str}")
|
||||
|
||||
text = ";".join(desc_list)
|
||||
|
||||
# 如果有条件,添加条件描述
|
||||
if effects.get("when"):
|
||||
cond = translate_condition(str(effects["when"]))
|
||||
return f"[{cond}] {text}"
|
||||
|
||||
return text
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar.core import Avatar
|
||||
|
||||
from src.classes.effect import _merge_effects, _evaluate_conditional_effect
|
||||
from .process import _merge_effects, _evaluate_conditional_effect
|
||||
from src.classes.hp import HP_MAX_BY_REALM
|
||||
|
||||
|
||||
@@ -204,3 +204,4 @@ class EffectsMixin:
|
||||
def move_step_length(self: "Avatar") -> int:
|
||||
"""获取角色的移动步长"""
|
||||
return self.cultivation_progress.get_move_step()
|
||||
|
||||
@@ -222,3 +222,4 @@ def build_effects_map_from_df(
|
||||
if eff:
|
||||
effects_map[key] = eff
|
||||
return effects_map
|
||||
|
||||
0
src/classes/elixir.py
Normal file
0
src/classes/elixir.py
Normal file
@@ -72,7 +72,7 @@ def _load_personas() -> tuple[dict[int, Persona], dict[str, Persona]]:
|
||||
|
||||
# 解析effects
|
||||
effects = load_effect_from_str(get_str(row, "effects"))
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
effect_desc = format_effects_to_text(effects)
|
||||
|
||||
persona = Persona(
|
||||
|
||||
@@ -129,7 +129,7 @@ _root_effects_by_root = build_effects_map_from_df(
|
||||
effects_column="effects",
|
||||
)
|
||||
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
_root_effect_desc_by_root = {
|
||||
root: format_effects_to_text(effects)
|
||||
for root, effects in _root_effects_by_root.items()
|
||||
|
||||
@@ -207,7 +207,7 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
|
||||
|
||||
# 读取 effects
|
||||
effects = load_effect_from_str(get_str(row, "effects"))
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
effect_desc = format_effects_to_text(effects)
|
||||
|
||||
# 读取倾向兵器类型
|
||||
|
||||
@@ -40,7 +40,7 @@ class SpiritAnimal:
|
||||
return {"extra_battle_strength_points": pts} if pts else {}
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
return {
|
||||
"name": self.name,
|
||||
"desc": f"境界:{self.realm.value}",
|
||||
|
||||
@@ -130,7 +130,7 @@ def loads() -> tuple[dict[int, Technique], dict[str, Technique]]:
|
||||
sect = None
|
||||
|
||||
effects = load_effect_from_str(get_str(row, "effects"))
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
effect_desc = format_effects_to_text(effects)
|
||||
|
||||
t = Technique(
|
||||
|
||||
@@ -70,7 +70,7 @@ def _load_weapons() -> tuple[Dict[int, Weapon], Dict[str, Weapon]]:
|
||||
|
||||
for row in df:
|
||||
effects = load_effect_from_str(get_str(row, "effects"))
|
||||
from src.utils.effect_desc import format_effects_to_text
|
||||
from src.classes.effect import format_effects_to_text
|
||||
effect_desc = format_effects_to_text(effects)
|
||||
|
||||
# 解析weapon_type
|
||||
|
||||
Reference in New Issue
Block a user