Files
NewWPSBot/Plugins/WPSCombatSystem/combat_plugin_adventure.py

512 lines
20 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""冒险系统插件 - PVE冒险模式"""
from __future__ import annotations
from datetime import datetime
from typing import List, Optional, Sequence
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
from PWF.Convention.Runtime.Architecture import Architecture
from Plugins.WPSAPI import GuideEntry, GuideSection
from Plugins.WPSBackpackSystem import WPSBackpackSystem
from .combat_plugin_base import WPSCombatBase
from .combat_models import CombatConfig
logger: ProjectConfig = ProjectConfig()
class WPSCombatAdventure(WPSCombatBase):
"""冒险系统插件"""
def get_guide_subtitle(self) -> str:
return "阶段式 PVE 冒险,产出装备与素材"
def collect_command_entries(self) -> Sequence[GuideEntry]:
return (
GuideEntry(
title="冒险 开始",
identifier="冒险 开始 [食物...]",
description="启动第 1 阶段冒险,可额外投入食物缩短时间并提升成功率。",
metadata={"别名": "start"},
icon="🚀",
tags=("阶段1", "需未受伤"),
details=[
{
"type": "steps",
"items": [
"检查自身状态,确认未处于受伤或其他冒险中。",
"可选:准备食物 `food_id` 列表;每个食物将被立即消耗。",
"系统计算预计耗时、成功率并生成冒险记录。",
"创建时钟任务,时间到后自动结算。",
],
},
"结算时会根据装备强度与运势发放奖励。",
],
),
GuideEntry(
title="继续冒险",
identifier="冒险 / 继续冒险",
description="在已有链路下进入下一阶段或查看剩余时间。",
metadata={"别名": "adventure / continue"},
icon="⏱️",
tags=("阶段推进",),
details=[
{
"type": "steps",
"items": [
"查询当前冒险记录,若已在倒计时阶段则返回剩余时间。",
"如冒险链完成并准备进入下一阶段,可再次投喂食物并启动。",
"系统保持阶段编号,累计奖励与时间。",
],
}
],
),
GuideEntry(
title="冒险 停止",
identifier="冒险 停止 / 放弃",
description="放弃当前冒险链,结算状态并清空倒计时。",
metadata={"别名": "放弃 / 停止"},
icon="🛑",
tags=("风险",),
details=[
"放弃后当前阶段奖励作废,未来可从第 1 阶段重开。",
"若系统已开始结算,则命令会提示等待完成。",
],
),
)
def collect_guide_entries(self) -> Sequence[GuideEntry]:
return (
GuideEntry(
title="冒险阶段",
description="Adventure 链路包含多个阶段,难度逐渐提升,奖励也随之增加。",
icon="⚙️",
details=[
{
"type": "list",
"items": [
"阶段时间:基础 15 分钟,最高不超过 24 小时。",
"投喂食物:每份食物提供额外时间与成功率加成。",
"装备影响:装备强度越高,成功率越高且时间越短。",
"运势影响:由运势系统提供当前整点的幸运值,用于修正成功率。",
],
}
],
),
GuideEntry(
title="奖励构成",
description="冒险完成后会发放积分、装备、材料、纪念品等。",
icon="🎁",
details=[
{
"type": "list",
"items": [
"基础积分奖励随阶段提升。",
"装备掉落根据稀有度加权抽取。",
"部分阶段触发事件可获得药剂、种子或纪念品。",
],
}
],
),
)
def collect_additional_sections(self) -> Sequence[GuideSection]:
sections = list(super().collect_additional_sections())
adventure_config_entries: List[GuideEntry] = []
config_labels = {
"combat_adventure_base_time": "阶段起始时间 (分钟)",
"combat_adventure_max_time": "时间上限 (分钟)",
"combat_food_support_time": "单个食物提供时间 (分钟)",
"combat_adventure_base_success_rate": "基础成功率",
"combat_adventure_stage_penalty": "阶段成功率衰减",
"combat_adventure_equipment_coeff": "装备强度加成系数",
"combat_adventure_fortune_coeff": "运势加成系数",
"combat_time_reduction_divisor": "时间缩减除数",
}
for key, label in config_labels.items():
value = CombatConfig.get(key)
adventure_config_entries.append(
GuideEntry(
title=label,
identifier=key,
description=f"{value}",
category="配置项",
icon="📐",
)
)
sections.append(
GuideSection(
title="冒险参数一览",
entries=adventure_config_entries,
layout="grid",
section_id="adventure-config",
description="核心公式与系数决定冒险耗时、成功率与奖励结构。",
)
)
return tuple(sections)
def is_enable_plugin(self) -> bool:
return True
def wake_up(self) -> None:
super().wake_up()
logger.Log(
"Info",
f"{ConsoleFrontColor.GREEN}WPSCombatAdventure 插件已加载{ConsoleFrontColor.RESET}"
)
self.register_plugin("冒险")
self.register_plugin("adventure")
self.register_plugin("继续冒险")
# 恢复过期冒险
service = self.service()
service.recover_overdue_adventures()
async def callback(self, message: str, chat_id: int, user_id: int) -> Optional[str]:
"""
处理冒险命令
命令格式:
- 冒险 开始 [食物1] [食物2] ...
- 继续冒险 [食物1] [食物2] ...
"""
message = self.parse_message_after_at(message).strip()
tokens = message.split()
if not tokens:
# 默认视为继续冒险,支持直接命令 `继续冒险`
return await self._handle_continue_adventure(chat_id, user_id, [])
# 判断是开始新冒险、继续或结束
command = tokens[0].lower()
if command in ["放弃", "停止"]:
return await self._handle_finish_adventure(chat_id, user_id)
if command in ["开始", "start"]:
# 开始新冒险第1阶段
food_items = tokens[1:] if len(tokens) > 1 else []
return await self._handle_start_adventure(chat_id, user_id, food_items)
elif command in ["继续", "continue"]:
food_items = tokens[1:] if len(tokens) > 1 else []
return await self._handle_continue_adventure(chat_id, user_id, food_items)
else:
# 默认视为继续冒险tokens 即为食物列表
food_items = tokens
return await self._handle_continue_adventure(chat_id, user_id, food_items)
def _normalize_food_items(self, user_id: int, raw_items: List[str]) -> List[str]:
"""将用户输入的食物标识转换为背包中的物品ID"""
if not raw_items:
return []
backpack: WPSBackpackSystem = Architecture.Get(WPSBackpackSystem)
try:
user_items = backpack.get_user_items(user_id)
except Exception:
return [item for item in raw_items if item]
id_lookup = {item.item_id.lower(): item.item_id for item in user_items}
name_lookup = {
item.definition.name.lower(): item.item_id for item in user_items
if item.definition and item.definition.name
}
normalized: List[str] = []
for raw in raw_items:
token = raw.strip()
if not token:
continue
lowered = token.lower()
if lowered in id_lookup:
normalized.append(id_lookup[lowered])
continue
if lowered in name_lookup:
normalized.append(name_lookup[lowered])
continue
normalized.append(token)
return normalized
async def _handle_start_adventure(
self,
chat_id: int,
user_id: int,
food_items: list
) -> Optional[str]:
"""处理开始新冒险"""
service = self.service()
# 第1阶段
stage = 1
normalized_food = self._normalize_food_items(user_id, food_items)
success, msg, adventure_id = service.start_adventure(
user_id=user_id,
chat_id=chat_id,
stage=stage,
food_items=normalized_food,
register_callback=self
)
return await self.send_markdown_message(msg, chat_id, user_id)
async def _handle_finish_adventure(
self,
chat_id: int,
user_id: int
) -> Optional[str]:
"""处理结束冒险(不再继续)"""
service = self.service()
status = service.get_player_status(user_id)
if status.get("current_adventure_id"):
_success, msg = service.abort_current_adventure(user_id)
return await self.send_markdown_message(msg, chat_id, user_id)
last_record = service.get_last_adventure_record(user_id)
if not last_record:
return await self.send_markdown_message(
" 你尚未开始冒险,可使用 `冒险 开始`。",
chat_id,
user_id
)
record_status = last_record["status"]
stage = last_record["stage"]
if record_status == "success":
return await self.send_markdown_message(
(
f"✅ 第 {stage} 阶段冒险已完成并发放奖励。\n"
"若不再继续,冒险链已自然结束;未来可随时使用 `冒险 开始` 重新开启。"
),
chat_id,
user_id
)
if record_status == "failed":
return await self.send_markdown_message(
"❌ 最近一次冒险失败,奖励已结算且需要治疗或重新开始。",
chat_id,
user_id
)
if record_status == "abandoned":
return await self.send_markdown_message(
" 上一次冒险已放弃,无需额外操作。如需重新开启,请使用 `冒险 开始`。",
chat_id,
user_id
)
return await self.send_markdown_message(
f" 最近一次冒险状态为 {record_status},无需执行放弃指令。",
chat_id,
user_id
)
async def _handle_continue_adventure(
self,
chat_id: int,
user_id: int,
food_items: list
) -> Optional[str]:
"""处理继续冒险"""
service = self.service()
# 获取当前冒险状态
status = service.get_player_status(user_id)
current_adventure_id = status.get("current_adventure_id")
if current_adventure_id:
adventure = service.get_adventure_by_id(current_adventure_id)
if not adventure:
return await self.send_markdown_message(
(
"❌ 你已经在冒险中,但未找到相关记录,请稍后重试或联系管理员。"
f"\n- 冒险ID{current_adventure_id}"
),
chat_id,
user_id
)
expected_end_str = adventure.get("expected_end_time")
try:
expected_end = datetime.fromisoformat(expected_end_str) if expected_end_str else None
except (TypeError, ValueError):
expected_end = None
if expected_end:
remaining_seconds = (expected_end - datetime.now()).total_seconds()
if remaining_seconds <= 0:
message = [
"⏳ 当前冒险已进入结算,请稍候等待系统发放结果。",
f"- 冒险ID{current_adventure_id}",
f"- 预计完成:{expected_end.strftime('%Y-%m-%d %H:%M')}"
]
else:
remaining_minutes = int((remaining_seconds + 59) // 60)
remaining_text = (
"不到 1 分钟" if remaining_minutes == 0 else f"{remaining_minutes} 分钟"
)
message = [
"⏳ 你已经在冒险中,请等待当前冒险完成。",
f"- 冒险ID{current_adventure_id}",
f"- 剩余时间:{remaining_text}",
f"- 预计完成:{expected_end.strftime('%Y-%m-%d %H:%M')}"
]
else:
message = [
"❌ 你已经在冒险中,但无法解析预计结束时间,请稍后重试。",
f"- 冒险ID{current_adventure_id}"
]
return await self.send_markdown_message("\n".join(message), chat_id, user_id)
last_record = service.get_last_adventure_record(user_id)
if not last_record:
return await self.send_markdown_message(
"❌ 你还没有完成任何冒险,请使用 `冒险 开始 [食物...]` 开始第1阶段",
chat_id,
user_id
)
if last_record["status"] == "failed":
return await self.send_markdown_message(
"❌ 最近一次冒险失败,冒险已结束。请先使用 `冒险 开始 [食物...]` 重新从第1阶段开始",
chat_id,
user_id
)
if last_record["status"] == "abandoned":
return await self.send_markdown_message(
"⚠️ 最近一次冒险已被你放弃需要重新从第1阶段开始",
chat_id,
user_id
)
if last_record["status"] != "success":
return await self.send_markdown_message(
f"❌ 最近一次冒险状态为 {last_record['status']},无法继续。请使用 `冒险 开始 [食物...]` 重新冒险",
chat_id,
user_id
)
# 下一阶段
next_stage = last_record["stage"] + 1
normalized_food = self._normalize_food_items(user_id, food_items)
success, msg, adventure_id = service.start_adventure(
user_id=user_id,
chat_id=chat_id,
stage=next_stage,
food_items=normalized_food,
register_callback=self
)
return await self.send_markdown_message(msg, chat_id, user_id)
async def _settle_adventure_callback(
self,
adventure_id: int,
user_id: int,
chat_id: int
) -> None:
"""冒险结算回调(时钟任务)"""
service = self.service()
success, msg, rewards = service.settle_adventure(adventure_id)
# 发送结算消息
await self.send_markdown_message(msg, chat_id, user_id)
def _help_message(self) -> str:
"""帮助信息"""
return """# 🗺️ 冒险系统
**命令格式:**
- `冒险 开始 [食物1] [食物2] ...`开始第1阶段冒险
- `继续冒险 [食物1] [食物2] ...` 或 `冒险 继续 ...`:继续下一阶段
- `冒险 放弃`:结束当前冒险链(阶段奖励已结算)
- `停止冒险` / `放弃冒险`:在冒险进行中立即终止当前阶段(无奖励)
**说明:**
- 每个阶段耗时翻倍15min → 30min → 60min...
- 食物果酒是可选的可提供buff加成时间缩减、收益提升等
- 阶段结束时立即判定成功/失败并发放奖励
- 完成后可选择继续下一阶段或直接放弃
- 冒险失败会受伤需要消耗100积分治疗
**示例:**
- `冒险 开始`不使用食物开始第1阶段
- `冒险 开始 薄荷果酒`使用1个薄荷果酒时间缩减10%
- `继续冒险 银杏果酒`:继续下一阶段并使用银杏果酒
- `冒险 放弃`:冒险阶段已结算但不再继续
"""
class WPSCombatAdventureAbort(WPSCombatBase):
"""冒险放弃指令插件"""
def is_enable_plugin(self) -> bool:
return True
def wake_up(self) -> None:
super().wake_up()
logger.Log(
"Info",
f"{ConsoleFrontColor.GREEN}WPSCombatAdventureAbort 插件已加载{ConsoleFrontColor.RESET}"
)
self.register_plugin("停止冒险")
self.register_plugin("放弃冒险")
self.register_plugin("abort_adventure")
async def callback(self, message: str, chat_id: int, user_id: int) -> Optional[str]:
"""直接放弃当前冒险"""
service = self.service()
status = service.get_player_status(user_id)
if status.get("current_adventure_id"):
_success, msg = service.abort_current_adventure(user_id)
return await self.send_markdown_message(msg, chat_id, user_id)
last_record = service.get_last_adventure_record(user_id)
if not last_record:
return await self.send_markdown_message(
" 你尚未开始冒险,可使用 `冒险 开始`。",
chat_id,
user_id
)
record_status = last_record["status"]
stage = last_record["stage"]
if record_status == "success":
return await self.send_markdown_message(
(
f"✅ 第 {stage} 阶段冒险已完成并发放奖励。\n"
"若不再继续,冒险链已自然结束;未来可随时使用 `冒险 开始` 重新开启。"
),
chat_id,
user_id
)
if record_status == "failed":
return await self.send_markdown_message(
"❌ 最近一次冒险失败,奖励已结算且需要治疗或重新开始。",
chat_id,
user_id
)
if record_status == "abandoned":
return await self.send_markdown_message(
" 上一次冒险已放弃,无需额外操作。如需重新开启,请使用 `冒险 开始`。",
chat_id,
user_id
)
return await self.send_markdown_message(
f" 最近一次冒险状态为 {record_status},无需执行放弃指令。",
chat_id,
user_id
)
__all__ = ["WPSCombatAdventure", "WPSCombatAdventureAbort"]