This commit is contained in:
bridge
2025-12-11 00:40:11 +08:00
parent a51f0a0ad2
commit 9a16e2aa16
14 changed files with 1305 additions and 1138 deletions

311
src/classes/avatar/core.py Normal file
View File

@@ -0,0 +1,311 @@
"""
Avatar 核心类
精简后的 Avatar 类,通过 Mixin 组合完整功能。
"""
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List, TYPE_CHECKING
if TYPE_CHECKING:
from src.classes.sect_ranks import SectRank
from src.classes.calendar import MonthStamp
from src.classes.world import World
from src.sim.save.avatar_save_mixin import AvatarSaveMixin
from src.sim.load.avatar_load_mixin import AvatarLoadMixin
from src.classes.tile import Tile
from src.classes.region import Region
from src.classes.cultivation import CultivationProgress
from src.classes.root import Root
from src.classes.technique import Technique, get_technique_by_sect
from src.classes.age import Age
from src.classes.event import Event
from src.classes.action_runtime import ActionPlan, ActionInstance
from src.classes.alignment import Alignment
from src.classes.persona import Persona, get_random_compatible_personas
from src.classes.item import Item
from src.classes.weapon import Weapon
from src.classes.auxiliary import Auxiliary
from src.classes.magic_stone import MagicStone
from src.classes.hp_and_mp import HP, HP_MAX_BY_REALM
from src.classes.relation import Relation
from src.classes.sect import Sect
from src.classes.appearance import Appearance, get_random_appearance
from src.classes.spirit_animal import SpiritAnimal
from src.classes.long_term_objective import LongTermObjective
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.avatar.inventory_mixin import InventoryMixin
from src.classes.avatar.action_mixin import ActionMixin
persona_num = CONFIG.avatar.persona_num
class Gender(Enum):
MALE = "male"
FEMALE = "female"
def __str__(self) -> str:
return gender_strs.get(self, self.value)
gender_strs = {
Gender.MALE: "",
Gender.FEMALE: "",
}
# 历史事件的最大数量
MAX_HISTORY_EVENTS = 10
@dataclass
class Avatar(
AvatarSaveMixin,
AvatarLoadMixin,
EffectsMixin,
InventoryMixin,
ActionMixin,
):
"""
NPC的类。
包含了这个角色的一切信息。
"""
world: World
name: str
id: str
birth_month_stamp: MonthStamp
age: Age
gender: Gender
cultivation_progress: CultivationProgress = field(default_factory=lambda: CultivationProgress(0))
pos_x: int = 0
pos_y: int = 0
tile: Optional[Tile] = None
root: Root = field(default_factory=lambda: random.choice(list(Root)))
personas: List[Persona] = field(default_factory=list)
technique: Technique | None = None
history_events: List[Event] = field(default_factory=list)
_pending_events: List[Event] = field(default_factory=list)
current_action: Optional[ActionInstance] = None
planned_actions: List[ActionPlan] = field(default_factory=list)
thinking: str = ""
short_term_objective: str = ""
long_term_objective: Optional[LongTermObjective] = None
magic_stone: MagicStone = field(default_factory=lambda: MagicStone(0))
items: dict[Item, int] = field(default_factory=dict)
hp: HP = field(default_factory=lambda: HP(0, 0))
relations: dict["Avatar", Relation] = field(default_factory=dict)
alignment: Alignment | None = None
sect: Sect | None = None
sect_rank: "SectRank | None" = None
appearance: Appearance = field(default_factory=get_random_appearance)
weapon: Optional[Weapon] = None
weapon_proficiency: float = 0.0
auxiliary: Optional[Auxiliary] = None
spirit_animal: Optional[SpiritAnimal] = None
nickname: Optional[Nickname] = None
custom_pic_id: Optional[int] = None
is_dead: bool = False
death_info: Optional[dict] = None
_new_action_set_this_step: bool = False
_action_cd_last_months: dict[str, int] = field(default_factory=dict)
known_regions: set[int] = field(default_factory=set)
# ========== 宗门相关 ==========
def join_sect(self, sect: Sect, rank: "SectRank") -> None:
"""加入宗门"""
if self.is_dead:
return
if self.sect:
self.leave_sect()
self.sect = sect
self.sect_rank = rank
sect.add_member(self)
def leave_sect(self) -> None:
"""退出宗门"""
if self.sect:
self.sect.remove_member(self)
self.sect = None
self.sect_rank = None
def get_sect_str(self) -> str:
"""获取宗门显示名:有宗门则返回"宗门名+职位",否则返回"散修""""
if self.sect is None:
return "散修"
if self.sect_rank is None:
return self.sect.name
from src.classes.sect_ranks import get_rank_display_name
rank_name = get_rank_display_name(self.sect_rank, self.sect)
return f"{self.sect.name}{rank_name}"
def get_sect_rank_name(self) -> str:
"""获取宗门职位的显示名称"""
if self.sect is None or self.sect_rank is None:
return "散修"
from src.classes.sect_ranks import get_rank_display_name
return get_rank_display_name(self.sect_rank, self.sect)
# ========== 死亡相关 ==========
def set_dead(self, reason: str, time: MonthStamp) -> None:
"""设置角色死亡状态。"""
if self.is_dead:
return
self.is_dead = True
self.death_info = {
"time": int(time),
"reason": reason,
"location": (self.pos_x, self.pos_y)
}
self.planned_actions.clear()
self.current_action = None
self._pending_events.clear()
self.thinking = ""
self.short_term_objective = ""
if self.sect:
self.sect.remove_member(self)
def death_by_old_age(self) -> bool:
"""检查是否老死"""
return self.age.death_by_old_age(self.cultivation_progress.realm)
# ========== 年龄与修为 ==========
def update_age(self, current_month_stamp: MonthStamp):
"""更新年龄"""
self.age.update_age(current_month_stamp, self.birth_month_stamp)
def update_cultivation(self, new_level: int):
"""更新修仙进度,并在境界提升时更新寿命和宗门职位"""
old_realm = self.cultivation_progress.realm
self.cultivation_progress.level = new_level
self.cultivation_progress.realm = self.cultivation_progress.get_realm(new_level)
if self.cultivation_progress.realm != old_realm:
self.age.update_realm(self.cultivation_progress.realm)
self.recalc_effects()
from src.classes.sect_ranks import check_and_promote_sect_rank
check_and_promote_sect_rank(self, old_realm, self.cultivation_progress.realm)
# ========== 区域与位置 ==========
def is_in_region(self, region: Region | None) -> bool:
current_region = self.tile.region
if current_region is None:
tile = self.world.map.get_tile(self.pos_x, self.pos_y)
current_region = tile.region
return current_region == region
def get_co_region_avatars(self, avatars: List["Avatar"]) -> List["Avatar"]:
"""返回与自己处于同一区域的角色列表(不含自己)。"""
if self.tile is None:
return []
same_region: list[Avatar] = []
for other in avatars:
if other is self or other.tile is None:
continue
if other.tile.region == self.tile.region:
same_region.append(other)
return same_region
def _init_known_regions(self):
"""初始化已知区域:当前位置 + 宗门驻地"""
if self.tile and self.tile.region:
self.known_regions.add(self.tile.region.id)
if self.sect:
for r in self.world.map.sect_regions.values():
if r.sect_id == self.sect.id:
self.known_regions.add(r.id)
break
# ========== 关系相关 ==========
def set_relation(self, other: "Avatar", relation: Relation) -> None:
"""设置与另一个角色的关系。"""
from src.classes.relations import set_relation
set_relation(self, other, relation)
def get_relation(self, other: "Avatar") -> Optional[Relation]:
"""获取与另一个角色的关系。"""
from src.classes.relations import get_relation
return get_relation(self, other)
def clear_relation(self, other: "Avatar") -> None:
"""清除与另一个角色的关系。"""
from src.classes.relations import clear_relation
clear_relation(self, other)
# ========== 信息展示(委托) ==========
def get_info(self, detailed: bool = False) -> dict:
from src.classes.avatar.info_presenter import get_avatar_info
return get_avatar_info(self, detailed)
def get_structured_info(self) -> dict:
from src.classes.avatar.info_presenter import get_avatar_structured_info
return get_avatar_structured_info(self)
def get_hover_info(self) -> list[str]:
from src.classes.avatar.info_presenter import get_avatar_hover_info
return get_avatar_hover_info(self)
def get_expanded_info(
self,
co_region_avatars: Optional[List["Avatar"]] = None,
other_avatar: Optional["Avatar"] = None,
detailed: bool = False
) -> dict:
from src.classes.avatar.info_presenter import get_avatar_expanded_info
return get_avatar_expanded_info(self, co_region_avatars, other_avatar, detailed)
def get_other_avatar_info(self, other_avatar: "Avatar") -> str:
from src.classes.avatar.info_presenter import get_other_avatar_info
return get_other_avatar_info(self, other_avatar)
# ========== 魔法方法 ==========
def __post_init__(self):
"""在Avatar创建后自动初始化tile和HP"""
self.tile = self.world.map.get_tile(self.pos_x, self.pos_y)
max_hp = HP_MAX_BY_REALM.get(self.cultivation_progress.realm, 100)
self.hp = HP(max_hp, max_hp)
if not self.personas:
self.personas = get_random_compatible_personas(persona_num, avatar=self)
if self.technique is None:
self.technique = get_technique_by_sect(self.sect)
if self.sect:
self.sect.add_member(self)
if self.alignment is None:
if self.sect is not None:
self.alignment = self.sect.alignment
else:
self.alignment = random.choice(list(Alignment))
self.recalc_effects()
self._init_known_regions()
def __hash__(self) -> int:
return hash(self.id)
def __str__(self) -> str:
return str(self.get_info(detailed=False))