Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f0023d468 | ||
|
|
5c043bc0c8 | ||
|
|
1be91147c1 | ||
|
|
0024bb22f5 | ||
|
|
00948cffb1 | ||
|
|
9da91ff3d4 | ||
|
|
698f803694 | ||
|
|
fea8ca9287 | ||
|
|
7bda14ea81 | ||
|
|
1fda07574c | ||
|
|
ac495ad6e9 | ||
|
|
8e30a27a41 | ||
|
|
e3691a2319 | ||
|
|
f62008209f | ||
|
|
34d4f698dd | ||
|
|
f16c1c5ab0 | ||
|
|
4ab21b7f7b | ||
|
|
eb937dd309 | ||
|
|
4841ceb871 | ||
|
|
01fceaa4fe | ||
|
|
3d42c1d283 | ||
|
|
aa2c5811de | ||
|
|
69ddbe4669 | ||
|
|
564d9858f5 | ||
|
|
ba06fd8967 | ||
|
|
e48bdf03da | ||
|
|
f4e192ff27 | ||
|
|
c6f24bc3c2 | ||
|
|
ca6a414ba4 | ||
|
|
ac13ab9280 | ||
|
|
ba2ecf19fc | ||
|
|
776b408c9c | ||
|
|
d42f0aa433 | ||
|
|
04a2cc7b4d | ||
|
|
475865f1e9 | ||
|
|
fce307cc94 | ||
|
|
db294e6998 | ||
|
|
1ef506d2fd | ||
|
|
a475d0188b | ||
|
|
7dc55bccf1 | ||
|
|
afa2259ad8 | ||
|
|
c131692b19 | ||
|
|
cca3dc5d4d | ||
|
|
2fbaf5d54f | ||
|
|
6d3eb5d48f | ||
|
|
ff5b446b52 | ||
|
|
55394e3590 | ||
|
|
0cd9b9d8db | ||
|
|
8243a1e1c8 | ||
|
|
6b99ec427c | ||
|
|
b5334dfc01 | ||
|
|
72baa5d095 | ||
|
|
719494e4e5 | ||
|
|
5d3d8a4ef1 | ||
|
|
b72190cc45 | ||
|
|
4cd0302426 |
@@ -1 +1,197 @@
|
||||
__pycache__
|
||||
/config/sign_in.json
|
||||
/resource/*
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,43 +1,114 @@
|
||||
<div align="center">
|
||||
|
||||
# 真寻农场(zhenxun_plugin_farm)
|
||||
<p align="center">
|
||||
<a href="./LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-GPL3.0-FE7D37" alt="license">
|
||||
</a>
|
||||
<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>
|
||||
<a href="https://github.com/zhenxun-org/zhenxun_bot">
|
||||
<img src="https://img.shields.io/badge/zhenxun-0.2.4%2B-%23ECD9D3" alt="Python">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
[](https://qm.qq.com/q/7hsOD4rOw2)
|
||||
|
||||
</p>
|
||||
|
||||
你是说可以种地对吧🤔?
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
## 目录
|
||||
- [真寻农场(zhenxun\_plugin\_farm)](#真寻农场zhenxun_plugin_farm)
|
||||
- [目录](#目录)
|
||||
- [农场界面](#农场界面)
|
||||
- [如何安装](#如何安装)
|
||||
- [使用指令](#使用指令)
|
||||
- [更新日志(详细):](#更新日志详细)
|
||||
- [用户方面](#用户方面)
|
||||
- [代码方面](#代码方面)
|
||||
- [待办事宜 `Todo` 列表](#待办事宜-todo-列表)
|
||||
- [关于](#关于)
|
||||
- [致谢](#致谢)
|
||||
- [许可证](#许可证)
|
||||
|
||||
---
|
||||
|
||||
## 农场界面
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 如何安装
|
||||
|
||||
方法一(推荐):在小真寻后台的插件商店下载即可。
|
||||
方法一(推荐):在小真寻后台的插件商店下载即可<br>
|
||||
方法二:下载源码放在小真寻`plugin`目录下
|
||||
|
||||
---
|
||||
|
||||
## 使用指令
|
||||
|
||||
| 指令 | 描述 | Tip |
|
||||
| --- | --- | --- |
|
||||
| @小真寻 开通农场 | 首次开通农场 | |
|
||||
| 我的农场 | 你的农场 | |
|
||||
| 农场详述 | 农场详细信息 | |
|
||||
| 我的农场币 | 查询农场币 | |
|
||||
| 种子商店 [页数] | 查看种子商店 | 数量不填默认为1 |
|
||||
| 种子商店 [筛选关键字] [页数] or [页数] | 查看种子商店 | 当第一个参数为非整数时,会默认进入筛选状态。页数不填默认为1 |
|
||||
| 购买种子 [种子名称] [数量] | 购买种子 | 数量不填默认为1 |
|
||||
| 我的种子 | 查询仓库种子 | |
|
||||
| 播种 [种子名称] [数量] | 播种种子 | 数量不填默认将最大可能播种 |
|
||||
| 收获 | 收获成熟作物 | |
|
||||
| 铲除 | 铲除荒废作物 | |
|
||||
| 我的作物 | | |
|
||||
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 作物不填默认清空仓库 数量不填默认全部 |
|
||||
| @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 |
|
||||
| 我的作物 | 你的作物 | |
|
||||
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 |
|
||||
| 偷菜 @美波理 | 偷别人的菜 | 每人每天只能偷5次 |
|
||||
| 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% |
|
||||
| 更改农场名 [新的农场名] | 改名 | 农场名称无法存储特殊字符 |
|
||||
| 农场签到 | 签到 | 需要注意,该项会从服务器拉取签到数据 |
|
||||
| 土地升级 [地块ID] | 将土地升级,带来收益提升 | 如果土地升级时,土地有播种作物,那么将直接成熟 |
|
||||
|
||||
---
|
||||
|
||||
## 更新日志[(详细)](./log/log.md):
|
||||
用户方面
|
||||
---
|
||||
- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
|
||||
- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
|
||||
- 定时更新签到文件、作物资源从00:30调整至04:30
|
||||
- 修正了部分土地资源错误的情况
|
||||
- 修正了部分文本信息错误的情况
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 修正部分事件连接机制
|
||||
- 修正网络请求端口
|
||||
|
||||
## 待办事宜 `Todo` 列表
|
||||
|
||||
- [x] 完善我的农场图片,例如左上角显示用户数据
|
||||
- [x] 完善升级数据、作物数据、作物图片
|
||||
- [ ] 完善升级数据、作物数据、作物图片
|
||||
- [x] 签到功能
|
||||
- [x] 添加渔场功能
|
||||
- [x] 增加活动、交易行功能
|
||||
- [x] 增加交易行总行功能
|
||||
- [x] 添加其他游戏种子素材
|
||||
- [x] 想不到了,想到再说
|
||||
- [x] 在线更新作物信息
|
||||
- [ ] 添加渔场功能
|
||||
- [ ] 增加活动、交易行功能
|
||||
- [ ] 增加交易行总行功能
|
||||
- [ ] 添加其他游戏种子素材
|
||||
- [ ] 想不到了,想到再说
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
from nonebot import get_driver
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
from .command import diuse_farm, diuse_register, reclamation
|
||||
from .config import g_pJsonManager
|
||||
from .database import g_pSqlManager
|
||||
from .farm.farm import g_pFarmManager
|
||||
from .farm.shop import g_pShopManager
|
||||
from .request import g_pRequestManager
|
||||
from .core.database.database import g_pSqlManager
|
||||
from .core.dbService import g_pDBService
|
||||
from .core.farm import g_pFarmManager
|
||||
from .core.help import g_pHelpManager
|
||||
from .core.player.playerPool import g_pUserPool
|
||||
from .core.shop import g_pShopManager
|
||||
from .event.event import g_pEventManager
|
||||
from .utils.json import g_pJsonManager
|
||||
from .utils.request import g_pRequestManager
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="真寻农场",
|
||||
@@ -20,22 +25,26 @@ __plugin_meta__ = PluginMetadata(
|
||||
指令:
|
||||
at 开通农场
|
||||
我的农场
|
||||
农场详述
|
||||
我的农场币
|
||||
种子商店 [页数]
|
||||
种子商店 [筛选关键字] [页数] or [页数]
|
||||
购买种子 [作物/种子名称] [数量]
|
||||
我的种子
|
||||
播种 [作物/种子名称] [数量]
|
||||
播种 [作物/种子名称] [数量] (数量不填默认将最大可能播种
|
||||
收获
|
||||
铲除
|
||||
我的作物
|
||||
出售作物 [作物/种子名称] [数量]
|
||||
偷菜 at
|
||||
出售作物 [作物/种子名称] [数量] (不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售
|
||||
偷菜 at (每人每天只能偷5次
|
||||
开垦
|
||||
购买农场币 [数量] 数量为消耗金币的数量
|
||||
更改农场名 [新农场名]
|
||||
农场签到
|
||||
土地升级 [地块ID](通过农场详述获取)
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="Art_Sakura",
|
||||
version="1.0",
|
||||
version="1.5.2",
|
||||
commands=[Command(command="我的农场")],
|
||||
menu_type="群内小游戏",
|
||||
configs=[
|
||||
@@ -51,6 +60,12 @@ __plugin_meta__ = PluginMetadata(
|
||||
help="金币兑换农场币的倍数 默认值为: 2倍",
|
||||
default_value="2",
|
||||
),
|
||||
RegisterConfig(
|
||||
key="点券兑换倍数",
|
||||
value="2000",
|
||||
help="农场币兑换点券的倍数 比例为2000:1",
|
||||
default_value="2000",
|
||||
),
|
||||
RegisterConfig(
|
||||
key="手续费",
|
||||
value="0.2",
|
||||
@@ -62,8 +77,8 @@ __plugin_meta__ = PluginMetadata(
|
||||
value="http://diuse.work",
|
||||
help="签到、交易行、活动等服务器地址",
|
||||
default_value="http://diuse.work",
|
||||
)
|
||||
]
|
||||
),
|
||||
],
|
||||
).to_dict(),
|
||||
)
|
||||
driver = get_driver()
|
||||
@@ -72,13 +87,34 @@ driver = get_driver()
|
||||
# 构造函数
|
||||
@driver.on_startup
|
||||
async def start():
|
||||
# 初始化数据库
|
||||
# 数据库加载
|
||||
await g_pSqlManager.init()
|
||||
|
||||
# 初始化读取Json
|
||||
await g_pJsonManager.init()
|
||||
|
||||
# 初始化数据库变量 和 加载作物数据库
|
||||
await g_pDBService.init()
|
||||
|
||||
# 检查作物文件是否缺失 or 更新
|
||||
await g_pRequestManager.initPlantDBFile()
|
||||
|
||||
await g_pHelpManager.createHelpImage()
|
||||
|
||||
|
||||
# 析构函数
|
||||
@driver.on_shutdown
|
||||
async def shutdown():
|
||||
# 单独卸载 作物数据库
|
||||
await g_pDBService.cleanup()
|
||||
|
||||
await g_pSqlManager.cleanup()
|
||||
|
||||
|
||||
@scheduler.scheduled_job(trigger="cron", hour=4, minute=30, id="signInFile")
|
||||
async def signInFile():
|
||||
try:
|
||||
await g_pJsonManager.initSignInFile()
|
||||
await g_pRequestManager.initPlantDBFile()
|
||||
except Exception as e:
|
||||
logger.error("农场定时检查出错", e=e)
|
||||
|
||||
@@ -1,83 +1,145 @@
|
||||
from nonebot.adapters import Event, MessageTemplate
|
||||
import inspect
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
from nonebot_plugin_alconna import (Alconna, AlconnaQuery, Args, Arparma, At,
|
||||
Match, MultiVar, Option, Query, Subcommand,
|
||||
on_alconna, store_true)
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
AlconnaQuery,
|
||||
Args,
|
||||
At,
|
||||
Match,
|
||||
MultiVar,
|
||||
Option,
|
||||
Query,
|
||||
Subcommand,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
from nonebot_plugin_waiter import waiter
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.depends import UserName
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
from .database import g_pSqlManager
|
||||
from .farm.farm import g_pFarmManager
|
||||
from .farm.shop import g_pShopManager
|
||||
|
||||
|
||||
async def isRegisteredByUid(uid: str) -> bool:
|
||||
point = await g_pSqlManager.getUserPointByUid(uid)
|
||||
|
||||
if point < 0:
|
||||
await MessageUtils.build_message("尚未开通农场,快at我发送 开通农场 开通吧").send()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
from .core.dbService import g_pDBService
|
||||
from .core.farm import g_pFarmManager
|
||||
from .core.shop import g_pShopManager
|
||||
from .utils.config import g_bSignStatus, g_sTranslation
|
||||
from .utils.json import g_pJsonManager
|
||||
from .utils.tool import g_pToolManager
|
||||
|
||||
diuse_register = on_alconna(
|
||||
Alconna("开通农场"),
|
||||
priority=5,
|
||||
rule=to_me(),
|
||||
block=True,
|
||||
use_cmd_start=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_register.handle()
|
||||
async def _(session: Uninfo):
|
||||
async def handle_register(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is not None and await player.isRegistered():
|
||||
await MessageUtils.build_message(g_sTranslation["register"]["already"]).send(
|
||||
reply_to=True
|
||||
)
|
||||
return
|
||||
|
||||
user = await g_pSqlManager.getUserInfoByUid(uid)
|
||||
try:
|
||||
raw_name = str(session.user.name)
|
||||
safe_name = g_pToolManager.sanitize_username(raw_name)
|
||||
|
||||
if user:
|
||||
await MessageUtils.build_message("你已经有啦").send(reply_to=True)
|
||||
else:
|
||||
aaa = await g_pSqlManager.initUserInfoByUid(uid, str(session.user.name), 0, 100)
|
||||
success = await g_pDBService.user.initUserInfo(uid, safe_name)
|
||||
|
||||
logger.info(f"用户 {uid} 选择的农场名称为: {raw_name} | 过滤后为: {safe_name}")
|
||||
|
||||
msg = (
|
||||
g_sTranslation["register"]["success"].format(point=500)
|
||||
if success
|
||||
else g_sTranslation["register"]["error"]
|
||||
)
|
||||
logger.info(f"用户注册 {'成功' if success else '失败'}:{uid}")
|
||||
|
||||
except Exception as e:
|
||||
msg = g_sTranslation["register"]["error"]
|
||||
logger.error(f"注册异常 | UID:{uid} | 错误:{e}")
|
||||
|
||||
await MessageUtils.build_message(msg).send(reply_to=True)
|
||||
|
||||
await MessageUtils.build_message(str(aaa)).send(reply_to=True)
|
||||
|
||||
diuse_farm = on_alconna(
|
||||
Alconna(
|
||||
"我的农场",
|
||||
Option("--all", action=store_true),
|
||||
Subcommand("detail", help_text="农场详述"),
|
||||
Subcommand("my-point", help_text="我的农场币"),
|
||||
Subcommand("seed-shop", Args["num?", int], help_text="种子商店"),
|
||||
Subcommand("seed-shop", Args["res?", MultiVar(str)], help_text="种子商店"),
|
||||
Subcommand("buy-seed", Args["name?", str]["num?", int], help_text="购买种子"),
|
||||
Subcommand("my-seed", help_text="我的种子"),
|
||||
Subcommand("sowing", Args["name?", str]["num?", int], help_text="播种"),
|
||||
Subcommand("harvest", help_text="收获"),
|
||||
Subcommand("eradicate", help_text="铲除"),
|
||||
Subcommand("my-plant", help_text="我的作物"),
|
||||
# Subcommand("reclamation", Args["isBool?", str], 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="购买农场币"),
|
||||
#Subcommand("sell-point", Args["num?", int], help_text="转换金币")
|
||||
# Subcommand("sell-point", Args["num?", int], help_text="转换金币")
|
||||
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="我的点券"),
|
||||
Subcommand("farm-help", help_text="农场帮助"),
|
||||
Subcommand("vipSeed-shop", Args["res?", MultiVar(str)], help_text="种子商店"),
|
||||
),
|
||||
priority=5,
|
||||
block=True,
|
||||
use_cmd_start=True,
|
||||
)
|
||||
|
||||
@diuse_farm.assign("$main")
|
||||
async def _(session: Uninfo, nickname: str = UserName()):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
@diuse_farm.assign("$main")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
image = await g_pFarmManager.drawFarmByUid(uid, nickname)
|
||||
image = await g_pFarmManager.drawFarmByUid(uid)
|
||||
await MessageUtils.build_message(image).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"农场详述",
|
||||
command="我的农场",
|
||||
arguments=["detail"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("detail")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
info = await g_pFarmManager.drawDetailFarmByUid(uid)
|
||||
|
||||
await MessageUtils.alc_forward_msg(
|
||||
[info], session.self_id, BotConfig.self_nickname
|
||||
).send()
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"我的农场币",
|
||||
command="我的农场",
|
||||
@@ -85,16 +147,25 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("my-point")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
point = await g_pSqlManager.getUserPointByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
point = player.user["point"]
|
||||
|
||||
if point < 0:
|
||||
await MessageUtils.build_message("尚未开通农场,快at我发送 开通农场 开通吧").send()
|
||||
await MessageUtils.build_message(g_sTranslation["basic"]["notFarm"]).send()
|
||||
return False
|
||||
|
||||
await MessageUtils.build_message(f"你的当前农场币为: {point}").send(reply_to=True)
|
||||
await MessageUtils.build_message(
|
||||
g_sTranslation["basic"]["point"].format(point=point)
|
||||
).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"种子商店(.*?)",
|
||||
@@ -103,16 +174,47 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
@diuse_farm.assign("seed-shop")
|
||||
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 1)):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
@diuse_farm.assign("seed-shop")
|
||||
async def _(session: Uninfo, res: Match[tuple[str, ...]]):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
image = await g_pShopManager.getSeedShopImage(num.result)
|
||||
if res.result is inspect._empty:
|
||||
raw = []
|
||||
else:
|
||||
raw = res.result
|
||||
|
||||
filterKey: str | int | None = None
|
||||
page: int = 1
|
||||
|
||||
if len(raw) >= 1 and raw[0] is not None:
|
||||
first = raw[0]
|
||||
if isinstance(first, str) and first.isdigit():
|
||||
page = int(first)
|
||||
else:
|
||||
filterKey = first
|
||||
|
||||
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:
|
||||
image = await g_pShopManager.getSeedShopImage(page, 0, 0)
|
||||
else:
|
||||
image = await g_pShopManager.getSeedShopImage(filterKey, page, 0)
|
||||
|
||||
await MessageUtils.build_message(image).send()
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"购买种子(?P<name>.*?)",
|
||||
command="我的农场",
|
||||
@@ -120,21 +222,26 @@ 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(
|
||||
"请在指令后跟需要购买的种子名称"
|
||||
).finish(reply_to=True)
|
||||
await MessageUtils.build_message(g_sTranslation["buySeed"]["notSeed"]).finish(
|
||||
reply_to=True
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pShopManager.buySeed(uid, name.result, num.result)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"我的种子",
|
||||
command="我的农场",
|
||||
@@ -142,16 +249,19 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("my-seed")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.getUserSeedByUid(uid)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"播种(?P<name>.*?)",
|
||||
command="我的农场",
|
||||
@@ -159,16 +269,20 @@ 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(
|
||||
"请在指令后跟需要播种的种子名称"
|
||||
).finish(reply_to=True)
|
||||
await MessageUtils.build_message(g_sTranslation["sowing"]["notSeed"]).finish(
|
||||
reply_to=True
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.sowing(uid, name.result, num.result)
|
||||
@@ -182,16 +296,19 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("harvest")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.harvest(uid)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"铲除",
|
||||
command="我的农场",
|
||||
@@ -199,11 +316,13 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("eradicate")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.eradicate(uid)
|
||||
@@ -217,47 +336,58 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("my-plant")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.getUserPlantByUid(uid)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
reclamation = on_alconna(
|
||||
Alconna("开垦"),
|
||||
priority=5,
|
||||
block=True,
|
||||
diuse_farm.shortcut(
|
||||
"作物加锁(?P<name>)",
|
||||
command="我的农场",
|
||||
arguments=["lock-plant", "{name}"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
@reclamation.handle()
|
||||
async def _(session: Uninfo):
|
||||
|
||||
@diuse_farm.assign("lock-plant")
|
||||
async def _(session: Uninfo, name: Match[str]):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
condition = await g_pFarmManager.reclamationCondition(uid)
|
||||
condition += "\n 回复是将执行开垦"
|
||||
await MessageUtils.build_message(condition).send(reply_to=True)
|
||||
result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 1)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
@waiter(waits=["message"], keep_session=True)
|
||||
async def check(event: Event):
|
||||
return event.get_plaintext()
|
||||
|
||||
resp = await check.wait(timeout=60)
|
||||
if resp is None:
|
||||
await MessageUtils.build_message("等待超时").send(reply_to=True)
|
||||
return
|
||||
if not resp == "是":
|
||||
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)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
res = await g_pFarmManager.reclamation(uid)
|
||||
await MessageUtils.build_message(res).send(reply_to=True)
|
||||
result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 0)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"出售作物(?P<name>.*?)",
|
||||
@@ -266,21 +396,58 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("sell-plant")
|
||||
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1),):
|
||||
if not name.available:
|
||||
await MessageUtils.build_message(
|
||||
"请在指令后跟需要出售的作物名称"
|
||||
).finish(reply_to=True)
|
||||
|
||||
async def _(
|
||||
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)
|
||||
):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
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)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
condition = await g_pFarmManager.reclamationCondition(uid)
|
||||
condition += f"\n{g_sTranslation['reclamation']['confirm']}"
|
||||
await MessageUtils.build_message(condition).send(reply_to=True)
|
||||
|
||||
@waiter(waits=["message"], keep_session=True)
|
||||
async def check(event: Event):
|
||||
return event.get_plaintext()
|
||||
|
||||
resp = await check.wait(timeout=60)
|
||||
if resp is None:
|
||||
await MessageUtils.build_message(g_sTranslation["reclamation"]["timeOut"]).send(
|
||||
reply_to=True
|
||||
)
|
||||
return
|
||||
if not resp == "是":
|
||||
return
|
||||
|
||||
res = await g_pFarmManager.reclamation(uid)
|
||||
await MessageUtils.build_message(res).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"偷菜",
|
||||
command="我的农场",
|
||||
@@ -288,26 +455,33 @@ diuse_farm.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("stealing")
|
||||
async def _(session: Uninfo, target: Match[At]):
|
||||
uid = str(session.user.id)
|
||||
|
||||
if await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
if not target.available:
|
||||
await MessageUtils.build_message("请在指令后跟需要at的人").finish(reply_to=True)
|
||||
await MessageUtils.build_message(g_sTranslation["stealing"]["noTarget"]).finish(
|
||||
reply_to=True
|
||||
)
|
||||
|
||||
tar = target.result
|
||||
point = await g_pSqlManager.getUserPointByUid(tar.target)
|
||||
result = await g_pDBService.user.isRegistered(tar.target)
|
||||
|
||||
if point < 0:
|
||||
await MessageUtils.build_message("目标尚未开通农场,快邀请ta开通吧").send()
|
||||
if not result:
|
||||
await MessageUtils.build_message(
|
||||
g_sTranslation["stealing"]["targetNotFarm"]
|
||||
).send()
|
||||
return None
|
||||
|
||||
result = await g_pFarmManager.stealing(uid, tar.target)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"购买农场币(.*?)",
|
||||
command="我的农场",
|
||||
@@ -315,17 +489,290 @@ 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 await isRegisteredByUid(uid) == False:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.buyPointByUid(uid, num.result)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"更改农场名(?P<name>)",
|
||||
command="我的农场",
|
||||
arguments=["change-name", "{name}"],
|
||||
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
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None:
|
||||
await MessageUtils.build_message(g_sTranslation["changeName"]["error1"]).send(
|
||||
reply_to=True
|
||||
)
|
||||
return
|
||||
|
||||
result = await player.updateName(name.result)
|
||||
await MessageUtils.build_message(g_sTranslation["changeName"][result]).send(
|
||||
reply_to=True
|
||||
)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"农场签到",
|
||||
command="我的农场",
|
||||
arguments=["sign-in"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("sign-in")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
# 判断签到是否正常加载
|
||||
if not g_bSignStatus:
|
||||
await MessageUtils.build_message(g_sTranslation["signIn"]["error"]).send()
|
||||
|
||||
return
|
||||
|
||||
toDay = g_pToolManager.dateTime().date().today()
|
||||
message = ""
|
||||
status = await g_pDBService.userSign.sign(uid, toDay.strftime("%Y-%m-%d"))
|
||||
|
||||
# 如果完成签到
|
||||
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")
|
||||
)
|
||||
|
||||
message += g_sTranslation["signIn"]["success"].format(
|
||||
day=signDay, exp=exp, num=point
|
||||
)
|
||||
|
||||
reward = g_pJsonManager.m_pSign["continuou"].get(f"{signDay}", None)
|
||||
|
||||
if reward:
|
||||
extraPoint = reward.get("point", 0)
|
||||
extraExp = reward.get("exp", 0)
|
||||
|
||||
plant = reward.get("plant", {})
|
||||
|
||||
message += g_sTranslation["signIn"]["grandTotal"].format(
|
||||
exp=extraExp, num=extraPoint
|
||||
)
|
||||
|
||||
vipPoint = reward.get("vipPoint", 0)
|
||||
|
||||
if vipPoint > 0:
|
||||
message += g_sTranslation["signIn"]["grandTotal1"].format(num=vipPoint)
|
||||
|
||||
if plant:
|
||||
for key, value in plant.items():
|
||||
message += g_sTranslation["signIn"]["grandTotal2"].format(
|
||||
name=key, num=value
|
||||
)
|
||||
else:
|
||||
message = g_sTranslation["signIn"]["error1"]
|
||||
|
||||
await MessageUtils.build_message(message).send()
|
||||
|
||||
|
||||
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)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
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="我的农场",
|
||||
arguments=["admin-up"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("admin-up")
|
||||
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
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)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
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)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
await MessageUtils.build_message(
|
||||
g_sTranslation["basic"]["vipPoint"].format(vipPoint=player.user["vipPoint"])
|
||||
).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"农场帮助",
|
||||
command="我的农场",
|
||||
arguments=["farm-help"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("farm-help")
|
||||
async def _(session: Uninfo):
|
||||
savePath = DATA_PATH / "farm_res/html/help.png"
|
||||
|
||||
image = BuildImage(background=savePath)
|
||||
|
||||
await MessageUtils.build_message(image).send(reply_to=True)
|
||||
|
||||
|
||||
diuse_farm.shortcut(
|
||||
"点券商店(.*?)",
|
||||
command="我的农场",
|
||||
arguments=["vipSeed-shop"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@diuse_farm.assign("vipSeed-shop")
|
||||
async def _(session: Uninfo, res: Match[tuple[str, ...]]):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if player is None or not await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
if res.result is inspect._empty:
|
||||
raw = []
|
||||
else:
|
||||
raw = res.result
|
||||
|
||||
filterKey: str | int | None = None
|
||||
page: int = 1
|
||||
|
||||
if len(raw) >= 1 and raw[0] is not None:
|
||||
first = raw[0]
|
||||
if isinstance(first, str) and first.isdigit():
|
||||
page = int(first)
|
||||
else:
|
||||
filterKey = first
|
||||
|
||||
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:
|
||||
image = await g_pShopManager.getSeedShopImage(page, 0, 1)
|
||||
else:
|
||||
image = await g_pShopManager.getSeedShopImage(filterKey, page, 1)
|
||||
|
||||
await MessageUtils.build_message(image).send()
|
||||
|
||||
@@ -8,80 +8,164 @@
|
||||
{
|
||||
"4":
|
||||
{
|
||||
"level": 2,
|
||||
"level": 2,
|
||||
"point": 800,
|
||||
"item": []
|
||||
},
|
||||
"5":
|
||||
{
|
||||
"level": 3,
|
||||
"level": 3,
|
||||
"point": 1300,
|
||||
"item": []
|
||||
},
|
||||
"6":
|
||||
{
|
||||
"level": 5,
|
||||
"level": 5,
|
||||
"point": 3200,
|
||||
"item": []
|
||||
},
|
||||
"7":
|
||||
{
|
||||
"level": 7,
|
||||
"level": 7,
|
||||
"point": 5500,
|
||||
"item": []
|
||||
},
|
||||
"8":
|
||||
{
|
||||
"level": 9,
|
||||
"level": 9,
|
||||
"point": 12000,
|
||||
"item": []
|
||||
},
|
||||
"9":
|
||||
{
|
||||
"level": 15,
|
||||
"level": 11,
|
||||
"point": 19800,
|
||||
"item": []
|
||||
},
|
||||
"10":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 13,
|
||||
"point": 30000,
|
||||
"item": []
|
||||
},
|
||||
"11":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 15,
|
||||
"point": 50000,
|
||||
"item": []
|
||||
},
|
||||
"12":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 17,
|
||||
"point": 70000,
|
||||
"item": []
|
||||
},
|
||||
"13":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 19,
|
||||
"point": 90000,
|
||||
"item": []
|
||||
},
|
||||
"14":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 21,
|
||||
"point": 130000,
|
||||
"item": []
|
||||
},
|
||||
"15":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 23,
|
||||
"point": 150000,
|
||||
"item": []
|
||||
},
|
||||
"16":
|
||||
{
|
||||
"level":3,
|
||||
"point": 3000,
|
||||
"level": 25,
|
||||
"point": 180000,
|
||||
"item": []
|
||||
},
|
||||
"17":
|
||||
{
|
||||
"level": 27,
|
||||
"point": 230000,
|
||||
"item": []
|
||||
},
|
||||
"18":
|
||||
{
|
||||
"level": 29,
|
||||
"point": 300000,
|
||||
"item": []
|
||||
},
|
||||
"19":
|
||||
{
|
||||
"level": 31,
|
||||
"point": 500000,
|
||||
"item": []
|
||||
},
|
||||
"20":
|
||||
{
|
||||
"level": 33,
|
||||
"point": 850000,
|
||||
"item": []
|
||||
},
|
||||
"21":
|
||||
{
|
||||
"level": 36,
|
||||
"point": 1100000,
|
||||
"item": []
|
||||
},
|
||||
"22":
|
||||
{
|
||||
"level": 39,
|
||||
"point": 1300000,
|
||||
"item": []
|
||||
},
|
||||
"23":
|
||||
{
|
||||
"level": 42,
|
||||
"point": 1500000,
|
||||
"item": []
|
||||
},
|
||||
"24":
|
||||
{
|
||||
"level": 45,
|
||||
"point": 1700000,
|
||||
"item": []
|
||||
},
|
||||
"25":
|
||||
{
|
||||
"level": 48,
|
||||
"point": 2000000,
|
||||
"item": []
|
||||
},
|
||||
"26":
|
||||
{
|
||||
"level": 51,
|
||||
"point": 2200000,
|
||||
"item": []
|
||||
},
|
||||
"27":
|
||||
{
|
||||
"level": 54,
|
||||
"point": 2800000,
|
||||
"item": []
|
||||
},
|
||||
"28":
|
||||
{
|
||||
"level": 57,
|
||||
"point": 3500000,
|
||||
"item": []
|
||||
},
|
||||
"29":
|
||||
{
|
||||
"level": 60,
|
||||
"point": 4300000,
|
||||
"item": []
|
||||
},
|
||||
"30":
|
||||
{
|
||||
"level": 70,
|
||||
"point": 5200000,
|
||||
"item": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
{
|
||||
"version": 0.01,
|
||||
"zhuShi":
|
||||
[
|
||||
"level: 解锁等级",
|
||||
"limit: 限制等级 0:普通土地 1:红土地 2:黄土地 3:黑土地",
|
||||
"experience: 收获经验",
|
||||
"harvest: 收获数量",
|
||||
"price: 单价",
|
||||
"time: 成熟时间 小时",
|
||||
"crop: 作物可以收几次",
|
||||
"again: 再次成熟时间 单位:小时",
|
||||
"phase: 阶段 目前为 成熟时间 / 阶段 来显示每阶段图片",
|
||||
"general: 第一阶段是否为通用阶段素材",
|
||||
"sell: 是否可以上架交易行"
|
||||
],
|
||||
"plant":
|
||||
{
|
||||
"胡萝卜":
|
||||
{
|
||||
"level": 0,
|
||||
"limit": 0,
|
||||
"experience": 18,
|
||||
"harvest": 17,
|
||||
"price": 21,
|
||||
"time": 13,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 5,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"白萝卜":
|
||||
{
|
||||
"level": 0,
|
||||
"limit": 0,
|
||||
"experience": 15,
|
||||
"harvest": 16,
|
||||
"price": 17,
|
||||
"time": 10,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 5,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"牧草":
|
||||
{
|
||||
"level": 0,
|
||||
"limit": 0,
|
||||
"experience": 10,
|
||||
"harvest": 25,
|
||||
"price": 6,
|
||||
"time": 8,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 5,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"大白菜":
|
||||
{
|
||||
"level": 1,
|
||||
"limit": 0,
|
||||
"experience": 19,
|
||||
"harvest": 17,
|
||||
"price": 22,
|
||||
"time": 14,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 5,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"大蒜":
|
||||
{
|
||||
"level": 1,
|
||||
"limit": 0,
|
||||
"experience": 19,
|
||||
"harvest": 17,
|
||||
"price": 22,
|
||||
"time": 14,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 6,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"水稻":
|
||||
{
|
||||
"level": 2,
|
||||
"limit": 0,
|
||||
"experience": 19,
|
||||
"harvest": 18,
|
||||
"price": 21,
|
||||
"time": 14,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 6,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"小麦":
|
||||
{
|
||||
"level": 2,
|
||||
"limit": 0,
|
||||
"experience": 19,
|
||||
"harvest": 18,
|
||||
"price": 21,
|
||||
"time": 14,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 6,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"玉米":
|
||||
{
|
||||
"level": 3,
|
||||
"limit": 0,
|
||||
"experience": 19,
|
||||
"harvest": 17,
|
||||
"price": 23,
|
||||
"time": 14,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 6,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"油菜":
|
||||
{
|
||||
"level": 4,
|
||||
"limit": 0,
|
||||
"experience": 29,
|
||||
"harvest": 23,
|
||||
"price": 24,
|
||||
"time": 17,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 5,
|
||||
"general": true,
|
||||
"sell": false
|
||||
},
|
||||
"生菜":
|
||||
{
|
||||
"level": 4,
|
||||
"limit": 0,
|
||||
"experience": 25,
|
||||
"harvest": 21,
|
||||
"price": 24,
|
||||
"time": 19,
|
||||
"crop": 1,
|
||||
"again": 0,
|
||||
"phase": 6,
|
||||
"general": true,
|
||||
"sell": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,5 +153,104 @@
|
||||
"x": 1451,
|
||||
"y": 1072
|
||||
}
|
||||
},
|
||||
"upgrade":
|
||||
{
|
||||
"red": [
|
||||
{ "level": 28, "point": 200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 29, "point": 220000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 30, "point": 240000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 31, "point": 260000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 32, "point": 290000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 33, "point": 320000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 34, "point": 350000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 35, "point": 380000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 36, "point": 410000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 37, "point": 440000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 38, "point": 480000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 39, "point": 520000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 40, "point": 560000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 41, "point": 600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 42, "point": 650000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 43, "point": 700000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 44, "point": 770000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 45, "point": 900000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 47, "point": 1500000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 49, "point": 2000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 53, "point": 4000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 53, "point": 4000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 55, "point": 5500000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 57, "point": 6800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 60, "point": 10000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 64, "point": 15000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 68, "point": 20000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 73, "point": 30000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 78, "point": 50000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 83, "point": 80000000, "vipPoint": 0, "item":{}}
|
||||
],
|
||||
"black": [
|
||||
{ "level": 40, "point": 500000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 41, "point": 600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 42, "point": 700000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 43, "point": 800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 44, "point": 900000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 45, "point": 1000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 46, "point": 2000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 47, "point": 2200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 48, "point": 2400000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 49, "point": 2600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 50, "point": 2800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 51, "point": 3000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 52, "point": 4000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 53, "point": 4200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 54, "point": 4400000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 55, "point": 4600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 56, "point": 4800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 57, "point": 5000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 59, "point": 6000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 61, "point": 6200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 65, "point": 6600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 65, "point": 6600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 67, "point": 6800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 69, "point": 7000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 72, "point": 10000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 76, "point": 16000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 80, "point": 27000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 85, "point": 43000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 90, "point": 65000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 95, "point": 93000000, "vipPoint": 0, "item":{}}
|
||||
],
|
||||
"gold": [
|
||||
{ "level": 58, "point": 4000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 59, "point": 4200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 60, "point": 4400000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 61, "point": 4600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 62, "point": 4800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 63, "point": 5000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 64, "point": 5200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 65, "point": 5400000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 66, "point": 5600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 67, "point": 5800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 68, "point": 6000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 69, "point": 6200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 70, "point": 6600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 71, "point": 7000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 72, "point": 7400000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 73, "point": 7800000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 74, "point": 8200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 75, "point": 8600000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 77, "point": 9500000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 79, "point": 10400000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 83, "point": 12200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 83, "point": 12200000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 85, "point": 13100000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 87, "point": 14000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 90, "point": 16000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 94, "point": 20000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 98, "point": 28000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 103, "point": 44000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 108, "point": 76000000, "vipPoint": 0, "item":{}},
|
||||
{ "level": 113, "point": 1080000000, "vipPoint": 0, "item":{}}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
class ActivityManager:
|
||||
"""活动管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.activities = {} # 活动ID -> 活动配置
|
||||
self.active_activities = set() # 当前活跃的活动ID
|
||||
self.effect_handlers = {} # 效果类型 -> 处理器
|
||||
|
||||
# 注册效果处理器
|
||||
self._register_handlers()
|
||||
|
||||
def _register_handlers(self):
|
||||
"""注册所有效果处理器"""
|
||||
self.effect_handlers[EffectType.MULTIPLIER] = MultiplierHandler()
|
||||
self.effect_handlers[EffectType.FIXED_BONUS] = FixedBonusHandler()
|
||||
self.effect_handlers[EffectType.BUFF_APPLICATION] = BuffApplicationHandler()
|
||||
self.effect_handlers[EffectType.QUEST_TRIGGER] = QuestTriggerHandler()
|
||||
|
||||
def load_activities_from_config(self, config_path: str):
|
||||
"""从配置文件加载活动"""
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
for activity_data in config["activities"]:
|
||||
activity = Activity(activity_data)
|
||||
self.activities[activity.id] = activity
|
||||
print(f"加载活动: {activity.name}")
|
||||
|
||||
def update_activity_status(self):
|
||||
"""更新活动状态(定时调用)"""
|
||||
now = datetime.now()
|
||||
|
||||
for activity in self.activities.values():
|
||||
is_active = activity.start_time <= now <= activity.end_time
|
||||
|
||||
if is_active and activity.id not in self.active_activities:
|
||||
# 活动开始
|
||||
self.active_activities.add(activity.id)
|
||||
print(f"活动开始: {activity.name}")
|
||||
|
||||
elif not is_active and activity.id in self.active_activities:
|
||||
# 活动结束
|
||||
self.active_activities.remove(activity.id)
|
||||
print(f"活动结束: {activity.name}")
|
||||
|
||||
def get_active_activities(self, activity_type: ActivityType = None) -> List:
|
||||
"""获取当前活跃的活动"""
|
||||
active_list = []
|
||||
|
||||
for activity_id in self.active_activities:
|
||||
activity = self.activities[activity_id]
|
||||
if activity_type is None or activity.activity_type == activity_type:
|
||||
active_list.append(activity)
|
||||
|
||||
return active_list
|
||||
|
||||
def apply_activity_effects(
|
||||
self,
|
||||
activity_type: ActivityType,
|
||||
player: Player,
|
||||
base_value: int,
|
||||
context: Dict = None,
|
||||
) -> int:
|
||||
"""应用活动效果"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
context["base_value"] = base_value
|
||||
result = base_value
|
||||
|
||||
# 获取同类型的所有活跃活动
|
||||
active_activities = self.get_active_activities(activity_type)
|
||||
|
||||
for activity in active_activities:
|
||||
print(f"为玩家 {player.player_id} 应用活动: {activity.name}")
|
||||
|
||||
for effect in activity.effects:
|
||||
handler = self.effect_handlers.get(effect.type)
|
||||
if handler:
|
||||
try:
|
||||
effect_result = handler.execute(effect.params, player, context)
|
||||
if effect_result is not None:
|
||||
result = effect_result
|
||||
context["base_value"] = result # 更新基础值供后续效果使用
|
||||
except Exception as e:
|
||||
print(f"效果执行失败: {effect.type}, 错误: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Activity:
|
||||
"""活动类"""
|
||||
|
||||
def __init__(self, data: Dict):
|
||||
self.id = data["id"]
|
||||
self.name = data["name"]
|
||||
self.activity_type = ActivityType(data["activity_type"])
|
||||
self.start_time = datetime.strptime(data["start_time"], "%Y-%m-%d %H:%M:%S")
|
||||
self.end_time = datetime.strptime(data["end_time"], "%Y-%m-%d %H:%M:%S")
|
||||
self.effects = [Effect(effect_data) for effect_data in data["effects"]]
|
||||
|
||||
|
||||
class Effect:
|
||||
"""效果类"""
|
||||
|
||||
def __init__(self, data: Dict):
|
||||
self.type = EffectType(data["type"])
|
||||
self.params = data.get("params", {})
|
||||
@@ -0,0 +1,95 @@
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
|
||||
# 活动类型枚举
|
||||
class ActivityType(Enum):
|
||||
PLANTING = "planting" # 种植活动
|
||||
HARVESTING = "harvesting" # 收获活动
|
||||
FISHING = "fishing" # 钓鱼活动
|
||||
COMBAT = "combat" # 战斗活动
|
||||
|
||||
|
||||
# 效果类型枚举
|
||||
class EffectType(Enum):
|
||||
MULTIPLIER = "multiplier" # 倍数加成
|
||||
FIXED_BONUS = "fixed_bonus" # 固定加成
|
||||
BUFF_APPLICATION = "buff_application" # 施加BUFF
|
||||
QUEST_TRIGGER = "quest_trigger" # 任务触发
|
||||
|
||||
|
||||
class EffectHandler:
|
||||
"""
|
||||
效果处理器基类
|
||||
"""
|
||||
|
||||
def execute(self, params: dict, uid: str, context: dict) -> Any:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MultiplierHandler(EffectHandler):
|
||||
"""
|
||||
倍数效果处理器
|
||||
"""
|
||||
|
||||
def execute(self, params: dict, uid: str, context: dict) -> float:
|
||||
base_value = context.get("base_value", 0)
|
||||
multiplier = params.get("value", 1.0)
|
||||
|
||||
# 检查条件
|
||||
if self._check_conditions(params.get("conditions", {}), player):
|
||||
result = base_value * multiplier
|
||||
print(f"倍数效果: {base_value} × {multiplier} = {result}")
|
||||
return result
|
||||
return base_value
|
||||
|
||||
def _check_conditions(self, conditions: dict, player: Player) -> bool:
|
||||
"""检查生效条件"""
|
||||
# 等级要求
|
||||
min_level = conditions.get("min_level", 0)
|
||||
if player.level < min_level:
|
||||
return False
|
||||
|
||||
# 需要特定物品
|
||||
required_items = conditions.get("required_items", [])
|
||||
for item in required_items:
|
||||
if player.inventory.get(item, 0) <= 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class FixedBonusHandler(EffectHandler):
|
||||
"""固定加成处理器"""
|
||||
|
||||
def execute(self, params: dict, player: Player, context: dict) -> int:
|
||||
base_value = context.get("base_value", 0)
|
||||
bonus = params.get("value", 0)
|
||||
|
||||
result = base_value + bonus
|
||||
print(f"固定加成: {base_value} + {bonus} = {result}")
|
||||
return result
|
||||
|
||||
|
||||
class BuffApplicationHandler(EffectHandler):
|
||||
"""BUFF应用处理器"""
|
||||
|
||||
def execute(self, params: dict, player: Player, context: dict) -> None:
|
||||
buff_id = params["buff_id"]
|
||||
duration = params.get("duration", 3600) # 默认1小时
|
||||
properties = params.get("properties", {})
|
||||
|
||||
player.add_buff(buff_id, duration, properties)
|
||||
|
||||
# 立即应用BUFF效果(如果有)
|
||||
if "immediate_effect" in params:
|
||||
self._apply_immediate_effect(params["immediate_effect"], player)
|
||||
|
||||
|
||||
class QuestTriggerHandler(EffectHandler):
|
||||
"""任务触发处理器"""
|
||||
|
||||
def execute(self, params: dict, player: Player, context: dict) -> None:
|
||||
quest_id = params["quest_id"]
|
||||
print(f"为玩家 {player.player_id} 触发任务: {quest_id}")
|
||||
# 这里会调用任务系统来分配任务
|
||||
@@ -0,0 +1,399 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ...utils.config import g_sDBFilePath, g_sDBPath
|
||||
|
||||
|
||||
class CSqlManager:
|
||||
def __init__(self):
|
||||
dbPath = Path(g_sDBPath)
|
||||
if dbPath and not dbPath.exists():
|
||||
os.makedirs(dbPath, exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
async def cleanup(cls):
|
||||
if hasattr(cls, "m_pDB") and cls.m_pDB:
|
||||
await cls.m_pDB.close()
|
||||
|
||||
@classmethod
|
||||
async def init(cls) -> bool:
|
||||
try:
|
||||
cls.m_pDB = await aiosqlite.connect(g_sDBFilePath)
|
||||
cls.m_pDB.row_factory = aiosqlite.Row
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场初始化总数据库失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
@asynccontextmanager
|
||||
async def _transaction(cls):
|
||||
await cls.m_pDB.execute("BEGIN;")
|
||||
try:
|
||||
yield
|
||||
except:
|
||||
await cls.m_pDB.execute("ROLLBACK;")
|
||||
raise
|
||||
else:
|
||||
await cls.m_pDB.execute("COMMIT;")
|
||||
|
||||
@classmethod
|
||||
async def getTableInfo(cls, tableName: str) -> list:
|
||||
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}")')
|
||||
rows = await cursor.fetchall()
|
||||
return [{"name": row[1], "type": row[2]} for row in rows]
|
||||
except aiosqlite.Error:
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def ensureTableSchema(cls, tableName: str, columns: dict) -> bool:
|
||||
"""由AI生成
|
||||
创建表或为已存在表添加缺失字段。
|
||||
返回 True 表示有变更(创建或新增列),False 则无操作
|
||||
|
||||
Args:
|
||||
tableName (_type_): 表名
|
||||
columns (_type_): 字典
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
|
||||
info = await cls.getTableInfo(tableName)
|
||||
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", "")
|
||||
|
||||
if not existing:
|
||||
colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items())
|
||||
if primaryKey:
|
||||
colsDef += f", PRIMARY KEY {primaryKey}"
|
||||
await cls.m_pDB.execute(f'CREATE TABLE "{tableName}" ({colsDef});')
|
||||
return True
|
||||
|
||||
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]
|
||||
]
|
||||
|
||||
if toAdd and not toRemove and not typeMismatch:
|
||||
for col in toAdd:
|
||||
await cls.m_pDB.execute(
|
||||
f'ALTER TABLE "{tableName}" ADD COLUMN "{col}" {columns[col]}'
|
||||
)
|
||||
return True
|
||||
|
||||
async with cls._transaction():
|
||||
tmpTable = f"{tableName}_new"
|
||||
colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items())
|
||||
if primaryKey:
|
||||
colsDef += f", PRIMARY KEY {primaryKey}"
|
||||
await cls.m_pDB.execute(f'CREATE TABLE "{tmpTable}" ({colsDef});')
|
||||
|
||||
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}";'
|
||||
)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def executeDB(cls, command: str) -> bool:
|
||||
"""执行自定义SQL
|
||||
|
||||
Args:
|
||||
command (str): SQL语句
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not command:
|
||||
return False
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(command)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def insert(cls, tableName: str, data: dict) -> bool:
|
||||
"""
|
||||
插入数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
data: 要插入的数据字典,键为字段名,值为字段值
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not data:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 构建参数化查询
|
||||
columns = ", ".join(f'"{k}"' for k in data.keys())
|
||||
placeholders = ", ".join("?" for _ in data.keys())
|
||||
values = list(data.values())
|
||||
|
||||
sql = f'INSERT INTO "{tableName}" ({columns}) VALUES ({placeholders})'
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(sql, values)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场插入数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def batch_insert(cls, tableName: str, data_list: list) -> bool:
|
||||
"""
|
||||
批量插入数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
data_list: 要插入的数据字典列表
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not data_list:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 使用第一个字典的键作为所有记录的字段
|
||||
columns = ", ".join(f'"{k}"' for k in data_list[0].keys())
|
||||
placeholders = ", ".join("?" for _ in data_list[0].keys())
|
||||
|
||||
sql = f'INSERT INTO "{tableName}" ({columns}) VALUES ({placeholders})'
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.executemany(
|
||||
sql, [list(data.values()) for data in data_list]
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场批量插入数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def select(
|
||||
cls,
|
||||
tableName: str,
|
||||
columns: list[Any] | None = None,
|
||||
where: dict[str, Any] | None = None,
|
||||
order_by: str | None = None,
|
||||
limit: int | None = None,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
查询数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
columns: 要查询的字段列表,None表示所有字段
|
||||
where: 查询条件字典
|
||||
order_by: 排序字段
|
||||
limit: 限制返回记录数
|
||||
|
||||
Returns:
|
||||
list: 查询结果列表,每个元素是一个字典
|
||||
"""
|
||||
try:
|
||||
# 构建SELECT部分
|
||||
if columns:
|
||||
select_clause = ", ".join(f'"{col}"' for col in columns)
|
||||
else:
|
||||
select_clause = "*"
|
||||
|
||||
sql = f'SELECT {select_clause} FROM "{tableName}"'
|
||||
|
||||
# 构建WHERE部分
|
||||
params = []
|
||||
if where:
|
||||
where_conditions = []
|
||||
for key, value in where.items():
|
||||
if isinstance(value, (list, tuple)):
|
||||
# 处理IN查询
|
||||
placeholders = ", ".join("?" for _ in value)
|
||||
where_conditions.append(f'"{key}" IN ({placeholders})')
|
||||
params.extend(value)
|
||||
else:
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
if where_conditions:
|
||||
sql += " WHERE " + " AND ".join(where_conditions)
|
||||
|
||||
# 构建ORDER BY部分
|
||||
if order_by:
|
||||
sql += f" ORDER BY {order_by}"
|
||||
|
||||
# 构建LIMIT部分
|
||||
if limit:
|
||||
sql += f" LIMIT {limit}"
|
||||
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
# 转换为字典列表
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场查询数据失败!", e=e)
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def update(cls, tableName: str, data: dict, where: dict) -> bool:
|
||||
"""
|
||||
更新数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
data: 要更新的数据字典
|
||||
where: 更新条件字典
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not data:
|
||||
return False
|
||||
|
||||
if not where:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 构建SET部分
|
||||
set_conditions = []
|
||||
params = []
|
||||
for key, value in data.items():
|
||||
set_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
# 构建WHERE部分
|
||||
where_conditions = []
|
||||
for key, value in where.items():
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
sql = f'UPDATE "{tableName}" SET {", ".join(set_conditions)} WHERE {" AND ".join(where_conditions)}'
|
||||
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
# 检查是否影响了行
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场更新数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, tableName: str, where: dict) -> bool:
|
||||
"""
|
||||
删除数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
where: 删除条件字典
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not where:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 构建WHERE部分
|
||||
where_conditions = []
|
||||
params = []
|
||||
for key, value in where.items():
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
sql = f'DELETE FROM "{tableName}" WHERE {" AND ".join(where_conditions)}'
|
||||
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
# 检查是否影响了行
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场删除数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def exists(cls, tableName: str, where: dict) -> bool:
|
||||
"""
|
||||
检查记录是否存在
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
where: 查询条件字典
|
||||
|
||||
Returns:
|
||||
bool: 是否存在符合条件的记录
|
||||
"""
|
||||
try:
|
||||
result = await cls.select(tableName, columns=["1"], where=where, limit=1)
|
||||
return len(result) > 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场检查数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def count(cls, tableName: str, where: dict = {}) -> int:
|
||||
"""
|
||||
统计记录数量
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
where: 查询条件字典
|
||||
|
||||
Returns:
|
||||
int: 记录数量
|
||||
"""
|
||||
try:
|
||||
# 构建WHERE部分
|
||||
sql = f'SELECT COUNT(*) as count FROM "{tableName}"'
|
||||
params = []
|
||||
|
||||
if where:
|
||||
where_conditions = []
|
||||
for key, value in where.items():
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
sql += " WHERE " + " AND ".join(where_conditions)
|
||||
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
row = await cursor.fetchone()
|
||||
return row["count"] if row else 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场统计数据失败!", e=e)
|
||||
return 0
|
||||
|
||||
|
||||
g_pSqlManager = CSqlManager()
|
||||
@@ -0,0 +1,281 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import os
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ...utils.config import g_bIsDebug, g_sPlantPath, g_sResourcePath
|
||||
from ...utils.request import g_pRequestManager
|
||||
|
||||
|
||||
class CPlantManager:
|
||||
def __init__(self):
|
||||
try:
|
||||
os.mkdir(g_sPlantPath)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
async def cleanup(self):
|
||||
if hasattr(self, "m_pDB") and self.m_pDB:
|
||||
await self.m_pDB.close()
|
||||
|
||||
async def init(self) -> bool:
|
||||
try:
|
||||
_ = os.path.exists(g_sPlantPath)
|
||||
|
||||
if g_bIsDebug:
|
||||
self.m_pDB = await aiosqlite.connect(
|
||||
str(g_sPlantPath.parent / "plant-test.db")
|
||||
)
|
||||
else:
|
||||
self.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
|
||||
|
||||
self.m_pDB.row_factory = aiosqlite.Row
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("初始化植物数据库失败", e=e)
|
||||
return False
|
||||
|
||||
@asynccontextmanager
|
||||
async def _transaction(self):
|
||||
await self.m_pDB.execute("BEGIN;")
|
||||
try:
|
||||
yield
|
||||
except:
|
||||
await self.m_pDB.execute("ROLLBACK;")
|
||||
raise
|
||||
else:
|
||||
await self.m_pDB.execute("COMMIT;")
|
||||
|
||||
async def executeDB(self, command: str) -> bool:
|
||||
"""执行自定义SQL
|
||||
|
||||
Args:
|
||||
command (str): SQL语句
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not command:
|
||||
logger.warning("数据库语句长度为空!")
|
||||
return False
|
||||
|
||||
try:
|
||||
async with self._transaction():
|
||||
await self.m_pDB.execute(command)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"数据库语句执行出错: {command}", e=e)
|
||||
return False
|
||||
|
||||
async def getPlantByName(self, name: str) -> dict | None:
|
||||
"""根据作物名称查询记录
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
dict | None: 返回记录字典,未找到返回None
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT * FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物失败: {name}", e=e)
|
||||
return None
|
||||
|
||||
async def getPlantPhaseByName(self, name: str) -> list[int]:
|
||||
"""根据作物名称获取作物各个阶段
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
list: 阶段数组
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return []
|
||||
|
||||
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)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return []
|
||||
|
||||
async def getPlantPhaseNumberByName(self, name: str) -> int:
|
||||
"""根据作物名称获取作物总阶段数
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
int: 总阶段数
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return -1
|
||||
|
||||
phase = row[0].split(",")
|
||||
|
||||
# 去重
|
||||
seen = set()
|
||||
result = []
|
||||
for x in phase:
|
||||
if x not in seen:
|
||||
seen.add(x)
|
||||
result.append(x)
|
||||
|
||||
return len(result)
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return -1
|
||||
|
||||
async def getPlantAgainByName(self, name: str) -> int:
|
||||
"""根据作物名称获取作物再次成熟时间
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
int: 再次成熟时间 单位:h
|
||||
"""
|
||||
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return -1
|
||||
|
||||
phase = row[0].split(",")
|
||||
again = phase[-1] - phase[3] / 60 / 60
|
||||
|
||||
return again
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return -1
|
||||
|
||||
async def existsPlant(self, name: str) -> bool:
|
||||
"""判断作物是否存在
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool: 存在返回True,否则False
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT 1 FROM plant WHERE name = ? LIMIT 1", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return True if row else False
|
||||
except Exception as e:
|
||||
logger.warning(f"检查作物存在性失败: {name}", e=e)
|
||||
return False
|
||||
|
||||
async def countPlants(self, onlyBuy: bool = False) -> int:
|
||||
"""获取作物总数
|
||||
|
||||
Args:
|
||||
onlyBuy (bool): 若为True,仅统计isBuy=1的记录,默认False
|
||||
|
||||
Returns:
|
||||
int: 符合条件的记录数
|
||||
"""
|
||||
try:
|
||||
if onlyBuy:
|
||||
sql = "SELECT COUNT(*) FROM plant WHERE isBuy = 1"
|
||||
params: tuple = ()
|
||||
else:
|
||||
sql = "SELECT COUNT(*) FROM plant"
|
||||
params: tuple = ()
|
||||
async with self.m_pDB.execute(sql, params) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
except Exception as e:
|
||||
logger.warning(f"统计作物数量失败, onlyBuy={onlyBuy}", e=e)
|
||||
return 0
|
||||
|
||||
async def listPlants(self) -> list[dict]:
|
||||
"""查询所有作物记录"""
|
||||
try:
|
||||
async with self.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 []
|
||||
|
||||
async def downloadPlant(self) -> bool:
|
||||
"""遍历所有作物,下载各阶段图片及icon文件到指定文件夹
|
||||
|
||||
Returns:
|
||||
bool: 全部下载完成返回True,如有失败返回False
|
||||
"""
|
||||
success = True
|
||||
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
|
||||
|
||||
baseUrl = baseUrl.rstrip("/") + ":8998/file"
|
||||
try:
|
||||
plants = await self.listPlants()
|
||||
for plant in plants:
|
||||
name = plant["name"]
|
||||
phaseCount = await self.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
|
||||
@@ -0,0 +1,211 @@
|
||||
import math
|
||||
|
||||
from ...utils.tool import g_pToolManager
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserDB(CSqlManager):
|
||||
def __init__(self):
|
||||
self.currencies: list[str] = ["point", "vipPoint"]
|
||||
|
||||
async def initDB(self):
|
||||
userInfo = {
|
||||
"uid": "TEXT PRIMARY KEY", # 用户Uid
|
||||
"name": "TEXT NOT NULL", # 农场名称
|
||||
"exp": "INTEGER DEFAULT 0", # 经验值
|
||||
"point": "INTEGER DEFAULT 0", # 金币
|
||||
"vipPoint": "INTEGER DEFAULT 0", # 点券
|
||||
"soil": "INTEGER DEFAULT 3", # 解锁土地数量
|
||||
"stealTime": "TEXT DEFAULT ''", # 偷菜时间字符串
|
||||
"stealCount": "INTEGER DEFAULT 0", # 剩余偷菜次数
|
||||
}
|
||||
|
||||
await self.ensureTableSchema("user", userInfo)
|
||||
|
||||
async def initUserInfo(self, uid: str, name: str) -> bool:
|
||||
"""初始化用户信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
name (str): 农场名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功初始化用户信息
|
||||
"""
|
||||
nowStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
|
||||
result = await self.insert(
|
||||
"user",
|
||||
data={
|
||||
"uid": uid,
|
||||
"name": name,
|
||||
"exp": 0,
|
||||
"point": 500,
|
||||
"soil": 3,
|
||||
"stealTime": nowStr,
|
||||
"stealCount": 5,
|
||||
},
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def getUserInfoByUid(self, uid: str) -> dict:
|
||||
"""根据用户ID获取用户信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
|
||||
Returns:
|
||||
dict: 用户信息字典,未找到返回空字典
|
||||
"""
|
||||
if uid == "":
|
||||
return {}
|
||||
|
||||
records = await self.select("user", where={"uid": uid})
|
||||
|
||||
return records[0] if records else {}
|
||||
|
||||
async def isRegistered(self, uid: str) -> bool:
|
||||
"""检查用户是否注册农场
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
|
||||
Returns:
|
||||
bool: 是否注册农场
|
||||
"""
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
return await self.exists("user", where={"uid": uid})
|
||||
|
||||
async def updatePoint(self, uid: str, type: str, index: int) -> bool:
|
||||
"""更新货币
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
type (str): 货币类型 point/vipPoint
|
||||
index (int): 更新后的数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功更新货币
|
||||
"""
|
||||
if type not in self.currencies:
|
||||
return False
|
||||
|
||||
if index < 0:
|
||||
index = 0
|
||||
|
||||
await self.update("user", {type: index}, {"uid": uid})
|
||||
|
||||
return True
|
||||
|
||||
async def updateExp(self, uid: str, exp: int) -> bool:
|
||||
"""更新经验值
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
exp (int): 更新后的经验值
|
||||
|
||||
Returns:
|
||||
bool: 是否成功更新经验值
|
||||
"""
|
||||
if exp < 0:
|
||||
exp = 0
|
||||
|
||||
return await self.update("user", {"exp": exp}, {"uid": uid})
|
||||
|
||||
async def updateName(self, uid: str, name: str) -> str:
|
||||
"""更新农场名称
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
name (str): 农场名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功更新农场名称
|
||||
"""
|
||||
safeName = g_pToolManager.sanitize_username(name)
|
||||
|
||||
if safeName == "神秘农夫":
|
||||
return "error"
|
||||
|
||||
if await self.update("user", {"name": safeName}, {"uid": uid}):
|
||||
return "success"
|
||||
|
||||
return "error1"
|
||||
|
||||
async def getUserLevelByUid(self, uid: str) -> tuple[int, int, int]:
|
||||
"""获取用户等级信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
tuple[int, int, int]: 成功返回(当前等级, 升至下级还需经验, 当前等级已获经验)
|
||||
失败返回(-1, -1, -1)
|
||||
"""
|
||||
if not uid:
|
||||
return -1, -1, -1
|
||||
|
||||
records = await self.select("user", where={"uid": uid}, columns=["exp"])
|
||||
|
||||
if not records:
|
||||
return -1, -1, -1
|
||||
|
||||
try:
|
||||
exp = int(records[0].get("exp", 0))
|
||||
except Exception:
|
||||
exp = 0
|
||||
|
||||
levelStep = 200 # 每级经验增量
|
||||
|
||||
discriminant = 1 + 8 * exp / levelStep
|
||||
level = int((-1 + math.sqrt(discriminant)) // 2)
|
||||
if level < 0:
|
||||
level = 0
|
||||
|
||||
def cumExp(k: int) -> int:
|
||||
return levelStep * k * (k + 1) // 2
|
||||
|
||||
totalExpCurrentLevel = cumExp(level)
|
||||
totalExpNextLevel = cumExp(level + 1)
|
||||
|
||||
currentExp = exp - totalExpCurrentLevel
|
||||
|
||||
return level, totalExpNextLevel, currentExp
|
||||
|
||||
async def updateStealCountByUid(
|
||||
self, uid: str, stealTime: str, stealCount: int
|
||||
) -> bool:
|
||||
"""根据用户Uid更新剩余偷菜次数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
stealTime (str): 偷菜日期
|
||||
stealCount (int): 新剩余偷菜次数
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or stealCount < 0:
|
||||
return False
|
||||
|
||||
return await self.update(
|
||||
"user", {"stealTime": stealTime, "stealCount": stealCount}, {"uid": uid}
|
||||
)
|
||||
|
||||
async def updateFieldByUid(self, uid: str, field: str, value) -> bool:
|
||||
"""更新单字段信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
field (str): 字段名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功更新字段
|
||||
"""
|
||||
if not uid or not field:
|
||||
return False
|
||||
|
||||
return await self.update("user", {field: value}, {"uid": uid})
|
||||
@@ -0,0 +1,158 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserItemDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userItem = {
|
||||
"uid": "TEXT NOT NULL", # 用户Uid
|
||||
"item": "TEXT NOT NULL", # 物品名称
|
||||
"count": "INTEGER NOT NULL DEFAULT 0", # 数量
|
||||
"PRIMARY KEY": "(uid, item)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userItem", userItem)
|
||||
|
||||
@classmethod
|
||||
async def getUserItemByName(cls, uid: str, item: str) -> int | None:
|
||||
"""根据道具名称查询某一项数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
|
||||
Returns:
|
||||
Optional[int]: 数量(不存在返回None)
|
||||
"""
|
||||
if not uid or not item:
|
||||
return None
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"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)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def getUserItemByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取全部道具信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
|
||||
Returns:
|
||||
dict: {itemName: count, ...}
|
||||
"""
|
||||
if not uid:
|
||||
return {}
|
||||
try:
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"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)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def deleteUserItemByName(cls, uid: str, item: str) -> bool:
|
||||
"""根据道具名删除道具
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
if not uid or not item:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserItemByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def updateUserItemByName(cls, uid: str, item: str, count: int) -> bool:
|
||||
"""根据道具名直接更新道具数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
count (int): 要更新的新数量
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or not item:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
if count <= 0:
|
||||
await cls.m_pDB.execute(
|
||||
"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),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserItemByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def addUserItemByUid(cls, uid: str, item: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加道具信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
count (int, optional): 数量.Defaults to 1.
|
||||
|
||||
Returns:
|
||||
bool: 是否添加成功
|
||||
"""
|
||||
if not uid or not item:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row:
|
||||
newCount = row[0] + count
|
||||
if newCount <= 0:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userItem WHERE uid = ? AND item = ?",
|
||||
(uid, item),
|
||||
)
|
||||
else:
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
|
||||
(newCount, uid, item),
|
||||
)
|
||||
else:
|
||||
if count > 0:
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)",
|
||||
(uid, item, count),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("addUserItemByUid失败!", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,205 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserPlantDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userPlant = {
|
||||
"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)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userPlant", userPlant)
|
||||
|
||||
@classmethod
|
||||
async def addUserPlantByUid(cls, uid: str, plant: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加作物信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
count (int): 数量
|
||||
|
||||
Returns:
|
||||
bool: 是否添加成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
# 检查是否已存在该作物
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
|
||||
(uid, plant),
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row:
|
||||
# 如果作物已存在,则更新数量
|
||||
new_count = row[0] + count
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
|
||||
(new_count, uid, plant),
|
||||
)
|
||||
else:
|
||||
# 如果作物不存在,则插入新记录
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userPlant (uid, plant, count) VALUES (?, ?, ?)",
|
||||
(uid, plant, count),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("addUserPlantByUid 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserPlantByUid(cls, uid: str) -> dict[str, int]:
|
||||
"""根据用户uid获取全部作物信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
|
||||
Returns:
|
||||
Dict[str, int]: 作物名称和数量
|
||||
"""
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"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:
|
||||
"""根据作物名称获取用户的作物数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
Optional[int]: 作物数量
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"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)
|
||||
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 表中某个作物的数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
count (int): 新的作物数量
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
try:
|
||||
if count <= 0:
|
||||
return await cls.deleteUserPlantByName(uid, plant)
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userPlant SET count = ? WHERE uid = ? AND 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)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteUserPlantByName(cls, uid: str, plant: str) -> bool:
|
||||
"""从 userPlant 表中删除某个作物记录
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserPlantByName 失败!", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,212 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserSeedDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userSeed = {
|
||||
"uid": "TEXT NOT NULL", # 用户Uid
|
||||
"seed": "TEXT NOT NULL", # 种子名称
|
||||
"count": "INTEGER NOT NULL DEFAULT 0", # 数量
|
||||
"PRIMARY KEY": "(uid, seed)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userSeed", userSeed)
|
||||
|
||||
@classmethod
|
||||
async def addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加种子信息(事务版本)
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
count (int): 数量
|
||||
|
||||
Returns:
|
||||
bool: 是否添加成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row:
|
||||
newCount = row[0] + count
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
|
||||
(newCount, uid, seed),
|
||||
)
|
||||
else:
|
||||
newCount = count
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
|
||||
(uid, seed, count),
|
||||
)
|
||||
|
||||
if newCount <= 0:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("addUserSeedByUid 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def _addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加种子信息(非事务版,复用其他非事务接口)"""
|
||||
try:
|
||||
existing = await cls.getUserSeedByName(uid, seed)
|
||||
newCount = (existing or 0) + count
|
||||
|
||||
if existing is not None:
|
||||
await cls._updateUserSeedByName(uid, seed, newCount)
|
||||
else:
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
|
||||
(uid, seed, newCount),
|
||||
)
|
||||
|
||||
if newCount <= 0:
|
||||
await cls._deleteUserSeedByName(uid, seed)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("_addUserSeedByUid 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByName(cls, uid: str, seed: str) -> int | None:
|
||||
"""根据种子名称获取种子数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
Optional[int]: 种子数量
|
||||
"""
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"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)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取仓库全部种子信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
|
||||
Returns:
|
||||
dict: 种子信息
|
||||
"""
|
||||
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT seed, count FROM userSeed WHERE uid=?", (uid,)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return {row["seed"]: row["count"] for row in rows}
|
||||
|
||||
@classmethod
|
||||
async def updateUserSeedByName(cls, uid: str, seed: str, count: int) -> bool:
|
||||
"""根据种子名称更新种子数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
count (int): 种子数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
if count <= 0:
|
||||
return await cls.deleteUserSeedByName(uid, seed)
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
|
||||
(count, uid, seed),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserSeedByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def _updateUserSeedByName(cls, uid: str, seed: str, count: int) -> bool:
|
||||
"""根据种子名称更新种子数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
count (int): 种子数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
if count <= 0:
|
||||
return await cls.deleteUserSeedByName(uid, seed)
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
|
||||
(count, uid, seed),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserSeedByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteUserSeedByName(cls, uid: str, seed: str) -> bool:
|
||||
"""根据种子名称从种子仓库中删除种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserSeedByName 删除失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def _deleteUserSeedByName(cls, uid: str, seed: str) -> bool:
|
||||
"""根据种子名称从种子仓库中删除种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserSeedByName 删除失败!", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,288 @@
|
||||
import calendar
|
||||
from datetime import timedelta
|
||||
import random
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
|
||||
from ...utils.config import g_bIsDebug
|
||||
from ...utils.json import g_pJsonManager
|
||||
from ...utils.tool import g_pToolManager
|
||||
from ..dbService import g_pDBService
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserSignDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
# userSignLog 表结构,每条为一次签到事件
|
||||
userSignLog = {
|
||||
"uid": "TEXT NOT NULL", # 用户ID
|
||||
"signDate": "DATE NOT NULL", # 签到日期
|
||||
"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)",
|
||||
}
|
||||
|
||||
# userSignSummary 表结构,每用户一行用于缓存签到状态
|
||||
userSignSummary = {
|
||||
"uid": "TEXT PRIMARY KEY NOT NULL", # 用户ID
|
||||
"totalSignDays": "INT NOT NULL DEFAULT 0", # 累计签到天数
|
||||
"currentMonth": "CHAR(7) NOT NULL DEFAULT ''", # 当前月份(如2025-05)
|
||||
"monthSignDays": "INT NOT NULL DEFAULT 0", # 本月签到次数
|
||||
"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
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userSignLog", userSignLog)
|
||||
await cls.ensureTableSchema("userSignSummary", userSignSummary)
|
||||
|
||||
@classmethod
|
||||
async def getUserSignRewardByDate(cls, uid: str, date: str) -> tuple[int, int]:
|
||||
"""根据指定日期获取用户签到随机奖励
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
date (str): 用户签到日期 示例:2025-05-27
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: 经验、金币
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT exp, point FROM userSignLog WHERE uid=? AND signDate=?",
|
||||
(uid, date),
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row is None:
|
||||
return 0, 0
|
||||
|
||||
exp = row["exp"]
|
||||
point = row["point"]
|
||||
|
||||
return exp, point
|
||||
except Exception as e:
|
||||
logger.warning("获取用户签到数据失败", e=e)
|
||||
return 0, 0
|
||||
|
||||
@classmethod
|
||||
async def getUserSignCountByDate(cls, uid: str, monthStr: str) -> int:
|
||||
"""根据日期查询用户签到总天数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
monthStr (str): 需要查询的日期 示例: 2025-05
|
||||
|
||||
Returns:
|
||||
int: 查询月总签到天数
|
||||
"""
|
||||
try:
|
||||
sql = "SELECT COUNT(*) FROM userSignLog WHERE uid=? AND signDate LIKE ?"
|
||||
param = f"{monthStr}-%"
|
||||
async with cls.m_pDB.execute(sql, (uid, param)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
except Exception as e:
|
||||
logger.warning("统计用户月签到次数失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def hasSigned(cls, uid: str, signDate: str) -> bool:
|
||||
"""判断指定日期是否已签到
|
||||
|
||||
Args:
|
||||
uid (int): 用户ID
|
||||
signDate (str): 日期字符串 'YYYY-MM-DD'
|
||||
|
||||
Returns:
|
||||
bool: True=已签到,False=未签到
|
||||
"""
|
||||
try:
|
||||
sql = "SELECT 1 FROM userSignLog WHERE uid=? AND signDate=? LIMIT 1"
|
||||
async with cls.m_pDB.execute(sql, (uid, signDate)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row is not None
|
||||
except Exception as e:
|
||||
logger.warning("查询是否已签到失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def sign(cls, uid: str, signDate: str = "") -> int:
|
||||
"""签到
|
||||
|
||||
Args:
|
||||
uid (int): 用户ID
|
||||
signDate (str): 日期字符串 'YYYY-MM-DD' 不传默认当前系统日期
|
||||
|
||||
Returns:
|
||||
bool: 0: 签到失败 1: 签到成功 2: 重复签到
|
||||
"""
|
||||
try:
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return 0
|
||||
|
||||
if not signDate:
|
||||
signDate = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
|
||||
if await cls.hasSigned(uid, signDate):
|
||||
return 2
|
||||
|
||||
todayStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
isSupplement = 0 if signDate == todayStr else 1
|
||||
|
||||
expMax, expMin, pointMax, pointMin = [
|
||||
g_pJsonManager.m_pSign.get(key, default)
|
||||
for key, default in (
|
||||
("exp_max", 50),
|
||||
("exp_min", 5),
|
||||
("point_max", 2000),
|
||||
("point_min", 200),
|
||||
)
|
||||
]
|
||||
|
||||
exp = random.randint(expMin, expMax)
|
||||
point = random.randint(pointMin, pointMax)
|
||||
vipPoint = 0
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userSignLog (uid, signDate, isSupplement, exp, point) VALUES (?, ?, ?, ?, ?)",
|
||||
(uid, signDate, isSupplement, exp, point),
|
||||
)
|
||||
|
||||
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"]
|
||||
)
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
UPDATE userSignSummary
|
||||
SET totalSignDays=totalSignDays+1,
|
||||
currentMonth=?,
|
||||
monthSignDays=?,
|
||||
lastSignDate=?,
|
||||
continuousDays=?,
|
||||
supplementCount=?
|
||||
WHERE uid=?
|
||||
""",
|
||||
(
|
||||
currentMonth,
|
||||
monthSignDays,
|
||||
signDate,
|
||||
continuousDays,
|
||||
supplementCount,
|
||||
uid,
|
||||
),
|
||||
)
|
||||
else:
|
||||
monthSignDays = 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,
|
||||
),
|
||||
)
|
||||
|
||||
# 计算累签奖励
|
||||
reward = g_pJsonManager.m_pSign["continuou"].get(f"{monthSignDays}", None)
|
||||
if reward:
|
||||
point += reward.get("point", 0)
|
||||
exp += reward.get("exp", 0)
|
||||
vipPoint = reward.get("vipPoint", 0)
|
||||
|
||||
plant = reward.get("plant", {})
|
||||
if plant:
|
||||
for key, value in plant.items():
|
||||
await g_pDBService.userSeed.addUserSeedByUid(uid, key, value)
|
||||
|
||||
if g_bIsDebug:
|
||||
exp += 9999
|
||||
|
||||
# 向数据库更新
|
||||
await player.addExp(exp)
|
||||
await player.addPoint("point", point)
|
||||
|
||||
if vipPoint > 0:
|
||||
await player.addPoint("vipPoint", vipPoint)
|
||||
|
||||
return 1
|
||||
except Exception as e:
|
||||
logger.warning("执行签到失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def drawSignCalendarImage(cls, uid: str, year: int, month: int):
|
||||
# 绘制签到图,自动提取数据库中该用户该月的签到天数
|
||||
cellSize = 80
|
||||
padding = 40
|
||||
titleHeight = 80
|
||||
cols = 7
|
||||
rows = 6
|
||||
width = cellSize * cols + padding * 2
|
||||
height = cellSize * rows + padding * 2 + titleHeight
|
||||
|
||||
img = BuildImage(width, height, color=(255, 255, 255))
|
||||
await img.text((padding, 20), f"{year}年{month}月签到表", font_size=36)
|
||||
|
||||
firstWeekday, totalDays = calendar.monthrange(year, month)
|
||||
monthStr = f"{year:04d}-{month:02d}"
|
||||
try:
|
||||
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()}
|
||||
except Exception as e:
|
||||
logger.warning("绘制签到图时数据库查询失败", e=e)
|
||||
signedDays = set()
|
||||
|
||||
for day in range(1, totalDays + 1):
|
||||
index = day + firstWeekday - 1
|
||||
row = index // cols
|
||||
col = index % cols
|
||||
x1 = padding + col * cellSize
|
||||
y1 = padding + titleHeight + row * cellSize
|
||||
x2 = x1 + cellSize - 10
|
||||
y2 = y1 + cellSize - 10
|
||||
color = (112, 196, 112) if day in signedDays else (220, 220, 220)
|
||||
await img.rectangle((x1, y1, x2, y2), fill=color, outline="black", width=2)
|
||||
await img.text((x1 + 10, y1 + 10), str(day), font_size=24)
|
||||
|
||||
return img
|
||||
@@ -0,0 +1,541 @@
|
||||
import math
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ...utils.config import g_bIsDebug
|
||||
from ...utils.tool import g_pToolManager
|
||||
from ..dbService import g_pDBService
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserSoilDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userSoil = {
|
||||
"uid": "TEXT NOT NULL",
|
||||
"soilIndex": "INTEGER NOT NULL", # 地块索引从1开始
|
||||
"plantName": "TEXT DEFAULT ''", # 作物名称
|
||||
"plantTime": "INTEGER DEFAULT 0", # 播种时间
|
||||
"matureTime": "INTEGER DEFAULT 0", # 成熟时间
|
||||
"soilLevel": "INTEGER DEFAULT 0", # 土地等级 0=普通地,1=红土地,2=黑土地,3=金土地
|
||||
"wiltStatus": "INTEGER DEFAULT 0", # 枯萎状态 0=未枯萎,1=枯萎
|
||||
"fertilizerStatus": "INTEGER DEFAULT 0", # 施肥状态 0=未施肥,1=施肥 2=增肥
|
||||
"bugStatus": "INTEGER DEFAULT 0", # 虫害状态 0=无虫害,1=有虫害
|
||||
"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)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userSoil", userSoil)
|
||||
|
||||
@classmethod
|
||||
async def nextPhase(cls, uid: str, soilIndex: int):
|
||||
"""将指定地块的作物进入下个阶段
|
||||
|
||||
Args:
|
||||
soilIndex (int): 地块索引 从1开始
|
||||
"""
|
||||
if not g_bIsDebug:
|
||||
return
|
||||
|
||||
soilInfo = await cls.getUserSoil(uid, soilIndex)
|
||||
|
||||
if not soilInfo:
|
||||
return
|
||||
|
||||
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"])
|
||||
|
||||
if currentTime >= soilInfo["matureTime"]:
|
||||
return
|
||||
|
||||
elapsedTime = currentTime - soilInfo["plantTime"]
|
||||
currentStage = currentStage = sum(1 for thr in phaseList if elapsedTime >= thr)
|
||||
|
||||
t = int(soilInfo["plantTime"]) - phaseList[currentStage]
|
||||
s = int(soilInfo["matureTime"]) - phaseList[currentStage]
|
||||
|
||||
await cls.updateUserSoilFields(
|
||||
uid, soilIndex, {"plantTime": t, "matureTime": s}
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def matureNow(cls, uid: str, soilIndex: int):
|
||||
"""将指定地块的作物直接成熟
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 地块索引(从1开始)
|
||||
"""
|
||||
# 与 nextPhase 不同:无需调试模式检查,允许在任何模式下调用
|
||||
soilInfo = await cls.getUserSoil(uid, soilIndex)
|
||||
if not soilInfo:
|
||||
return
|
||||
|
||||
plantName = soilInfo.get("plantName")
|
||||
if not plantName:
|
||||
return
|
||||
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(plantName)
|
||||
if not plantInfo:
|
||||
return
|
||||
|
||||
currentTime = int(g_pToolManager.dateTime().now().timestamp())
|
||||
# 如果当前时间已经超过或等于成熟时间,则作物已成熟或可收获
|
||||
if currentTime >= soilInfo["matureTime"]:
|
||||
return
|
||||
|
||||
# 将作物成熟时间直接更新为当前时间,实现立即成熟
|
||||
await cls.updateUserSoilFields(uid, soilIndex, {"matureTime": currentTime})
|
||||
|
||||
@classmethod
|
||||
async def getUserFarmByUid(cls, uid: str) -> dict:
|
||||
"""获取指定用户的旧农场数据
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
|
||||
Returns:
|
||||
dict: 包含字段名-值的字典; 若无数据则返回空字典
|
||||
"""
|
||||
cursor = await cls.m_pDB.execute("SELECT * FROM soil WHERE uid = ?", (uid,))
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return {}
|
||||
columns = [description[0] for description in cursor.description]
|
||||
return dict(zip(columns, row))
|
||||
|
||||
@classmethod
|
||||
async def insertUserSoil(cls, soilInfo: dict):
|
||||
"""插入一条新的 userSoil 记录
|
||||
|
||||
Args:
|
||||
soilInfo (dict): 新土地数据
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
INSERT INTO userSoil
|
||||
(uid, soilIndex, plantName, plantTime, matureTime,
|
||||
soilLevel, wiltStatus, fertilizerStatus, bugStatus,
|
||||
weedStatus, waterStatus, harvestCount, isSoilPlanted)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
soilInfo["uid"],
|
||||
soilInfo["soilIndex"],
|
||||
soilInfo.get("plantName", ""),
|
||||
soilInfo.get("plantTime", 0),
|
||||
soilInfo.get("matureTime", 0),
|
||||
soilInfo.get("soilLevel", 0),
|
||||
soilInfo.get("wiltStatus", 0),
|
||||
soilInfo.get("fertilizerStatus", 0),
|
||||
soilInfo.get("bugStatus", 0),
|
||||
soilInfo.get("weedStatus", 0),
|
||||
soilInfo.get("waterStatus", 0),
|
||||
soilInfo.get("harvestCount", 0),
|
||||
soilInfo.get("isSoilPlanted", 0),
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _insertUserSoil(cls, soilInfo: dict):
|
||||
"""插入一条新的 userSoil 记录
|
||||
|
||||
Args:
|
||||
soilInfo (dict): 新土地数据
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
INSERT INTO userSoil
|
||||
(uid, soilIndex, plantName, plantTime, matureTime,
|
||||
soilLevel, wiltStatus, fertilizerStatus, bugStatus,
|
||||
weedStatus, waterStatus, harvestCount, isSoilPlanted)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
soilInfo["uid"],
|
||||
soilInfo["soilIndex"],
|
||||
soilInfo.get("plantName", ""),
|
||||
soilInfo.get("plantTime", 0),
|
||||
soilInfo.get("matureTime", 0),
|
||||
soilInfo.get("soilLevel", 0),
|
||||
soilInfo.get("wiltStatus", 0),
|
||||
soilInfo.get("fertilizerStatus", 0),
|
||||
soilInfo.get("bugStatus", 0),
|
||||
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:
|
||||
"""获取指定用户某块土地的详细信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
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),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
columns = [description[0] for description in cursor.description]
|
||||
return dict(zip(columns, row))
|
||||
|
||||
@classmethod
|
||||
async def countSoilByLevel(cls, uid: str, soilLevel: int) -> int:
|
||||
"""统计指定用户在指定土地等级的土地数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilLevel (int): 土地等级
|
||||
|
||||
Returns:
|
||||
int: 符合条件的土地数量
|
||||
"""
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT COUNT(*) FROM userSoil WHERE uid = ? AND soilLevel = ?",
|
||||
(uid, soilLevel),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
|
||||
@classmethod
|
||||
async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value):
|
||||
"""更新指定用户土地的单个字段
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
field (str): 需更新的字段名
|
||||
value: 新值
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
f"UPDATE userSoil SET {field} = ? WHERE uid = ? AND soilIndex = ?",
|
||||
(value, uid, soilIndex),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _updateUserSoil(cls, uid: str, soilIndex: int, field: str, value):
|
||||
"""更新指定用户土地的单个字段
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
field (str): 需更新的字段名
|
||||
value: 新值
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
await cls.m_pDB.execute(
|
||||
f"UPDATE userSoil SET {field} = ? WHERE uid = ? AND soilIndex = ?",
|
||||
(value, uid, soilIndex),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def updateUserSoilFields(
|
||||
cls, uid: str, soilIndex: int, updates: dict
|
||||
) -> bool:
|
||||
"""批量更新指定用户土地的多个字段
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
updates (dict): 字段-新值的字典
|
||||
|
||||
Returns:
|
||||
bool: 如果无可更新字段则返回 False,否则更新成功返回 True
|
||||
"""
|
||||
# 允许更新的列白名单
|
||||
allowedFields = {
|
||||
"plantName",
|
||||
"plantTime",
|
||||
"matureTime",
|
||||
"soilLevel",
|
||||
"wiltStatus",
|
||||
"fertilizerStatus",
|
||||
"bugStatus",
|
||||
"weedStatus",
|
||||
"waterStatus",
|
||||
"harvestCount",
|
||||
"isSoilPlanted",
|
||||
}
|
||||
setClauses = []
|
||||
values = []
|
||||
for field, value in updates.items():
|
||||
if field not in allowedFields:
|
||||
continue
|
||||
setClauses.append(f'"{field}" = ?')
|
||||
values.append(value)
|
||||
if not setClauses:
|
||||
return False
|
||||
|
||||
values.extend([uid, soilIndex])
|
||||
sql = f"UPDATE userSoil SET {', '.join(setClauses)} WHERE uid = ? AND soilIndex = ?"
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(sql, tuple(values))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"批量更新土地字段失败: {e}")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteUserSoil(cls, uid: str, soilIndex: int):
|
||||
"""删除指定用户的土地记录
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _deleteUserSoil(cls, uid: str, soilIndex: int):
|
||||
"""删除指定用户的土地记录
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def sowingByPlantName(cls, uid: str, soilIndex: int, plantName: str) -> bool:
|
||||
"""播种指定作物到用户土地区
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地区索引
|
||||
plantName (str): 植物名
|
||||
|
||||
Returns:
|
||||
bool: 播种成功返回 True,否则返回 False
|
||||
"""
|
||||
# 校验土地区是否已种植
|
||||
soilInfo = await cls.getUserSoil(uid, soilIndex)
|
||||
if soilInfo and soilInfo.get("plantName"):
|
||||
return False
|
||||
|
||||
# 获取植物配置
|
||||
plantCfg = await g_pDBService.plant.getPlantByName(plantName)
|
||||
if not plantCfg:
|
||||
logger.error(f"未知植物: {plantName}")
|
||||
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
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
prev = soilInfo or {}
|
||||
await cls._deleteUserSoil(uid, soilIndex)
|
||||
await cls._insertUserSoil(
|
||||
{
|
||||
"uid": uid,
|
||||
"soilIndex": soilIndex,
|
||||
"plantName": plantName,
|
||||
"plantTime": nowTs,
|
||||
"matureTime": matureTs,
|
||||
"soilLevel": prev.get("soilLevel", 0),
|
||||
"wiltStatus": 0,
|
||||
"fertilizerStatus": 0,
|
||||
"bugStatus": 0,
|
||||
"weedStatus": 0,
|
||||
"waterStatus": 0,
|
||||
"harvestCount": 0,
|
||||
"isSoilPlanted": 1,
|
||||
}
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("播种失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserSoilStatus(cls, uid: str, soilIndex: int) -> str:
|
||||
status = []
|
||||
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
|
||||
|
||||
if not soilInfo:
|
||||
return ""
|
||||
|
||||
if soilInfo.get("wiltStatus", 0) == 1:
|
||||
return "枯萎"
|
||||
|
||||
if soilInfo.get("fertilizerStatus", 0) == 1:
|
||||
status.append("施肥")
|
||||
elif soilInfo.get("fertilizerStatus", 0) == 2:
|
||||
status.append("增肥")
|
||||
|
||||
if soilInfo.get("bugStatus", 0) == 1:
|
||||
status.append("虫害")
|
||||
|
||||
if soilInfo.get("weedStatus", 0) == 1:
|
||||
status.append("杂草")
|
||||
|
||||
if soilInfo.get("waterStatus", 0) == 1:
|
||||
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
|
||||
@@ -0,0 +1,222 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserStealDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userSteal = {
|
||||
"uid": "TEXT NOT NULL", # 被偷用户Uid
|
||||
"soilIndex": "INTEGER NOT NULL", # 被偷的地块索引 从1开始
|
||||
"stealerUid": "TEXT NOT NULL", # 偷菜用户Uid
|
||||
"stealCount": "INTEGER NOT NULL", # 被偷数量
|
||||
"stealTime": "INTEGER NOT NULL", # 被偷时间
|
||||
"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:
|
||||
"""添加偷菜记录
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
stealerUid (str): 偷菜用户Uid
|
||||
stealCount (int): 被偷数量
|
||||
stealTime (int): 被偷时间(时间戳)
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
'INSERT INTO "userSteal"(uid, soilIndex, stealerUid, stealCount, stealTime) VALUES(?, ?, ?, ?, ?);',
|
||||
(uid, soilIndex, stealerUid, stealCount, stealTime),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("添加偷菜记录失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getStealRecordsByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取所有偷菜记录
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
|
||||
Returns:
|
||||
list: 偷菜记录字典列表,每条包含 soilIndex, stealerUid, stealCount, stealTime
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;',
|
||||
(uid,),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return {}
|
||||
|
||||
result = dict(row)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning("获取偷菜记录失败", e=e)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def getStealRecord(cls, uid: str, soilIndex: int) -> list:
|
||||
"""获取指定地块的所有偷菜记录
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
list: 偷菜记录字典列表,每条包含 stealerUid, stealCount, stealTime
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return [
|
||||
{
|
||||
"uid": uid,
|
||||
"soilIndex": soilIndex,
|
||||
"stealerUid": row[0],
|
||||
"stealCount": row[1],
|
||||
"stealTime": row[2],
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
except Exception as e:
|
||||
logger.warning("获取单地块偷菜记录失败", e=e)
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def getTotalStolenCount(cls, uid: str, soilIndex: int) -> int:
|
||||
"""计算指定地块被偷的总数量(所有用户偷取数量之和)
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
int: 被偷的总数量,如果无记录则返回 0
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT SUM(stealCount) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row[0] or 0 # type: ignore
|
||||
except Exception as e:
|
||||
logger.warning("计算总偷菜数量失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def getStealerCount(cls, uid: str, soilIndex: int) -> int:
|
||||
"""计算指定地块被多少人偷过(不同偷菜用户数量)
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
int: 偷菜者总数,如果无记录则返回 0
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT COUNT(DISTINCT stealerUid) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row[0] or 0 # type: ignore
|
||||
except Exception as e:
|
||||
logger.warning("计算偷菜者数量失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def hasStealed(cls, uid: str, soilIndex: int, stealerUid: str) -> bool:
|
||||
"""判断指定用户是否曾偷取过该地块
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
stealerUid (str): 偷菜用户Uid
|
||||
|
||||
Returns:
|
||||
bool: 若存在记录返回 True,否则返回 False
|
||||
"""
|
||||
try:
|
||||
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),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return bool(row)
|
||||
except Exception as e:
|
||||
logger.warning("检查偷菜记录失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def updateStealRecord(
|
||||
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
|
||||
) -> bool:
|
||||
"""更新偷菜记录的数量和时间
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
stealerUid (str): 偷菜用户Uid
|
||||
stealCount (int): 新的偷菜数量
|
||||
stealTime (int): 新的偷菜时间(时间戳)
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
"""
|
||||
try:
|
||||
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),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("更新偷菜记录失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteStealRecord(cls, uid: str, soilIndex: int) -> bool:
|
||||
"""删除指定偷菜记录(只需被偷用户Uid和地块索引)
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
bool: 删除是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
'DELETE FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("删除偷菜记录失败", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,40 @@
|
||||
class CDBService:
|
||||
async def init(self):
|
||||
from .database.plant import CPlantManager
|
||||
from .database.user import CUserDB
|
||||
from .database.userItem import CUserItemDB
|
||||
from .database.userPlant import CUserPlantDB
|
||||
from .database.userSeed import CUserSeedDB
|
||||
from .database.userSign import CUserSignDB
|
||||
from .database.userSoil import CUserSoilDB
|
||||
from .database.userSteal import CUserStealDB
|
||||
|
||||
self.plant = CPlantManager()
|
||||
await self.plant.init()
|
||||
|
||||
self.user = CUserDB()
|
||||
await self.user.initDB()
|
||||
|
||||
self.userSoil = CUserSoilDB()
|
||||
await self.userSoil.initDB()
|
||||
|
||||
self.userPlant = CUserPlantDB()
|
||||
await self.userPlant.initDB()
|
||||
|
||||
self.userSeed = CUserSeedDB()
|
||||
await self.userSeed.initDB()
|
||||
|
||||
self.userItem = CUserItemDB()
|
||||
await self.userItem.initDB()
|
||||
|
||||
self.userSteal = CUserStealDB()
|
||||
await self.userSteal.initDB()
|
||||
|
||||
self.userSign = CUserSignDB()
|
||||
await self.userSign.initDB()
|
||||
|
||||
async def cleanup(self):
|
||||
await self.plant.cleanup()
|
||||
|
||||
|
||||
g_pDBService = CDBService()
|
||||
@@ -0,0 +1,123 @@
|
||||
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 ..utils.config import g_sResourcePath
|
||||
|
||||
|
||||
class CHelpManager:
|
||||
@classmethod
|
||||
def renderHtmlToFile(
|
||||
cls, 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")
|
||||
|
||||
@classmethod
|
||||
async def screenshotHtmlToBytes(cls, 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(
|
||||
viewport={"width": 1200, "height": 900}, device_scale_factor=1
|
||||
)
|
||||
file_url = Path(path).resolve().as_uri()
|
||||
await page.goto(file_url, wait_until="networkidle")
|
||||
await page.evaluate("""() => {
|
||||
return new Promise(r => setTimeout(r, 200));
|
||||
}""")
|
||||
image_bytes = await page.screenshot(full_page=True)
|
||||
await browser.close()
|
||||
|
||||
return image_bytes
|
||||
|
||||
@classmethod
|
||||
async def screenshotSave(
|
||||
cls, path: str, save: str, width: int, height: int
|
||||
) -> None:
|
||||
"""
|
||||
使用 Playwright 渲染本地 HTML 并将截图保存到指定路径
|
||||
|
||||
Args:
|
||||
path (str): HTML 文件路径
|
||||
save (str): PNG 保存路径(如 output/image.png)
|
||||
width (int): 图片宽度
|
||||
height (int): 图片高度
|
||||
"""
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch()
|
||||
page = await browser.new_page(
|
||||
viewport={"width": width, "height": height}, device_scale_factor=1
|
||||
)
|
||||
|
||||
file_url = Path(path).resolve().as_uri()
|
||||
await page.goto(file_url, wait_until="networkidle")
|
||||
await page.evaluate("""() => {
|
||||
return new Promise(r => setTimeout(r, 200));
|
||||
}""")
|
||||
|
||||
# 确保保存目录存在
|
||||
Path(save).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 截图并保存到本地文件
|
||||
await page.screenshot(path=save, full_page=True)
|
||||
await browser.close()
|
||||
|
||||
@classmethod
|
||||
async def createHelpImage(cls) -> bool:
|
||||
templatePath = g_sResourcePath / "html/help.html"
|
||||
outputPath = g_sResourcePath / "temp_html/help.html"
|
||||
savePath = DATA_PATH / "farm_res/html/help.png"
|
||||
|
||||
context = {
|
||||
"main_title": "真寻农场帮助菜单",
|
||||
"subtitle": "[]中为可选参数",
|
||||
"page_title": "真寻农场帮助菜单",
|
||||
"font_family": "MyFont",
|
||||
"contents": [
|
||||
{"title": "主要指令", "commands": ["指令A", "指令B"]},
|
||||
{"title": "B", "commands": ["指令D", "指令E", "指令M", "指令i"]},
|
||||
],
|
||||
}
|
||||
|
||||
try:
|
||||
cls.renderHtmlToFile(templatePath, context, outputPath)
|
||||
|
||||
bytes = await cls.screenshotSave(str(outputPath), str(savePath), 1500, 2300)
|
||||
except Exception as e:
|
||||
logger.warning("绘制农场帮助菜单失败", e=e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
g_pHelpManager = CHelpManager()
|
||||
@@ -0,0 +1,219 @@
|
||||
from ..dbService import g_pDBService
|
||||
|
||||
|
||||
class CPlayer:
|
||||
def __init__(self):
|
||||
self.user = {
|
||||
"uid": "", # 用户Uid
|
||||
"name": "", # 农场名称
|
||||
"exp": 0, # 经验值
|
||||
"point": 0, # 金币
|
||||
"vipPoint": 0, # 点券
|
||||
"soil": 3, # 解锁土地数量
|
||||
"stealTime": "", # 偷菜时间字符串
|
||||
"stealCount": 0, # 剩余偷菜次数
|
||||
}
|
||||
|
||||
async def init(self, uid: str) -> bool:
|
||||
self.user["uid"] = uid
|
||||
return await self.loadFormDB()
|
||||
|
||||
async def loadFormDB(self) -> bool:
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
self.user = await g_pDBService.user.getUserInfoByUid(uid)
|
||||
|
||||
return True
|
||||
|
||||
async def isRegistered(self) -> bool:
|
||||
"""检查用户是否注册农场
|
||||
|
||||
Returns:
|
||||
bool: 是否注册农场
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
return await g_pDBService.user.isRegistered(uid)
|
||||
|
||||
async def addPoint(self, type: str, index: int) -> bool:
|
||||
"""增加货币
|
||||
|
||||
Args:
|
||||
type (str): 货币类型 point/vipPoint
|
||||
index (int): 增加的数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功增加货币
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "" or type not in g_pDBService.user.currencies:
|
||||
return False
|
||||
|
||||
if index == 0:
|
||||
return True
|
||||
|
||||
nowIndex = self.user.get(type, 0) + index
|
||||
|
||||
if nowIndex < 0:
|
||||
nowIndex = 0
|
||||
|
||||
if await g_pDBService.user.updatePoint(uid, type, nowIndex):
|
||||
self.user[type] = nowIndex
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def subPoint(self, type: str, index: int) -> bool:
|
||||
"""减少货币
|
||||
|
||||
Args:
|
||||
type (str): 货币类型 point/vipPoint
|
||||
index (int): 减少的数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功减少货币
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "" or type not in g_pDBService.user.currencies:
|
||||
return False
|
||||
|
||||
if index == 0:
|
||||
return True
|
||||
|
||||
nowIndex = self.user.get(type, 0) - index
|
||||
|
||||
if nowIndex < 0:
|
||||
nowIndex = 0
|
||||
|
||||
if await g_pDBService.user.updatePoint(uid, type, nowIndex):
|
||||
self.user[type] = nowIndex
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def addExp(self, exp: int) -> bool:
|
||||
"""增加经验值
|
||||
|
||||
Args:
|
||||
exp (int): 增加的经验值
|
||||
|
||||
Returns:
|
||||
bool: 是否成功增加经验值
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
if exp == 0:
|
||||
return True
|
||||
|
||||
nowExp = self.user.get("exp", 0) + exp
|
||||
|
||||
if nowExp < 0:
|
||||
nowExp = 0
|
||||
|
||||
if await g_pDBService.user.updateExp(uid, nowExp):
|
||||
self.user["exp"] = nowExp
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def subExp(self, exp: int) -> bool:
|
||||
"""减少经验值
|
||||
|
||||
Args:
|
||||
exp (int): 减少的经验值
|
||||
|
||||
Returns:
|
||||
bool: 是否成功减少经验值
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
if exp == 0:
|
||||
return True
|
||||
|
||||
nowExp = self.user.get("exp", 0) - exp
|
||||
|
||||
if nowExp < 0:
|
||||
nowExp = 0
|
||||
|
||||
if await g_pDBService.user.updateExp(uid, nowExp):
|
||||
self.user["exp"] = nowExp
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def updateName(self, name: str) -> str:
|
||||
"""更新农场名称
|
||||
|
||||
Args:
|
||||
name (str): 农场名称
|
||||
|
||||
Returns:
|
||||
str: success/error/error1
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return "error"
|
||||
|
||||
return await g_pDBService.user.updateName(uid, name)
|
||||
|
||||
async def getUserLevel(self) -> tuple[int, int, int]:
|
||||
"""获取用户等级信息
|
||||
|
||||
Returns:
|
||||
tuple[int, int, int]: 成功返回(当前等级, 升至下级还需经验, 当前等级已获经验)
|
||||
失败返回(-1, -1, -1)
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return -1, -1, -1
|
||||
|
||||
return await g_pDBService.user.getUserLevelByUid(uid)
|
||||
|
||||
async def updateStealCountByUid(
|
||||
self, uid: str, stealTime: str, stealCount: int
|
||||
) -> bool:
|
||||
"""根据用户Uid更新剩余偷菜次数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
stealTime (str): 偷菜日期
|
||||
stealCount (int): 新剩余偷菜次数
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
return await g_pDBService.user.updateStealCountByUid(uid, stealTime, stealCount)
|
||||
|
||||
async def updateField(self, field: str, value) -> bool:
|
||||
"""更新单字段信息
|
||||
|
||||
Returns:
|
||||
bool: 是否成功更新单字段信息
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
return await g_pDBService.user.updateFieldByUid(uid, field, value)
|
||||
@@ -0,0 +1,216 @@
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class CPlayerPool:
|
||||
"""
|
||||
用户池管理类
|
||||
管理用户对象的生命周期,支持自动清理超时用户
|
||||
"""
|
||||
|
||||
def __init__(self, timeoutSeconds: int = 300, cleanupInterval: int = 3600):
|
||||
"""
|
||||
初始化用户池
|
||||
|
||||
Args:
|
||||
timeoutSeconds: 用户超时时间(秒),默认5分钟
|
||||
cleanupInterval: 清理间隔(秒),默认1小时
|
||||
"""
|
||||
self._players: dict[str, dict[str, Any]] = {}
|
||||
self._lock = threading.RLock()
|
||||
self.timeoutSeconds = timeoutSeconds
|
||||
self.cleanupInterval = cleanupInterval
|
||||
|
||||
# 启动后台清理线程
|
||||
self._cleanupThread = threading.Thread(target=self._cleanupWorker, daemon=True)
|
||||
self._running = True
|
||||
self._cleanupThread.start()
|
||||
|
||||
logger.debug(
|
||||
f"用户池初始化完成,超时时间: {timeoutSeconds}秒, 清理间隔: {cleanupInterval}秒"
|
||||
)
|
||||
|
||||
def createUser(self, uid: str, userObj: Any) -> bool:
|
||||
"""
|
||||
创建并管理用户对象
|
||||
|
||||
Args:
|
||||
uid: 用户ID
|
||||
userObj: 用户对象
|
||||
|
||||
Returns:
|
||||
bool: 是否创建成功
|
||||
"""
|
||||
with self._lock:
|
||||
if uid in self._players:
|
||||
logger.debug(f"用户 {uid} 已存在,正在覆盖")
|
||||
# 可以选择返回False或者覆盖,这里选择覆盖
|
||||
# return False
|
||||
|
||||
self._players[uid] = {
|
||||
"object": userObj,
|
||||
"lastActive": time.time(),
|
||||
"activeCount": 0,
|
||||
}
|
||||
logger.debug(f"用户 {uid} 创建并开始管理")
|
||||
return True
|
||||
|
||||
def getUser(self, uid: str) -> Any | None:
|
||||
"""
|
||||
获取用户对象并刷新活跃时间
|
||||
|
||||
Args:
|
||||
uid: 用户ID
|
||||
|
||||
Returns:
|
||||
Optional[Any]: 用户对象,如果不存在或已超时则返回None
|
||||
"""
|
||||
with self._lock:
|
||||
userData = self._players.get(uid)
|
||||
|
||||
if not userData:
|
||||
logger.debug(f"用户 {uid} 不存在")
|
||||
return None
|
||||
|
||||
# 检查是否已超时(防御性检查)
|
||||
currentTime = time.time()
|
||||
if currentTime - userData["lastActive"] > self.timeoutSeconds:
|
||||
logger.debug(f"用户 {uid} 在获取操作期间已超时")
|
||||
self._removeUser(uid)
|
||||
return None
|
||||
|
||||
# 刷新活跃时间
|
||||
userData["lastActive"] = currentTime
|
||||
userData["activeCount"] += 1
|
||||
|
||||
logger.debug(f"用户 {uid} 获取成功,活跃次数: {userData['activeCount']}")
|
||||
return userData["object"]
|
||||
|
||||
def updateUser(self, uid: str, userObj: Any) -> bool:
|
||||
"""
|
||||
更新用户对象
|
||||
|
||||
Args:
|
||||
uid: 用户ID
|
||||
userObj: 新的用户对象
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
with self._lock:
|
||||
if uid not in self._players:
|
||||
logger.debug(f"用户 {uid} 不存在,无法更新")
|
||||
return False
|
||||
|
||||
self._players[uid]["object"] = userObj
|
||||
self._players[uid]["lastActive"] = time.time()
|
||||
logger.debug(f"用户 {uid} 更新成功")
|
||||
return True
|
||||
|
||||
def removeUser(self, uid: str) -> bool:
|
||||
"""
|
||||
主动移除用户
|
||||
|
||||
Args:
|
||||
uid: 用户ID
|
||||
|
||||
Returns:
|
||||
bool: 是否移除成功
|
||||
"""
|
||||
with self._lock:
|
||||
return self._removeUser(uid)
|
||||
|
||||
def _removeUser(self, uid: str) -> bool:
|
||||
"""内部移除用户方法"""
|
||||
if uid in self._players:
|
||||
userData = self._players.pop(uid)
|
||||
# 如果需要清理资源,可以在这里处理
|
||||
if hasattr(userData["object"], "close"):
|
||||
try:
|
||||
userData["object"].close()
|
||||
except Exception as e:
|
||||
logger.debug(f"关闭用户 {uid} 时出错: {e}")
|
||||
|
||||
logger.debug(f"用户 {uid} 已移除,总活跃次数: {userData['activeCount']}")
|
||||
return True
|
||||
return False
|
||||
|
||||
def _cleanupWorker(self):
|
||||
"""后台清理线程的工作函数"""
|
||||
while self._running:
|
||||
try:
|
||||
self._cleanupExpiredUsers()
|
||||
except Exception as e:
|
||||
logger.debug(f"清理工作线程出错: {e}")
|
||||
|
||||
# 休眠指定间隔
|
||||
time.sleep(self.cleanupInterval)
|
||||
|
||||
def _cleanupExpiredUsers(self):
|
||||
"""清理超时用户"""
|
||||
currentTime = time.time()
|
||||
expiredUsers = []
|
||||
|
||||
# 首先收集过期的用户ID,避免在迭代中修改字典
|
||||
with self._lock:
|
||||
for uid, userData in self._players.items():
|
||||
if currentTime - userData["lastActive"] > self.timeoutSeconds:
|
||||
expiredUsers.append(uid)
|
||||
|
||||
# 移除过期用户
|
||||
for uid in expiredUsers:
|
||||
with self._lock:
|
||||
# 再次检查,防止在收集和移除之间用户被更新
|
||||
if (
|
||||
uid in self._players
|
||||
and currentTime - self._players[uid]["lastActive"]
|
||||
> self.timeoutSeconds
|
||||
):
|
||||
self._removeUser(uid)
|
||||
|
||||
if expiredUsers:
|
||||
logger.debug(f"已清理 {len(expiredUsers)} 个过期用户: {expiredUsers}")
|
||||
|
||||
def getActiveUsers(self) -> dict[str, dict[str, Any]]:
|
||||
"""
|
||||
获取当前活跃用户信息
|
||||
|
||||
Returns:
|
||||
Dict: 用户信息字典
|
||||
"""
|
||||
with self._lock:
|
||||
# 返回副本避免外部修改
|
||||
return {
|
||||
uid: {
|
||||
"lastActive": data["lastActive"],
|
||||
"activeCount": data["activeCount"],
|
||||
"timeRemaining": self.timeoutSeconds
|
||||
- (time.time() - data["lastActive"]),
|
||||
}
|
||||
for uid, data in self._players.items()
|
||||
}
|
||||
|
||||
def userCount(self) -> int:
|
||||
"""获取当前用户数量"""
|
||||
with self._lock:
|
||||
return len(self._players)
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭用户池,清理资源"""
|
||||
self._running = False
|
||||
if self._cleanupThread.is_alive():
|
||||
self._cleanupThread.join(timeout=5)
|
||||
|
||||
# 清理所有用户
|
||||
with self._lock:
|
||||
uids = list(self._players.keys())
|
||||
for uid in uids:
|
||||
self._removeUser(uid)
|
||||
|
||||
logger.debug("用户池关闭完成")
|
||||
|
||||
|
||||
g_pUserPool = CPlayerPool()
|
||||
@@ -0,0 +1,245 @@
|
||||
import math
|
||||
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
|
||||
from ..core.dbService import g_pDBService
|
||||
from ..utils.config import g_sResourcePath, g_sTranslation
|
||||
from ..utils.tool import g_pToolManager
|
||||
|
||||
|
||||
class CShopManager:
|
||||
@classmethod
|
||||
async def getSeedShopImage(
|
||||
cls, filterKey: str | int = 1, num: int = 1, isVip: int = 0
|
||||
) -> bytes:
|
||||
"""获取商店页面
|
||||
|
||||
Args:
|
||||
filterKey (str|int):
|
||||
- 字符串: 根据关键字筛选种子名称
|
||||
- 整数: 翻至对应页(无筛选)
|
||||
num (int, optional): 当 filterKey 为字符串时,用于指定页码。Defaults to 1.
|
||||
|
||||
Returns:
|
||||
bytes: 返回商店图片bytes
|
||||
"""
|
||||
# 解析参数:区分筛选关键字和页码
|
||||
filterStr = None
|
||||
if isinstance(filterKey, int):
|
||||
page = filterKey
|
||||
else:
|
||||
filterStr = filterKey
|
||||
page = num
|
||||
|
||||
# 表头定义
|
||||
columnName = [
|
||||
"-",
|
||||
"种子名称",
|
||||
"农场币",
|
||||
"解锁等级",
|
||||
"果实单价",
|
||||
"收获经验",
|
||||
"收获数量",
|
||||
"成熟时间(小时)",
|
||||
"收获次数",
|
||||
"是否可以上架交易行",
|
||||
]
|
||||
|
||||
# 查询所有可购买作物,并根据筛选关键字过滤
|
||||
plants = await g_pDBService.plant.listPlants()
|
||||
filteredPlants = []
|
||||
|
||||
# 如果是点券商店
|
||||
if isVip:
|
||||
columnName[2] = "点券"
|
||||
for plant in plants:
|
||||
# 只留下点券购买的种子
|
||||
if plant["isVip"] == 0:
|
||||
continue
|
||||
# 跳过未解锁购买的种子
|
||||
if plant["isBuy"] == 0:
|
||||
continue
|
||||
# 字符串筛选
|
||||
if filterStr and filterStr not in plant["name"]:
|
||||
continue
|
||||
filteredPlants.append(plant)
|
||||
else:
|
||||
for plant in plants:
|
||||
# 只留下农场币购买的种子
|
||||
if plant["isVip"] == 1:
|
||||
continue
|
||||
# 跳过未解锁购买的种子
|
||||
if plant["isBuy"] == 0:
|
||||
continue
|
||||
# 字符串筛选
|
||||
if filterStr and filterStr not in plant["name"]:
|
||||
continue
|
||||
filteredPlants.append(plant)
|
||||
|
||||
# 计算分页
|
||||
totalCount = len(filteredPlants)
|
||||
pageCount = math.ceil(totalCount / 15) if totalCount else 1
|
||||
startIndex = (page - 1) * 15
|
||||
pageItems = filteredPlants[startIndex : startIndex + 15]
|
||||
|
||||
# 构建数据行
|
||||
dataList = []
|
||||
for plant in pageItems:
|
||||
# 图标处理
|
||||
icon = ""
|
||||
iconPath = g_sResourcePath / f"plant/{plant['name']}/icon.png"
|
||||
if iconPath.exists():
|
||||
icon = (iconPath, 33, 33)
|
||||
|
||||
# 交易行标记
|
||||
sell = "可以" if plant["sell"] else "不可以"
|
||||
|
||||
dataList.append(
|
||||
[
|
||||
icon,
|
||||
plant["name"], # 种子名称
|
||||
plant["buy"], # 农场币种子单价
|
||||
plant["level"], # 解锁等级
|
||||
plant["price"], # 果实单价
|
||||
plant["experience"], # 收获经验
|
||||
plant["harvest"], # 收获数量
|
||||
plant["time"], # 成熟时间(小时)
|
||||
plant["crop"], # 收获次数
|
||||
sell, # 是否可上架交易行
|
||||
]
|
||||
)
|
||||
if isVip:
|
||||
dataList[-1][2] = plant["vipBuy"] # 点券种子单价
|
||||
|
||||
# 页码标题
|
||||
title = f"种子商店 页数: {page}/{pageCount}"
|
||||
|
||||
# 渲染表格并返回图片bytes
|
||||
result = await ImageTemplate.table_page(
|
||||
title,
|
||||
"购买示例:@小真寻 购买种子 大白菜 5",
|
||||
columnName,
|
||||
dataList,
|
||||
)
|
||||
return result.pic2bytes()
|
||||
|
||||
@classmethod
|
||||
async def buySeed(cls, uid: str, name: str, num: int = 1) -> str:
|
||||
"""购买种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 植物名称
|
||||
num (int, optional): 购买数量
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if num <= 0:
|
||||
return g_sTranslation["buySeed"]["notNum"]
|
||||
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(name)
|
||||
if not plantInfo or not player:
|
||||
return g_sTranslation["buySeed"]["error"]
|
||||
|
||||
level = player.user.get("level", 0)
|
||||
|
||||
if level < int(plantInfo["level"]):
|
||||
return g_sTranslation["buySeed"]["noLevel"]
|
||||
|
||||
vipSeed = plantInfo.get("isVip", 0) == 1
|
||||
currencyType = "vipPoint" if vipSeed else "point"
|
||||
price = int(plantInfo["vipBuy" if vipSeed else "buy"])
|
||||
totalCost = price * num
|
||||
|
||||
currentCurrency = player.user.get(currencyType, 0)
|
||||
if currentCurrency < totalCost:
|
||||
return g_sTranslation["buySeed"][f"no{'Vip' if vipSeed else ''}Point"]
|
||||
|
||||
await player.addPoint(currencyType, currentCurrency - totalCost)
|
||||
|
||||
if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num):
|
||||
return g_sTranslation["buySeed"]["errorSql"]
|
||||
|
||||
success_key = "vipSuccess" if vipSeed else "success"
|
||||
remaining_currency = currentCurrency - totalCost
|
||||
|
||||
return g_sTranslation["buySeed"][success_key].format(
|
||||
name=name, total=totalCost, point=remaining_currency
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def sellPlantByUid(cls, uid: str, name: str = "", num: int = 1) -> str:
|
||||
"""出售作物
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if not isinstance(name, str) or name.strip() == "":
|
||||
name = ""
|
||||
|
||||
plant = await g_pDBService.userPlant.getUserPlantByUid(uid)
|
||||
if not plant:
|
||||
return g_sTranslation["sellPlant"]["no"]
|
||||
|
||||
point = 0
|
||||
totalSold = 0
|
||||
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
|
||||
await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0)
|
||||
else:
|
||||
if name not in plant:
|
||||
return g_sTranslation["sellPlant"]["error"].format(name=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
|
||||
)
|
||||
totalSold = sellAmount
|
||||
|
||||
if name == "":
|
||||
totalPoint = point
|
||||
else:
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(name)
|
||||
if not plantInfo:
|
||||
price = 0
|
||||
else:
|
||||
price = plantInfo["price"]
|
||||
|
||||
totalPoint = totalSold * price
|
||||
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
|
||||
currentPoint = player.user.get("point", 0)
|
||||
await player.addPoint("point", currentPoint + totalPoint)
|
||||
|
||||
result = "success1" if name == "" else "success"
|
||||
|
||||
return g_sTranslation["sellPlant"][result].format(
|
||||
point=totalPoint, num=currentPoint + totalPoint
|
||||
)
|
||||
|
||||
|
||||
g_pShopManager = CShopManager()
|
||||
@@ -1,649 +0,0 @@
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from datetime import date, datetime, timedelta
|
||||
from io import StringIO
|
||||
from math import e
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .config import g_pJsonManager, g_sDBFilePath, g_sDBPath
|
||||
|
||||
|
||||
class CSqlManager:
|
||||
def __init__(self):
|
||||
g_sDBPath.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
async def cleanup(cls):
|
||||
if cls.m_pDB:
|
||||
await cls.m_pDB.close()
|
||||
|
||||
@classmethod
|
||||
async def init(cls) -> bool:
|
||||
bIsExist = os.path.exists(g_sDBFilePath)
|
||||
|
||||
cls.m_pDB = await aiosqlite.connect(g_sDBFilePath)
|
||||
|
||||
#if bIsExist == False:
|
||||
#TODO 缺少判断创建失败事件
|
||||
#await cls.createDB()
|
||||
|
||||
await cls.checkDB()
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def getColumns(cls, tableName):
|
||||
""" 由AI生成
|
||||
获取表的列信息
|
||||
"""
|
||||
try:
|
||||
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")')
|
||||
columns = [row[1] for row in await cursor.fetchall()]
|
||||
return columns
|
||||
except aiosqlite.Error as e:
|
||||
logger.error(f"获取表结构失败: {str(e)}")
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
async def ensure_table_exists(cls, tableName, columns) -> bool:
|
||||
"""智能创建并分析数据库表、字段是否存在 由AI生成
|
||||
|
||||
Args:
|
||||
tableName (_type_): 表名
|
||||
columns (_type_): 字典
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
try:
|
||||
current_columns = await cls.getColumns(tableName)
|
||||
|
||||
#检查表是否存在
|
||||
table_exists = bool(current_columns)
|
||||
|
||||
#如果表不存在,直接创建
|
||||
if not table_exists:
|
||||
create_sql = f'''
|
||||
CREATE TABLE "{tableName}" (
|
||||
{", ".join(f'"{k}" {v}' for k, v in columns.items())}
|
||||
);
|
||||
'''
|
||||
await cls.m_pDB.execute(create_sql)
|
||||
await cls.m_pDB.commit() #显式提交新建表操作
|
||||
return True
|
||||
|
||||
#表存在时的处理
|
||||
columns_to_add = []
|
||||
columns_to_remove = []
|
||||
|
||||
#检查需要添加的列
|
||||
for k, v in columns.items():
|
||||
if k not in current_columns:
|
||||
columns_to_add.append(f'"{k}" {v}')
|
||||
|
||||
#检查需要移除的列
|
||||
for col in current_columns:
|
||||
if col not in columns.keys():
|
||||
columns_to_remove.append(col)
|
||||
|
||||
#执行修改
|
||||
if columns_to_add or columns_to_remove:
|
||||
try:
|
||||
#开启事务(使用connection级别的事务控制)
|
||||
await cls.m_pDB.execute('BEGIN TRANSACTION')
|
||||
|
||||
#添加新列
|
||||
for col_def in columns_to_add:
|
||||
await cls.m_pDB.execute(f'ALTER TABLE "{tableName}" ADD COLUMN {col_def}')
|
||||
|
||||
#删除旧列
|
||||
for col in columns_to_remove:
|
||||
await cls.m_pDB.execute(f'ALTER TABLE "{tableName}" DROP COLUMN "{col}"')
|
||||
|
||||
#显式提交事务
|
||||
await cls.m_pDB.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
#回滚事务
|
||||
await cls.m_pDB.rollback()
|
||||
logger.error(f"表结构迁移失败: {str(e)}")
|
||||
|
||||
return False
|
||||
except aiosqlite.Error as e:
|
||||
logger.error(f"表结构迁移失败: {str(e)}")
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def checkDB(cls) -> bool:
|
||||
userInfo = {
|
||||
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||
"name": "TEXT NOT NULL",
|
||||
"exp": "INTEGER DEFAULT 0",
|
||||
"point": "INTEGER DEFAULT 0",
|
||||
"soil": "INTEGER DEFAULT 3",
|
||||
"stealing": "TEXT DEFAULT NULL"
|
||||
}
|
||||
|
||||
userStorehouse = {
|
||||
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||
"item": "TEXT DEFAULT ''",
|
||||
"plant": "TEXT DEFAULT ''",
|
||||
"seed": "TEXT DEFAULT ''"
|
||||
}
|
||||
|
||||
userSoilInfo = {
|
||||
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||
**{f"soil{i}": "TEXT DEFAULT ''" for i in range(1, 31)}
|
||||
}
|
||||
|
||||
await cls.ensure_table_exists("user", userInfo)
|
||||
|
||||
await cls.ensure_table_exists("storehouse", userStorehouse)
|
||||
|
||||
await cls.ensure_table_exists("soil", userSoilInfo)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def executeDB(cls, command: str) -> bool:
|
||||
"""执行自定义SQL
|
||||
|
||||
Args:
|
||||
command (str): SQL语句
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
|
||||
if len(command) <= 0:
|
||||
logger.warning("数据库语句长度不能!")
|
||||
return False
|
||||
|
||||
try:
|
||||
await cls.m_pDB.execute(command)
|
||||
await cls.m_pDB.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("数据库语句执行出错:" + command)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 100):
|
||||
"""初始化用户信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 农场名称
|
||||
exp (int): 农场经验
|
||||
point (int): 农场币
|
||||
"""
|
||||
|
||||
#用户信息
|
||||
userInfo = f"""
|
||||
INSERT INTO user (uid, name, exp, point, soil, stealing) VALUES ({uid}, '{name}', {exp}, {point}, 3, '{date.today()}|5')
|
||||
"""
|
||||
|
||||
#用户仓库
|
||||
userStorehouse = f"""
|
||||
INSERT INTO storehouse (uid) VALUES ({uid});
|
||||
"""
|
||||
|
||||
#用户土地
|
||||
userSoilInfo = f"""
|
||||
INSERT INTO soil (uid) VALUES ({uid});
|
||||
"""
|
||||
|
||||
if not await cls.executeDB(userInfo):
|
||||
return False
|
||||
|
||||
if not await cls.executeDB(userStorehouse):
|
||||
return False
|
||||
|
||||
if not await cls.executeDB(userSoilInfo):
|
||||
return False
|
||||
|
||||
return "开通农场成功"
|
||||
|
||||
@classmethod
|
||||
async def getUserInfoByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取用户信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
list[dict]: 用户信息
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return {}
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT * FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
async for row in cursor:
|
||||
userDict = {
|
||||
"uid": row[0],
|
||||
"name": row[1],
|
||||
"exp": row[2],
|
||||
"point": row[3],
|
||||
"soil": row[4],
|
||||
"stealing": row[5]
|
||||
}
|
||||
|
||||
return userDict
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserInfoByUid查询失败: {e}")
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def getUserPointByUid(cls, uid: str) -> int:
|
||||
"""根据用户Uid获取用户农场币
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 用户农场币
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return -1
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(f"SELECT point FROM user WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
return int(row[0])
|
||||
|
||||
return -1
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserPointByUid查询失败: {e}")
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def updateUserPointByUid(cls, uid: str, point: int) -> int:
|
||||
"""根据用户Uid修改用户农场币
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
point (int): 要更新的新农场币数量(需 ≥ 0)
|
||||
|
||||
Returns:
|
||||
int: 更新后的农场币数量(成功时),-1(失败时)
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
logger.warning("参数校验失败: uid为空或农场币值无效")
|
||||
return -1
|
||||
|
||||
try:
|
||||
return await cls.executeDB(f"UPDATE user SET point = {point} WHERE uid = {uid}")
|
||||
except Exception as e:
|
||||
logger.error(f"金币更新失败: {e}")
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def getUserExpByUid(cls, uid: str) -> int:
|
||||
"""根据用户Uid获取用户经验
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 用户经验值
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return -1
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(f"SELECT exp FROM user WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
return int(row[0])
|
||||
|
||||
return -1
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserLevelByUid查询失败: {e}")
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def UpdateUserExpByUid(cls, uid: str, exp: int) -> bool:
|
||||
"""根据用户Uid刷新用户经验
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return False
|
||||
|
||||
sql = f"UPDATE user SET exp = '{exp}' WHERE uid = {uid}"
|
||||
|
||||
return await cls.executeDB(sql)
|
||||
|
||||
@classmethod
|
||||
async def getUserLevelByUid(cls, uid: str) -> tuple[int, int, int]:
|
||||
"""根据用户Uid获取用户等级
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
tuple[int, int, int]: (当前等级, 下级所需经验, 当前等级剩余经验)
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return -1, -1, -1
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(f"SELECT exp FROM user WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
exp = int(row[0])
|
||||
|
||||
level = exp // 200
|
||||
nextLevelExp = 200 * (level + 1)
|
||||
|
||||
currentLevelExp = level * 200
|
||||
remainingExp = exp - currentLevelExp
|
||||
|
||||
return level, nextLevelExp, remainingExp
|
||||
|
||||
return -1, -1, -1
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserLevelByUid查询失败: {e}")
|
||||
return -1, -1, -1
|
||||
|
||||
@classmethod
|
||||
async def getUserSoilByUid(cls, uid: str) -> int:
|
||||
"""根据用户Uid获取解锁地块
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 解锁几块地
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return 0
|
||||
|
||||
async with cls.m_pDB.execute(f"SELECT soil FROM user WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
if not row[0]:
|
||||
return 0
|
||||
else:
|
||||
return int(row[0])
|
||||
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def getUserSoilStatusBySoilID(cls, uid: str, soil: str) -> tuple[bool, str]:
|
||||
"""根据土地块获取用户土地状态
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
soil (str): 土地id
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: [是否可以播种,土地信息]
|
||||
"""
|
||||
if len(uid) <= 0:
|
||||
return False, ""
|
||||
|
||||
async with cls.m_pDB.execute(f"SELECT {soil} FROM soil WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
if row[0] == None or len(row[0]) <= 0:
|
||||
return True, ""
|
||||
else:
|
||||
return False, row[0]
|
||||
|
||||
return False, ""
|
||||
|
||||
@classmethod
|
||||
async def updateUserSoilStatusByPlantName(cls, uid: str, soil: str,
|
||||
plant: str = "",
|
||||
status: int = 0) -> bool:
|
||||
"""根据种子名称使用户播种
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
soil (str): 土地id
|
||||
plant (str): 种子名称
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return False
|
||||
|
||||
if len(plant) <= 0 and status == 4:
|
||||
s = f",,,{status},"
|
||||
elif len(plant) <= 0 and status != 4:
|
||||
s = ""
|
||||
else:
|
||||
#获取种子信息 这里能崩我吃
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][plant]
|
||||
|
||||
currentTime = datetime.now()
|
||||
newTime = currentTime + timedelta(hours=int(plantInfo['time']))
|
||||
|
||||
#0: 种子名称
|
||||
#1: 种下时间
|
||||
#2: 预计成熟时间
|
||||
#3: 地状态:0:无 1:长草 2:生虫 3:缺水 4:枯萎
|
||||
#4: 是否被偷 示例:QQ号-偷取数量|QQ号-偷取数量
|
||||
#5: 土地等级 0:普通 1:红土地 2:黑土地 3:金土地 4:紫晶土地 5:蓝晶土地 6:黑晶土地
|
||||
s = f"{plant},{int(currentTime.timestamp())},{int(newTime.timestamp())},{status},,"
|
||||
|
||||
sql = f"UPDATE soil SET {soil} = '{s}' WHERE uid = {uid}"
|
||||
|
||||
return await cls.executeDB(sql)
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByUid(cls, uid: str) -> str:
|
||||
"""获取用户仓库种子信息
|
||||
|
||||
Args:
|
||||
info (list[dict]): 用户信息
|
||||
|
||||
Returns:
|
||||
str: 仓库种子信息
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return ""
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(f"SELECT seed FROM storehouse WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
return row[0]
|
||||
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserSeedByUid查询失败: {e}")
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByName(cls, uid: str, name: str) -> int:
|
||||
"""获取用户仓库种子信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户信息
|
||||
name (str): 种子名称
|
||||
|
||||
Returns:
|
||||
int: 仓库种子信息
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return -1
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(f"SELECT seed FROM storehouse WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
if row[0] == None or len(row[0]) == 0:
|
||||
return -1
|
||||
|
||||
plantDict: Dict[str, int] = {}
|
||||
for item in row[0].split(','):
|
||||
if '|' in item:
|
||||
seedName, count = item.split('|', 1)
|
||||
plantDict[seedName] = int(count)
|
||||
|
||||
if name in plantDict:
|
||||
return plantDict[name]
|
||||
else:
|
||||
return -1
|
||||
|
||||
return -1
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserSeedByUid查询失败: {e}")
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def updateUserSeedByUid(cls, uid: str, seed: str) -> bool:
|
||||
"""更新用户种子仓库
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return False
|
||||
|
||||
sql = f"UPDATE storehouse SET seed = '{seed}' WHERE uid = {uid}"
|
||||
|
||||
return await cls.executeDB(sql)
|
||||
|
||||
@classmethod
|
||||
async def addUserSeedByPlant(cls, uid: str, seed: str, num: int) -> bool:
|
||||
"""添加作物信息至仓库
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
seed (str): 种子名称
|
||||
num(str): 种子数量
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return False
|
||||
|
||||
seedsDict = {}
|
||||
currentSeeds = await cls.getUserSeedByUid(uid)
|
||||
|
||||
if currentSeeds:
|
||||
for item in currentSeeds.split(','):
|
||||
if item.strip():
|
||||
name, count = item.split('|')
|
||||
seedsDict[name.strip()] = int(count.strip())
|
||||
|
||||
if seed in seedsDict:
|
||||
seedsDict[seed] += num
|
||||
if seedsDict[seed] <= 0:
|
||||
del seedsDict[seed]
|
||||
else:
|
||||
if num > 0:
|
||||
seedsDict[seed] = num
|
||||
|
||||
updatedSeeds = ','.join([f"{name}|{count}" for name, count in seedsDict.items()])
|
||||
|
||||
sql = f"UPDATE storehouse SET seed = '{updatedSeeds}' WHERE uid = {uid}"
|
||||
|
||||
return await cls.executeDB(sql)
|
||||
|
||||
@classmethod
|
||||
async def getUserPlantByUid(cls, uid: str) -> str:
|
||||
"""获取用户仓库作物信息
|
||||
|
||||
Args:
|
||||
info (list[dict]): 用户信息
|
||||
|
||||
Returns:
|
||||
str: 仓库作物信息
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return ""
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(f"SELECT plant FROM storehouse WHERE uid = {uid}") as cursor:
|
||||
async for row in cursor:
|
||||
return row[0]
|
||||
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.warning(f"getUserPlantByUid查询失败: {e}")
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def updateUserPlantByUid(cls, uid: str, plant: str) -> bool:
|
||||
"""更新用户作物仓库
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return False
|
||||
|
||||
sql = f"UPDATE storehouse SET plant = '{plant}' WHERE uid = {uid}"
|
||||
|
||||
return await cls.executeDB(sql)
|
||||
|
||||
@classmethod
|
||||
async def addUserPlantByPlant(cls, uid: str, plant: str, num: int) -> bool:
|
||||
"""添加作物信息至仓库
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
plant (str): 作物名称
|
||||
num(str): 作物数量
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
|
||||
if len(uid) <= 0:
|
||||
return False
|
||||
|
||||
plantsDict = {}
|
||||
currentPlants = await cls.getUserPlantByUid(uid)
|
||||
|
||||
if currentPlants:
|
||||
for item in currentPlants.split(','):
|
||||
if item.strip():
|
||||
name, count = item.split('|')
|
||||
plantsDict[name.strip()] = int(count.strip())
|
||||
|
||||
if plant in plantsDict:
|
||||
plantsDict[plant] += num
|
||||
if plantsDict[plant] <= 0:
|
||||
del plantsDict[plant]
|
||||
else:
|
||||
if num > 0:
|
||||
plantsDict[plant] = num
|
||||
|
||||
updatedPlants = ','.join([f"{name}|{count}" for name, count in plantsDict.items()])
|
||||
|
||||
sql = f"UPDATE storehouse SET plant = '{updatedPlants}' WHERE uid = {uid}"
|
||||
|
||||
return await cls.executeDB(sql)
|
||||
|
||||
g_pSqlManager = CSqlManager()
|
||||
@@ -0,0 +1,123 @@
|
||||
import inspect
|
||||
import time
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class Signal:
|
||||
def __set_name__(self, owner, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
bound = instance.__dict__.get(self.name)
|
||||
if bound is None:
|
||||
bound = _SignalBound()
|
||||
instance.__dict__[self.name] = bound
|
||||
return bound
|
||||
|
||||
|
||||
class _SignalBound:
|
||||
def __init__(self):
|
||||
self._slots = []
|
||||
self._onceSlots = []
|
||||
|
||||
def connect(self, func=None, *, priority=0):
|
||||
if func is None:
|
||||
return lambda f: self.connect(f, priority=priority)
|
||||
if callable(func) and not any(s[0] == func for s in self._slots):
|
||||
self._slots.append((func, priority))
|
||||
self._slots.sort(key=lambda x: -x[1])
|
||||
return func
|
||||
|
||||
def connect_once(self, func=None, *, priority=0):
|
||||
if func is None:
|
||||
return lambda f: self.connect_once(f, priority=priority)
|
||||
if callable(func) and not any(s[0] == func for s in self._onceSlots):
|
||||
self._onceSlots.append((func, priority))
|
||||
self._onceSlots.sort(key=lambda x: -x[1])
|
||||
return func
|
||||
|
||||
def disconnect(self, func):
|
||||
self._slots = [s for s in self._slots if s[0] != func]
|
||||
self._onceSlots = [s for s in self._onceSlots if s[0] != func]
|
||||
|
||||
async def emit(self, *args, **kwargs):
|
||||
slots = list(self._slots)
|
||||
onceSlots = list(self._onceSlots)
|
||||
self._onceSlots.clear()
|
||||
for slot, _ in slots + onceSlots:
|
||||
start = time.time()
|
||||
try:
|
||||
if inspect.iscoroutinefunction(slot):
|
||||
await slot(*args, **kwargs)
|
||||
else:
|
||||
slot(*args, **kwargs)
|
||||
logger.debug(
|
||||
f"【真寻农场】事件槽 {slot.__name__} 执行完成,耗时 {(time.time() - start) * 1000:.2f} ms"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"事件槽 {slot.__name__} 触发异常: {e}")
|
||||
|
||||
|
||||
class FarmEventManager:
|
||||
m_beforePlant = Signal()
|
||||
"""播种前信号
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 播种种子名称
|
||||
num (int): 播种数量
|
||||
"""
|
||||
|
||||
m_afterPlant = Signal()
|
||||
"""播种后信号 每块地播种都会触发该信号
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 播种种子名称
|
||||
soilIndex (int): 播种地块索引 从1开始
|
||||
"""
|
||||
|
||||
m_beforeHarvest = Signal()
|
||||
"""收获前信号
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
"""
|
||||
|
||||
m_afterHarvest = Signal()
|
||||
"""收获后信号 每块地收获都会触发该信号
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 收获作物名称
|
||||
num (int): 收获数量
|
||||
soilIndex (int): 收获地块索引 从1开始
|
||||
"""
|
||||
|
||||
m_beforeEradicate = Signal()
|
||||
"""铲除前信号
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
"""
|
||||
|
||||
m_afterEradicate = Signal()
|
||||
"""铲除后信号 每块地铲除都会触发该信号
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
soilIndex (index): 铲除地块索引 从1开始
|
||||
"""
|
||||
|
||||
m_beforeExpand = Signal()
|
||||
m_afterExpand = Signal()
|
||||
m_beforeSteal = Signal()
|
||||
m_afterSteal = Signal()
|
||||
|
||||
m_dit = Signal()
|
||||
|
||||
|
||||
g_pEventManager = FarmEventManager()
|
||||
@@ -1,723 +0,0 @@
|
||||
import asyncio
|
||||
import math
|
||||
import random
|
||||
from datetime import date, datetime
|
||||
from io import StringIO
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from numpy import number
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
from zhenxun.utils.enum import GoldHandle
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from ..config import g_pJsonManager, g_sResourcePath
|
||||
from ..database import g_pSqlManager
|
||||
|
||||
|
||||
class CFarmManager:
|
||||
@classmethod
|
||||
async def buyPointByUid(cls, uid: str, num: int) -> str:
|
||||
if num <= 0:
|
||||
return "你是怎么做到购买不是正数的农场币的"
|
||||
|
||||
user = await UserConsole.get_user(uid)
|
||||
|
||||
pro = float(Config.get_config("zhenxun_plugin_farm", "兑换倍数"))
|
||||
tax = float(Config.get_config("zhenxun_plugin_farm", "手续费"))
|
||||
|
||||
#计算手续费
|
||||
fee = math.floor(num * tax)
|
||||
#实际扣费金额
|
||||
deduction = num + fee
|
||||
|
||||
if user.gold < deduction:
|
||||
return f"你的金币不足或不足承担手续费。当前手续费为{fee}"
|
||||
|
||||
await UserConsole.reduce_gold(uid, num, GoldHandle.PLUGIN , 'zhenxun_plugin_farm')
|
||||
await UserConsole.reduce_gold(uid, fee, GoldHandle.PLUGIN , 'zhenxun_plugin_farm')
|
||||
|
||||
point = num * pro
|
||||
|
||||
p = await g_pSqlManager.getUserPointByUid(uid)
|
||||
number = point + p
|
||||
|
||||
await g_pSqlManager.updateUserPointByUid(uid, int(number))
|
||||
|
||||
return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}"
|
||||
|
||||
@classmethod
|
||||
async def drawFarmByUid(cls, uid: str, name: str) -> bytes:
|
||||
"""绘制用户农场
|
||||
|
||||
Args:
|
||||
uid (str): 用户UID
|
||||
|
||||
Returns:
|
||||
bytes: 返回绘制结果
|
||||
"""
|
||||
img = BuildImage(background = g_sResourcePath / "background/background.jpg")
|
||||
|
||||
soilSize = g_pJsonManager.m_pSoil['size']
|
||||
|
||||
#TODO 缺少判断用户土地资源状况
|
||||
soil = BuildImage(background = g_sResourcePath / "soil/普通土地.png")
|
||||
await soil.resize(0, soilSize[0], soilSize[1])
|
||||
|
||||
grass = BuildImage(background = g_sResourcePath / "soil/草土地.png")
|
||||
await grass.resize(0, soilSize[0], soilSize[1])
|
||||
|
||||
soilPos = g_pJsonManager.m_pSoil['soil']
|
||||
soilUnlock = await g_pSqlManager.getUserSoilByUid(uid)
|
||||
|
||||
x = 0
|
||||
y = 0
|
||||
isFirstExpansion = True #首次添加扩建图片
|
||||
isFirstRipe = True
|
||||
plant = None
|
||||
for index in range(0, 30):
|
||||
x = soilPos[str(index + 1)]['x']
|
||||
y = soilPos[str(index + 1)]['y']
|
||||
|
||||
#如果土地已经到达对应等级
|
||||
if index < soilUnlock:
|
||||
await img.paste(soil, (x, y))
|
||||
|
||||
isPlant, plant, isRipe= await cls.drawSoilPlant(uid, f"soil{str(index + 1)}")
|
||||
|
||||
if isPlant:
|
||||
await img.paste(plant, (x + soilSize[0] // 2 - plant.width // 2,
|
||||
y + soilSize[1] // 2 - plant.height // 2))
|
||||
|
||||
#1700 275
|
||||
#首次添加可收获图片
|
||||
if isRipe and isFirstRipe:
|
||||
ripe = BuildImage(background = g_sResourcePath / "background/ripe.png")
|
||||
|
||||
await img.paste(ripe, (x + soilSize[0] // 2 - ripe.width // 2,
|
||||
y - ripe.height // 2))
|
||||
|
||||
isFirstRipe = False
|
||||
else:
|
||||
await img.paste(grass, (x, y))
|
||||
|
||||
if isFirstExpansion:
|
||||
isFirstExpansion = False
|
||||
|
||||
#首次添加扩建图片
|
||||
expansion = BuildImage(background = g_sResourcePath / "background/expansion.png")
|
||||
await expansion.resize(0, 69, 69)
|
||||
await img.paste(expansion, (x + soilSize[0] // 2 - expansion.width // 2,
|
||||
y + soilSize[1] // 2 - expansion.height))
|
||||
|
||||
#左上角绘制用户信息
|
||||
#头像
|
||||
image = await PlatformUtils.get_user_avatar(uid, "qq")
|
||||
|
||||
if image:
|
||||
avatar = BuildImage(background = image)
|
||||
|
||||
await img.paste(avatar, (125, 85))
|
||||
|
||||
#头像框
|
||||
frame = BuildImage(background = g_sResourcePath / "background/frame.png")
|
||||
await img.paste(frame, (75, 44))
|
||||
|
||||
#用户名
|
||||
nameImg = await BuildImage.build_text_image(name, size = 24, font_color = (77, 35, 4))
|
||||
await img.paste(nameImg, (300, 92))
|
||||
|
||||
#经验值
|
||||
level = await g_pSqlManager.getUserLevelByUid(uid)
|
||||
|
||||
beginX = 309
|
||||
endX = 627
|
||||
#绘制宽度计算公式为 (当前经验值 / 经验值上限) * 宽度
|
||||
width = int((level[2] / level[1]) * (endX - beginX))
|
||||
await img.rectangle((beginX, 188, beginX + width, 222), (171, 194, 41))
|
||||
|
||||
expImg = await BuildImage.build_text_image(f"{level[2]} / {level[1]}", size = 24, font_color = (102, 120, 19))
|
||||
await img.paste(expImg, (390, 193))
|
||||
|
||||
#等级
|
||||
levelImg = await BuildImage.build_text_image(str(level[0]), size = 32, font_color = (214, 111, 1))
|
||||
await img.paste(levelImg, (660, 187))
|
||||
|
||||
#金币
|
||||
point = await g_pSqlManager.getUserPointByUid(uid)
|
||||
pointImg = await BuildImage.build_text_image(str(point), size = 24, font_color = (253, 253, 253))
|
||||
await img.paste(pointImg, (330, 255))
|
||||
|
||||
#点券
|
||||
bonds = await g_pSqlManager.getUserPointByUid(uid)
|
||||
bondsImg = await BuildImage.build_text_image("0", size = 24, font_color = (253, 253, 253))
|
||||
await img.paste(bondsImg, (570, 255))
|
||||
|
||||
#清晰度
|
||||
definition = Config.get_config("zhenxun_plugin_farm", "绘制农场清晰度")
|
||||
if definition == "medium":
|
||||
await img.resize(0.6)
|
||||
elif definition == "hight":
|
||||
await img.resize(0.8)
|
||||
elif definition == "original":
|
||||
pass
|
||||
else:
|
||||
await img.resize(0.4)
|
||||
|
||||
return img.pic2bytes()
|
||||
|
||||
@classmethod
|
||||
async def drawSoilPlant(cls, uid: str, soilid: str) -> tuple[bool, BuildImage, bool]:
|
||||
"""绘制植物资源
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
soilid (str): 土地id
|
||||
|
||||
Returns:
|
||||
tuple[bool, BuildImage]: [绘制是否成功,资源图片, 是否成熟]
|
||||
"""
|
||||
|
||||
plant = None
|
||||
soilStatus, soilInfo = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilid)
|
||||
|
||||
if soilStatus == True:
|
||||
return False, None, False
|
||||
else:
|
||||
soilInfo = soilInfo.split(',')
|
||||
|
||||
if int(soilInfo[3]) == 4:
|
||||
plant = BuildImage(background = g_sResourcePath / f"plant/basic/9.png")
|
||||
await plant.resize(0, 150, 212)
|
||||
return True, plant, False
|
||||
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][soilInfo[0]]
|
||||
|
||||
currentTime = datetime.now()
|
||||
matureTime = datetime.fromtimestamp(int(soilInfo[2]))
|
||||
|
||||
if currentTime >= matureTime:
|
||||
phase = int(plantInfo['phase'])
|
||||
plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/{phase - 1}.png")
|
||||
|
||||
return True, plant, True
|
||||
else:
|
||||
plantedTime = datetime.fromtimestamp(int(soilInfo[1]))
|
||||
|
||||
elapsedTime = currentTime - plantedTime
|
||||
elapsedHour = elapsedTime.total_seconds() / 60 / 60
|
||||
|
||||
currentStage = int(elapsedHour / (plantInfo['time'] / (plantInfo['phase'] - 1)))
|
||||
|
||||
#TODO 缺少判断部分种子是否是通用0阶段图片
|
||||
if currentStage <= 0:
|
||||
plant = BuildImage(background = g_sResourcePath / f"plant/basic/0.png")
|
||||
await plant.resize(0, 35, 58)
|
||||
else:
|
||||
plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/{currentStage}.png")
|
||||
|
||||
return True, plant, False
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByUid(cls, uid: str) -> bytes:
|
||||
"""获取用户种子仓库
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
bytes: 返回图片
|
||||
"""
|
||||
|
||||
data_list = []
|
||||
column_name = [
|
||||
"-",
|
||||
"种子名称",
|
||||
"数量",
|
||||
"收获经验",
|
||||
"收获数量",
|
||||
"成熟时间(小时)",
|
||||
"收获次数",
|
||||
"再次成熟时间(小时)",
|
||||
"是否可以上架交易行"
|
||||
]
|
||||
|
||||
seed = await g_pSqlManager.getUserSeedByUid(uid)
|
||||
|
||||
if seed == None:
|
||||
result = await ImageTemplate.table_page(
|
||||
"种子仓库",
|
||||
"播种示例:@小真寻 播种 大白菜 [数量]",
|
||||
column_name,
|
||||
data_list,
|
||||
)
|
||||
|
||||
return result.pic2bytes()
|
||||
|
||||
sell = ""
|
||||
for item in seed.split(','):
|
||||
if '|' in item:
|
||||
seedName, count = item.split('|', 1) #分割一次,避免多竖线问题
|
||||
try:
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][seedName]
|
||||
|
||||
icon = ""
|
||||
icon_path = g_sResourcePath / f"plant/{seedName}/icon.png"
|
||||
if icon_path.exists():
|
||||
icon = (icon_path, 33, 33)
|
||||
|
||||
if plantInfo['again'] == True:
|
||||
sell = "可以"
|
||||
else:
|
||||
sell = "不可以"
|
||||
|
||||
data_list.append(
|
||||
[
|
||||
icon,
|
||||
seedName,
|
||||
count,
|
||||
plantInfo['experience'],
|
||||
plantInfo['harvest'],
|
||||
plantInfo['time'],
|
||||
plantInfo['crop'],
|
||||
plantInfo['again'],
|
||||
sell
|
||||
]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
result = await ImageTemplate.table_page(
|
||||
"种子仓库",
|
||||
"播种示例:@小真寻 播种 大白菜 [数量]",
|
||||
column_name,
|
||||
data_list,
|
||||
)
|
||||
|
||||
return result.pic2bytes()
|
||||
|
||||
@classmethod
|
||||
async def sowing(cls, uid: str, name: str, num: int = -1) -> str:
|
||||
"""播种
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 播种种子名称
|
||||
num (int, optional): 播种数量
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
|
||||
number = 0
|
||||
count = await g_pSqlManager.getUserSeedByName(uid, name)
|
||||
|
||||
if count <= 0:
|
||||
return f"没有在你的仓库发现{name}种子,快去买点吧!"
|
||||
|
||||
#如果播种超过仓库种子
|
||||
isMax = False
|
||||
if count < num:
|
||||
isMax = True
|
||||
|
||||
#如果播种全部
|
||||
isAll = False
|
||||
if num == -1:
|
||||
isAll = True
|
||||
|
||||
soilNumber = await g_pSqlManager.getUserSoilByUid(uid)
|
||||
|
||||
for i in range(1, soilNumber + 1):
|
||||
if count > 0:
|
||||
if isAll or num > 0:
|
||||
soilName = f"soil{i}"
|
||||
success, message = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilName)
|
||||
if success:
|
||||
#更新种子数量
|
||||
num -= 1
|
||||
count -= 1
|
||||
|
||||
#记录种子消耗数量
|
||||
number -= 1
|
||||
|
||||
#更新数据库
|
||||
await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soilName, name)
|
||||
|
||||
str = ""
|
||||
|
||||
if isMax:
|
||||
str = f"仓库数量不够那么多,已将剩余数量全部播种!"
|
||||
elif num > 0:
|
||||
str = f"播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库还剩下{count}个种子"
|
||||
else:
|
||||
str = f"播种{name}成功!仓库还剩下{count}个种子"
|
||||
|
||||
await g_pSqlManager.addUserSeedByPlant(uid, name, number)
|
||||
|
||||
return str
|
||||
|
||||
@classmethod
|
||||
async def harvest(cls, uid: str) -> str:
|
||||
"""收获作物
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str: 返回
|
||||
"""
|
||||
|
||||
isStealingPlant = 0
|
||||
soilUnlock = await g_pSqlManager.getUserSoilByUid(uid)
|
||||
|
||||
soilNames = [f"soil{i + 1}" for i in range(soilUnlock)]
|
||||
soilStatuses = await asyncio.gather(*[
|
||||
g_pSqlManager.getUserSoilStatusBySoilID(uid, name)
|
||||
for name in soilNames
|
||||
])
|
||||
|
||||
harvestRecords: List[str] = []
|
||||
experience = 0
|
||||
|
||||
for (soil_name, (status, info)) in zip(soilNames, soilStatuses):
|
||||
if len(info) <= 0:
|
||||
continue
|
||||
|
||||
soilInfo = info.split(',')
|
||||
if int(soilInfo[3]) == 4:
|
||||
continue
|
||||
|
||||
plantId = soilInfo[0]
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][plantId]
|
||||
|
||||
currentTime = datetime.now()
|
||||
matureTime = datetime.fromtimestamp(int(soilInfo[2]))
|
||||
|
||||
if currentTime >= matureTime:
|
||||
number = plantInfo['harvest']
|
||||
|
||||
#判断该土地作物是否被透过
|
||||
if len(soilInfo[4]) > 0:
|
||||
stealingStatus = soilInfo[4].split('|')
|
||||
for isUser in stealingStatus:
|
||||
user = isUser.split('-')
|
||||
number -= int(user[1])
|
||||
|
||||
isStealingPlant += 1
|
||||
|
||||
experience += plantInfo['experience']
|
||||
harvestRecords.append(f"收获作物:{plantId},数量为:{number},经验为:{plantInfo['experience']}")
|
||||
|
||||
#更新数据库操作
|
||||
await g_pSqlManager.addUserPlantByPlant(uid, plantId, number)
|
||||
await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soil_name, "", 4)
|
||||
|
||||
if experience > 0:
|
||||
harvestRecords.append(f"\t累计获得经验:{experience}")
|
||||
exp = await g_pSqlManager.getUserExpByUid(uid)
|
||||
await g_pSqlManager.UpdateUserExpByUid(uid, exp + experience)
|
||||
|
||||
if isStealingPlant <= 0:
|
||||
return "没有可收获的作物哦~ 不要试图拔苗助长"
|
||||
else:
|
||||
return "\n".join(harvestRecords)
|
||||
|
||||
@classmethod
|
||||
async def eradicate(cls, uid: str) -> str:
|
||||
"""铲除作物
|
||||
TODO 缺少随意铲除作物 目前只能铲除荒废作物
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str: 返回
|
||||
"""
|
||||
|
||||
soilUnlock = await g_pSqlManager.getUserSoilByUid(uid)
|
||||
|
||||
soilNames = [f"soil{i + 1}" for i in range(soilUnlock)]
|
||||
soilStatuses = await asyncio.gather(*[
|
||||
g_pSqlManager.getUserSoilStatusBySoilID(uid, name)
|
||||
for name in soilNames
|
||||
])
|
||||
|
||||
experience = 0
|
||||
for (soil_name, (status, info)) in zip(soilNames, soilStatuses):
|
||||
if info:
|
||||
soilInfo = info.split(',')
|
||||
if int(soilInfo[3]) == 4:
|
||||
experience += 3
|
||||
|
||||
#批量更新数据库操作
|
||||
await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soil_name, "", 0)
|
||||
|
||||
if experience > 0:
|
||||
exp = await g_pSqlManager.getUserExpByUid(uid)
|
||||
await g_pSqlManager.UpdateUserExpByUid(uid, exp + experience)
|
||||
|
||||
return f"成功铲除荒废作物,累计获得经验:{experience}"
|
||||
else:
|
||||
return "没有可以铲除的作物"
|
||||
|
||||
@classmethod
|
||||
async def getUserPlantByUid(cls, uid: str) -> bytes:
|
||||
"""获取用户作物仓库
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
bytes: 返回图片
|
||||
"""
|
||||
|
||||
data_list = []
|
||||
column_name = [
|
||||
"-",
|
||||
"作物名称",
|
||||
"数量",
|
||||
"单价",
|
||||
"总价",
|
||||
"是否可以上架交易行"
|
||||
]
|
||||
|
||||
plant = await g_pSqlManager.getUserPlantByUid(uid)
|
||||
|
||||
if plant == None:
|
||||
result = await ImageTemplate.table_page(
|
||||
"作物仓库",
|
||||
"播种示例:@小真寻 出售作物 大白菜 [数量]",
|
||||
column_name,
|
||||
data_list,
|
||||
)
|
||||
|
||||
return result.pic2bytes()
|
||||
|
||||
sell = ""
|
||||
for item in plant.split(','):
|
||||
if '|' in item:
|
||||
plantName, count = item.split('|', 1) #分割一次,避免多竖线问题
|
||||
try:
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][plantName]
|
||||
|
||||
icon = ""
|
||||
icon_path = g_sResourcePath / f"plant/{plantName}/icon.png"
|
||||
if icon_path.exists():
|
||||
icon = (icon_path, 33, 33)
|
||||
|
||||
if plantInfo['again'] == True:
|
||||
sell = "可以"
|
||||
else:
|
||||
sell = "不可以"
|
||||
|
||||
number = int(count) * plantInfo['price']
|
||||
|
||||
data_list.append(
|
||||
[
|
||||
icon,
|
||||
plantName,
|
||||
count,
|
||||
plantInfo['price'],
|
||||
number,
|
||||
sell
|
||||
]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
result = await ImageTemplate.table_page(
|
||||
"作物仓库",
|
||||
"播种示例:@小真寻 出售作物 大白菜 [数量]",
|
||||
column_name,
|
||||
data_list,
|
||||
)
|
||||
|
||||
return result.pic2bytes()
|
||||
|
||||
@classmethod
|
||||
async def stealing(cls, uid: str, target: str) -> str:
|
||||
"""偷菜
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
target (str): 被偷用户Uid
|
||||
|
||||
Returns:
|
||||
str: 返回
|
||||
"""
|
||||
|
||||
#用户信息
|
||||
userInfo = await g_pSqlManager.getUserInfoByUid(uid)
|
||||
#用户可偷次数
|
||||
userStealing = userInfo["stealing"].split('|')
|
||||
|
||||
if userStealing[0] == '':
|
||||
userStealing[0] = date.today().strftime('%Y-%m-%d')
|
||||
userStealing.append(5)
|
||||
elif date.fromisoformat(userStealing[0]) != date.today():
|
||||
userStealing[0] = date.today().strftime('%Y-%m-%d')
|
||||
userStealing[1] = 5
|
||||
|
||||
if int(userStealing[1]) <= 0:
|
||||
return "你今天可偷次数到达上限啦,手下留情吧"
|
||||
|
||||
#获取用户解锁地块数量
|
||||
soilUnlockNum = int(userInfo["soil"])
|
||||
plant = {}
|
||||
|
||||
#根据解锁土地,获取每块土地状态信息
|
||||
soilNames = [f"soil{i + 1}" for i in range(soilUnlockNum)]
|
||||
soilStatuses = await asyncio.gather(*[
|
||||
g_pSqlManager.getUserSoilStatusBySoilID(target, name)
|
||||
for name in soilNames
|
||||
])
|
||||
|
||||
isStealing = False
|
||||
harvestRecords: List[str] = []
|
||||
isStealingNumber = 0
|
||||
isStealingPlant = 0
|
||||
|
||||
for (soilName, (status, info)) in zip(soilNames, soilStatuses):
|
||||
isStealing = False
|
||||
|
||||
if not info:
|
||||
continue
|
||||
|
||||
soilInfo = info.split(',')
|
||||
if int(soilInfo[3]) == 4:
|
||||
continue
|
||||
|
||||
#作物ID
|
||||
plantId = soilInfo[0]
|
||||
#作物信息
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][plantId]
|
||||
|
||||
currentTime = datetime.now()
|
||||
matureTime = datetime.fromtimestamp(int(soilInfo[2]))
|
||||
|
||||
#偷窃状态
|
||||
stealingStatus: list[str] = []
|
||||
#偷窃数量
|
||||
stealingNumber = 0
|
||||
if currentTime >= matureTime:
|
||||
if soilInfo[4]:
|
||||
#先获取用户是否偷过该土地
|
||||
stealingStatus = soilInfo[4].split('|')
|
||||
|
||||
for isUser in stealingStatus:
|
||||
user = isUser.split('-')
|
||||
|
||||
if user[0] == uid:
|
||||
isStealing = True
|
||||
isStealingNumber += 1
|
||||
break
|
||||
|
||||
stealingNumber -= int(user[1])
|
||||
|
||||
#如果偷过,则跳过该土地
|
||||
if isStealing:
|
||||
continue
|
||||
|
||||
stealingNumber += plantInfo['harvest']
|
||||
randomNumber = random.choice([1, 2])
|
||||
randomNumber = min(randomNumber, stealingNumber)
|
||||
|
||||
logger.info(f"{randomNumber}")
|
||||
|
||||
if randomNumber > 0:
|
||||
await g_pSqlManager.addUserPlantByPlant(uid, plantId, randomNumber)
|
||||
|
||||
harvestRecords.append(f"成功偷到作物:{plantId},数量为:{randomNumber}")
|
||||
stealingStatus.append(f"{uid}-{randomNumber}")
|
||||
|
||||
isStealingPlant += 1
|
||||
|
||||
#如果将作物偷完,就直接更新状态 并记录用户偷取过
|
||||
if plantInfo['harvest'] - randomNumber + stealingNumber == 0:
|
||||
sql = f"UPDATE soil SET {soilName} = ',,,4,{'|'.join(stealingStatus)}' WHERE uid = '{target}'"
|
||||
else:
|
||||
sql = f"UPDATE soil SET {soilName} = '{soilInfo[0]},{soilInfo[1]},{soilInfo[2]},{soilInfo[3]},{'|'.join(stealingStatus)}' WHERE uid = '{target}'"
|
||||
|
||||
await g_pSqlManager.executeDB(sql)
|
||||
|
||||
if isStealingPlant <= 0 and isStealingNumber <= 0:
|
||||
return "目标没有作物可以被偷"
|
||||
elif isStealingPlant <= 0 and isStealingNumber > 0:
|
||||
return "你已经偷过目标啦,请手下留情"
|
||||
else:
|
||||
userStealing[1] = int(userStealing[1]) - 1
|
||||
# 转换所有元素为字符串
|
||||
userStealing_str = [str(item) for item in userStealing]
|
||||
|
||||
sql = f"UPDATE user SET stealing = '{userStealing[0]}|{userStealing[1]}' WHERE uid = {uid}"
|
||||
|
||||
#更新用户每日偷取次数
|
||||
await g_pSqlManager.executeDB(sql)
|
||||
|
||||
return "\n".join(harvestRecords)
|
||||
|
||||
@classmethod
|
||||
async def reclamationCondition(cls, uid: str) -> str:
|
||||
userInfo = await g_pSqlManager.getUserInfoByUid(uid)
|
||||
rec = g_pJsonManager.m_pLevel["reclamation"]
|
||||
|
||||
try:
|
||||
if userInfo['soil'] >= 30:
|
||||
return "你已经开垦了全部土地"
|
||||
|
||||
rec = rec[f"{userInfo['soil'] + 1}"]
|
||||
|
||||
level = rec['level']
|
||||
point = rec['point']
|
||||
item = rec['item']
|
||||
|
||||
str = ""
|
||||
if len(item) == 0:
|
||||
str = f"下次升级所需条件:等级:{level},农场币:{point}"
|
||||
else:
|
||||
str = f"下次升级所需条件:等级:{level},农场币:{point},物品:{item}"
|
||||
|
||||
return str
|
||||
except Exception as e:
|
||||
return "获取升级土地条件失败!"
|
||||
|
||||
@classmethod
|
||||
async def reclamation(cls, uid: str) -> str:
|
||||
userInfo = await g_pSqlManager.getUserInfoByUid(uid)
|
||||
level = await g_pSqlManager.getUserLevelByUid(uid)
|
||||
|
||||
rec = g_pJsonManager.m_pLevel["reclamation"]
|
||||
|
||||
try:
|
||||
if userInfo['soil'] >= 30:
|
||||
return "你已经开垦了全部土地"
|
||||
|
||||
rec = rec[f"{userInfo['soil'] + 1}"]
|
||||
|
||||
levelFileter = rec['level']
|
||||
point = rec['point']
|
||||
item = rec['item']
|
||||
|
||||
logger.info(f"{level[0]}")
|
||||
|
||||
if level[0] < levelFileter:
|
||||
return f"当前用户等级{level[0]},升级所需等级为{levelFileter}"
|
||||
|
||||
if userInfo['point'] < point:
|
||||
return f"当前用户农场币不足,升级所需农场币为{point}"
|
||||
|
||||
#TODO 缺少判断需要的Item
|
||||
sql = f"UPDATE user SET point = '{userInfo['point'] - point}', soil = '{userInfo['soil'] + 1}' WHERE uid = {uid}"
|
||||
|
||||
await g_pSqlManager.executeDB(sql)
|
||||
|
||||
return "开垦土地成功!"
|
||||
except Exception as e:
|
||||
return "执行开垦失败!"
|
||||
|
||||
g_pFarmManager = CFarmManager()
|
||||
@@ -1,209 +0,0 @@
|
||||
import math
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
|
||||
from ..config import g_pJsonManager, g_sResourcePath
|
||||
from ..database import g_pSqlManager
|
||||
|
||||
|
||||
class CShopManager:
|
||||
|
||||
@classmethod
|
||||
async def getSeedShopImage(cls, num: int = 1) -> bytes:
|
||||
"""获取商店页面
|
||||
|
||||
Returns:
|
||||
bytes: 返回商店图片bytes
|
||||
"""
|
||||
|
||||
data_list = []
|
||||
column_name = [
|
||||
"-",
|
||||
"种子名称",
|
||||
"解锁等级",
|
||||
"种子单价",
|
||||
"收获经验",
|
||||
"收获数量",
|
||||
"成熟时间(小时)",
|
||||
"收获次数",
|
||||
"再次成熟时间(小时)",
|
||||
"是否可以上架交易行"
|
||||
]
|
||||
|
||||
sell = ""
|
||||
plants = list(g_pJsonManager.m_pPlant['plant'].items())
|
||||
start = (num - 1) * 15
|
||||
maxItems = min(len(plants) - start, 15)
|
||||
items = plants[start:start + maxItems]
|
||||
|
||||
for key, plant in items:
|
||||
icon = ""
|
||||
icon_path = g_sResourcePath / f"plant/{key}/icon.png"
|
||||
if icon_path.exists():
|
||||
icon = (icon_path, 33, 33)
|
||||
|
||||
if plant['again'] == True:
|
||||
sell = "可以"
|
||||
else:
|
||||
sell = "不可以"
|
||||
|
||||
data_list.append(
|
||||
[
|
||||
icon,
|
||||
key,
|
||||
plant['level'],
|
||||
plant['price'],
|
||||
plant['experience'],
|
||||
plant['harvest'],
|
||||
plant['time'],
|
||||
plant['crop'],
|
||||
plant['again'],
|
||||
sell
|
||||
]
|
||||
)
|
||||
|
||||
count = math.ceil(len(g_pJsonManager.m_pPlant['plant']) / 15)
|
||||
title = f"种子商店 页数: {num}/{count}"
|
||||
|
||||
result = await ImageTemplate.table_page(
|
||||
title,
|
||||
"购买示例:@小真寻 购买种子 大白菜 5",
|
||||
column_name,
|
||||
data_list,
|
||||
)
|
||||
|
||||
return result.pic2bytes()
|
||||
|
||||
@classmethod
|
||||
async def buySeed(cls, uid: str, name: str, num: int = 1) -> str:
|
||||
"""购买种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 植物名称
|
||||
num (int, optional): 购买数量
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
|
||||
if num <= 0:
|
||||
return "请输入购买数量!"
|
||||
|
||||
plantInfo = None
|
||||
|
||||
try:
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][name]
|
||||
except Exception as e:
|
||||
return "购买出错!请检查需购买的种子名称!"
|
||||
|
||||
level = await g_pSqlManager.getUserLevelByUid(uid)
|
||||
|
||||
if level[0] < int(plantInfo['level']):
|
||||
return "你的等级不够哦,努努力吧"
|
||||
|
||||
point = await g_pSqlManager.getUserPointByUid(uid)
|
||||
total = int(plantInfo['price']) * num
|
||||
|
||||
logger.debug(f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}")
|
||||
|
||||
if point < total:
|
||||
return "你的农场币不够哦~ 快速速氪金吧!"
|
||||
else:
|
||||
await g_pSqlManager.updateUserPointByUid(uid, point - total)
|
||||
|
||||
if await g_pSqlManager.addUserSeedByPlant(uid, name, num) == False:
|
||||
return "购买失败,执行数据库错误!"
|
||||
|
||||
return f"成功购买{name},花费{total}农场币, 剩余{point - total}农场币"
|
||||
|
||||
@classmethod
|
||||
async def sellPlantByUid(cls, uid: str, name: str = "", num: int = 1) -> str:
|
||||
"""出售作物
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
|
||||
plant = await g_pSqlManager.getUserPlantByUid(uid)
|
||||
|
||||
if plant == None:
|
||||
return "你仓库没有可以出售的作物"
|
||||
|
||||
point = 0
|
||||
totalSold = 0
|
||||
remainingItems = []
|
||||
|
||||
isAll = False
|
||||
if num == -1:
|
||||
isAll = True
|
||||
|
||||
items = plant.split(',')
|
||||
if len(name) <= 0:
|
||||
#出售全部
|
||||
for item in items:
|
||||
if '|' in item:
|
||||
plant_name, count_str = item.split('|', 1)
|
||||
try:
|
||||
count = int(count_str)
|
||||
plant_info = g_pJsonManager.m_pPlant['plant'][plant_name]
|
||||
point += plant_info['price'] * count
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
await g_pSqlManager.updateUserPlantByUid(uid, "") # 清空仓库
|
||||
else:
|
||||
for item in items:
|
||||
if '|' in item:
|
||||
plantName, countStr = item.split('|', 1)
|
||||
try:
|
||||
count = int(countStr)
|
||||
if plantName == name:
|
||||
|
||||
if isAll:
|
||||
sellAmount = count
|
||||
else:
|
||||
sellAmount = min(num, count)
|
||||
|
||||
totalSold += sellAmount
|
||||
remaining = count - sellAmount
|
||||
|
||||
if remaining > 0:
|
||||
remainingItems.append(f"{plantName}|{remaining}")
|
||||
|
||||
if isAll == False:
|
||||
num -= sellAmount
|
||||
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
if num > 0 and isAll == False:
|
||||
return f"出售作物{name}出错:数量不足"
|
||||
|
||||
#计算收益
|
||||
try:
|
||||
plantInfo = g_pJsonManager.m_pPlant['plant'][name]
|
||||
totalPoint = plantInfo['price'] * totalSold
|
||||
except KeyError:
|
||||
return f"出售作物{name}出错:作物不存在"
|
||||
|
||||
#更新剩余作物
|
||||
remainingPlant = ','.join(remainingItems) if remainingItems else ""
|
||||
await g_pSqlManager.updateUserPlantByUid(uid, remainingPlant)
|
||||
|
||||
#更新农场币
|
||||
p = await g_pSqlManager.getUserPointByUid(uid)
|
||||
await g_pSqlManager.updateUserPointByUid(uid, p + totalPoint)
|
||||
|
||||
if name:
|
||||
return f"成功出售{name},获得农场币:{totalPoint}"
|
||||
else:
|
||||
return f"成功出售所有作物,获得农场币:{totalPoint}"
|
||||
|
||||
g_pShopManager = CShopManager()
|
||||
@@ -0,0 +1,71 @@
|
||||
# 真寻农场更新日志
|
||||
|
||||
## V1.5
|
||||
用户方面
|
||||
---
|
||||
- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
|
||||
- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
|
||||
- 定时更新签到文件、作物资源从00:30调整至04:30
|
||||
- 修正了部分土地资源错误的情况
|
||||
- 修正了部分文本信息错误的情况
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 修正部分事件连接机制
|
||||
- 修正网络请求端口
|
||||
|
||||
## V1.4
|
||||
用户方面
|
||||
---
|
||||
- 新增种子商店筛选功能(如果没有BUG的话后续我的种子、我的作物等也会加入筛选功能
|
||||
- 新增签到功能(测试
|
||||
- 新增农场详述功能,能通过该功能更加详细的观看农场数据
|
||||
- 修复了迁移旧数据库无法正常迁移的BUG
|
||||
- 修复了偷菜会导致偷自己的BUG
|
||||
- 修正了作物阶段绘制不正确的BUG
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 修正了多阶段作物成长素材计算逻辑
|
||||
- 修正了作物数据库字段错乱的问题
|
||||
- 作物新增offset字段,用于以后偏移坐标和大小(尚未启用,该模式有商议
|
||||
|
||||
## V1.3
|
||||
用户方面
|
||||
---
|
||||
- 新增部分作物
|
||||
- 修复一个严重BUG, 该BUG会导致无法重复偷别人菜的问题
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 将作物数据从plant.json迁移至resource/db/plant.db中,并将操作json改为操作数据库
|
||||
|
||||
## V1.2
|
||||
用户方面
|
||||
---
|
||||
- 修复重大BUG,该BUG会导致用户经验等级计算异常
|
||||
- 修正种子价格,现在不会以单价的形式购买种子了,而是新增的种子价格
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 彻底重构数据库,但是不会像V1.1一样导致用户数据丢失
|
||||
- 迁移部分代码结构,使文件名和代码功能更加匹配
|
||||
- 加入事件机制,采用类似Qt信号槽机制
|
||||
|
||||
## V1.1
|
||||
用户方面
|
||||
---
|
||||
- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了
|
||||
- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物
|
||||
- 完善播种逻辑,现在可以空置数量来一键播种指定作物了
|
||||
- 新增更改农场名指令
|
||||
- 改进对土地开垦条件判断
|
||||
|
||||
代码方面
|
||||
---
|
||||
- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。
|
||||
- 更正数据库写法,但是会导致V1.0用户的作物和种子丢失
|
||||
|
||||
## V1.0
|
||||
|
||||
世界的起源。
|
||||
@@ -1,43 +0,0 @@
|
||||
from unittest import result
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class CRequestManager:
|
||||
|
||||
@classmethod
|
||||
async def post(cls, url, json_data=None, form_data=None):
|
||||
"""发送 POST 请求(支持 JSON/Form-Data 格式)"""
|
||||
try:
|
||||
headers = {"Content-Type": "application/json"} if json_data else None
|
||||
response = requests.post(
|
||||
url,
|
||||
json=json_data,
|
||||
data=form_data,
|
||||
headers=headers,
|
||||
timeout=5
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"请求失败: {e}")
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def sign(cls, uid: str) -> str:
|
||||
a = await cls.post("http://diuse.work:9099/testPost", json_data={"level":3})
|
||||
|
||||
result = ""
|
||||
|
||||
type = int(a["type"])
|
||||
if type == 1:
|
||||
result = f"签到成功 type = 1"
|
||||
elif type == 2:
|
||||
result = f"签到成功 type = 2"
|
||||
else:
|
||||
result = f"签到成功 type = {type}"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
g_pRequestManager = CRequestManager()
|
||||
|
After Width: | Height: | Size: 2.7 MiB |
@@ -0,0 +1 @@
|
||||
{"version": 0.41}
|
||||
@@ -0,0 +1,777 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>智能功能展示 - 参数可视化</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "{{ font_family }}", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "{{ font_family }}";
|
||||
src: url("../font/Rounded.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #fff9f9 0%, #f0f9ff 100%);
|
||||
color: #5a5a5a;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #ff85a2;
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
text-shadow: 2px 2px 0px rgba(255, 133, 162, 0.2);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h1::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100px;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #ff85a2, #a2d2ff);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.2rem;
|
||||
color: #888;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
line-height: 1.6;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
padding: 8px 15px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.required-color {
|
||||
background-color: #ff6b9c;
|
||||
}
|
||||
|
||||
.optional-color {
|
||||
background-color: #a2d2ff;
|
||||
}
|
||||
|
||||
.conditional-color {
|
||||
background-color: #b9fbc0;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 30px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.feature-box {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f8f8 100%);
|
||||
border-radius: 25px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 8px 20px rgba(255, 133, 162, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
border: 3px solid transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feature-box:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 25px rgba(255, 133, 162, 0.25);
|
||||
border-color: #ffc2d1;
|
||||
}
|
||||
|
||||
.feature-box::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(90deg, #ff85a2, #a2d2ff);
|
||||
}
|
||||
|
||||
.feature-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
color: #ff6b9c;
|
||||
}
|
||||
|
||||
.parameters-section {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1rem;
|
||||
color: #888;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: "📋";
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.parameters-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.parameter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.parameter-item:hover {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.required-param {
|
||||
background-color: rgba(255, 107, 156, 0.1);
|
||||
border-left: 4px solid #ff6b9c;
|
||||
}
|
||||
|
||||
.optional-param {
|
||||
background-color: rgba(162, 210, 255, 0.1);
|
||||
border-left: 4px solid #a2d2ff;
|
||||
}
|
||||
|
||||
.conditional-param {
|
||||
background-color: rgba(185, 251, 192, 0.1);
|
||||
border-left: 4px solid #b9fbc0;
|
||||
}
|
||||
|
||||
.param-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
margin-right: 12px;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.required-badge {
|
||||
background-color: #ff6b9c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.optional-badge {
|
||||
background-color: #a2d2ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.conditional-badge {
|
||||
background-color: #b9fbc0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.param-description {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.logic-section {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: rgba(255, 213, 165, 0.1);
|
||||
border-radius: 12px;
|
||||
border-left: 4px solid #ffd6a5;
|
||||
}
|
||||
|
||||
.logic-title {
|
||||
font-weight: bold;
|
||||
color: #ff9e6d;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logic-title::before {
|
||||
content: "🔍";
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.logic-content {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.usage-example {
|
||||
margin-top: 15px;
|
||||
padding: 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: #333;
|
||||
border-left: 3px solid #888;
|
||||
}
|
||||
|
||||
.decoration {
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0.1;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.decoration-1 {
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
background-color: #ff85a2;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.decoration-2 {
|
||||
bottom: -20px;
|
||||
right: -20px;
|
||||
background-color: #a2d2ff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
font-size: 0.9rem;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>✨ 真寻农场帮助菜单 ✨</h1>
|
||||
<p class="description">简单介绍一下农场的各个功能和食用方法</p>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-color required-color"></div>
|
||||
<span>必填参数</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color optional-color"></div>
|
||||
<span>可选参数</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color conditional-color"></div>
|
||||
<span>条件参数</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="features-grid">
|
||||
<!-- 必填 + 可选参数 -->
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">at 开通农场</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">条件逻辑</div>
|
||||
<div class="logic-content">
|
||||
• 需要at小真寻<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">我的农场</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">农场详述</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">我的农场币</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 条件参数 -->
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">种子商店</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item conditional-param">
|
||||
<span class="param-badge conditional-badge">条件</span>
|
||||
<span class="param-description">参数 - 根据类型决定含义</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">条件逻辑</div>
|
||||
<div class="logic-content">
|
||||
• 如果参数是中文 → 进入筛选模式,可接页码参数<br>
|
||||
• 如果参数是数字 → 直接作为页码使用<br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usage-example">
|
||||
使用示例1: 种子商店 胡萝<br>
|
||||
使用示例2: 种子商店 2<br>
|
||||
使用示例3: 种子商店 胡萝 3
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 多个参数 -->
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">购买种子</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item required-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">作物/种子名称</span>
|
||||
</div>
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge optional-badge">可选</span>
|
||||
<span class="param-description">数量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usage-example">
|
||||
使用示例: 购买种子 胡萝卜
|
||||
使用示例: 购买种子 胡萝卜 5
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">我的种子</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">播种</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item conditional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">作物/种子名称</span>
|
||||
</div>
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge optional-badge">可选</span>
|
||||
<span class="param-description">数量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 数量不填默认将最大可能播种
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usage-example">
|
||||
使用示例1: 种子商店 胡萝<br>
|
||||
使用示例2: 种子商店 2<br>
|
||||
使用示例3: 种子商店 胡萝 3
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">收获</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">铲除</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">我的作物</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">出售作物</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item conditional-param">
|
||||
<span class="param-badge optional-badge">可选</span>
|
||||
<span class="param-description">作物/种子名称</span>
|
||||
</div>
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge optional-badge">可选</span>
|
||||
<span class="param-description">数量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 不填写作物名将售卖仓库种全部作物<br>
|
||||
• 填作物名不填数量将指定作物全部出售
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">偷菜 at</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">条件逻辑</div>
|
||||
<div class="logic-content">
|
||||
• 每人每天只能偷5次
|
||||
• 后续需要at目标且目标开通真寻农场
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">开垦</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">购买农场币</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">数量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 数量为消耗金币的数量
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">更改农场名</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">新的农场名</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 仅支持部分特殊符号
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">农场签到</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">土地升级</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">地块ID</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 地块ID通过农场详述获取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">作物加锁</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">地块ID</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 地块ID通过农场详述获取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">作物解锁</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">地块ID</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 地块ID通过农场详述获取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">我的点券</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">地块ID</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 地块ID通过农场详述获取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
|
||||
<div class="feature-header">
|
||||
<div class="feature-name">点券兑换</div>
|
||||
</div>
|
||||
|
||||
<div class="parameters-section">
|
||||
<div class="section-title">参数列表</div>
|
||||
<div class="parameters-list">
|
||||
<div class="parameter-item optional-param">
|
||||
<span class="param-badge required-badge">必填</span>
|
||||
<span class="param-description">地块ID</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logic-section">
|
||||
<div class="logic-title">操作提示</div>
|
||||
<div class="logic-content">
|
||||
• 地块ID通过农场详述获取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 749 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 841 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |