Files

824 lines
72 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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