824 lines
72 KiB
Python
824 lines
72 KiB
Python
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
|