🎨 新增Player类 更改项目文件结构
This commit is contained in:
parent
7bda14ea81
commit
00948cffb1
23
__init__.py
23
__init__.py
@ -7,14 +7,15 @@ from zhenxun.services.log import logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
from .command import diuse_farm, diuse_register, reclamation
|
||||
from .database.database import g_pSqlManager
|
||||
from .dbService import g_pDBService
|
||||
from .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 .farm.farm import g_pFarmManager
|
||||
from .farm.help import g_pHelpManager
|
||||
from .farm.shop import g_pShopManager
|
||||
from .json import g_pJsonManager
|
||||
from .request import g_pRequestManager
|
||||
from .utils.json import g_pJsonManager
|
||||
from .utils.request import g_pRequestManager
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="真寻农场",
|
||||
@ -86,12 +87,13 @@ driver = get_driver()
|
||||
# 构造函数
|
||||
@driver.on_startup
|
||||
async def start():
|
||||
# 初始化数据库
|
||||
# 数据库加载
|
||||
await g_pSqlManager.init()
|
||||
|
||||
# 初始化读取Json
|
||||
await g_pJsonManager.init()
|
||||
|
||||
# 初始化数据库变量 和 加载作物数据库
|
||||
await g_pDBService.init()
|
||||
|
||||
# 检查作物文件是否缺失 or 更新
|
||||
@ -103,10 +105,11 @@ async def start():
|
||||
# 析构函数
|
||||
@driver.on_shutdown
|
||||
async def shutdown():
|
||||
await g_pSqlManager.cleanup()
|
||||
|
||||
# 单独卸载 作物数据库
|
||||
await g_pDBService.cleanup()
|
||||
|
||||
await g_pSqlManager.cleanup()
|
||||
|
||||
|
||||
@scheduler.scheduled_job(trigger="cron", hour=4, minute=30, id="signInFile")
|
||||
async def signInFile():
|
||||
|
||||
149
command.py
149
command.py
@ -24,12 +24,12 @@ from zhenxun.services.log import logger
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
from .config import g_bSignStatus, g_sTranslation
|
||||
from .dbService import g_pDBService
|
||||
from .farm.farm import g_pFarmManager
|
||||
from .farm.shop import g_pShopManager
|
||||
from .json import g_pJsonManager
|
||||
from .tool import g_pToolManager
|
||||
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("开通农场"),
|
||||
@ -43,22 +43,19 @@ diuse_register = on_alconna(
|
||||
@diuse_register.handle()
|
||||
async def handle_register(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
user = await g_pDBService.user.getUserInfoByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if user:
|
||||
await MessageUtils.build_message(g_sTranslation["register"]["repeat"]).send(
|
||||
reply_to=True
|
||||
)
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
try:
|
||||
raw_name = str(session.user.name)
|
||||
safe_name = g_pToolManager.sanitize_username(raw_name)
|
||||
|
||||
# 初始化用户信息
|
||||
success = await g_pDBService.user.initUserInfoByUid(
|
||||
uid=uid, name=safe_name, exp=0, point=500
|
||||
)
|
||||
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)
|
||||
@ -109,8 +106,10 @@ diuse_farm = on_alconna(
|
||||
@diuse_farm.assign("$main")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
image = await g_pFarmManager.drawFarmByUid(uid)
|
||||
@ -128,8 +127,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("detail")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
info = await g_pFarmManager.drawDetailFarmByUid(uid)
|
||||
@ -150,7 +151,13 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("my-point")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
point = await g_pDBService.user.getUserPointByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
point = player.user["point"]
|
||||
|
||||
if point < 0:
|
||||
await MessageUtils.build_message(g_sTranslation["basic"]["notFarm"]).send()
|
||||
@ -172,8 +179,10 @@ diuse_farm.shortcut(
|
||||
@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 not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
if res.result is inspect._empty:
|
||||
@ -225,8 +234,10 @@ async def _(
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pShopManager.buySeed(uid, name.result, num.result)
|
||||
@ -244,8 +255,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("my-seed")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.getUserSeedByUid(uid)
|
||||
@ -270,8 +283,10 @@ async def _(
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.sowing(uid, name.result, num.result)
|
||||
@ -289,8 +304,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("harvest")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.harvest(uid)
|
||||
@ -308,8 +325,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("eradicate")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.eradicate(uid)
|
||||
@ -327,8 +346,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("my-plant")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.getUserPlantByUid(uid)
|
||||
@ -346,8 +367,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("lock-plant")
|
||||
async def _(session: Uninfo, name: Match[str]):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 1)
|
||||
@ -365,8 +388,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("unlock-plant")
|
||||
async def _(session: Uninfo, name: Match[str]):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 0)
|
||||
@ -386,8 +411,10 @@ async def _(
|
||||
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)
|
||||
):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result)
|
||||
@ -405,8 +432,10 @@ reclamation = on_alconna(
|
||||
@reclamation.handle()
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
condition = await g_pFarmManager.reclamationCondition(uid)
|
||||
@ -441,8 +470,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("stealing")
|
||||
async def _(session: Uninfo, target: Match[At]):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
if not target.available:
|
||||
@ -451,7 +482,7 @@ async def _(session: Uninfo, target: Match[At]):
|
||||
)
|
||||
|
||||
tar = target.result
|
||||
result = await g_pDBService.user.isUserExist(tar.target)
|
||||
result = await g_pDBService.user.isRegistered(tar.target)
|
||||
|
||||
if not result:
|
||||
await MessageUtils.build_message(
|
||||
@ -479,8 +510,10 @@ async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.buyPointByUid(uid, num.result)
|
||||
@ -503,28 +536,23 @@ async def _(session: Uninfo, name: Match[str]):
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
safeName = g_pToolManager.sanitize_username(name.result)
|
||||
|
||||
if safeName == "神秘农夫":
|
||||
await MessageUtils.build_message(g_sTranslation["changeName"]["error"]).send(
|
||||
reply_to=True
|
||||
)
|
||||
return
|
||||
|
||||
result = await g_pDBService.user.updateUserNameByUid(uid, safeName)
|
||||
|
||||
if result:
|
||||
await MessageUtils.build_message(g_sTranslation["changeName"]["success"]).send(
|
||||
reply_to=True
|
||||
)
|
||||
else:
|
||||
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(
|
||||
@ -538,8 +566,10 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("sign-in")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
# 判断签到是否正常加载
|
||||
@ -593,8 +623,6 @@ async def _(session: Uninfo):
|
||||
|
||||
await MessageUtils.build_message(message).send()
|
||||
|
||||
# await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send(reply_to=True)
|
||||
|
||||
|
||||
soil_upgrade = on_alconna(
|
||||
Alconna("土地升级", Args["index", int]),
|
||||
@ -607,8 +635,10 @@ soil_upgrade = on_alconna(
|
||||
@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 not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
condition = await g_pFarmManager.soilUpgradeCondition(uid, index.result)
|
||||
@ -646,8 +676,10 @@ diuse_farm.shortcut(
|
||||
@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 not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
await g_pDBService.userSoil.nextPhase(uid, num.result)
|
||||
@ -669,8 +701,10 @@ async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
|
||||
)
|
||||
|
||||
uid = str(session.user.id)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
result = await g_pFarmManager.pointToVipPointByUid(uid, num.result)
|
||||
@ -688,13 +722,14 @@ diuse_farm.shortcut(
|
||||
@diuse_farm.assign("my-vipPoint")
|
||||
async def _(session: Uninfo):
|
||||
uid = str(session.user.id)
|
||||
vipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
|
||||
if not await g_pToolManager.isRegisteredByUid(uid):
|
||||
if player is None or await player.isRegistered():
|
||||
await g_pToolManager.repeat()
|
||||
return
|
||||
|
||||
await MessageUtils.build_message(
|
||||
g_sTranslation["basic"]["vipPoint"].format(vipPoint=vipPoint)
|
||||
g_sTranslation["basic"]["vipPoint"].format(vipPoint=player.user["vipPoint"])
|
||||
).send(reply_to=True)
|
||||
|
||||
|
||||
|
||||
108
core/activity/activity.py
Normal file
108
core/activity/activity.py
Normal file
@ -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", {})
|
||||
95
core/activity/effect.py
Normal file
95
core/activity/effect.py
Normal file
@ -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}")
|
||||
# 这里会调用任务系统来分配任务
|
||||
399
core/database/database.py
Normal file
399
core/database/database.py
Normal file
@ -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()
|
||||
@ -6,8 +6,8 @@ import aiosqlite
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..config import g_bIsDebug, g_sPlantPath, g_sResourcePath
|
||||
from ..request import g_pRequestManager
|
||||
from ...utils.config import g_bIsDebug, g_sPlantPath, g_sResourcePath
|
||||
from ...utils.request import g_pRequestManager
|
||||
|
||||
|
||||
class CPlantManager:
|
||||
@ -17,43 +17,39 @@ class CPlantManager:
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
async def cleanup(cls):
|
||||
if hasattr(cls, "m_pDB") and cls.m_pDB:
|
||||
await cls.m_pDB.close()
|
||||
async def cleanup(self):
|
||||
if hasattr(self, "m_pDB") and self.m_pDB:
|
||||
await self.m_pDB.close()
|
||||
|
||||
@classmethod
|
||||
async def init(cls) -> bool:
|
||||
async def init(self) -> bool:
|
||||
try:
|
||||
_ = os.path.exists(g_sPlantPath)
|
||||
|
||||
if g_bIsDebug:
|
||||
cls.m_pDB = await aiosqlite.connect(
|
||||
self.m_pDB = await aiosqlite.connect(
|
||||
str(g_sPlantPath.parent / "plant-test.db")
|
||||
)
|
||||
else:
|
||||
cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
|
||||
self.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
|
||||
|
||||
cls.m_pDB.row_factory = aiosqlite.Row
|
||||
self.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;")
|
||||
async def _transaction(self):
|
||||
await self.m_pDB.execute("BEGIN;")
|
||||
try:
|
||||
yield
|
||||
except:
|
||||
await cls.m_pDB.execute("ROLLBACK;")
|
||||
await self.m_pDB.execute("ROLLBACK;")
|
||||
raise
|
||||
else:
|
||||
await cls.m_pDB.execute("COMMIT;")
|
||||
await self.m_pDB.execute("COMMIT;")
|
||||
|
||||
@classmethod
|
||||
async def executeDB(cls, command: str) -> bool:
|
||||
async def executeDB(self, command: str) -> bool:
|
||||
"""执行自定义SQL
|
||||
|
||||
Args:
|
||||
@ -67,15 +63,14 @@ class CPlantManager:
|
||||
return False
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(command)
|
||||
async with self._transaction():
|
||||
await self.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:
|
||||
async def getPlantByName(self, name: str) -> dict | None:
|
||||
"""根据作物名称查询记录
|
||||
|
||||
Args:
|
||||
@ -85,7 +80,7 @@ class CPlantManager:
|
||||
dict | None: 返回记录字典,未找到返回None
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT * FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
@ -94,8 +89,7 @@ class CPlantManager:
|
||||
logger.warning(f"查询作物失败: {name}", e=e)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def getPlantPhaseByName(cls, name: str) -> list[int]:
|
||||
async def getPlantPhaseByName(self, name: str) -> list[int]:
|
||||
"""根据作物名称获取作物各个阶段
|
||||
|
||||
Args:
|
||||
@ -105,7 +99,7 @@ class CPlantManager:
|
||||
list: 阶段数组
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
@ -130,8 +124,7 @@ class CPlantManager:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def getPlantPhaseNumberByName(cls, name: str) -> int:
|
||||
async def getPlantPhaseNumberByName(self, name: str) -> int:
|
||||
"""根据作物名称获取作物总阶段数
|
||||
|
||||
Args:
|
||||
@ -141,7 +134,7 @@ class CPlantManager:
|
||||
int: 总阶段数
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
@ -164,8 +157,7 @@ class CPlantManager:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def getPlantAgainByName(cls, name: str) -> int:
|
||||
async def getPlantAgainByName(self, name: str) -> int:
|
||||
"""根据作物名称获取作物再次成熟时间
|
||||
|
||||
Args:
|
||||
@ -176,7 +168,7 @@ class CPlantManager:
|
||||
"""
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
@ -193,8 +185,7 @@ class CPlantManager:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def existsPlant(cls, name: str) -> bool:
|
||||
async def existsPlant(self, name: str) -> bool:
|
||||
"""判断作物是否存在
|
||||
|
||||
Args:
|
||||
@ -204,7 +195,7 @@ class CPlantManager:
|
||||
bool: 存在返回True,否则False
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT 1 FROM plant WHERE name = ? LIMIT 1", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
@ -213,8 +204,7 @@ class CPlantManager:
|
||||
logger.warning(f"检查作物存在性失败: {name}", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def countPlants(cls, onlyBuy: bool = False) -> int:
|
||||
async def countPlants(self, onlyBuy: bool = False) -> int:
|
||||
"""获取作物总数
|
||||
|
||||
Args:
|
||||
@ -230,18 +220,17 @@ class CPlantManager:
|
||||
else:
|
||||
sql = "SELECT COUNT(*) FROM plant"
|
||||
params: tuple = ()
|
||||
async with cls.m_pDB.execute(sql, params) as cursor:
|
||||
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
|
||||
|
||||
@classmethod
|
||||
async def listPlants(cls) -> list[dict]:
|
||||
async def listPlants(self) -> list[dict]:
|
||||
"""查询所有作物记录"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT * FROM plant ORDER BY level"
|
||||
) as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
@ -250,8 +239,7 @@ class CPlantManager:
|
||||
logger.warning("查询所有作物失败", e=e)
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def downloadPlant(cls) -> bool:
|
||||
async def downloadPlant(self) -> bool:
|
||||
"""遍历所有作物,下载各阶段图片及icon文件到指定文件夹
|
||||
|
||||
Returns:
|
||||
@ -262,10 +250,10 @@ class CPlantManager:
|
||||
|
||||
baseUrl = baseUrl.rstrip("/") + ":8998/file"
|
||||
try:
|
||||
plants = await cls.listPlants()
|
||||
plants = await self.listPlants()
|
||||
for plant in plants:
|
||||
name = plant["name"]
|
||||
phaseCount = await cls.getPlantPhaseNumberByName(name)
|
||||
phaseCount = await self.getPlantPhaseNumberByName(name)
|
||||
saveDir = os.path.join(g_sResourcePath, "plant", name)
|
||||
begin = 0 if plant["general"] == 0 else 1
|
||||
|
||||
215
core/database/user.py
Normal file
215
core/database/user.py
Normal file
@ -0,0 +1,215 @@
|
||||
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 getUserSoilByUid(self, uid: str) -> int:
|
||||
"""获取用户解锁土地数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 解锁土地数量,失败返回-1
|
||||
"""
|
||||
if not uid:
|
||||
return -1
|
||||
|
||||
records = await self.select("user", where={"uid": uid}, columns=["soil"])
|
||||
|
||||
if not records:
|
||||
return -1
|
||||
|
||||
try:
|
||||
soil = int(records[0].get("soil", 3))
|
||||
except Exception:
|
||||
soil = 3
|
||||
|
||||
return soil
|
||||
|
||||
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})
|
||||
@ -5,10 +5,10 @@ import random
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
|
||||
from ..config import g_bIsDebug
|
||||
from ...utils.config import g_bIsDebug
|
||||
from ...utils.json import g_pJsonManager
|
||||
from ...utils.tool import g_pToolManager
|
||||
from ..dbService import g_pDBService
|
||||
from ..json import g_pJsonManager
|
||||
from ..tool import g_pToolManager
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ import math
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..config import g_bIsDebug
|
||||
from ..dbService import g_pDBService
|
||||
from ..tool import g_pToolManager
|
||||
from ..utils.config import g_bIsDebug
|
||||
from ..utils.tool import g_pToolManager
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ class CUserStealDB(CSqlManager):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getStealRecordsByUid(cls, uid: str) -> list:
|
||||
async def getStealRecordsByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取所有偷菜记录
|
||||
|
||||
Args:
|
||||
@ -59,20 +59,16 @@ class CUserStealDB(CSqlManager):
|
||||
'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;',
|
||||
(uid,),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return [
|
||||
{
|
||||
"uid": uid,
|
||||
"soilIndex": row[0],
|
||||
"stealerUid": row[1],
|
||||
"stealCount": row[2],
|
||||
"stealTime": row[3],
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return {}
|
||||
|
||||
result = dict(row)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning("获取偷菜记录失败", e=e)
|
||||
return []
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def getStealRecord(cls, uid: str, soilIndex: int) -> list:
|
||||
43
core/dbService.py
Normal file
43
core/dbService.py
Normal file
@ -0,0 +1,43 @@
|
||||
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()
|
||||
|
||||
# 迁移旧数据库
|
||||
await self.userSoil.migrateOldFarmData()
|
||||
|
||||
async def cleanup(self):
|
||||
await self.plant.cleanup()
|
||||
|
||||
|
||||
g_pDBService = CDBService()
|
||||
@ -9,11 +9,11 @@ from zhenxun.utils.enum import GoldHandle
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from ..config import g_bIsDebug, g_iSoilLevelMax, g_sResourcePath, g_sTranslation
|
||||
from ..dbService import g_pDBService
|
||||
from ..core.dbService import g_pDBService
|
||||
from ..event.event import g_pEventManager
|
||||
from ..json import g_pJsonManager
|
||||
from ..tool import g_pToolManager
|
||||
from ..utils.config import g_bIsDebug, g_iSoilLevelMax, g_sResourcePath, g_sTranslation
|
||||
from ..utils.json import g_pJsonManager
|
||||
from ..utils.tool import g_pToolManager
|
||||
|
||||
|
||||
class CFarmManager:
|
||||
@ -36,20 +36,27 @@ class CFarmManager:
|
||||
return f"你的金币不足或不足承担手续费。当前手续费为{fee}"
|
||||
|
||||
await UserConsole.reduce_gold(
|
||||
uid, num, GoldHandle.PLUGIN, "zhenxun_plugin_farm"
|
||||
uid,
|
||||
num,
|
||||
GoldHandle.PLUGIN, # type: ignore
|
||||
"zhenxun_plugin_farm",
|
||||
)
|
||||
await UserConsole.reduce_gold(
|
||||
uid, fee, GoldHandle.PLUGIN, "zhenxun_plugin_farm"
|
||||
) # type: ignore
|
||||
uid,
|
||||
fee,
|
||||
GoldHandle.PLUGIN, # type: ignore
|
||||
"zhenxun_plugin_farm",
|
||||
)
|
||||
|
||||
point = num * pro
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
|
||||
p = await g_pDBService.user.getUserPointByUid(uid)
|
||||
number = point + p
|
||||
p = player.user.get("point", 0)
|
||||
await player.addPoint("point", point + p)
|
||||
|
||||
await g_pDBService.user.updateUserPointByUid(uid, int(number))
|
||||
|
||||
return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}"
|
||||
return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{point + p}"
|
||||
|
||||
@classmethod
|
||||
async def drawFarmByUid(cls, uid: str) -> bytes:
|
||||
@ -69,9 +76,11 @@ class CFarmManager:
|
||||
await grass.resize(0, soilSize[0], soilSize[1])
|
||||
|
||||
soilPos = g_pJsonManager.m_pSoil["soil"]
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return img.pic2bytes()
|
||||
|
||||
userInfo = await g_pDBService.user.getUserInfoByUid(uid)
|
||||
soilUnlock = int(userInfo["soil"])
|
||||
soilUnlock = int(player.user.get("soil", 3))
|
||||
|
||||
x = 0
|
||||
y = 0
|
||||
@ -168,12 +177,12 @@ class CFarmManager:
|
||||
|
||||
# 用户名
|
||||
nameImg = await BuildImage.build_text_image(
|
||||
userInfo["name"], size=24, font_color=(77, 35, 4)
|
||||
player.user["name"], size=24, font_color=(77, 35, 4)
|
||||
)
|
||||
await img.paste(nameImg, (300, 92))
|
||||
|
||||
# 经验值
|
||||
level = await g_pDBService.user.getUserLevelByUid(uid)
|
||||
level = await player.getUserLevel()
|
||||
|
||||
beginX = 309
|
||||
endX = 627
|
||||
@ -194,7 +203,7 @@ class CFarmManager:
|
||||
|
||||
# 金币
|
||||
pointImg = await BuildImage.build_text_image(
|
||||
str(userInfo["point"]), size=24, font_color=(253, 253, 253)
|
||||
str(player.user["point"]), size=24, font_color=(253, 253, 253)
|
||||
)
|
||||
await img.paste(pointImg, (330, 255))
|
||||
|
||||
@ -220,8 +229,10 @@ class CFarmManager:
|
||||
@classmethod
|
||||
async def drawDetailFarmByUid(cls, uid: str) -> list:
|
||||
info = []
|
||||
|
||||
farm = await cls.drawFarmByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return info
|
||||
|
||||
info.append(BuildImage.open(farm))
|
||||
|
||||
@ -238,7 +249,7 @@ class CFarmManager:
|
||||
]
|
||||
|
||||
icon = ""
|
||||
soilNumber = await g_pDBService.user.getUserSoilByUid(uid)
|
||||
soilNumber = player.user.get("soil", 3)
|
||||
|
||||
for i in range(1, soilNumber + 1):
|
||||
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, i)
|
||||
@ -490,7 +501,10 @@ class CFarmManager:
|
||||
return g_sTranslation["sowing"]["noNum"].format(name=name, num=count)
|
||||
|
||||
# 获取用户土地数量
|
||||
soilNumber = await g_pDBService.user.getUserSoilByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
soilNumber = player.user.get("soil", 3)
|
||||
|
||||
# 如果播种数量为 -1,表示播种所有可播种的土地
|
||||
if num == -1:
|
||||
@ -546,7 +560,10 @@ class CFarmManager:
|
||||
try:
|
||||
await g_pEventManager.m_beforeHarvest.emit(uid=uid) # type: ignore
|
||||
|
||||
soilNumber = await g_pDBService.user.getUserSoilByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
soilNumber = player.user.get("soil", 3)
|
||||
|
||||
harvestRecords = [] # 收获日志记录
|
||||
experience = 0 # 总经验值
|
||||
@ -644,8 +661,8 @@ class CFarmManager:
|
||||
)
|
||||
|
||||
if experience > 0:
|
||||
exp = await g_pDBService.user.getUserExpByUid(uid)
|
||||
await g_pDBService.user.updateUserExpByUid(uid, exp + experience)
|
||||
exp = player.user.get("exp", 0)
|
||||
await player.addExp(exp + experience)
|
||||
harvestRecords.append(
|
||||
g_sTranslation["harvest"]["exp"].format(
|
||||
exp=experience,
|
||||
@ -671,7 +688,10 @@ class CFarmManager:
|
||||
Returns:
|
||||
str: 返回
|
||||
"""
|
||||
soilNumber = await g_pDBService.user.getUserSoilByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
soilNumber = player.user.get("soil", 3)
|
||||
|
||||
await g_pEventManager.m_beforeEradicate.emit(uid=uid) # type: ignore
|
||||
|
||||
@ -715,8 +735,8 @@ class CFarmManager:
|
||||
await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) # type: ignore
|
||||
|
||||
if experience > 0:
|
||||
exp = await g_pDBService.user.getUserExpByUid(uid)
|
||||
await g_pDBService.user.updateUserExpByUid(uid, exp + experience)
|
||||
exp = player.user.get("exp", 0)
|
||||
await player.addExp(exp + experience)
|
||||
|
||||
return g_sTranslation["eradicate"]["success"].format(exp=experience)
|
||||
else:
|
||||
@ -828,10 +848,12 @@ class CFarmManager:
|
||||
str: 返回
|
||||
"""
|
||||
# 用户信息
|
||||
userInfo = await g_pDBService.user.getUserInfoByUid(uid)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
|
||||
stealTime = userInfo.get("stealTime", "")
|
||||
stealCount = int(userInfo["stealCount"])
|
||||
stealTime = player.user.get("stealTime", "")
|
||||
stealCount = int(player.user["stealCount"])
|
||||
|
||||
if stealTime == "" or not stealTime:
|
||||
stealTime = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
@ -6,12 +6,12 @@ from playwright.async_api import async_playwright
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..config import g_sResourcePath
|
||||
from ..utils.config import g_sResourcePath
|
||||
|
||||
|
||||
class CHelpManager:
|
||||
@classmethod
|
||||
def rendeerHtmlToFile(
|
||||
def renderHtmlToFile(
|
||||
cls, path: Path | str, context: dict, output: Path | str
|
||||
) -> None:
|
||||
"""
|
||||
@ -110,7 +110,7 @@ class CHelpManager:
|
||||
}
|
||||
|
||||
try:
|
||||
cls.rendeerHtmlToFile(templatePath, context, outputPath)
|
||||
cls.renderHtmlToFile(templatePath, context, outputPath)
|
||||
|
||||
bytes = await cls.screenshotSave(str(outputPath), str(savePath), 1500, 2300)
|
||||
except Exception as e:
|
||||
199
core/player/player.py
Normal file
199
core/player/player.py
Normal file
@ -0,0 +1,199 @@
|
||||
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 updateField(self, field: str) -> bool:
|
||||
"""更新单字段信息
|
||||
|
||||
Returns:
|
||||
bool: 是否成功更新单字段信息
|
||||
"""
|
||||
uid = self.user.get("uid", "")
|
||||
|
||||
if uid == "":
|
||||
return False
|
||||
|
||||
return await g_pDBService.farm.updateFarmFieldByUid(uid)
|
||||
216
core/player/playerPool.py
Normal file
216
core/player/playerPool.py
Normal file
@ -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()
|
||||
@ -2,8 +2,9 @@ import math
|
||||
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
|
||||
from ..config import g_sResourcePath, g_sTranslation
|
||||
from ..dbService import g_pDBService
|
||||
from ..core.dbService import g_pDBService
|
||||
from ..utils.config import g_sResourcePath, g_sTranslation
|
||||
from ..utils.tool import g_pToolManager
|
||||
|
||||
|
||||
class CShopManager:
|
||||
@ -113,48 +114,39 @@ class CShopManager:
|
||||
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:
|
||||
if not plantInfo or not player:
|
||||
return g_sTranslation["buySeed"]["error"]
|
||||
|
||||
level = await g_pDBService.user.getUserLevelByUid(uid)
|
||||
level = player.user.get("level", 0)
|
||||
|
||||
if level[0] < int(plantInfo["level"]):
|
||||
if level < int(plantInfo["level"]):
|
||||
return g_sTranslation["buySeed"]["noLevel"]
|
||||
|
||||
"""
|
||||
logger.debug(
|
||||
f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}"
|
||||
)
|
||||
"""
|
||||
if plantInfo["isVip"] == 1:
|
||||
vipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
|
||||
total = int(plantInfo["vipBuy"]) * num
|
||||
if vipPoint < total:
|
||||
return g_sTranslation["buySeed"]["noVipPoint"]
|
||||
await g_pDBService.user.updateUserVipPointByUid(uid, vipPoint - total)
|
||||
else:
|
||||
point = await g_pDBService.user.getUserPointByUid(uid)
|
||||
total = int(plantInfo["buy"]) * num
|
||||
if point < total:
|
||||
return g_sTranslation["buySeed"]["noPoint"]
|
||||
await g_pDBService.user.updateUserPointByUid(uid, point - total)
|
||||
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"]
|
||||
|
||||
if plantInfo["isVip"] == 1:
|
||||
return g_sTranslation["buySeed"]["vipSuccess"].format(
|
||||
name=name, total=total, point=vipPoint - total
|
||||
)
|
||||
else:
|
||||
return g_sTranslation["buySeed"]["success"].format(
|
||||
name=name, total=total, point=point - total
|
||||
)
|
||||
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:
|
||||
@ -215,17 +207,18 @@ class CShopManager:
|
||||
|
||||
totalPoint = totalSold * price
|
||||
|
||||
currentPoint = await g_pDBService.user.getUserPointByUid(uid)
|
||||
await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint)
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
|
||||
if name == "":
|
||||
return g_sTranslation["sellPlant"]["success"].format(
|
||||
point=totalPoint, num=currentPoint + totalPoint
|
||||
)
|
||||
else:
|
||||
return g_sTranslation["sellPlant"]["success1"].format(
|
||||
name=name, point=totalPoint, num=currentPoint + totalPoint
|
||||
)
|
||||
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,143 +0,0 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..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.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 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:
|
||||
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
|
||||
|
||||
|
||||
g_pSqlManager = CSqlManager()
|
||||
479
database/user.py
479
database/user.py
@ -1,479 +0,0 @@
|
||||
import math
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..tool import g_pToolManager
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
"""初始化用户表结构,确保user表存在且字段完整"""
|
||||
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 cls.ensureTableSchema("user", userInfo)
|
||||
|
||||
@classmethod
|
||||
async def initUserInfoByUid(
|
||||
cls, uid: str, name: str = "", exp: int = 0, point: int = 500
|
||||
) -> bool | str:
|
||||
"""初始化用户信息,包含初始偷菜时间字符串与次数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 农场名称
|
||||
exp (int): 农场经验
|
||||
point (int): 农场币
|
||||
|
||||
Returns:
|
||||
bool | str: False 表示失败,字符串表示成功信息
|
||||
"""
|
||||
nowStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
sql = (
|
||||
f"INSERT INTO user (uid, name, exp, point, soil, stealTime, stealCount) "
|
||||
f"VALUES ({uid}, '{name}', {exp}, {point}, 3, '{nowStr}', 5)"
|
||||
)
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(sql)
|
||||
return "开通农场成功"
|
||||
except Exception as e:
|
||||
logger.warning("initUserInfoByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getAllUsers(cls) -> list[str]:
|
||||
"""获取所有用户UID列表
|
||||
|
||||
Returns:
|
||||
list[str]: 用户UID列表
|
||||
"""
|
||||
cursor = await cls.m_pDB.execute("SELECT uid FROM user")
|
||||
rows = await cursor.fetchall()
|
||||
return [row[0] for row in rows]
|
||||
|
||||
@classmethod
|
||||
async def isUserExist(cls, uid: str) -> bool:
|
||||
"""判断用户是否存在
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
bool: 如果用户存在返回True,否则返回False
|
||||
"""
|
||||
if not uid:
|
||||
return False
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT 1 FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row is not None
|
||||
except Exception as e:
|
||||
logger.warning("isUserExist 查询失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserInfoByUid(cls, uid: str) -> dict:
|
||||
"""获取指定用户完整信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
dict: 包含所有用户字段的字典
|
||||
"""
|
||||
if not uid:
|
||||
return {}
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT * FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
return {}
|
||||
|
||||
result = dict(row)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning("getUserInfoByUid 查询失败!", e=e)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def getUserNameByUid(cls, uid: str) -> str:
|
||||
"""根据用户Uid获取用户名
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str: 用户名,失败返回空字符串
|
||||
"""
|
||||
if not uid:
|
||||
return ""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT name FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row["name"] if row else ""
|
||||
except Exception as e:
|
||||
logger.warning("getUserNameByUid 查询失败!", e=e)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def updateUserNameByUid(cls, uid: str, name: str) -> bool:
|
||||
"""根据用户Uid更新用户名
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 新用户名
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or not name:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET name = ? WHERE uid = ?", (name, uid)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserNameByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserPointByUid(cls, uid: str) -> int:
|
||||
"""获取指定用户农场币
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 农场币数量,失败返回 -1
|
||||
"""
|
||||
if not uid:
|
||||
return -1
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT point FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return int(row[0]) if row and row[0] is not None else -1
|
||||
except Exception as e:
|
||||
logger.warning("getUserPointByUid 查询失败!", e=e)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def updateUserPointByUid(cls, uid: str, point: int) -> bool:
|
||||
"""根据用户Uid更新农场币数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
point (int): 新农场币数量
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or point < 0:
|
||||
logger.warning("updateUserPointByUid 参数校验失败!")
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET point = ? WHERE uid = ?", (point, uid)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("updateUserPointByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserVipPointByUid(cls, uid: str) -> int:
|
||||
"""获取指定用户点券
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 点券数量,失败返回 -1
|
||||
"""
|
||||
if not uid:
|
||||
return -1
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT vipPoint FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return int(row[0]) if row and row[0] is not None else -1
|
||||
except Exception as e:
|
||||
logger.warning("getUservipPointByUid 查询失败!", e=e)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def updateUserVipPointByUid(cls, uid: str, vipPoint: int) -> bool:
|
||||
"""根据用户Uid更新点券数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
vipPoint (int): 新点券数量
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or vipPoint < 0:
|
||||
logger.warning("updateUservipPointByUid 参数校验失败!")
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET vipPoint = ? WHERE uid = ?", (vipPoint, uid)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("updateUservipPointByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserExpByUid(cls, uid: str) -> int:
|
||||
"""获取指定用户经验值
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 经验值,失败返回 -1
|
||||
"""
|
||||
if not uid:
|
||||
return -1
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT exp FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return int(row[0]) if row and row[0] is not None else -1
|
||||
except Exception as e:
|
||||
logger.warning("getUserExpByUid 查询失败!", e=e)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def updateUserExpByUid(cls, uid: str, exp: int) -> bool:
|
||||
"""根据用户Uid更新经验值
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
exp (int): 新经验值
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET exp = ? WHERE uid = ?", (exp, uid)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserExpByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserLevelByUid(cls, 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
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT exp FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
if not row or row[0] is None:
|
||||
return -1, -1, -1
|
||||
|
||||
expVal = int(row[0])
|
||||
levelStep = 200 # 每级经验增量
|
||||
|
||||
discriminant = 1 + 8 * expVal / 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 = expVal - totalExpCurrentLevel
|
||||
|
||||
return level, totalExpNextLevel, currentExp
|
||||
|
||||
return -1, -1, -1
|
||||
except Exception as e:
|
||||
logger.warning("getUserLevelByUid 查询失败!", e=e)
|
||||
return -1, -1, -1
|
||||
|
||||
@classmethod
|
||||
async def getUserSoilByUid(cls, uid: str) -> int:
|
||||
"""获取解锁土地数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 解锁土地块数,失败返回0
|
||||
"""
|
||||
if not uid:
|
||||
return 0
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT soil FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return int(row[0]) if row and row[0] is not None else 0
|
||||
except Exception as e:
|
||||
logger.warning("getUserSoilByUid 查询失败!", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def updateUserSoilByUid(cls, uid: str, soil: int) -> bool:
|
||||
"""更新指定用户解锁土地数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
soil (int): 新土地数量
|
||||
|
||||
Returns:
|
||||
bool: 更新成功返回True,否则False
|
||||
"""
|
||||
if not uid or soil < 0:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET soil = ? WHERE uid = ?", (soil, uid)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserSoilByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getStealTimeByUid(cls, uid: str) -> str:
|
||||
"""根据用户Uid获取偷菜时间字符串
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str: 偷菜时间字符串,失败返回空字符串
|
||||
"""
|
||||
if not uid:
|
||||
return ""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT stealTime FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row and row[0] else ""
|
||||
except Exception as e:
|
||||
logger.warning("getStealTimeByUid 查询失败!", e=e)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def updateStealTimeByUid(cls, uid: str, stealTime: str) -> bool:
|
||||
"""根据用户Uid更新偷菜时间字符串
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
stealTime (str): 新偷菜时间字符串
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or not stealTime:
|
||||
logger.warning("updateStealTimeByUid 参数校验失败!")
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET stealTime = ? WHERE uid = ?", (stealTime, uid)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateStealTimeByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getStealCountByUid(cls, uid: str) -> int:
|
||||
"""根据用户Uid获取剩余偷菜次数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
int: 剩余偷菜次数,失败返回 -1
|
||||
"""
|
||||
if not uid:
|
||||
return -1
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT stealCount FROM user WHERE uid = ?", (uid,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return int(row[0]) if row and row[0] is not None else 0
|
||||
except Exception as e:
|
||||
logger.warning("getStealCountByUid 查询失败!", e=e)
|
||||
return -1
|
||||
|
||||
@classmethod
|
||||
async def updateStealCountByUid(
|
||||
cls, uid: str, stealTime: str, stealCount: int
|
||||
) -> bool:
|
||||
"""根据用户Uid更新剩余偷菜次数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
stealTime (str): 偷菜日期
|
||||
stealCount (int): 新剩余偷菜次数
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or stealCount < 0:
|
||||
logger.warning("updateStealCountByUid 参数校验失败!")
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE user SET stealTime = ?, stealCount = ? WHERE uid = ?",
|
||||
(stealTime, stealCount, uid),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateStealCountByUid 事务执行失败!", e=e)
|
||||
return False
|
||||
45
dbService.py
45
dbService.py
@ -1,45 +0,0 @@
|
||||
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
|
||||
from .database.userSeed import CUserSeedDB
|
||||
from .database.userSign import CUserSignDB
|
||||
from .database.userSoil import CUserSoilDB
|
||||
from .database.userSteal import CUserStealDB
|
||||
|
||||
cls.plant = CPlantManager()
|
||||
await cls.plant.init()
|
||||
|
||||
cls.user = CUserDB()
|
||||
await cls.user.initDB()
|
||||
|
||||
cls.userSoil = CUserSoilDB()
|
||||
await cls.userSoil.initDB()
|
||||
|
||||
cls.userPlant = CUserPlantDB()
|
||||
await cls.userPlant.initDB()
|
||||
|
||||
cls.userSeed = CUserSeedDB()
|
||||
await cls.userSeed.initDB()
|
||||
|
||||
cls.userItem = CUserItemDB()
|
||||
await cls.userItem.initDB()
|
||||
|
||||
cls.userSteal = CUserStealDB()
|
||||
await cls.userSteal.initDB()
|
||||
|
||||
cls.userSign = CUserSignDB()
|
||||
await cls.userSign.initDB()
|
||||
|
||||
# 迁移旧数据库
|
||||
await cls.userSoil.migrateOldFarmData()
|
||||
|
||||
@classmethod
|
||||
async def cleanup(cls):
|
||||
await cls.plant.cleanup()
|
||||
|
||||
|
||||
g_pDBService = CDBService()
|
||||
@ -15,13 +15,13 @@ g_sDBPath = DATA_PATH / "farm_db"
|
||||
g_sDBFilePath = DATA_PATH / "farm_db/farm.db"
|
||||
|
||||
# 农场资源文件目录
|
||||
g_sResourcePath = Path(__file__).resolve().parent / "resource"
|
||||
g_sResourcePath = Path(__file__).resolve().parent / "../resource"
|
||||
|
||||
# 农场作物数据库
|
||||
g_sPlantPath = g_sResourcePath / "db/plant.db"
|
||||
|
||||
# 农场配置文件目录
|
||||
g_sConfigPath = Path(__file__).resolve().parent / "config"
|
||||
g_sConfigPath = Path(__file__).resolve().parent / "../config"
|
||||
|
||||
# 农场签到文件路径
|
||||
g_sSignInPath = g_sConfigPath / "sign_in.json"
|
||||
@ -35,6 +35,7 @@ g_sTranslation = {
|
||||
"notFarm": "尚未开通农场,快at我发送 开通农场 开通吧 🌱🚜",
|
||||
"point": "你的当前农场币为: {point} 🌾💰",
|
||||
"vipPoint": "你的当前点券为: {vipPoint} 🌾💰",
|
||||
"error": "❌ 农场功能异常,请稍后再试 💔",
|
||||
},
|
||||
"register": {
|
||||
"success": "✅ 农场开通成功!\n💼 初始资金:{point}农场币 🥳🎉",
|
||||
@ -10,11 +10,12 @@ from rich.progress import (
|
||||
TimeRemainingColumn,
|
||||
TransferSpeedColumn,
|
||||
)
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..core.dbService import g_pDBService
|
||||
from .config import g_sPlantPath, g_sSignInPath
|
||||
from .dbService import g_pDBService
|
||||
from .tool import g_pToolManager
|
||||
|
||||
|
||||
@ -1,25 +1,31 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
import os
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
from .dbService import g_pDBService
|
||||
from ..core.player.player import CPlayer
|
||||
from ..core.player.playerPool import g_pUserPool
|
||||
|
||||
|
||||
class CToolManager:
|
||||
@classmethod
|
||||
async def isRegisteredByUid(cls, uid: str) -> bool:
|
||||
result = await g_pDBService.user.isUserExist(uid)
|
||||
async def repeat(cls):
|
||||
await MessageUtils.build_message(
|
||||
"尚未开通农场,快at我发送 开通农场 开通吧"
|
||||
).send()
|
||||
|
||||
if not result:
|
||||
await MessageUtils.build_message(
|
||||
"尚未开通农场,快at我发送 开通农场 开通吧"
|
||||
).send()
|
||||
return False
|
||||
@classmethod
|
||||
async def getPlayerByUid(cls, uid: str) -> CPlayer | None:
|
||||
player = g_pUserPool.getUser(uid)
|
||||
if player is None:
|
||||
player = CPlayer()
|
||||
if not await player.init(uid):
|
||||
return None
|
||||
g_pUserPool.createUser(uid, player)
|
||||
|
||||
return True
|
||||
return player
|
||||
|
||||
@classmethod
|
||||
def sanitize_username(cls, username: str, max_length: int = 15) -> str:
|
||||
Loading…
Reference in New Issue
Block a user