diff --git a/__init__.py b/__init__.py index d443634..8844eda 100644 --- a/__init__.py +++ b/__init__.py @@ -86,3 +86,5 @@ async def start(): @driver.on_shutdown async def shutdown(): await g_pSqlManager.cleanup() + + await g_pDBService.cleanup() diff --git a/config.py b/config.py index 33445e0..d8be25f 100644 --- a/config.py +++ b/config.py @@ -6,3 +6,4 @@ g_sDBPath = DATA_PATH / "farm_db" g_sDBFilePath = DATA_PATH / "farm_db/farm.db" g_sResourcePath = Path(__file__).resolve().parent / "resource" +g_sPlantPath = g_sResourcePath / "db/plant.db" diff --git a/config/plant.json b/config/plant.json deleted file mode 100644 index 5c5e523..0000000 --- a/config/plant.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "version": 0.01, - "zhuShi": - [ - "level: 解锁等级", - "buy: 购买价格", - "limit: 限制等级 0:普通土地 1:红土地 2:黄土地 3:黑土地", - "experience: 收获经验", - "harvest: 收获数量", - "price: 果实售价", - "time: 成熟时间 小时", - "crop: 作物可以收几次", - "again: 再次成熟时间 单位:小时", - "phase: 阶段 目前为 成熟时间 / 阶段 来显示每阶段图片", - "general: 第一阶段是否为通用阶段素材", - "sell: 是否可以上架交易行" - ], - "plant": - { - "胡萝卜": - { - "level": 0, - "buy": 163, - "limit": 0, - "experience": 18, - "harvest": 17, - "price": 21, - "time": 13, - "crop": 1, - "again": 0, - "phase": 5, - "general": true, - "sell": false - }, - "白萝卜": - { - "level": 0, - "buy": 125, - "limit": 0, - "experience": 15, - "harvest": 16, - "price": 17, - "time": 10, - "crop": 1, - "again": 0, - "phase": 5, - "general": true, - "sell": false - }, - "牧草": - { - "level": 0, - "buy": 80, - "limit": 0, - "experience": 10, - "harvest": 25, - "price": 6, - "time": 8, - "crop": 1, - "again": 0, - "phase": 5, - "general": true, - "sell": false - }, - "大白菜": - { - "level": 1, - "buy": 168, - "limit": 0, - "experience": 19, - "harvest": 17, - "price": 22, - "time": 14, - "crop": 1, - "again": 0, - "phase": 5, - "general": true, - "sell": false - }, - "大蒜": - { - "level": 1, - "buy": 169, - "limit": 0, - "experience": 19, - "harvest": 17, - "price": 22, - "time": 14, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "水稻": - { - "level": 2, - "buy": 168, - "limit": 0, - "experience": 19, - "harvest": 18, - "price": 21, - "time": 14, - "crop": 1, - "again": 0, - "phase": 5, - "general": false, - "sell": false - }, - "小麦": - { - "level": 2, - "buy": 168, - "limit": 0, - "experience": 19, - "harvest": 18, - "price": 21, - "time": 14, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "玉米": - { - "level": 3, - "buy": 175, - "limit": 0, - "experience": 19, - "harvest": 17, - "price": 23, - "time": 14, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "油菜": - { - "level": 4, - "buy": 194, - "limit": 0, - "experience": 29, - "harvest": 23, - "price": 24, - "time": 17, - "crop": 1, - "again": 0, - "phase": 5, - "general": true, - "sell": false - }, - "生菜": - { - "level": 4, - "buy": 195, - "limit": 0, - "experience": 25, - "harvest": 21, - "price": 24, - "time": 19, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "小白菜": - { - "level": 4, - "buy": 195, - "limit": 0, - "experience": 15, - "harvest": 18, - "price": 21, - "time": 11, - "crop": 1, - "again": 0, - "phase": 5, - "general": true, - "sell": false - }, - "红枣": - { - "level": 5, - "buy": 237, - "limit": 0, - "experience": 21, - "harvest": 20, - "price": 25, - "time": 16, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "茄子": - { - "level": 5, - "buy": 237, - "limit": 0, - "experience": 21, - "harvest": 20, - "price": 25, - "time": 16, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "番茄": - { - "level": 6, - "buy": 251, - "limit": 0, - "experience": 22, - "harvest": 21, - "price": 26, - "time": 17, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "ATTomato": - { - "level": 6, - "buy": 99999, - "limit": 0, - "experience": 22, - "harvest": 21, - "price": 7000, - "time": 17, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "红玫瑰": - { - "level": 7, - "buy": 251, - "limit": 0, - "experience": 23, - "harvest": 22, - "price": 27, - "time": 18, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - }, - "豌豆": - { - "level": 7, - "buy": 266, - "limit": 0, - "experience": 23, - "harvest": 22, - "price": 27, - "time": 18, - "crop": 1, - "again": 0, - "phase": 6, - "general": true, - "sell": false - } - } -} diff --git a/config/sign_in.json b/config/sign_in.json index 4b4e999..db5f229 100644 --- a/config/sign_in.json +++ b/config/sign_in.json @@ -1,4 +1,31 @@ { "date": "202505", - "" + "point_max": 5000, + "point_min": 200, + "continuou": + { + "1": + { + "point": 5000, + "exp": 20 + }, + "3": + { + "point": 7000, + "exp": 25, + "plant": + { + "胡萝卜": 3 + } + }, + "5": + { + "point": 9000, + "exp": 50, + "plant": + { + "胡萝卜": 3 + } + } + } } diff --git a/database/database.py b/database/database.py index f0cf4dd..5ae1305 100644 --- a/database/database.py +++ b/database/database.py @@ -12,21 +12,26 @@ from ..config import g_sDBFilePath, g_sDBPath class CSqlManager: def __init__(self): - g_sDBPath.mkdir(parents=True, exist_ok=True) + try: + os.mkdir(g_sDBFilePath) + except FileExistsError: + pass @classmethod async def cleanup(cls): - if cls.m_pDB: + if hasattr(cls, "m_pDB") and 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) - cls.m_pDB.row_factory = aiosqlite.Row - - return True + try: + _ = os.path.exists(g_sDBFilePath) + cls.m_pDB = await aiosqlite.connect(str(g_sDBFilePath)) + cls.m_pDB.row_factory = aiosqlite.Row + return True + except Exception as e: + logger.warning("初始化总数据库失败", e=e) + return False @classmethod @asynccontextmanager @@ -115,7 +120,7 @@ class CSqlManager: Returns: bool: 是否执行成功 """ - if len(command) <= 0: + if not command: logger.warning("数据库语句长度为空!") return False @@ -124,7 +129,7 @@ class CSqlManager: await cls.m_pDB.execute(command) return True except Exception as e: - logger.warning("数据库语句执行出错:" + command, e=e) + logger.warning(f"数据库语句执行出错: {command}", e=e) return False g_pSqlManager = CSqlManager() diff --git a/database/plant.py b/database/plant.py new file mode 100644 index 0000000..b8ff5a0 --- /dev/null +++ b/database/plant.py @@ -0,0 +1,143 @@ +import os +import re +from contextlib import asynccontextmanager + +import aiosqlite + +from zhenxun.services.log import logger + +from ..config import g_sPlantPath + + +class CPlantManager: + def __init__(self): + try: + os.mkdir(g_sPlantPath) + except FileExistsError: + pass + + @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: + _ = os.path.exists(g_sPlantPath) + cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath)) + cls.m_pDB.row_factory = aiosqlite.Row + return True + except Exception as e: + logger.warning("初始化植物数据库失败", 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 executeDB(cls, command: str) -> bool: + """执行自定义SQL + + Args: + command (str): SQL语句 + + Returns: + bool: 是否执行成功 + """ + if not command: + logger.warning("数据库语句长度为空!") + return False + + try: + async with cls._transaction(): + await cls.m_pDB.execute(command) + return True + except Exception as e: + logger.warning(f"数据库语句执行出错: {command}", e=e) + return False + + + @classmethod + async def getPlantByName(cls, name: str) -> dict | None: + """根据作物名称查询记录 + + Args: + name (str): 作物名称 + + Returns: + dict | None: 返回记录字典,未找到返回None + """ + try: + async with cls.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 + + @classmethod + async def existsPlant(cls, name: str) -> bool: + """判断作物是否存在 + + Args: + name (str): 作物名称 + + Returns: + bool: 存在返回True,否则False + """ + try: + async with cls.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 + + @classmethod + async def countPlants(cls, 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 cls.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 + + @classmethod + async def listPlants(cls) -> list[dict]: + """查询所有作物记录""" + try: + async with cls.m_pDB.execute("SELECT * FROM plant") as cursor: + rows = await cursor.fetchall() + return [dict(r) for r in rows] + except Exception as e: + logger.warning("查询所有作物失败", e=e) + return [] diff --git a/database/userSoil.py b/database/userSoil.py index d8c9c7c..bbad63a 100644 --- a/database/userSoil.py +++ b/database/userSoil.py @@ -295,7 +295,7 @@ class CUserSoilDB(CSqlManager): return False # 获取植物配置 - plantCfg = g_pJsonManager.m_pPlant.get("plant", {}).get(plantName) + plantCfg = await g_pDBService.plant.getPlantByName(plantName) if not plantCfg: logger.error(f"未知植物: {plantName}") return False diff --git a/dbService.py b/dbService.py index 79d3480..fc0fdbf 100644 --- a/dbService.py +++ b/dbService.py @@ -4,6 +4,7 @@ from typing import Optional class CDBService: @classmethod async def init(cls): + from .database.plant import CPlantManager from .database.user import CUserDB from .database.userItem import CUserItemDB from .database.userPlant import CUserPlantDB @@ -11,6 +12,9 @@ class CDBService: from .database.userSoil import CUserSoilDB from .database.userSteal import CUserStealDB + cls.plant = CPlantManager() + await cls.plant.init() + cls.user = CUserDB() await cls.user.initDB() @@ -32,4 +36,8 @@ class CDBService: #迁移旧数据库 await cls.userSoil.migrateOldFarmData() + @classmethod + async def cleanup(cls): + await cls.plant.cleanup() + g_pDBService = CDBService() diff --git a/farm/farm.py b/farm/farm.py index 1219988..86997b5 100644 --- a/farm/farm.py +++ b/farm/farm.py @@ -195,7 +195,10 @@ class CFarmManager: return True, plant, False #获取作物详细信息 - plantInfo = g_pJsonManager.m_pPlant['plant'][soilInfo['plantName']] + plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName']) + if not plantInfo: + logger.error(f"绘制植物资源失败: {soilInfo['plantName']}") + return False, None, False #type: ignore currentTime = datetime.now() matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) @@ -257,7 +260,10 @@ class CFarmManager: for seedName, count in seedRecords.items(): try: - plantInfo = g_pJsonManager.m_pPlant['plant'][seedName] + plantInfo = await g_pDBService.plant.getPlantByName(seedName) + if not plantInfo: + continue + iconPath = g_sResourcePath / f"plant/{seedName}/icon.png" icon = (iconPath, 33, 33) if iconPath.exists() else "" sellable = "可以" if plantInfo['again'] else "不可以" @@ -382,7 +388,10 @@ class CFarmManager: if soilInfo.get("wiltStatus", 1) == 1: continue - plantInfo = g_pJsonManager.m_pPlant.get("plant", {}).get(soilInfo['plantName']) + plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName']) + if not plantInfo: + continue + currentTime = datetime.now() matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) @@ -497,7 +506,10 @@ class CFarmManager: sell = "" for name, count in plant.items(): - plantInfo = g_pJsonManager.m_pPlant['plant'][name] + plantInfo = await g_pDBService.plant.getPlantByName(name) + if not plantInfo: + continue + icon = "" icon_path = g_sResourcePath / f"plant/{name}/icon.png" if icon_path.exists(): @@ -577,7 +589,10 @@ class CFarmManager: continue #作物信息 - plantInfo = g_pJsonManager.m_pPlant.get("plant", {}).get(soilInfo['plantName']) + plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName']) + if not plantInfo: + continue + currentTime = datetime.now() matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) diff --git a/farm/shop.py b/farm/shop.py index 3357bd7..ae240f6 100644 --- a/farm/shop.py +++ b/farm/shop.py @@ -1,4 +1,6 @@ import math +import plistlib +from itertools import islice from zhenxun.services.log import logger from zhenxun.utils._build_image import BuildImage @@ -35,14 +37,18 @@ class CShopManager: ] sell = "" - plants = list(g_pJsonManager.m_pPlant['plant'].items()) - start = (num - 1) * 15 - maxItems = min(len(plants) - start, 15) - items = plants[start:start + maxItems] + plants = await g_pDBService.plant.listPlants() + plantSize = await g_pDBService.plant.countPlants(True) + + start = (num - 1) * 15 + items = islice(plants, start, start + 15) + + for plant in items: + if plant['isBuy'] == 0: + continue - for key, plant in items: icon = "" - icon_path = g_sResourcePath / f"plant/{key}/icon.png" + icon_path = g_sResourcePath / f"plant/{plant['name']}/icon.png" if icon_path.exists(): icon = (icon_path, 33, 33) @@ -54,7 +60,7 @@ class CShopManager: data_list.append( [ icon, - key, + plant['name'], plant['buy'], plant['level'], plant['price'], @@ -67,7 +73,7 @@ class CShopManager: ] ) - count = math.ceil(len(g_pJsonManager.m_pPlant['plant']) / 15) + count = math.ceil(plantSize / 15) title = f"种子商店 页数: {num}/{count}" result = await ImageTemplate.table_page( @@ -95,11 +101,8 @@ class CShopManager: if num <= 0: return "请输入购买数量!" - plantInfo = None - - try: - plantInfo = g_pJsonManager.m_pPlant['plant'][name] - except Exception as e: + plantInfo = await g_pDBService.plant.getPlantByName(name) + if not plantInfo: return "购买出错!请检查需购买的种子名称!" level = await g_pDBService.user.getUserLevelByUid(uid) @@ -145,7 +148,10 @@ class CShopManager: if name == "": for plantName, count in plant.items(): - plantInfo = g_pJsonManager.m_pPlant['plant'][plantName] + plantInfo = await g_pDBService.plant.getPlantByName(plantName) + if not plantInfo: + continue + point += plantInfo['price'] * count await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0) else: @@ -158,7 +164,17 @@ class CShopManager: await g_pDBService.userPlant.updateUserPlantByName(uid, name, available - sellAmount) totalSold = sellAmount - totalPoint = point if name == "" else totalSold * g_pJsonManager.m_pPlant['plant'][name]['price'] + if name == "": + totalPoint = point + else: + plantInfo = await g_pDBService.plant.getPlantByName(name) + if not plantInfo: + price = 0 + else: + price = plantInfo['price'] + + totalPoint = totalSold * price + currentPoint = await g_pDBService.user.getUserPointByUid(uid) await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint) diff --git a/json.py b/json.py index 6fde8df..422dcc0 100644 --- a/json.py +++ b/json.py @@ -7,7 +7,6 @@ from zhenxun.services.log import logger class CJsonManager: def __init__(self): self.m_pItem = {} - self.m_pPlant = {} self.m_pLevel = {} self.m_pSoil = {} @@ -15,9 +14,6 @@ class CJsonManager: if not await self.initItem(): return False - if not await self.initPlant(): - return False - if not await self.initLevel(): return False @@ -44,24 +40,6 @@ class CJsonManager: logger.warning(f"item.json JSON格式错误: {e}") return False - async def initPlant(self) -> bool: - current_file_path = Path(__file__) - - try: - with open( - current_file_path.resolve().parent / "config/plant.json", - encoding="utf-8", - ) as file: - self.m_pPlant = json.load(file) - - return True - except FileNotFoundError: - logger.warning("plant.json 打开失败") - return False - except json.JSONDecodeError as e: - logger.warning(f"plant.json JSON格式错误: {e}") - return False - async def initLevel(self) -> bool: current_file_path = Path(__file__) diff --git a/resource/db/plant.db b/resource/db/plant.db new file mode 100644 index 0000000..fad7821 Binary files /dev/null and b/resource/db/plant.db differ diff --git a/resource/plant/土豆/1.png b/resource/plant/土豆/1.png new file mode 100644 index 0000000..9a24c8c Binary files /dev/null and b/resource/plant/土豆/1.png differ diff --git a/resource/plant/土豆/2.png b/resource/plant/土豆/2.png new file mode 100644 index 0000000..587867c Binary files /dev/null and b/resource/plant/土豆/2.png differ diff --git a/resource/plant/土豆/3.png b/resource/plant/土豆/3.png new file mode 100644 index 0000000..867b55c Binary files /dev/null and b/resource/plant/土豆/3.png differ diff --git a/resource/plant/土豆/4.png b/resource/plant/土豆/4.png new file mode 100644 index 0000000..e3e4ed7 Binary files /dev/null and b/resource/plant/土豆/4.png differ diff --git a/resource/plant/土豆/5.png b/resource/plant/土豆/5.png new file mode 100644 index 0000000..a51a9f7 Binary files /dev/null and b/resource/plant/土豆/5.png differ diff --git a/resource/plant/土豆/icon.png b/resource/plant/土豆/icon.png new file mode 100644 index 0000000..40bfbae Binary files /dev/null and b/resource/plant/土豆/icon.png differ diff --git a/resource/plant/花菜/1.png b/resource/plant/花菜/1.png new file mode 100644 index 0000000..f63101d Binary files /dev/null and b/resource/plant/花菜/1.png differ diff --git a/resource/plant/花菜/2.png b/resource/plant/花菜/2.png new file mode 100644 index 0000000..715edd4 Binary files /dev/null and b/resource/plant/花菜/2.png differ diff --git a/resource/plant/花菜/3.png b/resource/plant/花菜/3.png new file mode 100644 index 0000000..2384e28 Binary files /dev/null and b/resource/plant/花菜/3.png differ diff --git a/resource/plant/花菜/4.png b/resource/plant/花菜/4.png new file mode 100644 index 0000000..0f459ca Binary files /dev/null and b/resource/plant/花菜/4.png differ diff --git a/resource/plant/花菜/5.png b/resource/plant/花菜/5.png new file mode 100644 index 0000000..5de7ea4 Binary files /dev/null and b/resource/plant/花菜/5.png differ diff --git a/resource/plant/花菜/icon.png b/resource/plant/花菜/icon.png new file mode 100644 index 0000000..a7a4b92 Binary files /dev/null and b/resource/plant/花菜/icon.png differ diff --git a/resource/plant/莲藕/1.png b/resource/plant/莲藕/1.png new file mode 100644 index 0000000..c318b07 Binary files /dev/null and b/resource/plant/莲藕/1.png differ diff --git a/resource/plant/莲藕/2.png b/resource/plant/莲藕/2.png new file mode 100644 index 0000000..d9ca2b5 Binary files /dev/null and b/resource/plant/莲藕/2.png differ diff --git a/resource/plant/莲藕/3.png b/resource/plant/莲藕/3.png new file mode 100644 index 0000000..7ab1121 Binary files /dev/null and b/resource/plant/莲藕/3.png differ diff --git a/resource/plant/莲藕/4.png b/resource/plant/莲藕/4.png new file mode 100644 index 0000000..51d3179 Binary files /dev/null and b/resource/plant/莲藕/4.png differ diff --git a/resource/plant/莲藕/5.png b/resource/plant/莲藕/5.png new file mode 100644 index 0000000..f7280f8 Binary files /dev/null and b/resource/plant/莲藕/5.png differ diff --git a/resource/plant/莲藕/icon.png b/resource/plant/莲藕/icon.png new file mode 100644 index 0000000..9853d7b Binary files /dev/null and b/resource/plant/莲藕/icon.png differ diff --git a/resource/plant/韭菜/1.png b/resource/plant/韭菜/1.png new file mode 100644 index 0000000..9469c84 Binary files /dev/null and b/resource/plant/韭菜/1.png differ diff --git a/resource/plant/韭菜/2.png b/resource/plant/韭菜/2.png new file mode 100644 index 0000000..7377528 Binary files /dev/null and b/resource/plant/韭菜/2.png differ diff --git a/resource/plant/韭菜/3.png b/resource/plant/韭菜/3.png new file mode 100644 index 0000000..ee2588e Binary files /dev/null and b/resource/plant/韭菜/3.png differ diff --git a/resource/plant/韭菜/4.png b/resource/plant/韭菜/4.png new file mode 100644 index 0000000..a7c24b0 Binary files /dev/null and b/resource/plant/韭菜/4.png differ diff --git a/resource/plant/韭菜/5.png b/resource/plant/韭菜/5.png new file mode 100644 index 0000000..67b0c4f Binary files /dev/null and b/resource/plant/韭菜/5.png differ diff --git a/resource/plant/韭菜/icon.png b/resource/plant/韭菜/icon.png new file mode 100644 index 0000000..8f73979 Binary files /dev/null and b/resource/plant/韭菜/icon.png differ