diff --git a/src/classes/action/__init__.py b/src/classes/action/__init__.py index 22c4f4f..864b036 100644 --- a/src/classes/action/__init__.py +++ b/src/classes/action/__init__.py @@ -35,6 +35,7 @@ from .catch import Catch from .nurture_weapon import NurtureWeapon from .switch_weapon import SwitchWeapon from .assassinate import Assassinate +from .move_to_direction import MoveToDirection # 注册到 ActionRegistry(标注是否为实际可执行动作) register_action(actual=False)(Action) @@ -66,6 +67,7 @@ register_action(actual=True)(Catch) register_action(actual=True)(NurtureWeapon) register_action(actual=True)(SwitchWeapon) register_action(actual=True)(Assassinate) +register_action(actual=True)(MoveToDirection) # Talk 已移动到 mutual_action 模块,在那里注册 __all__ = [ @@ -100,6 +102,7 @@ __all__ = [ "NurtureWeapon", "SwitchWeapon", "Assassinate", + "MoveToDirection", # Talk 已移动到 mutual_action 模块 ] diff --git a/src/classes/action/move_to_direction.py b/src/classes/action/move_to_direction.py index 8b13789..5ac2e57 100644 --- a/src/classes/action/move_to_direction.py +++ b/src/classes/action/move_to_direction.py @@ -1 +1,81 @@ +from __future__ import annotations + +import random +from src.classes.action import DefineAction, ActualActionMixin, Move +from src.classes.event import Event +from src.classes.action_runtime import ActionResult, ActionStatus +from src.utils.distance import manhattan_distance +from src.classes.region import Region + +class MoveToDirection(DefineAction, ActualActionMixin): + """ + 向某个方向移动探索(固定时长6个月) + """ + + COMMENT = "向某个方向探索未知区域" + DOABLES_REQUIREMENTS = "任何时候都可以执行" + PARAMS = {"direction": "direction (North/South/East/West)"} + IS_MAJOR = False + + # 固定持续时间 + DURATION = 6 + + def __init__(self, avatar, world): + super().__init__(avatar, world) + # 记录本次动作的开始状态 + self.start_monthstamp = None + self.direction = None + + # 方向向量映射 (假设 (0,0) 在左上角) + # North: y减小 + # South: y增加 + # West: x减小 + # East: x增加 + self.DIR_VECTORS = { + "North": (0, -1), + "South": (0, 1), + "West": (-1, 0), + "East": (1, 0), + "北": (0, -1), + "南": (0, 1), + "西": (-1, 0), + "东": (1, 0) + } + + def can_start(self, direction: str | None = None) -> tuple[bool, str]: + if not direction: + return False, "缺少方向参数" + if direction not in self.DIR_VECTORS: + return False, f"无效的方向: {direction}" + return True, "" + + def start(self, direction: str) -> Event: + self.start_monthstamp = self.world.month_stamp + self.direction = direction + return Event(self.world.month_stamp, f"{self.avatar.name} 开始向{direction}方探索未知区域", related_avatars=[self.avatar.id]) + + def step(self, direction: str) -> ActionResult: + # 确保方向已设置 + self.direction = direction + dx_dir, dy_dir = self.DIR_VECTORS[direction] + + # 计算本次移动步长 + step_len = getattr(self.avatar, "move_step_length", 1) + + # 计算实际位移 + dx = dx_dir * step_len + dy = dy_dir * step_len + + # 执行移动 + Move(self.avatar, self.world).execute(dx, dy) + + # 检查是否完成(固定时长) + # 修正:(current - start) >= duration - 1,即第1个月执行后,差值为0,如果duration=1则完成 + elapsed = self.world.month_stamp - self.start_monthstamp + is_done = elapsed >= (self.DURATION - 1) + + return ActionResult(status=(ActionStatus.COMPLETED if is_done else ActionStatus.RUNNING), events=[]) + + async def finish(self, direction: str) -> list[Event]: + return [Event(self.world.month_stamp, f"{self.avatar.name} 结束了向{direction}方的探索", related_avatars=[self.avatar.id])] diff --git a/src/classes/ai.py b/src/classes/ai.py index 87afe8b..ef2e217 100644 --- a/src/classes/ai.py +++ b/src/classes/ai.py @@ -71,7 +71,7 @@ class LLMAI(AI): general_action_infos = ACTION_INFOS_STR async def decide_one(avatar: Avatar): # 获取基于该角色已知区域的世界信息(包含距离计算) - world_info = world.get_info(avatar=avatar) + world_info = world.get_info(avatar=avatar, detailed=True) # 在提示中包含处于角色观测范围内的其他角色 observed = world.get_observable_avatars(avatar) diff --git a/src/classes/map.py b/src/classes/map.py index be19643..75ce62b 100644 --- a/src/classes/map.py +++ b/src/classes/map.py @@ -91,7 +91,6 @@ class Map(): from src.classes.avatar import Avatar from src.classes.region import NormalRegion, CultivateRegion, CityRegion - from src.utils.distance import chebyshev_distance known_region_ids = avatar.known_regions if avatar else None current_loc = (avatar.pos_x, avatar.pos_y) if avatar else None @@ -99,23 +98,14 @@ class Map(): def filter_regions(cls): return { rid: r for rid, r in self.regions.items() - if known_region_ids is None or rid in known_region_ids + if isinstance(r, cls) and (known_region_ids is None or rid in known_region_ids) } def build_regions_info(regions_dict) -> list[str]: infos = [] + step_len = avatar.move_step_length if avatar else 1 for r in regions_dict.values(): - base_info = r.get_detailed_info() if detailed else r.get_info() - - # 如果有当前位置,追加距离信息 - dist = chebyshev_distance(current_loc, r.center_loc) - # 估算到达时间:距离 / 步长 (向上取整) - step_len = avatar.move_step_length - months = (dist + step_len - 1) // step_len - # 避免显示 0 个月 - months = max(1, months) - base_info += f"(距离:{months}月)" - + base_info = r.get_detailed_info(current_loc, step_len) if detailed else r.get_info(current_loc, step_len) infos.append(base_info) return infos diff --git a/src/classes/region.py b/src/classes/region.py index 20da9f6..e78b6b4 100644 --- a/src/classes/region.py +++ b/src/classes/region.py @@ -5,6 +5,7 @@ from abc import ABC, abstractmethod from src.utils.df import game_configs, get_str, get_int, get_list_int from src.utils.config import CONFIG +from src.utils.distance import chebyshev_distance from src.classes.essence import EssenceType, Essence from src.classes.animal import Animal, animals_by_id from src.classes.plant import Plant, plants_by_id @@ -65,11 +66,26 @@ class Region(ABC): f"描述: {self.desc}", ] - def get_info(self) -> str: - return self.name + @abstractmethod + def _get_desc(self) -> str: + """返回紧跟在名字后的描述,通常包含括号,例如 '(金行灵气:5)'""" + pass - def get_detailed_info(self) -> str: - return f"{self.name} - {self.desc}" + def _get_distance_desc(self, current_loc: tuple[int, int] = None, step_len: int = 1) -> str: + if current_loc is None: + return "" + dist = chebyshev_distance(current_loc, self.center_loc) + # 估算到达时间:距离 / 步长 (向上取整) + months = (dist + step_len - 1) // step_len + # 避免显示 0 个月 + months = max(1, months) + return f"(距离:{months}月)" + + def get_info(self, current_loc: tuple[int, int] = None, step_len: int = 1) -> str: + return f"{self.name}{self._get_distance_desc(current_loc, step_len)}" + + def get_detailed_info(self, current_loc: tuple[int, int] = None, step_len: int = 1) -> str: + return f"{self.name}{self._get_desc()} - {self.desc}{self._get_distance_desc(current_loc, step_len)}" def get_structured_info(self) -> dict: return { @@ -108,32 +124,16 @@ class NormalRegion(Region): info_parts.extend([a.get_info() for a in self.animals]) if self.plants: info_parts.extend([p.get_info() for p in self.plants]) - return "; ".join(info_parts) if info_parts else "暂无特色物种" + return "; ".join(info_parts) if info_parts else "无特色物种" - def _get_species_brief(self) -> str: - briefs: list[str] = [] - if self.animals: - briefs.extend([f"{a.name}({a.realm.value})" for a in self.animals]) - if self.plants: - briefs.extend([f"{p.name}({p.realm.value})" for p in self.plants]) - return "、".join(briefs) + def _get_desc(self) -> str: + species_info = self.get_species_info() + return f"(物种分布:{species_info})" def __str__(self) -> str: species_info = self.get_species_info() return f"普通区域:{self.name} - {self.desc} | 物种分布:{species_info}" - def get_info(self) -> str: - brief = self._get_species_brief() - return f"{self.name}({brief})" if brief else self.name - - def get_detailed_info(self) -> str: - brief = self._get_species_brief() - name_with_brief = f"{self.name}({brief})" if brief else self.name - species_info = self.get_species_info() - if not species_info or species_info == "暂无特色物种": - return f"{name_with_brief} - {self.desc}" - return f"{name_with_brief} - {self.desc} | 物种分布:{species_info}" - def get_hover_info(self) -> list[str]: lines = super().get_hover_info() species_info = self.get_species_info() @@ -183,15 +183,12 @@ class CultivateRegion(Region): def get_region_type(self) -> str: return "cultivate" + def _get_desc(self) -> str: + return f"({self.essence_type}行灵气:{self.essence_density})" + def __str__(self) -> str: return f"修炼区域:{self.name}({self.essence_type}行灵气:{self.essence_density})- {self.desc}" - def get_info(self) -> str: - return f"{self.name}({self.essence_type}行灵气:{self.essence_density})" - - def get_detailed_info(self) -> str: - return f"{self.name}({self.essence_type}行灵气:{self.essence_density})- {self.desc}" - def get_hover_info(self) -> list[str]: lines = super().get_hover_info() stars = "★" * self.essence_density + "☆" * (10 - self.essence_density) @@ -217,12 +214,6 @@ class CityRegion(Region): def __str__(self) -> str: return f"城市区域:{self.name} - {self.desc}" - def get_info(self) -> str: - return self.name - - def get_detailed_info(self) -> str: - return f"{self.name} - {self.desc}" - def get_structured_info(self) -> dict: info = super().get_structured_info() info["type_name"] = "城市区域" diff --git a/static/templates/ai.txt b/static/templates/ai.txt index 1352abe..f29f6ea 100644 --- a/static/templates/ai.txt +++ b/static/templates/ai.txt @@ -22,4 +22,5 @@ - 长期目标是非常重要的一个参数,其权重最高 - 执行动作只能从给定的全部动作中选,且需满足对应条件,见动作的requirements文本 - 一些动作需要先移动满足某些条件才可执行,可以适当规划。 -- 和另一个角色交互的动作,必须在对应角色附近。执行前可以先MoveToAvatar \ No newline at end of file +- 和另一个角色交互的动作,必须在对应角色附近。执行前可以先MoveToAvatar +- 如果对世界了解太少,可以先通过MoveToDirection探索世界 \ No newline at end of file