from typing import Optional from sqlalchemy import select, func as sa_func from sqlalchemy.ext.asyncio import AsyncSession from database import Achievement, UserAchievement, ActivityFeed, InventoryItem, CaseOpening, Contract from datetime import datetime import json import math _COLLECTION_CACHE = None def _build_collection_cache(): global _COLLECTION_CACHE if _COLLECTION_CACHE is not None: return from backend import ALL_ITEMS, COLLECTIONS_INDEX, extract_base_name coll_base_names = {} coll_item_ids = {} for coll_name, item_ids in COLLECTIONS_INDEX.items(): base_set = set() clean_ids = [cid for cid in item_ids if cid < len(ALL_ITEMS)] for cid in clean_ids: item = ALL_ITEMS[cid] base = extract_base_name(item.get("market_hash_name", "")) if base: base_set.add(base) if base_set: coll_base_names[coll_name] = base_set coll_item_ids[coll_name] = set(clean_ids) coll_is_custom = {} for coll_name, item_ids in COLLECTIONS_INDEX.items(): is_custom = False for cid in item_ids: if cid < len(ALL_ITEMS) and ALL_ITEMS[cid].get("price_source") == "custom": is_custom = True break coll_is_custom[coll_name] = is_custom _COLLECTION_CACHE = {"base_names": coll_base_names, "item_ids": coll_item_ids, "is_custom": coll_is_custom} # ─── Ачивки ───────────────────────────────────────────────────────────────── ACHIEVEMENT_DEFS = [ {"name": "first_case", "title": "Первый шаг", "description": "Открой свой первый кейс", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 1, "reward_amount": 100, "sort_order": 1}, {"name": "case_collector_10", "title": "Коллекционер", "description": "Открой 10 кейсов", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 10, "reward_amount": 250, "sort_order": 2}, {"name": "case_collector_100", "title": "Заядлый коллекционер", "description": "Открой 100 кейсов", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 100, "reward_amount": 1000, "sort_order": 3}, {"name": "case_collector_1000", "title": "Одержимый", "description": "Открой 1000 кейсов", "icon": "💎", "category": "cases", "requirement_type": "open_cases", "requirement_value": 1000, "reward_amount": 5000, "sort_order": 4}, {"name": "first_contract", "title": "Обменщик", "description": "Выполни первый контракт обмена", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 1, "reward_amount": 200, "sort_order": 5}, {"name": "contract_master", "title": "Мастер обмена", "description": "Выполни 10 контрактов обмена", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 10, "reward_amount": 500, "sort_order": 6}, {"name": "first_upgrade", "title": "Улучшайзер", "description": "Проведи первый апгрейд", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 1, "reward_amount": 150, "sort_order": 7}, {"name": "upgrade_winner", "title": "Везучий улучшайзер", "description": "Выиграй первый апгрейд", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 1, "reward_amount": 300, "sort_order": 8}, {"name": "rare_finder", "title": "Охотник за редким", "description": "Найди редкий предмет", "icon": "⭐", "category": "cases", "requirement_type": "find_rare", "requirement_value": 1, "reward_amount": 500, "sort_order": 9}, {"name": "knife_collector", "title": "Ножичек", "description": "Получи нож или перчатки", "icon": "🔪", "category": "cases", "requirement_type": "knife", "requirement_value": 1, "reward_amount": 1000, "sort_order": 10}, {"name": "big_spender", "title": "Транжира", "description": "Потрать 10,000 ₽ на кейсы", "icon": "💰", "category": "spending", "requirement_type": "total_spent", "requirement_value": 10000, "reward_amount": 500, "sort_order": 11}, {"name": "whale", "title": "Кит", "description": "Потрать 100,000 ₽ на кейсы", "icon": "🐳", "category": "spending", "requirement_type": "total_spent", "requirement_value": 100000, "reward_amount": 2000, "sort_order": 12}, {"name": "rich_100k", "title": "Богач", "description": "Накопи 100,000 ₽ на балансе", "icon": "💎", "category": "wealth", "requirement_type": "balance", "requirement_value": 1, "reward_amount": 1000, "sort_order": 13, "hidden": False}, {"name": "slot_winner", "title": "Счастливчик", "description": "Выиграй в слот-машине", "icon": "🎰", "category": "slots", "requirement_type": "slot_match", "requirement_value": 1, "reward_amount": 500, "sort_order": 14}, {"name": "coll_first", "title": "Коллекция! :3", "description": "Собери предметы из 1 коллекции", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 1, "reward_amount": 500, "sort_order": 15}, {"name": "coll_5", "title": "Коллекционер коллекций", "description": "Собери предметы из 5 коллекций", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 5, "reward_amount": 1000, "sort_order": 16}, {"name": "coll_10", "title": "Заядлый коллекционер коллекций", "description": "Собери предметы из 10 коллекций", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 10, "reward_amount": 2000, "sort_order": 17}, {"name": "coll_25", "title": "Коллекционер-эксперт", "description": "Собери предметы из 25 коллекций", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 25, "reward_amount": 5000, "sort_order": 18}, {"name": "coll_50", "title": "Коллекционер-мастер", "description": "Собери предметы из 50 коллекций", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 50, "reward_amount": 10000, "sort_order": 19}, {"name": "coll_75", "title": "Архивариус", "description": "Собери предметы из 75 коллекций", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 75, "reward_amount": 25000, "sort_order": 20}, {"name": "coll_100", "title": "Библиотекарь", "description": "Собери предметы из 100 коллекций", "icon": "📚", "category": "collections", "requirement_type": "collections_owned", "requirement_value": 100, "reward_amount": 50000, "sort_order": 21}, {"name": "coll_complete_1", "title": "Полная коллекция", "description": "Заверши 1 коллекцию полностью", "icon": "🏆", "category": "collections", "requirement_type": "collections_complete", "requirement_value": 1, "reward_amount": 1000, "sort_order": 22}, {"name": "coll_complete_5", "title": "Коллекция завершена", "description": "Заверши 5 коллекций полностью", "icon": "🏆", "category": "collections", "requirement_type": "collections_complete", "requirement_value": 5, "reward_amount": 5000, "sort_order": 23}, {"name": "coll_complete_10", "title": "Мастер завершения", "description": "Заверши 10 коллекций полностью", "icon": "🏆", "category": "collections", "requirement_type": "collections_complete", "requirement_value": 10, "reward_amount": 10000, "sort_order": 24}, {"name": "coll_complete_25", "title": "Перфекционист", "description": "Заверши 25 коллекций полностью", "icon": "🏆", "category": "collections", "requirement_type": "collections_complete", "requirement_value": 25, "reward_amount": 50000, "sort_order": 25}, {"name": "coll_complete_50", "title": "Абсолютный коллекционер", "description": "Заверши 50 коллекций полностью", "icon": "👑", "category": "collections", "requirement_type": "collections_complete", "requirement_value": 50, "reward_amount": 100000, "sort_order": 26}, {"name": "coll_items_10", "title": "Начинающий коллекционер", "description": "Собери 10 уникальных скинов", "icon": "🎨", "category": "collections", "requirement_type": "unique_skins", "requirement_value": 10, "reward_amount": 500, "sort_order": 27}, {"name": "coll_items_50", "title": "Ценитель искусства", "description": "Собери 50 уникальных скинов", "icon": "🎨", "category": "collections", "requirement_type": "unique_skins", "requirement_value": 50, "reward_amount": 1000, "sort_order": 28}, {"name": "coll_items_100", "title": "Галерист", "description": "Собери 100 уникальных скинов", "icon": "🎨", "category": "collections", "requirement_type": "unique_skins", "requirement_value": 100, "reward_amount": 2000, "sort_order": 29}, {"name": "coll_items_250", "title": "Музейный работник", "description": "Собери 250 уникальных скинов", "icon": "🎨", "category": "collections", "requirement_type": "unique_skins", "requirement_value": 250, "reward_amount": 5000, "sort_order": 30}, {"name": "coll_items_500", "title": "Эстет", "description": "Собери 500 уникальных скинов", "icon": "🎨", "category": "collections", "requirement_type": "unique_skins", "requirement_value": 500, "reward_amount": 10000, "sort_order": 31}, {"name": "coll_items_1000", "title": "Синефил", "description": "Собери 1000 уникальных скинов", "icon": "🎨", "category": "collections", "requirement_type": "unique_skins", "requirement_value": 1000, "reward_amount": 25000, "sort_order": 32}, {"name": "coll_all_base", "title": "Базовый коллекционер", "description": "Собери все скины из всех базовых коллекций", "icon": "💠", "category": "collections", "requirement_type": "all_base_collections", "requirement_value": 1, "reward_amount": 100000, "sort_order": 33}, {"name": "coll_all", "title": "Властелин коллекций", "description": "Собери все скины из ВСЕХ коллекций (включая кастом)", "icon": "👑", "category": "collections", "requirement_type": "all_collections", "requirement_value": 1, "reward_amount": 500000, "sort_order": 34}, {"name": "coll_cheap_collector", "title": "Бюджетный коллекционер", "description": "Заверши любую коллекцию только из Consumer Grade предметов", "icon": "🪙", "category": "collections", "requirement_type": "cheap_collection", "requirement_value": 1, "reward_amount": 1000, "sort_order": 35}, {"name": "coll_mid_collector", "title": "Середина", "description": "Заверши коллекцию только из Mil-Spec предметов", "icon": "🥈", "category": "collections", "requirement_type": "mid_collection", "requirement_value": 1, "reward_amount": 2000, "sort_order": 36}, {"name": "coll_rare_collector", "title": "Элитарный коллекционер", "description": "Заверши коллекцию только из редких предметов (Restricted+)", "icon": "💎", "category": "collections", "requirement_type": "rare_collection", "requirement_value": 1, "reward_amount": 5000, "sort_order": 37}, {"name": "coll_covert_collector", "title": "Тайный коллекционер", "description": "Заверши коллекцию только из Covert предметов", "icon": "🔮", "category": "collections", "requirement_type": "covert_collection", "requirement_value": 1, "reward_amount": 10000, "sort_order": 38}, {"name": "coll_full_case", "title": "Кейс-коллекционер", "description": "Собери полный набор из любого кейса", "icon": "📦", "category": "collections", "requirement_type": "full_case", "requirement_value": 1, "reward_amount": 2000, "sort_order": 39}, {"name": "dildo_owned", "title": "Ахтунг", "description": "Получи первый Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_count", "requirement_value": 1, "reward_amount": 100, "sort_order": 40, "hidden": True}, {"name": "dildo_5", "title": "Диванный эксперт", "description": "Собери 5 Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_count", "requirement_value": 5, "reward_amount": 500, "sort_order": 41, "hidden": True}, {"name": "dildo_10", "title": "Вечеринка", "description": "Собери 10 Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_count", "requirement_value": 10, "reward_amount": 1000, "sort_order": 42, "hidden": True}, {"name": "dildo_25", "title": "Промышленные масштабы", "description": "Собери 25 Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_count", "requirement_value": 25, "reward_amount": 5000, "sort_order": 43, "hidden": True}, {"name": "dildo_50", "title": "Склад готовой продукции", "description": "Собери 50 Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_count", "requirement_value": 50, "reward_amount": 10000, "sort_order": 44, "hidden": True}, {"name": "dildo_all_rarities", "title": "Dildo всех редкостей", "description": "Собери Dildo хотя бы одной каждой редкости", "icon": "🌈", "category": "dildo", "requirement_type": "dildo_rarities", "requirement_value": 1, "reward_amount": 5000, "sort_order": 46, "hidden": True}, {"name": "dildo_full_set", "title": "Dildo-коллекция", "description": "Собери все паттерны Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_patterns", "requirement_value": 1, "reward_amount": 25000, "sort_order": 47, "hidden": True}, {"name": "dildo_float_001", "title": "Идеальный Dildo", "description": "Найди Dildo с float < 0.01", "icon": "✨", "category": "dildo", "requirement_type": "dildo_best_float", "requirement_value": 1, "reward_amount": 5000, "sort_order": 48, "hidden": True}, {"name": "dildo_float_0005", "title": "Девственный Dildo", "description": "Найди Dildo с float < 0.005", "icon": "💎", "category": "dildo", "requirement_type": "dildo_best_float_2", "requirement_value": 2, "reward_amount": 15000, "sort_order": 49, "hidden": True}, {"name": "crash_first", "title": "Крэшер", "description": "Сыграй первую игру в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 1, "reward_amount": 100, "sort_order": 50}, {"name": "crash_pro", "title": "Крэш-профи", "description": "Сыграй 10 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 10, "reward_amount": 500, "sort_order": 51}, {"name": "crash_5x", "title": "Крэш-5x", "description": "Забери деньги на множителе 5x", "icon": "📈", "category": "crash", "requirement_type": "crash_cashout", "requirement_value": 5, "reward_amount": 1000, "sort_order": 52}, {"name": "inventory_50", "title": "Инвентарь 50", "description": "Собери 50 предметов в инвентаре", "icon": "🎒", "category": "inventory", "requirement_type": "inventory_items", "requirement_value": 50, "reward_amount": 500, "sort_order": 53}, {"name": "inventory_100", "title": "Инвентарь 100", "description": "Собери 100 предметов в инвентаре", "icon": "🎒", "category": "inventory", "requirement_type": "inventory_items", "requirement_value": 100, "reward_amount": 1000, "sort_order": 54}, {"name": "inventory_250", "title": "Инвентарь 250", "description": "Собери 250 предметов в инвентаре", "icon": "🎒", "category": "inventory", "requirement_type": "inventory_items", "requirement_value": 250, "reward_amount": 5000, "sort_order": 55}, {"name": "inventory_500", "title": "Инвентарь 500", "description": "Собери 500 предметов в инвентаре", "icon": "🎒", "category": "inventory", "requirement_type": "inventory_items", "requirement_value": 500, "reward_amount": 10000, "sort_order": 56}, {"name": "inventory_1000", "title": "Инвентарь 1000", "description": "Собери 1000 предметов в инвентаре", "icon": "🎒", "category": "inventory", "requirement_type": "inventory_items", "requirement_value": 1000, "reward_amount": 25000, "sort_order": 57}, {"name": "contracts_10", "title": "Контракты 10", "description": "Выполни 10 контрактов", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 10, "reward_amount": 1000, "sort_order": 58}, {"name": "contracts_25", "title": "Контракты 25", "description": "Выполни 25 контрактов", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 25, "reward_amount": 3000, "sort_order": 59}, {"name": "contracts_100", "title": "Контракты 100", "description": "Выполни 100 контрактов", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 100, "reward_amount": 10000, "sort_order": 60}, {"name": "upgrade_5", "title": "Апгрейды 5", "description": "Проведи 5 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 5, "reward_amount": 500, "sort_order": 61}, {"name": "upgrade_25", "title": "Апгрейды 25", "description": "Проведи 25 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 25, "reward_amount": 2000, "sort_order": 62}, {"name": "upgrade_50", "title": "Апгрейды 50", "description": "Проведи 50 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 50, "reward_amount": 5000, "sort_order": 63}, {"name": "upgrade_100", "title": "Апгрейд-мастер", "description": "Проведи 100 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 100, "reward_amount": 10000, "sort_order": 64}, {"name": "upgrade_wins_5", "title": "5 побед в апгрейде", "description": "Выиграй 5 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 5, "reward_amount": 500, "sort_order": 65}, {"name": "upgrade_wins_25", "title": "25 побед в апгрейде", "description": "Выиграй 25 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 25, "reward_amount": 2500, "sort_order": 66}, {"name": "upgrade_wins_50", "title": "50 побед в апгрейде", "description": "Выиграй 50 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 50, "reward_amount": 10000, "sort_order": 67}, {"name": "upgrade_wins_100", "title": "100 побед в апгрейде", "description": "Выиграй 100 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 100, "reward_amount": 25000, "sort_order": 68}, {"name": "dildo_100", "title": "Dildo-магнат", "description": "Собери 100 Dildo", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_count", "requirement_value": 100, "reward_amount": 50000, "sort_order": 69, "hidden": True}, {"name": "dildo_worth_100m", "title": "Dildo-миллиардер", "description": "Стоимость Dildo в инвентаре превышает 100 млн ₽", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_total_value", "requirement_value": 100000000, "reward_amount": 100000, "sort_order": 70, "hidden": True}, {"name": "dildo_from_contract", "title": "Dildo из контракта", "description": "Получи Dildo из контракта обмена", "icon": "🍆", "category": "dildo", "requirement_type": "dildo_contract", "requirement_value": 1, "reward_amount": 5000, "sort_order": 71, "hidden": True}, {"name": "balance_1m", "title": "Баланс 1 млн", "description": "Накопи 1,000,000 ₽ на балансе", "icon": "💰", "category": "wealth", "requirement_type": "balance", "requirement_value": 1000000, "reward_amount": 5000, "sort_order": 72}, {"name": "balance_10m", "title": "Баланс 10 млн", "description": "Накопи 10,000,000 ₽ на балансе", "icon": "💰", "category": "wealth", "requirement_type": "balance", "requirement_value": 10000000, "reward_amount": 25000, "sort_order": 73}, {"name": "balance_100m", "title": "Баланс 100 млн", "description": "Накопи 100,000,000 ₽ на балансе", "icon": "💰", "category": "wealth", "requirement_type": "balance", "requirement_value": 100000000, "reward_amount": 100000, "sort_order": 74}, {"name": "balance_1b", "title": "Баланс 1 млрд", "description": "Накопи 1,000,000,000 ₽ на балансе", "icon": "💎", "category": "wealth", "requirement_type": "balance", "requirement_value": 1000000000, "reward_amount": 500000, "sort_order": 75}, {"name": "item_worth_1m", "title": "Ценный предмет I", "description": "Владей предметом стоимостью от 1 млн ₽", "icon": "💎", "category": "items", "requirement_type": "best_item", "requirement_value": 1000000, "reward_amount": 1000, "sort_order": 76}, {"name": "item_worth_10m", "title": "Ценный предмет II", "description": "Владей предметом стоимостью от 10 млн ₽", "icon": "💎", "category": "items", "requirement_type": "best_item", "requirement_value": 10000000, "reward_amount": 5000, "sort_order": 77}, {"name": "item_worth_100m", "title": "Ценный предмет III", "description": "Владей предметом стоимостью от 100 млн ₽", "icon": "💎", "category": "items", "requirement_type": "best_item", "requirement_value": 100000000, "reward_amount": 25000, "sort_order": 78}, {"name": "item_worth_1b", "title": "Ценный предмет IV", "description": "Владей предметом стоимостью от 1 млрд ₽", "icon": "💎", "category": "items", "requirement_type": "best_item", "requirement_value": 1000000000, "reward_amount": 100000, "sort_order": 79}, {"name": "inventory_value_10m", "title": "Инвентарь 10 млн", "description": "Стоимость инвентаря превысила 10 млн ₽", "icon": "💰", "category": "inventory", "requirement_type": "inventory_total_value", "requirement_value": 10000000, "reward_amount": 5000, "sort_order": 80}, {"name": "inventory_value_100m", "title": "Инвентарь 100 млн", "description": "Стоимость инвентаря превысила 100 млн ₽", "icon": "💰", "category": "inventory", "requirement_type": "inventory_total_value", "requirement_value": 100000000, "reward_amount": 25000, "sort_order": 81}, {"name": "inventory_value_1b", "title": "Инвентарь 1 млрд", "description": "Стоимость инвентаря превысила 1 млрд ₽", "icon": "💰", "category": "inventory", "requirement_type": "inventory_total_value", "requirement_value": 1000000000, "reward_amount": 100000, "sort_order": 82}, {"name": "spent_500k", "title": "Потрачено 500к", "description": "Потрать 500,000 ₽ на кейсы", "icon": "💸", "category": "spending", "requirement_type": "total_spent", "requirement_value": 500000, "reward_amount": 5000, "sort_order": 83}, {"name": "spent_1m", "title": "Потрачен миллион", "description": "Потрать 1,000,000 ₽ на кейсы", "icon": "💸", "category": "spending", "requirement_type": "total_spent", "requirement_value": 1000000, "reward_amount": 10000, "sort_order": 84}, {"name": "spent_10m", "title": "Потрачено 10 млн", "description": "Потрать 10,000,000 ₽ на кейсы", "icon": "💸", "category": "spending", "requirement_type": "total_spent", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 85}, {"name": "spent_100m", "title": "Потрачено 100 млн", "description": "Потрать 100,000,000 ₽ на кейсы", "icon": "💸", "category": "spending", "requirement_type": "total_spent", "requirement_value": 100000000, "reward_amount": 100000, "sort_order": 86}, {"name": "spent_500m", "title": "Потрачено 500 млн", "description": "Потрать 500,000,000 ₽ на кейсы", "icon": "💸", "category": "spending", "requirement_type": "total_spent", "requirement_value": 500000000, "reward_amount": 500000, "sort_order": 87}, {"name": "spent_1b", "title": "Потрачен миллиард", "description": "Потрать 1,000,000,000 ₽ на кейсы", "icon": "💎", "category": "spending", "requirement_type": "total_spent", "requirement_value": 1000000000, "reward_amount": 1000000, "sort_order": 88}, {"name": "cases_2500", "title": "2500 кейсов", "description": "Открой 2500 кейсов", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 2500, "reward_amount": 10000, "sort_order": 89}, {"name": "cases_5000", "title": "5000 кейсов", "description": "Открой 5000 кейсов", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 5000, "reward_amount": 25000, "sort_order": 90}, {"name": "cases_10000", "title": "10000 кейсов", "description": "Открой 10000 кейсов", "icon": "💎", "category": "cases", "requirement_type": "open_cases", "requirement_value": 10000, "reward_amount": 100000, "sort_order": 91}, {"name": "cases_25000", "title": "25000 кейсов", "description": "Открой 25000 кейсов", "icon": "💎", "category": "cases", "requirement_type": "open_cases", "requirement_value": 25000, "reward_amount": 500000, "sort_order": 92}, {"name": "contracts_250", "title": "250 контрактов", "description": "Выполни 250 контрактов", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 250, "reward_amount": 50000, "sort_order": 93}, {"name": "crash_10", "title": "10 игр в Crash", "description": "Сыграй 10 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 10, "reward_amount": 1000, "sort_order": 94}, {"name": "crash_25", "title": "25 игр в Crash", "description": "Сыграй 25 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 25, "reward_amount": 2500, "sort_order": 95}, {"name": "crash_100", "title": "100 игр в Crash", "description": "Сыграй 100 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 100, "reward_amount": 10000, "sort_order": 96}, {"name": "crash_500", "title": "500 игр в Crash", "description": "Сыграй 500 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 500, "reward_amount": 50000, "sort_order": 97}, {"name": "crash_1000", "title": "1000 игр в Crash", "description": "Сыграй 1000 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 1000, "reward_amount": 100000, "sort_order": 98}, {"name": "crash_5000", "title": "5000 игр в Crash", "description": "Сыграй 5000 игр в Crash", "icon": "📈", "category": "crash", "requirement_type": "crash_games", "requirement_value": 5000, "reward_amount": 500000, "sort_order": 99}, {"name": "crash_cashout_10x", "title": "Кэшаут 10x", "description": "Забери деньги на множителе 10x", "icon": "📈", "category": "crash", "requirement_type": "crash_cashout", "requirement_value": 10, "reward_amount": 2000, "sort_order": 100}, {"name": "crash_cashout_25x", "title": "Кэшаут 25x", "description": "Забери деньги на множителе 25x", "icon": "📈", "category": "crash", "requirement_type": "crash_cashout", "requirement_value": 25, "reward_amount": 5000, "sort_order": 101}, {"name": "crash_cashout_50x", "title": "Кэшаут 50x", "description": "Забери деньги на множителе 50x", "icon": "📈", "category": "crash", "requirement_type": "crash_cashout", "requirement_value": 50, "reward_amount": 15000, "sort_order": 102}, {"name": "crash_cashout_100x", "title": "Кэшаут 100x", "description": "Забери деньги на множителе 100x", "icon": "🔥", "category": "crash", "requirement_type": "crash_cashout", "requirement_value": 100, "reward_amount": 50000, "sort_order": 103}, {"name": "upgrade_250", "title": "250 апгрейдов", "description": "Проведи 250 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 250, "reward_amount": 25000, "sort_order": 104}, {"name": "upgrade_500", "title": "500 апгрейдов", "description": "Проведи 500 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 500, "reward_amount": 50000, "sort_order": 105}, {"name": "upgrade_1000", "title": "1000 апгрейдов", "description": "Проведи 1000 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 1000, "reward_amount": 100000, "sort_order": 106}, {"name": "upgrade_2000", "title": "2000 апгрейдов", "description": "Проведи 2000 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 2000, "reward_amount": 250000, "sort_order": 107}, {"name": "upgrade_wins_250", "title": "250 побед в апгрейде", "description": "Выиграй 250 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 250, "reward_amount": 100000, "sort_order": 108}, {"name": "upgrade_consecutive_wins_3", "title": "3 победы подряд", "description": "Выиграй 3 апгрейда подряд", "icon": "🔥", "category": "upgrade", "requirement_type": "upgrade_consecutive_wins", "requirement_value": 3, "reward_amount": 500, "sort_order": 109}, {"name": "upgrade_consecutive_wins_5", "title": "5 побед подряд", "description": "Выиграй 5 апгрейдов подряд", "icon": "🔥", "category": "upgrade", "requirement_type": "upgrade_consecutive_wins", "requirement_value": 5, "reward_amount": 2000, "sort_order": 110}, {"name": "upgrade_consecutive_wins_10", "title": "10 побед подряд", "description": "Выиграй 10 апгрейдов подряд", "icon": "🔥", "category": "upgrade", "requirement_type": "upgrade_consecutive_wins", "requirement_value": 10, "reward_amount": 10000, "sort_order": 111}, {"name": "upgrade_consecutive_losses_5", "title": "5 поражений подряд", "description": "Проиграй 5 апгрейдов подряд", "icon": "💀", "category": "upgrade", "requirement_type": "upgrade_consecutive_losses", "requirement_value": 5, "reward_amount": 1000, "sort_order": 112}, {"name": "upgrade_consecutive_losses_10", "title": "10 поражений подряд", "description": "Проиграй 10 апгрейдов подряд", "icon": "💀", "category": "upgrade", "requirement_type": "upgrade_consecutive_losses", "requirement_value": 10, "reward_amount": 5000, "sort_order": 113}, {"name": "upgrade_miracle", "title": "Чудо", "description": "Выиграй апгрейд с шансом <10%", "icon": "🙏", "category": "upgrade", "requirement_type": "low_prob_wins", "requirement_value": 1, "reward_amount": 500, "sort_order": 114}, {"name": "upgrade_miracle_5", "title": "Чудотворец", "description": "Выиграй 5 апгрейдов с шансом <10%", "icon": "🙏", "category": "upgrade", "requirement_type": "low_prob_wins", "requirement_value": 5, "reward_amount": 5000, "sort_order": 115}, {"name": "upgrade_spent_100k", "title": "Потери 100к", "description": "Потеряй предметы на 100,000 ₽ в неудачных апгрейдах", "icon": "💸", "category": "upgrade", "requirement_type": "upgrade_loss_value", "requirement_value": 100000, "reward_amount": 2000, "sort_order": 116}, {"name": "upgrade_spent_1m", "title": "Потери 1 млн", "description": "Потеряй предметы на 1,000,000 ₽ в неудачных апгрейдах", "icon": "💸", "category": "upgrade", "requirement_type": "upgrade_loss_value", "requirement_value": 1000000, "reward_amount": 10000, "sort_order": 117}, {"name": "upgrade_spent_10m", "title": "Потери 10 млн", "description": "Потеряй предметы на 10,000,000 ₽ в неудачных апгрейдах", "icon": "💸", "category": "upgrade", "requirement_type": "upgrade_loss_value", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 118}, {"name": "upgrade_spent_100m", "title": "Потери 100 млн", "description": "Потеряй предметы на 100,000,000 ₽ в неудачных апгрейдах", "icon": "💸", "category": "upgrade", "requirement_type": "upgrade_loss_value", "requirement_value": 100000000, "reward_amount": 250000, "sort_order": 119}, {"name": "upgrade_high_risk_50k", "title": "Риск 50к", "description": "Поставь в апгрейд предмет стоимостью 50,000 ₽", "icon": "🎲", "category": "upgrade", "requirement_type": "upgrade_risk_value", "requirement_value": 50000, "reward_amount": 500, "sort_order": 120}, {"name": "upgrade_high_risk_500k", "title": "Риск 500к", "description": "Поставь в апгрейд предмет стоимостью 500,000 ₽", "icon": "🎲", "category": "upgrade", "requirement_type": "upgrade_risk_value", "requirement_value": 500000, "reward_amount": 5000, "sort_order": 121}, {"name": "upgrade_high_risk_10m", "title": "Риск 10 млн", "description": "Поставь в апгрейд предмет стоимостью 10,000,000 ₽", "icon": "🎲", "category": "upgrade", "requirement_type": "upgrade_risk_value", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 122}, {"name": "slots_10", "title": "10 игр в слоты", "description": "Сыграй 10 раз в слот-машину", "icon": "🎰", "category": "slots", "requirement_type": "slot_plays", "requirement_value": 10, "reward_amount": 500, "sort_order": 123}, {"name": "slots_25", "title": "25 игр в слоты", "description": "Сыграй 25 раз в слот-машину", "icon": "🎰", "category": "slots", "requirement_type": "slot_plays", "requirement_value": 25, "reward_amount": 1000, "sort_order": 124}, {"name": "slots_100", "title": "100 игр в слоты", "description": "Сыграй 100 раз в слот-машину", "icon": "🎰", "category": "slots", "requirement_type": "slot_plays", "requirement_value": 100, "reward_amount": 5000, "sort_order": 125}, {"name": "slots_500", "title": "500 игр в слоты", "description": "Сыграй 500 раз в слот-машину", "icon": "🎰", "category": "slots", "requirement_type": "slot_plays", "requirement_value": 500, "reward_amount": 20000, "sort_order": 126}, {"name": "slots_jackpot_5", "title": "5 джекпотов", "description": "Собери 5 совпадений в слотах", "icon": "🎰", "category": "slots", "requirement_type": "slot_jackpots", "requirement_value": 5, "reward_amount": 2000, "sort_order": 127}, {"name": "slots_jackpot_25", "title": "25 джекпотов", "description": "Собери 25 совпадений в слотах", "icon": "🎰", "category": "slots", "requirement_type": "slot_jackpots", "requirement_value": 25, "reward_amount": 10000, "sort_order": 128}, {"name": "items_unboxed_100", "title": "100 предметов", "description": "Открой 100 предметов (кейсы/контракты)", "icon": "📦", "category": "items", "requirement_type": "total_items", "requirement_value": 100, "reward_amount": 500, "sort_order": 129}, {"name": "items_unboxed_1000", "title": "1000 предметов", "description": "Открой 1000 предметов (кейсы/контракты)", "icon": "📦", "category": "items", "requirement_type": "total_items", "requirement_value": 1000, "reward_amount": 5000, "sort_order": 130}, {"name": "items_unboxed_10000", "title": "10000 предметов", "description": "Открой 10000 предметов (кейсы/контракты)", "icon": "📦", "category": "items", "requirement_type": "total_items", "requirement_value": 10000, "reward_amount": 50000, "sort_order": 131}, {"name": "total_profit_100k", "title": "Профит 100к", "description": "Получи 100,000 ₽ чистой прибыли", "icon": "💰", "category": "profit", "requirement_type": "total_profit", "requirement_value": 100000, "reward_amount": 1000, "sort_order": 132}, {"name": "total_profit_1m", "title": "Профит 1 млн", "description": "Получи 1,000,000 ₽ чистой прибыли", "icon": "💰", "category": "profit", "requirement_type": "total_profit", "requirement_value": 1000000, "reward_amount": 5000, "sort_order": 133}, {"name": "total_profit_10m", "title": "Профит 10 млн", "description": "Получи 10,000,000 ₽ чистой прибыли", "icon": "💰", "category": "profit", "requirement_type": "total_profit", "requirement_value": 10000000, "reward_amount": 25000, "sort_order": 134}, {"name": "total_profit_100m", "title": "Профит 100 млн", "description": "Получи 100,000,000 ₽ чистой прибыли", "icon": "💰", "category": "profit", "requirement_type": "total_profit", "requirement_value": 100000000, "reward_amount": 100000, "sort_order": 135}, {"name": "achievements_10", "title": "10 достижений", "description": "Получи 10 достижений", "icon": "🏆", "category": "achievements", "requirement_type": "achievements_count", "requirement_value": 10, "reward_amount": 1000, "sort_order": 136}, {"name": "achievements_25", "title": "25 достижений", "description": "Получи 25 достижений", "icon": "🏆", "category": "achievements", "requirement_type": "achievements_count", "requirement_value": 25, "reward_amount": 5000, "sort_order": 137}, {"name": "achievements_50", "title": "50 достижений", "description": "Получи 50 достижений", "icon": "🏆", "category": "achievements", "requirement_type": "achievements_count", "requirement_value": 50, "reward_amount": 25000, "sort_order": 138}, {"name": "achievements_100", "title": "100 достижений", "description": "Получи 100 достижений", "icon": "🏆", "category": "achievements", "requirement_type": "achievements_count", "requirement_value": 100, "reward_amount": 100000, "sort_order": 139}, {"name": "rare_item_10", "title": "10 редких предметов", "description": "Собери 10 редких предметов в инвентаре", "icon": "⭐", "category": "items", "requirement_type": "rare_items_count", "requirement_value": 10, "reward_amount": 5000, "sort_order": 140}, {"name": "rare_item_50", "title": "50 редких предметов", "description": "Собери 50 редких предметов в инвентаре", "icon": "⭐", "category": "items", "requirement_type": "rare_items_count", "requirement_value": 50, "reward_amount": 25000, "sort_order": 141}, {"name": "knife_5", "title": "Пять ножей", "description": "Собери 5 ножей или перчаток", "icon": "🔪", "category": "items", "requirement_type": "knife_count", "requirement_value": 5, "reward_amount": 25000, "sort_order": 142}, {"name": "ultra_rare_float", "title": "Ультра-редкий float", "description": "Найди предмет с float < 0.001", "icon": "💎", "category": "items", "requirement_type": "ultra_rare_float", "requirement_value": 1, "reward_amount": 10000, "sort_order": 143, "hidden": True}, {"name": "inventory_2500", "title": "Инвентарь 2500", "description": "Собери 2500 предметов в инвентаре", "icon": "🎒", "category": "inventory", "requirement_type": "inventory_items", "requirement_value": 2500, "reward_amount": 100000, "sort_order": 144}, {"name": "upgrade_from_nothing", "title": "Из грязи в князи", "description": "Выиграй апгрейд с предметом стоимостью до 1000 ₽", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_from_nothing", "requirement_value": 1000, "reward_amount": 1000, "sort_order": 145}, # Секретные {"name": "secret_99_fail", "title": "99% неудачи", "description": "Провали апгрейд с шансом 99%", "icon": "💀", "category": "secret", "requirement_type": "secret_fail_99", "requirement_value": 1, "reward_amount": 5000, "sort_order": 200, "hidden": True}, {"name": "secret_midnight", "title": "Полуночник", "description": "Открой кейс в промежутке между 00:00 и 04:00", "icon": "🌙", "category": "secret", "requirement_type": "secret_midnight", "requirement_value": 1, "reward_amount": 2000, "sort_order": 201, "hidden": True}, {"name": "secret_exact_balance", "title": "Ровно 1337", "description": "Имей ровно 1337 ₽ на балансе", "icon": "💀", "category": "secret", "requirement_type": "secret_exact_balance", "requirement_value": 1, "reward_amount": 1337, "sort_order": 202, "hidden": True}, {"name": "secret_negative_balance", "title": "В долгах", "description": "Уйди в минус по балансу", "icon": "💀", "category": "secret", "requirement_type": "secret_negative_balance", "requirement_value": 1, "reward_amount": 5000, "sort_order": 203, "hidden": True}, {"name": "secret_1000_exact", "title": "Ровно 1000", "description": "Открой ровно 1000 кейсов", "icon": "🎯", "category": "secret", "requirement_type": "secret_1000_exact", "requirement_value": 1, "reward_amount": 50000, "sort_order": 204, "hidden": True}, {"name": "secret_empty_inv", "title": "Пустой инвентарь", "description": "Имей 0 предметов в инвентаре", "icon": "💀", "category": "secret", "requirement_type": "secret_empty_inv", "requirement_value": 1, "reward_amount": 1000, "sort_order": 205, "hidden": True}, {"name": "secret_spent_exact", "title": "Число зверя", "description": "Потрать ровно 666,666 ₽", "icon": "😈", "category": "secret", "requirement_type": "secret_spent_exact", "requirement_value": 1, "reward_amount": 6666, "sort_order": 206, "hidden": True}, {"name": "secret_low_prob_win", "title": "Удача новичка", "description": "Выиграй апгрейд с шансом <10% первый раз", "icon": "🎯", "category": "secret", "requirement_type": "secret_low_prob_win", "requirement_value": 1, "reward_amount": 10000, "sort_order": 207, "hidden": True}, {"name": "secret_dildo_specific", "title": "Конкретный Dildo", "description": "Найди Dildo с названием содержащим 'Big Black'", "icon": "🍆", "category": "secret", "requirement_type": "secret_dildo_specific", "requirement_value": 1, "reward_amount": 10000, "sort_order": 208, "hidden": True}, {"name": "secret_crash_1x", "title": "Крэш 1x", "description": "Забери деньги на множителе 1.00x", "icon": "🎯", "category": "secret", "requirement_type": "secret_crash_1x", "requirement_value": 1, "reward_amount": 1000, "sort_order": 209, "hidden": True}, {"name": "secret_balance_777", "title": "777", "description": "Баланс оканчивается на 777", "icon": "🎰", "category": "secret", "requirement_type": "secret_balance_777", "requirement_value": 777, "reward_amount": 7777, "sort_order": 210, "hidden": True}, {"name": "secret_one_item_inv", "title": "Одиночка", "description": "Имей ровно 1 предмет в инвентаре", "icon": "🎯", "category": "secret", "requirement_type": "secret_one_item_inv", "requirement_value": 1, "reward_amount": 1000, "sort_order": 211, "hidden": True}, {"name": "secret_contract_dildo", "title": "Случайный Dildo", "description": "Получи Dildo из контракта обмена", "icon": "🍆", "category": "secret", "requirement_type": "secret_contract_dildo", "requirement_value": 1, "reward_amount": 25000, "sort_order": 212, "hidden": True}, {"name": "secret_slot_zero", "title": "Ноль совпадений", "description": "Получи 0 совпадений в слоте", "icon": "🎰", "category": "secret", "requirement_type": "secret_slot_zero", "requirement_value": 1, "reward_amount": 5000, "sort_order": 213, "hidden": True}, {"name": "secret_exact_float", "title": "Точный float", "description": "Найди предмет с float ровно 0.001 (±0.0001)", "icon": "🎯", "category": "secret", "requirement_type": "secret_exact_float", "requirement_value": 1, "reward_amount": 50000, "sort_order": 214, "hidden": True}, ] _DILDO_IDS_CACHE = None def _get_dildo_ids(): global _DILDO_IDS_CACHE if _DILDO_IDS_CACHE is None: from backend import ALL_ITEMS _DILDO_IDS_CACHE = [i for i, it in enumerate(ALL_ITEMS) if it.get("weapon") == "Dildo"] return _DILDO_IDS_CACHE # ─── async seed ───────────────────────────────────────────────────────────── async def seed_achievements(db: AsyncSession): result = await db.execute(select(sa_func.count()).select_from(Achievement)) existing = result.scalar() or 0 if existing > 0: result = await db.execute(select(Achievement.name)) existing_names = {row[0] for row in result.all()} added = 0 for defn in ACHIEVEMENT_DEFS: if defn["name"] not in existing_names: db.add(Achievement(**defn)) added += 1 if added: await db.commit() print(f"[Achievements] Добавлено {added} новых достижений") return for defn in ACHIEVEMENT_DEFS: db.add(Achievement(**defn)) await db.commit() async def get_or_create_user_achievement(db: AsyncSession, user_id: int, achievement_id: int) -> UserAchievement: result = await db.execute( select(UserAchievement).where( UserAchievement.user_id == user_id, UserAchievement.achievement_id == achievement_id ) ) ua = result.scalar_one_or_none() if not ua: ua = UserAchievement(user_id=user_id, achievement_id=achievement_id) db.add(ua) await db.commit() await db.refresh(ua) return ua async def check_achievement(db: AsyncSession, user_id: int, achievement_name: str, current_value: float) -> Optional[dict]: result = await db.execute(select(Achievement).where(Achievement.name == achievement_name)) ach = result.scalar_one_or_none() if not ach: return None ua = await get_or_create_user_achievement(db, user_id, ach.id) if ua.unlocked_at: return None progress_val = min(int(current_value), ach.requirement_value) if isinstance(ach.requirement_value, int) else min(current_value, ach.requirement_value) ua.progress = int(progress_val) if ach.requirement_type not in ("dildo_best_float", "ultra_rare_float", "secret_exact_float") else int(progress_val * 10000) if current_value >= ach.requirement_value: ua.unlocked_at = datetime.utcnow() activity = ActivityFeed( user_id=user_id, username=f"user_{user_id}", activity_type="achievement", message=f"🏆 Получено достижение: {ach.title}", data_json=json.dumps({"achievement_name": ach.name, "achievement_title": ach.title, "achievement_icon": ach.icon}) ) db.add(activity) await db.flush() return {"name": ach.name, "title": ach.title, "icon": ach.icon, "reward": ach.reward_amount} await db.flush() return None # ─── Проверки (async) ────────────────────────────────────────────────────── async def check_open_cases_achievements(db: AsyncSession, user_id: int): result = await db.execute(select(sa_func.count()).select_from(CaseOpening).where(CaseOpening.user_id == user_id)) count = result.scalar() or 0 results = [] for n in ["first_case", "case_collector_10", "case_collector_100", "case_collector_1000", "cases_2500", "cases_5000", "cases_10000", "cases_25000"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_contracts_achievements(db: AsyncSession, user_id: int): result = await db.execute(select(sa_func.count()).select_from(Contract).where(Contract.user_id == user_id)) count = result.scalar() or 0 results = [] for n in ["first_contract", "contract_master", "contracts_10", "contracts_25", "contracts_100", "contracts_250"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_upgrade_achievements(db: AsyncSession, user_id: int): from database import Upgrade result = await db.execute(select(sa_func.count()).select_from(Upgrade).where(Upgrade.user_id == user_id)) attempts = result.scalar() or 0 result = await db.execute(select(sa_func.count()).select_from(Upgrade).where(Upgrade.user_id == user_id, Upgrade.success == True)) wins = result.scalar() or 0 results = [] for n in ["first_upgrade", "upgrade_5", "upgrade_25", "upgrade_50", "upgrade_100", "upgrade_250", "upgrade_500", "upgrade_1000", "upgrade_2000"]: r = await check_achievement(db, user_id, n, attempts) if r: results.append(r) for n in ["upgrade_winner", "upgrade_wins_5", "upgrade_wins_25", "upgrade_wins_50", "upgrade_wins_100", "upgrade_wins_250"]: r = await check_achievement(db, user_id, n, wins) if r: results.append(r) from backend import ALL_ITEMS result = await db.execute(select(Upgrade.input_item_id).where(Upgrade.user_id == user_id, Upgrade.success == False)) total_spent_val = sum(ALL_ITEMS[iid].get("price_rub", 0) for (iid,) in result.all() if iid < len(ALL_ITEMS)) for n in ["upgrade_spent_100k", "upgrade_spent_1m", "upgrade_spent_10m", "upgrade_spent_100m"]: r = await check_achievement(db, user_id, n, total_spent_val) if r: results.append(r) return results async def check_upgrade_risk_achievements(db: AsyncSession, user_id: int, item_price: float): results = [] for n in ["upgrade_high_risk_50k", "upgrade_high_risk_500k", "upgrade_high_risk_10m"]: r = await check_achievement(db, user_id, n, item_price) if r: results.append(r) return results async def check_upgrade_result_achievements(db: AsyncSession, user_id: int, success: bool, probability: float, item_price: float): from database import Upgrade from upgrade import get_upgrade_rpu_async results = [] rpu = await get_upgrade_rpu_async(db, user_id) if rpu.current_win_streak > 0: for n in ["upgrade_consecutive_wins_3", "upgrade_consecutive_wins_5", "upgrade_consecutive_wins_10"]: r = await check_achievement(db, user_id, n, rpu.current_win_streak) if r: results.append(r) if rpu.current_lose_streak > 0: for n in ["upgrade_consecutive_losses_5", "upgrade_consecutive_losses_10"]: r = await check_achievement(db, user_id, n, rpu.current_lose_streak) if r: results.append(r) if success and item_price <= 1000: r = await check_achievement(db, user_id, "upgrade_from_nothing", 1000) if r: results.append(r) if success and probability < 10: result = await db.execute(select(sa_func.count()).select_from(Upgrade).where( Upgrade.user_id == user_id, Upgrade.success == True, Upgrade.probability < 10 )) low_wins = result.scalar() or 0 for n in ["upgrade_miracle", "upgrade_miracle_5"]: r = await check_achievement(db, user_id, n, low_wins) if r: results.append(r) if not success and probability >= 99: r = await check_achievement(db, user_id, "secret_99_fail", 1) if r: results.append(r) if success and probability < 10: r = await check_achievement(db, user_id, "secret_low_prob_win", 1) if r: results.append(r) return results async def check_rare_item_achievement(db: AsyncSession, user_id: int, rarity: str): if rarity in ("Covert", "Extraordinary", "Rare Special Item"): r = await check_achievement(db, user_id, "rare_finder", 1) return [r] if r else [] return [] async def check_knife_achievement(db: AsyncSession, user_id: int, item_data: dict): from backend import is_knife, is_gloves if is_knife(item_data) or is_gloves(item_data): r = await check_achievement(db, user_id, "knife_collector", 1) return [r] if r else [] return [] async def check_spent_achievements(db: AsyncSession, user_id: int, total_spent: float): results = [] for n in ["big_spender", "whale", "spent_500k", "spent_1m", "spent_10m", "spent_100m", "spent_500m", "spent_1b"]: r = await check_achievement(db, user_id, n, int(total_spent)) if r: results.append(r) if int(total_spent) == 666666: r = await check_achievement(db, user_id, "secret_spent_exact", 1) if r: results.append(r) return results async def check_balance_achievements(db: AsyncSession, user_id: int): result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: return [] results = [] if user.balance >= 100000: r = await check_achievement(db, user_id, "rich_100k", 1) if r: results.append(r) for n in ["balance_1m", "balance_10m", "balance_100m", "balance_1b"]: r = await check_achievement(db, user_id, n, user.balance) if r: results.append(r) if user.balance == 1337: r = await check_achievement(db, user_id, "secret_exact_balance", 1) if r: results.append(r) if user.balance > 0 and user.balance % 1000 == 777: r = await check_achievement(db, user_id, "secret_balance_777", 777) if r: results.append(r) if user.balance < 0: r = await check_achievement(db, user_id, "secret_negative_balance", 1) if r: results.append(r) return results async def check_slot_match_achievements(db: AsyncSession, user_id: int): r = await check_achievement(db, user_id, "slot_winner", 1) return [r] if r else [] async def check_slot_plays_achievements(db: AsyncSession, user_id: int): result = await db.execute( select(sa_func.count()).select_from(CaseOpening).where( CaseOpening.user_id == user_id, CaseOpening.case_name.ilike("%slot%") ) ) count = result.scalar() or 0 results = [] for n in ["slots_10", "slots_25", "slots_100", "slots_500"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_slot_match_count_achievements(db: AsyncSession, user_id: int): result = await db.execute( select(sa_func.count()).select_from(InventoryItem).where( InventoryItem.user_id == user_id, InventoryItem.obtained_from.ilike("slot_bonus:%") ) ) count = result.scalar() or 0 results = [] for n in ["slots_jackpot_5", "slots_jackpot_25"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_inventory_achievements(db: AsyncSession, user_id: int): result = await db.execute(select(sa_func.count()).select_from(InventoryItem).where(InventoryItem.user_id == user_id)) count = result.scalar() or 0 results = [] for n in ["inventory_50", "inventory_100", "inventory_250", "inventory_500", "inventory_1000", "inventory_2500"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) if count == 0: r = await check_achievement(db, user_id, "secret_empty_inv", 1) if r: results.append(r) if count == 1: r = await check_achievement(db, user_id, "secret_one_item_inv", 1) if r: results.append(r) return results async def check_dildo_achievements(db: AsyncSession, user_id: int): from backend import ALL_ITEMS dildo_ids = _get_dildo_ids() results = [] if dildo_ids: result = await db.execute( select(InventoryItem).where( InventoryItem.user_id == user_id, InventoryItem.item_id.in_(dildo_ids) ) ) dildo_inv_raw = result.scalars().all() else: dildo_inv_raw = [] dildo_inv = [(inv, ALL_ITEMS[inv.item_id]) for inv in dildo_inv_raw if inv.item_id < len(ALL_ITEMS)] dildo_count = len(dildo_inv) for n in ["dildo_owned", "dildo_5", "dildo_10", "dildo_25", "dildo_50", "dildo_100"]: r = await check_achievement(db, user_id, n, dildo_count) if r: results.append(r) result = await db.execute( select(sa_func.max(InventoryItem.price_rub)).where(InventoryItem.user_id == user_id) ) best_price = result.scalar() or 0 for n in ["item_worth_1m", "item_worth_10m", "item_worth_100m", "item_worth_1b"]: r = await check_achievement(db, user_id, n, best_price) if r: results.append(r) result = await db.execute( select(sa_func.sum(InventoryItem.price_rub)).where(InventoryItem.user_id == user_id) ) total_val = result.scalar() or 0 for n in ["inventory_value_10m", "inventory_value_100m", "inventory_value_1b"]: r = await check_achievement(db, user_id, n, total_val) if r: results.append(r) if dildo_ids: result = await db.execute( select(sa_func.min(InventoryItem.float_value)).where( InventoryItem.user_id == user_id, InventoryItem.item_id.in_(dildo_ids) ) ) best_dildo_float = result.scalar() or 1.0 else: best_dildo_float = 1.0 if best_dildo_float < 0.01: r = await check_achievement(db, user_id, "dildo_float_001", 1) if r: results.append(r) if best_dildo_float < 0.005: r = await check_achievement(db, user_id, "dildo_float_0005", 2) if r: results.append(r) result = await db.execute( select(sa_func.min(InventoryItem.float_value)).where(InventoryItem.user_id == user_id) ) ultra_rare = result.scalar() or 1.0 if ultra_rare < 0.001: r = await check_achievement(db, user_id, "ultra_rare_float", 0.001) if r: results.append(r) result = await db.execute( select(InventoryItem).where( InventoryItem.user_id == user_id, sa_func.abs(InventoryItem.float_value - 0.001) < 0.0001 ).limit(1) ) if result.scalar_one_or_none(): r = await check_achievement(db, user_id, "secret_exact_float", 1) if r: results.append(r) if dildo_inv: dildo_rarities = set() unique_patterns = set() dildo_total = 0 for inv_item, item_data in dildo_inv: dildo_rarities.add(item_data.get("rarity")) unique_patterns.add(item_data.get("market_hash_name", "")) dildo_total += item_data.get("price_rub", 0) if len(dildo_rarities) >= 5: r = await check_achievement(db, user_id, "dildo_all_rarities", 1) if r: results.append(r) r = await check_achievement(db, user_id, "dildo_full_set", len(unique_patterns)) if r: results.append(r) r = await check_achievement(db, user_id, "dildo_worth_100m", dildo_total) if r: results.append(r) for inv_item, item_data in dildo_inv: if "Big Black" in item_data.get("market_hash_name", ""): r = await check_achievement(db, user_id, "secret_dildo_specific", 1) if r: results.append(r) break return results async def check_dildo_contract_achievement(db: AsyncSession, user_id: int): from backend import ALL_ITEMS result = await db.execute(select(Contract).where(Contract.user_id == user_id)) contracts = result.scalars().all() results = [] for c in contracts: if c.output_item_id < len(ALL_ITEMS) and ALL_ITEMS[c.output_item_id].get("weapon") == "Dildo": r = await check_achievement(db, user_id, "dildo_from_contract", 1) if r: results.append(r) break for c in contracts: if c.output_item_id < len(ALL_ITEMS) and ALL_ITEMS[c.output_item_id].get("weapon") == "Dildo": r = await check_achievement(db, user_id, "secret_contract_dildo", 1) if r: results.append(r) break return results async def check_crash_achievements(db: AsyncSession, user_id: int, crash_count: int): results = [] for n in ["crash_first", "crash_pro", "crash_10", "crash_25", "crash_100", "crash_500", "crash_1000", "crash_5000"]: r = await check_achievement(db, user_id, n, crash_count) if r: results.append(r) return results async def check_crash_cashout_achievements(db: AsyncSession, user_id: int, multiplier: float): results = [] for n in ["crash_5x", "crash_cashout_10x", "crash_cashout_25x", "crash_cashout_50x", "crash_cashout_100x"]: req_val = {"crash_5x": 5, "crash_cashout_10x": 10, "crash_cashout_25x": 25, "crash_cashout_50x": 50, "crash_cashout_100x": 100}[n] if multiplier >= req_val: r = await check_achievement(db, user_id, n, multiplier) if r: results.append(r) if abs(multiplier - 1.0) < 0.01: r = await check_achievement(db, user_id, "secret_crash_1x", 1) if r: results.append(r) return results async def check_items_unboxed_achievements(db: AsyncSession, user_id: int): result = await db.execute(select(sa_func.count()).select_from(CaseOpening).where(CaseOpening.user_id == user_id)) count = result.scalar() or 0 results = [] for n in ["items_unboxed_100", "items_unboxed_1000", "items_unboxed_10000"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_total_profit_achievements(db: AsyncSession, user_id: int, profit: float): results = [] for n in ["total_profit_100k", "total_profit_1m", "total_profit_10m", "total_profit_100m"]: r = await check_achievement(db, user_id, n, profit) if r: results.append(r) return results async def check_achievements_count_achievements(db: AsyncSession, user_id: int): result = await db.execute( select(sa_func.count()).select_from(UserAchievement).where( UserAchievement.user_id == user_id, UserAchievement.unlocked_at.isnot(None) ) ) count = result.scalar() or 0 results = [] for n in ["achievements_10", "achievements_25", "achievements_50", "achievements_100"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_rare_item_count_achievements(db: AsyncSession, user_id: int): result = await db.execute( select(sa_func.count()).select_from(InventoryItem).where( InventoryItem.user_id == user_id, InventoryItem.rarity.in_(["Covert", "Extraordinary", "Rare Special Item"]) ) ) count = result.scalar() or 0 results = [] for n in ["rare_item_10", "rare_item_50"]: r = await check_achievement(db, user_id, n, count) if r: results.append(r) return results async def check_knife_count_achievements(db: AsyncSession, user_id: int): from backend import ALL_ITEMS, is_knife, is_gloves result = await db.execute(select(InventoryItem.item_id).where(InventoryItem.user_id == user_id)) knife_count = 0 for (item_id,) in result.all(): if item_id < len(ALL_ITEMS): item_data = ALL_ITEMS[item_id] if is_knife(item_data) or is_gloves(item_data): knife_count += 1 results = [] for n in ["knife_5"]: r = await check_achievement(db, user_id, n, knife_count) if r: results.append(r) return results async def check_midnight_case_achievement(db: AsyncSession, user_id: int) -> list: from sqlalchemy import func as sf result = await db.execute( select(sa_func.count()).select_from(CaseOpening).where( CaseOpening.user_id == user_id, sf.extract('hour', CaseOpening.opened_at).between(0, 4) ) ) if (result.scalar() or 0) > 0: r = await check_achievement(db, user_id, "secret_midnight", 1) return [r] if r else [] return [] async def check_exact_cases_achievement(db: AsyncSession, user_id: int, case_count: int) -> list: r = await check_achievement(db, user_id, "secret_1000_exact", 1 if case_count == 1000 else 0) return [r] if r else [] async def check_collection_achievements(db: AsyncSession, user_id: int): _build_collection_cache() cache = _COLLECTION_CACHE coll_base_names = cache["base_names"] coll_item_ids = cache["item_ids"] coll_is_custom = cache["is_custom"] results = [] result = await db.execute(select(InventoryItem.item_id).where(InventoryItem.user_id == user_id)) user_ids = {row[0] for row in result.all()} from backend import ALL_ITEMS, COLLECTIONS_INDEX, extract_base_name, RARITY_ORDER user_coll_base_names = {} for coll_name, item_ids_set in coll_item_ids.items(): owned = user_ids & item_ids_set if owned: owned_bases = set() for cid in owned: if cid < len(ALL_ITEMS): base = extract_base_name(ALL_ITEMS[cid].get("market_hash_name", "")) if base: owned_bases.add(base) if owned_bases: user_coll_base_names[coll_name] = owned_bases coll_count = len(user_coll_base_names) for n in ["coll_first", "coll_5", "coll_10", "coll_25", "coll_50", "coll_75", "coll_100"]: r = await check_achievement(db, user_id, n, coll_count) if r: results.append(r) all_owned_bases = set() for bases in user_coll_base_names.values(): all_owned_bases.update(bases) total_unique = len(all_owned_bases) for n in ["coll_items_10", "coll_items_50", "coll_items_100", "coll_items_250", "coll_items_500", "coll_items_1000"]: r = await check_achievement(db, user_id, n, total_unique) if r: results.append(r) complete_count = 0 for coll_name, all_bases in coll_base_names.items(): owned = user_coll_base_names.get(coll_name, set()) if all_bases.issubset(owned): complete_count += 1 for n in ["coll_complete_1", "coll_complete_5", "coll_complete_10", "coll_complete_25", "coll_complete_50"]: r = await check_achievement(db, user_id, n, complete_count) if r: results.append(r) non_custom = {k: v for k, v in coll_base_names.items() if not coll_is_custom.get(k)} all_base_ok = all(non_custom[c].issubset(user_coll_base_names.get(c, set())) for c in non_custom) if all_base_ok and non_custom: r = await check_achievement(db, user_id, "coll_all_base", 1) if r: results.append(r) all_ok = all(coll_base_names[c].issubset(user_coll_base_names.get(c, set())) for c in coll_base_names) if all_ok and coll_base_names: r = await check_achievement(db, user_id, "coll_all", 1) if r: results.append(r) rarity_collectors = [ ("coll_cheap_collector", {0}), ("coll_mid_collector", {2}), ("coll_rare_collector", {3, 4, 5, 6, 7, 8}), ("coll_covert_collector", {5}), ] for ach_name, target_levels in rarity_collectors: found = False for coll_name in coll_base_names: owned = user_coll_base_names.get(coll_name, set()) all_target = set() owned_target = set() for cid in COLLECTIONS_INDEX.get(coll_name, []): if cid >= len(ALL_ITEMS): continue item = ALL_ITEMS[cid] base = extract_base_name(item.get("market_hash_name", "")) if not base: continue rl = RARITY_ORDER.get(item.get("rarity", ""), -1) if rl in target_levels: all_target.add(base) if cid in user_ids: owned_target.add(base) if all_target and all_target.issubset(owned_target): found = True break if found: r = await check_achievement(db, user_id, ach_name, 1) if r: results.append(r) for coll_name in coll_base_names: if coll_name.startswith("Case:"): owned = user_coll_base_names.get(coll_name, set()) if coll_base_names[coll_name].issubset(owned): r = await check_achievement(db, user_id, "coll_full_case", 1) if r: results.append(r) break return results async def check_all_secret_achievements(db: AsyncSession, user_id: int): return [] async def get_user_achievements(db: AsyncSession, user_id: int) -> list: result = await db.execute(select(Achievement).order_by(Achievement.sort_order)) all_ach = result.scalars().all() result_list = [] for ach in all_ach: result_ua = await db.execute( select(UserAchievement).where( UserAchievement.user_id == user_id, UserAchievement.achievement_id == ach.id ) ) ua = result_ua.scalar_one_or_none() unlocked = ua.unlocked_at is not None if ua else False if ach.hidden and not unlocked: result_list.append({ "id": ach.id, "name": ach.name, "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": ach.category, "requirement_type": ach.requirement_type, "requirement_value": ach.requirement_value, "reward_amount": ach.reward_amount, "progress": 0, "unlocked": False, "unlocked_at": None, "hidden": True, }) else: result_list.append({ "id": ach.id, "name": ach.name, "title": ach.title, "description": ach.description, "icon": ach.icon, "category": ach.category, "requirement_type": ach.requirement_type, "requirement_value": ach.requirement_value, "reward_amount": ach.reward_amount, "progress": ua.progress if ua else 0, "unlocked": unlocked, "unlocked_at": ua.unlocked_at.isoformat() if ua and ua.unlocked_at else None, "hidden": ach.hidden if hasattr(ach, 'hidden') else False, }) return result_list