Compare commits

..

No commits in common. "main" and "v1.4" have entirely different histories.
main ... v1.4

492 changed files with 942 additions and 2534 deletions

197
.gitignore vendored
View File

@ -1,196 +1,3 @@
/config/sign_in.json
__pycache__
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the enitre vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
./config/sign_in.json

View File

@ -8,9 +8,6 @@
<a href="https://www.python.org">
<img src="https://img.shields.io/badge/Python-3.10%2B-blue" alt="Python">
</a>
<a href="https://github.com/astral-sh/ruff">
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="ruff">
</a>
<a href="https://nonebot.dev/">
<img src="https://img.shields.io/badge/Nonebot-2.0.0%2B-black" alt="Nonebot">
</a>
@ -34,7 +31,6 @@
## 目录
- [真寻农场(zhenxun\_plugin\_farm)](#真寻农场zhenxun_plugin_farm)
- [目录](#目录)
- [农场界面](#农场界面)
- [如何安装](#如何安装)
- [使用指令](#使用指令)
- [更新日志(详细)](#更新日志详细)
@ -47,12 +43,6 @@
---
## 农场界面
![农场界面](./resource/1.png)
---
## 如何安装
方法一(推荐):在小真寻后台的插件商店下载即可<br>
@ -76,34 +66,35 @@
| 铲除 | 铲除荒废作物 | |
| 我的作物 | 你的作物 | |
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 |
| 偷菜 @美波理 | 偷别人的菜 | 每人每天只能偷5次 |
| @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 |
| 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% |
| 更改农场名 [新的农场名] | 改名 | 农场名称无法存储特殊字符 |
| 农场签到 | 签到 | 需要注意,该项会从服务器拉取签到数据 |
| 土地升级 [地块ID] | 将土地升级,带来收益提升 | 如果土地升级时,土地有播种作物,那么将直接成熟 |
---
## 更新日志[(详细)](./log/log.md)
用户方面
---
- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
- 定时更新签到文件、作物资源从00:30调整至04:30
- 修正了部分土地资源错误的情况
- 修正了部分文本信息错误的情况
- 新增种子商店筛选功能如果没有BUG的话后续我的种子、我的作物等也会加入筛选功能
- 新增签到功能(测试
- 新增农场详述功能,能通过该功能更加详细的观看农场数据
- 修复了迁移旧数据库无法正常迁移的BUG
- 修复了偷菜会导致偷自己的BUG
- 修正了作物阶段绘制不正确的BUG
代码方面
---
- 修正部分事件连接机制
- 修正网络请求端口
- 修正了多阶段作物成长素材计算逻辑
- 修正了作物数据库字段错乱的问题
- 作物新增offset字段用于以后偏移坐标和大小(尚未启用,该模式有商议
---
## 待办事宜 `Todo` 列表
- [x] 完善我的农场图片,例如左上角显示用户数据
- [ ] 完善升级数据、作物数据、作物图片
- [x] 签到功能
- [x] 在线更新作物信息
- [ ] 添加渔场功能
- [ ] 增加活动、交易行功能
- [ ] 增加交易行总行功能

View File

@ -9,7 +9,6 @@ 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
@ -23,7 +22,6 @@ __plugin_meta__ = PluginMetadata(
指令
at 开通农场
我的农场
农场详述
我的农场币
种子商店 [筛选关键字] [页数] or [页数]
购买种子 [作物/种子名称] [数量]
@ -38,11 +36,10 @@ __plugin_meta__ = PluginMetadata(
购买农场币 [数量] 数量为消耗金币的数量
更改农场名 [新农场名]
农场签到
土地升级 [地块ID]通过农场详述获取
""".strip(),
extra=PluginExtraData(
author="Art_Sakura",
version="1.5.2",
version="1.4",
commands=[Command(command="我的农场")],
menu_type="群内小游戏",
configs=[
@ -58,12 +55,6 @@ __plugin_meta__ = PluginMetadata(
help="金币兑换农场币的倍数 默认值为: 2倍",
default_value="2",
),
RegisterConfig(
key="点券兑换倍数",
value="2000",
help="农场币兑换点券的倍数 比例为2000:1",
default_value="2000",
),
RegisterConfig(
key="手续费",
value="0.2",
@ -75,8 +66,8 @@ __plugin_meta__ = PluginMetadata(
value="http://diuse.work",
help="签到、交易行、活动等服务器地址",
default_value="http://diuse.work",
),
],
)
]
).to_dict(),
)
driver = get_driver()
@ -93,10 +84,6 @@ async def start():
await g_pDBService.init()
# 检查作物文件是否缺失 or 更新
await g_pRequestManager.initPlantDBFile()
# 析构函数
@driver.on_shutdown
async def shutdown():
@ -105,10 +92,14 @@ async def shutdown():
await g_pDBService.cleanup()
@scheduler.scheduled_job(trigger="cron", hour=4, minute=30, id="signInFile")
@scheduler.scheduled_job(
trigger="cron",
hour=0,
minute=30,
id="signInFile"
)
async def signInFile():
try:
await g_pJsonManager.initSignInFile()
await g_pRequestManager.initPlantDBFile()
except Exception as e:
logger.error("农场定时检查出错", e=e)
except:
logger.info("农场签到文件下载失败!")

View File

@ -1,20 +1,10 @@
import inspect
from nonebot.adapters import Event
from nonebot.adapters import Event, MessageTemplate
from nonebot.rule import to_me
from nonebot_plugin_alconna import (
Alconna,
AlconnaQuery,
Args,
At,
Match,
MultiVar,
Option,
Query,
Subcommand,
on_alconna,
store_true,
)
from nonebot_plugin_alconna import (Alconna, AlconnaMatch, AlconnaQuery, Args,
Arparma, At, Match, MultiVar, Option,
Query, Subcommand, on_alconna, store_true)
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_waiter import waiter
@ -22,55 +12,114 @@ from zhenxun.configs.config import BotConfig
from zhenxun.services.log import logger
from zhenxun.utils.message import MessageUtils
from .config import g_bSignStatus, g_sTranslation
from .config import g_bSignStatus
from .dbService import g_pDBService
from .farm.farm import g_pFarmManager
from .farm.shop import g_pShopManager
from .json import g_pJsonManager
from .tool import g_pToolManager
async def isRegisteredByUid(uid: str) -> bool:
result = await g_pDBService.user.isUserExist(uid)
if not result:
await MessageUtils.build_message("尚未开通农场快at我发送 开通农场 开通吧").send()
return False
return True
diuse_register = on_alconna(
Alconna("开通农场"),
priority=5,
rule=to_me(),
block=True,
use_cmd_start=True,
)
@diuse_register.handle()
async def handle_register(session: Uninfo):
uid = str(session.user.id)
user = await g_pDBService.user.getUserInfoByUid(uid)
if user:
await MessageUtils.build_message(g_sTranslation["register"]["repeat"]).send(
reply_to=True
)
await MessageUtils.build_message("🎉 您已经开通农场啦~").send(reply_to=True)
return
try:
raw_name = str(session.user.name)
safe_name = g_pToolManager.sanitize_username(raw_name)
safe_name = sanitize_username(raw_name)
# 初始化用户信息
success = await g_pDBService.user.initUserInfoByUid(
uid=uid, name=safe_name, exp=0, point=500
uid=uid,
name=safe_name,
exp=0,
point=500
)
msg = (
g_sTranslation["register"]["success"].format(point=500)
"✅ 农场开通成功!\n💼 初始资金500农场币"
if success
else g_sTranslation["register"]["error"]
else "⚠️ 开通失败,请稍后再试"
)
logger.info(f"用户注册 {'成功' if success else '失败'}{uid}")
except Exception as e:
msg = g_sTranslation["register"]["error"]
logger.error(f"注册异常 | UID:{uid} | 错误:{e}")
msg = "⚠️ 系统繁忙,请稍后再试"
logger.error(f"注册异常 | UID:{uid} | 错误:{str(e)}")
await MessageUtils.build_message(msg).send(reply_to=True)
def sanitize_username(username: str, max_length: int = 15) -> str:
"""
安全处理用户名
功能
1. 移除首尾空白
2. 过滤危险字符
3. 转义单引号
4. 处理空值
5. 限制长度
"""
# 处理空值
if not username:
return "神秘农夫"
# 基础清洗
cleaned = username.strip()
# 允许的字符白名单(可自定义扩展)
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',
}
# 添加常用中文字符Unicode范围
safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF+1))
# 过滤危险字符
filtered = [
c if c in safe_chars or 0x4E00 <= ord(c) <= 0x9FFF
else ''
for c in cleaned
]
# 合并处理结果
safe_str = ''.join(filtered)
# 转义单引号(双重保障)
escaped = safe_str.replace("'", "''")
# 处理空结果
if not escaped:
return "神秘农夫"
# 长度限制
return escaped[:max_length]
diuse_farm = on_alconna(
Alconna(
@ -85,8 +134,6 @@ diuse_farm = on_alconna(
Subcommand("harvest", help_text="收获"),
Subcommand("eradicate", help_text="铲除"),
Subcommand("my-plant", help_text="我的作物"),
Subcommand("lock-plant", Args["name?", str], help_text="作物加锁"),
Subcommand("unlock-plant", Args["name?", str], help_text="作物解锁"),
Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"),
Subcommand("stealing", Args["target?", At], help_text="偷菜"),
Subcommand("buy-point", Args["num?", int], help_text="购买农场币"),
@ -94,26 +141,21 @@ diuse_farm = on_alconna(
Subcommand("change-name", Args["name?", str], help_text="更改农场名"),
Subcommand("sign-in", help_text="农场签到"),
Subcommand("admin-up", Args["num?", int], help_text="农场下阶段"),
Subcommand("point-to-vipPoint", Args["num?", int], help_text="点券兑换"),
Subcommand("my-vipPoint", help_text="我的点券"),
),
priority=5,
block=True,
use_cmd_start=True,
)
@diuse_farm.assign("$main")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
image = await g_pFarmManager.drawFarmByUid(uid)
await MessageUtils.build_message(image).send(reply_to=True)
diuse_farm.shortcut(
"农场详述",
command="我的农场",
@ -121,20 +163,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("detail")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
info = await g_pFarmManager.drawDetailFarmByUid(uid)
await MessageUtils.alc_forward_msg(
[info], session.self_id, BotConfig.self_nickname
).send()
await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send()
diuse_farm.shortcut(
"我的农场币",
@ -143,20 +181,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("my-point")
async def _(session: Uninfo):
uid = str(session.user.id)
point = await g_pDBService.user.getUserPointByUid(uid)
if point < 0:
await MessageUtils.build_message(g_sTranslation["basic"]["notFarm"]).send()
await MessageUtils.build_message("尚未开通农场快at我发送 开通农场 开通吧").send()
return False
await MessageUtils.build_message(
g_sTranslation["basic"]["point"].format(point=point)
).send(reply_to=True)
await MessageUtils.build_message(f"你的当前农场币为: {point}").send(reply_to=True)
diuse_farm.shortcut(
"种子商店(.*?)",
@ -165,12 +199,11 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("seed-shop")
async def _(session: Uninfo, res: Match[tuple[str, ...]]):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
if res.result is inspect._empty:
@ -188,12 +221,7 @@ async def _(session: Uninfo, res: Match[tuple[str, ...]]):
else:
filterKey = first
if (
len(raw) >= 2
and raw[1] is not None
and isinstance(raw[1], str)
and raw[1].isdigit()
):
if len(raw) >= 2 and raw[1] is not None and isinstance(raw[1], str) and raw[1].isdigit():
page = int(raw[1])
if filterKey is None:
@ -203,7 +231,6 @@ async def _(session: Uninfo, res: Match[tuple[str, ...]]):
await MessageUtils.build_message(image).send()
diuse_farm.shortcut(
"购买种子(?P<name>.*?)",
command="我的农场",
@ -211,25 +238,21 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("buy-seed")
async def _(
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1)
):
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1)):
if not name.available:
await MessageUtils.build_message(g_sTranslation["buySeed"]["notSeed"]).finish(
reply_to=True
)
await MessageUtils.build_message(
"请在指令后跟需要购买的种子名称"
).finish(reply_to=True)
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pShopManager.buySeed(uid, name.result, num.result)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"我的种子",
command="我的农场",
@ -237,18 +260,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("my-seed")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pFarmManager.getUserSeedByUid(uid)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"播种(?P<name>.*?)",
command="我的农场",
@ -256,19 +277,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("sowing")
async def _(
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)
):
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)):
if not name.available:
await MessageUtils.build_message(g_sTranslation["sowing"]["notSeed"]).finish(
reply_to=True
)
await MessageUtils.build_message(
"请在指令后跟需要播种的种子名称"
).finish(reply_to=True)
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pFarmManager.sowing(uid, name.result, num.result)
@ -282,18 +300,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("harvest")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pFarmManager.harvest(uid)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"铲除",
command="我的农场",
@ -301,12 +317,11 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("eradicate")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pFarmManager.eradicate(uid)
@ -320,94 +335,32 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("my-plant")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pFarmManager.getUserPlantByUid(uid)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"作物加锁(?P<name>)",
command="我的农场",
arguments=["lock-plant", "{name}"],
prefix=True,
)
@diuse_farm.assign("lock-plant")
async def _(session: Uninfo, name: Match[str]):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 1)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"作物解锁(?P<name>)",
command="我的农场",
arguments=["unlock-plant", "{name}"],
prefix=True,
)
@diuse_farm.assign("unlock-plant")
async def _(session: Uninfo, name: Match[str]):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 0)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"出售作物(?P<name>.*?)",
command="我的农场",
arguments=["sell-plant", "{name}"],
prefix=True,
)
@diuse_farm.assign("sell-plant")
async def _(
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)
):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result)
await MessageUtils.build_message(result).send(reply_to=True)
reclamation = on_alconna(
Alconna("开垦"),
priority=5,
block=True,
use_cmd_start=True,
)
@reclamation.handle()
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
condition = await g_pFarmManager.reclamationCondition(uid)
condition += f"\n{g_sTranslation['reclamation']['confirm']}"
condition += "\n 回复是将执行开垦"
await MessageUtils.build_message(condition).send(reply_to=True)
@waiter(waits=["message"], keep_session=True)
@ -416,9 +369,7 @@ async def _(session: Uninfo):
resp = await check.wait(timeout=60)
if resp is None:
await MessageUtils.build_message(g_sTranslation["reclamation"]["timeOut"]).send(
reply_to=True
)
await MessageUtils.build_message("等待超时").send(reply_to=True)
return
if not resp == "":
return
@ -426,6 +377,22 @@ async def _(session: Uninfo):
res = await g_pFarmManager.reclamation(uid)
await MessageUtils.build_message(res).send(reply_to=True)
diuse_farm.shortcut(
"出售作物(?P<name>.*?)",
command="我的农场",
arguments=["sell-plant", "{name}"],
prefix=True,
)
@diuse_farm.assign("sell-plant")
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)):
uid = str(session.user.id)
if not await isRegisteredByUid(uid):
return
result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"偷菜",
@ -434,32 +401,26 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("stealing")
async def _(session: Uninfo, target: Match[At]):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
if not target.available:
await MessageUtils.build_message(g_sTranslation["stealing"]["noTarget"]).finish(
reply_to=True
)
await MessageUtils.build_message("请在指令后跟需要at的人").finish(reply_to=True)
tar = target.result
result = await g_pDBService.user.isUserExist(tar.target)
if not result:
await MessageUtils.build_message(
g_sTranslation["stealing"]["targetNotFarm"]
).send()
await MessageUtils.build_message("目标尚未开通农场快邀请ta开通吧").send()
return None
result = await g_pFarmManager.stealing(uid, tar.target)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"购买农场币(.*?)",
command="我的农场",
@ -467,17 +428,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("buy-point")
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
if num.result <= 0:
await MessageUtils.build_message("请在指令后跟需要购买农场币的数量").finish(
reply_to=True
)
await MessageUtils.build_message(
"请在指令后跟需要购买农场币的数量"
).finish(reply_to=True)
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
result = await g_pFarmManager.buyPointByUid(uid, num.result)
@ -491,38 +451,30 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("change-name")
async def _(session: Uninfo, name: Match[str]):
if not name.available:
await MessageUtils.build_message(g_sTranslation["changeName"]["noName"]).finish(
reply_to=True
)
await MessageUtils.build_message(
"请在指令后跟需要更改的农场名"
).finish(reply_to=True)
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
safeName = g_pToolManager.sanitize_username(name.result)
safeName = sanitize_username(name.result)
if safeName == "神秘农夫":
await MessageUtils.build_message(g_sTranslation["changeName"]["error"]).send(
reply_to=True
)
await MessageUtils.build_message("农场名不支持特殊符号!").send(reply_to=True)
return
result = await g_pDBService.user.updateUserNameByUid(uid, safeName)
if result:
await MessageUtils.build_message(g_sTranslation["changeName"]["success"]).send(
reply_to=True
)
if result == True:
await MessageUtils.build_message("更新农场名成功!").send(reply_to=True)
else:
await MessageUtils.build_message(g_sTranslation["changeName"]["error1"]).send(
reply_to=True
)
await MessageUtils.build_message("更新农场名失败!").send(reply_to=True)
diuse_farm.shortcut(
"农场签到",
@ -531,17 +483,16 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("sign-in")
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
#判断签到是否正常加载
if not g_bSignStatus:
await MessageUtils.build_message(g_sTranslation["signIn"]["error"]).send()
await MessageUtils.build_message("签到功能异常!").send()
return
@ -552,86 +503,36 @@ async def _(session: Uninfo):
#如果完成签到
if status == 1 or status == 2:
#获取签到总天数
signDay = await g_pDBService.userSign.getUserSignCountByDate(
uid, toDay.strftime("%Y-%m")
)
exp, point = await g_pDBService.userSign.getUserSignRewardByDate(
uid, toDay.strftime("%Y-%m-%d")
)
signDay = await g_pDBService.userSign.getUserSignCountByDate(uid, toDay.strftime("%Y-%m"))
exp, point = await g_pDBService.userSign.getUserSignRewardByDate(uid, toDay.strftime("%Y-%m-%d"))
message += g_sTranslation["signIn"]["success"].format(
day=signDay, exp=exp, num=point
)
message += f"签到成功!累计签到天数:{signDay}\n获得经验{exp},获得金币{point}"
reward = g_pJsonManager.m_pSign["continuou"].get(f"{signDay}", None)
reward = g_pJsonManager.m_pSign['continuou'].get(f"{signDay}", None)
if reward:
extraPoint = reward.get("point", 0)
extraExp = reward.get("exp", 0)
extraPoint = reward.get('point', 0)
extraExp = reward.get('exp', 0)
plant = reward.get("plant", {})
plant = reward.get('plant', {})
message += g_sTranslation["signIn"]["grandTotal"].format(
exp=extraExp, num=extraPoint
)
message += f"\n\n成功领取累计签到奖励:\n额外获得经验{extraExp},额外获得金币{extraPoint}"
vipPoint = reward.get("vipPoint", 0)
vipPoint = reward.get('vipPoint', 0)
if vipPoint > 0:
message += g_sTranslation["signIn"]["grandTotal1"].format(num=vipPoint)
message += f",额外获得点券{vipPoint}"
if plant:
for key, value in plant.items():
message += g_sTranslation["signIn"]["grandTotal2"].format(
name=key, num=value
)
message += f"\n获得{key}种子 * {value}"
else:
message = g_sTranslation["signIn"]["error1"]
message = "签到失败!未知错误"
await MessageUtils.build_message(message).send()
# 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,
use_cmd_start=True,
)
@soil_upgrade.handle()
async def _(session: Uninfo, index: Query[int] = AlconnaQuery("index", 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)
if not condition.startswith("将土地升级至:"):
return
@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="我的农场",
@ -639,57 +540,11 @@ diuse_farm.shortcut(
prefix=True,
)
@diuse_farm.assign("admin-up")
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
if not await isRegisteredByUid(uid):
return
await g_pDBService.userSoil.nextPhase(uid, num.result)
diuse_farm.shortcut(
"点券兑换(.*?)",
command="我的农场",
arguments=["point-to-vipPoint"],
prefix=True,
)
@diuse_farm.assign("point-to-vipPoint")
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
if num.result <= 0:
await MessageUtils.build_message("请在指令后跟需要购买点券的数量").finish(
reply_to=True
)
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
result = await g_pFarmManager.pointToVipPointByUid(uid, num.result)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"我的点券",
command="我的农场",
arguments=["my-vipPoint"],
prefix=True,
)
@diuse_farm.assign("my-vipPoint")
async def _(session: Uninfo):
uid = str(session.user.id)
vipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
if not await g_pToolManager.isRegisteredByUid(uid):
return
await MessageUtils.build_message(
g_sTranslation["basic"]["vipPoint"].format(vipPoint=vipPoint)
).send(reply_to=True)

119
config.py
View File

@ -2,132 +2,15 @@ from pathlib import Path
from zhenxun.configs.path_config import DATA_PATH
# 签到状态
g_bSignStatus = True
# 是否处于Debug模式
g_bIsDebug = False
# 数据库文件目录
g_sDBPath = DATA_PATH / "farm_db"
# 数据库文件路径
g_sDBFilePath = DATA_PATH / "farm_db/farm.db"
# 农场资源文件目录
g_sResourcePath = Path(__file__).resolve().parent / "resource"
# 农场作物数据库
g_sPlantPath = g_sResourcePath / "db/plant.db"
# 农场配置文件目录
g_sConfigPath = Path(__file__).resolve().parent / "config"
# 农场签到文件路径
g_sSignInPath = g_sConfigPath / "sign_in.json"
# 土地等级上限
g_iSoilLevelMax = 3
# 农场同一文本
g_sTranslation = {
"basic": {
"notFarm": "尚未开通农场快at我发送 开通农场 开通吧 🌱🚜",
"point": "你的当前农场币为: {point} 🌾💰",
"vipPoint": "你的当前点券为: {vipPoint} 🌾💰",
},
"register": {
"success": "✅ 农场开通成功!\n💼 初始资金:{point}农场币 🥳🎉",
"repeat": "🎉 您已经开通农场啦~ 😄",
"error": "⚠️ 开通失败,请稍后再试 💔",
},
"buySeed": {
"notSeed": "🌱 请在指令后跟需要购买的种子名称",
"notNum": "❗️ 请输入购买数量!",
"noLevel": "🔒 你的等级不够哦,努努力吧 💪",
"noPoint": "💰 你的农场币不够哦~ 快速速氪金吧!💸",
"noVipPoint": "💰 你的点券不够哦~ 快速速氪金吧!💸",
"success": "✅ 成功购买{name},花费{total}农场币,剩余{point}农场币 🌾",
"vipSuccess": "✅ 成功购买{name},花费{total}点券,剩余{point}点券 🌾",
"errorSql": "❌ 购买失败,执行数据库错误!🛑",
"error": "❌ 购买出错!请检查需购买的种子名称!🔍",
},
"sowing": {
"notSeed": "🌱 请在指令后跟需要播种的种子名称",
"noSeed": "❌ 没有在你的仓库发现{name}种子,快去买点吧!🛒",
"noNum": "⚠️ 播种失败!仓库中的{name}种子数量不足,当前剩余{num}个种子 🍂",
"success": "✅ 播种{name}成功!仓库剩余{num}个种子 🌱",
"success2": "✅ 播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库剩余{num}个种子 🌾",
"error": "❌ 播种失败,请稍后重试!⏳",
},
"harvest": {
"append": "🌾 收获作物:{name},数量为:{num},经验为:{exp}",
"exp": "✨ 累计获得经验:{exp} 📈",
"no": "🤷‍♂️ 没有可收获的作物哦~ 不要试图拔苗助长 🚫",
"error": "❌ 收获失败,请稍后重试!⏳",
},
"eradicate": {
"success": "🗑️ 成功铲除荒废作物,累计获得经验:{exp}",
"error": "❌ 没有可以铲除的作物 🚜",
},
"reclamation": {
"confirm": "⚠️ 回复“是”将执行开垦 ⛏️",
"timeOut": "⏰ 等待开垦回复超时,请重试",
"perfect": "🌟 你已经开垦了全部土地 🎉",
"next": "🔜 下次开垦所需条件:等级:{level},农场币:{num} 💰",
"next2": "🔜 下次开垦所需条件:等级:{level},农场币:{num},物品:{item} 📦",
"nextLevel": "📈 当前用户等级{level},升级所需等级为{next}",
"noNum": "💰 当前用户农场币不足,升级所需农场币为{num} 💸",
"success": "✅ 开垦土地成功!🌱",
"error": "❌ 获取开垦土地条件失败!",
"error1": "❌ 执行开垦失败!",
"error2": "❌ 未知错误{e}💥",
},
"sellPlant": {
"no": "🤷‍♀️ 你仓库没有可以出售的作物 🌾",
"success": "💰 成功出售所有作物,获得农场币:{point},当前农场币:{num} 🎉",
"success1": "💰 成功出售{name},获得农场币:{point},当前农场币:{num} 🥳",
"error": "❌ 出售作物{name}出错:仓库中不存在该作物 🚫",
"error1": "❌ 出售作物{name}出错:数量不足 ⚠️",
},
"stealing": {
"noTarget": "🎯 请在指令后跟需要at的人 👤",
"targetNotFarm": "🚜 目标尚未开通农场快邀请ta开通吧 😉",
"max": "❌ 你今天可偷次数到达上限啦,手下留情吧 🙏",
"info": "🤫 成功偷到作物:{name},数量为:{num} 🍒",
"noPlant": "🌱 目标没有作物可以被偷 🌾",
"repeat": "🚫 你已经偷过目标啦,请手下留情 🙏",
},
"changeName": {
"noName": "✏️ 请在指令后跟需要更改的农场名",
"success": "✅ 更新农场名成功!🎉",
"error": "❌ 农场名不支持特殊符号!🚫",
"error1": "❌ 更新农场名失败!💔",
},
"signIn": {
"success": "📝 签到成功!累计签到天数:{day}\n🎁 获得经验{exp},获得金币{num} 💰",
"grandTotal": "\n🎉 成功领取累计签到奖励:\n✨ 额外获得经验{exp},额外获得金币{num} 🥳",
"grandTotal1": ",额外获得点券{num} 🎫",
"grandTotal2": "\n🌱 获得{name}种子 * {num} 🌟",
"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%",
},
"lockPlant": {
"noPlant": "很抱歉,你的作物仓库中没有该作物",
"lockPlant": "加锁成功,现在{name}不会被一键售卖了",
"unlockPlant": "解锁成功,现在{name}会被一键售卖了",
"error": "未知错误",
},
}
g_bSignStatus = True

78
config/sign_in.json Normal file
View File

@ -0,0 +1,78 @@
{
"date": "202505",
"exp_max": 50,
"exp_min": 5,
"point_max": 2000,
"point_min": 200,
"continuou":
{
"1":
{
"point": 3000,
"exp": 20
},
"3":
{
"point": 5000,
"exp": 25,
"plant":
{
"胡萝卜": 3
}
},
"5":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"7":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"10":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"15":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"20":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"25":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
}
}
}

View File

@ -153,104 +153,5 @@
"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":{}}
]
}
}

View File

@ -1,7 +1,8 @@
from contextlib import asynccontextmanager
import math
import os
from pathlib import Path
import re
from contextlib import asynccontextmanager
from pathlib import Path
import aiosqlite
@ -45,7 +46,7 @@ class CSqlManager:
@classmethod
async def getTableInfo(cls, tableName: str) -> list:
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", tableName):
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', tableName):
raise ValueError(f"Illegal table name: {tableName}")
try:
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")')
@ -69,7 +70,7 @@ class CSqlManager:
"""
info = await cls.getTableInfo(tableName)
existing = {col["name"]: col["type"].upper() for col in info}
existing = {col['name']: col['type'].upper() for col in info}
desired = {k: v.upper() for k, v in columns.items() if k != "PRIMARY KEY"}
primaryKey = columns.get("PRIMARY KEY", "")
@ -82,9 +83,7 @@ class CSqlManager:
toAdd = [k for k in desired if k not in existing]
toRemove = [k for k in existing if k not in desired]
typeMismatch = [
k for k in desired if k in existing and existing[k] != desired[k]
]
typeMismatch = [k for k in desired if k in existing and existing[k] != desired[k]]
if toAdd and not toRemove and not typeMismatch:
for col in toAdd:
@ -103,18 +102,11 @@ class CSqlManager:
commonCols = [k for k in desired if k in existing]
if commonCols:
colsStr = ", ".join(f'"{c}"' for c in commonCols)
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}";'
f'INSERT INTO "{tmpTable}" ({colsStr}) SELECT {colsStr} FROM "{tableName}";'
)
await cls.m_pDB.execute(f'DROP TABLE "{tableName}";')
await cls.m_pDB.execute(f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";')
return True
@classmethod
@ -139,5 +131,4 @@ class CSqlManager:
logger.warning(f"数据库语句执行出错: {command}", e=e)
return False
g_pSqlManager = CSqlManager()

View File

@ -1,13 +1,14 @@
from contextlib import asynccontextmanager
import ast
import os
import re
from contextlib import asynccontextmanager
from unittest import result
import aiosqlite
from zhenxun.configs.config import Config
from zhenxun.services.log import logger
from ..config import g_bIsDebug, g_sPlantPath, g_sResourcePath
from ..request import g_pRequestManager
from ..config import g_bIsDebug, g_sPlantPath
class CPlantManager:
@ -28,9 +29,7 @@ class CPlantManager:
_ = os.path.exists(g_sPlantPath)
if g_bIsDebug:
cls.m_pDB = await aiosqlite.connect(
str(g_sPlantPath.parent / "plant-test.db")
)
cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath.parent / "plant-test.db"))
else:
cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
@ -74,6 +73,7 @@ class CPlantManager:
logger.warning(f"数据库语句执行出错: {command}", e=e)
return False
@classmethod
async def getPlantByName(cls, name: str) -> dict | None:
"""根据作物名称查询记录
@ -113,17 +113,14 @@ class CPlantManager:
if not row:
return []
phase = row[0].split(",")
phase = row[0].split(',')
seen = set()
result = []
for x in phase:
num = int(x)
if num not in seen:
seen.add(num)
result.append(num)
if x not in seen:
seen.add(int(x))
result.append(int(x))
return result
except Exception as e:
@ -149,7 +146,7 @@ class CPlantManager:
if not row:
return -1
phase = row[0].split(",")
phase = row[0].split(',')
#去重
seen = set()
@ -184,7 +181,7 @@ class CPlantManager:
if not row:
return -1
phase = row[0].split(",")
phase = row[0].split(',')
again = phase[-1] - phase[3] / 60 / 60
return again
@ -241,53 +238,9 @@ class CPlantManager:
async def listPlants(cls) -> list[dict]:
"""查询所有作物记录"""
try:
async with cls.m_pDB.execute(
"SELECT * FROM plant ORDER BY level"
) as cursor:
async with cls.m_pDB.execute("SELECT * FROM plant ORDER BY level") as cursor:
rows = await cursor.fetchall()
return [dict(r) for r in rows]
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

View File

@ -1,4 +1,6 @@
import math
from typing import List, Union
from unittest import result
from zhenxun.services.log import logger
@ -18,14 +20,12 @@ class CUserDB(CSqlManager):
"vipPoint": "INTEGER DEFAULT 0", #点券
"soil": "INTEGER DEFAULT 3", #解锁土地数量
"stealTime": "TEXT DEFAULT ''", #偷菜时间字符串
"stealCount": "INTEGER DEFAULT 0", # 剩余偷菜次数
"stealCount": "INTEGER DEFAULT 0" #剩余偷菜次数
}
await cls.ensureTableSchema("user", userInfo)
@classmethod
async def initUserInfoByUid(
cls, uid: str, name: str = "", exp: int = 0, point: int = 500
) -> bool | str:
async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500) -> Union[bool, str]:
"""初始化用户信息,包含初始偷菜时间字符串与次数
Args:
@ -35,9 +35,9 @@ class CUserDB(CSqlManager):
point (int): 农场币
Returns:
bool | str: False 表示失败字符串表示成功信息
Union[bool, str]: False 表示失败字符串表示成功信息
"""
nowStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
nowStr = g_pToolManager.dateTime().date().today().strftime('%Y-%m-%d')
sql = (
f"INSERT INTO user (uid, name, exp, point, soil, stealTime, stealCount) "
f"VALUES ({uid}, '{name}', {exp}, {point}, 3, '{nowStr}', 5)"
@ -51,11 +51,11 @@ class CUserDB(CSqlManager):
return False
@classmethod
async def getAllUsers(cls) -> list[str]:
async def getAllUsers(cls) -> List[str]:
"""获取所有用户UID列表
Returns:
list[str]: 用户UID列表
List[str]: 用户UID列表
"""
cursor = await cls.m_pDB.execute("SELECT uid FROM user")
rows = await cursor.fetchall()
@ -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,8 +300,7 @@ 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
@ -396,7 +395,8 @@ class CUserDB(CSqlManager):
return ""
try:
async with cls.m_pDB.execute(
"SELECT stealTime FROM user WHERE uid = ?", (uid,)
"SELECT stealTime FROM user WHERE uid = ?", (uid,
)
) as cursor:
row = await cursor.fetchone()
return row[0] if row and row[0] else ""
@ -451,14 +451,11 @@ class CUserDB(CSqlManager):
return -1
@classmethod
async def updateStealCountByUid(
cls, uid: str, stealTime: str, stealCount: int
) -> bool:
async def updateStealCountByUid(cls, uid: str, stealCount: int) -> bool:
"""根据用户Uid更新剩余偷菜次数
Args:
uid (str): 用户Uid
stealTime (str): 偷菜日期
stealCount (int): 新剩余偷菜次数
Returns:
@ -470,8 +467,7 @@ class CUserDB(CSqlManager):
try:
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE user SET stealTime = ?, stealCount = ? WHERE uid = ?",
(stealTime, stealCount, uid),
"UPDATE user SET stealCount = ? WHERE uid = ?", (stealCount, uid)
)
return True
except Exception as e:

View File

@ -1,3 +1,5 @@
from typing import Optional
from zhenxun.services.log import logger
from .database import CSqlManager
@ -10,13 +12,13 @@ class CUserItemDB(CSqlManager):
"uid": "TEXT NOT NULL", #用户Uid
"item": "TEXT NOT NULL", #物品名称
"count": "INTEGER NOT NULL DEFAULT 0", #数量
"PRIMARY KEY": "(uid, item)",
"PRIMARY KEY": "(uid, item)"
}
await cls.ensureTableSchema("userItem", userItem)
@classmethod
async def getUserItemByName(cls, uid: str, item: str) -> int | None:
async def getUserItemByName(cls, uid: str, item: str) -> Optional[int]:
"""根据道具名称查询某一项数量
Args:
@ -30,12 +32,13 @@ class CUserItemDB(CSqlManager):
return None
try:
async with cls.m_pDB.execute(
"SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
"SELECT count FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
except Exception as e:
logger.warning("getUserItemByName查询失败", e=e)
logger.warning(f"getUserItemByName查询失败", e=e)
return None
@classmethod
@ -52,12 +55,13 @@ class CUserItemDB(CSqlManager):
return {}
try:
cursor = await cls.m_pDB.execute(
"SELECT item, count FROM userItem WHERE uid = ?", (uid,)
"SELECT item, count FROM userItem WHERE uid = ?",
(uid,)
)
rows = await cursor.fetchall()
return {row["item"]: row["count"] for row in rows}
except Exception as e:
logger.warning("getUserItemByUid查询失败", e=e)
logger.warning(f"getUserItemByUid查询失败", e=e)
return {}
@classmethod
@ -76,11 +80,12 @@ class CUserItemDB(CSqlManager):
try:
async with cls._transaction():
await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
"DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
)
return True
except Exception as e:
logger.warning("deleteUserItemByName失败", e=e)
logger.warning(f"deleteUserItemByName失败", e=e)
return False
@classmethod
@ -101,16 +106,17 @@ class CUserItemDB(CSqlManager):
async with cls._transaction():
if count <= 0:
await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
"DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
)
else:
await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
(count, uid, item),
(count, uid, item)
)
return True
except Exception as e:
logger.warning("updateUserItemByName失败", e=e)
logger.warning(f"updateUserItemByName失败", e=e)
return False
@classmethod
@ -130,7 +136,8 @@ class CUserItemDB(CSqlManager):
try:
async with cls._transaction():
async with cls.m_pDB.execute(
"SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
"SELECT count FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
) as cursor:
row = await cursor.fetchone()
@ -139,20 +146,20 @@ class CUserItemDB(CSqlManager):
if newCount <= 0:
await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item),
(uid, item)
)
else:
await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
(newCount, uid, item),
(newCount, uid, item)
)
else:
if count > 0:
await cls.m_pDB.execute(
"INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)",
(uid, item, count),
(uid, item, count)
)
return True
except Exception as e:
logger.warning("addUserItemByUid失败", e=e)
logger.warning(f"addUserItemByUid失败", e=e)
return False

View File

@ -1,3 +1,5 @@
from typing import Dict, Optional
from zhenxun.services.log import logger
from .database import CSqlManager
@ -10,8 +12,7 @@ class CUserPlantDB(CSqlManager):
"uid": "TEXT NOT NULL", #用户Uid
"plant": "TEXT NOT NULL", #作物名称
"count": "INTEGER NOT NULL DEFAULT 0", #数量
"isLock": "INTEGER NOT NULL DEFAULT 0", # 是否上锁 0=没有非0=有
"PRIMARY KEY": "(uid, plant)",
"PRIMARY KEY": "(uid, plant)"
}
await cls.ensureTableSchema("userPlant", userPlant)
@ -33,7 +34,7 @@ class CUserPlantDB(CSqlManager):
#检查是否已存在该作物
async with cls.m_pDB.execute(
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant),
(uid, plant)
) as cursor:
row = await cursor.fetchone()
@ -42,21 +43,21 @@ class CUserPlantDB(CSqlManager):
new_count = row[0] + count
await cls.m_pDB.execute(
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
(new_count, uid, plant),
(new_count, uid, plant)
)
else:
#如果作物不存在,则插入新记录
await cls.m_pDB.execute(
"INSERT INTO userPlant (uid, plant, count) VALUES (?, ?, ?)",
(uid, plant, count),
(uid, plant, count)
)
return True
except Exception as e:
logger.warning("addUserPlantByUid 失败!", e=e)
logger.warning(f"addUserPlantByUid 失败!", e=e)
return False
@classmethod
async def getUserPlantByUid(cls, uid: str) -> dict[str, int]:
async def getUserPlantByUid(cls, uid: str) -> Dict[str, int]:
"""根据用户uid获取全部作物信息
Args:
@ -66,13 +67,14 @@ class CUserPlantDB(CSqlManager):
Dict[str, int]: 作物名称和数量
"""
cursor = await cls.m_pDB.execute(
"SELECT plant, count FROM userPlant WHERE uid=?", (uid,)
"SELECT plant, count FROM userPlant WHERE uid=?",
(uid,)
)
rows = await cursor.fetchall()
return {row["plant"]: row["count"] for row in rows}
@classmethod
async def getUserPlantByName(cls, uid: str, plant: str) -> int | None:
async def getUserPlantByName(cls, uid: str, plant: str) -> Optional[int]:
"""根据作物名称获取用户的作物数量
Args:
@ -84,35 +86,15 @@ class CUserPlantDB(CSqlManager):
"""
try:
async with cls.m_pDB.execute(
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant)
) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
except Exception as e:
logger.warning("getUserPlantByName 查询失败!", e=e)
logger.warning(f"getUserPlantByName 查询失败!", e=e)
return None
@classmethod
async def checkUserPlantByName(cls, uid: str, plant: str) -> bool:
"""根据作物名称判断用户作物仓库是否存在该作物
Args:
uid (str): 用户uid
plant (str): 作物名称
Returns:
bool: 是否存在
"""
try:
async with cls.m_pDB.execute(
"SELECT * FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
) as cursor:
row = await cursor.fetchone()
return True if row else False
except Exception as e:
logger.warning("checkUserPlantByName 查询失败!", e=e)
return False
@classmethod
async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool:
"""更新 userPlant 表中某个作物的数量
@ -132,55 +114,11 @@ class CUserPlantDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
(count, uid, plant),
(count, uid, plant)
)
return True
except Exception as e:
logger.warning("updateUserPlantByName 失败!", e=e)
return False
@classmethod
async def lockUserPlantByName(cls, uid: str, plant: str, lock: int) -> bool:
"""给作物加锁,防止一键售卖
Args:
uid (str): 用户uid
plant (str): 作物名称
lock (int): 0为解锁非0均为加锁
Returns:
bool: 是否加锁成功
"""
try:
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE userPlant SET isLock = ? WHERE uid = ? AND plant = ?",
(lock, uid, plant),
)
return True
except Exception as e:
logger.warning("lockUserPlantByName 失败!", e=e)
return False
@classmethod
async def checkPlantLockByName(cls, uid: str, plant: str) -> bool:
"""根据作物名称判断是否加锁
Args:
uid (str): 用户uid
plant (str): 作物名称
Returns:
bool: 是否加锁
"""
try:
async with cls.m_pDB.execute(
"SELECT isLock FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
) as cursor:
row = await cursor.fetchone()
return row[0] > 0 if row else False
except Exception as e:
logger.warning("checkUserPlantByName 查询失败!", e=e)
logger.warning(f"updateUserPlantByName失败", e=e)
return False
@classmethod
@ -197,9 +135,10 @@ class CUserPlantDB(CSqlManager):
try:
async with cls._transaction():
await cls.m_pDB.execute(
"DELETE FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
"DELETE FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant)
)
return True
except Exception as e:
logger.warning("deleteUserPlantByName 失败!", e=e)
logger.warning(f"deleteUserPlantByName 失败!", e=e)
return False

View File

@ -1,3 +1,5 @@
from typing import Optional
from zhenxun.services.log import logger
from .database import CSqlManager
@ -10,7 +12,7 @@ class CUserSeedDB(CSqlManager):
"uid": "TEXT NOT NULL", #用户Uid
"seed": "TEXT NOT NULL", #种子名称
"count": "INTEGER NOT NULL DEFAULT 0", #数量
"PRIMARY KEY": "(uid, seed)",
"PRIMARY KEY": "(uid, seed)"
}
await cls.ensureTableSchema("userSeed", userSeed)
@ -30,7 +32,8 @@ class CUserSeedDB(CSqlManager):
try:
async with cls._transaction():
async with cls.m_pDB.execute(
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
) as cursor:
row = await cursor.fetchone()
@ -38,22 +41,23 @@ class CUserSeedDB(CSqlManager):
newCount = row[0] + count
await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(newCount, uid, seed),
(newCount, uid, seed)
)
else:
newCount = count
await cls.m_pDB.execute(
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
(uid, seed, count),
(uid, seed, count)
)
if newCount <= 0:
await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
"DELETE FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
)
return True
except Exception as e:
logger.warning("addUserSeedByUid 失败!", e=e)
logger.warning(f"addUserSeedByUid 失败!", e=e)
return False
@classmethod
@ -68,7 +72,7 @@ class CUserSeedDB(CSqlManager):
else:
await cls.m_pDB.execute(
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
(uid, seed, newCount),
(uid, seed, newCount)
)
if newCount <= 0:
@ -76,11 +80,12 @@ class CUserSeedDB(CSqlManager):
return True
except Exception as e:
logger.warning("_addUserSeedByUid 失败!", e=e)
logger.warning(f"_addUserSeedByUid 失败!", e=e)
return False
@classmethod
async def getUserSeedByName(cls, uid: str, seed: str) -> int | None:
async def getUserSeedByName(cls, uid: str, seed: str) -> Optional[int]:
"""根据种子名称获取种子数量
Args:
@ -93,12 +98,13 @@ class CUserSeedDB(CSqlManager):
try:
async with cls.m_pDB.execute(
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
except Exception as e:
logger.warning("getUserSeedByName 查询失败!", e=e)
logger.warning(f"getUserSeedByName 查询失败!", e=e)
return None
@classmethod
@ -113,7 +119,8 @@ class CUserSeedDB(CSqlManager):
"""
cursor = await cls.m_pDB.execute(
"SELECT seed, count FROM userSeed WHERE uid=?", (uid,)
"SELECT seed, count FROM userSeed WHERE uid=?",
(uid,)
)
rows = await cursor.fetchall()
return {row["seed"]: row["count"] for row in rows}
@ -137,11 +144,11 @@ class CUserSeedDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(count, uid, seed),
(count, uid, seed)
)
return True
except Exception as e:
logger.warning("updateUserSeedByName失败", e=e)
logger.warning(f"updateUserSeedByName失败", e=e)
return False
@classmethod
@ -163,11 +170,11 @@ class CUserSeedDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(count, uid, seed),
(count, uid, seed)
)
return True
except Exception as e:
logger.warning("updateUserSeedByName失败", e=e)
logger.warning(f"updateUserSeedByName失败", e=e)
return False
@classmethod
@ -184,11 +191,12 @@ class CUserSeedDB(CSqlManager):
try:
async with cls._transaction():
await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
"DELETE FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
)
return True
except Exception as e:
logger.warning("deleteUserSeedByName 删除失败!", e=e)
logger.warning(f"deleteUserSeedByName 删除失败!", e=e)
return False
@classmethod
@ -204,9 +212,10 @@ class CUserSeedDB(CSqlManager):
"""
try:
await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
"DELETE FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
)
return True
except Exception as e:
logger.warning("deleteUserSeedByName 删除失败!", e=e)
logger.warning(f"deleteUserSeedByName 删除失败!", e=e)
return False

View File

@ -1,6 +1,7 @@
import calendar
from datetime import timedelta
import random
from datetime import date, datetime, timedelta
from typing import Optional
from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage
@ -22,8 +23,8 @@ 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'))", # 创建时间 # noqa: E501
"PRIMARY KEY": "(uid, signDate)",
"createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))",#创建时间
"PRIMARY KEY": "(uid, signDate)"
}
#userSignSummary 表结构,每用户一行用于缓存签到状态
@ -35,7 +36,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'))", # 更新时间 # noqa: E501
"updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))"#更新时间
}
await cls.ensureTableSchema("userSignLog", userSignLog)
@ -56,15 +57,15 @@ class CUserSignDB(CSqlManager):
async with cls._transaction():
async with cls.m_pDB.execute(
"SELECT exp, point FROM userSignLog WHERE uid=? AND signDate=?",
(uid, date),
(uid, date)
) as cursor:
row = await cursor.fetchone()
if row is None:
return 0, 0
exp = row["exp"]
point = row["point"]
exp = row['exp']
point = row['point']
return exp, point
except Exception as e:
@ -113,7 +114,7 @@ class CUserSignDB(CSqlManager):
return False
@classmethod
async def sign(cls, uid: str, signDate: str = "") -> int:
async def sign(cls, uid: str, signDate: str = '') -> int:
"""签到
Args:
@ -150,34 +151,20 @@ class CUserSignDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
"INSERT INTO userSignLog (uid, signDate, isSupplement, exp, point) VALUES (?, ?, ?, ?, ?)",
(uid, signDate, isSupplement, exp, point),
(uid, signDate, isSupplement, exp, point)
)
cursor = await cls.m_pDB.execute(
"SELECT * FROM userSignSummary WHERE uid=?", (uid,)
)
cursor = await cls.m_pDB.execute("SELECT * FROM userSignSummary WHERE uid=?", (uid,))
row = await cursor.fetchone()
currentMonth = signDate[:7]
if row:
monthSignDays = (
row["monthSignDays"] + 1
if row["currentMonth"] == currentMonth
else 1
)
lastDate = row["lastSignDate"]
prevDate = (
g_pToolManager.dateTime().strptime(signDate, "%Y-%m-%d")
- timedelta(days=1)
).strftime("%Y-%m-%d")
continuousDays = (
row["continuousDays"] + 1 if lastDate == prevDate else 1
)
supplementCount = (
row["supplementCount"] + 1
if isSupplement
else row["supplementCount"]
)
monthSignDays = row['monthSignDays'] + 1 if row['currentMonth'] == currentMonth else 1
totalSignDays = row['totalSignDays']
lastDate = row['lastSignDate']
prevDate = (g_pToolManager.dateTime().strptime(signDate, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
continuousDays = row['continuousDays'] + 1 if lastDate == prevDate else 1
supplementCount = row['supplementCount'] + 1 if isSupplement else row['supplementCount']
await cls.m_pDB.execute(
"""
UPDATE userSignSummary
@ -189,42 +176,29 @@ class CUserSignDB(CSqlManager):
supplementCount=?
WHERE uid=?
""",
(
currentMonth,
monthSignDays,
signDate,
continuousDays,
supplementCount,
uid,
),
(currentMonth, monthSignDays, signDate, continuousDays, supplementCount, uid)
)
else:
monthSignDays = 1
totalSignDays = 1
await cls.m_pDB.execute(
"""
INSERT INTO userSignSummary
(uid, totalSignDays, currentMonth, monthSignDays, lastSignDate, continuousDays, supplementCount)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
uid,
1,
currentMonth,
monthSignDays,
signDate,
1,
1 if isSupplement else 0,
),
(uid, 1, currentMonth, 1, signDate, 1, 1 if isSupplement else 0)
)
#计算累签奖励
reward = g_pJsonManager.m_pSign["continuou"].get(f"{monthSignDays}", None)
if reward:
point += reward.get("point", 0)
exp += reward.get("exp", 0)
vipPoint = reward.get("vipPoint", 0)
reward = g_pJsonManager.m_pSign['continuou'].get(f"{totalSignDays}", None)
if reward:
point += reward.get('point', 0)
exp += reward.get('exp', 0)
vipPoint = reward.get('vipPoint', 0)
plant = reward.get('plant', {})
plant = reward.get("plant", {})
if plant:
for key, value in plant.items():
await g_pDBService.userSeed.addUserSeedByUid(uid, key, value)
@ -241,9 +215,7 @@ class CUserSignDB(CSqlManager):
if vipPoint > 0:
currentVipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
await g_pDBService.user.updateUserVipPointByUid(
uid, currentVipPoint + vipPoint
)
await g_pDBService.user.updateUserVipPointByUid(uid, currentVipPoint + vipPoint)
return 1
except Exception as e:
@ -270,7 +242,7 @@ class CUserSignDB(CSqlManager):
sql = "SELECT signDate FROM userSignLog WHERE uid=? AND signDate LIKE ?"
async with cls.m_pDB.execute(sql, (uid, f"{monthStr}-%")) as cursor:
rows = await cursor.fetchall()
signedDays = {int(r[0][-2:]) for r in rows if r[0][-2:].isdigit()}
signedDays = set(int(r[0][-2:]) for r in rows if r[0][-2:].isdigit())
except Exception as e:
logger.warning("绘制签到图时数据库查询失败", e=e)
signedDays = set()

View File

@ -1,9 +1,10 @@
import math
from typing import Optional
from zhenxun.services.log import logger
from ..config import g_bIsDebug
from ..dbService import g_pDBService
from ..json import g_pJsonManager
from ..tool import g_pToolManager
from .database import CSqlManager
@ -24,7 +25,6 @@ class CUserSoilDB(CSqlManager):
"weedStatus": "INTEGER DEFAULT 0", #杂草状态 0=无杂草1=有杂草
"waterStatus": "INTEGER DEFAULT 0", #缺水状态 0=不缺水1=缺水
"harvestCount": "INTEGER DEFAULT 0", #收获次数
"isSoilPlanted": "INTEGER DEFAULT NULL", # 是否种植作物 0=没有1=有
"PRIMARY KEY": "(uid, soilIndex)",
}
@ -45,59 +45,35 @@ class CUserSoilDB(CSqlManager):
if not soilInfo:
return
plantInfo = await g_pDBService.plant.getPlantByName(soilInfo["plantName"])
plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName'])
if not plantInfo:
return
currentTime = g_pToolManager.dateTime().now().timestamp()
phaseList = await g_pDBService.plant.getPlantPhaseByName(soilInfo["plantName"])
phaseList = await g_pDBService.plant.getPlantPhaseByName(soilInfo['plantName'])
if currentTime >= soilInfo["matureTime"]:
if currentTime >= soilInfo['matureTime']:
return
elapsedTime = currentTime - soilInfo["plantTime"]
currentStage = currentStage = sum(1 for thr in phaseList if elapsedTime >= thr)
currentStage = 0
elapsedTime = currentTime - soilInfo['plantTime']
t = int(soilInfo["plantTime"]) - phaseList[currentStage]
s = int(soilInfo["matureTime"]) - phaseList[currentStage]
for idx, thr in enumerate(phaseList, start=1):
if elapsedTime < thr:
currentStage = idx
break
await cls.updateUserSoilFields(
uid, soilIndex, {"plantTime": t, "matureTime": s}
)
t = int(soilInfo['plantTime']) - phaseList[currentStage]
s = int(soilInfo['matureTime']) - phaseList[currentStage]
logger.debug(
f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}"
)
await cls.updateUserSoilFields(uid, soilIndex,
{
"plantTime": t,
"matureTime": 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 = int(g_pToolManager.dateTime().now().timestamp())
# 如果当前时间已经超过或等于成熟时间,则作物已成熟或可收获
if currentTime >= soilInfo["matureTime"]:
return
# 将作物成熟时间直接更新为当前时间,实现立即成熟
await cls.updateUserSoilFields(uid, soilIndex, {"matureTime": currentTime})
logger.debug(f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}")
@classmethod
async def getUserFarmByUid(cls, uid: str) -> dict:
@ -141,14 +117,9 @@ class CUserSoilDB(CSqlManager):
data = farmInfo.get(key)
if not data:
continue
if data == ",,,4,":
continue
parts = data.split(",")
if len(parts) < 3:
continue
name = parts[0]
pt = int(parts[1])
mt = int(parts[2])
@ -183,8 +154,8 @@ class CUserSoilDB(CSqlManager):
INSERT INTO userSoil
(uid, soilIndex, plantName, plantTime, matureTime,
soilLevel, wiltStatus, fertilizerStatus, bugStatus,
weedStatus, waterStatus, harvestCount, isSoilPlanted)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
weedStatus, waterStatus, harvestCount)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
soilInfo["uid"],
@ -199,7 +170,6 @@ class CUserSoilDB(CSqlManager):
soilInfo.get("weedStatus", 0),
soilInfo.get("waterStatus", 0),
soilInfo.get("harvestCount", 0),
soilInfo.get("isSoilPlanted", 0),
),
)
@ -218,8 +188,8 @@ class CUserSoilDB(CSqlManager):
INSERT INTO userSoil
(uid, soilIndex, plantName, plantTime, matureTime,
soilLevel, wiltStatus, fertilizerStatus, bugStatus,
weedStatus, waterStatus, harvestCount, isSoilPlanted)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
weedStatus, waterStatus, harvestCount)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
soilInfo["uid"],
@ -234,12 +204,11 @@ class CUserSoilDB(CSqlManager):
soilInfo.get("weedStatus", 0),
soilInfo.get("waterStatus", 0),
soilInfo.get("harvestCount", 0),
soilInfo.get("isSoilPlanted", 0),
),
)
@classmethod
async def getUserSoil(cls, uid: str, soilIndex: int) -> dict:
async def getUserSoil(cls, uid: str, soilIndex: int) -> Optional[dict]:
"""获取指定用户某块土地的详细信息
Args:
@ -247,30 +216,9 @@ class CUserSoilDB(CSqlManager):
soilIndex (int): 土地索引
Returns:
dict: 记录存在返回字段-值字典否则返回 None
Optional[dict]: 记录存在返回字段-值字典否则返回 None
"""
async with cls._transaction():
cursor = await cls.m_pDB.execute(
"SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?",
(uid, soilIndex),
)
row = await cursor.fetchone()
if not row:
return {}
columns = [description[0] for description in cursor.description]
return dict(zip(columns, row))
@classmethod
async def _getUserSoil(cls, uid: str, soilIndex: int) -> dict | None:
"""获取指定用户某块土地的详细信息
Args:
uid (str): 用户ID
soilIndex (int): 土地索引
Returns:
dict | None: 记录存在返回字段-值字典否则返回 None
"""
cursor = await cls.m_pDB.execute(
"SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?",
(uid, soilIndex),
@ -282,23 +230,25 @@ class CUserSoilDB(CSqlManager):
return dict(zip(columns, row))
@classmethod
async def countSoilByLevel(cls, uid: str, soilLevel: int) -> int:
"""统计指定用户在指定土地等级的土地数量
async def _getUserSoil(cls, uid: str, soilIndex: int) -> Optional[dict]:
"""获取指定用户某块土地的详细信息
Args:
uid (str): 用户ID
soilLevel (int): 土地等级
soilIndex (int): 土地索引
Returns:
int: 符合条件的土地数量
Optional[dict]: 记录存在返回字段-值字典否则返回 None
"""
async with cls._transaction():
cursor = await cls.m_pDB.execute(
"SELECT COUNT(*) FROM userSoil WHERE uid = ? AND soilLevel = ?",
(uid, soilLevel),
"SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?",
(uid, soilIndex),
)
row = await cursor.fetchone()
return row[0] if row else 0
if not row:
return None
columns = [description[0] for description in cursor.description]
return dict(zip(columns, row))
@classmethod
async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value):
@ -338,9 +288,7 @@ class CUserSoilDB(CSqlManager):
)
@classmethod
async def updateUserSoilFields(
cls, uid: str, soilIndex: int, updates: dict
) -> bool:
async def updateUserSoilFields(cls, uid: str, soilIndex: int, updates: dict) -> bool:
"""批量更新指定用户土地的多个字段
Args:
@ -353,17 +301,9 @@ class CUserSoilDB(CSqlManager):
"""
#允许更新的列白名单
allowedFields = {
"plantName",
"plantTime",
"matureTime",
"soilLevel",
"wiltStatus",
"fertilizerStatus",
"bugStatus",
"weedStatus",
"waterStatus",
"harvestCount",
"isSoilPlanted",
"plantName", "plantTime", "matureTime", "soilLevel",
"wiltStatus", "fertilizerStatus", "bugStatus",
"weedStatus", "waterStatus", "harvestCount"
}
setClauses = []
values = []
@ -376,7 +316,7 @@ class CUserSoilDB(CSqlManager):
return False
values.extend([uid, soilIndex])
sql = f"UPDATE userSoil SET {', '.join(setClauses)} WHERE uid = ? AND soilIndex = ?"
sql = f'UPDATE userSoil SET {", ".join(setClauses)} WHERE uid = ? AND soilIndex = ?'
try:
async with cls._transaction():
@ -417,6 +357,23 @@ class CUserSoilDB(CSqlManager):
"DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex)
)
@classmethod
async def isSoilPlanted(cls, uid: str, soilIndex: int) -> bool:
"""判断指定用户的指定土地是否已种植
Args:
uid (str): 用户ID
soilIndex (int): 土地索引
Returns:
bool: 如果 plantName 不为空且 plantTime 大于 0则视为已种植返回 True否则 False
"""
soilInfo = await cls.getUserSoil(uid, soilIndex)
if not soilInfo:
return False
return bool(soilInfo.get("plantName")) and soilInfo.get("plantTime", 0) > 0
@classmethod
async def sowingByPlantName(cls, uid: str, soilIndex: int, plantName: str) -> bool:
"""播种指定作物到用户土地区
@ -430,8 +387,8 @@ class CUserSoilDB(CSqlManager):
bool: 播种成功返回 True否则返回 False
"""
#校验土地区是否已种植
soilInfo = await cls.getUserSoil(uid, soilIndex)
if soilInfo and soilInfo.get("plantName"):
soilRecord = await cls.getUserSoil(uid, soilIndex)
if soilRecord and soilRecord.get("plantName"):
return False
#获取植物配置
@ -441,18 +398,11 @@ class CUserSoilDB(CSqlManager):
return False
nowTs = int(g_pToolManager.dateTime().now().timestamp())
time = int(plantCfg.get("time", 0))
percent = await cls.getSoilLevelTime(soilInfo.get("soilLevel", 0))
# 处理土地等级带来的时间缩短
time = math.floor(time * (100 + percent) // 100)
matureTs = nowTs + time * 3600
matureTs = nowTs + int(plantCfg.get("time", 0)) * 3600
try:
async with cls._transaction():
prev = soilInfo or {}
prev = soilRecord or {}
await cls._deleteUserSoil(uid, soilIndex)
await cls._insertUserSoil(
{
@ -462,18 +412,17 @@ class CUserSoilDB(CSqlManager):
"plantTime": nowTs,
"matureTime": matureTs,
"soilLevel": prev.get("soilLevel", 0),
"wiltStatus": 0,
"fertilizerStatus": 0,
"bugStatus": 0,
"weedStatus": 0,
"waterStatus": 0,
"harvestCount": 0,
"isSoilPlanted": 1,
"wiltStatus": prev.get("wiltStatus", 0),
"fertilizerStatus": prev.get("fertilizerStatus", 0),
"bugStatus": prev.get("bugStatus", 0),
"weedStatus": prev.get("weedStatus", 0),
"waterStatus": prev.get("waterStatus", 0),
"harvestCount": 0
}
)
return True
except Exception as e:
logger.error("播种失败!", e=e)
logger.error(f"播种失败!", e=e)
return False
@classmethod
@ -502,90 +451,3 @@ 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

View File

@ -12,14 +12,12 @@ class CUserStealDB(CSqlManager):
"stealerUid": "TEXT NOT NULL", #偷菜用户Uid
"stealCount": "INTEGER NOT NULL", #被偷数量
"stealTime": "INTEGER NOT NULL", #被偷时间
"PRIMARY KEY": "(uid, soilIndex, stealerUid)",
"PRIMARY KEY": "(uid, soilIndex, stealerUid)"
}
await cls.ensureTableSchema("userSteal", userSteal)
@classmethod
async def addStealRecord(
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
) -> bool:
async def addStealRecord(cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int) -> bool:
"""添加偷菜记录
Args:
@ -36,7 +34,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
'INSERT INTO "userSteal"(uid, soilIndex, stealerUid, stealCount, stealTime) VALUES(?, ?, ?, ?, ?);',
(uid, soilIndex, stealerUid, stealCount, stealTime),
(uid, soilIndex, stealerUid, stealCount, stealTime)
)
return True
except Exception as e:
@ -57,7 +55,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
cursor = await cls.m_pDB.execute(
'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;',
(uid,),
(uid,)
)
rows = await cursor.fetchall()
return [
@ -66,7 +64,7 @@ class CUserStealDB(CSqlManager):
"soilIndex": row[0],
"stealerUid": row[1],
"stealCount": row[2],
"stealTime": row[3],
"stealTime": row[3]
}
for row in rows
]
@ -89,7 +87,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
cursor = await cls.m_pDB.execute(
'SELECT stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex),
(uid, soilIndex)
)
rows = await cursor.fetchall()
return [
@ -98,7 +96,7 @@ class CUserStealDB(CSqlManager):
"soilIndex": soilIndex,
"stealerUid": row[0],
"stealCount": row[1],
"stealTime": row[2],
"stealTime": row[2]
}
for row in rows
]
@ -121,7 +119,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
cursor = await cls.m_pDB.execute(
'SELECT SUM(stealCount) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex),
(uid, soilIndex)
)
row = await cursor.fetchone()
return row[0] or 0 # type: ignore
@ -144,7 +142,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
cursor = await cls.m_pDB.execute(
'SELECT COUNT(DISTINCT stealerUid) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex),
(uid, soilIndex)
)
row = await cursor.fetchone()
return row[0] or 0 # type: ignore
@ -168,7 +166,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
cursor = await cls.m_pDB.execute(
'SELECT 1 FROM "userSteal" WHERE uid=? AND soilIndex=? AND stealerUid=? LIMIT 1;',
(uid, soilIndex, stealerUid),
(uid, soilIndex, stealerUid)
)
row = await cursor.fetchone()
return bool(row)
@ -177,9 +175,7 @@ class CUserStealDB(CSqlManager):
return False
@classmethod
async def updateStealRecord(
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
) -> bool:
async def updateStealRecord(cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int) -> bool:
"""更新偷菜记录的数量和时间
Args:
@ -196,7 +192,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
'UPDATE "userSteal" SET stealCount=?, stealTime=? WHERE uid=? AND soilIndex=? AND stealerUid=?;',
(stealCount, stealTime, uid, soilIndex, stealerUid),
(stealCount, stealTime, uid, soilIndex, stealerUid)
)
return True
except Exception as e:
@ -218,7 +214,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction():
await cls.m_pDB.execute(
'DELETE FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex),
(uid, soilIndex)
)
return True
except Exception as e:

View File

@ -1,3 +1,6 @@
from typing import Optional
class CDBService:
@classmethod
async def init(cls):
@ -41,5 +44,4 @@ class CDBService:
async def cleanup(cls):
await cls.plant.cleanup()
g_pDBService = CDBService()

View File

@ -1,68 +1,51 @@
import inspect
import asyncio
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, 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))
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))
self._slots.sort(key=lambda x: -x[1])
return func
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))
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))
self._onceSlots.sort(key=lambda x: -x[1])
return func
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]
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]
async def emit(self, *args, **kwargs):
slots = list(self._slots)
onceSlots = list(self._onceSlots)
self._onceSlots.clear()
for slot, _ in slots + onceSlots:
start = time.time()
startTime = time.time()
try:
if inspect.iscoroutinefunction(slot):
if asyncio.iscoroutinefunction(slot):
await slot(*args, **kwargs)
else:
slot(*args, **kwargs)
logger.debug(
f"【真寻农场】事件槽 {slot.__name__} 执行完成,耗时 {(time.time() - start) * 1000:.2f} ms"
)
duration = (time.time() - startTime) * 1000
logger.debug(f"事件槽 {slot.__name__} 执行完成,耗时 {duration:.2f} ms")
except Exception as e:
logger.warning(f"事件槽 {slot.__name__} 触发异常: {e}")
class FarmEventManager:
m_beforePlant = Signal()
def __init__(self):
self.m_beforePlant = Signal()
"""播种前信号
Args:
@ -71,7 +54,7 @@ class FarmEventManager:
num (int): 播种数量
"""
m_afterPlant = Signal()
self.m_afterPlant = Signal()
"""播种后信号 每块地播种都会触发该信号
Args:
@ -80,14 +63,14 @@ class FarmEventManager:
soilIndex (int): 播种地块索引 从1开始
"""
m_beforeHarvest = Signal()
self.m_beforeHarvest = Signal()
"""收获前信号
Args:
uid (str): 用户Uid
"""
m_afterHarvest = Signal()
self.m_afterHarvest = Signal()
"""收获后信号 每块地收获都会触发该信号
Args:
@ -97,14 +80,14 @@ class FarmEventManager:
soilIndex (int): 收获地块索引 从1开始
"""
m_beforeEradicate = Signal()
self.m_beforeEradicate = Signal()
"""铲除前信号
Args:
uid (str): 用户Uid
"""
m_afterEradicate = Signal()
self.m_afterEradicate = Signal()
"""铲除后信号 每块地铲除都会触发该信号
Args:
@ -112,12 +95,9 @@ class FarmEventManager:
soilIndex (index): 铲除地块索引 从1开始
"""
m_beforeExpand = Signal()
m_afterExpand = Signal()
m_beforeSteal = Signal()
m_afterSteal = Signal()
m_dit = Signal()
self.m_beforeExpand = Signal()
self.m_afterExpand = Signal()
self.m_beforeSteal = Signal()
self.m_afterSteal = Signal()
g_pEventManager = FarmEventManager()

File diff suppressed because it is too large Load Diff

View File

@ -1,117 +0,0 @@
from pathlib import Path
from jinja2 import Template
from playwright.async_api import async_playwright
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.services.log import logger
from ..config import g_sResourcePath
def rendeerHtmlToFile(path: Path | str, context: dict, output: Path | str) -> None:
"""
使用 Jinja2 渲染 HTML 模板并保存到指定文件会自动创建父目录
Args:
path (str): 模板 HTML 路径
context (dict): 用于渲染的上下文字典
output (str): 输出 HTML 文件路径
"""
templatePath = str(path)
outputPath = str(output)
templateStr = Path(templatePath).read_text(encoding="utf-8")
template = Template(templateStr)
rendered = template.render(**context)
# 自动创建目录
Path(outputPath).parent.mkdir(parents=True, exist_ok=True)
Path(outputPath).write_text(rendered, encoding="utf-8")
async def screenshotHtmlToBytes(path: str) -> bytes:
"""
使用 Playwright 截图本地 HTML 文件并返回 PNG 图片字节数据
Args:
path (str): 本地 HTML 文件路径
Returns:
bytes: PNG 图片的原始字节内容
"""
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
file_url = Path(path).resolve().as_uri()
await page.goto(file_url)
image_bytes = await page.screenshot(full_page=True)
await browser.close()
return image_bytes
async def screenshotSave(path: str, save: str) -> None:
"""
使用 Playwright 渲染本地 HTML 并将截图保存到指定路径
Args:
path (str): HTML 文件路径
save (str): PNG 保存路径 output/image.png
"""
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
file_url = Path(path).resolve().as_uri()
await page.goto(file_url)
# 确保保存目录存在
Path(save).parent.mkdir(parents=True, exist_ok=True)
# 截图并保存到本地文件
await page.screenshot(path=save, full_page=True)
await browser.close()
async def createHelpImage() -> bool:
templatePath = g_sResourcePath / "html/help.html"
outputPath = DATA_PATH / "farm_res/html/help.html"
context = {
"title": "功能指令总览",
"data": [
{
"command": "开通农场",
"description": "首次进入游戏开通农场",
"tip": "",
},
{
"command": "购买种子",
"description": "从商店中购买可用种子",
"tip": "",
},
{"command": "播种", "description": "将种子种入土地中", "tip": "先开垦土地"},
{
"command": "收获",
"description": "收获成熟作物获得收益",
"tip": "",
},
{
"command": "偷菜",
"description": "从好友农场中偷取成熟作物",
"tip": "1",
},
],
}
try:
rendeerHtmlToFile(templatePath, context, outputPath)
image_bytes = await screenshot_html_to_bytes(html_output_path)
except Exception as e:
logger.warning("绘制农场帮助菜单失败", e=e)
return False
return True

View File

@ -1,9 +1,14 @@
import math
import plistlib
from itertools import islice
from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage
from zhenxun.utils.image_utils import ImageTemplate
from ..config import g_sResourcePath, g_sTranslation
from ..config import g_sResourcePath
from ..dbService import g_pDBService
from ..json import g_pJsonManager
class CShopManager:
@ -32,15 +37,14 @@ class CShopManager:
columnName = [
"-",
"种子名称",
"农场币",
"点券",
"种子单价",
"解锁等级",
"果实单价",
"收获经验",
"收获数量",
"成熟时间(小时)",
"收获次数",
"是否可以上架交易行",
"是否可以上架交易行"
]
# 查询所有可购买作物,并根据筛选关键字过滤
@ -48,10 +52,10 @@ class CShopManager:
filteredPlants = []
for plant in plants:
# 跳过未解锁购买的种子
if plant["isBuy"] == 0:
if plant['isBuy'] == 0:
continue
# 字符串筛选
if filterStr and filterStr not in plant["name"]:
if filterStr and filterStr not in plant['name']:
continue
filteredPlants.append(plant)
@ -71,23 +75,20 @@ class CShopManager:
icon = (iconPath, 33, 33)
# 交易行标记
sell = "可以" if plant["sell"] else "不可以"
sell = "可以" if plant['sell'] else "不可以"
dataList.append(
[
dataList.append([
icon,
plant["name"], # 种子名称
plant["buy"], # 农场币种子单价
plant["vipBuy"], # 点券种子单价
plant["level"], # 解锁等级
plant["price"], # 果实单价
plant["experience"], # 收获经验
plant["harvest"], # 收获数量
plant["time"], # 成熟时间(小时)
plant["crop"], # 收获次数
sell, # 是否可上架交易行
]
)
plant['name'], # 种子名称
plant['buy'], # 种子单价
plant['level'], # 解锁等级
plant['price'], # 果实单价
plant['experience'], # 收获经验
plant['harvest'], # 收获数量
plant['time'], # 成熟时间(小时)
plant['crop'], # 收获次数
sell # 是否可上架交易行
])
# 页码标题
title = f"种子商店 页数: {page}/{pageCount}"
@ -101,6 +102,7 @@ class CShopManager:
)
return result.pic2bytes()
@classmethod
async def buySeed(cls, uid: str, name: str, num: int = 1) -> str:
"""购买种子
@ -115,46 +117,31 @@ class CShopManager:
"""
if num <= 0:
return g_sTranslation["buySeed"]["notNum"]
return "请输入购买数量!"
plantInfo = await g_pDBService.plant.getPlantByName(name)
if not plantInfo:
return g_sTranslation["buySeed"]["error"]
return "购买出错!请检查需购买的种子名称!"
level = await g_pDBService.user.getUserLevelByUid(uid)
if level[0] < int(plantInfo["level"]):
return g_sTranslation["buySeed"]["noLevel"]
if level[0] < int(plantInfo['level']):
return "你的等级不够哦,努努力吧"
"""
logger.debug(
f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}"
)
"""
if plantInfo["isVip"] == 1:
vipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
total = int(plantInfo["vipBuy"]) * num
if vipPoint < total:
return g_sTranslation["buySeed"]["noVipPoint"]
await g_pDBService.user.updateUserVipPointByUid(uid, vipPoint - total)
else:
point = await g_pDBService.user.getUserPointByUid(uid)
total = int(plantInfo["buy"]) * num
total = int(plantInfo['buy']) * num
logger.debug(f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}")
if point < total:
return g_sTranslation["buySeed"]["noPoint"]
return "你的农场币不够哦~ 快速速氪金吧!"
else:
await g_pDBService.user.updateUserPointByUid(uid, point - total)
if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num):
return g_sTranslation["buySeed"]["errorSql"]
return "购买失败,执行数据库错误!"
if plantInfo["isVip"] == 1:
return g_sTranslation["buySeed"]["vipSuccess"].format(
name=name, total=total, point=vipPoint - total
)
else:
return g_sTranslation["buySeed"]["success"].format(
name=name, total=total, point=point - total
)
return f"成功购买{name},花费{total}农场币, 剩余{point - total}农场币"
@classmethod
async def sellPlantByUid(cls, uid: str, name: str = "", num: int = 1) -> str:
@ -171,37 +158,28 @@ class CShopManager:
plant = await g_pDBService.userPlant.getUserPlantByUid(uid)
if not plant:
return g_sTranslation["sellPlant"]["no"]
return "你仓库没有可以出售的作物"
point = 0
totalSold = 0
isAll = num == -1
isAll = (num == -1)
if name == "":
for plantName, count in plant.items():
isLock = await g_pDBService.userPlant.checkPlantLockByName(
uid, plantName
)
if isLock:
continue
plantInfo = await g_pDBService.plant.getPlantByName(plantName)
if not plantInfo:
continue
point += plantInfo["price"] * count
point += plantInfo['price'] * count
await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0)
else:
if name not in plant:
return g_sTranslation["sellPlant"]["error"].format(name=name)
return f"出售作物{name}出错:仓库中不存在该作物"
available = plant[name]
sellAmount = available if isAll else min(available, num)
if sellAmount <= 0:
return g_sTranslation["sellPlant"]["error1"].format(name=name)
await g_pDBService.userPlant.updateUserPlantByName(
uid, name, available - sellAmount
)
return f"出售作物{name}出错:数量不足"
await g_pDBService.userPlant.updateUserPlantByName(uid, name, available - sellAmount)
totalSold = sellAmount
if name == "":
@ -211,7 +189,7 @@ class CShopManager:
if not plantInfo:
price = 0
else:
price = plantInfo["price"]
price = plantInfo['price']
totalPoint = totalSold * price
@ -219,13 +197,8 @@ class CShopManager:
await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint)
if name == "":
return g_sTranslation["sellPlant"]["success"].format(
point=totalPoint, num=currentPoint + totalPoint
)
return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"
else:
return g_sTranslation["sellPlant"]["success1"].format(
name=name, point=totalPoint, num=currentPoint + totalPoint
)
return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"
g_pShopManager = CShopManager()

View File

@ -1,4 +1,5 @@
import json
from pathlib import Path
from zhenxun.services.log import logger
@ -79,11 +80,7 @@ class CJsonManager:
return False
else:
result = await self.initSign()
config.g_bSignStatus = result
return result
return await self.initSign()
async def initSign(self) -> bool:
try:
@ -101,5 +98,4 @@ class CJsonManager:
logger.warning(f"sign_in.json JSON格式错误: {e}")
return False
g_pJsonManager = CJsonManager()

View File

@ -1,19 +1,5 @@
# 真寻农场更新日志
## V1.5
用户方面
---
- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
- 定时更新签到文件、作物资源从00:30调整至04:30
- 修正了部分土地资源错误的情况
- 修正了部分文本信息错误的情况
代码方面
---
- 修正部分事件连接机制
- 修正网络请求端口
## V1.4
用户方面
---

View File

@ -1,20 +1,13 @@
import json
import os
from datetime import datetime
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_sPlantPath, g_sSignInPath
from .dbService import g_pDBService
from .config import g_sSignInPath
from .tool import g_pToolManager
@ -22,14 +15,12 @@ class CRequestManager:
m_sTokens = "xZ%?z5LtWV7H:0-Xnwp+bNRNQ-jbfrxG"
@classmethod
async def download(
cls,
async def download(cls,
url: str,
savePath: str,
fileName: str,
params: dict | None = None,
jsonData: dict | None = None,
) -> bool:
jsonData: dict | None = None) -> bool:
"""下载文件到指定路径并覆盖已存在的文件
Args:
@ -38,52 +29,30 @@ 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=30.0) as client:
async with httpx.AsyncClient(timeout=10.0) as client:
requestArgs: dict = {"headers": headers}
if params:
requestArgs["params"] = params
if jsonData:
requestArgs["json"] = jsonData
response = await client.request(
"GET", url, **requestArgs, follow_redirects=True
)
response = await client.request("GET", url, **requestArgs)
if response.status_code != 200:
logger.warning(
f"文件下载失败: HTTP {response.status_code} {response.text}"
)
return False
totalLength = int(response.headers.get("Content-Length", 0))
if response.status_code == 200:
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))
f.write(response.content)
return True
else:
logger.warning(f"文件下载失败: HTTP {response.status_code} {response.text}")
return False
except Exception as e:
logger.warning(f"下载文件异常: {e}")
@ -105,7 +74,7 @@ class CRequestManager:
dict: 返回请求结果的JSON数据
"""
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
url = f"{baseUrl.rstrip('/')}:8998/{endpoint.lstrip('/')}"
url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}"
headers = {"token": cls.m_sTokens}
try:
@ -115,9 +84,7 @@ class CRequestManager:
if response.status_code == 200:
return response.json()
else:
logger.warning(
f"{name}请求失败: HTTP {response.status_code} {response.text}"
)
logger.warning(f"{name}请求失败: HTTP {response.status_code} {response.text}")
return {}
except httpx.RequestError as e:
logger.warning(f"{name}请求异常", e=e)
@ -138,7 +105,7 @@ class CRequestManager:
dict: 返回请求结果的JSON数据
"""
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
url = f"{baseUrl.rstrip('/')}:8998/{endpoint.lstrip('/')}"
url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}"
headers = {"token": cls.m_sTokens}
try:
@ -148,9 +115,7 @@ class CRequestManager:
if response.status_code == 200:
return response.json()
else:
logger.warning(
f"{name}请求失败: HTTP {response.status_code} {response.text}"
)
logger.warning(f"{name}请求失败: HTTP {response.status_code} {response.text}")
return {}
except httpx.RequestError as e:
logger.warning(f"{name}请求异常", e=e)
@ -163,7 +128,7 @@ class CRequestManager:
async def initSignInFile(cls) -> bool:
if os.path.exists(g_sSignInPath):
try:
with open(g_sSignInPath, encoding="utf-8") as f:
with open(g_sSignInPath, "r", encoding="utf-8") as f:
content = f.read()
sign = json.loads(content)
@ -176,117 +141,28 @@ class CRequestManager:
else:
logger.warning("真寻农场签到文件检查失败, 即将下载")
return await cls.downloadSignInFile()
except json.JSONDecodeError:
logger.warning("真寻农场签到文件格式错误, 即将下载")
except json.JSONDecodeError as e:
logger.warning(f"真寻农场签到文件格式错误, 即将下载")
return await cls.downloadSignInFile()
else:
return await cls.downloadSignInFile()
@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")
# 下载为 signTemp.json
success = await cls.download(
url=url,
savePath=path,
fileName="signTemp.json",
jsonData={"date": yearMonth},
)
if not success:
return False
# 重命名为 sign_in.json
await cls.download(url, path, "signTemp.json", jsonData={'date':yearMonth})
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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

View File

@ -1 +0,0 @@
{"version": 0.41}

View File

@ -1,75 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<style>
body {
background-color: #ffe4e9;
font-family: "Microsoft YaHei", sans-serif;
margin: 0;
padding: 0;
}
.container {
display: flex;
justify-content: center;
padding: 40px 20px;
}
.content-box {
background-color: #fff0f5;
border-radius: 24px;
padding: 30px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
width: 900px;
}
.title {
text-align: center;
font-size: 32px;
font-weight: bold;
margin-bottom: 40px;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #ffb6c1;
font-weight: bold;
padding: 12px;
text-align: center;
}
td {
padding: 12px;
text-align: center;
}
tr:not(:last-child) {
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="container">
<div class="content-box">
<div class="title">{{ title }}</div>
<table>
<thead>
<tr>
<th>指令</th>
<th>描述</th>
<th>Tip</th>
</tr>
</thead>
<tbody>
{% for entry in data %}
<tr>
<td>{{ entry.command }}</td>
<td>{{ entry.description }}</td>
<td>{{ entry.tip }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
resource/plant/南瓜/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Some files were not shown because too many files have changed in this diff Show More