diff --git a/__init__.py b/__init__.py index 77ba75b..9c139ae 100644 --- a/__init__.py +++ b/__init__.py @@ -58,6 +58,12 @@ __plugin_meta__ = PluginMetadata( help="金币兑换农场币的倍数 默认值为: 2倍", default_value="2", ), + RegisterConfig( + key="点券兑换倍数", + value="2000", + help="农场币兑换点券的倍数 比例为2000:1", + default_value="2000", + ), RegisterConfig( key="手续费", value="0.2", diff --git a/command.py b/command.py index 229f673..9e83925 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="购买农场币"), @@ -92,6 +94,8 @@ diuse_farm = on_alconna( Subcommand("change-name", Args["name?", str], help_text="更改农场名"), Subcommand("sign-in", help_text="农场签到"), Subcommand("admin-up", Args["num?", int], help_text="农场下阶段"), + Subcommand("point-to-vipPoint", Args["num?", int], help_text="点券兑换"), + Subcommand("my-vipPoint", help_text="我的点券"), ), priority=5, block=True, @@ -328,6 +332,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 +427,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="我的农场", @@ -606,3 +648,48 @@ async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)): return await g_pDBService.userSoil.nextPhase(uid, num.result) + + +diuse_farm.shortcut( + "点券兑换(.*?)", + command="我的农场", + arguments=["point-to-vipPoint"], + prefix=True, +) + + +@diuse_farm.assign("point-to-vipPoint") +async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)): + if num.result <= 0: + await MessageUtils.build_message("请在指令后跟需要购买点券的数量").finish( + reply_to=True + ) + + uid = str(session.user.id) + + if not await g_pToolManager.isRegisteredByUid(uid): + return + + result = await g_pFarmManager.pointToVipPointByUid(uid, num.result) + await MessageUtils.build_message(result).send(reply_to=True) + + +diuse_farm.shortcut( + "我的点券", + command="我的农场", + arguments=["my-vipPoint"], + prefix=True, +) + + +@diuse_farm.assign("my-vipPoint") +async def _(session: Uninfo): + uid = str(session.user.id) + vipPoint = await g_pDBService.user.getUserVipPointByUid(uid) + + if not await g_pToolManager.isRegisteredByUid(uid): + return + + await MessageUtils.build_message( + g_sTranslation["basic"]["vipPoint"].format(vipPoint=vipPoint) + ).send(reply_to=True) diff --git a/config.py b/config.py index c878887..091c386 100644 --- a/config.py +++ b/config.py @@ -34,6 +34,7 @@ g_sTranslation = { "basic": { "notFarm": "尚未开通农场,快at我发送 开通农场 开通吧 🌱🚜", "point": "你的当前农场币为: {point} 🌾💰", + "vipPoint": "你的当前点券为: {vipPoint} 🌾💰", }, "register": { "success": "✅ 农场开通成功!\n💼 初始资金:{point}农场币 🥳🎉", @@ -45,7 +46,9 @@ g_sTranslation = { "notNum": "❗️ 请输入购买数量!", "noLevel": "🔒 你的等级不够哦,努努力吧 💪", "noPoint": "💰 你的农场币不够哦~ 快速速氪金吧!💸", + "noVipPoint": "💰 你的点券不够哦~ 快速速氪金吧!💸", "success": "✅ 成功购买{name},花费{total}农场币,剩余{point}农场币 🌾", + "vipSuccess": "✅ 成功购买{name},花费{total}点券,剩余{point}点券 🌾", "errorSql": "❌ 购买失败,执行数据库错误!🛑", "error": "❌ 购买出错!请检查需购买的种子名称!🔍", }, @@ -121,4 +124,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..b8adf95 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: """偷菜 @@ -1106,5 +1148,54 @@ class CFarmManager: text=g_sTranslation["soilInfo"][soilLevelText], ) + @classmethod + async def pointToVipPointByUid(cls, uid: str, num: int) -> str: + """点券兑换 + num:用户传参,即将兑换的点券 + pro:兑换倍数;兑换倍数乘以num即为需要消耗的农场币 + Args: + uid (str): 用户Uid + num (int): 兑换点券数量 + Returns: + str: 返回结果 + 兑换比例在配置文件中配置 + 目前配置文件中默认是20倍 + 100点券需要20000农场币 + 赠送点券规则: + 小于2000点券:0 + 2000-5000点券:100 + 5000-50000点券:280 + 大于50000点券:3000 + """ + if num < 100: + return "点券兑换数量必须大于等于100" + + pro = int(Config.get_config("zhenxun_plugin_farm", "点券兑换倍数")) + pro *= num + + point = await g_pDBService.user.getUserPointByUid(uid) + if point < pro: + return f"你的农场币不足,当前农场币为{point},兑换还需要{pro - point}农场币" + + p = await g_pDBService.user.getUserVipPointByUid(uid) + + giftPoints: int + if num < 2000: + giftPoints = 0 + elif num < 5000: + giftPoints = 100 + elif num < 50000: + giftPoints = 280 + else: + giftPoints = 3000 + + number = num + p + giftPoints + await g_pDBService.user.updateUserVipPointByUid(uid, int(number)) + + point -= pro + await g_pDBService.user.updateUserPointByUid(uid, int(point)) + + return f"兑换{num}点券成功,当前点券:{number},赠送点券:{giftPoints},当前农场币:{point}" + g_pFarmManager = CFarmManager() 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..e1a8b00 100644 --- a/farm/shop.py +++ b/farm/shop.py @@ -1,6 +1,5 @@ import math -from zhenxun.services.log import logger from zhenxun.utils.image_utils import ImageTemplate from ..config import g_sResourcePath, g_sTranslation @@ -33,7 +32,8 @@ class CShopManager: columnName = [ "-", "种子名称", - "种子单价", + "农场币", + "点券", "解锁等级", "果实单价", "收获经验", @@ -77,7 +77,8 @@ class CShopManager: [ icon, plant["name"], # 种子名称 - plant["buy"], # 种子单价 + plant["buy"], # 农场币种子单价 + plant["vipBuy"], # 点券种子单价 plant["level"], # 解锁等级 plant["price"], # 果实单价 plant["experience"], # 收获经验 @@ -125,21 +126,32 @@ class CShopManager: if level[0] < int(plantInfo["level"]): return g_sTranslation["buySeed"]["noLevel"] - point = await g_pDBService.user.getUserPointByUid(uid) - total = int(plantInfo["buy"]) * num - + """ logger.debug( f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}" ) - - if point < total: - return g_sTranslation["buySeed"]["noPoint"] + """ + if plantInfo["isVip"] == 1: + vipPoint = await g_pDBService.user.getUserVipPointByUid(uid) + total = int(plantInfo["vipBuy"]) * num + if vipPoint < total: + return g_sTranslation["buySeed"]["noVipPoint"] + await g_pDBService.user.updateUserVipPointByUid(uid, vipPoint - total) else: + point = await g_pDBService.user.getUserPointByUid(uid) + total = int(plantInfo["buy"]) * num + if point < total: + return g_sTranslation["buySeed"]["noPoint"] await g_pDBService.user.updateUserPointByUid(uid, point - total) - if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num): - return g_sTranslation["buySeed"]["errorSql"] + if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num): + return g_sTranslation["buySeed"]["errorSql"] + if plantInfo["isVip"] == 1: + return g_sTranslation["buySeed"]["vipSuccess"].format( + name=name, total=total, point=vipPoint - total + ) + else: return g_sTranslation["buySeed"]["success"].format( name=name, total=total, point=point - total ) @@ -167,6 +179,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/db/plant.db b/resource/db/plant.db index a198f31..7597ade 100644 Binary files a/resource/db/plant.db and b/resource/db/plant.db differ 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 }}
+
+
+ +