Files
main_plugin_farm/core/farm.py
T
Diuse a6691595e5 🎨 使用Python包管理和模块访问
🎨 重构数据库结构, 使用集中式服务定位器与数据库交互
🎨 更改部分代码逻辑, 减小内存消耗
2025-11-30 16:45:13 +08:00

892 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import math
import random
import re
from zhenxun.configs.config import Config
from zhenxun.models.user_console import UserConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import GoldHandle
from zhenxun.utils.image_utils import ImageTemplate
from ..core.dbService import g_pDBService
from ..event import g_pEventManager
from ..utils import config, getJsonManager, getToolManager
class CFarmManager:
@classmethod
async def buyPointByUid(cls, uid: str, num: int) -> str:
if num <= 0:
return "你是怎么做到购买不是正数的农场币的"
user = await UserConsole.get_user(uid)
pro = float(Config.get_config("zhenxun_plugin_farm", "兑换倍数"))
tax = float(Config.get_config("zhenxun_plugin_farm", "手续费"))
# 计算手续费
fee = math.floor(num * tax)
# 实际扣费金额
deduction = num + fee
if user.gold < deduction:
return f"你的金币不足或不足承担手续费。当前手续费为{fee}"
await UserConsole.reduce_gold(
uid,
num,
GoldHandle.PLUGIN, # type: ignore
"zhenxun_plugin_farm",
)
await UserConsole.reduce_gold(
uid,
fee,
GoldHandle.PLUGIN, # type: ignore
"zhenxun_plugin_farm",
)
point = num * pro
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
await player.addPoint("point", int(point))
return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{player.user.get('point', 0)}"
@classmethod
async def getUserSeedByUid(cls, uid: str) -> bytes:
"""获取用户种子仓库"""
dataList = []
columnNames = [
"-",
"种子名称",
"数量",
"收获经验",
"收获数量",
"成熟时间(小时)",
"收获次数",
"是否可以上架交易行",
]
# 从数据库获取结构化数据
seedRecords = await g_pDBService.userSeed.getUserSeedByUid(uid) or {}
if not seedRecords:
result = await ImageTemplate.table_page(
"种子仓库",
"播种示例:@小真寻 播种 大白菜 [数量]",
columnNames,
dataList,
)
return result.pic2bytes()
for seedName, count in seedRecords.items():
try:
plantInfo = await g_pDBService.plant.getPlantByName(seedName)
if not plantInfo:
continue
iconPath = config.g_sResourcePath / f"plant/{seedName}/icon.png"
icon = (iconPath, 33, 33) if iconPath.exists() else ""
sellable = "可以" if plantInfo["sell"] else "不可以"
dataList.append(
[
icon,
seedName,
count,
plantInfo["experience"],
plantInfo["harvest"],
plantInfo["time"],
plantInfo["crop"],
sellable,
]
)
except KeyError:
continue
result = await ImageTemplate.table_page(
"种子仓库",
"播种示例:@小真寻 播种 大白菜 [数量]",
columnNames,
dataList,
)
return result.pic2bytes()
@classmethod
async def sowing(cls, uid: str, name: str, num: int = -1) -> str:
"""播种
Args:
uid (str): 用户Uid
name (str): 播种种子名称
num (int, optional): 播种数量
Returns:
str: 返回结果
"""
try:
# 获取用户的种子数量
count = await g_pDBService.userSeed.getUserSeedByName(uid, name)
if count is None:
count = 0 # 如果返回 None,则视为没有种子
if count <= 0:
return config.g_sTranslation["sowing"]["noSeed"].format(name=name)
# 如果播种数量超过仓库种子数量
if count < num and num != -1:
return config.g_sTranslation["sowing"]["noNum"].format(
name=name, num=count
)
# 获取用户土地数量
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
soilNumber = player.user.get("soil", 3)
# 如果播种数量为 -1,表示播种所有可播种的土地
if num == -1:
num = count
# 发送播种前信号
await g_pEventManager.m_beforePlant.emit(uid=uid, name=name, num=num) # type: ignore
# 记录是否成功播种
successCount = 0
for i in range(1, soilNumber + 1):
if count > 0 and num > 0:
success = await g_pDBService.userSoil.sowingByPlantName(
uid, i, name
)
if success:
# 更新种子数量
num -= 1
count -= 1
# 记录种子消耗数量
successCount += 1
# 发送播种后信号
await g_pEventManager.m_afterPlant.emit( # type: ignore
uid=uid, name=name, soilIndex=i
)
# 确保用户仓库数量更新
if successCount > 0:
await g_pDBService.userSeed.updateUserSeedByName(uid, name, count)
# 根据播种结果给出反馈
if num == 0:
return config.g_sTranslation["sowing"]["success"].format(
name=name, num=count
)
else:
return config.g_sTranslation["sowing"]["success2"].format(
name=name, num=count
)
except Exception as e:
logger.warning("播种操作失败!", e=e)
return config.g_sTranslation["sowing"]["error"]
@classmethod
async def harvest(cls, uid: str) -> str:
"""收获作物
Args:
uid (str): 用户Uid
Returns:
str: 返回
"""
try:
await g_pEventManager.m_beforeHarvest.emit(uid=uid) # type: ignore
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
soilNumber = player.user.get("soil", 3)
harvestRecords = [] # 收获日志记录
experience = 0 # 总经验值
harvestCount = 0 # 成功收获数量
for i in range(1, soilNumber + 1):
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, i)
if not soilInfo:
continue
# 如果没有种植
if soilInfo.get("isSoilPlanted", 1) == 0:
continue
level = soilInfo.get("soilLevel", 0)
# 如果是枯萎状态
if soilInfo.get("wiltStatus", 1) == 1:
continue
plantInfo = await g_pDBService.plant.getPlantByName(
soilInfo["plantName"]
)
if not plantInfo:
continue
currentTime = getToolManager().dateTime().now()
matureTime = (
getToolManager()
.dateTime()
.fromtimestamp(int(soilInfo["matureTime"]))
)
if currentTime >= matureTime:
number = plantInfo["harvest"]
# 处理偷菜扣除数量
stealNum = await g_pDBService.userSteal.getTotalStolenCount(uid, i)
number -= stealNum
# 处理土地等级带来的数量增长 向下取整
percent = await g_pDBService.userSoil.getSoilLevelHarvestNumber(
level
)
number = math.floor(number * (100 + percent) // 100)
if number <= 0:
continue
harvestCount += 1
experience += plantInfo["experience"]
# 处理土地等级带来的经验增长 向下取整
percent = await g_pDBService.userSoil.getSoilLevelHarvestExp(level)
experience = math.floor(experience * (100 + percent) // 100)
harvestRecords.append(
config.g_sTranslation["harvest"]["append"].format(
name=soilInfo["plantName"],
num=number,
exp=plantInfo["experience"],
)
)
await g_pDBService.userPlant.addUserPlantByUid(
uid, soilInfo["plantName"], number
)
# 统计收获次数
try:
# 处理星级前缀(只在前两字为 一星/二星/三星/四星/五星 时去掉前缀)
plant_name = soilInfo["plantName"]
m = re.match(r"^(?:一星|二星|三星|四星|五星)", plant_name)
if m:
plant_name = plant_name[len(m.group(0)) :]
await g_pDBService.userPlantCount.addUserPlantCountByUid(
uid, plant_name
)
except Exception:
# 忽略统计异常,避免影响主流程
pass
# 如果到达收获次数上限
if soilInfo["harvestCount"] + 1 >= plantInfo["crop"]:
await g_pDBService.userSoil.updateUserSoil(
uid, i, "wiltStatus", 1
)
else:
phase = await g_pDBService.plant.getPlantPhaseByName(
soilInfo["plantName"]
)
ts, hc = (
int(currentTime.timestamp()),
soilInfo["harvestCount"] + 1,
)
p1, p2, *rest = phase
await g_pDBService.userSoil.updateUserSoilFields(
uid,
i,
{
"harvestCount": hc,
"plantTime": ts - p1 - p2,
"matureTime": ts + p2 + sum(rest),
},
)
await g_pEventManager.m_afterHarvest.emit( # type: ignore
uid=uid, name=soilInfo["plantName"], num=number, soilIndex=i
)
if experience > 0:
exp = player.user.get("exp", 0)
await player.addExp(exp + experience)
harvestRecords.append(
config.g_sTranslation["harvest"]["exp"].format(
exp=experience,
)
)
if harvestCount <= 0:
return config.g_sTranslation["harvest"]["no"]
else:
return "\n".join(harvestRecords)
except Exception as e:
logger.warning("收获操作失败!", e=e)
return config.g_sTranslation["harvest"]["error"]
@classmethod
async def eradicate(cls, uid: str) -> str:
"""铲除作物
TODO 缺少随意铲除作物 目前只能铲除荒废作物
Args:
uid (str): 用户Uid
Returns:
str: 返回
"""
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
soilNumber = player.user.get("soil", 3)
await g_pEventManager.m_beforeEradicate.emit(uid=uid) # type: ignore
experience = 0
for i in range(1, soilNumber + 1):
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, i)
if not soilInfo:
continue
# 如果没有种植
if soilInfo.get("isSoilPlanted", 1) == 0:
continue
# 如果不是枯萎状态
if soilInfo.get("wiltStatus", 0) == 0:
continue
experience += 3
if config.g_bIsDebug:
experience += 999
# 更新数据库操作
await g_pDBService.userSoil.deleteUserSoil(uid, i)
await g_pDBService.userSoil.updateUserSoilFields(
uid,
i,
{
"plantName": "",
"plantTime": 0,
"matureTime": 0,
"wiltStatus": 0,
"isSoilPlanted": 0,
},
)
# 铲除作物会将偷菜记录清空
await g_pDBService.userSteal.deleteStealRecord(uid, i)
await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) # type: ignore
if experience > 0:
exp = player.user.get("exp", 0)
await player.addExp(exp + experience)
return config.g_sTranslation["eradicate"]["success"].format(exp=experience)
else:
return config.g_sTranslation["eradicate"]["error"]
@classmethod
async def getUserPlantByUid(cls, uid: str) -> bytes:
"""获取用户作物仓库
Args:
uid (str): 用户Uid
Returns:
bytes: 返回图片
"""
data_list = []
column_name = [
"-",
"作物名称",
"数量",
"单价",
"总价",
"是否上锁",
"是否可以上架交易行",
]
plant = await g_pDBService.userPlant.getUserPlantByUid(uid)
if plant is None:
result = await ImageTemplate.table_page(
"作物仓库",
"出售示例:@小真寻 出售作物 大白菜 [数量]",
column_name,
data_list,
)
return result.pic2bytes()
sell = ""
for name, count in plant.items():
plantInfo = await g_pDBService.plant.getPlantByName(name)
if not plantInfo:
continue
icon = ""
icon_path = config.g_sResourcePath / f"plant/{name}/icon.png"
if icon_path.exists():
icon = (icon_path, 33, 33)
if plantInfo["sell"]:
sell = "可以"
else:
sell = "不可以"
number = int(count) * plantInfo["price"]
isLock = await g_pDBService.userPlant.checkPlantLockByName(uid, name)
if isLock:
lock = "上锁"
else:
lock = "未上锁"
data_list.append(
[icon, name, count, plantInfo["price"], number, lock, sell]
)
result = await ImageTemplate.table_page(
"作物仓库",
"出售示例:@小真寻 出售作物 大白菜 [数量]",
column_name,
data_list,
)
return result.pic2bytes()
@classmethod
async def lockUserPlantByUid(cls, uid: str, name: str, lock: int) -> str:
"""加/解 锁用户作物
Args:
uid (str): 用户uid
name (str): 作物名称
lock (int): 0=解锁 非0=加锁
Returns:
str: 返回
"""
# 先判断该用户仓库是否有该作物
if not await g_pDBService.userPlant.checkUserPlantByName(uid, name):
return config.g_sTranslation["lockPlant"]["noPlant"]
# 更改加锁状态
if await g_pDBService.userPlant.lockUserPlantByName(uid, name, lock):
if lock == 0:
return config.g_sTranslation["lockPlant"]["unlockPlant"].format(
name=name
)
else:
return config.g_sTranslation["lockPlant"]["lockPlant"].format(name=name)
else:
return config.g_sTranslation["lockPlant"]["error"]
@classmethod
async def stealing(cls, uid: str, target: str) -> str:
"""偷菜
Args:
uid (str): 用户Uid
target (str): 被偷用户Uid
Returns:
str: 返回
"""
# 用户信息
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
stealTime = player.user.get("stealTime", "")
stealCount = int(player.user["stealCount"])
if stealTime == "" or not stealTime:
stealTime = getToolManager().dateTime().date().today().strftime("%Y-%m-%d")
stealCount = 5
elif (
getToolManager().dateTime().date().fromisoformat(stealTime)
!= getToolManager().dateTime().date().today()
):
stealTime = getToolManager().dateTime().date().today().strftime("%Y-%m-%d")
stealCount = 5
if stealCount <= 0:
return config.g_sTranslation["stealing"]["max"]
# 获取用户解锁地块数量
soilNumber = player.user.get("soil", 3)
harvestRecords: list[str] = []
isStealingNumber = 0
isStealingPlant = 0
for i in range(1, soilNumber + 1):
soilInfo = await g_pDBService.userSoil.getUserSoil(target, i)
if not soilInfo:
continue
# 如果没有种植
if soilInfo.get("isSoilPlanted", 1) == 0:
continue
# 如果是枯萎状态
if soilInfo.get("wiltStatus", 1) == 1:
continue
# 作物信息
plantInfo = await g_pDBService.plant.getPlantByName(soilInfo["plantName"])
if not plantInfo:
continue
currentTime = getToolManager().dateTime().now()
matureTime = (
getToolManager().dateTime().fromtimestamp(int(soilInfo["matureTime"]))
)
if currentTime >= matureTime:
# 如果偷过,则跳过该土地
if await g_pDBService.userSteal.hasStealed(target, i, uid):
isStealingNumber += 1
continue
stealingNumber = plantInfo[
"harvest"
] - await g_pDBService.userSteal.getTotalStolenCount(target, i)
randomNumber = random.choice([1, 2])
randomNumber = min(randomNumber, stealingNumber)
if randomNumber > 0:
await g_pDBService.userPlant.addUserPlantByUid(
uid, soilInfo["plantName"], randomNumber
)
harvestRecords.append(
config.g_sTranslation["stealing"]["info"].format(
name=soilInfo["plantName"], num=randomNumber
)
)
isStealingPlant += 1
# 如果将作物偷完,就直接更新状态 并记录用户偷取过
if plantInfo["harvest"] - randomNumber + stealingNumber == 0:
# 如果作物 是最后一阶段作物且偷完 则直接枯萎
if soilInfo["harvestCount"] + 1 >= plantInfo["crop"]:
await g_pDBService.userSoil.updateUserSoil(
target, i, "wiltStatus", 1
)
else:
phase = await g_pDBService.plant.getPlantPhaseByName(
soilInfo["plantName"]
)
ts, hc = (
int(currentTime.timestamp()),
soilInfo["harvestCount"] + 1,
)
p1, p2, *rest = phase
await g_pDBService.userSoil.updateUserSoilFields(
uid,
i,
{
"harvestCount": hc,
"plantTime": ts - p1 - p2,
"matureTime": ts + p2 + sum(rest),
},
)
await g_pDBService.userSteal.addStealRecord(
target,
i,
uid,
randomNumber,
int(getToolManager().dateTime().now().timestamp()),
)
else:
await g_pDBService.userSteal.addStealRecord(
target,
i,
uid,
randomNumber,
int(getToolManager().dateTime().now().timestamp()),
)
if isStealingPlant <= 0 and isStealingNumber <= 0:
return config.g_sTranslation["stealing"]["noPlant"]
elif isStealingPlant <= 0 and isStealingNumber > 0:
return config.g_sTranslation["stealing"]["repeat"]
else:
stealCount -= 1
await player.updateStealCountByUid(uid, stealTime, stealCount)
return "\n".join(harvestRecords)
@classmethod
async def reclamationCondition(cls, uid: str) -> str:
"""获取开垦条件
Args:
uid (str): 用户Uid
Returns:
str: 返回条件文本信息
"""
jLevel = await getJsonManager().getLevel()
rec = jLevel["reclamation"]
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
try:
if player.user["soil"] >= 30:
return config.g_sTranslation["reclamation"]["perfect"]
rec = rec[f"{player.user['soil'] + 1}"]
level = rec["level"]
point = rec["point"]
item = rec["item"]
str = ""
if len(item) == 0:
str = config.g_sTranslation["reclamation"]["next"].format(
level=level, num=point
)
else:
str = config.g_sTranslation["reclamation"]["next2"].format(
level=level, num=point, item=item
)
return str
except Exception:
return config.g_sTranslation["reclamation"]["error"]
@classmethod
async def reclamation(cls, uid: str) -> str:
"""开垦
Args:
uid (str): 用户Uid
Returns:
str: _description_
"""
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
level = await player.getUserLevel()
jsonLevel = await getJsonManager().getLevel()
rec = jsonLevel["reclamation"]
try:
if player.user["soil"] >= 30:
return config.g_sTranslation["reclamation"]["perfect"]
rec = rec[f"{player.user['soil'] + 1}"]
levelFileter = rec["level"]
point = rec["point"]
# item = rec["item"]
if level[0] < levelFileter:
return config.g_sTranslation["reclamation"]["nextLevel"].format(
level=level[0], next=levelFileter
)
if player.user["point"] < point:
return config.g_sTranslation["reclamation"]["noNum"].format(num=point)
# TODO 缺少判断消耗的item
await player.subPoint("point", point)
await player.updateField("soil", player.user["soil"] + 1)
return config.g_sTranslation["reclamation"]["success"]
except Exception:
return config.g_sTranslation["reclamation"]["error1"]
@classmethod
async def soilUpgradeCondition(cls, uid: str, soilIndex: int) -> str:
"""获取土地升级条件
Args:
uid (str): 用户Uid
soilIndex (str): 土地索引
Returns:
str: 返回土地升级条件
"""
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
if not soilInfo:
return config.g_sTranslation["soilInfo"]["error"]
soilLevel = soilInfo.get("soilLevel", 0) + 1
if soilLevel >= config.g_iSoilLevelMax:
return config.g_sTranslation["soilInfo"]["error1"]
# 获取用户当前土地 的下一级土地 数量
countSoil = await g_pDBService.userSoil.countSoilByLevel(uid, soilLevel)
# 获取升级所需
soilLevelText = await g_pDBService.userSoil.getSoilLevel(soilLevel)
jSoil = await getJsonManager().getSoil()
fileter = jSoil["upgrade"][soilLevelText][countSoil]
nextLevel = await g_pDBService.userSoil.getSoilLevelText(soilLevel)
lines = ["将土地升级至:" + nextLevel + "。", "所需:"]
fields = [
("level", "等级"),
("point", "金币"),
("vipPoint", "点券"),
]
for key, label in fields:
value = fileter.get(key, 0)
if value > 0:
lines.append(f"{label}:{value}")
items = fileter.get("item", {})
for name, qty in items.items():
if qty:
lines.append(f"{name}:{qty}")
lines.append("回复“是”将执行升级")
return "\n".join(lines)
@classmethod
async def soilUpgrade(cls, uid: str, soilIndex: int) -> str:
"""土地升级
Args:
uid (str): 用户Uid
soilIndex (int): 土地索引
Returns:
str:
"""
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
if not soilInfo:
return config.g_sTranslation["soilInfo"]["error"]
soilLevel = soilInfo.get("soilLevel", 0) + 1
if soilLevel >= config.g_iSoilLevelMax:
return config.g_sTranslation["soilInfo"]["error1"]
countSoil = await g_pDBService.userSoil.countSoilByLevel(uid, soilLevel)
soilLevelText = await g_pDBService.userSoil.getSoilLevel(soilLevel)
soil = await getJsonManager().getSoil()
fileter = soil["upgrade"][soilLevelText][countSoil]
getters = {
"level": (await player.getUserLevel())[0],
"point": player.user.get("point", 0),
"vipPoint": player.user.get("vipPoint", 0),
}
requirements = {
"level": "等级",
"point": "金币",
"vipPoint": "点券",
}
for key, val in getters.items():
need = fileter.get(key, 0)
if val < need:
return f"你的{requirements[key]}不够哦~"
# 缺少item判断
# 更新数据库字段
await g_pDBService.userSoil.updateUserSoil(
uid, soilIndex, "soilLevel", soilLevel
)
# 如果有作物的话直接成熟
await g_pDBService.userSoil.matureNow(uid, soilIndex)
# 更新数据库字段
await player.subPoint("point", fileter.get("point", 0))
await player.subPoint("vipPoint", fileter.get("vipPoint", 0))
return config.g_sTranslation["soilInfo"]["success"].format(
name=await g_pDBService.userSoil.getSoilLevelText(soilLevel),
text=config.g_sTranslation["soilInfo"][soilLevelText],
)
@classmethod
async def pointToVipPointByUid(cls, uid: str, num: int) -> str:
"""点券兑换
Args:
uid (str): 用户Uid
num (int): 兑换点券数量
Returns:
str: 返回结果
兑换比例在配置文件中配置
目前配置文件中默认是20倍
100点券需要20000农场币
赠送点券规则:
小于2000点券:0
2000-5000点券:100
5000-50000点券:280
大于50000点券:3000
"""
if num < 100:
return "点券兑换数量必须大于等于100"
pro = int(Config.get_config("zhenxun_plugin_farm", "点券兑换倍数"))
pro *= num
player = await getToolManager().getPlayerByUid(uid)
if not player:
return config.g_sTranslation["basic"]["error"]
point = player.user.get("point", 0)
if point < pro:
return f"你的农场币不足,当前农场币为{point},兑换还需要{pro - point}农场币"
giftPoints: int
if num < 2000:
giftPoints = 0
elif num < 5000:
giftPoints = 100
elif num < 50000:
giftPoints = 280
else:
giftPoints = 3000
number = num + giftPoints
await player.addPoint("vipPoint", number)
await player.subPoint("point", pro)
return f"兑换{num}点券成功,当前点券:{number},赠送点券:{giftPoints},当前农场币:{point}"