update
This commit is contained in:
@@ -2,6 +2,7 @@ import random
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional, List
|
||||
from collections import defaultdict
|
||||
import json
|
||||
|
||||
from src.classes.calendar import MonthStamp
|
||||
@@ -18,7 +19,7 @@ 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
|
||||
from src.classes.action_runtime import ActionPlan, ActionInstance
|
||||
|
||||
from src.classes.effect import _merge_effects
|
||||
from src.classes.persona import Persona, personas_by_id, get_random_compatible_personas
|
||||
from src.classes.item import Item
|
||||
from src.classes.magic_stone import MagicStone
|
||||
@@ -87,6 +88,7 @@ class Avatar:
|
||||
appearance: Appearance = field(default_factory=get_random_appearance)
|
||||
# 当月/当步新设动作标记:在 commit_next_plan 设为 True,首次 tick_action 后清为 False
|
||||
_new_action_set_this_step: bool = False
|
||||
# 不缓存 effects;实时从宗门与功法合并
|
||||
|
||||
def __post_init__(self):
|
||||
"""
|
||||
@@ -120,6 +122,18 @@ class Avatar:
|
||||
from src.classes.alignment import Alignment as _Alignment
|
||||
self.alignment = random.choice(list(_Alignment))
|
||||
|
||||
# effects 改为实时属性,不在此初始化
|
||||
|
||||
@property
|
||||
def effects(self) -> dict[str, object]:
|
||||
merged: dict[str, object] = defaultdict(str)
|
||||
if self.sect is not None and getattr(self.sect, "effects", None):
|
||||
merged = _merge_effects(merged, self.sect.effects)
|
||||
if self.technique is not None and getattr(self.technique, "effects", None):
|
||||
merged = _merge_effects(merged, self.technique.effects)
|
||||
return merged
|
||||
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
64
src/classes/effect.py
Normal file
64
src/classes/effect.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import ast
|
||||
from typing import Any
|
||||
|
||||
|
||||
def load_effect_from_str(value: object) -> dict[str, Any]:
|
||||
"""
|
||||
解析 effects 字符串为 dict:
|
||||
- 支持 JSON 格式(双引号)
|
||||
- 支持 Python 字面量(单引号,如 {'k': ['v']})
|
||||
- value 为 None/空字符串/'nan' 时返回 {}
|
||||
- 解析非 dict 则返回 {}
|
||||
"""
|
||||
if value is None:
|
||||
return {}
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
s = str(value).strip()
|
||||
if not s or s == "nan":
|
||||
return {}
|
||||
try:
|
||||
obj = json.loads(s)
|
||||
return obj if isinstance(obj, dict) else {}
|
||||
except Exception:
|
||||
try:
|
||||
obj = ast.literal_eval(s)
|
||||
return obj if isinstance(obj, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _merge_effects(base: dict[str, object], addition: dict[str, object]) -> dict[str, object]:
|
||||
"""
|
||||
合并两个 effects 字典:
|
||||
- list 型(如 legal_actions):做去重并集
|
||||
- 数值型:相加
|
||||
- 其他:后者覆盖前者
|
||||
返回新字典,不修改原对象。
|
||||
"""
|
||||
if not base and not addition:
|
||||
return {}
|
||||
merged: dict[str, object] = dict(base) if base else {}
|
||||
for key, val in (addition or {}).items():
|
||||
if key in merged:
|
||||
old = merged[key]
|
||||
if isinstance(old, list) and isinstance(val, list):
|
||||
# 去重并集,保持相对顺序
|
||||
seen: set[object] = set()
|
||||
result: list[object] = []
|
||||
for x in old + val:
|
||||
if x in seen:
|
||||
continue
|
||||
seen.add(x)
|
||||
result.append(x)
|
||||
merged[key] = result
|
||||
elif isinstance(old, (int, float)) and isinstance(val, (int, float)):
|
||||
merged[key] = old + val
|
||||
else:
|
||||
merged[key] = val
|
||||
else:
|
||||
merged[key] = val
|
||||
return merged
|
||||
@@ -38,9 +38,10 @@ class DualCultivation(MutualAction):
|
||||
def can_start(self, target_avatar: "Avatar|str|None" = None) -> bool:
|
||||
if target_avatar is None:
|
||||
return False
|
||||
# 必须为合欢宗
|
||||
sect = self.avatar.sect
|
||||
if sect is None or sect.name != "合欢宗":
|
||||
# 基于 effects 判断是否允许
|
||||
effects = self.avatar.effects
|
||||
legal_actions = effects["legal_actions"]
|
||||
if not isinstance(legal_actions, list) or "DualCultivation" not in legal_actions:
|
||||
return False
|
||||
target = self._get_target_avatar(target_avatar)
|
||||
if target is None:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from src.classes.alignment import Alignment
|
||||
from src.utils.df import game_configs
|
||||
from src.classes.effect import load_effect_from_str
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
|
||||
@@ -40,6 +42,8 @@ class Sect:
|
||||
technique_names: list[str]
|
||||
# 随机选择宗门时使用的权重(默认1)
|
||||
weight: float = 1.0
|
||||
# 影响角色或系统的效果
|
||||
effects: dict[str, object] = field(default_factory=dict)
|
||||
# 功法:在technique.csv中配置
|
||||
# TODO:法宝
|
||||
# TODO:宗内等级和称谓
|
||||
@@ -86,6 +90,9 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
|
||||
weight_val = row.get("weight", 1)
|
||||
weight = float(str(weight_val)) if str(weight_val) != "nan" else 1.0
|
||||
|
||||
# 读取 effects(兼容 JSON/单引号字面量/空)
|
||||
effects = load_effect_from_str(row.get("effects", ""))
|
||||
|
||||
sect = Sect(
|
||||
id=int(row["id"]),
|
||||
name=str(row["name"]),
|
||||
@@ -103,6 +110,7 @@ def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
|
||||
),
|
||||
technique_names=technique_names,
|
||||
weight=weight,
|
||||
effects=effects,
|
||||
)
|
||||
sects_by_id[sect.id] = sect
|
||||
sects_by_name[sect.name] = sect
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional, Dict, List
|
||||
import json
|
||||
from src.classes.effect import load_effect_from_str
|
||||
|
||||
from src.utils.df import game_configs
|
||||
from src.classes.alignment import Alignment
|
||||
@@ -51,6 +53,8 @@ class Technique:
|
||||
condition: str
|
||||
# 归属宗门名称;None/空表示无宗门要求(散修可修)
|
||||
sect: Optional[str] = None
|
||||
# 影响角色或系统的效果
|
||||
effects: dict[str, object] = field(default_factory=dict)
|
||||
|
||||
def is_allowed_for(self, avatar) -> bool:
|
||||
if not self.condition:
|
||||
@@ -96,6 +100,9 @@ def loads() -> tuple[dict[int, Technique], dict[str, Technique]]:
|
||||
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()
|
||||
# 读取 effects(兼容 JSON/单引号字面量/空)
|
||||
effects = load_effect_from_str(row.get("effects", ""))
|
||||
|
||||
t = Technique(
|
||||
id=int(row["id"]),
|
||||
name=name,
|
||||
@@ -105,6 +112,7 @@ def loads() -> tuple[dict[int, Technique], dict[str, Technique]]:
|
||||
weight=weight,
|
||||
condition=condition,
|
||||
sect=sect,
|
||||
effects=effects,
|
||||
)
|
||||
techniques_by_id[t.id] = t
|
||||
techniques_by_name[t.name] = t
|
||||
|
||||
Reference in New Issue
Block a user