新增菜园陷阱
This commit is contained in:
68
.tasks/2025-11-15_2_trap-system.md
Normal file
68
.tasks/2025-11-15_2_trap-system.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 背景
|
||||
文件名: 2025-11-15_2_trap-system.md
|
||||
创建于: 2025-11-15_16:41:02
|
||||
创建者: ASUS
|
||||
主分支: main
|
||||
任务分支: (不需要创建)
|
||||
Yolo模式: Off
|
||||
|
||||
# 任务描述
|
||||
在菜园系统中加入陷阱功能,支持多种陷阱物品:
|
||||
- 可以在某个地块放置陷阱(如防盗网等)
|
||||
- 陷阱有不同等级,具有不同的触发概率、罚金金额、禁止偷盗时长和消息内容
|
||||
- 偷盗者有概率触发陷阱,触发后立即缴纳罚金并被禁止偷盗一定时间
|
||||
- 陷阱物品可以通过炼金合成获得,使用已有的矿物和木材作为材料
|
||||
|
||||
# 项目概览
|
||||
菜园系统基于PWF插件体系,依赖现有WPSConfigSystem、WPSBackpackSystem、WPSStoreSystem、WPSFortuneSystem以及WPSAlchemyGame。
|
||||
|
||||
# 分析
|
||||
- 数据库扩展:需要在 `garden_plots` 表中添加 `trap_item_id` 和 `trap_config` 字段来存储陷阱信息
|
||||
- 新增 `garden_theft_ban` 表记录用户偷盗禁令(user_id, banned_until)
|
||||
- 陷阱定义:需要设计多个等级的陷阱物品,包含触发概率、罚金、禁止时长、消息等属性
|
||||
- 物品注册:在 `garden_plugin_base.py` 的 `wake_up` 中注册陷阱物品
|
||||
- 炼金配方:注册陷阱的炼金合成配方,使用矿物和木材作为材料
|
||||
- 服务层扩展:`GardenService` 需要添加 `place_trap`、`_check_trap`、`_is_theft_banned`、`_ban_theft` 等方法
|
||||
- 修改偷盗逻辑:在 `steal` 方法中检查禁令和陷阱触发
|
||||
- 插件层:创建 `garden_plugin_trap.py` 实现放置陷阱指令
|
||||
|
||||
# 提议的解决方案
|
||||
- 设计3-4个等级的陷阱物品(普通、稀有、史诗、传说),每个等级有不同的效果
|
||||
- 使用矿物(矿石、宝石、水晶、精华)和木材(银杏、樱花、红枫)合成不同等级的陷阱
|
||||
- 陷阱触发后立即扣除罚金并设置禁止偷盗时间,同时发送陷阱消息给偷盗者
|
||||
- 禁止偷盗期间用户无法偷取任何地块的作物
|
||||
|
||||
# 当前执行步骤:"已完成实现"
|
||||
|
||||
# 任务进度
|
||||
- 2025-11-15_16:43:34
|
||||
- 已修改:
|
||||
- Plugins/WPSGardenSystem/garden_models.py: 添加 GardenTrapDefinition 类和4种陷阱定义,扩展数据库模型
|
||||
- Plugins/WPSGardenSystem/garden_service.py: 添加陷阱相关方法(place_trap, remove_trap, _check_trap, _is_theft_banned, _ban_theft),修改 steal 方法集成陷阱检查
|
||||
- Plugins/WPSGardenSystem/garden_plugin_steal.py: 集成陷阱触发逻辑,扣除罚金并发送消息
|
||||
- Plugins/WPSGardenSystem/garden_plugin_trap.py: 创建放置/移除陷阱的插件
|
||||
- Plugins/WPSGardenSystem/garden_plugin_base.py: 在 wake_up 中注册陷阱物品和炼金配方
|
||||
- 更改:
|
||||
- 实现了完整的陷阱系统,包括4个等级的陷阱(防盗网、荆棘陷阱、魔法结界、传奇守护)
|
||||
- 每个陷阱有不同的触发概率、罚金、禁止时长和消息内容
|
||||
- 陷阱可以通过炼金合成获得,使用矿物和木材作为材料
|
||||
- 偷盗时会检查禁令和陷阱,触发后立即扣除罚金并设置禁止偷盗时间
|
||||
- 添加了 garden_theft_ban 表记录用户偷盗禁令
|
||||
- 原因:实现用户需求的陷阱功能
|
||||
- 阻碍因素:无
|
||||
- 状态:未确认
|
||||
|
||||
- 2025-11-15_16:46:13
|
||||
- 已修改:
|
||||
- Plugins/WPSGardenSystem/garden_models.py: 为 GardenTrapDefinition 添加 durability 字段,设置各等级陷阱的耐久度(普通3次、稀有5次、史诗8次、传说15次)
|
||||
- Plugins/WPSGardenSystem/garden_service.py: 在 _check_trap 中添加耐久度检查和减少逻辑,耐久度归零时自动移除陷阱;在 place_trap 中设置初始耐久度
|
||||
- Plugins/WPSGardenSystem/garden_plugin_steal.py: 在消息中显示陷阱剩余耐久度,耐久度耗尽时提示
|
||||
- Plugins/WPSGardenSystem/garden_plugin_trap.py: 在放置陷阱消息中显示耐久度
|
||||
- Plugins/WPSGardenSystem/garden_plugin_base.py: 在菜园概览中显示陷阱信息及剩余耐久度
|
||||
- 更改:实现了陷阱耐久度系统,每次触发后减少耐久度,耐久度归零时自动移除陷阱
|
||||
- 原因:满足用户需求,让陷阱具有使用次数限制
|
||||
- 阻碍因素:无
|
||||
- 状态:未确认
|
||||
|
||||
# 最终审查
|
||||
|
||||
@@ -26,6 +26,22 @@ class GardenExtraReward(BaseModel):
|
||||
allow_mutation = False
|
||||
|
||||
|
||||
class GardenTrapDefinition(BaseModel):
|
||||
"""陷阱物品定义"""
|
||||
item_id: str
|
||||
display_name: str
|
||||
tier: str # common / rare / epic / legendary
|
||||
description: str
|
||||
trigger_rate: float = Field(..., ge=0.0, le=1.0, description="触发概率")
|
||||
fine_points: int = Field(..., ge=0, description="罚金积分")
|
||||
ban_hours: int = Field(..., ge=0, description="禁止偷盗时长(小时)")
|
||||
durability: int = Field(..., ge=1, description="耐久度(可触发次数)")
|
||||
trigger_message: str = Field(..., description="触发时发送给偷盗者的消息")
|
||||
|
||||
class Config:
|
||||
allow_mutation = False
|
||||
|
||||
|
||||
class GardenCropDefinition(BaseModel):
|
||||
seed_id: str
|
||||
fruit_id: str
|
||||
@@ -382,6 +398,57 @@ GARDEN_MISC_ITEMS = {
|
||||
}
|
||||
}
|
||||
|
||||
# 陷阱物品定义
|
||||
GARDEN_TRAPS: Tuple[GardenTrapDefinition, ...] = (
|
||||
GardenTrapDefinition(
|
||||
item_id="garden_trap_net",
|
||||
display_name="防盗网",
|
||||
tier="common",
|
||||
description="基础防护陷阱,50%概率触发,对偷盗者造成小额罚金并短时禁止偷盗。",
|
||||
trigger_rate=0.5,
|
||||
fine_points=1000,
|
||||
ban_hours=12,
|
||||
durability=1, # 普通陷阱1次使用
|
||||
trigger_message="🕸️ 你触发了防盗网!被罚款 {fine} 分,并且在 {hours} 小时内无法继续偷盗。",
|
||||
),
|
||||
GardenTrapDefinition(
|
||||
item_id="garden_trap_thorn",
|
||||
display_name="荆棘陷阱",
|
||||
tier="rare",
|
||||
description="带刺的防护陷阱,60%概率触发,造成中等罚金并禁止偷盗更长时间。",
|
||||
trigger_rate=0.6,
|
||||
fine_points=2500,
|
||||
ban_hours=24,
|
||||
durability=2, # 稀有陷阱2次使用
|
||||
trigger_message="🌵 你踩到了荆棘陷阱!被罚款 {fine} 分,并且在 {hours} 小时内无法继续偷盗。",
|
||||
),
|
||||
GardenTrapDefinition(
|
||||
item_id="garden_trap_magic",
|
||||
display_name="魔法结界",
|
||||
tier="epic",
|
||||
description="强大的魔法防护,70%概率触发,造成高额罚金并长时间禁止偷盗。",
|
||||
trigger_rate=0.7,
|
||||
fine_points=5000,
|
||||
ban_hours=48,
|
||||
durability=3, # 史诗陷阱4次使用
|
||||
trigger_message="✨ 你触碰到了魔法结界!被罚款 {fine} 分,并且在 {hours} 小时内无法继续偷盗。",
|
||||
),
|
||||
GardenTrapDefinition(
|
||||
item_id="garden_trap_legend",
|
||||
display_name="传奇守护",
|
||||
tier="legendary",
|
||||
description="传说级的防护装置,80%概率触发,造成巨额罚金并长期禁止偷盗。",
|
||||
trigger_rate=0.8,
|
||||
fine_points=10000,
|
||||
ban_hours=72,
|
||||
durability=4, # 传说陷阱4次使用
|
||||
trigger_message="⚡ 你惊醒了传奇守护!被罚款 {fine} 分,并且在 {hours} 小时内无法继续偷盗。",
|
||||
),
|
||||
)
|
||||
|
||||
# 陷阱物品字典(item_id -> GardenTrapDefinition)
|
||||
GARDEN_TRAPS_DICT: Dict[str, GardenTrapDefinition] = {trap.item_id: trap for trap in GARDEN_TRAPS}
|
||||
|
||||
|
||||
def get_garden_db_models() -> List[DatabaseModel]:
|
||||
return [
|
||||
@@ -402,18 +469,32 @@ def get_garden_db_models() -> List[DatabaseModel]:
|
||||
"remaining_fruit": "INTEGER NOT NULL",
|
||||
"theft_users": "TEXT DEFAULT '[]'",
|
||||
"scheduled_task_id": "INTEGER",
|
||||
"trap_item_id": "TEXT",
|
||||
"trap_config": "TEXT",
|
||||
"trap_durability": "INTEGER DEFAULT 0",
|
||||
"PRIMARY KEY (user_id, plot_index)": "",
|
||||
},
|
||||
),
|
||||
DatabaseModel(
|
||||
table_name="garden_theft_ban",
|
||||
column_defs={
|
||||
"user_id": "INTEGER NOT NULL",
|
||||
"banned_until": "TEXT NOT NULL",
|
||||
"PRIMARY KEY (user_id)": "",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"GardenCropDefinition",
|
||||
"GardenExtraReward",
|
||||
"GardenTrapDefinition",
|
||||
"GARDEN_CROPS",
|
||||
"GARDEN_FRUITS",
|
||||
"GARDEN_MISC_ITEMS",
|
||||
"GARDEN_TRAPS",
|
||||
"GARDEN_TRAPS_DICT",
|
||||
"get_garden_db_models",
|
||||
"load_crops_from_config",
|
||||
"register_crop",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional, Sequence, Type, Union, override
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union, override
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
|
||||
@@ -23,6 +23,7 @@ from .garden_models import (
|
||||
GARDEN_CROPS,
|
||||
GARDEN_FRUITS,
|
||||
GARDEN_MISC_ITEMS,
|
||||
GARDEN_TRAPS,
|
||||
GardenCropDefinition,
|
||||
get_garden_db_models,
|
||||
)
|
||||
@@ -291,6 +292,59 @@ class WPSGardenBase(WPSAPI):
|
||||
BackpackItemTier.COMMON,
|
||||
meta.get("description", ""),
|
||||
)
|
||||
|
||||
# 注册陷阱物品
|
||||
for trap in GARDEN_TRAPS:
|
||||
trap_tier = tier_map.get(trap.tier.lower(), BackpackItemTier.RARE)
|
||||
self._safe_register_item(
|
||||
backpack,
|
||||
trap.item_id,
|
||||
trap.display_name,
|
||||
trap_tier,
|
||||
trap.description,
|
||||
)
|
||||
|
||||
# 注册陷阱的炼金合成配方
|
||||
# 获取稀有树木的木材ID
|
||||
ginkgo_crop = GARDEN_CROPS.get("garden_seed_ginkgo")
|
||||
sakura_crop = GARDEN_CROPS.get("garden_seed_sakura")
|
||||
maple_crop = GARDEN_CROPS.get("garden_seed_maple")
|
||||
|
||||
# 防盗网:矿石 + 银杏木材 + 樱花木材
|
||||
if ginkgo_crop and sakura_crop and ginkgo_crop.extra_item_id and sakura_crop.extra_item_id:
|
||||
self._safe_register_trap_recipe(
|
||||
alchemy,
|
||||
("combat_material_ore", ginkgo_crop.extra_item_id, sakura_crop.extra_item_id),
|
||||
"garden_trap_net",
|
||||
0.75, # 75%成功率
|
||||
)
|
||||
|
||||
# 荆棘陷阱:宝石 + 红枫木材 + 银杏木材
|
||||
if maple_crop and ginkgo_crop and maple_crop.extra_item_id and ginkgo_crop.extra_item_id:
|
||||
self._safe_register_trap_recipe(
|
||||
alchemy,
|
||||
("combat_material_gem", maple_crop.extra_item_id, ginkgo_crop.extra_item_id),
|
||||
"garden_trap_thorn",
|
||||
0.70, # 70%成功率
|
||||
)
|
||||
|
||||
# 魔法结界:水晶 + 两种木材
|
||||
if ginkgo_crop and sakura_crop and ginkgo_crop.extra_item_id and sakura_crop.extra_item_id:
|
||||
self._safe_register_trap_recipe(
|
||||
alchemy,
|
||||
("combat_material_crystal", ginkgo_crop.extra_item_id, sakura_crop.extra_item_id),
|
||||
"garden_trap_magic",
|
||||
0.65, # 65%成功率
|
||||
)
|
||||
|
||||
# 传奇守护:精华 + 水晶 + 红枫木材
|
||||
if maple_crop and maple_crop.extra_item_id:
|
||||
self._safe_register_trap_recipe(
|
||||
alchemy,
|
||||
("combat_material_essence", "combat_material_crystal", maple_crop.extra_item_id),
|
||||
"garden_trap_legend",
|
||||
0.60, # 60%成功率
|
||||
)
|
||||
|
||||
logger.Log(
|
||||
"Info",
|
||||
@@ -393,6 +447,24 @@ class WPSGardenBase(WPSAPI):
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _safe_register_trap_recipe(
|
||||
self,
|
||||
alchemy: WPSAlchemyGame,
|
||||
materials: Tuple[str, str, str],
|
||||
result_item_id: str,
|
||||
success_rate: float,
|
||||
) -> None:
|
||||
"""注册陷阱的炼金合成配方"""
|
||||
try:
|
||||
alchemy.register_recipe(
|
||||
materials,
|
||||
result_item_id,
|
||||
"alchemy_ash", # 失败产物是炉灰
|
||||
success_rate,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _clock_mark_mature(self, user_id: int, chat_id: int, plot_index: int) -> None:
|
||||
service = self.service()
|
||||
@@ -454,9 +526,16 @@ class WPSGardenBase(WPSAPI):
|
||||
if is_mature:
|
||||
remaining = plot["remaining_fruit"]
|
||||
theft_users = len(json.loads(plot["theft_users"])) if plot.get("theft_users") else 0
|
||||
trap_info = ""
|
||||
if plot.get("trap_item_id"):
|
||||
trap_durability = int(plot.get("trap_durability", 0))
|
||||
from .garden_models import GARDEN_TRAPS_DICT
|
||||
trap = GARDEN_TRAPS_DICT.get(plot["trap_item_id"])
|
||||
if trap:
|
||||
trap_info = f"|陷阱:{trap.display_name}({trap_durability}次)"
|
||||
status = f"✅ 已成熟(成熟于 {formatted_time})"
|
||||
lines.append(
|
||||
f"- 地块 {idx}|{name}|{status}|剩余果实 {remaining}|被偷次数 {theft_users}"
|
||||
f"- 地块 {idx}|{name}|{status}|剩余果实 {remaining}|被偷次数 {theft_users}{trap_info}"
|
||||
)
|
||||
else:
|
||||
status = f"⌛ 生长中,预计成熟 {formatted_time}"
|
||||
|
||||
143
Plugins/WPSGardenSystem/garden_plugin_place_trap.py
Normal file
143
Plugins/WPSGardenSystem/garden_plugin_place_trap.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Place trap plugin for garden system."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from PWF.Convention.Runtime.Architecture import Architecture
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
|
||||
from .garden_plugin_base import WPSGardenBase
|
||||
from .garden_models import GARDEN_TRAPS_DICT
|
||||
|
||||
|
||||
class WPSGardenPlaceTrap(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "在地块上放置防护陷阱"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="放置陷阱",
|
||||
identifier="放置陷阱 <地块序号> <陷阱物品>",
|
||||
description="在地块上放置防护陷阱,当偷盗者触发时会受到惩罚。",
|
||||
metadata={"别名": "place_trap"},
|
||||
icon="🪤",
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"输入地块序号和陷阱物品名称或ID。",
|
||||
"系统检查地块是否存在且背包中是否有该陷阱。",
|
||||
"成功放置后陷阱会在下次偷盗时生效。",
|
||||
],
|
||||
},
|
||||
"陷阱有不同的触发概率、罚金和禁止时长,等级越高效果越强。",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "指令格式",
|
||||
"description": "`放置陷阱 <地块序号> <陷阱物品>`。",
|
||||
},
|
||||
{
|
||||
"title": "陷阱效果",
|
||||
"description": "不同等级的陷阱具有不同的触发概率、罚金和禁止时长。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("放置陷阱")
|
||||
self.register_plugin("place_trap")
|
||||
|
||||
def _resolve_trap_id(self, keyword: str) -> Optional[str]:
|
||||
"""解析陷阱物品ID"""
|
||||
key = keyword.strip().lower()
|
||||
for trap_item_id, trap in GARDEN_TRAPS_DICT.items():
|
||||
if trap_item_id.lower() == key:
|
||||
return trap_item_id
|
||||
if trap.display_name.lower() == key:
|
||||
return trap_item_id
|
||||
if trap.display_name.lower().replace("陷阱", "").replace("守护", "").replace("结界", "").replace("网", "") == key:
|
||||
return trap_item_id
|
||||
return None
|
||||
|
||||
async def callback(self, message: str, chat_id: int, user_id: int) -> Optional[str]:
|
||||
payload = self.parse_message_after_at(message).strip()
|
||||
if not payload:
|
||||
return await self.send_markdown_message(
|
||||
"❌ 指令格式:`放置陷阱 <地块序号> <陷阱物品>`",
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
tokens = [token.strip() for token in payload.split() if token.strip()]
|
||||
if len(tokens) < 2:
|
||||
return await self.send_markdown_message(
|
||||
"❌ 指令格式:`放置陷阱 <地块序号> <陷阱物品>`",
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
if not tokens[0].isdigit():
|
||||
return await self.send_markdown_message(
|
||||
"❌ 指令格式:`放置陷阱 <地块序号> <陷阱物品>`",
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
plot_index = int(tokens[0])
|
||||
trap_identifier = " ".join(tokens[1:])
|
||||
|
||||
trap_item_id = self._resolve_trap_id(trap_identifier)
|
||||
if not trap_item_id:
|
||||
return await self.send_markdown_message(
|
||||
"❌ 未找到该陷阱物品,可用陷阱:\n" +
|
||||
"\n".join([f"- {trap.display_name} ({trap_item_id})"
|
||||
for trap_item_id, trap in GARDEN_TRAPS_DICT.items()]),
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
# 检查背包中是否有该陷阱
|
||||
backpack: WPSBackpackSystem = Architecture.Get(WPSBackpackSystem)
|
||||
user_items = backpack.get_user_items(user_id)
|
||||
owned_quantity = 0
|
||||
for item in user_items:
|
||||
if item.item_id == trap_item_id:
|
||||
owned_quantity = item.quantity
|
||||
break
|
||||
|
||||
if owned_quantity <= 0:
|
||||
trap = GARDEN_TRAPS_DICT[trap_item_id]
|
||||
return await self.send_markdown_message(
|
||||
f"❌ 背包中没有 {trap.display_name},需要先合成获取",
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
try:
|
||||
self.service().place_trap(
|
||||
user_id=user_id,
|
||||
plot_index=plot_index,
|
||||
trap_item_id=trap_item_id,
|
||||
)
|
||||
# 消耗陷阱物品
|
||||
backpack.set_item_quantity(user_id, trap_item_id, owned_quantity - 1)
|
||||
|
||||
trap = GARDEN_TRAPS_DICT[trap_item_id]
|
||||
return await self.send_markdown_message(
|
||||
f"✅ 已在地块 {plot_index} 上放置 {trap.display_name}\n"
|
||||
f"- 触发概率:{trap.trigger_rate * 100:.0f}%\n"
|
||||
f"- 罚金:{trap.fine_points} 分\n"
|
||||
f"- 禁止时长:{trap.ban_hours} 小时\n"
|
||||
f"- 耐久度:{trap.durability} 次",
|
||||
chat_id, user_id
|
||||
)
|
||||
except ValueError as exc:
|
||||
return await self.send_markdown_message(f"❌ {exc}", chat_id, user_id)
|
||||
|
||||
|
||||
__all__ = ["WPSGardenPlaceTrap"]
|
||||
|
||||
77
Plugins/WPSGardenSystem/garden_plugin_remove_trap.py
Normal file
77
Plugins/WPSGardenSystem/garden_plugin_remove_trap.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""Remove trap plugin for garden system."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
|
||||
from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
|
||||
class WPSGardenRemoveTrap(WPSGardenBase):
|
||||
def get_guide_subtitle(self) -> str:
|
||||
return "移除地块上的防护陷阱"
|
||||
|
||||
def collect_command_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
GuideEntry(
|
||||
title="移除陷阱",
|
||||
identifier="移除陷阱 <地块序号>",
|
||||
description="移除地块上的陷阱。",
|
||||
metadata={"别名": "remove_trap"},
|
||||
icon="🗑️",
|
||||
details=[
|
||||
{
|
||||
"type": "steps",
|
||||
"items": [
|
||||
"输入地块序号。",
|
||||
"系统检查地块是否存在且是否有陷阱。",
|
||||
"成功移除陷阱。",
|
||||
],
|
||||
},
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def collect_guide_entries(self) -> Sequence[GuideEntry]:
|
||||
return (
|
||||
{
|
||||
"title": "指令格式",
|
||||
"description": "`移除陷阱 <地块序号>`。",
|
||||
},
|
||||
)
|
||||
|
||||
def wake_up(self) -> None:
|
||||
super().wake_up()
|
||||
self.register_plugin("移除陷阱")
|
||||
self.register_plugin("remove_trap")
|
||||
|
||||
async def callback(self, message: str, chat_id: int, user_id: int) -> Optional[str]:
|
||||
payload = self.parse_message_after_at(message).strip()
|
||||
if not payload:
|
||||
return await self.send_markdown_message(
|
||||
"❌ 指令格式:`移除陷阱 <地块序号>`",
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
tokens = [token.strip() for token in payload.split() if token.strip()]
|
||||
if not tokens or not tokens[0].isdigit():
|
||||
return await self.send_markdown_message(
|
||||
"❌ 指令格式:`移除陷阱 <地块序号>`",
|
||||
chat_id, user_id
|
||||
)
|
||||
|
||||
plot_index = int(tokens[0])
|
||||
try:
|
||||
self.service().remove_trap(user_id=user_id, plot_index=plot_index)
|
||||
return await self.send_markdown_message(
|
||||
f"✅ 已移除地块 {plot_index} 上的陷阱",
|
||||
chat_id, user_id
|
||||
)
|
||||
except ValueError as exc:
|
||||
return await self.send_markdown_message(f"❌ {exc}", chat_id, user_id)
|
||||
|
||||
|
||||
__all__ = ["WPSGardenRemoveTrap"]
|
||||
|
||||
@@ -9,6 +9,7 @@ from PWF.Convention.Runtime.Architecture import Architecture
|
||||
from PWF.CoreModules.database import get_db
|
||||
from Plugins.WPSAPI import GuideEntry
|
||||
from Plugins.WPSBackpackSystem import WPSBackpackSystem
|
||||
from Plugins.WPSConfigSystem import WPSConfigAPI
|
||||
|
||||
from .garden_plugin_base import WPSGardenBase
|
||||
|
||||
@@ -89,12 +90,36 @@ class WPSGardenSteal(WPSGardenBase):
|
||||
backpack.add_item(user_id, crop.fruit_id, result["stolen_quantity"])
|
||||
|
||||
remaining = result["remaining"]
|
||||
trap_result = result.get("trap_result")
|
||||
|
||||
# 处理陷阱触发
|
||||
if trap_result:
|
||||
trap = trap_result["trap"]
|
||||
config_api: WPSConfigAPI = Architecture.Get(WPSConfigAPI)
|
||||
# 扣除罚金
|
||||
current_points = config_api.get_user_points(user_id)
|
||||
actual_fine = min(trap_result["fine_points"], current_points)
|
||||
if actual_fine > 0:
|
||||
await config_api.adjust_user_points(
|
||||
chat_id, user_id, -actual_fine, f"触发陷阱罚金:{trap.display_name}"
|
||||
)
|
||||
|
||||
# 发送陷阱触发消息给偷盗者
|
||||
trap_message = (
|
||||
f"# 🚨 陷阱触发\n"
|
||||
f"{trap_result['trigger_message']}\n"
|
||||
f"- 扣除罚金:{actual_fine} 分"
|
||||
)
|
||||
await self.send_markdown_message(trap_message, chat_id, user_id)
|
||||
|
||||
message = (
|
||||
"# 🕵️ 偷取成功\n"
|
||||
f"- 目标:{crop.display_name}\n"
|
||||
f"- 获得:{crop.display_name}的果实 × {result['stolen_quantity']}\n"
|
||||
f"- 目标剩余果实:{remaining}"
|
||||
)
|
||||
if trap_result:
|
||||
message += f"\n- ⚠️ 注意:你触发了陷阱!"
|
||||
await self.send_markdown_message(message, chat_id, user_id)
|
||||
|
||||
owner_chat = result.get("chat_id")
|
||||
@@ -104,6 +129,13 @@ class WPSGardenSteal(WPSGardenBase):
|
||||
f"- 你的 {crop.display_name} 被偷走了 1 个果实\n"
|
||||
f"- 当前剩余果实:{remaining}"
|
||||
)
|
||||
if trap_result:
|
||||
trap = trap_result["trap"]
|
||||
durability = trap_result.get("durability", 0)
|
||||
if trap_result.get("durability_exhausted", False):
|
||||
owner_message += f"\n- 🎯 你的{trap.display_name}成功触发了!但陷阱耐久度已耗尽并被移除。"
|
||||
else:
|
||||
owner_message += f"\n- 🎯 好消息:你的{trap.display_name}成功触发了!剩余耐久度:{durability}次"
|
||||
await self.send_markdown_message(owner_message, owner_chat, owner_id)
|
||||
return None
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ from .garden_models import (
|
||||
GARDEN_CROPS,
|
||||
GARDEN_FRUITS,
|
||||
GARDEN_MISC_ITEMS,
|
||||
GARDEN_TRAPS_DICT,
|
||||
GardenCropDefinition,
|
||||
GardenTrapDefinition,
|
||||
get_garden_db_models,
|
||||
)
|
||||
|
||||
@@ -248,12 +250,134 @@ class GardenService:
|
||||
# endregion
|
||||
|
||||
# region Steal
|
||||
def _is_theft_banned(self, user_id: int) -> Tuple[bool, Optional[str]]:
|
||||
"""检查用户是否被禁止偷盗
|
||||
|
||||
Returns:
|
||||
(是否被禁止, 如果被禁止则返回解封时间字符串,否则为None)
|
||||
"""
|
||||
cursor = self._db.conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT banned_until FROM garden_theft_ban WHERE user_id = ?",
|
||||
(user_id,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return False, None
|
||||
|
||||
banned_until_str = row["banned_until"]
|
||||
banned_until = _parse_local_iso(banned_until_str)
|
||||
now = _local_now()
|
||||
|
||||
if banned_until > now:
|
||||
return True, banned_until_str
|
||||
else:
|
||||
# 已过期,删除记录
|
||||
cursor.execute("DELETE FROM garden_theft_ban WHERE user_id = ?", (user_id,))
|
||||
self._db.conn.commit()
|
||||
return False, None
|
||||
|
||||
def _ban_theft(self, user_id: int, ban_hours: int) -> str:
|
||||
"""禁止用户偷盗一定时间
|
||||
|
||||
Returns:
|
||||
解封时间字符串
|
||||
"""
|
||||
banned_until = _local_now() + timedelta(hours=ban_hours)
|
||||
banned_until_str = banned_until.isoformat()
|
||||
|
||||
cursor = self._db.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO garden_theft_ban (user_id, banned_until)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(user_id) DO UPDATE SET banned_until = excluded.banned_until
|
||||
""",
|
||||
(user_id, banned_until_str),
|
||||
)
|
||||
self._db.conn.commit()
|
||||
return banned_until_str
|
||||
|
||||
def _check_trap(self, plot: Dict[str, object], thief_id: int) -> Optional[Dict[str, object]]:
|
||||
"""检查并触发陷阱
|
||||
|
||||
Returns:
|
||||
如果触发陷阱,返回陷阱信息字典;否则返回None
|
||||
"""
|
||||
trap_item_id = plot.get("trap_item_id")
|
||||
if not trap_item_id:
|
||||
return None
|
||||
|
||||
# 检查陷阱耐久度
|
||||
trap_durability = int(plot.get("trap_durability", 0))
|
||||
if trap_durability <= 0:
|
||||
return None
|
||||
|
||||
trap = GARDEN_TRAPS_DICT.get(trap_item_id)
|
||||
if not trap:
|
||||
return None
|
||||
|
||||
# 检查触发概率
|
||||
if random.random() > trap.trigger_rate:
|
||||
return None
|
||||
|
||||
# 触发陷阱:设置禁令
|
||||
banned_until_str = self._ban_theft(thief_id, trap.ban_hours)
|
||||
|
||||
# 减少耐久度
|
||||
new_durability = trap_durability - 1
|
||||
user_id = int(plot["user_id"])
|
||||
plot_index = int(plot["plot_index"])
|
||||
|
||||
cursor = self._db.conn.cursor()
|
||||
if new_durability <= 0:
|
||||
# 耐久度归零,移除陷阱
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE garden_plots SET trap_item_id = NULL, trap_config = NULL, trap_durability = 0
|
||||
WHERE user_id = ? AND plot_index = ?
|
||||
""",
|
||||
(user_id, plot_index),
|
||||
)
|
||||
else:
|
||||
# 更新耐久度
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE garden_plots SET trap_durability = ?
|
||||
WHERE user_id = ? AND plot_index = ?
|
||||
""",
|
||||
(new_durability, user_id, plot_index),
|
||||
)
|
||||
self._db.conn.commit()
|
||||
|
||||
return {
|
||||
"trap": trap,
|
||||
"fine_points": trap.fine_points,
|
||||
"ban_hours": trap.ban_hours,
|
||||
"banned_until": banned_until_str,
|
||||
"durability": new_durability,
|
||||
"durability_exhausted": new_durability <= 0,
|
||||
"trigger_message": trap.trigger_message.format(
|
||||
fine=trap.fine_points,
|
||||
hours=trap.ban_hours,
|
||||
),
|
||||
}
|
||||
|
||||
def steal(self, *, thief_id: int, owner_id: int, plot_index: int) -> Dict[str, object]:
|
||||
plot = self.get_plot(owner_id, plot_index)
|
||||
if not plot:
|
||||
raise ValueError("目标地块不存在")
|
||||
if int(plot["is_mature"]) != 1:
|
||||
raise ValueError("目标作物尚未成熟")
|
||||
|
||||
# 检查是否被禁止偷盗
|
||||
is_banned, banned_until_str = self._is_theft_banned(thief_id)
|
||||
if is_banned:
|
||||
banned_until = _parse_local_iso(banned_until_str)
|
||||
now = _local_now()
|
||||
remaining_hours = (banned_until - now).total_seconds() / 3600
|
||||
raise ValueError(f"你已被禁止偷盗,解封时间:{self.format_display_time(banned_until_str)}(剩余约{int(remaining_hours)}小时)")
|
||||
|
||||
crop = GARDEN_CROPS.get(plot["seed_id"])
|
||||
if not crop:
|
||||
raise ValueError("未知作物")
|
||||
@@ -264,6 +388,10 @@ class GardenService:
|
||||
theft_users = set(json.loads(plot["theft_users"]))
|
||||
if thief_id in theft_users:
|
||||
raise ValueError("你已经偷取过该作物")
|
||||
|
||||
# 检查陷阱(在偷盗之前检查)
|
||||
trap_result = self._check_trap(plot, thief_id)
|
||||
|
||||
theft_users.add(thief_id)
|
||||
remaining -= 1
|
||||
cursor = self._db.conn.cursor()
|
||||
@@ -280,12 +408,61 @@ class GardenService:
|
||||
),
|
||||
)
|
||||
self._db.conn.commit()
|
||||
return {
|
||||
|
||||
result = {
|
||||
"crop": crop,
|
||||
"stolen_quantity": 1,
|
||||
"remaining": remaining,
|
||||
"chat_id": plot["chat_id"],
|
||||
"trap_result": trap_result,
|
||||
}
|
||||
return result
|
||||
|
||||
# endregion
|
||||
|
||||
# region Trap
|
||||
def place_trap(self, *, user_id: int, plot_index: int, trap_item_id: str) -> None:
|
||||
"""在地块上放置陷阱"""
|
||||
plot = self.get_plot(user_id, plot_index)
|
||||
if not plot:
|
||||
raise ValueError("目标地块不存在")
|
||||
|
||||
trap = GARDEN_TRAPS_DICT.get(trap_item_id)
|
||||
if not trap:
|
||||
raise ValueError("未知陷阱物品")
|
||||
|
||||
# 构建陷阱配置(JSON格式)
|
||||
trap_config = json.dumps({
|
||||
"item_id": trap.item_id,
|
||||
"display_name": trap.display_name,
|
||||
"tier": trap.tier,
|
||||
})
|
||||
|
||||
cursor = self._db.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE garden_plots SET trap_item_id = ?, trap_config = ?, trap_durability = ?
|
||||
WHERE user_id = ? AND plot_index = ?
|
||||
""",
|
||||
(trap_item_id, trap_config, trap.durability, user_id, plot_index),
|
||||
)
|
||||
self._db.conn.commit()
|
||||
|
||||
def remove_trap(self, *, user_id: int, plot_index: int) -> None:
|
||||
"""移除地块上的陷阱"""
|
||||
plot = self.get_plot(user_id, plot_index)
|
||||
if not plot:
|
||||
raise ValueError("目标地块不存在")
|
||||
|
||||
cursor = self._db.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE garden_plots SET trap_item_id = NULL, trap_config = NULL, trap_durability = 0
|
||||
WHERE user_id = ? AND plot_index = ?
|
||||
""",
|
||||
(user_id, plot_index),
|
||||
)
|
||||
self._db.conn.commit()
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
Reference in New Issue
Block a user