add treasures

This commit is contained in:
bridge
2025-10-19 01:33:22 +08:00
parent a0abf1cc4b
commit a002d1bd70
10 changed files with 265 additions and 115 deletions

View File

@@ -19,6 +19,9 @@ class Move(DefineAction, ChunkActionMixin):
world = self.world
# 基于境界的移动步长:曼哈顿限制,优先斜向
step = getattr(self.avatar, "move_step_length", 1)
# 附加移动步长加成
extra_raw = self.avatar.effects.get("extra_move_step", 0)
step += int(extra_raw or 0)
clamped_dx, clamped_dy = clamp_manhattan_with_diagonal_priority(delta_x, delta_y, step)
new_x = self.avatar.pos_x + clamped_dx

View File

@@ -23,6 +23,7 @@ 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.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
@@ -88,6 +89,8 @@ class Avatar:
sect: Sect | None = None
# 外貌1~10级创建时随机生成
appearance: Appearance = field(default_factory=get_random_appearance)
# 装备的法宝(仅一个)
treasure: Optional[Treasure] = None
# 当月/当步新设动作标记:在 commit_next_plan 设为 True首次 tick_action 后清为 False
_new_action_set_this_step: bool = False
# 不缓存 effects实时从宗门与功法合并
@@ -136,6 +139,9 @@ class Avatar:
merged = _merge_effects(merged, self.technique.effects)
# 来自灵根
merged = _merge_effects(merged, self.root.effects)
# 来自法宝
if self.treasure is not None:
merged = _merge_effects(merged, self.treasure.effects)
return merged
@@ -172,6 +178,12 @@ class Avatar:
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()
# 法宝信息detailed 使用 get_detailed_info简略使用 get_info
if self.treasure is not None:
treasures_info = self.treasure.get_detailed_info() if detailed else self.treasure.get_info()
else:
treasures_info = ""
return {
"id": self.id,
"名字": self.name,
@@ -190,6 +202,7 @@ class Avatar:
"个性": personas_info,
"物品": items_info,
"外貌": appearance_info,
"法宝": treasures_info,
}
def __str__(self) -> str:
@@ -579,7 +592,8 @@ class Avatar:
relation = self.get_relation(other_avatar)
relation_str = str(relation)
sect_str = other_avatar.sect.name if other_avatar.sect is not None else "散修"
return f"{other_avatar.name},境界:{other_avatar.cultivation_progress.get_info()},关系:{relation_str},阵营:{other_avatar.alignment},宗门:{sect_str},外貌:{other_avatar.appearance.get_info()}"
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()}"
def update_time_effect(self) -> None:
"""
@@ -597,28 +611,4 @@ class Avatar:
"""
return self.cultivation_progress.get_move_step()
def get_new_avatar_from_ordinary(world: World, current_month_stamp: MonthStamp, name: str, age: Age):
"""
从凡人中来的新修士
这代表其境界为最低
"""
# 生成短ID替代UUID4
avatar_id = get_avatar_id()
birth_month_stamp = current_month_stamp - age.age * 12 + random.randint(0, 11) # 在出生年内随机选择月份
cultivation_progress = CultivationProgress(0)
pos_x = random.randint(0, world.map.width - 1)
pos_y = random.randint(0, world.map.height - 1)
gender = random.choice(list(Gender))
return Avatar(
world=world,
name=name,
id=avatar_id,
birth_month_stamp=MonthStamp(birth_month_stamp),
age=age,
gender=gender,
cultivation_progress=cultivation_progress,
pos_x=pos_x,
pos_y=pos_y,
)

View File

@@ -38,7 +38,10 @@ def get_base_strength(self_avatar: "Avatar") -> float:
grade_points = 0.0
if self_avatar.technique is not None:
grade_points = _GRADE_POINTS.get(self_avatar.technique.grade, 0.0)
return strength_from_level + grade_points
# 来自效果的额外战斗力点数(例如法宝带来的被动加成)
extra_raw = self_avatar.effects.get("extra_battle_strength_points", 0)
extra_points = float(extra_raw or 0.0)
return strength_from_level + grade_points + extra_points
def _combat_strength_vs(opponent: "Avatar", self_avatar: "Avatar") -> float:

View File

@@ -85,6 +85,10 @@ class DualCultivation(MutualAction):
jitter = random.uniform(-0.2, 0.2)
factor = max(3.0, min(5.0, factor + jitter))
exp_gain = int(base * factor)
# 附加“双修经验提升”效果(如法宝)
extra_raw = initiator.effects.get("extra_dual_cultivation_exp", 0)
extra = int(extra_raw or 0)
exp_gain += extra
initiator.cultivation_progress.add_exp(exp_gain)
self._dual_exp_gain = exp_gain

View File

@@ -28,7 +28,10 @@ def get_avatar_observation_radius(avatar: "Avatar") -> int:
"""
获取角色的感知半径。
"""
return get_observation_radius_by_realm(avatar.cultivation_progress.realm)
base = get_observation_radius_by_realm(avatar.cultivation_progress.realm)
extra_raw = avatar.effects.get("extra_observation_radius", 0)
extra = int(extra_raw or 0)
return max(1, base + extra)
def is_within_observation(initiator: "Avatar", other: "Avatar") -> bool:

78
src/classes/treasure.py Normal file
View File

@@ -0,0 +1,78 @@
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.sect import Sect, sects_by_id
@dataclass
class Treasure:
"""
法宝:配置驱动,暂不挂接到 Avatar。
字段与 static/game_configs/treasure.csv 对应:
- sect_id对应宗门ID见 sect.csv允许为空表示无特定宗门归属
- effects解析为 dict用于未来与 Avatar.effects 合并
"""
id: int
name: str
sect_id: Optional[int]
desc: str
effects: dict[str, object] = field(default_factory=dict)
sect: Optional[Sect] = None
def get_info(self) -> str:
return self.name
def get_detailed_info(self) -> str:
sect_name = self.sect.name if self.sect is not None else "散修可用"
return f"{self.name}(宗门:{sect_name}{self.desc}"
def _load_treasures() -> tuple[Dict[int, Treasure], Dict[str, Treasure], Dict[int, Treasure]]:
"""从配表加载 treasure 数据。
返回:(按ID、按名称、按宗门ID 的映射)。
若同一宗门配置多个法宝,按首次出现保留(每门至多一个)。
"""
treasures_by_id: Dict[int, Treasure] = {}
treasures_by_name: Dict[str, Treasure] = {}
treasures_by_sect_id: Dict[int, Treasure] = {}
df = game_configs.get("treasure")
if df is None:
return treasures_by_id, treasures_by_name, treasures_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))
effects = load_effect_from_str(row.get("effects", ""))
sect_obj: Optional[Sect] = sects_by_id.get(int(sect_id)) if sect_id is not None else None
t = Treasure(
id=int(row["id"]),
name=str(row["name"]),
sect_id=sect_id,
desc=str(row.get("desc", "")),
effects=effects,
sect=sect_obj,
)
treasures_by_id[t.id] = t
treasures_by_name[t.name] = t
if t.sect_id is not None and t.sect_id not in treasures_by_sect_id:
treasures_by_sect_id[t.sect_id] = t
return treasures_by_id, treasures_by_name, treasures_by_sect_id
treasures_by_id, treasures_by_name, treasures_by_sect_id = _load_treasures()
for name, treasure in treasures_by_name.items():
print(name, treasure.sect.name)