add heal
This commit is contained in:
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
|
||||
65
src/classes/action/self_heal.py
Normal file
65
src/classes/action/self_heal.py
Normal 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})")]
|
||||
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user