新增菜园陷阱

This commit is contained in:
2025-11-15 17:06:12 +08:00
parent 3a5c0b2eda
commit 5d08fa0820
7 changed files with 660 additions and 3 deletions

View 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: 在菜园概览中显示陷阱信息及剩余耐久度
- 更改:实现了陷阱耐久度系统,每次触发后减少耐久度,耐久度归零时自动移除陷阱
- 原因:满足用户需求,让陷阱具有使用次数限制
- 阻碍因素:无
- 状态:未确认
# 最终审查

View File

@@ -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",

View File

@@ -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}"

View 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"]

View 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"]

View File

@@ -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

View File

@@ -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