add occupy action
This commit is contained in:
@@ -104,6 +104,7 @@ __all__ = [
|
||||
"Assassinate",
|
||||
"MoveToDirection",
|
||||
# Talk 已移动到 mutual_action 模块
|
||||
# Occupy 已移动到 mutual_action 模块
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@ class Cultivate(TimedAction):
|
||||
return False, "修为已达瓶颈,无法继续修炼"
|
||||
if not isinstance(region, CultivateRegion):
|
||||
return False, "当前不在修炼区域"
|
||||
|
||||
# 检查洞府所有权
|
||||
if region.host_avatar is not None and region.host_avatar != self.avatar:
|
||||
return False, f"该洞府已被 {region.host_avatar.name} 占据,无法修炼"
|
||||
|
||||
if all(region.essence.get_density(et) == 0 for et in essence_types):
|
||||
return False, "当前区域无与灵根相符的灵气"
|
||||
return True, ""
|
||||
|
||||
@@ -593,10 +593,6 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
# 当前无动作时才清除标记,避免清除新提交动作的标记
|
||||
self._new_action_set_this_step = False
|
||||
|
||||
# 每次执行动作后,更新已知区域
|
||||
if self.tile and self.tile.region:
|
||||
self.known_regions.add(self.tile.region.id)
|
||||
|
||||
return events
|
||||
|
||||
def update_cultivation(self, new_level: int):
|
||||
|
||||
@@ -9,6 +9,7 @@ from .talk import Talk
|
||||
from .impart import Impart
|
||||
from .gift_spirit_stone import GiftSpiritStone
|
||||
from .spar import Spar
|
||||
from .occupy import Occupy
|
||||
from src.classes.action.registry import register_action
|
||||
|
||||
__all__ = [
|
||||
@@ -21,6 +22,7 @@ __all__ = [
|
||||
"Impart",
|
||||
"GiftSpiritStone",
|
||||
"Spar",
|
||||
"Occupy",
|
||||
]
|
||||
|
||||
# 注册 mutual actions(均为实际动作)
|
||||
@@ -32,5 +34,6 @@ register_action(actual=True)(Talk)
|
||||
register_action(actual=True)(Impart)
|
||||
register_action(actual=True)(GiftSpiritStone)
|
||||
register_action(actual=True)(Spar)
|
||||
register_action(actual=True)(Occupy)
|
||||
|
||||
|
||||
|
||||
86
src/classes/mutual_action/occupy.py
Normal file
86
src/classes/mutual_action/occupy.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from src.classes.mutual_action.mutual_action import MutualAction
|
||||
from src.classes.event import Event
|
||||
from src.classes.action.registry import register_action
|
||||
from src.classes.region import resolve_region, CultivateRegion
|
||||
from src.classes.action_runtime import ActionResult, ActionStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
from src.classes.world import World
|
||||
|
||||
@register_action(actual=True)
|
||||
class Occupy(MutualAction):
|
||||
"""
|
||||
占据动作(互动版):
|
||||
占据指定的洞府。如果是无主洞府直接占据;如果是有主洞府,则发起抢夺。
|
||||
"""
|
||||
ACTION_NAME = "Occupy"
|
||||
COMMENT = "占据或抢夺洞府"
|
||||
|
||||
# 参数:洞府名称
|
||||
PARAMS = {"region_name": "str"}
|
||||
|
||||
# 对方的反馈选项(仅在抢夺时有效)
|
||||
FEEDBACK_ACTIONS = ["Yield", "Reject"]
|
||||
|
||||
# 反馈对应的中文描述
|
||||
FEEDBACK_LABELS = {
|
||||
"Yield": "让步",
|
||||
"Reject": "拒绝",
|
||||
}
|
||||
|
||||
# 是大事
|
||||
IS_MAJOR = True
|
||||
|
||||
def _get_region_and_host(self, region_name: str) -> tuple[CultivateRegion | None, Avatar | None, str]:
|
||||
"""
|
||||
解析区域并获取主人
|
||||
"""
|
||||
try:
|
||||
region = resolve_region(self.world, region_name)
|
||||
except Exception as e:
|
||||
return None, None, f"无法找到区域:{region_name}"
|
||||
|
||||
if not isinstance(region, CultivateRegion):
|
||||
return None, None, f"{region.name} 不是修炼区域,无法占据"
|
||||
|
||||
return region, region.host_avatar, ""
|
||||
|
||||
def can_start(self, region_name: str) -> tuple[bool, str]:
|
||||
region, host, err = self._get_region_and_host(region_name)
|
||||
if err:
|
||||
return False, err
|
||||
|
||||
if region.host_avatar == self.avatar:
|
||||
return False, "已经是该洞府的主人了"
|
||||
|
||||
return super().can_start(target_avatar=host)
|
||||
|
||||
def start(self, region_name: str) -> Event:
|
||||
region, host, _ = self._get_region_and_host(region_name)
|
||||
return super().start(target_avatar=host)
|
||||
|
||||
def step(self, region_name: str) -> ActionResult:
|
||||
region, host, _ = self._get_region_and_host(region_name)
|
||||
return super().step(target_avatar=host)
|
||||
|
||||
def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None:
|
||||
"""
|
||||
处理反馈结果
|
||||
"""
|
||||
region = self.avatar.tile.region
|
||||
if feedback_name == "Yield":
|
||||
# 对方让步:转移所有权
|
||||
region.host_avatar = self.avatar
|
||||
|
||||
# 记录事件
|
||||
self.avatar.add_event(self.create_event(f"成功从 {target_avatar.name} 手中夺取了 {region.name}", related_avatars=[target_avatar.id]))
|
||||
target_avatar.add_event(Event(self.world.month_stamp, f"面对 {self.avatar.name} 的逼迫,不得不让出了 {region.name}", related_avatars=[self.avatar.id], is_major=True))
|
||||
|
||||
elif feedback_name == "Reject":
|
||||
# 对方拒绝:所有权不变
|
||||
self.avatar.add_event(self.create_event(f"试图抢夺 {region.name},但被 {target_avatar.name} 拒绝", related_avatars=[target_avatar.id]))
|
||||
target_avatar.add_event(Event(self.world.month_stamp, f"拒绝了 {self.avatar.name} 对 {region.name} 的抢夺要求", related_avatars=[self.avatar.id], is_major=True))
|
||||
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union, TypeVar, Type, Optional
|
||||
from typing import Union, TypeVar, Type, Optional, TYPE_CHECKING
|
||||
from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
@@ -11,6 +11,10 @@ from src.classes.animal import Animal, animals_by_id
|
||||
from src.classes.plant import Plant, plants_by_id
|
||||
from src.classes.sect import sects_by_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class Region(ABC):
|
||||
@@ -174,6 +178,9 @@ class CultivateRegion(Region):
|
||||
essence_density: int = 0
|
||||
essence: Essence = field(init=False)
|
||||
|
||||
# 洞府主人:默认为空(无主)
|
||||
host_avatar: Optional["Avatar"] = field(default=None, init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
essence_density_dict = {essence_type: 0 for essence_type in EssenceType}
|
||||
@@ -193,6 +200,10 @@ class CultivateRegion(Region):
|
||||
lines = super().get_hover_info()
|
||||
stars = "★" * self.essence_density + "☆" * (10 - self.essence_density)
|
||||
lines.append(f"主要灵气: {self.essence_type} {stars}")
|
||||
if self.host_avatar:
|
||||
lines.append(f"主人: {self.host_avatar.name}")
|
||||
else:
|
||||
lines.append("主人: 无(可占据)")
|
||||
return lines
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
@@ -202,6 +213,15 @@ class CultivateRegion(Region):
|
||||
"type": str(self.essence_type),
|
||||
"density": self.essence_density
|
||||
}
|
||||
|
||||
if self.host_avatar:
|
||||
info["host"] = {
|
||||
"id": self.host_avatar.id,
|
||||
"name": self.host_avatar.name
|
||||
}
|
||||
else:
|
||||
info["host"] = None
|
||||
|
||||
return info
|
||||
|
||||
|
||||
|
||||
@@ -124,6 +124,16 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
|
||||
# 将所有avatar添加到world
|
||||
world.avatar_manager.avatars = all_avatars
|
||||
|
||||
# 恢复洞府主人关系
|
||||
cultivate_regions_hosts = world_data.get("cultivate_regions_hosts", {})
|
||||
from src.classes.region import CultivateRegion
|
||||
for rid_str, avatar_id in cultivate_regions_hosts.items():
|
||||
rid = int(rid_str)
|
||||
if rid in game_map.regions:
|
||||
region = game_map.regions[rid]
|
||||
if isinstance(region, CultivateRegion) and avatar_id in all_avatars:
|
||||
region.host_avatar = all_avatars[avatar_id]
|
||||
|
||||
# 重建宗门成员关系与功法列表
|
||||
from src.classes.technique import techniques_by_name
|
||||
|
||||
|
||||
@@ -80,12 +80,21 @@ def save_game(
|
||||
}
|
||||
|
||||
# 构建世界数据
|
||||
# 收集有主洞府信息
|
||||
from src.classes.region import CultivateRegion
|
||||
cultivate_regions_hosts = {}
|
||||
if hasattr(world.map, 'regions'):
|
||||
for rid, region in world.map.regions.items():
|
||||
if isinstance(region, CultivateRegion) and region.host_avatar:
|
||||
cultivate_regions_hosts[str(rid)] = region.host_avatar.id
|
||||
|
||||
world_data = {
|
||||
"month_stamp": int(world.month_stamp),
|
||||
"existed_sect_ids": [sect.id for sect in existed_sects],
|
||||
# 天地灵机
|
||||
"current_phenomenon_id": world.current_phenomenon.id if world.current_phenomenon else None,
|
||||
"phenomenon_start_year": world.phenomenon_start_year if hasattr(world, 'phenomenon_start_year') else 0,
|
||||
"cultivate_regions_hosts": cultivate_regions_hosts,
|
||||
}
|
||||
|
||||
# 保存所有Avatar(第一阶段:不含relations)
|
||||
|
||||
@@ -23,6 +23,75 @@ class Simulator:
|
||||
self.world = world
|
||||
self.birth_rate = CONFIG.game.npc_birth_rate_per_month # 从配置文件读取NPC每月出生率
|
||||
|
||||
def _phase_update_perception_and_knowledge(self):
|
||||
"""
|
||||
感知更新阶段:
|
||||
1. 基于感知范围更新 known_regions
|
||||
2. 自动占据无主洞府(如果自己没有洞府)
|
||||
"""
|
||||
from src.classes.observe import get_avatar_observation_radius
|
||||
from src.classes.region import CultivateRegion
|
||||
|
||||
# 1. 缓存当前有洞府的角色ID
|
||||
avatars_with_home = set()
|
||||
# 注意:这里我们只关心 CultivateRegion 的 host
|
||||
# map.cultivate_regions 可能需要确保被正确初始化,如果没有,可以回退到遍历所有 regions
|
||||
# 为了稳妥,遍历所有 Region 筛选
|
||||
cultivate_regions = [
|
||||
r for r in self.world.map.regions.values()
|
||||
if isinstance(r, CultivateRegion)
|
||||
]
|
||||
|
||||
for r in cultivate_regions:
|
||||
if r.host_avatar:
|
||||
avatars_with_home.add(r.host_avatar.id)
|
||||
|
||||
# 2. 遍历所有存活角色
|
||||
for avatar in self.world.avatar_manager.get_living_avatars():
|
||||
# 计算感知半径(曼哈顿距离)
|
||||
radius = get_avatar_observation_radius(avatar)
|
||||
|
||||
# 扫描范围内的坐标
|
||||
# 优化:只扫描半径内的坐标可能比遍历所有region快,也可能慢,取决于地图大小和半径
|
||||
# 地图可能很大,半径通常很小(<10),所以基于坐标扫描更优
|
||||
|
||||
# 获取范围内的有效坐标
|
||||
start_x = max(0, avatar.pos_x - radius)
|
||||
end_x = min(self.world.map.width - 1, avatar.pos_x + radius)
|
||||
start_y = max(0, avatar.pos_y - radius)
|
||||
end_y = min(self.world.map.height - 1, avatar.pos_y + radius)
|
||||
|
||||
# 收集感知到的区域
|
||||
observed_regions = set()
|
||||
for x in range(start_x, end_x + 1):
|
||||
for y in range(start_y, end_y + 1):
|
||||
# 距离判定:曼哈顿距离
|
||||
if abs(x - avatar.pos_x) + abs(y - avatar.pos_y) <= radius:
|
||||
tile = self.world.map.get_tile(x, y)
|
||||
if tile.region:
|
||||
observed_regions.add(tile.region)
|
||||
|
||||
# 更新认知与自动占据
|
||||
for region in observed_regions:
|
||||
# 更新 known_regions
|
||||
avatar.known_regions.add(region.id)
|
||||
|
||||
# 自动占据逻辑
|
||||
# 只有当:是修炼区域 + 无主 + 自己无洞府 时触发
|
||||
if isinstance(region, CultivateRegion):
|
||||
if region.host_avatar is None:
|
||||
if avatar.id not in avatars_with_home:
|
||||
# 占据
|
||||
region.host_avatar = avatar
|
||||
avatars_with_home.add(avatar.id)
|
||||
# 记录事件
|
||||
event = Event(
|
||||
self.world.month_stamp,
|
||||
f"路过 {region.name},发现无主,将其占据。",
|
||||
related_avatars=[avatar.id]
|
||||
)
|
||||
avatar.add_event(event)
|
||||
|
||||
async def _phase_decide_actions(self):
|
||||
"""
|
||||
决策阶段:仅对需要新计划的角色调用 AI(当前无动作且无计划),
|
||||
@@ -242,6 +311,10 @@ class Simulator:
|
||||
"""
|
||||
events = [] # list of Event
|
||||
|
||||
# 0. 感知与认知更新阶段(包括自动占据洞府)
|
||||
# 在思考和决策之前,先让角色感知世界
|
||||
self._phase_update_perception_and_knowledge()
|
||||
|
||||
# 0.5 长期目标思考阶段(在决策之前)
|
||||
events.extend(await self._phase_long_term_objective_thinking())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user