diff --git a/command.py b/command.py index 229f673..30948ff 100644 --- a/command.py +++ b/command.py @@ -85,6 +85,8 @@ diuse_farm = on_alconna( Subcommand("harvest", help_text="收获"), Subcommand("eradicate", help_text="铲除"), Subcommand("my-plant", help_text="我的作物"), + Subcommand("lock-plant", Args["name?", str], help_text="作物加锁"), + Subcommand("unlock-plant", Args["name?", str], help_text="作物解锁"), Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"), Subcommand("stealing", Args["target?", At], help_text="偷菜"), Subcommand("buy-point", Args["num?", int], help_text="购买农场币"), @@ -328,6 +330,65 @@ async def _(session: Uninfo): await MessageUtils.build_message(result).send(reply_to=True) +diuse_farm.shortcut( + "作物加锁(?P)", + command="我的农场", + arguments=["lock-plant", "{name}"], + prefix=True, +) + + +@diuse_farm.assign("lock-plant") +async def _(session: Uninfo, name: Match[str]): + uid = str(session.user.id) + + if not await g_pToolManager.isRegisteredByUid(uid): + return + + result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 1) + await MessageUtils.build_message(result).send(reply_to=True) + + +diuse_farm.shortcut( + "作物解锁(?P)", + command="我的农场", + arguments=["unlock-plant", "{name}"], + prefix=True, +) + + +@diuse_farm.assign("unlock-plant") +async def _(session: Uninfo, name: Match[str]): + uid = str(session.user.id) + + if not await g_pToolManager.isRegisteredByUid(uid): + return + + result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 0) + await MessageUtils.build_message(result).send(reply_to=True) + + +diuse_farm.shortcut( + "出售作物(?P.*?)", + command="我的农场", + arguments=["sell-plant", "{name}"], + prefix=True, +) + + +@diuse_farm.assign("sell-plant") +async def _( + session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1) +): + uid = str(session.user.id) + + if not await g_pToolManager.isRegisteredByUid(uid): + return + + result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result) + await MessageUtils.build_message(result).send(reply_to=True) + + reclamation = on_alconna( Alconna("开垦"), priority=5, @@ -364,27 +425,6 @@ async def _(session: Uninfo): await MessageUtils.build_message(res).send(reply_to=True) -diuse_farm.shortcut( - "出售作物(?P.*?)", - command="我的农场", - arguments=["sell-plant", "{name}"], - prefix=True, -) - - -@diuse_farm.assign("sell-plant") -async def _( - session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1) -): - uid = str(session.user.id) - - if not await g_pToolManager.isRegisteredByUid(uid): - return - - result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result) - await MessageUtils.build_message(result).send(reply_to=True) - - diuse_farm.shortcut( "偷菜", command="我的农场", diff --git a/config.py b/config.py index c878887..d2f73a9 100644 --- a/config.py +++ b/config.py @@ -121,4 +121,10 @@ g_sTranslation = { "aquamarine": "增产+32% 经验+32% 时间-28% 幸运+1%", "blackcrystal": "增产+32% 经验+40% 时间-28% 幸运+2%", }, + "lockPlant": { + "noPlant": "很抱歉,你的作物仓库中没有该作物", + "lockPlant": "加锁成功,现在{name}不会被一键售卖了", + "unlockPlant": "解锁成功,现在{name}会被一键售卖了", + "error": "未知错误", + }, } diff --git a/database/userPlant.py b/database/userPlant.py index 4d403d8..7f8492e 100644 --- a/database/userPlant.py +++ b/database/userPlant.py @@ -10,6 +10,7 @@ class CUserPlantDB(CSqlManager): "uid": "TEXT NOT NULL", # 用户Uid "plant": "TEXT NOT NULL", # 作物名称 "count": "INTEGER NOT NULL DEFAULT 0", # 数量 + "isLock": "INTEGER NOT NULL DEFAULT 0", # 是否上锁 0=没有,非0=有 "PRIMARY KEY": "(uid, plant)", } @@ -91,6 +92,27 @@ class CUserPlantDB(CSqlManager): logger.warning("getUserPlantByName 查询失败!", e=e) return None + @classmethod + async def checkUserPlantByName(cls, uid: str, plant: str) -> bool: + """根据作物名称判断用户作物仓库是否存在该作物 + + Args: + uid (str): 用户uid + plant (str): 作物名称 + + Returns: + bool: 是否存在 + """ + try: + async with cls.m_pDB.execute( + "SELECT * FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant) + ) as cursor: + row = await cursor.fetchone() + return True if row else False + except Exception as e: + logger.warning("checkUserPlantByName 查询失败!", e=e) + return False + @classmethod async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool: """更新 userPlant 表中某个作物的数量 @@ -114,7 +136,51 @@ class CUserPlantDB(CSqlManager): ) return True except Exception as e: - logger.warning("updateUserPlantByName失败!", e=e) + logger.warning("updateUserPlantByName 失败!", e=e) + return False + + @classmethod + async def lockUserPlantByName(cls, uid: str, plant: str, lock: int) -> bool: + """给作物加锁,防止一键售卖 + + Args: + uid (str): 用户uid + plant (str): 作物名称 + lock (int): 0为解锁,非0均为加锁 + + Returns: + bool: 是否加锁成功 + """ + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE userPlant SET isLock = ? WHERE uid = ? AND plant = ?", + (lock, uid, plant), + ) + return True + except Exception as e: + logger.warning("lockUserPlantByName 失败!", e=e) + return False + + @classmethod + async def checkPlantLockByName(cls, uid: str, plant: str) -> bool: + """根据作物名称判断是否加锁 + + Args: + uid (str): 用户uid + plant (str): 作物名称 + + Returns: + bool: 是否加锁 + """ + try: + async with cls.m_pDB.execute( + "SELECT isLock FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant) + ) as cursor: + row = await cursor.fetchone() + return row[0] > 0 if row else False + except Exception as e: + logger.warning("checkUserPlantByName 查询失败!", e=e) return False @classmethod diff --git a/database/userSign.py b/database/userSign.py index ffe2abe..4046572 100644 --- a/database/userSign.py +++ b/database/userSign.py @@ -165,7 +165,6 @@ class CUserSignDB(CSqlManager): if row["currentMonth"] == currentMonth else 1 ) - totalSignDays = row["totalSignDays"] lastDate = row["lastSignDate"] prevDate = ( g_pToolManager.dateTime().strptime(signDate, "%Y-%m-%d") @@ -200,7 +199,7 @@ class CUserSignDB(CSqlManager): ), ) else: - totalSignDays = 1 + monthSignDays = 1 await cls.m_pDB.execute( """ INSERT INTO userSignSummary @@ -211,7 +210,7 @@ class CUserSignDB(CSqlManager): uid, 1, currentMonth, - 1, + monthSignDays, signDate, 1, 1 if isSupplement else 0, @@ -219,15 +218,13 @@ class CUserSignDB(CSqlManager): ) # 计算累签奖励 - reward = g_pJsonManager.m_pSign["continuou"].get(f"{totalSignDays}", None) - + reward = g_pJsonManager.m_pSign["continuou"].get(f"{monthSignDays}", None) if reward: point += reward.get("point", 0) exp += reward.get("exp", 0) vipPoint = reward.get("vipPoint", 0) plant = reward.get("plant", {}) - if plant: for key, value in plant.items(): await g_pDBService.userSeed.addUserSeedByUid(uid, key, value) diff --git a/database/userSoil.py b/database/userSoil.py index 48bce09..ad16ee6 100644 --- a/database/userSoil.py +++ b/database/userSoil.py @@ -24,7 +24,7 @@ class CUserSoilDB(CSqlManager): "weedStatus": "INTEGER DEFAULT 0", # 杂草状态 0=无杂草,1=有杂草 "waterStatus": "INTEGER DEFAULT 0", # 缺水状态 0=不缺水,1=缺水 "harvestCount": "INTEGER DEFAULT 0", # 收获次数 - "isSoilPlanted": "INTEGER DEFAULT NULL", # 是否种植作物 + "isSoilPlanted": "INTEGER DEFAULT NULL", # 是否种植作物 0=没有,1=有 "PRIMARY KEY": "(uid, soilIndex)", } diff --git a/farm/farm.py b/farm/farm.py index f88e110..cef620a 100644 --- a/farm/farm.py +++ b/farm/farm.py @@ -159,6 +159,7 @@ class CFarmManager: if image: avatar = BuildImage(background=image) + await avatar.resize(0, 140, 150) await img.paste(avatar, (125, 85)) # 头像框 @@ -732,7 +733,15 @@ class CFarmManager: bytes: 返回图片 """ data_list = [] - column_name = ["-", "作物名称", "数量", "单价", "总价", "是否可以上架交易行"] + column_name = [ + "-", + "作物名称", + "数量", + "单价", + "总价", + "是否上锁", + "是否可以上架交易行", + ] plant = await g_pDBService.userPlant.getUserPlantByUid(uid) @@ -763,7 +772,15 @@ class CFarmManager: number = int(count) * plantInfo["price"] - data_list.append([icon, name, count, plantInfo["price"], number, sell]) + isLock = await g_pDBService.userPlant.checkPlantLockByName(uid, name) + if isLock: + lock = "上锁" + else: + lock = "未上锁" + + data_list.append( + [icon, name, count, plantInfo["price"], number, lock, sell] + ) result = await ImageTemplate.table_page( "作物仓库", @@ -774,6 +791,31 @@ class CFarmManager: return result.pic2bytes() + @classmethod + async def lockUserPlantByUid(cls, uid: str, name: str, lock: int) -> str: + """加/解 锁用户作物 + + Args: + uid (str): 用户uid + name (str): 作物名称 + lock (int): 0=解锁 非0=加锁 + + Returns: + str: 返回 + """ + # 先判断该用户仓库是否有该作物 + if not await g_pDBService.userPlant.checkUserPlantByName(uid, name): + return g_sTranslation["lockPlant"]["noPlant"] + + # 更改加锁状态 + if await g_pDBService.userPlant.lockUserPlantByName(uid, name, lock): + if lock == 0: + return g_sTranslation["lockPlant"]["unlockPlant"].format(name=name) + else: + return g_sTranslation["lockPlant"]["lockPlant"].format(name=name) + else: + return g_sTranslation["lockPlant"]["error"] + @classmethod async def stealing(cls, uid: str, target: str) -> str: """偷菜 diff --git a/farm/help.py b/farm/help.py new file mode 100644 index 0000000..8094376 --- /dev/null +++ b/farm/help.py @@ -0,0 +1,117 @@ +from pathlib import Path + +from jinja2 import Template +from playwright.async_api import async_playwright + +from zhenxun.configs.path_config import DATA_PATH +from zhenxun.services.log import logger + +from ..config import g_sResourcePath + + +def rendeerHtmlToFile(path: Path | str, context: dict, output: Path | str) -> None: + """ + 使用 Jinja2 渲染 HTML 模板并保存到指定文件,会自动创建父目录 + + Args: + path (str): 模板 HTML 路径 + context (dict): 用于渲染的上下文字典 + output (str): 输出 HTML 文件路径 + """ + templatePath = str(path) + outputPath = str(output) + + templateStr = Path(templatePath).read_text(encoding="utf-8") + template = Template(templateStr) + rendered = template.render(**context) + + # 自动创建目录 + Path(outputPath).parent.mkdir(parents=True, exist_ok=True) + + Path(outputPath).write_text(rendered, encoding="utf-8") + + +async def screenshotHtmlToBytes(path: str) -> bytes: + """ + 使用 Playwright 截图本地 HTML 文件并返回 PNG 图片字节数据 + + Args: + path (str): 本地 HTML 文件路径 + + Returns: + bytes: PNG 图片的原始字节内容 + """ + async with async_playwright() as p: + browser = await p.chromium.launch() + page = await browser.new_page() + file_url = Path(path).resolve().as_uri() + await page.goto(file_url) + image_bytes = await page.screenshot(full_page=True) + await browser.close() + + return image_bytes + + +async def screenshotSave(path: str, save: str) -> None: + """ + 使用 Playwright 渲染本地 HTML 并将截图保存到指定路径 + + Args: + path (str): HTML 文件路径 + save (str): PNG 保存路径(如 output/image.png) + """ + async with async_playwright() as p: + browser = await p.chromium.launch() + page = await browser.new_page() + + file_url = Path(path).resolve().as_uri() + await page.goto(file_url) + + # 确保保存目录存在 + Path(save).parent.mkdir(parents=True, exist_ok=True) + + # 截图并保存到本地文件 + await page.screenshot(path=save, full_page=True) + await browser.close() + + +async def createHelpImage() -> bool: + templatePath = g_sResourcePath / "html/help.html" + outputPath = DATA_PATH / "farm_res/html/help.html" + + context = { + "title": "功能指令总览", + "data": [ + { + "command": "开通农场", + "description": "首次进入游戏开通农场", + "tip": "", + }, + { + "command": "购买种子", + "description": "从商店中购买可用种子", + "tip": "", + }, + {"command": "播种", "description": "将种子种入土地中", "tip": "先开垦土地"}, + { + "command": "收获", + "description": "收获成熟作物获得收益", + "tip": "", + }, + { + "command": "偷菜", + "description": "从好友农场中偷取成熟作物", + "tip": "1", + }, + ], + } + + try: + rendeerHtmlToFile(templatePath, context, outputPath) + + image_bytes = await screenshot_html_to_bytes(html_output_path) + except Exception as e: + logger.warning("绘制农场帮助菜单失败", e=e) + return False + + return True diff --git a/farm/shop.py b/farm/shop.py index 4f1bb10..812b1e2 100644 --- a/farm/shop.py +++ b/farm/shop.py @@ -167,6 +167,13 @@ class CShopManager: if name == "": for plantName, count in plant.items(): + isLock = await g_pDBService.userPlant.checkPlantLockByName( + uid, plantName + ) + + if isLock: + continue + plantInfo = await g_pDBService.plant.getPlantByName(plantName) if not plantInfo: continue diff --git a/resource/html/help.html b/resource/html/help.html new file mode 100644 index 0000000..2c6dc6d --- /dev/null +++ b/resource/html/help.html @@ -0,0 +1,75 @@ + + + + + {{ title }} + + + +
+
+
{{ title }}
+ + + + + + + + + + {% for entry in data %} + + + + + + {% endfor %} + +
指令描述Tip
{{ entry.command }}{{ entry.description }}{{ entry.tip }}
+
+
+ +