wokring marketplace without enchancts and durability on item
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from app.db.database import users_collection, sessions_collection
|
||||
from fastapi import HTTPException
|
||||
|
||||
class CoinsService:
|
||||
async def update_player_coins(self, player_id: str, player_name: str, online_time: int, server_ip: str):
|
||||
@ -100,3 +101,42 @@ class CoinsService:
|
||||
"formatted": f"{hours}ч {minutes}м {seconds}с"
|
||||
}
|
||||
}
|
||||
|
||||
async def get_balance(self, username: str) -> int:
|
||||
"""Получить текущий баланс пользователя"""
|
||||
user = await users_collection.find_one({"username": username})
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail=f"Пользователь {username} не найден")
|
||||
return user.get("coins", 0)
|
||||
|
||||
async def increase_balance(self, username: str, amount: int) -> int:
|
||||
"""Увеличить баланс пользователя"""
|
||||
if amount <= 0:
|
||||
raise ValueError("Сумма должна быть положительной")
|
||||
|
||||
result = await users_collection.update_one(
|
||||
{"username": username},
|
||||
{"$inc": {"coins": amount}}
|
||||
)
|
||||
|
||||
if result.modified_count == 0:
|
||||
raise HTTPException(status_code=404, detail=f"Пользователь {username} не найден")
|
||||
|
||||
user = await users_collection.find_one({"username": username})
|
||||
return user.get("coins", 0)
|
||||
|
||||
async def decrease_balance(self, username: str, amount: int) -> int:
|
||||
"""Уменьшить баланс пользователя"""
|
||||
if amount <= 0:
|
||||
raise ValueError("Сумма должна быть положительной")
|
||||
|
||||
result = await users_collection.update_one(
|
||||
{"username": username},
|
||||
{"$inc": {"coins": -amount}} # Уменьшаем на отрицательное значение
|
||||
)
|
||||
|
||||
if result.modified_count == 0:
|
||||
raise HTTPException(status_code=404, detail=f"Пользователь {username} не найден")
|
||||
|
||||
user = await users_collection.find_one({"username": username})
|
||||
return user.get("coins", 0)
|
||||
|
243
app/services/marketplace.py
Normal file
243
app/services/marketplace.py
Normal file
@ -0,0 +1,243 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException
|
||||
from app.db.database import db
|
||||
from app.services.coins import CoinsService
|
||||
from app.services.server.command import CommandService
|
||||
|
||||
# Коллекция для хранения товаров на торговой площадке
|
||||
marketplace_collection = db.marketplace_items
|
||||
|
||||
# Добавьте эту функцию
|
||||
def _serialize_mongodb_doc(doc):
|
||||
"""Преобразует MongoDB документ для JSON сериализации"""
|
||||
if doc is None:
|
||||
return None
|
||||
|
||||
result = {}
|
||||
for key, value in doc.items():
|
||||
# Обработка ObjectId
|
||||
if key == "_id":
|
||||
result["_id"] = str(value)
|
||||
continue
|
||||
|
||||
# Обработка ISODate
|
||||
if isinstance(value, datetime):
|
||||
result[key] = value.isoformat()
|
||||
# Обработка вложенных словарей
|
||||
elif isinstance(value, dict):
|
||||
if "$date" in value:
|
||||
# Это ISODate
|
||||
result[key] = datetime.fromisoformat(value["$date"].replace("Z", "+00:00")).isoformat()
|
||||
else:
|
||||
result[key] = _serialize_mongodb_doc(value)
|
||||
# Обработка списков
|
||||
elif isinstance(value, list):
|
||||
result[key] = [_serialize_mongodb_doc(item) if isinstance(item, dict) else item for item in value]
|
||||
else:
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
||||
class MarketplaceService:
|
||||
async def list_items(self, server_ip: str = None, page: int = 1, limit: int = 20):
|
||||
"""Получить список предметов на торговой площадке"""
|
||||
query = {}
|
||||
if server_ip:
|
||||
query["server_ip"] = server_ip
|
||||
|
||||
total = await marketplace_collection.count_documents(query)
|
||||
|
||||
items_cursor = marketplace_collection.find(query) \
|
||||
.sort("created_at", -1) \
|
||||
.skip((page - 1) * limit) \
|
||||
.limit(limit)
|
||||
|
||||
items = await items_cursor.to_list(limit)
|
||||
|
||||
# Преобразуем каждый документ
|
||||
serialized_items = [_serialize_mongodb_doc(item) for item in items]
|
||||
|
||||
return {
|
||||
"items": serialized_items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"pages": (total + limit - 1) // limit
|
||||
}
|
||||
|
||||
async def get_item(self, item_id: str):
|
||||
"""Получить информацию о конкретном предмете"""
|
||||
item = await marketplace_collection.find_one({"id": item_id})
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Предмет не найден")
|
||||
return _serialize_mongodb_doc(item)
|
||||
|
||||
async def add_item(self, username: str, slot_index: int, amount: int, price: int, server_ip: str):
|
||||
"""Выставить предмет на продажу"""
|
||||
# 1. Получаем инвентарь игрока
|
||||
cmd_service = CommandService()
|
||||
inventory_result = await cmd_service.get_player_inventory(username, server_ip, timeout=15)
|
||||
|
||||
if inventory_result["status"] != "success":
|
||||
raise HTTPException(status_code=400,
|
||||
detail=f"Не удалось получить инвентарь игрока. Игрок должен быть онлайн.")
|
||||
|
||||
# 2. Находим предмет в указанном слоте
|
||||
item_data = None
|
||||
for item in inventory_result.get("inventory", []):
|
||||
if item.get("slot") == slot_index:
|
||||
item_data = item
|
||||
break
|
||||
|
||||
if not item_data or item_data.get("material") == "AIR" or item_data.get("amount") < amount:
|
||||
raise HTTPException(status_code=400,
|
||||
detail=f"В указанном слоте нет предмета или недостаточное количество")
|
||||
|
||||
# 3. Создаем запись о предмете на торговой площадке
|
||||
item_id = str(uuid.uuid4())
|
||||
marketplace_item = {
|
||||
"id": item_id,
|
||||
"material": item_data.get("material"),
|
||||
"amount": amount,
|
||||
"price": price,
|
||||
"seller_name": username,
|
||||
"server_ip": server_ip,
|
||||
"display_name": item_data.get("display_name"),
|
||||
"lore": item_data.get("lore"),
|
||||
"enchants": item_data.get("enchants"),
|
||||
"item_data": item_data,
|
||||
"created_at": datetime.utcnow()
|
||||
}
|
||||
|
||||
await marketplace_collection.insert_one(marketplace_item)
|
||||
|
||||
# 4. Удаляем предмет из инвентаря игрока
|
||||
# Определяем тип слота и его номер для команды replaceitem
|
||||
slot_type = "inventory"
|
||||
slot_num = slot_index
|
||||
|
||||
# Преобразуем слот инвентаря в соответствующий формат для команды
|
||||
if 0 <= slot_index <= 8:
|
||||
# Панель быстрого доступа (хотбар)
|
||||
slot_type = "hotbar"
|
||||
slot_num = slot_index
|
||||
elif 9 <= slot_index <= 35:
|
||||
# Основной инвентарь
|
||||
slot_type = "inventory"
|
||||
slot_num = slot_index - 9
|
||||
elif slot_index == 36:
|
||||
# Ботинки
|
||||
slot_type = "armor.feet"
|
||||
slot_num = 0
|
||||
elif slot_index == 37:
|
||||
# Поножи
|
||||
slot_type = "armor.legs"
|
||||
slot_num = 0
|
||||
elif slot_index == 38:
|
||||
# Нагрудник
|
||||
slot_type = "armor.chest"
|
||||
slot_num = 0
|
||||
elif slot_index == 39:
|
||||
# Шлем
|
||||
slot_type = "armor.head"
|
||||
slot_num = 0
|
||||
elif slot_index == 40:
|
||||
# Вторая рука
|
||||
slot_type = "weapon.offhand"
|
||||
slot_num = 0
|
||||
|
||||
# Выполняем команду
|
||||
# Для Minecraft 1.16.5+
|
||||
command = f"item replace entity {username} {slot_type}.{slot_num} with air"
|
||||
# Для более старых версий (1.13-1.16)
|
||||
# command = f"replaceitem entity {username} {slot_type}.{slot_num} air"
|
||||
|
||||
from app.models.server.command import ServerCommand
|
||||
cmd = ServerCommand(
|
||||
command=command,
|
||||
server_ip=server_ip,
|
||||
require_online_player=True,
|
||||
target_message=f"Вы выставили на продажу {amount} шт. предмета {item_data.get('display_name', item_data.get('material'))} за {price} монет"
|
||||
)
|
||||
|
||||
await cmd_service.add_command(cmd)
|
||||
|
||||
return {"status": "success", "item_id": item_id}
|
||||
|
||||
async def buy_item(self, buyer_username: str, item_id: str):
|
||||
"""Купить предмет с торговой площадки"""
|
||||
# 1. Находим предмет
|
||||
item = await marketplace_collection.find_one({"id": item_id})
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Предмет не найден")
|
||||
|
||||
# 2. Проверяем, что покупатель не является продавцом
|
||||
if item["seller_name"] == buyer_username:
|
||||
raise HTTPException(status_code=400, detail="Вы не можете купить свой же предмет")
|
||||
|
||||
# 3. Проверяем баланс покупателя
|
||||
coins_service = CoinsService()
|
||||
buyer_balance = await coins_service.get_balance(buyer_username)
|
||||
|
||||
if buyer_balance < item["price"]:
|
||||
raise HTTPException(status_code=400,
|
||||
detail=f"Недостаточно монет. Требуется: {item['price']}, имеется: {buyer_balance}")
|
||||
|
||||
# 4. Проверяем, что покупатель онлайн на сервере
|
||||
cmd_service = CommandService()
|
||||
try:
|
||||
await cmd_service.get_player_inventory(buyer_username, item["server_ip"], timeout=5)
|
||||
except:
|
||||
raise HTTPException(status_code=400,
|
||||
detail=f"Вы должны быть онлайн на сервере для совершения покупки")
|
||||
|
||||
# 5. Списываем деньги с покупателя
|
||||
await coins_service.decrease_balance(buyer_username, item["price"])
|
||||
|
||||
# 6. Начисляем деньги продавцу
|
||||
await coins_service.increase_balance(item["seller_name"], item["price"])
|
||||
|
||||
# 7. Добавляем предмет в инвентарь покупателя
|
||||
material = item["material"]
|
||||
amount = item["amount"]
|
||||
|
||||
# Создаем команду с учетом всех свойств предмета
|
||||
command_base = f"give {buyer_username} {material} {amount}"
|
||||
|
||||
# Если у предмета есть мета-данные, добавляем их через NBT
|
||||
nbt_tags = []
|
||||
|
||||
if item.get("display_name"):
|
||||
nbt_tags.append(f'display:{{Name:\'[{{"text":"{item["display_name"]}","italic":false}}]\'}}')
|
||||
|
||||
if item.get("lore"):
|
||||
lore_json = ','.join([f'[{{"text":"{line}","italic":false}}]' for line in item["lore"]])
|
||||
nbt_tags.append(f'display:{{Lore:[{lore_json}]}}')
|
||||
|
||||
if item.get("enchants"):
|
||||
enchant_tags = []
|
||||
for ench_id, level in item["enchants"].items():
|
||||
enchant_tags.append(f'{{id:"{ench_id}",lvl:{level}s}}')
|
||||
nbt_tags.append(f'Enchantments:[{",".join(enchant_tags)}]')
|
||||
|
||||
if nbt_tags:
|
||||
command_base += " " + "{" + ",".join(nbt_tags) + "}"
|
||||
|
||||
from app.models.server.command import ServerCommand
|
||||
cmd = ServerCommand(
|
||||
command=command_base,
|
||||
server_ip=item["server_ip"],
|
||||
require_online_player=True,
|
||||
target_message=f"Вы купили {amount} шт. предмета {item.get('display_name', material)} за {item['price']} монет"
|
||||
)
|
||||
|
||||
await cmd_service.add_command(cmd)
|
||||
|
||||
# 8. Удаляем предмет с торговой площадки
|
||||
await marketplace_collection.delete_one({"id": item_id})
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Вы купили {amount} шт. предмета {item.get('display_name', material)}",
|
||||
"remaining_balance": buyer_balance - item["price"]
|
||||
}
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||
from fastapi import HTTPException
|
||||
from typing import Dict
|
||||
from app.db.database import db
|
||||
import asyncio
|
||||
|
||||
# Создаем коллекции для хранения команд и инвентаря
|
||||
pending_commands_collection = db.pending_commands
|
||||
|
Reference in New Issue
Block a user