🎨 新增Player类 更改项目文件结构
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
class ActivityManager:
|
||||
"""活动管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.activities = {} # 活动ID -> 活动配置
|
||||
self.active_activities = set() # 当前活跃的活动ID
|
||||
self.effect_handlers = {} # 效果类型 -> 处理器
|
||||
|
||||
# 注册效果处理器
|
||||
self._register_handlers()
|
||||
|
||||
def _register_handlers(self):
|
||||
"""注册所有效果处理器"""
|
||||
self.effect_handlers[EffectType.MULTIPLIER] = MultiplierHandler()
|
||||
self.effect_handlers[EffectType.FIXED_BONUS] = FixedBonusHandler()
|
||||
self.effect_handlers[EffectType.BUFF_APPLICATION] = BuffApplicationHandler()
|
||||
self.effect_handlers[EffectType.QUEST_TRIGGER] = QuestTriggerHandler()
|
||||
|
||||
def load_activities_from_config(self, config_path: str):
|
||||
"""从配置文件加载活动"""
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
for activity_data in config["activities"]:
|
||||
activity = Activity(activity_data)
|
||||
self.activities[activity.id] = activity
|
||||
print(f"加载活动: {activity.name}")
|
||||
|
||||
def update_activity_status(self):
|
||||
"""更新活动状态(定时调用)"""
|
||||
now = datetime.now()
|
||||
|
||||
for activity in self.activities.values():
|
||||
is_active = activity.start_time <= now <= activity.end_time
|
||||
|
||||
if is_active and activity.id not in self.active_activities:
|
||||
# 活动开始
|
||||
self.active_activities.add(activity.id)
|
||||
print(f"活动开始: {activity.name}")
|
||||
|
||||
elif not is_active and activity.id in self.active_activities:
|
||||
# 活动结束
|
||||
self.active_activities.remove(activity.id)
|
||||
print(f"活动结束: {activity.name}")
|
||||
|
||||
def get_active_activities(self, activity_type: ActivityType = None) -> List:
|
||||
"""获取当前活跃的活动"""
|
||||
active_list = []
|
||||
|
||||
for activity_id in self.active_activities:
|
||||
activity = self.activities[activity_id]
|
||||
if activity_type is None or activity.activity_type == activity_type:
|
||||
active_list.append(activity)
|
||||
|
||||
return active_list
|
||||
|
||||
def apply_activity_effects(
|
||||
self,
|
||||
activity_type: ActivityType,
|
||||
player: Player,
|
||||
base_value: int,
|
||||
context: Dict = None,
|
||||
) -> int:
|
||||
"""应用活动效果"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
context["base_value"] = base_value
|
||||
result = base_value
|
||||
|
||||
# 获取同类型的所有活跃活动
|
||||
active_activities = self.get_active_activities(activity_type)
|
||||
|
||||
for activity in active_activities:
|
||||
print(f"为玩家 {player.player_id} 应用活动: {activity.name}")
|
||||
|
||||
for effect in activity.effects:
|
||||
handler = self.effect_handlers.get(effect.type)
|
||||
if handler:
|
||||
try:
|
||||
effect_result = handler.execute(effect.params, player, context)
|
||||
if effect_result is not None:
|
||||
result = effect_result
|
||||
context["base_value"] = result # 更新基础值供后续效果使用
|
||||
except Exception as e:
|
||||
print(f"效果执行失败: {effect.type}, 错误: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Activity:
|
||||
"""活动类"""
|
||||
|
||||
def __init__(self, data: Dict):
|
||||
self.id = data["id"]
|
||||
self.name = data["name"]
|
||||
self.activity_type = ActivityType(data["activity_type"])
|
||||
self.start_time = datetime.strptime(data["start_time"], "%Y-%m-%d %H:%M:%S")
|
||||
self.end_time = datetime.strptime(data["end_time"], "%Y-%m-%d %H:%M:%S")
|
||||
self.effects = [Effect(effect_data) for effect_data in data["effects"]]
|
||||
|
||||
|
||||
class Effect:
|
||||
"""效果类"""
|
||||
|
||||
def __init__(self, data: Dict):
|
||||
self.type = EffectType(data["type"])
|
||||
self.params = data.get("params", {})
|
||||
@@ -0,0 +1,95 @@
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
|
||||
# 活动类型枚举
|
||||
class ActivityType(Enum):
|
||||
PLANTING = "planting" # 种植活动
|
||||
HARVESTING = "harvesting" # 收获活动
|
||||
FISHING = "fishing" # 钓鱼活动
|
||||
COMBAT = "combat" # 战斗活动
|
||||
|
||||
|
||||
# 效果类型枚举
|
||||
class EffectType(Enum):
|
||||
MULTIPLIER = "multiplier" # 倍数加成
|
||||
FIXED_BONUS = "fixed_bonus" # 固定加成
|
||||
BUFF_APPLICATION = "buff_application" # 施加BUFF
|
||||
QUEST_TRIGGER = "quest_trigger" # 任务触发
|
||||
|
||||
|
||||
class EffectHandler:
|
||||
"""
|
||||
效果处理器基类
|
||||
"""
|
||||
|
||||
def execute(self, params: dict, uid: str, context: dict) -> Any:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MultiplierHandler(EffectHandler):
|
||||
"""
|
||||
倍数效果处理器
|
||||
"""
|
||||
|
||||
def execute(self, params: dict, uid: str, context: dict) -> float:
|
||||
base_value = context.get("base_value", 0)
|
||||
multiplier = params.get("value", 1.0)
|
||||
|
||||
# 检查条件
|
||||
if self._check_conditions(params.get("conditions", {}), player):
|
||||
result = base_value * multiplier
|
||||
print(f"倍数效果: {base_value} × {multiplier} = {result}")
|
||||
return result
|
||||
return base_value
|
||||
|
||||
def _check_conditions(self, conditions: dict, player: Player) -> bool:
|
||||
"""检查生效条件"""
|
||||
# 等级要求
|
||||
min_level = conditions.get("min_level", 0)
|
||||
if player.level < min_level:
|
||||
return False
|
||||
|
||||
# 需要特定物品
|
||||
required_items = conditions.get("required_items", [])
|
||||
for item in required_items:
|
||||
if player.inventory.get(item, 0) <= 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class FixedBonusHandler(EffectHandler):
|
||||
"""固定加成处理器"""
|
||||
|
||||
def execute(self, params: dict, player: Player, context: dict) -> int:
|
||||
base_value = context.get("base_value", 0)
|
||||
bonus = params.get("value", 0)
|
||||
|
||||
result = base_value + bonus
|
||||
print(f"固定加成: {base_value} + {bonus} = {result}")
|
||||
return result
|
||||
|
||||
|
||||
class BuffApplicationHandler(EffectHandler):
|
||||
"""BUFF应用处理器"""
|
||||
|
||||
def execute(self, params: dict, player: Player, context: dict) -> None:
|
||||
buff_id = params["buff_id"]
|
||||
duration = params.get("duration", 3600) # 默认1小时
|
||||
properties = params.get("properties", {})
|
||||
|
||||
player.add_buff(buff_id, duration, properties)
|
||||
|
||||
# 立即应用BUFF效果(如果有)
|
||||
if "immediate_effect" in params:
|
||||
self._apply_immediate_effect(params["immediate_effect"], player)
|
||||
|
||||
|
||||
class QuestTriggerHandler(EffectHandler):
|
||||
"""任务触发处理器"""
|
||||
|
||||
def execute(self, params: dict, player: Player, context: dict) -> None:
|
||||
quest_id = params["quest_id"]
|
||||
print(f"为玩家 {player.player_id} 触发任务: {quest_id}")
|
||||
# 这里会调用任务系统来分配任务
|
||||
@@ -0,0 +1,399 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ...utils.config import g_sDBFilePath, g_sDBPath
|
||||
|
||||
|
||||
class CSqlManager:
|
||||
def __init__(self):
|
||||
dbPath = Path(g_sDBPath)
|
||||
if dbPath and not dbPath.exists():
|
||||
os.makedirs(dbPath, exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
async def cleanup(cls):
|
||||
if hasattr(cls, "m_pDB") and cls.m_pDB:
|
||||
await cls.m_pDB.close()
|
||||
|
||||
@classmethod
|
||||
async def init(cls) -> bool:
|
||||
try:
|
||||
cls.m_pDB = await aiosqlite.connect(g_sDBFilePath)
|
||||
cls.m_pDB.row_factory = aiosqlite.Row
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场初始化总数据库失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
@asynccontextmanager
|
||||
async def _transaction(cls):
|
||||
await cls.m_pDB.execute("BEGIN;")
|
||||
try:
|
||||
yield
|
||||
except:
|
||||
await cls.m_pDB.execute("ROLLBACK;")
|
||||
raise
|
||||
else:
|
||||
await cls.m_pDB.execute("COMMIT;")
|
||||
|
||||
@classmethod
|
||||
async def getTableInfo(cls, tableName: str) -> list:
|
||||
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", tableName):
|
||||
raise ValueError(f"Illegal table name: {tableName}")
|
||||
try:
|
||||
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")')
|
||||
rows = await cursor.fetchall()
|
||||
return [{"name": row[1], "type": row[2]} for row in rows]
|
||||
except aiosqlite.Error:
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def ensureTableSchema(cls, tableName: str, columns: dict) -> bool:
|
||||
"""由AI生成
|
||||
创建表或为已存在表添加缺失字段。
|
||||
返回 True 表示有变更(创建或新增列),False 则无操作
|
||||
|
||||
Args:
|
||||
tableName (_type_): 表名
|
||||
columns (_type_): 字典
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
|
||||
info = await cls.getTableInfo(tableName)
|
||||
existing = {col["name"]: col["type"].upper() for col in info}
|
||||
desired = {k: v.upper() for k, v in columns.items() if k != "PRIMARY KEY"}
|
||||
primaryKey = columns.get("PRIMARY KEY", "")
|
||||
|
||||
if not existing:
|
||||
colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items())
|
||||
if primaryKey:
|
||||
colsDef += f", PRIMARY KEY {primaryKey}"
|
||||
await cls.m_pDB.execute(f'CREATE TABLE "{tableName}" ({colsDef});')
|
||||
return True
|
||||
|
||||
toAdd = [k for k in desired if k not in existing]
|
||||
toRemove = [k for k in existing if k not in desired]
|
||||
typeMismatch = [
|
||||
k for k in desired if k in existing and existing[k] != desired[k]
|
||||
]
|
||||
|
||||
if toAdd and not toRemove and not typeMismatch:
|
||||
for col in toAdd:
|
||||
await cls.m_pDB.execute(
|
||||
f'ALTER TABLE "{tableName}" ADD COLUMN "{col}" {columns[col]}'
|
||||
)
|
||||
return True
|
||||
|
||||
async with cls._transaction():
|
||||
tmpTable = f"{tableName}_new"
|
||||
colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items())
|
||||
if primaryKey:
|
||||
colsDef += f", PRIMARY KEY {primaryKey}"
|
||||
await cls.m_pDB.execute(f'CREATE TABLE "{tmpTable}" ({colsDef});')
|
||||
|
||||
commonCols = [k for k in desired if k in existing]
|
||||
if commonCols:
|
||||
colsStr = ", ".join(f'"{c}"' for c in commonCols)
|
||||
|
||||
sql = (
|
||||
f'INSERT INTO "{tmpTable}" ({colsStr}) '
|
||||
f"SELECT {colsStr} "
|
||||
f'FROM "{tableName}";'
|
||||
)
|
||||
|
||||
await cls.m_pDB.execute(sql)
|
||||
await cls.m_pDB.execute(f'DROP TABLE "{tableName}";')
|
||||
await cls.m_pDB.execute(
|
||||
f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";'
|
||||
)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def executeDB(cls, command: str) -> bool:
|
||||
"""执行自定义SQL
|
||||
|
||||
Args:
|
||||
command (str): SQL语句
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not command:
|
||||
return False
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(command)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def insert(cls, tableName: str, data: dict) -> bool:
|
||||
"""
|
||||
插入数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
data: 要插入的数据字典,键为字段名,值为字段值
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not data:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 构建参数化查询
|
||||
columns = ", ".join(f'"{k}"' for k in data.keys())
|
||||
placeholders = ", ".join("?" for _ in data.keys())
|
||||
values = list(data.values())
|
||||
|
||||
sql = f'INSERT INTO "{tableName}" ({columns}) VALUES ({placeholders})'
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(sql, values)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场插入数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def batch_insert(cls, tableName: str, data_list: list) -> bool:
|
||||
"""
|
||||
批量插入数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
data_list: 要插入的数据字典列表
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not data_list:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 使用第一个字典的键作为所有记录的字段
|
||||
columns = ", ".join(f'"{k}"' for k in data_list[0].keys())
|
||||
placeholders = ", ".join("?" for _ in data_list[0].keys())
|
||||
|
||||
sql = f'INSERT INTO "{tableName}" ({columns}) VALUES ({placeholders})'
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.executemany(
|
||||
sql, [list(data.values()) for data in data_list]
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场批量插入数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def select(
|
||||
cls,
|
||||
tableName: str,
|
||||
columns: list[Any] | None = None,
|
||||
where: dict[str, Any] | None = None,
|
||||
order_by: str | None = None,
|
||||
limit: int | None = None,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
查询数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
columns: 要查询的字段列表,None表示所有字段
|
||||
where: 查询条件字典
|
||||
order_by: 排序字段
|
||||
limit: 限制返回记录数
|
||||
|
||||
Returns:
|
||||
list: 查询结果列表,每个元素是一个字典
|
||||
"""
|
||||
try:
|
||||
# 构建SELECT部分
|
||||
if columns:
|
||||
select_clause = ", ".join(f'"{col}"' for col in columns)
|
||||
else:
|
||||
select_clause = "*"
|
||||
|
||||
sql = f'SELECT {select_clause} FROM "{tableName}"'
|
||||
|
||||
# 构建WHERE部分
|
||||
params = []
|
||||
if where:
|
||||
where_conditions = []
|
||||
for key, value in where.items():
|
||||
if isinstance(value, (list, tuple)):
|
||||
# 处理IN查询
|
||||
placeholders = ", ".join("?" for _ in value)
|
||||
where_conditions.append(f'"{key}" IN ({placeholders})')
|
||||
params.extend(value)
|
||||
else:
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
if where_conditions:
|
||||
sql += " WHERE " + " AND ".join(where_conditions)
|
||||
|
||||
# 构建ORDER BY部分
|
||||
if order_by:
|
||||
sql += f" ORDER BY {order_by}"
|
||||
|
||||
# 构建LIMIT部分
|
||||
if limit:
|
||||
sql += f" LIMIT {limit}"
|
||||
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
# 转换为字典列表
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场查询数据失败!", e=e)
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def update(cls, tableName: str, data: dict, where: dict) -> bool:
|
||||
"""
|
||||
更新数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
data: 要更新的数据字典
|
||||
where: 更新条件字典
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not data:
|
||||
return False
|
||||
|
||||
if not where:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 构建SET部分
|
||||
set_conditions = []
|
||||
params = []
|
||||
for key, value in data.items():
|
||||
set_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
# 构建WHERE部分
|
||||
where_conditions = []
|
||||
for key, value in where.items():
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
sql = f'UPDATE "{tableName}" SET {", ".join(set_conditions)} WHERE {" AND ".join(where_conditions)}'
|
||||
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
# 检查是否影响了行
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场更新数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def delete(cls, tableName: str, where: dict) -> bool:
|
||||
"""
|
||||
删除数据
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
where: 删除条件字典
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not where:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 构建WHERE部分
|
||||
where_conditions = []
|
||||
params = []
|
||||
for key, value in where.items():
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
sql = f'DELETE FROM "{tableName}" WHERE {" AND ".join(where_conditions)}'
|
||||
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
# 检查是否影响了行
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场删除数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def exists(cls, tableName: str, where: dict) -> bool:
|
||||
"""
|
||||
检查记录是否存在
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
where: 查询条件字典
|
||||
|
||||
Returns:
|
||||
bool: 是否存在符合条件的记录
|
||||
"""
|
||||
try:
|
||||
result = await cls.select(tableName, columns=["1"], where=where, limit=1)
|
||||
return len(result) > 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场检查数据失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def count(cls, tableName: str, where: dict = {}) -> int:
|
||||
"""
|
||||
统计记录数量
|
||||
|
||||
Args:
|
||||
tableName: 表名
|
||||
where: 查询条件字典
|
||||
|
||||
Returns:
|
||||
int: 记录数量
|
||||
"""
|
||||
try:
|
||||
# 构建WHERE部分
|
||||
sql = f'SELECT COUNT(*) as count FROM "{tableName}"'
|
||||
params = []
|
||||
|
||||
if where:
|
||||
where_conditions = []
|
||||
for key, value in where.items():
|
||||
where_conditions.append(f'"{key}" = ?')
|
||||
params.append(value)
|
||||
|
||||
sql += " WHERE " + " AND ".join(where_conditions)
|
||||
|
||||
cursor = await cls.m_pDB.execute(sql, params)
|
||||
row = await cursor.fetchone()
|
||||
return row["count"] if row else 0
|
||||
except Exception as e:
|
||||
logger.debug("真寻农场统计数据失败!", e=e)
|
||||
return 0
|
||||
|
||||
|
||||
g_pSqlManager = CSqlManager()
|
||||
@@ -0,0 +1,281 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import os
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ...utils.config import g_bIsDebug, g_sPlantPath, g_sResourcePath
|
||||
from ...utils.request import g_pRequestManager
|
||||
|
||||
|
||||
class CPlantManager:
|
||||
def __init__(self):
|
||||
try:
|
||||
os.mkdir(g_sPlantPath)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
async def cleanup(self):
|
||||
if hasattr(self, "m_pDB") and self.m_pDB:
|
||||
await self.m_pDB.close()
|
||||
|
||||
async def init(self) -> bool:
|
||||
try:
|
||||
_ = os.path.exists(g_sPlantPath)
|
||||
|
||||
if g_bIsDebug:
|
||||
self.m_pDB = await aiosqlite.connect(
|
||||
str(g_sPlantPath.parent / "plant-test.db")
|
||||
)
|
||||
else:
|
||||
self.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
|
||||
|
||||
self.m_pDB.row_factory = aiosqlite.Row
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("初始化植物数据库失败", e=e)
|
||||
return False
|
||||
|
||||
@asynccontextmanager
|
||||
async def _transaction(self):
|
||||
await self.m_pDB.execute("BEGIN;")
|
||||
try:
|
||||
yield
|
||||
except:
|
||||
await self.m_pDB.execute("ROLLBACK;")
|
||||
raise
|
||||
else:
|
||||
await self.m_pDB.execute("COMMIT;")
|
||||
|
||||
async def executeDB(self, command: str) -> bool:
|
||||
"""执行自定义SQL
|
||||
|
||||
Args:
|
||||
command (str): SQL语句
|
||||
|
||||
Returns:
|
||||
bool: 是否执行成功
|
||||
"""
|
||||
if not command:
|
||||
logger.warning("数据库语句长度为空!")
|
||||
return False
|
||||
|
||||
try:
|
||||
async with self._transaction():
|
||||
await self.m_pDB.execute(command)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"数据库语句执行出错: {command}", e=e)
|
||||
return False
|
||||
|
||||
async def getPlantByName(self, name: str) -> dict | None:
|
||||
"""根据作物名称查询记录
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
dict | None: 返回记录字典,未找到返回None
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT * FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物失败: {name}", e=e)
|
||||
return None
|
||||
|
||||
async def getPlantPhaseByName(self, name: str) -> list[int]:
|
||||
"""根据作物名称获取作物各个阶段
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
list: 阶段数组
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return []
|
||||
|
||||
phase = row[0].split(",")
|
||||
|
||||
seen = set()
|
||||
result = []
|
||||
|
||||
for x in phase:
|
||||
num = int(x)
|
||||
|
||||
if num not in seen:
|
||||
seen.add(num)
|
||||
result.append(num)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return []
|
||||
|
||||
async def getPlantPhaseNumberByName(self, name: str) -> int:
|
||||
"""根据作物名称获取作物总阶段数
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
int: 总阶段数
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return -1
|
||||
|
||||
phase = row[0].split(",")
|
||||
|
||||
# 去重
|
||||
seen = set()
|
||||
result = []
|
||||
for x in phase:
|
||||
if x not in seen:
|
||||
seen.add(x)
|
||||
result.append(x)
|
||||
|
||||
return len(result)
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return -1
|
||||
|
||||
async def getPlantAgainByName(self, name: str) -> int:
|
||||
"""根据作物名称获取作物再次成熟时间
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
int: 再次成熟时间 单位:h
|
||||
"""
|
||||
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT phase FROM plant WHERE name = ?", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return -1
|
||||
|
||||
phase = row[0].split(",")
|
||||
again = phase[-1] - phase[3] / 60 / 60
|
||||
|
||||
return again
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"查询作物阶段失败: {name}", e=e)
|
||||
return -1
|
||||
|
||||
async def existsPlant(self, name: str) -> bool:
|
||||
"""判断作物是否存在
|
||||
|
||||
Args:
|
||||
name (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool: 存在返回True,否则False
|
||||
"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT 1 FROM plant WHERE name = ? LIMIT 1", (name,)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return True if row else False
|
||||
except Exception as e:
|
||||
logger.warning(f"检查作物存在性失败: {name}", e=e)
|
||||
return False
|
||||
|
||||
async def countPlants(self, onlyBuy: bool = False) -> int:
|
||||
"""获取作物总数
|
||||
|
||||
Args:
|
||||
onlyBuy (bool): 若为True,仅统计isBuy=1的记录,默认False
|
||||
|
||||
Returns:
|
||||
int: 符合条件的记录数
|
||||
"""
|
||||
try:
|
||||
if onlyBuy:
|
||||
sql = "SELECT COUNT(*) FROM plant WHERE isBuy = 1"
|
||||
params: tuple = ()
|
||||
else:
|
||||
sql = "SELECT COUNT(*) FROM plant"
|
||||
params: tuple = ()
|
||||
async with self.m_pDB.execute(sql, params) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
except Exception as e:
|
||||
logger.warning(f"统计作物数量失败, onlyBuy={onlyBuy}", e=e)
|
||||
return 0
|
||||
|
||||
async def listPlants(self) -> list[dict]:
|
||||
"""查询所有作物记录"""
|
||||
try:
|
||||
async with self.m_pDB.execute(
|
||||
"SELECT * FROM plant ORDER BY level"
|
||||
) as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
except Exception as e:
|
||||
logger.warning("查询所有作物失败", e=e)
|
||||
return []
|
||||
|
||||
async def downloadPlant(self) -> bool:
|
||||
"""遍历所有作物,下载各阶段图片及icon文件到指定文件夹
|
||||
|
||||
Returns:
|
||||
bool: 全部下载完成返回True,如有失败返回False
|
||||
"""
|
||||
success = True
|
||||
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
|
||||
|
||||
baseUrl = baseUrl.rstrip("/") + ":8998/file"
|
||||
try:
|
||||
plants = await self.listPlants()
|
||||
for plant in plants:
|
||||
name = plant["name"]
|
||||
phaseCount = await self.getPlantPhaseNumberByName(name)
|
||||
saveDir = os.path.join(g_sResourcePath, "plant", name)
|
||||
begin = 0 if plant["general"] == 0 else 1
|
||||
|
||||
for idx in range(begin, phaseCount + 1):
|
||||
fileName = f"{idx}.png"
|
||||
fullPath = os.path.join(saveDir, fileName)
|
||||
|
||||
if os.path.exists(fullPath):
|
||||
continue
|
||||
|
||||
url = f"{baseUrl}/{name}/{idx}.png"
|
||||
if not await g_pRequestManager.download(url, saveDir, f"{idx}.png"):
|
||||
success = False
|
||||
|
||||
iconName = "icon.png"
|
||||
iconPath = os.path.join(saveDir, iconName)
|
||||
if not os.path.exists(iconPath):
|
||||
iconUrl = f"{baseUrl}/{name}/{iconName}"
|
||||
if not await g_pRequestManager.download(iconUrl, saveDir, iconName):
|
||||
success = False
|
||||
|
||||
return success
|
||||
except Exception as e:
|
||||
logger.warning(f"下载作物资源异常: {e}")
|
||||
return False
|
||||
@@ -0,0 +1,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})
|
||||
@@ -0,0 +1,158 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserItemDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userItem = {
|
||||
"uid": "TEXT NOT NULL", # 用户Uid
|
||||
"item": "TEXT NOT NULL", # 物品名称
|
||||
"count": "INTEGER NOT NULL DEFAULT 0", # 数量
|
||||
"PRIMARY KEY": "(uid, item)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userItem", userItem)
|
||||
|
||||
@classmethod
|
||||
async def getUserItemByName(cls, uid: str, item: str) -> int | None:
|
||||
"""根据道具名称查询某一项数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
|
||||
Returns:
|
||||
Optional[int]: 数量(不存在返回None)
|
||||
"""
|
||||
if not uid or not item:
|
||||
return None
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
except Exception as e:
|
||||
logger.warning("getUserItemByName查询失败!", e=e)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def getUserItemByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取全部道具信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
|
||||
Returns:
|
||||
dict: {itemName: count, ...}
|
||||
"""
|
||||
if not uid:
|
||||
return {}
|
||||
try:
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT item, count FROM userItem WHERE uid = ?", (uid,)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return {row["item"]: row["count"] for row in rows}
|
||||
except Exception as e:
|
||||
logger.warning("getUserItemByUid查询失败!", e=e)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def deleteUserItemByName(cls, uid: str, item: str) -> bool:
|
||||
"""根据道具名删除道具
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
if not uid or not item:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserItemByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def updateUserItemByName(cls, uid: str, item: str, count: int) -> bool:
|
||||
"""根据道具名直接更新道具数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
count (int): 要更新的新数量
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
if not uid or not item:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
if count <= 0:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
|
||||
)
|
||||
else:
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
|
||||
(count, uid, item),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserItemByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def addUserItemByUid(cls, uid: str, item: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加道具信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
item (str): 道具名称
|
||||
count (int, optional): 数量.Defaults to 1.
|
||||
|
||||
Returns:
|
||||
bool: 是否添加成功
|
||||
"""
|
||||
if not uid or not item:
|
||||
return False
|
||||
try:
|
||||
async with cls._transaction():
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row:
|
||||
newCount = row[0] + count
|
||||
if newCount <= 0:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userItem WHERE uid = ? AND item = ?",
|
||||
(uid, item),
|
||||
)
|
||||
else:
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
|
||||
(newCount, uid, item),
|
||||
)
|
||||
else:
|
||||
if count > 0:
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)",
|
||||
(uid, item, count),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("addUserItemByUid失败!", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,205 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserPlantDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userPlant = {
|
||||
"uid": "TEXT NOT NULL", # 用户Uid
|
||||
"plant": "TEXT NOT NULL", # 作物名称
|
||||
"count": "INTEGER NOT NULL DEFAULT 0", # 数量
|
||||
"isLock": "INTEGER NOT NULL DEFAULT 0", # 是否上锁 0=没有,非0=有
|
||||
"PRIMARY KEY": "(uid, plant)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userPlant", userPlant)
|
||||
|
||||
@classmethod
|
||||
async def addUserPlantByUid(cls, uid: str, plant: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加作物信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
count (int): 数量
|
||||
|
||||
Returns:
|
||||
bool: 是否添加成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
# 检查是否已存在该作物
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
|
||||
(uid, plant),
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row:
|
||||
# 如果作物已存在,则更新数量
|
||||
new_count = row[0] + count
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
|
||||
(new_count, uid, plant),
|
||||
)
|
||||
else:
|
||||
# 如果作物不存在,则插入新记录
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userPlant (uid, plant, count) VALUES (?, ?, ?)",
|
||||
(uid, plant, count),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("addUserPlantByUid 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserPlantByUid(cls, uid: str) -> dict[str, int]:
|
||||
"""根据用户uid获取全部作物信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
|
||||
Returns:
|
||||
Dict[str, int]: 作物名称和数量
|
||||
"""
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT plant, count FROM userPlant WHERE uid=?", (uid,)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return {row["plant"]: row["count"] for row in rows}
|
||||
|
||||
@classmethod
|
||||
async def getUserPlantByName(cls, uid: str, plant: str) -> int | None:
|
||||
"""根据作物名称获取用户的作物数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
Optional[int]: 作物数量
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
except Exception as e:
|
||||
logger.warning("getUserPlantByName 查询失败!", e=e)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def checkUserPlantByName(cls, uid: str, plant: str) -> bool:
|
||||
"""根据作物名称判断用户作物仓库是否存在该作物
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool: 是否存在
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT * FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return True if row else False
|
||||
except Exception as e:
|
||||
logger.warning("checkUserPlantByName 查询失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool:
|
||||
"""更新 userPlant 表中某个作物的数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
count (int): 新的作物数量
|
||||
|
||||
Returns:
|
||||
bool: 是否更新成功
|
||||
"""
|
||||
try:
|
||||
if count <= 0:
|
||||
return await cls.deleteUserPlantByName(uid, plant)
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
|
||||
(count, uid, plant),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserPlantByName 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def lockUserPlantByName(cls, uid: str, plant: str, lock: int) -> bool:
|
||||
"""给作物加锁,防止一键售卖
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
lock (int): 0为解锁,非0均为加锁
|
||||
|
||||
Returns:
|
||||
bool: 是否加锁成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userPlant SET isLock = ? WHERE uid = ? AND plant = ?",
|
||||
(lock, uid, plant),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("lockUserPlantByName 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def checkPlantLockByName(cls, uid: str, plant: str) -> bool:
|
||||
"""根据作物名称判断是否加锁
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool: 是否加锁
|
||||
"""
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT isLock FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] > 0 if row else False
|
||||
except Exception as e:
|
||||
logger.warning("checkUserPlantByName 查询失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteUserPlantByName(cls, uid: str, plant: str) -> bool:
|
||||
"""从 userPlant 表中删除某个作物记录
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
plant (str): 作物名称
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserPlantByName 失败!", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,212 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserSeedDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userSeed = {
|
||||
"uid": "TEXT NOT NULL", # 用户Uid
|
||||
"seed": "TEXT NOT NULL", # 种子名称
|
||||
"count": "INTEGER NOT NULL DEFAULT 0", # 数量
|
||||
"PRIMARY KEY": "(uid, seed)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userSeed", userSeed)
|
||||
|
||||
@classmethod
|
||||
async def addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加种子信息(事务版本)
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
count (int): 数量
|
||||
|
||||
Returns:
|
||||
bool: 是否添加成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row:
|
||||
newCount = row[0] + count
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
|
||||
(newCount, uid, seed),
|
||||
)
|
||||
else:
|
||||
newCount = count
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
|
||||
(uid, seed, count),
|
||||
)
|
||||
|
||||
if newCount <= 0:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("addUserSeedByUid 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def _addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool:
|
||||
"""根据用户uid添加种子信息(非事务版,复用其他非事务接口)"""
|
||||
try:
|
||||
existing = await cls.getUserSeedByName(uid, seed)
|
||||
newCount = (existing or 0) + count
|
||||
|
||||
if existing is not None:
|
||||
await cls._updateUserSeedByName(uid, seed, newCount)
|
||||
else:
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
|
||||
(uid, seed, newCount),
|
||||
)
|
||||
|
||||
if newCount <= 0:
|
||||
await cls._deleteUserSeedByName(uid, seed)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("_addUserSeedByUid 失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByName(cls, uid: str, seed: str) -> int | None:
|
||||
"""根据种子名称获取种子数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
Optional[int]: 种子数量
|
||||
"""
|
||||
|
||||
try:
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
except Exception as e:
|
||||
logger.warning("getUserSeedByName 查询失败!", e=e)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def getUserSeedByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取仓库全部种子信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
|
||||
Returns:
|
||||
dict: 种子信息
|
||||
"""
|
||||
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT seed, count FROM userSeed WHERE uid=?", (uid,)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return {row["seed"]: row["count"] for row in rows}
|
||||
|
||||
@classmethod
|
||||
async def updateUserSeedByName(cls, uid: str, seed: str, count: int) -> bool:
|
||||
"""根据种子名称更新种子数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
count (int): 种子数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
if count <= 0:
|
||||
return await cls.deleteUserSeedByName(uid, seed)
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
|
||||
(count, uid, seed),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserSeedByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def _updateUserSeedByName(cls, uid: str, seed: str, count: int) -> bool:
|
||||
"""根据种子名称更新种子数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
count (int): 种子数量
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
if count <= 0:
|
||||
return await cls.deleteUserSeedByName(uid, seed)
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
|
||||
(count, uid, seed),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("updateUserSeedByName失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteUserSeedByName(cls, uid: str, seed: str) -> bool:
|
||||
"""根据种子名称从种子仓库中删除种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserSeedByName 删除失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def _deleteUserSeedByName(cls, uid: str, seed: str) -> bool:
|
||||
"""根据种子名称从种子仓库中删除种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户uid
|
||||
seed (str): 种子名称
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("deleteUserSeedByName 删除失败!", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,290 @@
|
||||
import calendar
|
||||
from datetime import timedelta
|
||||
import random
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
|
||||
from ...utils.config import g_bIsDebug
|
||||
from ...utils.json import g_pJsonManager
|
||||
from ...utils.tool import g_pToolManager
|
||||
from ..dbService import g_pDBService
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserSignDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
# userSignLog 表结构,每条为一次签到事件
|
||||
userSignLog = {
|
||||
"uid": "TEXT NOT NULL", # 用户ID
|
||||
"signDate": "DATE NOT NULL", # 签到日期
|
||||
"isSupplement": "TINYINT NOT NULL DEFAULT 0", # 是否补签
|
||||
"exp": "INT NOT NULL DEFAULT 0", # 当天签到经验
|
||||
"point": "INT NOT NULL DEFAULT 0", # 当天签到金币
|
||||
"createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 创建时间 # noqa: E501
|
||||
"PRIMARY KEY": "(uid, signDate)",
|
||||
}
|
||||
|
||||
# userSignSummary 表结构,每用户一行用于缓存签到状态
|
||||
userSignSummary = {
|
||||
"uid": "TEXT PRIMARY KEY NOT NULL", # 用户ID
|
||||
"totalSignDays": "INT NOT NULL DEFAULT 0", # 累计签到天数
|
||||
"currentMonth": "CHAR(7) NOT NULL DEFAULT ''", # 当前月份(如2025-05)
|
||||
"monthSignDays": "INT NOT NULL DEFAULT 0", # 本月签到次数
|
||||
"lastSignDate": "DATE DEFAULT NULL", # 上次签到日期
|
||||
"continuousDays": "INT NOT NULL DEFAULT 0", # 连续签到天数
|
||||
"supplementCount": "INT NOT NULL DEFAULT 0", # 补签次数
|
||||
"updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 更新时间 # noqa: E501
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userSignLog", userSignLog)
|
||||
await cls.ensureTableSchema("userSignSummary", userSignSummary)
|
||||
|
||||
@classmethod
|
||||
async def getUserSignRewardByDate(cls, uid: str, date: str) -> tuple[int, int]:
|
||||
"""根据指定日期获取用户签到随机奖励
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
date (str): 用户签到日期 示例:2025-05-27
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: 经验、金币
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
async with cls.m_pDB.execute(
|
||||
"SELECT exp, point FROM userSignLog WHERE uid=? AND signDate=?",
|
||||
(uid, date),
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if row is None:
|
||||
return 0, 0
|
||||
|
||||
exp = row["exp"]
|
||||
point = row["point"]
|
||||
|
||||
return exp, point
|
||||
except Exception as e:
|
||||
logger.warning("获取用户签到数据失败", e=e)
|
||||
return 0, 0
|
||||
|
||||
@classmethod
|
||||
async def getUserSignCountByDate(cls, uid: str, monthStr: str) -> int:
|
||||
"""根据日期查询用户签到总天数
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
monthStr (str): 需要查询的日期 示例: 2025-05
|
||||
|
||||
Returns:
|
||||
int: 查询月总签到天数
|
||||
"""
|
||||
try:
|
||||
sql = "SELECT COUNT(*) FROM userSignLog WHERE uid=? AND signDate LIKE ?"
|
||||
param = f"{monthStr}-%"
|
||||
async with cls.m_pDB.execute(sql, (uid, param)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
except Exception as e:
|
||||
logger.warning("统计用户月签到次数失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def hasSigned(cls, uid: str, signDate: str) -> bool:
|
||||
"""判断指定日期是否已签到
|
||||
|
||||
Args:
|
||||
uid (int): 用户ID
|
||||
signDate (str): 日期字符串 'YYYY-MM-DD'
|
||||
|
||||
Returns:
|
||||
bool: True=已签到,False=未签到
|
||||
"""
|
||||
try:
|
||||
sql = "SELECT 1 FROM userSignLog WHERE uid=? AND signDate=? LIMIT 1"
|
||||
async with cls.m_pDB.execute(sql, (uid, signDate)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
return row is not None
|
||||
except Exception as e:
|
||||
logger.warning("查询是否已签到失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def sign(cls, uid: str, signDate: str = "") -> int:
|
||||
"""签到
|
||||
|
||||
Args:
|
||||
uid (int): 用户ID
|
||||
signDate (str): 日期字符串 'YYYY-MM-DD' 不传默认当前系统日期
|
||||
|
||||
Returns:
|
||||
bool: 0: 签到失败 1: 签到成功 2: 重复签到
|
||||
"""
|
||||
try:
|
||||
if not signDate:
|
||||
signDate = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
|
||||
if await cls.hasSigned(uid, signDate):
|
||||
return 2
|
||||
|
||||
todayStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
|
||||
isSupplement = 0 if signDate == todayStr else 1
|
||||
|
||||
expMax, expMin, pointMax, pointMin = [
|
||||
g_pJsonManager.m_pSign.get(key, default)
|
||||
for key, default in (
|
||||
("exp_max", 50),
|
||||
("exp_min", 5),
|
||||
("point_max", 2000),
|
||||
("point_min", 200),
|
||||
)
|
||||
]
|
||||
|
||||
exp = random.randint(expMin, expMax)
|
||||
point = random.randint(pointMin, pointMax)
|
||||
vipPoint = 0
|
||||
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"INSERT INTO userSignLog (uid, signDate, isSupplement, exp, point) VALUES (?, ?, ?, ?, ?)",
|
||||
(uid, signDate, isSupplement, exp, point),
|
||||
)
|
||||
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT * FROM userSignSummary WHERE uid=?", (uid,)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
currentMonth = signDate[:7]
|
||||
if row:
|
||||
monthSignDays = (
|
||||
row["monthSignDays"] + 1
|
||||
if row["currentMonth"] == currentMonth
|
||||
else 1
|
||||
)
|
||||
lastDate = row["lastSignDate"]
|
||||
prevDate = (
|
||||
g_pToolManager.dateTime().strptime(signDate, "%Y-%m-%d")
|
||||
- timedelta(days=1)
|
||||
).strftime("%Y-%m-%d")
|
||||
continuousDays = (
|
||||
row["continuousDays"] + 1 if lastDate == prevDate else 1
|
||||
)
|
||||
supplementCount = (
|
||||
row["supplementCount"] + 1
|
||||
if isSupplement
|
||||
else row["supplementCount"]
|
||||
)
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
UPDATE userSignSummary
|
||||
SET totalSignDays=totalSignDays+1,
|
||||
currentMonth=?,
|
||||
monthSignDays=?,
|
||||
lastSignDate=?,
|
||||
continuousDays=?,
|
||||
supplementCount=?
|
||||
WHERE uid=?
|
||||
""",
|
||||
(
|
||||
currentMonth,
|
||||
monthSignDays,
|
||||
signDate,
|
||||
continuousDays,
|
||||
supplementCount,
|
||||
uid,
|
||||
),
|
||||
)
|
||||
else:
|
||||
monthSignDays = 1
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
INSERT INTO userSignSummary
|
||||
(uid, totalSignDays, currentMonth, monthSignDays, lastSignDate, continuousDays, supplementCount)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
uid,
|
||||
1,
|
||||
currentMonth,
|
||||
monthSignDays,
|
||||
signDate,
|
||||
1,
|
||||
1 if isSupplement else 0,
|
||||
),
|
||||
)
|
||||
|
||||
# 计算累签奖励
|
||||
reward = g_pJsonManager.m_pSign["continuou"].get(f"{monthSignDays}", None)
|
||||
if reward:
|
||||
point += reward.get("point", 0)
|
||||
exp += reward.get("exp", 0)
|
||||
vipPoint = reward.get("vipPoint", 0)
|
||||
|
||||
plant = reward.get("plant", {})
|
||||
if plant:
|
||||
for key, value in plant.items():
|
||||
await g_pDBService.userSeed.addUserSeedByUid(uid, key, value)
|
||||
|
||||
if g_bIsDebug:
|
||||
exp += 9999
|
||||
|
||||
# 向数据库更新
|
||||
currentExp = await g_pDBService.user.getUserExpByUid(uid)
|
||||
await g_pDBService.user.updateUserExpByUid(uid, currentExp + exp)
|
||||
|
||||
currentPoint = await g_pDBService.user.getUserPointByUid(uid)
|
||||
await g_pDBService.user.updateUserPointByUid(uid, currentPoint + point)
|
||||
|
||||
if vipPoint > 0:
|
||||
currentVipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
|
||||
await g_pDBService.user.updateUserVipPointByUid(
|
||||
uid, currentVipPoint + vipPoint
|
||||
)
|
||||
|
||||
return 1
|
||||
except Exception as e:
|
||||
logger.warning("执行签到失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def drawSignCalendarImage(cls, uid: str, year: int, month: int):
|
||||
# 绘制签到图,自动提取数据库中该用户该月的签到天数
|
||||
cellSize = 80
|
||||
padding = 40
|
||||
titleHeight = 80
|
||||
cols = 7
|
||||
rows = 6
|
||||
width = cellSize * cols + padding * 2
|
||||
height = cellSize * rows + padding * 2 + titleHeight
|
||||
|
||||
img = BuildImage(width, height, color=(255, 255, 255))
|
||||
await img.text((padding, 20), f"{year}年{month}月签到表", font_size=36)
|
||||
|
||||
firstWeekday, totalDays = calendar.monthrange(year, month)
|
||||
monthStr = f"{year:04d}-{month:02d}"
|
||||
try:
|
||||
sql = "SELECT signDate FROM userSignLog WHERE uid=? AND signDate LIKE ?"
|
||||
async with cls.m_pDB.execute(sql, (uid, f"{monthStr}-%")) as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
signedDays = {int(r[0][-2:]) for r in rows if r[0][-2:].isdigit()}
|
||||
except Exception as e:
|
||||
logger.warning("绘制签到图时数据库查询失败", e=e)
|
||||
signedDays = set()
|
||||
|
||||
for day in range(1, totalDays + 1):
|
||||
index = day + firstWeekday - 1
|
||||
row = index // cols
|
||||
col = index % cols
|
||||
x1 = padding + col * cellSize
|
||||
y1 = padding + titleHeight + row * cellSize
|
||||
x2 = x1 + cellSize - 10
|
||||
y2 = y1 + cellSize - 10
|
||||
color = (112, 196, 112) if day in signedDays else (220, 220, 220)
|
||||
await img.rectangle((x1, y1, x2, y2), fill=color, outline="black", width=2)
|
||||
await img.text((x1 + 10, y1 + 10), str(day), font_size=24)
|
||||
|
||||
return img
|
||||
@@ -0,0 +1,591 @@
|
||||
import math
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..dbService import g_pDBService
|
||||
from ..utils.config import g_bIsDebug
|
||||
from ..utils.tool import g_pToolManager
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserSoilDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userSoil = {
|
||||
"uid": "TEXT NOT NULL",
|
||||
"soilIndex": "INTEGER NOT NULL", # 地块索引从1开始
|
||||
"plantName": "TEXT DEFAULT ''", # 作物名称
|
||||
"plantTime": "INTEGER DEFAULT 0", # 播种时间
|
||||
"matureTime": "INTEGER DEFAULT 0", # 成熟时间
|
||||
"soilLevel": "INTEGER DEFAULT 0", # 土地等级 0=普通地,1=红土地,2=黑土地,3=金土地
|
||||
"wiltStatus": "INTEGER DEFAULT 0", # 枯萎状态 0=未枯萎,1=枯萎
|
||||
"fertilizerStatus": "INTEGER DEFAULT 0", # 施肥状态 0=未施肥,1=施肥 2=增肥
|
||||
"bugStatus": "INTEGER DEFAULT 0", # 虫害状态 0=无虫害,1=有虫害
|
||||
"weedStatus": "INTEGER DEFAULT 0", # 杂草状态 0=无杂草,1=有杂草
|
||||
"waterStatus": "INTEGER DEFAULT 0", # 缺水状态 0=不缺水,1=缺水
|
||||
"harvestCount": "INTEGER DEFAULT 0", # 收获次数
|
||||
"isSoilPlanted": "INTEGER DEFAULT NULL", # 是否种植作物 0=没有,1=有
|
||||
"PRIMARY KEY": "(uid, soilIndex)",
|
||||
}
|
||||
|
||||
await cls.ensureTableSchema("userSoil", userSoil)
|
||||
|
||||
@classmethod
|
||||
async def nextPhase(cls, uid: str, soilIndex: int):
|
||||
"""将指定地块的作物进入下个阶段
|
||||
|
||||
Args:
|
||||
soilIndex (int): 地块索引 从1开始
|
||||
"""
|
||||
if not g_bIsDebug:
|
||||
return
|
||||
|
||||
soilInfo = await cls.getUserSoil(uid, soilIndex)
|
||||
|
||||
if not soilInfo:
|
||||
return
|
||||
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(soilInfo["plantName"])
|
||||
|
||||
if not plantInfo:
|
||||
return
|
||||
|
||||
currentTime = g_pToolManager.dateTime().now().timestamp()
|
||||
phaseList = await g_pDBService.plant.getPlantPhaseByName(soilInfo["plantName"])
|
||||
|
||||
if currentTime >= soilInfo["matureTime"]:
|
||||
return
|
||||
|
||||
elapsedTime = currentTime - soilInfo["plantTime"]
|
||||
currentStage = currentStage = sum(1 for thr in phaseList if elapsedTime >= thr)
|
||||
|
||||
t = int(soilInfo["plantTime"]) - phaseList[currentStage]
|
||||
s = int(soilInfo["matureTime"]) - phaseList[currentStage]
|
||||
|
||||
await cls.updateUserSoilFields(
|
||||
uid, soilIndex, {"plantTime": t, "matureTime": s}
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def matureNow(cls, uid: str, soilIndex: int):
|
||||
"""将指定地块的作物直接成熟
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 地块索引(从1开始)
|
||||
"""
|
||||
# 与 nextPhase 不同:无需调试模式检查,允许在任何模式下调用
|
||||
soilInfo = await cls.getUserSoil(uid, soilIndex)
|
||||
if not soilInfo:
|
||||
return
|
||||
|
||||
plantName = soilInfo.get("plantName")
|
||||
if not plantName:
|
||||
return
|
||||
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(plantName)
|
||||
if not plantInfo:
|
||||
return
|
||||
|
||||
currentTime = int(g_pToolManager.dateTime().now().timestamp())
|
||||
# 如果当前时间已经超过或等于成熟时间,则作物已成熟或可收获
|
||||
if currentTime >= soilInfo["matureTime"]:
|
||||
return
|
||||
|
||||
# 将作物成熟时间直接更新为当前时间,实现立即成熟
|
||||
await cls.updateUserSoilFields(uid, soilIndex, {"matureTime": currentTime})
|
||||
|
||||
@classmethod
|
||||
async def getUserFarmByUid(cls, uid: str) -> dict:
|
||||
"""获取指定用户的旧农场数据
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
|
||||
Returns:
|
||||
dict: 包含字段名-值的字典; 若无数据则返回空字典
|
||||
"""
|
||||
cursor = await cls.m_pDB.execute("SELECT * FROM soil WHERE uid = ?", (uid,))
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return {}
|
||||
columns = [description[0] for description in cursor.description]
|
||||
return dict(zip(columns, row))
|
||||
|
||||
@classmethod
|
||||
async def migrateOldFarmData(cls) -> bool:
|
||||
"""迁移旧土地数据到新表 userSoil 并删除旧表
|
||||
|
||||
Returns:
|
||||
bool: 如果旧表不存在则返回 False,否则迁移并删除后返回 True
|
||||
"""
|
||||
# 检查旧表是否存在
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='soil'"
|
||||
)
|
||||
if not await cursor.fetchone():
|
||||
return False
|
||||
|
||||
async with cls._transaction():
|
||||
users = await g_pDBService.user.getAllUsers()
|
||||
|
||||
for uid in users:
|
||||
farmInfo = await cls.getUserFarmByUid(uid)
|
||||
for i in range(1, 31):
|
||||
key = f"soil{i}"
|
||||
data = farmInfo.get(key)
|
||||
if not data:
|
||||
continue
|
||||
|
||||
if data == ",,,4,":
|
||||
continue
|
||||
|
||||
parts = data.split(",")
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
name = parts[0]
|
||||
pt = int(parts[1])
|
||||
mt = int(parts[2])
|
||||
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
INSERT INTO userSoil
|
||||
(uid,soilIndex,plantName,plantTime,matureTime,harvestCount)
|
||||
VALUES (?,?,?,?,?,?)
|
||||
""",
|
||||
(uid, i, name, pt, mt, 0),
|
||||
)
|
||||
|
||||
await cls.m_pDB.execute("DROP TABLE soil")
|
||||
|
||||
logger.info("数据库迁移完毕!")
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def insertUserSoil(cls, soilInfo: dict):
|
||||
"""插入一条新的 userSoil 记录
|
||||
|
||||
Args:
|
||||
soilInfo (dict): 新土地数据
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
INSERT INTO userSoil
|
||||
(uid, soilIndex, plantName, plantTime, matureTime,
|
||||
soilLevel, wiltStatus, fertilizerStatus, bugStatus,
|
||||
weedStatus, waterStatus, harvestCount, isSoilPlanted)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
soilInfo["uid"],
|
||||
soilInfo["soilIndex"],
|
||||
soilInfo.get("plantName", ""),
|
||||
soilInfo.get("plantTime", 0),
|
||||
soilInfo.get("matureTime", 0),
|
||||
soilInfo.get("soilLevel", 0),
|
||||
soilInfo.get("wiltStatus", 0),
|
||||
soilInfo.get("fertilizerStatus", 0),
|
||||
soilInfo.get("bugStatus", 0),
|
||||
soilInfo.get("weedStatus", 0),
|
||||
soilInfo.get("waterStatus", 0),
|
||||
soilInfo.get("harvestCount", 0),
|
||||
soilInfo.get("isSoilPlanted", 0),
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _insertUserSoil(cls, soilInfo: dict):
|
||||
"""插入一条新的 userSoil 记录
|
||||
|
||||
Args:
|
||||
soilInfo (dict): 新土地数据
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
await cls.m_pDB.execute(
|
||||
"""
|
||||
INSERT INTO userSoil
|
||||
(uid, soilIndex, plantName, plantTime, matureTime,
|
||||
soilLevel, wiltStatus, fertilizerStatus, bugStatus,
|
||||
weedStatus, waterStatus, harvestCount, isSoilPlanted)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
soilInfo["uid"],
|
||||
soilInfo["soilIndex"],
|
||||
soilInfo.get("plantName", ""),
|
||||
soilInfo.get("plantTime", 0),
|
||||
soilInfo.get("matureTime", 0),
|
||||
soilInfo.get("soilLevel", 0),
|
||||
soilInfo.get("wiltStatus", 0),
|
||||
soilInfo.get("fertilizerStatus", 0),
|
||||
soilInfo.get("bugStatus", 0),
|
||||
soilInfo.get("weedStatus", 0),
|
||||
soilInfo.get("waterStatus", 0),
|
||||
soilInfo.get("harvestCount", 0),
|
||||
soilInfo.get("isSoilPlanted", 0),
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def getUserSoil(cls, uid: str, soilIndex: int) -> dict:
|
||||
"""获取指定用户某块土地的详细信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
dict: 记录存在返回字段-值字典,否则返回 None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?",
|
||||
(uid, soilIndex),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
return {}
|
||||
columns = [description[0] for description in cursor.description]
|
||||
return dict(zip(columns, row))
|
||||
|
||||
@classmethod
|
||||
async def _getUserSoil(cls, uid: str, soilIndex: int) -> dict | None:
|
||||
"""获取指定用户某块土地的详细信息
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
dict | None: 记录存在返回字段-值字典,否则返回 None
|
||||
"""
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?",
|
||||
(uid, soilIndex),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
columns = [description[0] for description in cursor.description]
|
||||
return dict(zip(columns, row))
|
||||
|
||||
@classmethod
|
||||
async def countSoilByLevel(cls, uid: str, soilLevel: int) -> int:
|
||||
"""统计指定用户在指定土地等级的土地数量
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilLevel (int): 土地等级
|
||||
|
||||
Returns:
|
||||
int: 符合条件的土地数量
|
||||
"""
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
"SELECT COUNT(*) FROM userSoil WHERE uid = ? AND soilLevel = ?",
|
||||
(uid, soilLevel),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
|
||||
@classmethod
|
||||
async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value):
|
||||
"""更新指定用户土地的单个字段
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
field (str): 需更新的字段名
|
||||
value: 新值
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
f"UPDATE userSoil SET {field} = ? WHERE uid = ? AND soilIndex = ?",
|
||||
(value, uid, soilIndex),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _updateUserSoil(cls, uid: str, soilIndex: int, field: str, value):
|
||||
"""更新指定用户土地的单个字段
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
field (str): 需更新的字段名
|
||||
value: 新值
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
await cls.m_pDB.execute(
|
||||
f"UPDATE userSoil SET {field} = ? WHERE uid = ? AND soilIndex = ?",
|
||||
(value, uid, soilIndex),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def updateUserSoilFields(
|
||||
cls, uid: str, soilIndex: int, updates: dict
|
||||
) -> bool:
|
||||
"""批量更新指定用户土地的多个字段
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
updates (dict): 字段-新值的字典
|
||||
|
||||
Returns:
|
||||
bool: 如果无可更新字段则返回 False,否则更新成功返回 True
|
||||
"""
|
||||
# 允许更新的列白名单
|
||||
allowedFields = {
|
||||
"plantName",
|
||||
"plantTime",
|
||||
"matureTime",
|
||||
"soilLevel",
|
||||
"wiltStatus",
|
||||
"fertilizerStatus",
|
||||
"bugStatus",
|
||||
"weedStatus",
|
||||
"waterStatus",
|
||||
"harvestCount",
|
||||
"isSoilPlanted",
|
||||
}
|
||||
setClauses = []
|
||||
values = []
|
||||
for field, value in updates.items():
|
||||
if field not in allowedFields:
|
||||
continue
|
||||
setClauses.append(f'"{field}" = ?')
|
||||
values.append(value)
|
||||
if not setClauses:
|
||||
return False
|
||||
|
||||
values.extend([uid, soilIndex])
|
||||
sql = f"UPDATE userSoil SET {', '.join(setClauses)} WHERE uid = ? AND soilIndex = ?"
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(sql, tuple(values))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"批量更新土地字段失败: {e}")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteUserSoil(cls, uid: str, soilIndex: int):
|
||||
"""删除指定用户的土地记录
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _deleteUserSoil(cls, uid: str, soilIndex: int):
|
||||
"""删除指定用户的土地记录
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地索引
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
await cls.m_pDB.execute(
|
||||
"DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def sowingByPlantName(cls, uid: str, soilIndex: int, plantName: str) -> bool:
|
||||
"""播种指定作物到用户土地区
|
||||
|
||||
Args:
|
||||
uid (str): 用户ID
|
||||
soilIndex (int): 土地区索引
|
||||
plantName (str): 植物名
|
||||
|
||||
Returns:
|
||||
bool: 播种成功返回 True,否则返回 False
|
||||
"""
|
||||
# 校验土地区是否已种植
|
||||
soilInfo = await cls.getUserSoil(uid, soilIndex)
|
||||
if soilInfo and soilInfo.get("plantName"):
|
||||
return False
|
||||
|
||||
# 获取植物配置
|
||||
plantCfg = await g_pDBService.plant.getPlantByName(plantName)
|
||||
if not plantCfg:
|
||||
logger.error(f"未知植物: {plantName}")
|
||||
return False
|
||||
|
||||
nowTs = int(g_pToolManager.dateTime().now().timestamp())
|
||||
|
||||
time = int(plantCfg.get("time", 0))
|
||||
percent = await cls.getSoilLevelTime(soilInfo.get("soilLevel", 0))
|
||||
|
||||
# 处理土地等级带来的时间缩短
|
||||
time = math.floor(time * (100 + percent) // 100)
|
||||
|
||||
matureTs = nowTs + time * 3600
|
||||
|
||||
try:
|
||||
async with cls._transaction():
|
||||
prev = soilInfo or {}
|
||||
await cls._deleteUserSoil(uid, soilIndex)
|
||||
await cls._insertUserSoil(
|
||||
{
|
||||
"uid": uid,
|
||||
"soilIndex": soilIndex,
|
||||
"plantName": plantName,
|
||||
"plantTime": nowTs,
|
||||
"matureTime": matureTs,
|
||||
"soilLevel": prev.get("soilLevel", 0),
|
||||
"wiltStatus": 0,
|
||||
"fertilizerStatus": 0,
|
||||
"bugStatus": 0,
|
||||
"weedStatus": 0,
|
||||
"waterStatus": 0,
|
||||
"harvestCount": 0,
|
||||
"isSoilPlanted": 1,
|
||||
}
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("播种失败!", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getUserSoilStatus(cls, uid: str, soilIndex: int) -> str:
|
||||
status = []
|
||||
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
|
||||
|
||||
if not soilInfo:
|
||||
return ""
|
||||
|
||||
if soilInfo.get("wiltStatus", 0) == 1:
|
||||
return "枯萎"
|
||||
|
||||
if soilInfo.get("fertilizerStatus", 0) == 1:
|
||||
status.append("施肥")
|
||||
elif soilInfo.get("fertilizerStatus", 0) == 2:
|
||||
status.append("增肥")
|
||||
|
||||
if soilInfo.get("bugStatus", 0) == 1:
|
||||
status.append("虫害")
|
||||
|
||||
if soilInfo.get("weedStatus", 0) == 1:
|
||||
status.append("杂草")
|
||||
|
||||
if soilInfo.get("waterStatus", 0) == 1:
|
||||
status.append("缺水")
|
||||
|
||||
return ",".join(status)
|
||||
|
||||
@classmethod
|
||||
async def getSoilLevel(cls, level: int) -> str:
|
||||
"""获取土地等级英文文本
|
||||
|
||||
Args:
|
||||
level (int): 土地等级
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if level == 1:
|
||||
return "red"
|
||||
elif level == 2:
|
||||
return "black"
|
||||
elif level == 3:
|
||||
return "gold"
|
||||
|
||||
return "default"
|
||||
|
||||
@classmethod
|
||||
async def getSoilLevelText(cls, level: int) -> str:
|
||||
"""获取土地等级中文文本
|
||||
|
||||
Args:
|
||||
level (int): 土地等级
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if level == 1:
|
||||
return "红土地"
|
||||
elif level == 2:
|
||||
return "黑土地"
|
||||
elif level == 3:
|
||||
return "金土地"
|
||||
|
||||
return "草土地"
|
||||
|
||||
@classmethod
|
||||
async def getSoilLevelHarvestNumber(cls, level: int) -> int:
|
||||
"""获取土地等级收获数量增加比例
|
||||
|
||||
Args:
|
||||
level (int): 土地等级
|
||||
|
||||
Returns:
|
||||
int:
|
||||
"""
|
||||
if level == 2:
|
||||
return 20
|
||||
elif level == 3:
|
||||
return 28
|
||||
|
||||
return 10
|
||||
|
||||
@classmethod
|
||||
async def getSoilLevelHarvestExp(cls, level: int) -> int:
|
||||
"""获取土地等级收获经验增加比例
|
||||
|
||||
Args:
|
||||
level (int): 土地等级
|
||||
|
||||
Returns:
|
||||
int:
|
||||
"""
|
||||
if level == 3:
|
||||
return 28
|
||||
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def getSoilLevelTime(cls, level: int) -> int:
|
||||
"""获取土地等级播种减少时间消耗
|
||||
|
||||
Args:
|
||||
level (int): 土地等级
|
||||
|
||||
Returns:
|
||||
int:
|
||||
"""
|
||||
if level == 2:
|
||||
return 20
|
||||
elif level == 3:
|
||||
return 20
|
||||
|
||||
return 0
|
||||
@@ -0,0 +1,222 @@
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .database import CSqlManager
|
||||
|
||||
|
||||
class CUserStealDB(CSqlManager):
|
||||
@classmethod
|
||||
async def initDB(cls):
|
||||
userSteal = {
|
||||
"uid": "TEXT NOT NULL", # 被偷用户Uid
|
||||
"soilIndex": "INTEGER NOT NULL", # 被偷的地块索引 从1开始
|
||||
"stealerUid": "TEXT NOT NULL", # 偷菜用户Uid
|
||||
"stealCount": "INTEGER NOT NULL", # 被偷数量
|
||||
"stealTime": "INTEGER NOT NULL", # 被偷时间
|
||||
"PRIMARY KEY": "(uid, soilIndex, stealerUid)",
|
||||
}
|
||||
await cls.ensureTableSchema("userSteal", userSteal)
|
||||
|
||||
@classmethod
|
||||
async def addStealRecord(
|
||||
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
|
||||
) -> bool:
|
||||
"""添加偷菜记录
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
stealerUid (str): 偷菜用户Uid
|
||||
stealCount (int): 被偷数量
|
||||
stealTime (int): 被偷时间(时间戳)
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
'INSERT INTO "userSteal"(uid, soilIndex, stealerUid, stealCount, stealTime) VALUES(?, ?, ?, ?, ?);',
|
||||
(uid, soilIndex, stealerUid, stealCount, stealTime),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("添加偷菜记录失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def getStealRecordsByUid(cls, uid: str) -> dict:
|
||||
"""根据用户Uid获取所有偷菜记录
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
|
||||
Returns:
|
||||
list: 偷菜记录字典列表,每条包含 soilIndex, stealerUid, stealCount, stealTime
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;',
|
||||
(uid,),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return {}
|
||||
|
||||
result = dict(row)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning("获取偷菜记录失败", e=e)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def getStealRecord(cls, uid: str, soilIndex: int) -> list:
|
||||
"""获取指定地块的所有偷菜记录
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
list: 偷菜记录字典列表,每条包含 stealerUid, stealCount, stealTime
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
return [
|
||||
{
|
||||
"uid": uid,
|
||||
"soilIndex": soilIndex,
|
||||
"stealerUid": row[0],
|
||||
"stealCount": row[1],
|
||||
"stealTime": row[2],
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
except Exception as e:
|
||||
logger.warning("获取单地块偷菜记录失败", e=e)
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def getTotalStolenCount(cls, uid: str, soilIndex: int) -> int:
|
||||
"""计算指定地块被偷的总数量(所有用户偷取数量之和)
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
int: 被偷的总数量,如果无记录则返回 0
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT SUM(stealCount) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row[0] or 0 # type: ignore
|
||||
except Exception as e:
|
||||
logger.warning("计算总偷菜数量失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def getStealerCount(cls, uid: str, soilIndex: int) -> int:
|
||||
"""计算指定地块被多少人偷过(不同偷菜用户数量)
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
int: 偷菜者总数,如果无记录则返回 0
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT COUNT(DISTINCT stealerUid) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return row[0] or 0 # type: ignore
|
||||
except Exception as e:
|
||||
logger.warning("计算偷菜者数量失败", e=e)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def hasStealed(cls, uid: str, soilIndex: int, stealerUid: str) -> bool:
|
||||
"""判断指定用户是否曾偷取过该地块
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
stealerUid (str): 偷菜用户Uid
|
||||
|
||||
Returns:
|
||||
bool: 若存在记录返回 True,否则返回 False
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
cursor = await cls.m_pDB.execute(
|
||||
'SELECT 1 FROM "userSteal" WHERE uid=? AND soilIndex=? AND stealerUid=? LIMIT 1;',
|
||||
(uid, soilIndex, stealerUid),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return bool(row)
|
||||
except Exception as e:
|
||||
logger.warning("检查偷菜记录失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def updateStealRecord(
|
||||
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
|
||||
) -> bool:
|
||||
"""更新偷菜记录的数量和时间
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
stealerUid (str): 偷菜用户Uid
|
||||
stealCount (int): 新的偷菜数量
|
||||
stealTime (int): 新的偷菜时间(时间戳)
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
'UPDATE "userSteal" SET stealCount=?, stealTime=? WHERE uid=? AND soilIndex=? AND stealerUid=?;',
|
||||
(stealCount, stealTime, uid, soilIndex, stealerUid),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("更新偷菜记录失败", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def deleteStealRecord(cls, uid: str, soilIndex: int) -> bool:
|
||||
"""删除指定偷菜记录(只需被偷用户Uid和地块索引)
|
||||
|
||||
Args:
|
||||
uid (str): 被偷用户Uid
|
||||
soilIndex (int): 被偷地块索引
|
||||
|
||||
Returns:
|
||||
bool: 删除是否成功
|
||||
"""
|
||||
try:
|
||||
async with cls._transaction():
|
||||
await cls.m_pDB.execute(
|
||||
'DELETE FROM "userSteal" WHERE uid=? AND soilIndex=?;',
|
||||
(uid, soilIndex),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("删除偷菜记录失败", e=e)
|
||||
return False
|
||||
@@ -0,0 +1,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()
|
||||
+1223
File diff suppressed because it is too large
Load Diff
+123
@@ -0,0 +1,123 @@
|
||||
from pathlib import Path
|
||||
|
||||
from jinja2 import Template
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ..utils.config import g_sResourcePath
|
||||
|
||||
|
||||
class CHelpManager:
|
||||
@classmethod
|
||||
def renderHtmlToFile(
|
||||
cls, path: Path | str, context: dict, output: Path | str
|
||||
) -> None:
|
||||
"""
|
||||
使用 Jinja2 渲染 HTML 模板并保存到指定文件,会自动创建父目录
|
||||
|
||||
Args:
|
||||
path (str): 模板 HTML 路径
|
||||
context (dict): 用于渲染的上下文字典
|
||||
output (str): 输出 HTML 文件路径
|
||||
"""
|
||||
templatePath = str(path)
|
||||
outputPath = str(output)
|
||||
|
||||
templateStr = Path(templatePath).read_text(encoding="utf-8")
|
||||
template = Template(templateStr)
|
||||
rendered = template.render(**context)
|
||||
|
||||
# 自动创建目录
|
||||
Path(outputPath).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Path(outputPath).write_text(rendered, encoding="utf-8")
|
||||
|
||||
@classmethod
|
||||
async def screenshotHtmlToBytes(cls, path: str) -> bytes:
|
||||
"""
|
||||
使用 Playwright 截图本地 HTML 文件并返回 PNG 图片字节数据
|
||||
|
||||
Args:
|
||||
path (str): 本地 HTML 文件路径
|
||||
|
||||
Returns:
|
||||
bytes: PNG 图片的原始字节内容
|
||||
"""
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch()
|
||||
page = await browser.new_page(
|
||||
viewport={"width": 1200, "height": 900}, device_scale_factor=1
|
||||
)
|
||||
file_url = Path(path).resolve().as_uri()
|
||||
await page.goto(file_url, wait_until="networkidle")
|
||||
await page.evaluate("""() => {
|
||||
return new Promise(r => setTimeout(r, 200));
|
||||
}""")
|
||||
image_bytes = await page.screenshot(full_page=True)
|
||||
await browser.close()
|
||||
|
||||
return image_bytes
|
||||
|
||||
@classmethod
|
||||
async def screenshotSave(
|
||||
cls, path: str, save: str, width: int, height: int
|
||||
) -> None:
|
||||
"""
|
||||
使用 Playwright 渲染本地 HTML 并将截图保存到指定路径
|
||||
|
||||
Args:
|
||||
path (str): HTML 文件路径
|
||||
save (str): PNG 保存路径(如 output/image.png)
|
||||
width (int): 图片宽度
|
||||
height (int): 图片高度
|
||||
"""
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch()
|
||||
page = await browser.new_page(
|
||||
viewport={"width": width, "height": height}, device_scale_factor=1
|
||||
)
|
||||
|
||||
file_url = Path(path).resolve().as_uri()
|
||||
await page.goto(file_url, wait_until="networkidle")
|
||||
await page.evaluate("""() => {
|
||||
return new Promise(r => setTimeout(r, 200));
|
||||
}""")
|
||||
|
||||
# 确保保存目录存在
|
||||
Path(save).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 截图并保存到本地文件
|
||||
await page.screenshot(path=save, full_page=True)
|
||||
await browser.close()
|
||||
|
||||
@classmethod
|
||||
async def createHelpImage(cls) -> bool:
|
||||
templatePath = g_sResourcePath / "html/help.html"
|
||||
outputPath = g_sResourcePath / "temp_html/help.html"
|
||||
savePath = DATA_PATH / "farm_res/html/help.png"
|
||||
|
||||
context = {
|
||||
"main_title": "真寻农场帮助菜单",
|
||||
"subtitle": "[]中为可选参数",
|
||||
"page_title": "真寻农场帮助菜单",
|
||||
"font_family": "MyFont",
|
||||
"contents": [
|
||||
{"title": "主要指令", "commands": ["指令A", "指令B"]},
|
||||
{"title": "B", "commands": ["指令D", "指令E", "指令M", "指令i"]},
|
||||
],
|
||||
}
|
||||
|
||||
try:
|
||||
cls.renderHtmlToFile(templatePath, context, outputPath)
|
||||
|
||||
bytes = await cls.screenshotSave(str(outputPath), str(savePath), 1500, 2300)
|
||||
except Exception as e:
|
||||
logger.warning("绘制农场帮助菜单失败", e=e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
g_pHelpManager = CHelpManager()
|
||||
@@ -0,0 +1,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)
|
||||
@@ -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()
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
import math
|
||||
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
|
||||
from ..core.dbService import g_pDBService
|
||||
from ..utils.config import g_sResourcePath, g_sTranslation
|
||||
from ..utils.tool import g_pToolManager
|
||||
|
||||
|
||||
class CShopManager:
|
||||
@classmethod
|
||||
async def getSeedShopImage(cls, filterKey: str | int = 1, num: int = 1) -> bytes:
|
||||
"""获取商店页面
|
||||
|
||||
Args:
|
||||
filterKey (str|int):
|
||||
- 字符串: 根据关键字筛选种子名称
|
||||
- 整数: 翻至对应页(无筛选)
|
||||
num (int, optional): 当 filterKey 为字符串时,用于指定页码。Defaults to 1.
|
||||
|
||||
Returns:
|
||||
bytes: 返回商店图片bytes
|
||||
"""
|
||||
# 解析参数:区分筛选关键字和页码
|
||||
filterStr = None
|
||||
if isinstance(filterKey, int):
|
||||
page = filterKey
|
||||
else:
|
||||
filterStr = filterKey
|
||||
page = num
|
||||
|
||||
# 表头定义
|
||||
columnName = [
|
||||
"-",
|
||||
"种子名称",
|
||||
"农场币",
|
||||
"点券",
|
||||
"解锁等级",
|
||||
"果实单价",
|
||||
"收获经验",
|
||||
"收获数量",
|
||||
"成熟时间(小时)",
|
||||
"收获次数",
|
||||
"是否可以上架交易行",
|
||||
]
|
||||
|
||||
# 查询所有可购买作物,并根据筛选关键字过滤
|
||||
plants = await g_pDBService.plant.listPlants()
|
||||
filteredPlants = []
|
||||
for plant in plants:
|
||||
# 跳过未解锁购买的种子
|
||||
if plant["isBuy"] == 0:
|
||||
continue
|
||||
# 字符串筛选
|
||||
if filterStr and filterStr not in plant["name"]:
|
||||
continue
|
||||
filteredPlants.append(plant)
|
||||
|
||||
# 计算分页
|
||||
totalCount = len(filteredPlants)
|
||||
pageCount = math.ceil(totalCount / 15) if totalCount else 1
|
||||
startIndex = (page - 1) * 15
|
||||
pageItems = filteredPlants[startIndex : startIndex + 15]
|
||||
|
||||
# 构建数据行
|
||||
dataList = []
|
||||
for plant in pageItems:
|
||||
# 图标处理
|
||||
icon = ""
|
||||
iconPath = g_sResourcePath / f"plant/{plant['name']}/icon.png"
|
||||
if iconPath.exists():
|
||||
icon = (iconPath, 33, 33)
|
||||
|
||||
# 交易行标记
|
||||
sell = "可以" if plant["sell"] else "不可以"
|
||||
|
||||
dataList.append(
|
||||
[
|
||||
icon,
|
||||
plant["name"], # 种子名称
|
||||
plant["buy"], # 农场币种子单价
|
||||
plant["vipBuy"], # 点券种子单价
|
||||
plant["level"], # 解锁等级
|
||||
plant["price"], # 果实单价
|
||||
plant["experience"], # 收获经验
|
||||
plant["harvest"], # 收获数量
|
||||
plant["time"], # 成熟时间(小时)
|
||||
plant["crop"], # 收获次数
|
||||
sell, # 是否可上架交易行
|
||||
]
|
||||
)
|
||||
|
||||
# 页码标题
|
||||
title = f"种子商店 页数: {page}/{pageCount}"
|
||||
|
||||
# 渲染表格并返回图片bytes
|
||||
result = await ImageTemplate.table_page(
|
||||
title,
|
||||
"购买示例:@小真寻 购买种子 大白菜 5",
|
||||
columnName,
|
||||
dataList,
|
||||
)
|
||||
return result.pic2bytes()
|
||||
|
||||
@classmethod
|
||||
async def buySeed(cls, uid: str, name: str, num: int = 1) -> str:
|
||||
"""购买种子
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
name (str): 植物名称
|
||||
num (int, optional): 购买数量
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if num <= 0:
|
||||
return g_sTranslation["buySeed"]["notNum"]
|
||||
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(name)
|
||||
if not plantInfo or not player:
|
||||
return g_sTranslation["buySeed"]["error"]
|
||||
|
||||
level = player.user.get("level", 0)
|
||||
|
||||
if level < int(plantInfo["level"]):
|
||||
return g_sTranslation["buySeed"]["noLevel"]
|
||||
|
||||
vipSeed = plantInfo.get("isVip", 0) == 1
|
||||
currencyType = "vipPoint" if vipSeed else "point"
|
||||
price = int(plantInfo["vipBuy" if vipSeed else "buy"])
|
||||
totalCost = price * num
|
||||
|
||||
currentCurrency = player.user.get(currencyType, 0)
|
||||
if currentCurrency < totalCost:
|
||||
return g_sTranslation["buySeed"][f"no{'Vip' if vipSeed else ''}Point"]
|
||||
|
||||
await player.addPoint(currencyType, currentCurrency - totalCost)
|
||||
|
||||
if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num):
|
||||
return g_sTranslation["buySeed"]["errorSql"]
|
||||
|
||||
success_key = "vipSuccess" if vipSeed else "success"
|
||||
remaining_currency = currentCurrency - totalCost
|
||||
|
||||
return g_sTranslation["buySeed"][success_key].format(
|
||||
name=name, total=totalCost, point=remaining_currency
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def sellPlantByUid(cls, uid: str, name: str = "", num: int = 1) -> str:
|
||||
"""出售作物
|
||||
|
||||
Args:
|
||||
uid (str): 用户Uid
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if not isinstance(name, str) or name.strip() == "":
|
||||
name = ""
|
||||
|
||||
plant = await g_pDBService.userPlant.getUserPlantByUid(uid)
|
||||
if not plant:
|
||||
return g_sTranslation["sellPlant"]["no"]
|
||||
|
||||
point = 0
|
||||
totalSold = 0
|
||||
isAll = num == -1
|
||||
|
||||
if name == "":
|
||||
for plantName, count in plant.items():
|
||||
isLock = await g_pDBService.userPlant.checkPlantLockByName(
|
||||
uid, plantName
|
||||
)
|
||||
|
||||
if isLock:
|
||||
continue
|
||||
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(plantName)
|
||||
if not plantInfo:
|
||||
continue
|
||||
|
||||
point += plantInfo["price"] * count
|
||||
await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0)
|
||||
else:
|
||||
if name not in plant:
|
||||
return g_sTranslation["sellPlant"]["error"].format(name=name)
|
||||
available = plant[name]
|
||||
sellAmount = available if isAll else min(available, num)
|
||||
if sellAmount <= 0:
|
||||
return g_sTranslation["sellPlant"]["error1"].format(name=name)
|
||||
await g_pDBService.userPlant.updateUserPlantByName(
|
||||
uid, name, available - sellAmount
|
||||
)
|
||||
totalSold = sellAmount
|
||||
|
||||
if name == "":
|
||||
totalPoint = point
|
||||
else:
|
||||
plantInfo = await g_pDBService.plant.getPlantByName(name)
|
||||
if not plantInfo:
|
||||
price = 0
|
||||
else:
|
||||
price = plantInfo["price"]
|
||||
|
||||
totalPoint = totalSold * price
|
||||
|
||||
player = await g_pToolManager.getPlayerByUid(uid)
|
||||
if not player:
|
||||
return g_sTranslation["basic"]["error"]
|
||||
|
||||
currentPoint = player.user.get("point", 0)
|
||||
await player.addPoint("point", currentPoint + totalPoint)
|
||||
|
||||
result = "success1" if name == "" else "success"
|
||||
|
||||
return g_sTranslation["sellPlant"][result].format(
|
||||
point=totalPoint, num=currentPoint + totalPoint
|
||||
)
|
||||
|
||||
|
||||
g_pShopManager = CShopManager()
|
||||
Reference in New Issue
Block a user