diff --git a/README.md b/README.md index e1b256d..57f25c7 100644 --- a/README.md +++ b/README.md @@ -77,31 +77,30 @@ | 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% | | 更改农场名 [新的农场名] | 改名 | 农场名称无法存储特殊字符 | | 农场签到 | 签到 | 需要注意,该项会从服务器拉取签到数据 | +| 土地升级 [地块ID] | 将土地升级,带来收益提升 | 如果土地升级时,土地有播种作物,那么将直接成熟 | --- ## 更新日志[(详细)](./log/log.md): 用户方面 --- -- 新增种子商店筛选功能(如果没有BUG的话后续我的种子、我的作物等也会加入筛选功能 -- 新增签到功能(测试 -- 新增农场详述功能,能通过该功能更加详细的观看农场数据 -- 修复了迁移旧数据库无法正常迁移的BUG -- 修复了偷菜会导致偷自己的BUG -- 修正了作物阶段绘制不正确的BUG +- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧 +- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件 +- 定时更新签到文件、作物资源从00:30调整至04:30 +- 修正了部分土地资源错误的情况 +- 修正了部分文本信息错误的情况 代码方面 --- -- 修正了多阶段作物成长素材计算逻辑 -- 修正了作物数据库字段错乱的问题 -- 作物新增offset字段,用于以后偏移坐标和大小(尚未启用,该模式有商议 ---- +- 修正部分事件连接机制 +- 修正网络请求端口 ## 待办事宜 `Todo` 列表 - [x] 完善我的农场图片,例如左上角显示用户数据 - [ ] 完善升级数据、作物数据、作物图片 - [x] 签到功能 +- [x] 在线更新作物信息 - [ ] 添加渔场功能 - [ ] 增加活动、交易行功能 - [ ] 增加交易行总行功能 diff --git a/__init__.py b/__init__.py index c7bb7ee..5e34def 100644 --- a/__init__.py +++ b/__init__.py @@ -9,6 +9,7 @@ from zhenxun.utils.message import MessageUtils from .command import diuse_farm, diuse_register, reclamation from .database.database import g_pSqlManager from .dbService import g_pDBService +from .event.event import g_pEventManager from .farm.farm import g_pFarmManager from .farm.shop import g_pShopManager from .json import g_pJsonManager @@ -22,6 +23,7 @@ __plugin_meta__ = PluginMetadata( 指令: at 开通农场 我的农场 + 农场详述 我的农场币 种子商店 [筛选关键字] [页数] or [页数] 购买种子 [作物/种子名称] [数量] @@ -36,10 +38,11 @@ __plugin_meta__ = PluginMetadata( 购买农场币 [数量] 数量为消耗金币的数量 更改农场名 [新农场名] 农场签到 + 土地升级 [地块ID](通过农场详述获取) """.strip(), extra=PluginExtraData( author="Art_Sakura", - version="1.4.3", + version="1.5.0", commands=[Command(command="我的农场")], menu_type="群内小游戏", configs=[ @@ -84,6 +87,9 @@ async def start(): await g_pDBService.init() + # 检查作物文件是否缺失 or 更新 + await g_pRequestManager.initPlantDBFile() + # 析构函数 @driver.on_shutdown @@ -93,9 +99,10 @@ async def shutdown(): await g_pDBService.cleanup() -@scheduler.scheduled_job(trigger="cron", hour=0, minute=30, id="signInFile") +@scheduler.scheduled_job(trigger="cron", hour=4, minute=30, id="signInFile") async def signInFile(): try: await g_pJsonManager.initSignInFile() - except: - logger.info("农场签到文件下载失败!") + await g_pRequestManager.initPlantDBFile() + except Exception as e: + logger.error("农场定时检查出错", e=e) diff --git a/command.py b/command.py index 96c33bc..e600dba 100644 --- a/command.py +++ b/command.py @@ -58,7 +58,7 @@ async def handle_register(session: Uninfo): ) msg = ( - g_sTranslation["register"]["success"] + g_sTranslation["register"]["success"].format(point=500) if success else g_sTranslation["register"]["error"] ) @@ -549,6 +549,41 @@ async def _(session: Uninfo): # await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send(reply_to=True) +soil_upgrade = on_alconna( + Alconna("土地升级", Args["index", int]), + priority=5, + block=True, +) + + +@soil_upgrade.handle() +async def _(session: Uninfo, index: Query[int] = AlconnaQuery("num", 1)): + uid = str(session.user.id) + + if not await g_pToolManager.isRegisteredByUid(uid): + return + + condition = await g_pFarmManager.soilUpgradeCondition(uid, index.result) + + await MessageUtils.build_message(condition).send(reply_to=True) + + @waiter(waits=["message"], keep_session=True) + async def check(event: Event): + return event.get_plaintext() + + resp = await check.wait(timeout=60) + if resp is None: + await MessageUtils.build_message(g_sTranslation["soilInfo"]["timeOut"]).send( + reply_to=True + ) + return + if not resp == "是": + return + + res = await g_pFarmManager.soilUpgrade(uid, index.result) + await MessageUtils.build_message(res).send(reply_to=True) + + diuse_farm.shortcut( "农场下阶段(.*?)", command="我的农场", diff --git a/config.py b/config.py index aed0345..c878887 100644 --- a/config.py +++ b/config.py @@ -26,6 +26,9 @@ g_sConfigPath = Path(__file__).resolve().parent / "config" # 农场签到文件路径 g_sSignInPath = g_sConfigPath / "sign_in.json" +# 土地等级上限 +g_iSoilLevelMax = 3 + # 农场同一文本 g_sTranslation = { "basic": { @@ -106,4 +109,16 @@ g_sTranslation = { "error": "❗️ 签到功能异常!", "error1": "❌ 签到失败!未知错误 💔", }, + "soilInfo": { + "success": "土地成功升级至{name},效果为:{text}", + "timeOut": "等待土地升级回复超时,请重试", + "error": "土地信息尚未查询到", + "error1": "该土地已经升至满级啦~", + "red": "增产+10%", + "black": "增产+20% 时间-20%", + "gold": "增产+28% 经验+28% 时间-20%", + "amethyst": "增产+30% 经验+30% 时间-25% 幸运+1%", + "aquamarine": "增产+32% 经验+32% 时间-28% 幸运+1%", + "blackcrystal": "增产+32% 经验+40% 时间-28% 幸运+2%", + }, } diff --git a/config/soil.json b/config/soil.json index b2f2f20..c828557 100644 --- a/config/soil.json +++ b/config/soil.json @@ -153,5 +153,104 @@ "x": 1451, "y": 1072 } + }, + "upgrade": + { + "red": [ + { "level": 28, "point": 200000, "vipPoint": 0, "item":{}}, + { "level": 29, "point": 220000, "vipPoint": 0, "item":{}}, + { "level": 30, "point": 240000, "vipPoint": 0, "item":{}}, + { "level": 31, "point": 260000, "vipPoint": 0, "item":{}}, + { "level": 32, "point": 290000, "vipPoint": 0, "item":{}}, + { "level": 33, "point": 320000, "vipPoint": 0, "item":{}}, + { "level": 34, "point": 350000, "vipPoint": 0, "item":{}}, + { "level": 35, "point": 380000, "vipPoint": 0, "item":{}}, + { "level": 36, "point": 410000, "vipPoint": 0, "item":{}}, + { "level": 37, "point": 440000, "vipPoint": 0, "item":{}}, + { "level": 38, "point": 480000, "vipPoint": 0, "item":{}}, + { "level": 39, "point": 520000, "vipPoint": 0, "item":{}}, + { "level": 40, "point": 560000, "vipPoint": 0, "item":{}}, + { "level": 41, "point": 600000, "vipPoint": 0, "item":{}}, + { "level": 42, "point": 650000, "vipPoint": 0, "item":{}}, + { "level": 43, "point": 700000, "vipPoint": 0, "item":{}}, + { "level": 44, "point": 770000, "vipPoint": 0, "item":{}}, + { "level": 45, "point": 900000, "vipPoint": 0, "item":{}}, + { "level": 47, "point": 1500000, "vipPoint": 0, "item":{}}, + { "level": 49, "point": 2000000, "vipPoint": 0, "item":{}}, + { "level": 53, "point": 4000000, "vipPoint": 0, "item":{}}, + { "level": 53, "point": 4000000, "vipPoint": 0, "item":{}}, + { "level": 55, "point": 5500000, "vipPoint": 0, "item":{}}, + { "level": 57, "point": 6800000, "vipPoint": 0, "item":{}}, + { "level": 60, "point": 10000000, "vipPoint": 0, "item":{}}, + { "level": 64, "point": 15000000, "vipPoint": 0, "item":{}}, + { "level": 68, "point": 20000000, "vipPoint": 0, "item":{}}, + { "level": 73, "point": 30000000, "vipPoint": 0, "item":{}}, + { "level": 78, "point": 50000000, "vipPoint": 0, "item":{}}, + { "level": 83, "point": 80000000, "vipPoint": 0, "item":{}} + ], + "black": [ + { "level": 40, "point": 500000, "vipPoint": 0, "item":{}}, + { "level": 41, "point": 600000, "vipPoint": 0, "item":{}}, + { "level": 42, "point": 700000, "vipPoint": 0, "item":{}}, + { "level": 43, "point": 800000, "vipPoint": 0, "item":{}}, + { "level": 44, "point": 900000, "vipPoint": 0, "item":{}}, + { "level": 45, "point": 1000000, "vipPoint": 0, "item":{}}, + { "level": 46, "point": 2000000, "vipPoint": 0, "item":{}}, + { "level": 47, "point": 2200000, "vipPoint": 0, "item":{}}, + { "level": 48, "point": 2400000, "vipPoint": 0, "item":{}}, + { "level": 49, "point": 2600000, "vipPoint": 0, "item":{}}, + { "level": 50, "point": 2800000, "vipPoint": 0, "item":{}}, + { "level": 51, "point": 3000000, "vipPoint": 0, "item":{}}, + { "level": 52, "point": 4000000, "vipPoint": 0, "item":{}}, + { "level": 53, "point": 4200000, "vipPoint": 0, "item":{}}, + { "level": 54, "point": 4400000, "vipPoint": 0, "item":{}}, + { "level": 55, "point": 4600000, "vipPoint": 0, "item":{}}, + { "level": 56, "point": 4800000, "vipPoint": 0, "item":{}}, + { "level": 57, "point": 5000000, "vipPoint": 0, "item":{}}, + { "level": 59, "point": 6000000, "vipPoint": 0, "item":{}}, + { "level": 61, "point": 6200000, "vipPoint": 0, "item":{}}, + { "level": 65, "point": 6600000, "vipPoint": 0, "item":{}}, + { "level": 65, "point": 6600000, "vipPoint": 0, "item":{}}, + { "level": 67, "point": 6800000, "vipPoint": 0, "item":{}}, + { "level": 69, "point": 7000000, "vipPoint": 0, "item":{}}, + { "level": 72, "point": 10000000, "vipPoint": 0, "item":{}}, + { "level": 76, "point": 16000000, "vipPoint": 0, "item":{}}, + { "level": 80, "point": 27000000, "vipPoint": 0, "item":{}}, + { "level": 85, "point": 43000000, "vipPoint": 0, "item":{}}, + { "level": 90, "point": 65000000, "vipPoint": 0, "item":{}}, + { "level": 95, "point": 93000000, "vipPoint": 0, "item":{}} + ], + "gold": [ + { "level": 58, "point": 4000000, "vipPoint": 0, "item":{}}, + { "level": 59, "point": 4200000, "vipPoint": 0, "item":{}}, + { "level": 60, "point": 4400000, "vipPoint": 0, "item":{}}, + { "level": 61, "point": 4600000, "vipPoint": 0, "item":{}}, + { "level": 62, "point": 4800000, "vipPoint": 0, "item":{}}, + { "level": 63, "point": 5000000, "vipPoint": 0, "item":{}}, + { "level": 64, "point": 5200000, "vipPoint": 0, "item":{}}, + { "level": 65, "point": 5400000, "vipPoint": 0, "item":{}}, + { "level": 66, "point": 5600000, "vipPoint": 0, "item":{}}, + { "level": 67, "point": 5800000, "vipPoint": 0, "item":{}}, + { "level": 68, "point": 6000000, "vipPoint": 0, "item":{}}, + { "level": 69, "point": 6200000, "vipPoint": 0, "item":{}}, + { "level": 70, "point": 6600000, "vipPoint": 0, "item":{}}, + { "level": 71, "point": 7000000, "vipPoint": 0, "item":{}}, + { "level": 72, "point": 7400000, "vipPoint": 0, "item":{}}, + { "level": 73, "point": 7800000, "vipPoint": 0, "item":{}}, + { "level": 74, "point": 8200000, "vipPoint": 0, "item":{}}, + { "level": 75, "point": 8600000, "vipPoint": 0, "item":{}}, + { "level": 77, "point": 9500000, "vipPoint": 0, "item":{}}, + { "level": 79, "point": 10400000, "vipPoint": 0, "item":{}}, + { "level": 83, "point": 12200000, "vipPoint": 0, "item":{}}, + { "level": 83, "point": 12200000, "vipPoint": 0, "item":{}}, + { "level": 85, "point": 13100000, "vipPoint": 0, "item":{}}, + { "level": 87, "point": 14000000, "vipPoint": 0, "item":{}}, + { "level": 90, "point": 16000000, "vipPoint": 0, "item":{}}, + { "level": 94, "point": 20000000, "vipPoint": 0, "item":{}}, + { "level": 98, "point": 28000000, "vipPoint": 0, "item":{}}, + { "level": 103, "point": 44000000, "vipPoint": 0, "item":{}}, + { "level": 108, "point": 76000000, "vipPoint": 0, "item":{}}, + { "level": 113, "point": 1080000000, "vipPoint": 0, "item":{}} + ] } } diff --git a/database/database.py b/database/database.py index 818eef8..6b1127f 100644 --- a/database/database.py +++ b/database/database.py @@ -1,7 +1,7 @@ -import os -import re from contextlib import asynccontextmanager +import os from pathlib import Path +import re import aiosqlite @@ -103,9 +103,14 @@ class CSqlManager: commonCols = [k for k in desired if k in existing] if commonCols: colsStr = ", ".join(f'"{c}"' for c in commonCols) - await cls.m_pDB.execute( - f'INSERT INTO "{tmpTable}" ({colsStr}) SELECT {colsStr} FROM "{tableName}";' + + sql = ( + f'INSERT INTO "{tmpTable}" ({colsStr}) ' + f"SELECT {colsStr} " + f'FROM "{tableName}";' ) + + await cls.m_pDB.execute(sql) await cls.m_pDB.execute(f'DROP TABLE "{tableName}";') await cls.m_pDB.execute( f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";' diff --git a/database/plant.py b/database/plant.py index c28a23a..a9ebc6f 100644 --- a/database/plant.py +++ b/database/plant.py @@ -1,11 +1,13 @@ -import os from contextlib import asynccontextmanager +import os import aiosqlite +from zhenxun.configs.config import Config from zhenxun.services.log import logger -from ..config import g_bIsDebug, g_sPlantPath +from ..config import g_bIsDebug, g_sPlantPath, g_sResourcePath +from ..request import g_pRequestManager class CPlantManager: @@ -247,3 +249,45 @@ class CPlantManager: except Exception as e: logger.warning("查询所有作物失败", e=e) return [] + + @classmethod + async def downloadPlant(cls) -> bool: + """遍历所有作物,下载各阶段图片及icon文件到指定文件夹 + + Returns: + bool: 全部下载完成返回True,如有失败返回False + """ + success = True + baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") + + baseUrl = baseUrl.rstrip("/") + ":8998/file" + try: + plants = await cls.listPlants() + for plant in plants: + name = plant["name"] + phaseCount = await cls.getPlantPhaseNumberByName(name) + saveDir = os.path.join(g_sResourcePath, "plant", name) + begin = 0 if plant["general"] == 0 else 1 + + for idx in range(begin, phaseCount + 1): + fileName = f"{idx}.png" + fullPath = os.path.join(saveDir, fileName) + + if os.path.exists(fullPath): + continue + + url = f"{baseUrl}/{name}/{idx}.png" + if not await g_pRequestManager.download(url, saveDir, f"{idx}.png"): + success = False + + iconName = "icon.png" + iconPath = os.path.join(saveDir, iconName) + if not os.path.exists(iconPath): + iconUrl = f"{baseUrl}/{name}/{iconName}" + if not await g_pRequestManager.download(iconUrl, saveDir, iconName): + success = False + + return success + except Exception as e: + logger.warning(f"下载作物资源异常: {e}") + return False diff --git a/database/user.py b/database/user.py index a80276f..dd01c87 100644 --- a/database/user.py +++ b/database/user.py @@ -225,11 +225,11 @@ class CUserDB(CSqlManager): @classmethod async def updateUserVipPointByUid(cls, uid: str, vipPoint: int) -> bool: - """根据用户Uid更新农场币数量 + """根据用户Uid更新点券数量 Args: uid (str): 用户Uid - vipPoint (int): 新农场币数量 + vipPoint (int): 新点券数量 Returns: bool: 是否更新成功 @@ -300,7 +300,8 @@ class CUserDB(CSqlManager): uid (str): 用户Uid Returns: - tuple[int, int, int]: (当前等级, 升至下级还需经验, 当前等级已获经验),失败返回(-1, -1, -1) + tuple[int, int, int]: 成功返回(当前等级, 升至下级还需经验, 当前等级已获经验) + 失败返回(-1, -1, -1) """ if not uid: return -1, -1, -1 diff --git a/database/userSign.py b/database/userSign.py index ba42ec6..a68569d 100644 --- a/database/userSign.py +++ b/database/userSign.py @@ -1,6 +1,6 @@ import calendar -import random from datetime import timedelta +import random from zhenxun.services.log import logger from zhenxun.utils._build_image import BuildImage @@ -22,7 +22,7 @@ class CUserSignDB(CSqlManager): "isSupplement": "TINYINT NOT NULL DEFAULT 0", # 是否补签 "exp": "INT NOT NULL DEFAULT 0", # 当天签到经验 "point": "INT NOT NULL DEFAULT 0", # 当天签到金币 - "createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 创建时间 + "createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 创建时间 # noqa: E501 "PRIMARY KEY": "(uid, signDate)", } @@ -35,7 +35,7 @@ class CUserSignDB(CSqlManager): "lastSignDate": "DATE DEFAULT NULL", # 上次签到日期 "continuousDays": "INT NOT NULL DEFAULT 0", # 连续签到天数 "supplementCount": "INT NOT NULL DEFAULT 0", # 补签次数 - "updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 更新时间 + "updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 更新时间 # noqa: E501 } await cls.ensureTableSchema("userSignLog", userSignLog) diff --git a/database/userSoil.py b/database/userSoil.py index a2ca943..627b7fc 100644 --- a/database/userSoil.py +++ b/database/userSoil.py @@ -67,6 +67,35 @@ class CUserSoilDB(CSqlManager): f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}" ) + @classmethod + async def matureNow(cls, uid: str, soilIndex: int): + """将指定地块的作物直接成熟 + + Args: + uid (str): 用户ID + soilIndex (int): 地块索引(从1开始) + """ + # 与 nextPhase 不同:无需调试模式检查,允许在任何模式下调用 + soilInfo = await cls.getUserSoil(uid, soilIndex) + if not soilInfo: + return + + plantName = soilInfo.get("plantName") + if not plantName: + return + + plantInfo = await g_pDBService.plant.getPlantByName(plantName) + if not plantInfo: + return + + currentTime = g_pToolManager.dateTime().now().timestamp() + # 如果当前时间已经超过或等于成熟时间,则作物已成熟或可收获 + if currentTime >= soilInfo["matureTime"]: + return + + # 将作物成熟时间直接更新为当前时间,实现立即成熟 + await cls.updateUserSoilFields(uid, soilIndex, {"matureTime": currentTime}) + @classmethod async def getUserFarmByUid(cls, uid: str) -> dict: """获取指定用户的旧农场数据 @@ -247,6 +276,25 @@ class CUserSoilDB(CSqlManager): columns = [description[0] for description in cursor.description] return dict(zip(columns, row)) + @classmethod + async def countSoilByLevel(cls, uid: str, soilLevel: int) -> int: + """统计指定用户在指定土地等级的土地数量 + + Args: + uid (str): 用户ID + soilLevel (int): 土地等级 + + Returns: + int: 符合条件的土地数量 + """ + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + "SELECT COUNT(*) FROM userSoil WHERE uid = ? AND soilLevel = ?", + (uid, soilLevel), + ) + row = await cursor.fetchone() + return row[0] if row else 0 + @classmethod async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value): """更新指定用户土地的单个字段 @@ -393,8 +441,8 @@ class CUserSoilDB(CSqlManager): bool: 播种成功返回 True,否则返回 False """ # 校验土地区是否已种植 - soilRecord = await cls.getUserSoil(uid, soilIndex) - if soilRecord and soilRecord.get("plantName"): + soilInfo = await cls.getUserSoil(uid, soilIndex) + if not soilInfo: return False # 获取植物配置 @@ -404,11 +452,18 @@ class CUserSoilDB(CSqlManager): return False nowTs = int(g_pToolManager.dateTime().now().timestamp()) - matureTs = nowTs + int(plantCfg.get("time", 0)) * 3600 + + time = int(plantCfg.get("time", 0)) + percent = await cls.getSoilLevelTime(soilInfo.get("soilLevel", 0)) + + # 处理土地等级带来的时间缩短 + time = time * (100 + percent) // 100 + + matureTs = nowTs + time * 3600 try: async with cls._transaction(): - prev = soilRecord or {} + prev = soilInfo or {} await cls._deleteUserSoil(uid, soilIndex) await cls._insertUserSoil( { @@ -457,3 +512,90 @@ class CUserSoilDB(CSqlManager): status.append("缺水") return ",".join(status) + + @classmethod + async def getSoilLevel(cls, level: int) -> str: + """获取土地等级英文文本 + + Args: + level (int): 土地等级 + + Returns: + str: + """ + if level == 1: + return "red" + elif level == 2: + return "black" + elif level == 3: + return "gold" + + return "default" + + @classmethod + async def getSoilLevelText(cls, level: int) -> str: + """获取土地等级中文文本 + + Args: + level (int): 土地等级 + + Returns: + str: + """ + if level == 1: + return "红土地" + elif level == 2: + return "黑土地" + elif level == 3: + return "金土地" + + return "草土地" + + @classmethod + async def getSoilLevelHarvestNumber(cls, level: int) -> int: + """获取土地等级收获数量增加比例 + + Args: + level (int): 土地等级 + + Returns: + int: + """ + if level == 2: + return 20 + elif level == 3: + return 28 + + return 10 + + @classmethod + async def getSoilLevelHarvestExp(cls, level: int) -> int: + """获取土地等级收获经验增加比例 + + Args: + level (int): 土地等级 + + Returns: + int: + """ + if level == 3: + return 28 + + return 0 + + @classmethod + async def getSoilLevelTime(cls, level: int) -> int: + """获取土地等级播种减少时间消耗 + + Args: + level (int): 土地等级 + + Returns: + int: + """ + if level == 2: + return 20 + elif level == 3: + return 20 + + return 0 diff --git a/event/event.py b/event/event.py index 44c4ab9..79e53b8 100644 --- a/event/event.py +++ b/event/event.py @@ -1,102 +1,123 @@ -import asyncio +import inspect import time from zhenxun.services.log import logger class Signal: + def __set_name__(self, owner, name): + self.name = name + + def __get__(self, instance, owner): + if instance is None: + return self + bound = instance.__dict__.get(self.name) + if bound is None: + bound = _SignalBound() + instance.__dict__[self.name] = bound + return bound + + +class _SignalBound: def __init__(self): - self._slots = [] # 绑定的槽函数列表 - self._onceSlots = [] # 只触发一次的槽函数列表 + self._slots = [] + self._onceSlots = [] - def connect(self, slot, priority=0): - if callable(slot) and not any(s[0] == slot for s in self._slots): - self._slots.append((slot, priority)) + def connect(self, func=None, *, priority=0): + if func is None: + return lambda f: self.connect(f, priority=priority) + if callable(func) and not any(s[0] == func for s in self._slots): + self._slots.append((func, priority)) self._slots.sort(key=lambda x: -x[1]) + return func - def connectOnce(self, slot, priority=0): - if callable(slot) and not any(s[0] == slot for s in self._onceSlots): - self._onceSlots.append((slot, priority)) + def connect_once(self, func=None, *, priority=0): + if func is None: + return lambda f: self.connect_once(f, priority=priority) + if callable(func) and not any(s[0] == func for s in self._onceSlots): + self._onceSlots.append((func, priority)) self._onceSlots.sort(key=lambda x: -x[1]) + return func - def disconnect(self, slot): - self._slots = [s for s in self._slots if s[0] != slot] - self._onceSlots = [s for s in self._onceSlots if s[0] != slot] + def disconnect(self, func): + self._slots = [s for s in self._slots if s[0] != func] + self._onceSlots = [s for s in self._onceSlots if s[0] != func] async def emit(self, *args, **kwargs): slots = list(self._slots) onceSlots = list(self._onceSlots) self._onceSlots.clear() - for slot, _ in slots + onceSlots: - startTime = time.time() + start = time.time() try: - if asyncio.iscoroutinefunction(slot): + if inspect.iscoroutinefunction(slot): await slot(*args, **kwargs) else: slot(*args, **kwargs) - duration = (time.time() - startTime) * 1000 - logger.debug(f"事件槽 {slot.__name__} 执行完成,耗时 {duration:.2f} ms") + logger.debug( + f"【真寻农场】事件槽 {slot.__name__} 执行完成,耗时 {(time.time() - start) * 1000:.2f} ms" + ) except Exception as e: logger.warning(f"事件槽 {slot.__name__} 触发异常: {e}") class FarmEventManager: - def __init__(self): - self.m_beforePlant = Signal() - """播种前信号 + m_beforePlant = Signal() + """播种前信号 - Args: - uid (str): 用户Uid - name (str): 播种种子名称 - num (int): 播种数量 - """ + Args: + uid (str): 用户Uid + name (str): 播种种子名称 + num (int): 播种数量 + """ - self.m_afterPlant = Signal() - """播种后信号 每块地播种都会触发该信号 + m_afterPlant = Signal() + """播种后信号 每块地播种都会触发该信号 - Args: - uid (str): 用户Uid - name (str): 播种种子名称 - soilIndex (int): 播种地块索引 从1开始 - """ + Args: + uid (str): 用户Uid + name (str): 播种种子名称 + soilIndex (int): 播种地块索引 从1开始 + """ - self.m_beforeHarvest = Signal() - """收获前信号 + m_beforeHarvest = Signal() + """收获前信号 - Args: - uid (str): 用户Uid - """ + Args: + uid (str): 用户Uid + """ - self.m_afterHarvest = Signal() - """收获后信号 每块地收获都会触发该信号 + m_afterHarvest = Signal() + """收获后信号 每块地收获都会触发该信号 - Args: - uid (str): 用户Uid - name (str): 收获作物名称 - num (int): 收获数量 - soilIndex (int): 收获地块索引 从1开始 - """ + Args: + uid (str): 用户Uid + name (str): 收获作物名称 + num (int): 收获数量 + soilIndex (int): 收获地块索引 从1开始 + """ - self.m_beforeEradicate = Signal() - """铲除前信号 + m_beforeEradicate = Signal() + """铲除前信号 - Args: - uid (str): 用户Uid - """ + Args: + uid (str): 用户Uid + """ - self.m_afterEradicate = Signal() - """铲除后信号 每块地铲除都会触发该信号 + m_afterEradicate = Signal() + """铲除后信号 每块地铲除都会触发该信号 - Args: - uid (str): 用户Uid - soilIndex (index): 铲除地块索引 从1开始 - """ + Args: + uid (str): 用户Uid + soilIndex (index): 铲除地块索引 从1开始 + """ - self.m_beforeExpand = Signal() - self.m_afterExpand = Signal() - self.m_beforeSteal = Signal() - self.m_afterSteal = Signal() + m_beforeExpand = Signal() + m_afterExpand = Signal() + m_beforeSteal = Signal() + m_afterSteal = Signal() + + m_dit = Signal() g_pEventManager = FarmEventManager() diff --git a/farm/farm.py b/farm/farm.py index acfb90c..1beab59 100644 --- a/farm/farm.py +++ b/farm/farm.py @@ -9,7 +9,7 @@ from zhenxun.utils.enum import GoldHandle from zhenxun.utils.image_utils import ImageTemplate from zhenxun.utils.platform import PlatformUtils -from ..config import g_bIsDebug, g_sResourcePath, g_sTranslation +from ..config import g_bIsDebug, g_iSoilLevelMax, g_sResourcePath, g_sTranslation from ..dbService import g_pDBService from ..event.event import g_pEventManager from ..json import g_pJsonManager @@ -37,7 +37,7 @@ class CFarmManager: await UserConsole.reduce_gold( uid, num, GoldHandle.PLUGIN, "zhenxun_plugin_farm" - ) # type: ignore + ) await UserConsole.reduce_gold( uid, fee, GoldHandle.PLUGIN, "zhenxun_plugin_farm" ) # type: ignore @@ -65,10 +65,6 @@ class CFarmManager: soilSize = g_pJsonManager.m_pSoil["size"] - # TODO 缺少判断用户土地资源状况 - soil = BuildImage(background=g_sResourcePath / "soil/普通土地.png") - await soil.resize(0, soilSize[0], soilSize[1]) - grass = BuildImage(background=g_sResourcePath / "soil/草土地.png") await grass.resize(0, soilSize[0], soilSize[1]) @@ -88,6 +84,27 @@ class CFarmManager: # 如果土地已经到达对应等级 if index < soilUnlock: + soilUrl = "" + # TODO 缺少判断用户土地资源状况 + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, index) + + if not soilInfo: + soilUrl = "soil/普通土地.png" + else: + soilLevel = soilInfo.get("soilLevel", 0) + + if soilLevel == 1: + soilUrl = "soil/红土地.png" + elif soilLevel == 2: + soilUrl = "soil/黑土地.png" + elif soilLevel == 3: + soilUrl = "soil/金土地.png" + else: + soilUrl = "soil/普通土地.png" + + soil = BuildImage(background=g_sResourcePath / soilUrl) + await soil.resize(0, soilSize[0], soilSize[1]) + await img.paste(soil, (x, y)) isPlant, plant, isRipe, offsetX, offsetY = await cls.drawSoilPlant( @@ -211,6 +228,7 @@ class CFarmManager: columnName = [ "-", "土地ID", + "土地等级", "作物名称", "成熟时间", "土地状态", @@ -226,7 +244,7 @@ class CFarmManager: if soilInfo: if soilInfo["soilLevel"] == 1: - iconPath = g_sResourcePath / "soil/TODO.png" + iconPath = g_sResourcePath / "soil/红土地.png" else: iconPath = g_sResourcePath / "soil/普通土地.png" @@ -262,6 +280,9 @@ class CFarmManager: [ icon, i, + await g_pDBService.userSoil.getSoilLevelText( + soilInfo["soilLevel"] + ), plantName, matureTime, soilStatus, @@ -344,8 +365,11 @@ class CFarmManager: return True, plant, True, offsetX, offsetY else: # 如果是多阶段作物 且没有成熟 #早期思路 多阶段作物 直接是倒数第二阶段图片 - # if soilInfo['harvestCount'] >= 1: - # plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo['plantName']}/{plantInfo['phase'] - 1s}.png") + # if soilInfo["harvestCount"] >= 1: + # plant = BuildImage( + # background=g_sResourcePath + # / f"plant/{soilInfo['plantName']}/{plantInfo['phase'] - 1}.png" + # ) # return True, plant, False, offsetX, offsetY @@ -466,7 +490,7 @@ class CFarmManager: num = count # 发送播种前信号 - await g_pEventManager.m_beforePlant.emit(uid=uid, name=name, num=num) + await g_pEventManager.m_beforePlant.emit(uid=uid, name=name, num=num) # type: ignore # 记录是否成功播种 successCount = 0 @@ -484,7 +508,7 @@ class CFarmManager: successCount += 1 # 发送播种后信号 - await g_pEventManager.m_afterPlant.emit( + await g_pEventManager.m_afterPlant.emit( # type: ignore uid=uid, name=name, soilIndex=i ) @@ -513,7 +537,7 @@ class CFarmManager: str: 返回 """ try: - await g_pEventManager.m_beforeHarvest.emit(uid=uid) + await g_pEventManager.m_beforeHarvest.emit(uid=uid) # type: ignore soilNumber = await g_pDBService.user.getUserSoilByUid(uid) @@ -530,6 +554,8 @@ class CFarmManager: if not soilInfo: continue + level = soilInfo.get("soilLevel", 0) + # 如果是枯萎状态 if soilInfo.get("wiltStatus", 1) == 1: continue @@ -550,20 +576,29 @@ class CFarmManager: # 处理偷菜扣除数量 stealNum = await g_pDBService.userSteal.getTotalStolenCount(uid, i) - number -= stealNum + # 处理土地等级带来的数量增长 向下取整 + percent = await g_pDBService.userSoil.getSoilLevelHarvestNumber( + level + ) + number = number * (100 + percent) // 100 + if number <= 0: continue harvestCount += 1 experience += plantInfo["experience"] + # 处理土地等级带来的经验增长 向下取整 + percent = await g_pDBService.userSoil.getSoilLevelHarvestExp(level) + experience = experience * (100 + percent) // 100 + harvestRecords.append( g_sTranslation["harvest"]["append"].format( name=soilInfo["plantName"], num=number, - exp=plantInfo["experience"], + exp=experience, ) ) @@ -597,7 +632,7 @@ class CFarmManager: }, ) - await g_pEventManager.m_afterHarvest.emit( + await g_pEventManager.m_afterHarvest.emit( # type: ignore uid=uid, name=soilInfo["plantName"], num=number, soilIndex=i ) @@ -631,7 +666,7 @@ class CFarmManager: """ soilNumber = await g_pDBService.user.getUserSoilByUid(uid) - await g_pEventManager.m_beforeEradicate.emit(uid=uid) + await g_pEventManager.m_beforeEradicate.emit(uid=uid) # type: ignore experience = 0 for i in range(1, soilNumber + 1): @@ -658,7 +693,7 @@ class CFarmManager: # 铲除作物会将偷菜记录清空 await g_pDBService.userSteal.deleteStealRecord(uid, i) - await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) + await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) # type: ignore if experience > 0: exp = await g_pDBService.user.getUserExpByUid(uid) @@ -703,7 +738,7 @@ class CFarmManager: if icon_path.exists(): icon = (icon_path, 33, 33) - if plantInfo["sell"] == True: + if plantInfo["sell"]: sell = "可以" else: sell = "不可以" @@ -863,6 +898,14 @@ class CFarmManager: @classmethod async def reclamationCondition(cls, uid: str) -> str: + """获取开垦条件 + + Args: + uid (str): 用户Uid + + Returns: + str: 返回条件文本信息 + """ userInfo = await g_pDBService.user.getUserInfoByUid(uid) rec = g_pJsonManager.m_pLevel["reclamation"] @@ -892,6 +935,14 @@ class CFarmManager: @classmethod async def reclamation(cls, uid: str) -> str: + """开垦 + + Args: + uid (str): 用户Uid + + Returns: + str: _description_ + """ userInfo = await g_pDBService.user.getUserInfoByUid(uid) level = await g_pDBService.user.getUserLevelByUid(uid) @@ -905,7 +956,7 @@ class CFarmManager: levelFileter = rec["level"] point = rec["point"] - item = rec["item"] + # item = rec["item"] if level[0] < levelFileter: return g_sTranslation["reclamation"]["nextLevel"].format( @@ -923,5 +974,119 @@ class CFarmManager: except Exception: return g_sTranslation["reclamation"]["error1"] + @classmethod + async def soilUpgradeCondition(cls, uid: str, soilIndex: int) -> str: + """获取土地升级条件 + + Args: + uid (str): 用户Uid + soilIndex (str): 土地索引 + + Returns: + str: 返回土地升级条件 + """ + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex) + + if not soilInfo: + return g_sTranslation["soilInfo"]["error"] + + soilLevel = soilInfo.get("soilLevel", 0) + 1 + if soilLevel >= g_iSoilLevelMax: + return g_sTranslation["soilInfo"]["error1"] + + # 获取用户当前土地 的下一级土地 数量 + countSoil = await g_pDBService.userSoil.countSoilByLevel(uid, soilLevel) + + # 获取升级所需 + soilLevelText = await g_pDBService.userSoil.getSoilLevel(soilLevel) + fileter = g_pJsonManager.m_pSoil["upgrade"][soilLevelText][countSoil] + + lines = ["升级该土地所需:"] + fields = [ + ("level", "等级"), + ("point", "金币"), + ("vipPoint", "点券"), + ] + for key, label in fields: + value = fileter.get(key, 0) + if value > 0: + lines.append(f"{label}:{value}") + + items = fileter.get("item", {}) + for name, qty in items.items(): + if qty: + lines.append(f"{name}:{qty}") + + lines.append("回复“是”将执行升级") + + return "\n".join(lines) + + @classmethod + async def soilUpgrade(cls, uid: str, soilIndex: int) -> str: + """土地升级 + + Args: + uid (str): 用户Uid + soilIndex (int): 土地索引 + + Returns: + str: + """ + userInfo = await g_pDBService.user.getUserInfoByUid(uid) + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex) + + if not soilInfo: + return g_sTranslation["soilInfo"]["error"] + + soilLevel = soilInfo.get("soilLevel", 0) + 1 + if soilLevel >= g_iSoilLevelMax: + return g_sTranslation["soilInfo"]["error1"] + + countSoil = await g_pDBService.userSoil.countSoilByLevel(uid, soilLevel) + + soilLevelText = await g_pDBService.userSoil.getSoilLevel(soilLevel) + fileter = g_pJsonManager.m_pSoil["upgrade"][soilLevelText][countSoil] + + getters = { + "level": (await g_pDBService.user.getUserLevelByUid(uid))[0], + "point": userInfo.get("point", 0), + "vipPoint": userInfo.get("vipPoint", 0), + } + + requirements = { + "level": "等级", + "point": "金币", + "vipPoint": "点券", + } + + for key, val in getters.items(): + need = fileter.get(key, 0) + if val < need: + return f"你的{requirements[key]}不够哦~" + + # 缺少item判断 + + # 更新数据库字段 + await g_pDBService.userSoil.updateUserSoil( + uid, soilIndex, "soilLevel", soilLevel + ) + + # 如果有作物的话直接成熟 + await g_pDBService.userSoil.matureNow(uid, soilIndex) + + # 更新数据库字段 + await g_pDBService.user.updateUserPointByUid( + uid, userInfo.get("point", 0) - fileter.get("point", 0) + ) + + await g_pDBService.user.updateUserPointByUid( + uid, userInfo.get("vipPoint", 0) - fileter.get("vipPoint", 0) + ) + + return g_sTranslation["soilInfo"]["success"].format( + name=await g_pDBService.userSoil.getSoilLevelText(soilLevel), + text=g_sTranslation["soilInfo"][soilLevelText], + ) + g_pFarmManager = CFarmManager() diff --git a/json.py b/json.py index fb90a96..ba42cbe 100644 --- a/json.py +++ b/json.py @@ -79,7 +79,11 @@ class CJsonManager: return False else: - return await self.initSign() + result = await self.initSign() + + config.g_bSignStatus = result + + return result async def initSign(self) -> bool: try: diff --git a/log/log.md b/log/log.md index f2c2218..010bb62 100644 --- a/log/log.md +++ b/log/log.md @@ -1,5 +1,19 @@ # 真寻农场更新日志 +## V1.5 +用户方面 +--- +- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧 +- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件 +- 定时更新签到文件、作物资源从00:30调整至04:30 +- 修正了部分土地资源错误的情况 +- 修正了部分文本信息错误的情况 + +代码方面 +--- +- 修正部分事件连接机制 +- 修正网络请求端口 + ## V1.4 用户方面 --- diff --git a/request.py b/request.py index a74c2da..9ca28ac 100644 --- a/request.py +++ b/request.py @@ -2,11 +2,20 @@ import json import os import httpx +from rich.progress import ( + BarColumn, + DownloadColumn, + Progress, + TextColumn, + TimeRemainingColumn, + TransferSpeedColumn, +) from zhenxun.configs.config import Config from zhenxun.services.log import logger -from .config import g_sSignInPath +from .config import g_sPlantPath, g_sSignInPath +from .dbService import g_pDBService from .tool import g_pToolManager @@ -22,7 +31,7 @@ class CRequestManager: params: dict | None = None, jsonData: dict | None = None, ) -> bool: - """下载文件到指定路径并覆盖已存在的文件 + """下载文件到指定路径并覆盖已存在的文件,并显示下载进度条 Args: url (str): 文件的下载链接 @@ -30,33 +39,53 @@ class CRequestManager: fileName (str): 保存后的文件名 params (dict | None): 可选的 URL 查询参数 jsonData (dict | None): 可选的 JSON 请求体 + Returns: bool: 是否下载成功 """ headers = {"token": cls.m_sTokens} try: - async with httpx.AsyncClient(timeout=10.0) as client: + async with httpx.AsyncClient(timeout=30.0) as client: requestArgs: dict = {"headers": headers} if params: requestArgs["params"] = params if jsonData: requestArgs["json"] = jsonData - response = await client.request("GET", url, **requestArgs) + response = await client.request( + "GET", url, **requestArgs, follow_redirects=True + ) - if response.status_code == 200: - fullPath = os.path.join(savePath, fileName) - os.makedirs(os.path.dirname(fullPath), exist_ok=True) - with open(fullPath, "wb") as f: - f.write(response.content) - return True - else: + if response.status_code != 200: logger.warning( f"文件下载失败: HTTP {response.status_code} {response.text}" ) return False + totalLength = int(response.headers.get("Content-Length", 0)) + fullPath = os.path.join(savePath, fileName) + os.makedirs(os.path.dirname(fullPath), exist_ok=True) + + with Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + DownloadColumn(), + TransferSpeedColumn(), + TimeRemainingColumn(), + transient=True, + ) as progress: + task = progress.add_task( + f"[green]【真寻农场】正在下载 {fileName}", total=totalLength + ) + + with open(fullPath, "wb") as f: + async for chunk in response.aiter_bytes(chunk_size=1024): + f.write(chunk) + progress.advance(task, len(chunk)) + + return True + except Exception as e: logger.warning(f"下载文件异常: {e}") return False @@ -77,7 +106,7 @@ class CRequestManager: dict: 返回请求结果的JSON数据 """ baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") - url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}" + url = f"{baseUrl.rstrip('/')}:8998/{endpoint.lstrip('/')}" headers = {"token": cls.m_sTokens} try: @@ -110,7 +139,7 @@ class CRequestManager: dict: 返回请求结果的JSON数据 """ baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") - url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}" + url = f"{baseUrl.rstrip('/')}:8998/{endpoint.lstrip('/')}" headers = {"token": cls.m_sTokens} try: @@ -156,20 +185,110 @@ class CRequestManager: @classmethod async def downloadSignInFile(cls) -> bool: + """下载签到文件,并重命名为 sign_in.json + + Returns: + bool: 是否下载成功 + """ try: baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") - url = f"{baseUrl.rstrip('/')}:8998/sign_in" path = str(g_sSignInPath.parent.resolve(strict=False)) yearMonth = g_pToolManager.dateTime().now().strftime("%Y%m") - await cls.download(url, path, "signTemp.json", jsonData={"date": yearMonth}) - g_pToolManager.renameFile(f"{path}/signTemp.json", "sign_in.json") + # 下载为 signTemp.json + success = await cls.download( + url=url, + savePath=path, + fileName="signTemp.json", + jsonData={"date": yearMonth}, + ) + if not success: + return False + + # 重命名为 sign_in.json + g_pToolManager.renameFile(f"{path}/signTemp.json", "sign_in.json") return True + except Exception as e: logger.error("下载签到文件失败", e=e) return False + @classmethod + async def initPlantDBFile(cls) -> bool: + """检查本地 plant.db 版本,如远程版本更新则重新下载 + + Returns: + bool: 是否为最新版或成功更新 + """ + versionPath = os.path.join(os.path.dirname(g_sPlantPath), "version.json") + + try: + with open(versionPath, encoding="utf-8") as f: + localVersion = json.load(f).get("version", 0) + except Exception as e: + logger.warning(f"读取本地版本失败,默认版本为0: {e}") + localVersion = 0 + + remoteInfo = await cls.get("plant_version", name="版本检查") + remoteVersion = remoteInfo.get("version") + + if remoteVersion is None: + logger.warning("获取远程版本失败") + return False + + if float(remoteVersion) <= float(localVersion): + logger.debug("plant.db 已为最新版本") + return True + + logger.warning( + f"发现新版本 plant.db(远程: {remoteVersion} / 本地: {localVersion}),开始更新..." + ) + + # 先断开数据库连接 + await g_pDBService.cleanup() + + return await cls.downloadPlantDBFile(remoteVersion) + + @classmethod + async def downloadPlantDBFile(cls, remoteVersion: float) -> bool: + """下载最新版 plant.db 并更新本地 version.json + + Args: + remoteVersion (float): 远程版本号 + + Returns: + bool: 是否下载并更新成功 + """ + baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") + + savePath = os.path.dirname(g_sPlantPath) + success = await cls.download( + url=f"{baseUrl.rstrip('/')}:8998/file/plant.db", + savePath=savePath, + fileName="plantTemp.db", + ) + + if not success: + return False + + # 重命名为 sign_in.json + g_pToolManager.renameFile(f"{savePath}/plantTemp.db", "plant.db") + + versionPath = os.path.join(savePath, "version.json") + try: + with open(versionPath, "w", encoding="utf-8") as f: + json.dump({"version": remoteVersion}, f) + logger.debug("版本文件已更新") + except Exception as e: + logger.warning(f"写入版本文件失败: {e}") + return False + + await g_pDBService.plant.init() + await g_pDBService.plant.downloadPlant() + + return True + g_pRequestManager = CRequestManager() diff --git a/resource/db/plant.db b/resource/db/plant.db index 4f0fd76..a198f31 100644 Binary files a/resource/db/plant.db and b/resource/db/plant.db differ diff --git a/resource/db/version.json b/resource/db/version.json new file mode 100644 index 0000000..5e8fde9 --- /dev/null +++ b/resource/db/version.json @@ -0,0 +1 @@ +{"version": 0.41} \ No newline at end of file diff --git a/resource/plant/南瓜/1.png b/resource/plant/南瓜/1.png new file mode 100644 index 0000000..ef8d7a0 Binary files /dev/null and b/resource/plant/南瓜/1.png differ diff --git a/resource/plant/南瓜/2.png b/resource/plant/南瓜/2.png index ef8d7a0..452b604 100644 Binary files a/resource/plant/南瓜/2.png and b/resource/plant/南瓜/2.png differ diff --git a/resource/plant/南瓜/3.png b/resource/plant/南瓜/3.png index 452b604..0424ecd 100644 Binary files a/resource/plant/南瓜/3.png and b/resource/plant/南瓜/3.png differ diff --git a/resource/plant/南瓜/4.png b/resource/plant/南瓜/4.png index 0424ecd..9393f6d 100644 Binary files a/resource/plant/南瓜/4.png and b/resource/plant/南瓜/4.png differ diff --git a/resource/plant/南瓜/5.png b/resource/plant/南瓜/5.png index 9393f6d..ae3e356 100644 Binary files a/resource/plant/南瓜/5.png and b/resource/plant/南瓜/5.png differ diff --git a/resource/plant/南瓜/6.png b/resource/plant/南瓜/6.png deleted file mode 100644 index ae3e356..0000000 Binary files a/resource/plant/南瓜/6.png and /dev/null differ diff --git a/resource/plant/双玉兰/1.png b/resource/plant/双玉兰/1.png new file mode 100644 index 0000000..0aa9045 Binary files /dev/null and b/resource/plant/双玉兰/1.png differ diff --git a/resource/plant/双玉兰/2.png b/resource/plant/双玉兰/2.png new file mode 100644 index 0000000..214922f Binary files /dev/null and b/resource/plant/双玉兰/2.png differ diff --git a/resource/plant/双玉兰/3.png b/resource/plant/双玉兰/3.png new file mode 100644 index 0000000..c8d7729 Binary files /dev/null and b/resource/plant/双玉兰/3.png differ diff --git a/resource/plant/双玉兰/4.png b/resource/plant/双玉兰/4.png new file mode 100644 index 0000000..dcc2b8d Binary files /dev/null and b/resource/plant/双玉兰/4.png differ diff --git a/resource/plant/双玉兰/5.png b/resource/plant/双玉兰/5.png new file mode 100644 index 0000000..3410590 Binary files /dev/null and b/resource/plant/双玉兰/5.png differ diff --git a/resource/plant/双玉兰/icon.png b/resource/plant/双玉兰/icon.png new file mode 100644 index 0000000..a2823ae Binary files /dev/null and b/resource/plant/双玉兰/icon.png differ diff --git a/resource/plant/大白菜/3.png b/resource/plant/大白菜/3.png index 64a011f..a649d85 100644 Binary files a/resource/plant/大白菜/3.png and b/resource/plant/大白菜/3.png differ diff --git a/resource/plant/大白菜/4.png b/resource/plant/大白菜/4.png index 9245a8a..64a011f 100644 Binary files a/resource/plant/大白菜/4.png and b/resource/plant/大白菜/4.png differ diff --git a/resource/plant/大白菜/5.png b/resource/plant/大白菜/5.png new file mode 100644 index 0000000..9245a8a Binary files /dev/null and b/resource/plant/大白菜/5.png differ diff --git a/resource/plant/红叶梅/1.png b/resource/plant/红叶梅/1.png new file mode 100644 index 0000000..dbba79a Binary files /dev/null and b/resource/plant/红叶梅/1.png differ diff --git a/resource/plant/红叶梅/2.png b/resource/plant/红叶梅/2.png new file mode 100644 index 0000000..417f839 Binary files /dev/null and b/resource/plant/红叶梅/2.png differ diff --git a/resource/plant/红叶梅/3.png b/resource/plant/红叶梅/3.png new file mode 100644 index 0000000..ac9fb87 Binary files /dev/null and b/resource/plant/红叶梅/3.png differ diff --git a/resource/plant/红叶梅/4.png b/resource/plant/红叶梅/4.png new file mode 100644 index 0000000..7fe556f Binary files /dev/null and b/resource/plant/红叶梅/4.png differ diff --git a/resource/plant/红叶梅/5.png b/resource/plant/红叶梅/5.png new file mode 100644 index 0000000..0f259a3 Binary files /dev/null and b/resource/plant/红叶梅/5.png differ diff --git a/resource/plant/红叶梅/icon.png b/resource/plant/红叶梅/icon.png new file mode 100644 index 0000000..af4a5dc Binary files /dev/null and b/resource/plant/红叶梅/icon.png differ diff --git a/resource/plant/红毛丹/1.png b/resource/plant/红毛丹/1.png new file mode 100644 index 0000000..74a4fde Binary files /dev/null and b/resource/plant/红毛丹/1.png differ diff --git a/resource/plant/红毛丹/2.png b/resource/plant/红毛丹/2.png new file mode 100644 index 0000000..ddf3bd8 Binary files /dev/null and b/resource/plant/红毛丹/2.png differ diff --git a/resource/plant/红毛丹/3.png b/resource/plant/红毛丹/3.png new file mode 100644 index 0000000..c3c0960 Binary files /dev/null and b/resource/plant/红毛丹/3.png differ diff --git a/resource/plant/红毛丹/4.png b/resource/plant/红毛丹/4.png new file mode 100644 index 0000000..79f9f0e Binary files /dev/null and b/resource/plant/红毛丹/4.png differ diff --git a/resource/plant/红毛丹/5.png b/resource/plant/红毛丹/5.png new file mode 100644 index 0000000..3737869 Binary files /dev/null and b/resource/plant/红毛丹/5.png differ diff --git a/resource/plant/红毛丹/icon.png b/resource/plant/红毛丹/icon.png new file mode 100644 index 0000000..a40cb5f Binary files /dev/null and b/resource/plant/红毛丹/icon.png differ diff --git a/resource/plant/鸾风玉/1.png b/resource/plant/鸾风玉/1.png new file mode 100644 index 0000000..7383d18 Binary files /dev/null and b/resource/plant/鸾风玉/1.png differ diff --git a/resource/plant/鸾风玉/2.png b/resource/plant/鸾风玉/2.png new file mode 100644 index 0000000..084ff84 Binary files /dev/null and b/resource/plant/鸾风玉/2.png differ diff --git a/resource/plant/鸾风玉/3.png b/resource/plant/鸾风玉/3.png new file mode 100644 index 0000000..472adba Binary files /dev/null and b/resource/plant/鸾风玉/3.png differ diff --git a/resource/plant/鸾风玉/4.png b/resource/plant/鸾风玉/4.png new file mode 100644 index 0000000..52be429 Binary files /dev/null and b/resource/plant/鸾风玉/4.png differ diff --git a/resource/plant/鸾风玉/5.png b/resource/plant/鸾风玉/5.png new file mode 100644 index 0000000..fcecec6 Binary files /dev/null and b/resource/plant/鸾风玉/5.png differ diff --git a/resource/plant/鸾风玉/icon.png b/resource/plant/鸾风玉/icon.png new file mode 100644 index 0000000..a7a9c63 Binary files /dev/null and b/resource/plant/鸾风玉/icon.png differ diff --git a/resource/plant/黄豆/1.png b/resource/plant/黄豆/1.png new file mode 100644 index 0000000..5432e0b Binary files /dev/null and b/resource/plant/黄豆/1.png differ diff --git a/resource/plant/黄豆/2.png b/resource/plant/黄豆/2.png index 5432e0b..b5166ea 100644 Binary files a/resource/plant/黄豆/2.png and b/resource/plant/黄豆/2.png differ diff --git a/resource/plant/黄豆/3.png b/resource/plant/黄豆/3.png index b5166ea..17a1181 100644 Binary files a/resource/plant/黄豆/3.png and b/resource/plant/黄豆/3.png differ diff --git a/resource/plant/黄豆/4.png b/resource/plant/黄豆/4.png index 17a1181..194b675 100644 Binary files a/resource/plant/黄豆/4.png and b/resource/plant/黄豆/4.png differ diff --git a/resource/plant/黄豆/5.png b/resource/plant/黄豆/5.png index 194b675..487d95f 100644 Binary files a/resource/plant/黄豆/5.png and b/resource/plant/黄豆/5.png differ diff --git a/resource/plant/黄豆/6.png b/resource/plant/黄豆/6.png deleted file mode 100644 index 487d95f..0000000 Binary files a/resource/plant/黄豆/6.png and /dev/null differ diff --git a/resource/soil/紫金土地-施肥.png b/resource/soil/紫金土地-施肥.png index 800440a..e8d2729 100644 Binary files a/resource/soil/紫金土地-施肥.png and b/resource/soil/紫金土地-施肥.png differ diff --git a/resource/soil/紫金土地-缺水.png b/resource/soil/紫金土地-缺水.png index 17f49d0..69d6369 100644 Binary files a/resource/soil/紫金土地-缺水.png and b/resource/soil/紫金土地-缺水.png differ diff --git a/resource/soil/紫金土地.png b/resource/soil/紫金土地.png index 3dd7aab..cbeae00 100644 Binary files a/resource/soil/紫金土地.png and b/resource/soil/紫金土地.png differ diff --git a/resource/soil/红土地-施肥.png b/resource/soil/红土地-施肥.png index fac008b..072775b 100644 Binary files a/resource/soil/红土地-施肥.png and b/resource/soil/红土地-施肥.png differ diff --git a/resource/soil/红土地-缺水.png b/resource/soil/红土地-缺水.png index 24ba85e..5c215a5 100644 Binary files a/resource/soil/红土地-缺水.png and b/resource/soil/红土地-缺水.png differ diff --git a/resource/soil/红土地.png b/resource/soil/红土地.png index c589d03..b3b88af 100644 Binary files a/resource/soil/红土地.png and b/resource/soil/红土地.png differ diff --git a/resource/soil/蓝晶土地-施肥.png b/resource/soil/蓝晶土地-施肥.png index 32205fd..9babc68 100644 Binary files a/resource/soil/蓝晶土地-施肥.png and b/resource/soil/蓝晶土地-施肥.png differ diff --git a/resource/soil/蓝晶土地-缺水.png b/resource/soil/蓝晶土地-缺水.png index 44287e2..3ee150e 100644 Binary files a/resource/soil/蓝晶土地-缺水.png and b/resource/soil/蓝晶土地-缺水.png differ diff --git a/resource/soil/蓝晶土地.png b/resource/soil/蓝晶土地.png index 48d9fe4..12265e4 100644 Binary files a/resource/soil/蓝晶土地.png and b/resource/soil/蓝晶土地.png differ diff --git a/resource/soil/金土地-施肥.png b/resource/soil/金土地-施肥.png index e469f94..3be5ea6 100644 Binary files a/resource/soil/金土地-施肥.png and b/resource/soil/金土地-施肥.png differ diff --git a/resource/soil/金土地-缺水.png b/resource/soil/金土地-缺水.png index 97d2344..4d64ec0 100644 Binary files a/resource/soil/金土地-缺水.png and b/resource/soil/金土地-缺水.png differ diff --git a/resource/soil/金土地.png b/resource/soil/金土地.png index 7e5e7c7..9ea4f17 100644 Binary files a/resource/soil/金土地.png and b/resource/soil/金土地.png differ diff --git a/resource/soil/黑土地-施肥.png b/resource/soil/黑土地-施肥.png index 691aff3..7d8cd3c 100644 Binary files a/resource/soil/黑土地-施肥.png and b/resource/soil/黑土地-施肥.png differ diff --git a/resource/soil/黑土地-缺水.png b/resource/soil/黑土地-缺水.png index e3be4dd..bd7601f 100644 Binary files a/resource/soil/黑土地-缺水.png and b/resource/soil/黑土地-缺水.png differ diff --git a/resource/soil/黑土地.png b/resource/soil/黑土地.png index f096f61..a8a0991 100644 Binary files a/resource/soil/黑土地.png and b/resource/soil/黑土地.png differ diff --git a/tool.py b/tool.py index ffd6190..09c0e90 100644 --- a/tool.py +++ b/tool.py @@ -40,89 +40,17 @@ class CToolManager: cleaned = username.strip() # 允许的字符白名单(可自定义扩展) + # fmt: off safe_chars = { - "_", - "-", - "!", - "@", - "#", - "$", - "%", - "^", - "&", - "*", - "(", - ")", - "+", - "=", - ".", - ",", - "~", - "·", - " ", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "H", - "I", - "J", - "K", - "L", - "M", - "N", - "O", - "P", - "Q", - "R", - "S", - "T", - "U", - "V", - "W", - "X", - "Y", - "Z", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", + "_", "-", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", + "+", "=", ".", ",", "~", "·", " ", + "a","b","c","d","e","f","g","h","i","j","k","l","m", + "n","o","p","q","r","s","t","u","v","w","x","y","z", + "A","B","C","D","E","F","G","H","I","J","K","L","M", + "N","O","P","Q","R","S","T","U","V","W","X","Y","Z", + "0","1","2","3","4","5","6","7","8","9", } + # fmt: on # 添加常用中文字符(Unicode范围) safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF + 1))