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