This commit is contained in:
bridge
2025-10-09 23:50:52 +08:00
parent 55145850ca
commit 24a0662ceb
5 changed files with 112 additions and 24 deletions

View File

@@ -31,6 +31,7 @@ from .plunder_mortals import PlunderMortals
from .help_mortals import HelpMortals
from .talk import Talk
from .devour_mortals import DevourMortals
from .self_heal import SelfHeal
# 注册到 ActionRegistry标注是否为实际可执行动作
register_action(actual=False)(Action)
@@ -58,6 +59,7 @@ register_action(actual=True)(PlunderMortals)
register_action(actual=True)(HelpMortals)
register_action(actual=True)(Talk)
register_action(actual=True)(DevourMortals)
register_action(actual=True)(SelfHeal)
__all__ = [
# 基类
@@ -87,6 +89,7 @@ __all__ = [
"HelpMortals",
"Talk",
"DevourMortals",
"SelfHeal",
]

View File

@@ -21,9 +21,11 @@ class Battle(InstantAction):
target = self._get_target(avatar_name)
if target is None:
return
winner, loser, damage = decide_battle(self.avatar, target)
loser.hp.reduce(damage)
self._last_result = (winner.name, loser.name, damage)
winner, loser, loser_damage, winner_damage = decide_battle(self.avatar, target)
# 应用双方伤害
loser.hp.reduce(loser_damage)
winner.hp.reduce(winner_damage)
self._last_result = (winner.name, loser.name, loser_damage, winner_damage)
def can_start(self, avatar_name: str | None = None) -> bool:
if avatar_name is None:
@@ -42,22 +44,19 @@ class Battle(InstantAction):
def finish(self, avatar_name: str) -> list[Event]:
res = self._last_result
if isinstance(res, tuple) and len(res) in (2, 3):
winner, loser = res[0], res[1]
damage = res[2] if len(res) == 3 else None
if damage is not None:
result_text = f"{winner} 战胜了 {loser}造成{damage}伤害"
else:
result_text = f"{winner} 战胜了 {loser}"
result_event = Event(self.world.month_stamp, result_text)
if not (isinstance(res, tuple) and len(res) == 4):
return []
winner, loser = res[0], res[1]
loser_damage, winner_damage = res[2], res[3]
result_text = f"{winner} 战胜了 {loser}{loser} 受伤{loser_damage}点,{winner} 也受伤{winner_damage}"
result_event = Event(self.world.month_stamp, result_text)
# 生成战斗小故事:直接复用已生成的事件文本
target = self._get_target(avatar_name)
avatar_infos = StoryTeller.build_avatar_infos(self.avatar, target)
start_text = getattr(self, "_start_event_content", "") or result_event.content
story = StoryTeller.tell_story(avatar_infos, start_text, result_event.content)
story_event = Event(self.world.month_stamp, story)
return [result_event, story_event]
return []
# 生成战斗小故事:直接复用已生成的事件文本
target = self._get_target(avatar_name)
avatar_infos = StoryTeller.build_avatar_infos(self.avatar, target)
start_text = getattr(self, "_start_event_content", "") or result_event.content
story = StoryTeller.tell_story(avatar_infos, start_text, result_event.content)
story_event = Event(self.world.month_stamp, story)
return [result_event, story_event]

View File

@@ -0,0 +1,65 @@
from __future__ import annotations
from src.classes.action import TimedAction
from src.classes.event import Event
from src.classes.sect_region import SectRegion
class SelfHeal(TimedAction):
"""
在宗门总部静养疗伤(仅宗门弟子可用,且必须位于自身宗门总部)。
单月动作执行后HP直接回满。
"""
COMMENT = "在宗门总部静养疗伤单月回满HP"
DOABLES_REQUIREMENTS = "自己是宗门弟子且位于本宗门总部区域且当前HP未满"
PARAMS = {}
# 单月动作
duration_months = 1
def _execute(self) -> None:
# 单月直接回满HP
hp_obj = self.avatar.hp
delta = int(max(0, hp_obj.max - hp_obj.cur))
if delta > 0:
hp_obj.recover(delta)
self._healed_total = delta
def _is_in_own_sect_headquarter(self) -> bool:
sect = getattr(self.avatar, "sect", None)
if sect is None:
return False
tile = getattr(self.avatar, "tile", None)
region = getattr(tile, "region", None)
if not isinstance(region, SectRegion):
return False
hq_name = getattr(getattr(sect, "headquarter", None), "name", None) or getattr(sect, "name", None)
return bool(hq_name) and region.name == hq_name
def can_start(self) -> bool:
# 必须是宗门弟子且在自身宗门总部且当前HP未满
if getattr(self.avatar, "sect", None) is None:
return False
if not self._is_in_own_sect_headquarter():
return False
hp_obj = getattr(self.avatar, "hp", None)
if hp_obj is None:
return False
return hp_obj.cur < hp_obj.max
def start(self) -> Event:
region = getattr(getattr(self.avatar, "tile", None), "region", None)
region_name = getattr(region, "name", "宗门总部")
# 重置累计量
self._healed_total = 0
return Event(self.world.month_stamp, f"{self.avatar.name}{region_name} 开始静养疗伤")
# TimedAction 已统一 step 逻辑
def finish(self) -> list[Event]:
healed_total = int(getattr(self, "_healed_total", 0))
# 统一用一次事件简要反馈
return [Event(self.world.month_stamp, f"{self.avatar.name} 疗伤完成HP已回满本次恢复{healed_total}当前HP {self.avatar.hp}")]

View File

@@ -49,10 +49,13 @@ def calc_win_rate(attacker: "Avatar", defender: "Avatar") -> float:
return max(0.1, min(0.9, base))
def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Avatar", int]:
def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Avatar", int, int]:
"""
结算一场战斗,返回(胜者, 败者, 伤害值)。
伤害值根据胜负双方境界差距给出,范围约 [30, 80]。
结算一场战斗,返回(胜者, 败者, 败者掉血, 赢家掉血)。
规则:
- 先按 calc_win_rate 判定胜负;
- 以 get_damage 计算基准伤害,再让败者“多掉一点血”(适度上调,例如 +15%
- 赢家也会受伤,但伤害不超过败者伤害的一半(随机 15%~40% 区间)。
"""
p = calc_win_rate(attacker, defender)
if random.random() < p:
@@ -60,8 +63,16 @@ def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Av
else:
winner, loser = defender, attacker
damage = get_damage(winner, loser)
return winner, loser, damage
base_damage = get_damage(winner, loser)
# 败者多掉一点血:适度上调,保持上限由 HP.reduce 自然处理
loser_damage = max(1, int(base_damage * 1.15))
# 赢家也掉血,但不超过败者的一半:在 15%~40% 的范围取随机值
rnd_ratio = random.uniform(0.15, 0.40)
winner_damage = int(loser_damage * rnd_ratio)
winner_damage = max(0, min(winner_damage, loser_damage // 2))
return winner, loser, loser_damage, winner_damage
def get_escape_success_rate(attacker: "Avatar", defender: "Avatar") -> float:
"""

View File

@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING, Optional
from src.classes.tile import Tile, TileType
from src.classes.sect_region import SectRegion
if TYPE_CHECKING:
from src.classes.region import Region
@@ -34,6 +35,9 @@ class Map():
self.cultivate_region_names = cultivate_regions_by_name
self.city_regions = city_regions_by_id
self.city_region_names = city_regions_by_name
# 宗门总部区域集合(由地图生成阶段注入)
# 若外部未注入 SectRegion这里仍可通过 regions 过滤得到
self.sect_regions = {rid: r for rid, r in self.regions.items() if isinstance(r, SectRegion)}
def is_in_bounds(self, x: int, y: int) -> bool:
"""
@@ -95,6 +99,12 @@ class Map():
# 城市区域
parts.append("城市区域(可以交易):")
parts.extend([f"- {str(region)}" for region in self.city_regions.values()])
parts.append("")
# 宗门总部区域
if getattr(self, "sect_regions", None):
parts.append("宗门总部:")
parts.extend([f"- {region.name} - {region.desc}" for region in self.sect_regions.values()])
return "\n".join(parts)