from typing import Optional from sqlalchemy.orm import Session from database import Achievement, UserAchievement, ActivityFeed from datetime import datetime import json import math # ─── Определения достижений ───────────────────────────────────────────────── 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": "Выполни 50 контрактов обмена", "icon": "🔄", "category": "contracts", "requirement_type": "contracts", "requirement_value": 50, "reward_amount": 2000, "sort_order": 6, }, { "name": "first_upgrade", "title": "Рисковый", "description": "Попробуй апгрейд в первый раз", "icon": "🆙", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 1, "reward_amount": 100, "sort_order": 7, }, { "name": "upgrade_winner", "title": "Везунчик", "description": "Выиграй 10 апгрейдов", "icon": "🍀", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 10, "reward_amount": 1500, "sort_order": 8, }, { "name": "rare_finder", "title": "Охотник за редким", "description": "Выбей предмет редкости Covert или Rare Special", "icon": "🔴", "category": "cases", "requirement_type": "rare_item", "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": 2000, "sort_order": 10, }, { "name": "big_spender", "title": "Транжира", "description": "Потрать 10000 ₽ на кейсы", "icon": "💰", "category": "general", "requirement_type": "total_spent", "requirement_value": 10000, "reward_amount": 1000, "sort_order": 11, }, { "name": "whale", "title": "Кит", "description": "Потрать 100000 ₽ на кейсы", "icon": "🐋", "category": "general", "requirement_type": "total_spent", "requirement_value": 100000, "reward_amount": 10000, "sort_order": 12, }, { "name": "slot_winner", "title": "Джекпот", "description": "Собери 3 одинаковых символа в слот-машине", "icon": "🎰", "category": "cases", "requirement_type": "slot_match", "requirement_value": 1, "reward_amount": 1000, "sort_order": 13, }, { "name": "rich_100k", "title": "Богач", "description": "Накопи 100000 ₽ на балансе", "icon": "🤑", "category": "general", "requirement_type": "balance_milestone", "requirement_value": 100000, "reward_amount": 5000, "sort_order": 14, }, { "name": "slots_10", "title": "Слото-фанат", "description": "Сыграй 10 раундов в слот-машине", "icon": "🎰", "category": "cases", "requirement_type": "slot_plays", "requirement_value": 10, "reward_amount": 500, "sort_order": 15, }, { "name": "crash_first", "title": "Крэш-тестер", "description": "Сыграй в Crash в первый раз", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 1, "reward_amount": 100, "sort_order": 16, }, { "name": "crash_pro", "title": "Крэш-профи", "description": "Сыграй 50 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 50, "reward_amount": 2000, "sort_order": 17, }, { "name": "crash_5x", "title": "Смельчак", "description": "Выведи деньги на множителе x5 или выше в Crash", "icon": "🚀", "category": "crash", "requirement_type": "crash_cashout_milestone", "requirement_value": 5, "reward_amount": 1500, "sort_order": 18, }, { "name": "inventory_50", "title": "Собиратель", "description": "Собери 50 предметов в инвентаре", "icon": "🎒", "category": "general", "requirement_type": "inventory_count", "requirement_value": 50, "reward_amount": 500, "sort_order": 19, }, # ═══════════════════════════════════════════════════════════════════════════ # 🆙 АПГРЕЙД (28 новых) # ═══════════════════════════════════════════════════════════════════════════ { "name": "upgrade_5", "title": "Начинающий", "description": "Сыграй 5 апгрейдов", "icon": "🆙", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 5, "reward_amount": 200, "sort_order": 20, }, { "name": "upgrade_25", "title": "Энтузиаст", "description": "Сыграй 25 апгрейдов", "icon": "🆙", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 25, "reward_amount": 500, "sort_order": 21, }, { "name": "upgrade_50", "title": "Мастер апгрейда", "description": "Сыграй 50 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 50, "reward_amount": 1500, "sort_order": 22, }, { "name": "upgrade_100", "title": "Профи", "description": "Сыграй 100 апгрейдов", "icon": "⬆️", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 100, "reward_amount": 3000, "sort_order": 23, }, { "name": "upgrade_250", "title": "Ветеран", "description": "Сыграй 250 апгрейдов", "icon": "⚡", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 250, "reward_amount": 7500, "sort_order": 24, }, { "name": "upgrade_500", "title": "Легенда", "description": "Сыграй 500 апгрейдов", "icon": "⚡", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 500, "reward_amount": 15000, "sort_order": 25, }, { "name": "upgrade_1000", "title": "Бессмертный", "description": "Сыграй 1000 апгрейдов", "icon": "🏆", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 1000, "reward_amount": 30000, "sort_order": 26, }, { "name": "upgrade_wins_5", "title": "Удачный старт", "description": "Выиграй 5 апгрейдов", "icon": "🍀", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 5, "reward_amount": 400, "sort_order": 27, }, { "name": "upgrade_wins_25", "title": "Счастливчик", "description": "Выиграй 25 апгрейдов", "icon": "🍀", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 25, "reward_amount": 2000, "sort_order": 28, }, { "name": "upgrade_wins_50", "title": "Баловень судьбы", "description": "Выиграй 50 апгрейдов", "icon": "✨", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 50, "reward_amount": 5000, "sort_order": 29, }, { "name": "upgrade_wins_100", "title": "Везучий", "description": "Выиграй 100 апгрейдов", "icon": "✨", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 100, "reward_amount": 10000, "sort_order": 30, }, { "name": "upgrade_wins_250", "title": "Невезучий... или нет?", "description": "Выиграй 250 апгрейдов", "icon": "🌟", "category": "upgrade", "requirement_type": "upgrade_wins", "requirement_value": 250, "reward_amount": 25000, "sort_order": 31, }, { "name": "upgrade_high_risk_50k", "title": "Ол-ин", "description": "Поставь предмет дороже 50 000 ₽ в апгрейд", "icon": "💀", "category": "upgrade", "requirement_type": "upgrade_high_risk_item", "requirement_value": 50000, "reward_amount": 2000, "sort_order": 32, }, { "name": "upgrade_high_risk_500k", "title": "Сорвиголова", "description": "Поставь предмет дороже 500 000 ₽ в апгрейд", "icon": "💀", "category": "upgrade", "requirement_type": "upgrade_high_risk_item", "requirement_value": 500000, "reward_amount": 15000, "sort_order": 33, }, { "name": "upgrade_high_risk_10m", "title": "Безумец", "description": "Поставь предмет дороже 10 000 000 ₽ в апгрейд", "icon": "☠️", "category": "upgrade", "requirement_type": "upgrade_high_risk_item", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 34, }, { "name": "upgrade_2000", "title": "Риск — дело благородное", "description": "Сыграй 2000 апгрейдов", "icon": "🎲", "category": "upgrade", "requirement_type": "upgrade_attempts", "requirement_value": 2000, "reward_amount": 5000, "sort_order": 35, }, { "name": "upgrade_consecutive_wins_3", "title": "Хет-трик", "description": "Выиграй 3 апгрейда подряд", "icon": "🔥", "category": "upgrade", "requirement_type": "upgrade_consecutive_wins", "requirement_value": 3, "reward_amount": 1000, "sort_order": 36, }, { "name": "upgrade_consecutive_wins_5", "title": "Победная серия", "description": "Выиграй 5 апгрейдов подряд", "icon": "🔥", "category": "upgrade", "requirement_type": "upgrade_consecutive_wins", "requirement_value": 5, "reward_amount": 3000, "sort_order": 37, }, { "name": "upgrade_consecutive_wins_10", "title": "Неостановимый", "description": "Выиграй 10 апгрейдов подряд", "icon": "🌋", "category": "upgrade", "requirement_type": "upgrade_consecutive_wins", "requirement_value": 10, "reward_amount": 10000, "sort_order": 38, }, { "name": "upgrade_consecutive_losses_5", "title": "Неудачник", "description": "Проиграй 5 апгрейдов подряд", "icon": "😅", "category": "upgrade", "requirement_type": "upgrade_consecutive_losses", "requirement_value": 5, "reward_amount": 500, "sort_order": 39, }, { "name": "upgrade_consecutive_losses_10", "title": "Жертва системы", "description": "Проиграй 10 апгрейдов подряд", "icon": "😭", "category": "upgrade", "requirement_type": "upgrade_consecutive_losses", "requirement_value": 10, "reward_amount": 1500, "sort_order": 40, }, { "name": "upgrade_from_nothing", "title": "Из грязи в князи", "description": "Выиграй апгрейд с предметом дешевле 1000 ₽", "icon": "🪴", "category": "upgrade", "requirement_type": "upgrade_low_value_win", "requirement_value": 1000, "reward_amount": 2000, "sort_order": 41, }, { "name": "upgrade_miracle", "title": "Чудо", "description": "Выиграй апгрейд с шансом менее 10%", "icon": "🙏", "category": "upgrade", "requirement_type": "upgrade_low_prob_win", "requirement_value": 10, "reward_amount": 5000, "sort_order": 42, }, { "name": "upgrade_miracle_5", "title": "Божественное вмешательство", "description": "Выиграй 5 апгрейдов с шансом менее 10%", "icon": "😇", "category": "upgrade", "requirement_type": "upgrade_low_prob_win", "requirement_value": 5, "reward_amount": 30000, "sort_order": 43, }, { "name": "upgrade_spent_100k", "title": "Инвестор", "description": "Суммарная стоимость проигранных предметов в апгрейдах — 100 000 ₽", "icon": "💸", "category": "upgrade", "requirement_type": "upgrade_total_spent_value", "requirement_value": 100000, "reward_amount": 2000, "sort_order": 44, }, { "name": "upgrade_spent_1m", "title": "Крупный инвестор", "description": "Суммарная стоимость проигранных предметов в апгрейдах — 1 000 000 ₽", "icon": "💸", "category": "upgrade", "requirement_type": "upgrade_total_spent_value", "requirement_value": 1000000, "reward_amount": 10000, "sort_order": 45, }, { "name": "upgrade_spent_10m", "title": "Магнат", "description": "Суммарная стоимость проигранных предметов в апгрейдах — 10 000 000 ₽", "icon": "💰", "category": "upgrade", "requirement_type": "upgrade_total_spent_value", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 46, }, { "name": "upgrade_spent_100m", "title": "Олигарх", "description": "Суммарная стоимость проигранных предметов в апгрейдах — 100 000 000 ₽", "icon": "💰", "category": "upgrade", "requirement_type": "upgrade_total_spent_value", "requirement_value": 100000000, "reward_amount": 200000, "sort_order": 47, }, # ═══════════════════════════════════════════════════════════════════════════ # 📦 КЕЙСЫ (12 новых) # ═══════════════════════════════════════════════════════════════════════════ { "name": "cases_2500", "title": "Кейсоман", "description": "Открой 2 500 кейсов", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 2500, "reward_amount": 10000, "sort_order": 48, }, { "name": "cases_5000", "title": "Индустрия кейсов", "description": "Открой 5 000 кейсов", "icon": "📦", "category": "cases", "requirement_type": "open_cases", "requirement_value": 5000, "reward_amount": 25000, "sort_order": 49, }, { "name": "cases_10000", "title": "Кейс-магнат", "description": "Открой 10 000 кейсов", "icon": "🏭", "category": "cases", "requirement_type": "open_cases", "requirement_value": 10000, "reward_amount": 75000, "sort_order": 50, }, { "name": "cases_25000", "title": "Повелитель кейсов", "description": "Открой 25 000 кейсов", "icon": "👑", "category": "cases", "requirement_type": "open_cases", "requirement_value": 25000, "reward_amount": 200000, "sort_order": 51, }, { "name": "slots_25", "title": "Слот-любитель", "description": "Сыграй 25 раундов в слот-машине", "icon": "🎰", "category": "cases", "requirement_type": "slot_plays", "requirement_value": 25, "reward_amount": 1000, "sort_order": 52, }, { "name": "slots_100", "title": "Слот-зависимый", "description": "Сыграй 100 раундов в слот-машине", "icon": "🎰", "category": "cases", "requirement_type": "slot_plays", "requirement_value": 100, "reward_amount": 5000, "sort_order": 53, }, { "name": "slots_500", "title": "Слот-король", "description": "Сыграй 500 раундов в слот-машине", "icon": "👑", "category": "cases", "requirement_type": "slot_plays", "requirement_value": 500, "reward_amount": 25000, "sort_order": 54, }, { "name": "slots_jackpot_5", "title": "Джекпот-хантер", "description": "Собери 3 одинаковых символа в слотах 5 раз", "icon": "🎯", "category": "cases", "requirement_type": "slot_match_count", "requirement_value": 5, "reward_amount": 10000, "sort_order": 55, }, { "name": "slots_jackpot_25", "title": "Джекпот-легенда", "description": "Собери 3 одинаковых символа в слотах 25 раз", "icon": "🎯", "category": "cases", "requirement_type": "slot_match_count", "requirement_value": 25, "reward_amount": 50000, "sort_order": 56, }, { "name": "rare_item_10", "title": "Охотник за редким II", "description": "Выбей 10 предметов редкости Covert+", "icon": "🔴", "category": "cases", "requirement_type": "rare_item_count", "requirement_value": 10, "reward_amount": 25000, "sort_order": 57, }, { "name": "rare_item_50", "title": "Охотник за редким III", "description": "Выбей 50 предметов редкости Covert+", "icon": "🔴", "category": "cases", "requirement_type": "rare_item_count", "requirement_value": 50, "reward_amount": 100000, "sort_order": 58, }, { "name": "knife_5", "title": "Ножевод II", "description": "Выбей 5 ножей или перчаток", "icon": "🔪", "category": "cases", "requirement_type": "knife_count", "requirement_value": 5, "reward_amount": 50000, "sort_order": 59, }, # ═══════════════════════════════════════════════════════════════════════════ # 🎯 ОБЩИЕ (40 новых) # ═══════════════════════════════════════════════════════════════════════════ { "name": "spent_500k", "title": "Транжира II", "description": "Потрать 500 000 ₽ на кейсы", "icon": "💰", "category": "general", "requirement_type": "total_spent", "requirement_value": 500000, "reward_amount": 5000, "sort_order": 60, }, { "name": "spent_1m", "title": "Транжира III", "description": "Потрать 1 000 000 ₽ на кейсы", "icon": "💰", "category": "general", "requirement_type": "total_spent", "requirement_value": 1000000, "reward_amount": 15000, "sort_order": 61, }, { "name": "spent_10m", "title": "Транжира IV", "description": "Потрать 10 000 000 ₽ на кейсы", "icon": "💵", "category": "general", "requirement_type": "total_spent", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 62, }, { "name": "spent_100m", "title": "Транжира V", "description": "Потрать 100 000 000 ₽ на кейсы", "icon": "💵", "category": "general", "requirement_type": "total_spent", "requirement_value": 100000000, "reward_amount": 200000, "sort_order": 63, }, { "name": "spent_500m", "title": "Транжира VI", "description": "Потрать 500 000 000 ₽ на кейсы", "icon": "💎", "category": "general", "requirement_type": "total_spent", "requirement_value": 500000000, "reward_amount": 500000, "sort_order": 64, }, { "name": "spent_1b", "title": "Транжира VII", "description": "Потрать 1 000 000 000 ₽ на кейсы", "icon": "💎", "category": "general", "requirement_type": "total_spent", "requirement_value": 1000000000, "reward_amount": 1000000, "sort_order": 65, }, { "name": "inventory_100", "title": "Собиратель II", "description": "Собери 100 предметов в инвентаре", "icon": "🎒", "category": "general", "requirement_type": "inventory_count", "requirement_value": 100, "reward_amount": 1000, "sort_order": 66, }, { "name": "inventory_250", "title": "Собиратель III", "description": "Собери 250 предметов в инвентаре", "icon": "🎒", "category": "general", "requirement_type": "inventory_count", "requirement_value": 250, "reward_amount": 2500, "sort_order": 67, }, { "name": "inventory_500", "title": "Собиратель IV", "description": "Собери 500 предметов в инвентаре", "icon": "🎒", "category": "general", "requirement_type": "inventory_count", "requirement_value": 500, "reward_amount": 5000, "sort_order": 68, }, { "name": "inventory_1000", "title": "Коллекционер", "description": "Собери 1 000 предметов в инвентаре", "icon": "🏛️", "category": "general", "requirement_type": "inventory_count", "requirement_value": 1000, "reward_amount": 10000, "sort_order": 69, }, { "name": "inventory_2500", "title": "Музей", "description": "Собери 2 500 предметов в инвентаре", "icon": "🏛️", "category": "general", "requirement_type": "inventory_count", "requirement_value": 2500, "reward_amount": 25000, "sort_order": 70, }, { "name": "balance_1m", "title": "Миллионер", "description": "Накопи 1 000 000 ₽ на балансе", "icon": "🤑", "category": "general", "requirement_type": "balance_milestone", "requirement_value": 1000000, "reward_amount": 10000, "sort_order": 71, }, { "name": "balance_10m", "title": "Мультимиллионер", "description": "Накопи 10 000 000 ₽ на балансе", "icon": "🤑", "category": "general", "requirement_type": "balance_milestone", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 72, }, { "name": "balance_100m", "title": "Богач", "description": "Накопи 100 000 000 ₽ на балансе", "icon": "💎", "category": "general", "requirement_type": "balance_milestone", "requirement_value": 100000000, "reward_amount": 250000, "sort_order": 73, }, { "name": "balance_1b", "title": "Миллиардер", "description": "Накопи 1 000 000 000 ₽ на балансе", "icon": "💎", "category": "general", "requirement_type": "balance_milestone", "requirement_value": 1000000000, "reward_amount": 1000000, "sort_order": 74, }, { "name": "contracts_10", "title": "Обменщик II", "description": "Выполни 10 контрактов обмена", "icon": "🔄", "category": "general", "requirement_type": "contracts", "requirement_value": 10, "reward_amount": 500, "sort_order": 75, }, { "name": "contracts_25", "title": "Обменщик III", "description": "Выполни 25 контрактов обмена", "icon": "🔄", "category": "general", "requirement_type": "contracts", "requirement_value": 25, "reward_amount": 1500, "sort_order": 76, }, { "name": "contracts_100", "title": "Обменщик IV", "description": "Выполни 100 контрактов обмена", "icon": "🔄", "category": "general", "requirement_type": "contracts", "requirement_value": 100, "reward_amount": 5000, "sort_order": 77, }, { "name": "contracts_250", "title": "Мастер обмена", "description": "Выполни 250 контрактов обмена", "icon": "🔄", "category": "general", "requirement_type": "contracts", "requirement_value": 250, "reward_amount": 15000, "sort_order": 78, }, { "name": "crash_10", "title": "Крэш-любитель", "description": "Сыграй 10 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 10, "reward_amount": 500, "sort_order": 79, }, { "name": "crash_25", "title": "Крэш-игрок", "description": "Сыграй 25 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 25, "reward_amount": 1000, "sort_order": 80, }, { "name": "crash_100", "title": "Крэш-фанат", "description": "Сыграй 100 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 100, "reward_amount": 5000, "sort_order": 81, }, { "name": "crash_500", "title": "Крэш-маньяк", "description": "Сыграй 500 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 500, "reward_amount": 15000, "sort_order": 82, }, { "name": "crash_1000", "title": "Крэш-легенда", "description": "Сыграй 1000 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 1000, "reward_amount": 50000, "sort_order": 83, }, { "name": "crash_cashout_10x", "title": "Смельчак II", "description": "Выведи деньги на множителе x10 или выше в Crash", "icon": "🚀", "category": "crash", "requirement_type": "crash_cashout_milestone", "requirement_value": 10, "reward_amount": 5000, "sort_order": 84, }, { "name": "crash_cashout_25x", "title": "Смельчак III", "description": "Выведи деньги на множителе x25 или выше в Crash", "icon": "🚀", "category": "crash", "requirement_type": "crash_cashout_milestone", "requirement_value": 25, "reward_amount": 15000, "sort_order": 85, }, { "name": "crash_cashout_50x", "title": "Смельчак IV", "description": "Выведи деньги на множителе x50 или выше в Crash", "icon": "🚀", "category": "crash", "requirement_type": "crash_cashout_milestone", "requirement_value": 50, "reward_amount": 50000, "sort_order": 86, }, { "name": "crash_cashout_100x", "title": "Смельчак V", "description": "Выведи деньги на множителе x100 или выше в Crash", "icon": "🚀", "category": "crash", "requirement_type": "crash_cashout_milestone", "requirement_value": 100, "reward_amount": 200000, "sort_order": 87, }, { "name": "items_unboxed_100", "title": "Распаковщик", "description": "Открой 100 предметов из кейсов", "icon": "🎁", "category": "general", "requirement_type": "items_unboxed", "requirement_value": 100, "reward_amount": 1000, "sort_order": 88, }, { "name": "items_unboxed_1000", "title": "Распаковщик II", "description": "Открой 1 000 предметов из кейсов", "icon": "🎁", "category": "general", "requirement_type": "items_unboxed", "requirement_value": 1000, "reward_amount": 5000, "sort_order": 89, }, { "name": "items_unboxed_10000", "title": "Распаковщик III", "description": "Открой 10 000 предметов из кейсов", "icon": "🎁", "category": "general", "requirement_type": "items_unboxed", "requirement_value": 10000, "reward_amount": 25000, "sort_order": 90, }, { "name": "total_profit_100k", "title": "Первая прибыль", "description": "Получи 100 000 ₽ чистой прибыли", "icon": "📈", "category": "general", "requirement_type": "total_profit", "requirement_value": 100000, "reward_amount": 2000, "sort_order": 91, }, { "name": "total_profit_1m", "title": "Прибыльный игрок", "description": "Получи 1 000 000 ₽ чистой прибыли", "icon": "📈", "category": "general", "requirement_type": "total_profit", "requirement_value": 1000000, "reward_amount": 10000, "sort_order": 92, }, { "name": "total_profit_10m", "title": "Бизнесмен", "description": "Получи 10 000 000 ₽ чистой прибыли", "icon": "📊", "category": "general", "requirement_type": "total_profit", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 93, }, { "name": "total_profit_100m", "title": "Магнат", "description": "Получи 100 000 000 ₽ чистой прибыли", "icon": "📊", "category": "general", "requirement_type": "total_profit", "requirement_value": 100000000, "reward_amount": 250000, "sort_order": 94, }, { "name": "achievements_10", "title": "Достигатор", "description": "Открой 10 достижений", "icon": "🏆", "category": "general", "requirement_type": "achievements_count", "requirement_value": 10, "reward_amount": 2000, "sort_order": 95, }, { "name": "achievements_25", "title": "Достигатор II", "description": "Открой 25 достижений", "icon": "🏆", "category": "general", "requirement_type": "achievements_count", "requirement_value": 25, "reward_amount": 5000, "sort_order": 96, }, { "name": "achievements_50", "title": "Достигатор III", "description": "Открой 50 достижений", "icon": "🏆", "category": "general", "requirement_type": "achievements_count", "requirement_value": 50, "reward_amount": 15000, "sort_order": 97, }, { "name": "achievements_100", "title": "Достигатор IV", "description": "Открой 100 достижений", "icon": "🏆", "category": "general", "requirement_type": "achievements_count", "requirement_value": 100, "reward_amount": 50000, "sort_order": 98, }, { "name": "crash_5000", "title": "Крэш-бог", "description": "Сыграй 5000 раундов в Crash", "icon": "💥", "category": "crash", "requirement_type": "crash_plays", "requirement_value": 5000, "reward_amount": 200000, "sort_order": 99, }, # ═══════════════════════════════════════════════════════════════════════════ # 🔞 ДИЛДО / ХАРД (20 новых) # ═══════════════════════════════════════════════════════════════════════════ { "name": "dildo_owned", "title": "Интересная находка", "description": "Получи свой первый Dildo", "icon": "🔞", "category": "general", "requirement_type": "dildo_count", "requirement_value": 1, "reward_amount": 5000, "sort_order": 100, }, { "name": "dildo_5", "title": "Коллекционер Dildo", "description": "Собери 5 Dildo в инвентаре", "icon": "🔞", "category": "general", "requirement_type": "dildo_count", "requirement_value": 5, "reward_amount": 15000, "sort_order": 101, }, { "name": "dildo_10", "title": "Знаток Dildo", "description": "Собери 10 Dildo в инвентаре", "icon": "🔞", "category": "general", "requirement_type": "dildo_count", "requirement_value": 10, "reward_amount": 30000, "sort_order": 102, }, { "name": "dildo_25", "title": "Dildo-маньяк", "description": "Собери 25 Dildo в инвентаре", "icon": "🔞", "category": "general", "requirement_type": "dildo_count", "requirement_value": 25, "reward_amount": 75000, "sort_order": 103, }, { "name": "dildo_50", "title": "Dildo-барон", "description": "Собери 50 Dildo в инвентаре", "icon": "🔞", "category": "general", "requirement_type": "dildo_count", "requirement_value": 50, "reward_amount": 200000, "sort_order": 104, }, { "name": "dildo_100", "title": "Dildo-император", "description": "Собери 100 Dildo в инвентаре", "icon": "👑", "category": "general", "requirement_type": "dildo_count", "requirement_value": 100, "reward_amount": 500000, "sort_order": 105, }, { "name": "item_worth_1m", "title": "Ценная вещица", "description": "Владей предметом дороже 1 000 000 ₽", "icon": "💎", "category": "general", "requirement_type": "best_item_price", "requirement_value": 1000000, "reward_amount": 10000, "sort_order": 106, }, { "name": "item_worth_10m", "title": "Дорогая игрушка", "description": "Владей предметом дороже 10 000 000 ₽", "icon": "💎", "category": "general", "requirement_type": "best_item_price", "requirement_value": 10000000, "reward_amount": 50000, "sort_order": 107, }, { "name": "item_worth_100m", "title": "Бесценно", "description": "Владей предметом дороже 100 000 000 ₽", "icon": "💎", "category": "general", "requirement_type": "best_item_price", "requirement_value": 100000000, "reward_amount": 250000, "sort_order": 108, }, { "name": "item_worth_1b", "title": "Священный Грааль", "description": "Владей предметом дороже 1 000 000 000 ₽", "icon": "👑", "category": "general", "requirement_type": "best_item_price", "requirement_value": 1000000000, "reward_amount": 2000000, "sort_order": 109, }, { "name": "inventory_value_10m", "title": "Инвентарь миллионера", "description": "Общая стоимость инвентаря > 10 000 000 ₽", "icon": "💰", "category": "general", "requirement_type": "inventory_total_value", "requirement_value": 10000000, "reward_amount": 25000, "sort_order": 110, }, { "name": "inventory_value_100m", "title": "Инвентарь магната", "description": "Общая стоимость инвентаря > 100 000 000 ₽", "icon": "💰", "category": "general", "requirement_type": "inventory_total_value", "requirement_value": 100000000, "reward_amount": 100000, "sort_order": 111, }, { "name": "inventory_value_1b", "title": "Инвентарь миллиардера", "description": "Общая стоимость инвентаря > 1 000 000 000 ₽", "icon": "👑", "category": "general", "requirement_type": "inventory_total_value", "requirement_value": 1000000000, "reward_amount": 500000, "sort_order": 112, }, { "name": "dildo_float_001", "title": "Идеальный Dildo", "description": "Получи Dildo с float < 0.01", "icon": "✨", "category": "general", "requirement_type": "dildo_best_float", "requirement_value": 1, "reward_amount": 50000, "sort_order": 113, }, { "name": "dildo_float_0005", "title": "Безупречный Dildo", "description": "Получи Dildo с float < 0.005", "icon": "✨", "category": "general", "requirement_type": "dildo_best_float", "requirement_value": 2, "reward_amount": 250000, "sort_order": 114, }, { "name": "dildo_from_contract", "title": "Контракт на Dildo", "description": "Получи Dildo из контракта обмена", "icon": "🔄", "category": "general", "requirement_type": "dildo_contract_count", "requirement_value": 1, "reward_amount": 20000, "sort_order": 115, }, { "name": "dildo_all_rarities", "title": "Гамма Dildo", "description": "Собери Dildo каждой редкости", "icon": "🌈", "category": "general", "requirement_type": "dildo_all_rarities", "requirement_value": 1, "reward_amount": 100000, "sort_order": 116, }, { "name": "ultra_rare_float", "title": "Совершенство", "description": "Получи любой предмет с float 0.000X", "icon": "💎", "category": "general", "requirement_type": "ultra_rare_float", "requirement_value": 0.001, "reward_amount": 500000, "sort_order": 117, }, { "name": "dildo_full_set", "title": "Полная коллекция", "description": "Собери 25 уникальных Dildo разных узоров", "icon": "🏆", "category": "general", "requirement_type": "dildo_unique_count", "requirement_value": 25, "reward_amount": 1000000, "sort_order": 118, }, { "name": "dildo_worth_100m", "title": "Dildo-магнат", "description": "Общая стоимость всех Dildo в инвентаре > 100 000 000 ₽", "icon": "💰", "category": "general", "requirement_type": "dildo_total_value", "requirement_value": 100000000, "reward_amount": 500000, "sort_order": 119, }, # ═══════════════════════════════════════════════════════════════════════════ # ❓ СЕКРЕТНЫЕ (15 новых) # ═══════════════════════════════════════════════════════════════════════════ { "name": "secret_midnight", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_midnight_case", "requirement_value": 1, "reward_amount": 5000, "sort_order": 200, "hidden": True, }, { "name": "secret_exact_balance", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_exact_balance", "requirement_value": 1337, "reward_amount": 5000, "sort_order": 201, "hidden": True, }, { "name": "secret_99_fail", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_99_fail", "requirement_value": 1, "reward_amount": 10000, "sort_order": 202, "hidden": True, }, { "name": "secret_negative_balance", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_negative_balance", "requirement_value": 1, "reward_amount": 5000, "sort_order": 203, "hidden": True, }, { "name": "secret_1000_exact", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_exact_cases", "requirement_value": 1000, "reward_amount": 10000, "sort_order": 204, "hidden": True, }, { "name": "secret_empty_inventory", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_empty_inv", "requirement_value": 1, "reward_amount": 5000, "sort_order": 205, "hidden": True, }, { "name": "secret_spent_exact", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_spent_exact", "requirement_value": 666666, "reward_amount": 15000, "sort_order": 206, "hidden": True, }, { "name": "secret_low_prob_win", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_low_prob_win", "requirement_value": 1, "reward_amount": 10000, "sort_order": 207, "hidden": True, }, { "name": "secret_dildo_specific", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_dildo_specific", "requirement_value": 1, "reward_amount": 5000, "sort_order": 208, "hidden": True, }, { "name": "secret_crash_1x", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_crash_1x", "requirement_value": 1, "reward_amount": 5000, "sort_order": 209, "hidden": True, }, { "name": "secret_balance_777", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_balance_777", "requirement_value": 777, "reward_amount": 7777, "sort_order": 210, "hidden": True, }, { "name": "secret_one_item_inv", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_one_item_inv", "requirement_value": 1, "reward_amount": 5000, "sort_order": 211, "hidden": True, }, { "name": "secret_contract_dildo", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_contract_dildo", "requirement_value": 1, "reward_amount": 10000, "sort_order": 212, "hidden": True, }, { "name": "secret_slot_zero", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_slot_zero", "requirement_value": 1, "reward_amount": 15000, "sort_order": 213, "hidden": True, }, { "name": "secret_exact_float", "title": "???", "description": "Хммм... похоже что я не знаю как его открыть", "icon": "❓", "category": "general", "requirement_type": "secret_exact_float", "requirement_value": 1, "reward_amount": 50000, "sort_order": 214, "hidden": True, }, # ═══════════════════════════════════════════════════════════════════════════ # 🎨 КОЛЛЕКЦИИ (25 новых) # ═══════════════════════════════════════════════════════════════════════════ { "name": "coll_first", "title": "Первая коллекция", "description": "Получи предмет, входящий в коллекцию", "icon": "🎨", "category": "general", "requirement_type": "collection_count", "requirement_value": 1, "reward_amount": 500, "sort_order": 120, }, { "name": "coll_5", "title": "Начинающий коллекционер", "description": "Собери предметы из 5 разных коллекций", "icon": "🎨", "category": "general", "requirement_type": "collection_count", "requirement_value": 5, "reward_amount": 1000, "sort_order": 121, }, { "name": "coll_10", "title": "Коллекционер", "description": "Собери предметы из 10 разных коллекций", "icon": "🎨", "category": "general", "requirement_type": "collection_count", "requirement_value": 10, "reward_amount": 2500, "sort_order": 122, }, { "name": "coll_25", "title": "Знаток коллекций", "description": "Собери предметы из 25 разных коллекций", "icon": "🏛️", "category": "general", "requirement_type": "collection_count", "requirement_value": 25, "reward_amount": 5000, "sort_order": 123, }, { "name": "coll_50", "title": "Мастер коллекций", "description": "Собери предметы из 50 разных коллекций", "icon": "🏛️", "category": "general", "requirement_type": "collection_count", "requirement_value": 50, "reward_amount": 10000, "sort_order": 124, }, { "name": "coll_75", "title": "Хранитель коллекций", "description": "Собери предметы из 75 разных коллекций", "icon": "🏛️", "category": "general", "requirement_type": "collection_count", "requirement_value": 75, "reward_amount": 25000, "sort_order": 125, }, { "name": "coll_100", "title": "Архивариус", "description": "Собери предметы из 100 разных коллекций", "icon": "📚", "category": "general", "requirement_type": "collection_count", "requirement_value": 100, "reward_amount": 50000, "sort_order": 126, }, { "name": "coll_all_base", "title": "Полная коллекция", "description": "Собери все стандартные коллекции (без кастомных)", "icon": "👑", "category": "general", "requirement_type": "collection_all_base", "requirement_value": 1, "reward_amount": 200000, "sort_order": 127, }, { "name": "coll_all", "title": "Абсолютная коллекция", "description": "Собери все коллекции, включая кастомные", "icon": "👑", "category": "general", "requirement_type": "collection_all", "requirement_value": 1, "reward_amount": 1000000, "sort_order": 128, }, { "name": "coll_items_10", "title": "Собиратель", "description": "Собери 10 уникальных предметов из коллекций", "icon": "🎨", "category": "general", "requirement_type": "collection_items", "requirement_value": 10, "reward_amount": 500, "sort_order": 129, }, { "name": "coll_items_50", "title": "Коллекция растёт", "description": "Собери 50 уникальных предметов из коллекций", "icon": "🎨", "category": "general", "requirement_type": "collection_items", "requirement_value": 50, "reward_amount": 2000, "sort_order": 130, }, { "name": "coll_items_100", "title": "Галерея", "description": "Собери 100 уникальных предметов из коллекций", "icon": "🖼️", "category": "general", "requirement_type": "collection_items", "requirement_value": 100, "reward_amount": 5000, "sort_order": 131, }, { "name": "coll_items_250", "title": "Музей", "description": "Собери 250 уникальных предметов из коллекций", "icon": "🖼️", "category": "general", "requirement_type": "collection_items", "requirement_value": 250, "reward_amount": 15000, "sort_order": 132, }, { "name": "coll_items_500", "title": "Эрмитаж", "description": "Собери 500 уникальных предметов из коллекций", "icon": "🏛️", "category": "general", "requirement_type": "collection_items", "requirement_value": 500, "reward_amount": 50000, "sort_order": 133, }, { "name": "coll_items_1000", "title": "Лувр", "description": "Собери 1000 уникальных предметов из коллекций", "icon": "🏛️", "category": "general", "requirement_type": "collection_items", "requirement_value": 1000, "reward_amount": 150000, "sort_order": 134, }, { "name": "coll_complete_1", "title": "Первая полная", "description": "Собери все скины одной коллекции (полный сет)", "icon": "🎯", "category": "general", "requirement_type": "collection_complete_count", "requirement_value": 1, "reward_amount": 5000, "sort_order": 135, }, { "name": "coll_complete_5", "title": "Набор коллекций", "description": "Собери полностью 5 разных коллекций", "icon": "🎯", "category": "general", "requirement_type": "collection_complete_count", "requirement_value": 5, "reward_amount": 20000, "sort_order": 136, }, { "name": "coll_complete_10", "title": "Коллекционер-перфекционист", "description": "Собери полностью 10 разных коллекций", "icon": "🎯", "category": "general", "requirement_type": "collection_complete_count", "requirement_value": 10, "reward_amount": 50000, "sort_order": 137, }, { "name": "coll_complete_25", "title": "Перфекционист", "description": "Собери полностью 25 разных коллекций", "icon": "🏆", "category": "general", "requirement_type": "collection_complete_count", "requirement_value": 25, "reward_amount": 150000, "sort_order": 138, }, { "name": "coll_complete_50", "title": "Абсолютный перфекционист", "description": "Собери полностью 50 разных коллекций", "icon": "🏆", "category": "general", "requirement_type": "collection_complete_count", "requirement_value": 50, "reward_amount": 500000, "sort_order": 139, }, { "name": "coll_cheap_collector", "title": "От простого", "description": "Собери все Consumer Grade скины любой коллекции", "icon": "⬜", "category": "general", "requirement_type": "coll_rarity_complete", "requirement_value": 1, "reward_amount": 1000, "sort_order": 140, }, { "name": "coll_mid_collector", "title": "Середина", "description": "Собери все Mil-Spec скины любой коллекции", "icon": "🟦", "category": "general", "requirement_type": "coll_rarity_complete", "requirement_value": 2, "reward_amount": 3000, "sort_order": 141, }, { "name": "coll_rare_collector", "title": "Элита", "description": "Собери все Restricted+ скины любой коллекции", "icon": "🟣", "category": "general", "requirement_type": "coll_rarity_complete", "requirement_value": 3, "reward_amount": 10000, "sort_order": 142, }, { "name": "coll_covert_collector", "title": "Крем", "description": "Собери все Covert скины любой коллекции", "icon": "🔴", "category": "general", "requirement_type": "coll_rarity_complete", "requirement_value": 4, "reward_amount": 30000, "sort_order": 143, }, { "name": "coll_full_case", "title": "Кейс закрыт", "description": "Собери все предметы из одного кейса", "icon": "📦", "category": "general", "requirement_type": "coll_case_complete", "requirement_value": 1, "reward_amount": 5000, "sort_order": 144, }, ] # ─── Функции проверки достижений ────────────────────────────────────────── def check_collection_achievements(db: Session, user_id: int): """Проверяет достижения, связанные с коллекциями""" from backend import ALL_ITEMS, COLLECTIONS_INDEX, extract_base_name from database import InventoryItem results = [] # Все предметы пользователя user_inv = db.query(InventoryItem.item_id).filter( InventoryItem.user_id == user_id ).all() user_ids = {iid for (iid,) in user_inv} # Собираем все коллекции, в которых есть предметы пользователя # и уникальные базовые имена предметов из коллекций user_collections = set() user_coll_base_names = {} # collection_name → set of base names owned all_coll_base_names = {} # collection_name → set of all base names in collection for coll_name, item_ids in COLLECTIONS_INDEX.items(): base_set = set() for cid in item_ids: item = ALL_ITEMS[cid] if cid < len(ALL_ITEMS) else None if item: base = extract_base_name(item.get("market_hash_name", "")) if base: base_set.add(base) if base_set: all_coll_base_names[coll_name] = base_set # Check if user owns any item from this collection owned_bases = set() for cid in item_ids: if cid in user_ids: item = ALL_ITEMS[cid] if cid < len(ALL_ITEMS) else None if item: base = extract_base_name(item.get("market_hash_name", "")) if base: owned_bases.add(base) if owned_bases: user_collections.add(coll_name) user_coll_base_names[coll_name] = owned_bases # collection_count: unique collections user has items from coll_count = len(user_collections) for n in ["coll_first", "coll_5", "coll_10", "coll_25", "coll_50", "coll_75", "coll_100"]: r = check_achievement(db, user_id, n, coll_count) if r: results.append(r) # collection_items: unique base items from collections owned all_owned_bases = set() for bases in user_coll_base_names.values(): all_owned_bases.update(bases) total_unique_items = 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 = check_achievement(db, user_id, n, total_unique_items) if r: results.append(r) # collection_complete_count: collections where user owns ALL base items complete_count = 0 for coll_name, all_bases in all_coll_base_names.items(): owned_bases = user_coll_base_names.get(coll_name, set()) if all_bases.issubset(owned_bases): complete_count += 1 for n in ["coll_complete_1", "coll_complete_5", "coll_complete_10", "coll_complete_25", "coll_complete_50"]: r = check_achievement(db, user_id, n, complete_count) if r: results.append(r) # coll_all_base: all NON-CUSTOM collections complete non_custom_collections = {} for coll_name, item_ids in COLLECTIONS_INDEX.items(): # Check if any item in this collection is custom is_custom = False for cid in item_ids: item = ALL_ITEMS[cid] if cid < len(ALL_ITEMS) else None if item and item.get("price_source") == "custom": is_custom = True break if not is_custom and coll_name in all_coll_base_names: non_custom_collections[coll_name] = all_coll_base_names[coll_name] all_base_complete = True for coll_name, all_bases in non_custom_collections.items(): owned_bases = user_coll_base_names.get(coll_name, set()) if not all_bases.issubset(owned_bases): all_base_complete = False break if all_base_complete and non_custom_collections: r = check_achievement(db, user_id, "coll_all_base", 1) if r: results.append(r) # coll_all: ALL collections complete (including custom) all_complete = True for coll_name, all_bases in all_coll_base_names.items(): owned_bases = user_coll_base_names.get(coll_name, set()) if not all_bases.issubset(owned_bases): all_complete = False break if all_complete and all_coll_base_names: r = check_achievement(db, user_id, "coll_all", 1) if r: results.append(r) # coll_rarity_complete: complete all items of specific rarity tiers in any collection from backend import RARITY_ORDER rarity_collectors = [ ("coll_cheap_collector", {0}), # Consumer Grade ("coll_mid_collector", {2}), # Mil-Spec / Mil-Spec Grade ("coll_rare_collector", {3, 4, 5, 6, 7, 8}), # Restricted+ ("coll_covert_collector", {5}), # Covert ] for ach_name, target_levels in rarity_collectors: found = False for coll_name in all_coll_base_names: # Collect all items at target rarity levels for this collection all_target = set() owned_target = set() for cid in COLLECTIONS_INDEX.get(coll_name, []): item = ALL_ITEMS[cid] if cid < len(ALL_ITEMS) else None if not item: continue 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 = check_achievement(db, user_id, ach_name, 1) if r: results.append(r) # coll_case_complete: complete all items from a case for coll_name, all_bases in all_coll_base_names.items(): if not coll_name.startswith("Case:"): continue owned_bases = user_coll_base_names.get(coll_name, set()) if all_bases.issubset(owned_bases): r = check_achievement(db, user_id, "coll_full_case", 1) if r: results.append(r) break # Also check non-case named collections for coll_name, all_bases in all_coll_base_names.items(): if coll_name.startswith("Case:"): continue owned_bases = user_coll_base_names.get(coll_name, set()) if all_bases.issubset(owned_bases): # This also counts for coll_full_case if user wants case completion pass # Already counted in collection_complete_count above return results def seed_achievements(db: Session): existing = db.query(Achievement).count() if existing > 0: # Добавляем только новые (отсутствующие в БД) existing_names = {a.name for a in db.query(Achievement.name).all()} added = 0 for defn in ACHIEVEMENT_DEFS: if defn["name"] not in existing_names: ach = Achievement(**defn) db.add(ach) added += 1 if added: db.commit() print(f"[Achievements] Добавлено {added} новых достижений") return for defn in ACHIEVEMENT_DEFS: ach = Achievement(**defn) db.add(ach) db.commit() def get_or_create_user_achievement(db: Session, user_id: int, achievement_id: int) -> UserAchievement: ua = db.query(UserAchievement).filter( UserAchievement.user_id == user_id, UserAchievement.achievement_id == achievement_id ).first() if not ua: ua = UserAchievement(user_id=user_id, achievement_id=achievement_id) db.add(ua) db.commit() db.refresh(ua) return ua def check_achievement(db: Session, user_id: int, achievement_name: str, current_value: float) -> Optional[dict]: """Проверяет и разблокирует достижение. Возвращает dict с инфой если разблокировано.""" ach = db.query(Achievement).filter(Achievement.name == achievement_name).first() if not ach: return None ua = 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() from database import User user = db.query(User).filter(User.id == user_id).first() if user: user.balance += ach.reward_amount db.commit() activity = ActivityFeed( user_id=user_id, username=user.username if user else "unknown", 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) db.commit() return { "name": ach.name, "title": ach.title, "icon": ach.icon, "reward": ach.reward_amount } db.commit() return None # ─── Существующие проверки (расширенные) ─────────────────────────────────── def check_open_cases_achievements(db: Session, user_id: int): from database import CaseOpening count = db.query(CaseOpening).filter(CaseOpening.user_id == user_id).count() names = ["first_case", "case_collector_10", "case_collector_100", "case_collector_1000", "cases_2500", "cases_5000", "cases_10000", "cases_25000"] results = [] for n in names: r = check_achievement(db, user_id, n, count) if r: results.append(r) return results def check_contracts_achievements(db: Session, user_id: int): from database import Contract count = db.query(Contract).filter(Contract.user_id == user_id).count() names = ["first_contract", "contract_master", "contracts_10", "contracts_25", "contracts_100", "contracts_250"] results = [] for n in names: r = check_achievement(db, user_id, n, count) if r: results.append(r) return results def check_upgrade_achievements(db: Session, user_id: int): from database import Upgrade attempts = db.query(Upgrade).filter(Upgrade.user_id == user_id).count() wins_count = db.query(Upgrade).filter(Upgrade.user_id == user_id, Upgrade.success == True).count() results = [] # upgrade_attempts based for n in ["first_upgrade", "upgrade_5", "upgrade_25", "upgrade_50", "upgrade_100", "upgrade_250", "upgrade_500", "upgrade_1000", "upgrade_2000"]: r = check_achievement(db, user_id, n, attempts) if r: results.append(r) # upgrade_wins based for n in ["upgrade_winner", "upgrade_wins_5", "upgrade_wins_25", "upgrade_wins_50", "upgrade_wins_100", "upgrade_wins_250"]: r = check_achievement(db, user_id, n, wins_count) if r: results.append(r) # Upgrade total spent value (look up price from ALL_ITEMS for failed upgrades) from backend import ALL_ITEMS failed_upgrades = db.query(Upgrade.input_item_id).filter( Upgrade.user_id == user_id, Upgrade.success == False ).all() total_spent_val = 0 for (iid,) in failed_upgrades: if iid < len(ALL_ITEMS): total_spent_val += ALL_ITEMS[iid].get("price_rub", 0) for n in ["upgrade_spent_100k", "upgrade_spent_1m", "upgrade_spent_10m", "upgrade_spent_100m"]: r = check_achievement(db, user_id, n, total_spent_val) if r: results.append(r) return results def check_upgrade_risk_achievements(db: Session, user_id: int, item_price: float): """Вызывается при каждой попытке апгрейда — проверяет рискованные ставки""" results = [] for n in ["upgrade_high_risk_50k", "upgrade_high_risk_500k", "upgrade_high_risk_10m"]: r = check_achievement(db, user_id, n, item_price) if r: results.append(r) return results def check_upgrade_result_achievements(db: Session, user_id: int, success: bool, probability: float, item_price: float): """Вызывается после завершения апгрейда — проверяет серии, чудеса и т.д.""" from database import Upgrade results = [] # Consecutive wins/losses all_upgrades = db.query(Upgrade.success).filter( Upgrade.user_id == user_id ).order_by(Upgrade.id.desc()).all() streak = 0 streak_type = None # True = wins, False = losses for (s,) in all_upgrades: if streak_type is None: streak_type = s streak = 1 elif s == streak_type: streak += 1 else: break if streak_type == True: for n in ["upgrade_consecutive_wins_3", "upgrade_consecutive_wins_5", "upgrade_consecutive_wins_10"]: r = check_achievement(db, user_id, n, streak) if r: results.append(r) elif streak_type == False: for n in ["upgrade_consecutive_losses_5", "upgrade_consecutive_losses_10"]: r = check_achievement(db, user_id, n, streak) if r: results.append(r) # Low value win if success and item_price <= 1000: r = check_achievement(db, user_id, "upgrade_from_nothing", 1000) if r: results.append(r) # Low probability win if success and probability < 10: low_prob_wins = db.query(Upgrade).filter( Upgrade.user_id == user_id, Upgrade.success == True, Upgrade.probability < 10 ).count() for n in ["upgrade_miracle", "upgrade_miracle_5"]: r = check_achievement(db, user_id, n, low_prob_wins) if r: results.append(r) # Secret: 99% fail if not success and probability >= 99: r = check_achievement(db, user_id, "secret_99_fail", 1) if r: results.append(r) # Secret: low prob win if success and probability < 10: r = check_achievement(db, user_id, "secret_low_prob_win", 1) if r: results.append(r) return results def check_rare_item_achievement(db: Session, user_id: int, rarity: str): results = [] if rarity in ("Covert", "Extraordinary", "Rare Special Item"): r = check_achievement(db, user_id, "rare_finder", 1) if r: results.append(r) return results def check_knife_achievement(db: Session, user_id: int, item_data: dict): from backend import is_knife, is_gloves results = [] if is_knife(item_data) or is_gloves(item_data): r = check_achievement(db, user_id, "knife_collector", 1) if r: results.append(r) return results def check_spent_achievements(db: Session, user_id: int, total_spent: float): names = ["big_spender", "whale", "spent_500k", "spent_1m", "spent_10m", "spent_100m", "spent_500m", "spent_1b"] results = [] for n in names: r = check_achievement(db, user_id, n, int(total_spent)) if r: results.append(r) # Secret: exact spent 666666 if int(total_spent) == 666666: r = check_achievement(db, user_id, "secret_spent_exact", 1) if r: results.append(r) return results def check_balance_achievements(db: Session, user_id: int): from database import User user = db.query(User).filter(User.id == user_id).first() if not user: return [] results = [] # rich_100k legacy — DB has requirement_value=1, pass flag when condition met if user.balance >= 100000: r = 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 = check_achievement(db, user_id, n, user.balance) if r: results.append(r) # Secret: exact balance 1337 if user.balance == 1337: r = check_achievement(db, user_id, "secret_exact_balance", 1) if r: results.append(r) # Secret: balance % 1000 == 777 if user.balance > 0 and user.balance % 1000 == 777: r = check_achievement(db, user_id, "secret_balance_777", 777) if r: results.append(r) # Secret: negative balance if user.balance < 0: r = check_achievement(db, user_id, "secret_negative_balance", 1) if r: results.append(r) return results def check_slot_match_achievements(db: Session, user_id: int): results = [] r = check_achievement(db, user_id, "slot_winner", 1) if r: results.append(r) return results def check_slot_plays_achievements(db: Session, user_id: int): from database import CaseOpening slot_count = db.query(CaseOpening).filter( CaseOpening.user_id == user_id, CaseOpening.case_name.ilike("%slot%") ).count() names = ["slots_10", "slots_25", "slots_100", "slots_500"] results = [] for n in names: r = check_achievement(db, user_id, n, slot_count) if r: results.append(r) return results def check_slot_match_count_achievements(db: Session, user_id: int): """Проверяет количество совпадений в слотах (считаем по бонусным предметам)""" from database import InventoryItem matches = db.query(InventoryItem).filter( InventoryItem.user_id == user_id, InventoryItem.obtained_from.ilike("slot_bonus:%") ).count() results = [] for n in ["slots_jackpot_5", "slots_jackpot_25"]: r = check_achievement(db, user_id, n, matches) if r: results.append(r) return results def check_inventory_achievements(db: Session, user_id: int): from database import InventoryItem count = db.query(InventoryItem).filter(InventoryItem.user_id == user_id).count() names = ["inventory_50", "inventory_100", "inventory_250", "inventory_500", "inventory_1000", "inventory_2500"] results = [] for n in names: r = check_achievement(db, user_id, n, count) if r: results.append(r) # Secret: empty inventory if count == 0: r = check_achievement(db, user_id, "secret_empty_inv", 1) if r: results.append(r) # Secret: exactly 1 item if count == 1: r = check_achievement(db, user_id, "secret_one_item_inv", 1) if r: results.append(r) return results def check_dildo_achievements(db: Session, user_id: int): """Проверяет все Dildo-связанные достижения""" from database import InventoryItem from backend import ALL_ITEMS results = [] items = db.query(InventoryItem).filter(InventoryItem.user_id == user_id).all() dildo_items = [] for inv_item in items: if inv_item.item_id < len(ALL_ITEMS): item_data = ALL_ITEMS[inv_item.item_id] if item_data.get("weapon") == "Dildo": dildo_items.append((inv_item, item_data)) dildo_count = len(dildo_items) # Dildo count achievements for n in ["dildo_owned", "dildo_5", "dildo_10", "dildo_25", "dildo_50", "dildo_100"]: r = check_achievement(db, user_id, n, dildo_count) if r: results.append(r) # Best item price in inventory best_price = 0 for inv_item in items: if inv_item.item_id < len(ALL_ITEMS): p = ALL_ITEMS[inv_item.item_id].get("price_rub", 0) if p > best_price: best_price = p for n in ["item_worth_1m", "item_worth_10m", "item_worth_100m", "item_worth_1b"]: r = check_achievement(db, user_id, n, best_price) if r: results.append(r) # Total inventory value total_val = 0 for inv_item in items: if inv_item.item_id < len(ALL_ITEMS): total_val += ALL_ITEMS[inv_item.item_id].get("price_rub", 0) for n in ["inventory_value_10m", "inventory_value_100m", "inventory_value_1b"]: r = check_achievement(db, user_id, n, total_val) if r: results.append(r) # Dildo float achievements best_float = 1.0 for inv_item, item_data in dildo_items: f = inv_item.float_value or 1.0 if f < best_float: best_float = f if best_float < 0.01: r = check_achievement(db, user_id, "dildo_float_001", 1) if r: results.append(r) if best_float < 0.005: r = check_achievement(db, user_id, "dildo_float_0005", 2) if r: results.append(r) # Ultra rare float (any item) ultra_rare = 1.0 for inv_item in items: f = inv_item.float_value or 1.0 if f < ultra_rare: ultra_rare = f if ultra_rare < 0.001: r = check_achievement(db, user_id, "ultra_rare_float", 0.001) if r: results.append(r) # Secret: exact float 0.001 (or close) for inv_item in items: f = inv_item.float_value or 0 if abs(f - 0.001) < 0.0001: r = check_achievement(db, user_id, "secret_exact_float", 1) if r: results.append(r) break # Dildo all rarities dildo_rarities = set() for inv_item, item_data in dildo_items: dildo_rarities.add(item_data.get("rarity")) if len(dildo_rarities) >= 5: # Consumer, Industrial, Mil-Spec, Restricted, Classified, Covert r = check_achievement(db, user_id, "dildo_all_rarities", 1) if r: results.append(r) # Dildo unique count (by pattern/name) unique_patterns = set() for inv_item, item_data in dildo_items: name = item_data.get("market_hash_name", "") unique_patterns.add(name) r = check_achievement(db, user_id, "dildo_full_set", len(unique_patterns)) if r: results.append(r) # Dildo total value dildo_total = 0 for inv_item, item_data in dildo_items: dildo_total += item_data.get("price_rub", 0) r = check_achievement(db, user_id, "dildo_worth_100m", dildo_total) if r: results.append(r) # Secret: specific dildo name for inv_item, item_data in dildo_items: name = item_data.get("market_hash_name", "") if "Big Black" in name: r = check_achievement(db, user_id, "secret_dildo_specific", 1) if r: results.append(r) break return results def check_dildo_contract_achievement(db: Session, user_id: int): """Проверяет получение Dildo из контракта""" from database import Contract, InventoryItem results = [] # Check if any contract output is a Dildo from backend import ALL_ITEMS contracts = db.query(Contract).filter(Contract.user_id == user_id).all() for c in contracts: if c.output_item_id < len(ALL_ITEMS): item_data = ALL_ITEMS[c.output_item_id] if item_data.get("weapon") == "Dildo": r = check_achievement(db, user_id, "dildo_from_contract", 1) if r: results.append(r) break # Secret: contract gave dildo for c in contracts: if c.output_item_id < len(ALL_ITEMS): item_data = ALL_ITEMS[c.output_item_id] if item_data.get("weapon") == "Dildo": r = check_achievement(db, user_id, "secret_contract_dildo", 1) if r: results.append(r) break return results def check_crash_achievements(db: Session, user_id: int, crash_count: int): from database import ActivityFeed as CrActivity results = [] for n in ["crash_first", "crash_pro", "crash_10", "crash_25", "crash_100", "crash_500", "crash_1000", "crash_5000"]: r = check_achievement(db, user_id, n, crash_count) if r: results.append(r) return results def check_crash_cashout_achievements(db: Session, 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 = check_achievement(db, user_id, n, multiplier) if r: results.append(r) # Secret: crash 1x if abs(multiplier - 1.0) < 0.01: r = check_achievement(db, user_id, "secret_crash_1x", 1) if r: results.append(r) return results def check_items_unboxed_achievements(db: Session, user_id: int): from database import CaseOpening count = db.query(CaseOpening).filter(CaseOpening.user_id == user_id).count() results = [] for n in ["items_unboxed_100", "items_unboxed_1000", "items_unboxed_10000"]: r = check_achievement(db, user_id, n, count) if r: results.append(r) return results def check_total_profit_achievements(db: Session, user_id: int, profit: float): results = [] for n in ["total_profit_100k", "total_profit_1m", "total_profit_10m", "total_profit_100m"]: r = check_achievement(db, user_id, n, profit) if r: results.append(r) return results def check_achievements_count_achievements(db: Session, user_id: int): count = db.query(UserAchievement).filter( UserAchievement.user_id == user_id, UserAchievement.unlocked_at.isnot(None) ).count() results = [] for n in ["achievements_10", "achievements_25", "achievements_50", "achievements_100"]: r = check_achievement(db, user_id, n, count) if r: results.append(r) return results def check_rare_item_count_achievements(db: Session, user_id: int): from database import InventoryItem from backend import ALL_ITEMS items = db.query(InventoryItem).filter(InventoryItem.user_id == user_id).all() rare_count = 0 for inv in items: if inv.item_id < len(ALL_ITEMS): rar = ALL_ITEMS[inv.item_id].get("rarity", "") if rar in ("Covert", "Extraordinary", "Rare Special Item"): rare_count += 1 results = [] for n in ["rare_item_10", "rare_item_50"]: r = check_achievement(db, user_id, n, rare_count) if r: results.append(r) return results def check_knife_count_achievements(db: Session, user_id: int): from database import InventoryItem from backend import ALL_ITEMS, is_knife, is_gloves items = db.query(InventoryItem).filter(InventoryItem.user_id == user_id).all() knife_count = 0 for inv in items: if inv.item_id < len(ALL_ITEMS): item_data = ALL_ITEMS[inv.item_id] if is_knife(item_data) or is_gloves(item_data): knife_count += 1 results = [] for n in ["knife_5"]: r = check_achievement(db, user_id, n, knife_count) if r: results.append(r) return results def check_midnight_case_achievement(db: Session, user_id: int) -> list: """Проверяет секретное достижение 'открытие кейса в полночь'""" from database import CaseOpening from sqlalchemy import func midnight_openings = db.query(CaseOpening).filter( CaseOpening.user_id == user_id, func.strftime("%H", CaseOpening.opened_at).between("00", "04") ).count() if midnight_openings > 0: r = check_achievement(db, user_id, "secret_midnight", 1) return [r] if r else [] return [] def check_exact_cases_achievement(db: Session, user_id: int, case_count: int) -> list: """Проверяет секретное достижение 'ровно 1000 кейсов'""" result = check_achievement(db, user_id, "secret_1000_exact", 1 if case_count == 1000 else 0) return [result] if result else [] def check_all_secret_achievements(db: Session, user_id: int): """Проверяет все секретные достижения (вызывается периодически)""" results = [] # These are checked in their respective functions above: # secret_midnight - check_midnight_case_achievement # secret_exact_balance - check_balance_achievements # secret_99_fail - check_upgrade_result_achievements # secret_negative_balance - check_balance_achievements # secret_1000_exact - check_exact_cases_achievement # secret_empty_inv - check_inventory_achievements # secret_spent_exact - check_spent_achievements # secret_low_prob_win - check_upgrade_result_achievements # secret_dildo_specific - check_dildo_achievements # secret_crash_1x - check_crash_cashout_achievements # secret_balance_777 - check_balance_achievements # secret_one_item_inv - check_inventory_achievements # secret_contract_dildo - check_dildo_contract_achievement # secret_slot_zero - need to implement # secret_exact_float - check_dildo_achievements return results # ─── Получение достижений пользователя ───────────────────────────────────── def get_user_achievements(db: Session, user_id: int) -> list: """Возвращает все достижения пользователя с прогрессом. Секретные (hidden) достижения показываются как ??? пока не открыты.""" all_ach = db.query(Achievement).order_by(Achievement.sort_order).all() result = [] for ach in all_ach: ua = db.query(UserAchievement).filter( UserAchievement.user_id == user_id, UserAchievement.achievement_id == ach.id ).first() unlocked = ua.unlocked_at is not None if ua else False # Секретные: скрываем название/описание пока не открыто if ach.hidden and not unlocked: result.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.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