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"] }