✨ 新增在线查漏补缺功能
19
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] 在线更新作物信息
|
||||
- [ ] 添加渔场功能
|
||||
- [ ] 增加活动、交易行功能
|
||||
- [ ] 增加交易行总行功能
|
||||
|
||||
15
__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)
|
||||
|
||||
37
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="我的农场",
|
||||
|
||||
15
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%",
|
||||
},
|
||||
}
|
||||
|
||||
@ -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":{}}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}";'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
141
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()
|
||||
|
||||
203
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()
|
||||
|
||||
6
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:
|
||||
|
||||
14
log/log.md
@ -1,5 +1,19 @@
|
||||
# 真寻农场更新日志
|
||||
|
||||
## V1.5
|
||||
用户方面
|
||||
---
|
||||
- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
|
||||
- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
|
||||
- 定时更新签到文件、作物资源从00:30调整至04:30
|
||||
- 修正了部分土地资源错误的情况
|
||||
- 修正了部分文本信息错误的情况
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 修正部分事件连接机制
|
||||
- 修正网络请求端口
|
||||
|
||||
## V1.4
|
||||
用户方面
|
||||
---
|
||||
|
||||
151
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()
|
||||
|
||||
1
resource/db/version.json
Normal file
@ -0,0 +1 @@
|
||||
{"version": 0.41}
|
||||
BIN
resource/plant/南瓜/1.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB |
BIN
resource/plant/双玉兰/1.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
resource/plant/双玉兰/2.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
resource/plant/双玉兰/3.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
resource/plant/双玉兰/4.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
resource/plant/双玉兰/5.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
resource/plant/双玉兰/icon.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
BIN
resource/plant/大白菜/5.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
resource/plant/红叶梅/1.png
Normal file
|
After Width: | Height: | Size: 966 B |
BIN
resource/plant/红叶梅/2.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resource/plant/红叶梅/3.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
resource/plant/红叶梅/4.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
resource/plant/红叶梅/5.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
resource/plant/红叶梅/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
resource/plant/红毛丹/1.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
resource/plant/红毛丹/2.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
resource/plant/红毛丹/3.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
resource/plant/红毛丹/4.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
resource/plant/红毛丹/5.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
resource/plant/红毛丹/icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
resource/plant/鸾风玉/1.png
Normal file
|
After Width: | Height: | Size: 1019 B |
BIN
resource/plant/鸾风玉/2.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
resource/plant/鸾风玉/3.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
resource/plant/鸾风玉/4.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
resource/plant/鸾风玉/5.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
resource/plant/鸾风玉/icon.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
resource/plant/黄豆/1.png
Normal file
|
After Width: | Height: | Size: 822 B |
|
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
90
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))
|
||||
|
||||
|
||||