add observe range
This commit is contained in:
@@ -8,48 +8,37 @@ from src.classes.event import Event
|
||||
|
||||
class Talk(InstantAction):
|
||||
"""
|
||||
攀谈:尝试与同区域内的某个NPC进行交谈。
|
||||
- can_start:同区域内存在其他NPC
|
||||
- 发起后:随机寻找“同一tile”的NPC,若不存在则本次无法攀谈
|
||||
- 若找到,则进入 MutualAction: Conversation(允许建立关系)
|
||||
攀谈:尝试与感知范围内的某个NPC进行交谈。
|
||||
- can_start:感知范围内存在其他NPC
|
||||
- 发起后:从感知范围内随机选择一个目标,进入 MutualAction: Conversation(允许建立关系)
|
||||
"""
|
||||
|
||||
COMMENT = "与同区域内的NPC发起攀谈,若同一tile有人则进入交谈"
|
||||
DOABLES_REQUIREMENTS = "同区域内存在其他NPC"
|
||||
COMMENT = "与感知范围内的NPC发起攀谈"
|
||||
DOABLES_REQUIREMENTS = "感知范围内存在其他NPC"
|
||||
PARAMS = {}
|
||||
|
||||
def _get_same_region_others(self) -> list["Avatar"]:
|
||||
return self.world.avatar_manager.get_avatars_in_same_region(self.avatar)
|
||||
def _get_observed_others(self) -> list["Avatar"]:
|
||||
return self.world.avatar_manager.get_observable_avatars(self.avatar)
|
||||
|
||||
def _get_same_tile_others(self) -> list["Avatar"]:
|
||||
same_tile: list["Avatar"] = []
|
||||
my_tile = self.avatar.tile
|
||||
if my_tile is None:
|
||||
return []
|
||||
for v in self.world.avatar_manager.avatars.values():
|
||||
if v is self.avatar or v.tile is None:
|
||||
continue
|
||||
if v.tile == my_tile:
|
||||
same_tile.append(v)
|
||||
return same_tile
|
||||
# 不再限定必须同一 tile,由感知范围统一约束
|
||||
|
||||
def _execute(self) -> None:
|
||||
# Talk 本身不做长期效果,主要在 step 中驱动 Conversation
|
||||
return
|
||||
|
||||
def can_start(self) -> bool:
|
||||
# 是否同区域存在其他NPC(用于展示在动作空间)
|
||||
return len(self._get_same_region_others()) > 0
|
||||
# 感知范围内是否存在其他NPC(用于展示在动作空间)
|
||||
return len(self._get_observed_others()) > 0
|
||||
|
||||
def start(self) -> Event:
|
||||
self.same_region_others = self._get_same_region_others()
|
||||
self.observed_others = self._get_observed_others()
|
||||
# 记录开始事件
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 尝试与同区域的他人攀谈")
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 尝试与感知范围内的他人攀谈")
|
||||
|
||||
def step(self) -> ActionResult:
|
||||
import random
|
||||
|
||||
target = random.choice(self.same_region_others)
|
||||
target = random.choice(self.observed_others)
|
||||
|
||||
# 进入交谈:由概率决定本次是否允许建立关系
|
||||
from src.classes.mutual_action import Conversation
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from typing import Optional, Iterable
|
||||
|
||||
from src.classes.tile import get_avatar_distance
|
||||
from src.classes.observe import get_observable_avatars
|
||||
|
||||
|
||||
class TargetingMixin:
|
||||
@@ -35,6 +36,9 @@ class TargetingMixin:
|
||||
def distance_between(self, a: "Avatar", b: "Avatar") -> int:
|
||||
return get_avatar_distance(a, b)
|
||||
|
||||
def avatars_in_observation_range(self, avatar: "Avatar") -> list["Avatar"]:
|
||||
return self.world.avatar_manager.get_observable_avatars(avatar)
|
||||
|
||||
def preempt_avatar(self, avatar: "Avatar") -> None:
|
||||
"""抢占目标:清空其计划并中断当前动作。"""
|
||||
avatar.clear_plans()
|
||||
|
||||
@@ -119,11 +119,11 @@ class LLMAI(AI):
|
||||
异步决策逻辑:通过LLM决定执行什么动作和参数
|
||||
"""
|
||||
global_info = world.get_info()
|
||||
# 在提示中包含与该角色处于同一区域的其他角色
|
||||
# 在提示中包含处于角色观测范围内的其他角色
|
||||
avatar_infos = {}
|
||||
for avatar in avatars_to_decide:
|
||||
co_region = world.get_avatars_in_same_region(avatar)
|
||||
avatar_infos[avatar.name] = avatar.get_prompt_info(co_region)
|
||||
observed = world.get_observable_avatars(avatar)
|
||||
avatar_infos[avatar.name] = avatar.get_prompt_info(observed)
|
||||
general_action_infos = ACTION_INFOS_STR
|
||||
info = {
|
||||
"avatar_infos": avatar_infos,
|
||||
|
||||
@@ -379,13 +379,13 @@ class Avatar:
|
||||
else:
|
||||
items_info = "物品持有情况:无"
|
||||
|
||||
# 同区域角色(可选)
|
||||
# 观测范围内角色(沿用参数名保持兼容)
|
||||
co_region_info = ""
|
||||
if co_region_avatars:
|
||||
entries: list[str] = []
|
||||
for other in co_region_avatars[:8]:
|
||||
entries.append(f"{other.name}(境界:{other.cultivation_progress.get_simple_info()})")
|
||||
co_region_info = "\n同区域角色:" + (",".join(entries) if entries else "无")
|
||||
co_region_info = "\n观测范围内角色:" + (",".join(entries) if entries else "无")
|
||||
|
||||
# 关系摘要
|
||||
relations_summary = self._get_relations_summary_str()
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Dict, List, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
from src.classes.observe import get_observable_avatars
|
||||
|
||||
@dataclass
|
||||
class AvatarManager:
|
||||
@@ -25,6 +26,13 @@ class AvatarManager:
|
||||
same_region.append(other)
|
||||
return same_region
|
||||
|
||||
def get_observable_avatars(self, avatar: "Avatar") -> List["Avatar"]:
|
||||
"""
|
||||
返回处于 avatar 感知范围内的其他角色列表(不含自己)。
|
||||
基于曼哈顿距离与境界映射的感知半径过滤。
|
||||
"""
|
||||
return get_observable_avatars(avatar, self.avatars.values())
|
||||
|
||||
def remove_avatar(self, avatar_id: str) -> None:
|
||||
"""
|
||||
从管理器中删除一个 avatar,并清理所有与其相关的双向关系。
|
||||
|
||||
@@ -30,7 +30,7 @@ class MutualAction(DefineAction, LLMAction, TargetingMixin):
|
||||
|
||||
ACTION_NAME: str = "MutualAction"
|
||||
COMMENT: str = ""
|
||||
DOABLES_REQUIREMENTS: str = "同区域内可互动"
|
||||
DOABLES_REQUIREMENTS: str = "感知范围内可互动"
|
||||
PARAMS: dict = {"target_avatar": "Avatar"}
|
||||
FEEDBACK_ACTIONS: list[str] = []
|
||||
|
||||
@@ -137,15 +137,15 @@ class MutualAction(DefineAction, LLMAction, TargetingMixin):
|
||||
# 实现 ActualActionMixin 接口
|
||||
def can_start(self, target_avatar: "Avatar|str|None" = None) -> bool:
|
||||
"""
|
||||
检查互动动作能否启动:两个角色距离必须小于等于2
|
||||
检查互动动作能否启动:目标需在发起者的感知范围内。
|
||||
"""
|
||||
if target_avatar is None:
|
||||
return False
|
||||
target = self._get_target_avatar(target_avatar)
|
||||
if target is None:
|
||||
return False
|
||||
distance = get_avatar_distance(self.avatar, target)
|
||||
return distance <= 3
|
||||
from src.classes.observe import is_within_observation
|
||||
return is_within_observation(self.avatar, target)
|
||||
|
||||
def start(self, target_avatar: "Avatar|str") -> Event:
|
||||
"""
|
||||
|
||||
55
src/classes/observe.py
Normal file
55
src/classes/observe.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable, List, TYPE_CHECKING
|
||||
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.tile import get_avatar_distance
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
|
||||
_OBSERVATION_RADIUS_BY_REALM: dict[Realm, int] = {
|
||||
Realm.Qi_Refinement: 2, # 练气
|
||||
Realm.Foundation_Establishment: 3, # 筑基
|
||||
Realm.Core_Formation: 4, # 金丹
|
||||
Realm.Nascent_Soul: 5, # 元婴
|
||||
}
|
||||
|
||||
|
||||
def get_observation_radius_by_realm(realm: Realm) -> int:
|
||||
"""
|
||||
根据境界返回感知半径(基于曼哈顿距离)。
|
||||
"""
|
||||
return _OBSERVATION_RADIUS_BY_REALM.get(realm, 2)
|
||||
|
||||
|
||||
def get_avatar_observation_radius(avatar: "Avatar") -> int:
|
||||
"""
|
||||
获取角色的感知半径。
|
||||
"""
|
||||
return get_observation_radius_by_realm(avatar.cultivation_progress.realm)
|
||||
|
||||
|
||||
def is_within_observation(initiator: "Avatar", other: "Avatar") -> bool:
|
||||
"""
|
||||
判断 other 是否处于 initiator 的感知范围内:
|
||||
汉明距离(曼哈顿距离) <= initiator 的感知半径。
|
||||
"""
|
||||
return get_avatar_distance(initiator, other) <= get_avatar_observation_radius(initiator)
|
||||
|
||||
|
||||
def get_observable_avatars(initiator: "Avatar", avatars: Iterable["Avatar"]) -> List["Avatar"]:
|
||||
"""
|
||||
从给定集合中过滤出处于 initiator 感知范围内的角色(不包含 initiator 本人)。
|
||||
算法:线性扫描 O(N),与现有管理器遍历复杂度一致。
|
||||
"""
|
||||
result: list["Avatar"] = []
|
||||
for v in avatars:
|
||||
if v is initiator:
|
||||
continue
|
||||
if is_within_observation(initiator, v):
|
||||
result.append(v)
|
||||
return result
|
||||
|
||||
|
||||
@@ -22,4 +22,7 @@ class World():
|
||||
return info
|
||||
|
||||
def get_avatars_in_same_region(self, avatar: "Avatar"):
|
||||
return self.avatar_manager.get_avatars_in_same_region(avatar)
|
||||
return self.avatar_manager.get_avatars_in_same_region(avatar)
|
||||
|
||||
def get_observable_avatars(self, avatar: "Avatar"):
|
||||
return self.avatar_manager.get_observable_avatars(avatar)
|
||||
Reference in New Issue
Block a user