Skip to main content

Buffs System

1. Summary

Goal: Механизм временных бонусов, активируемых из инвентаря. Игрок получает предметы-баффы как награды, активирует их и получает усиленные награды или защиту стрика.

User Value: Возможность усилить прогресс в нужный момент (+25-100% к XP или Scrap) или застраховать стрик от пропуска дня.

Источники получения BUFF:

  • Кейсы (Cases)
  • Рулетка (Daily Spins)
  • Квесты (Quests)
  • Достижения (Achievements)
  • Промокоды (Promo Codes)

Путь: Награда → Предмет BUFF в инвентаре → Активация → Временный бонус.


2. Business Logic

Types of Buffs

Эффект: Множитель получаемого XP с кейсов и рулетки

Тиры и множители:

TierМножительЭффект
TIER_1×1.25+25% XP
TIER_2×1.50+50% XP
TIER_3×2.00+100% XP

Длительность: 30 минут по умолчанию (настраивается админом при создании предмета)

Offline: Таймер тикает всегда (даже когда приложение закрыто)

Где работает: Только кейсы и рулетка (квесты, ачивки, рефералы — фиксированные награды)

Цель: Ускорение прогресса для активных игроков

Активные баффы в профиле
Активные баффы в профиле: XP Catalyst (+100%) и Scrap Catalyst (+50%) с таймерами
Streak Shield в модалке стрика
Streak Shield (3/3 щитов) отображается в модалке стрика

Activation Mechanics

Алгоритм активации (реализован в BuffService.activateBuff()):

1. Validation

  • Предмет в инвентаре (UserInventory) принадлежит пользователю
  • Тип предмета: BUFF (Item.itemType)
  • У предмета указан buffType

2. Limit Check (только для STREAK_SHIELD)

  • Проверка суммы usesLeft всех активных shields: totalShieldUses < 3

3. Stacking Strategy

Правила стакания
  • XP_BUFF / SCRAP_BUFF: Если активен бафф того же типа и того же тира → продлевается время
  • Разные тиры: Ошибка "Активен бафф другого уровня. Дождитесь окончания текущего баффа."
  • STREAK_SHIELD: Добавляет +1 use к существующей записи или создаёт новый shield

4. Atomic Transaction

  • Уменьшение quantity в инвентаре (или удаление записи если quantity <= 1)
  • Создание/продление записи в UserActiveBuff
  • Всё в одной транзакции Prisma

5. Extension Logic

Для временных баффов (XP/SCRAP):

newExpiresAt = max(currentExpiresAt, now) + durationMinutes

6. Event Logging

  • После успешной транзакции создаётся запись в BuffEvent с типом ACTIVATION или EXTENSION

Buff Application

Механика применения множителя при получении награды:

Момент проверки

Бафф проверяется в момент нажатия (открытие кейса / спин), а не в момент выдачи награды. Если бафф был активен при нажатии — бонус применяется, даже если истёк во время анимации.

XP_BUFF:

finalXP = baseXP × buffMultiplier
bonusXP = finalXP - baseXP

Применяется централизованно через addUserXPWithBuff() в common/utils/xp.service.ts.

SCRAP_BUFF:

finalScrap = baseScrap × buffMultiplier
bonusScrap = finalScrap - baseScrap

Применяется в CaseOpeningService и UserSpinService.

НЕ влияет на
  • Salvage — переработка предметов (можно накопить скрап, пока бафф активен)
  • Квесты/ачивки — фиксированные награды
  • Рефералы — пассивный доход

STREAK_SHIELD:

shieldsToUse = min(daysSkipped, totalShieldUses, 3)

Применяется автоматически в StreakService при обнаружении пропуска.

Event Types

EventTypeОписаниеЗаписывается при
ACTIVATIONАктивация нового баффаBuffService.activateBuff()
EXTENSIONПродление существующего баффаBuffService.activateBuff() (если бафф того же тира активен)
APPLICATIONПрименение множителя к наградеCaseOpeningService, UserSpinService
SHIELD_USEАвтоматическое использование shieldStreakService.claimDaily()

Protection

ДействиеRate LimitAuthValidation
Get active buffsgeneral (100/min)TelegramGetActiveBuffsSchema
Get buff historygeneral (100/min)TelegramGetBuffHistorySchema
Activate buffmutations (5/min)TelegramActivateBuffSchema
Детали реализации

См. Security Matrix для полного обзора защит.

Edge Cases

Что видит пользователь (UI):

СитуацияUI поведение
Нет баффов в инвентареСекция баффов скрыта или пустая
Активен бафф другого тираВ модалке активации: кнопка disabled, жёлтый блок с таймером до окончания текущего баффа
Max shields (3 uses)Кнопка disabled, tooltip "Достигнут лимит активных щитов"
Shield автоматически использованPush-уведомление "Streak Shield защитил вашу серию!"
Бафф применён к наградеВ модале награды показывается бонус с цветом тира
Конфликт тиров баффа
Попытка активации баффа при активном баффе другого тира
Backend Error Codes (для API/тестов)
КодHTTPСообщение
ITEM_NOT_FOUND400"Item not found in inventory"
NOT_A_BUFF400"Item is not a BUFF"
NO_BUFF_TYPE400"Item has no buffType"
TIER_MISMATCH400"Активен бафф другого уровня. Дождитесь окончания текущего баффа."
MAX_SHIELDS400"Maximum 3 active Streak Shields allowed"
FORBIDDEN403"Item does not belong to user"
Сценарии использования Streak Shield

Сценарий 1: Обычное продление стрика

День 1: Вход, streak = 1
День 2: Вход, streak = 2
→ Shields не используются

Сценарий 2: Пропуск 1 дня с shield

День 1: Вход, streak = 5, shields = 1
День 3: Вход (пропущен день 2)
→ 1 shield использован
→ Streak = 6 (продолжен)
→ Shields = 0

Сценарий 3: Пропуск 3 дней, 2 shields

День 1: Вход, streak = 10, shields = 2
День 5: Вход (пропущены дни 2,3,4)
→ Нужно защитить 3 пропуска
→ Есть только 2 shields
→ Shields использованы: 2
→ Streak СБРОШЕН (shields недостаточно)

Сценарий 4: Shield не защищает от Luck Pool AFK

День 1: Вход, в Luck Pool, shields = 3
День 15: Вход (пропущены дни 2-14)
→ 3 shields использованы (защитили 3 из 13 пропусков)
→ Streak СБРОШЕН (недостаточно shields)
→ Luck Pool: игрок УДАЛЁН (14 дней AFK)

3. ADR (Architectural Decisions)

Почему баффы одного тира стакаются, а разных — нет?

Проблема: Как обрабатывать активацию второго баффа того же типа?

Решение: Баффы одного типа и тира продлевают время, разных тиров — ошибка.

Альтернативы (отклонены):

  • Стакать любые баффы (множители складываются) — слишком мощно, ломает экономику
  • Заменять текущий бафф новым — плохой UX, теряется оставшееся время
  • Очередь баффов — сложная логика, неинтуитивный UI

Последствия: Простая и понятная механика. Пользователь сам решает, когда активировать бафф. Trade-off: нельзя "копить" баффы разных тиров.

Почему STREAK_SHIELD не имеет времени истечения?

Проблема: Streak Shield должен защитить от случайного пропуска, но время пропуска непредсказуемо.

Решение: Shield не истекает по времени, а тратится по событию (при обнаружении пропуска в StreakService).

Автоматическое использование

Shield расходуется автоматически в StreakService при проверке стрика. Пользователь не выбирает, использовать ли shield — это происходит мгновенно при первом запросе после пропуска.

Последствия: Справедливая защита от непредвиденных ситуаций. Лимит в 3 shields (сумма usesLeft) предотвращает накопление "вечной" защиты.

Почему активация потребляет предмет атомарно?

Проблема: Race condition — два параллельных запроса могут оба "увидеть" предмет и попытаться его использовать.

Решение: Prisma $transaction для atomic operations: декремент quantity + создание buff.

Последствия: Гарантированная консистентность. Невозможно активировать больше баффов, чем есть предметов.

Почему STREAK_SHIELD не защищает от Luck Pool AFK?

Проблема: Игрок с 3 shields может пропустить 3 дня и сохранить стрик. Должен ли он сохранить место в Luck Pool?

Решение: Shield защищает только streak, но НЕ влияет на AFK-статус в Luck Pool.

14 дней AFK = выход из пула

Независимо от количества shields, если игрок не заходил 14 дней — он удаляется из Luck Pool. Это справедливо: пул для активных игроков.

Альтернативы (отклонены):

  • Shield также защищает от AFK — даёт нечестное преимущество накопителям shields

Последствия: Shields полезны для сохранения стрика при коротких пропусках (1-3 дня), но не дают преимущества AFK-ерам в Luck Pool.

Почему BuffEvent хранит историю отдельно?

Проблема: Нужна история использования баффов, но UserActiveBuff — это текущее состояние.

Решение: Отдельная таблица BuffEvent для immutable логов событий.

Альтернативы (отклонены):

  • Хранить историю в UserActiveBuff с soft delete — засоряет таблицу, сложные запросы
  • Логировать только в analytics — нет возможности показать в UI

Последствия: Чистое разделение: UserActiveBuff для текущего состояния, BuffEvent для истории. Возможность показать историю в профиле.


4. Architecture

Services Overview

Buff Application Flow

Key Components

КомпонентПутьОписание
BuffServicebackend/src/domains/buffs/services/buff.service.tsОркестратор активации и применения
BuffRepositorybackend/src/domains/buffs/repositories/buff.repository.tsCRUD операции с UserActiveBuff и BuffEvent
BuffControllerbackend/src/domains/buffs/controllers/buff.controller.tsHTTP handlers
Routesbackend/src/domains/buffs/routes/buff.routes.tsUser API endpoints
Schemasbackend/src/domains/buffs/schemas/buff.schemas.tsВалидация и Swagger
Constantsshared/src/constants/buff.constants.tsBUFF_DURATION_MINUTES, STREAK_SHIELD_LIMITS

Integration Points

Сервис-потребительМетодОписание
XPServicegetBuffMultiplierWithName('XP_BUFF')Применение множителя XP с получением названия баффа
CaseOpeningServicegetBuffMultiplierWithName('SCRAP_BUFF')Применение множителя Scrap
UserSpinServicegetBuffMultiplierWithName('SCRAP_BUFF')Применение множителя Scrap
StreakServiceuseStreakShields(daysSkipped)Автоматическое использование shields
Централизованное начисление XP

XP_BUFF применяется через addUserXPWithBuff() в common/utils/xp.service.ts, а не напрямую в domain services.


5. Database Schema

Models

МодельОписаниеКлючевые поля
UserActiveBuffАктивные баффы пользователяuserId, buffType, multiplier, expiresAt, usesLeft, sourceItemId
BuffEventИстория событий баффовuserId, buffType, eventType, multiplier, baseAmount, bonusAmount
ItemПредмет-источник баффаitemType: BUFF, buffType, buffMultiplier, buffDurationMinutes
UserInventoryИнвентарь пользователяuserId, itemId, quantity

UserActiveBuff Fields

ПолеТипОписание
buffTypeBuffTypeXP_BUFF, SCRAP_BUFF, STREAK_SHIELD
multiplierFloat?1.25 / 1.5 / 2.0 (null для STREAK_SHIELD)
expiresAtDateTime?Время истечения (null для STREAK_SHIELD)
usesLeftInt?Оставшиеся использования (только STREAK_SHIELD)
sourceItemIdString?ID Item для аналитики и UI

BuffEvent Fields

ПолеТипОписание
buffTypeBuffTypeТип баффа
eventTypeBuffEventTypeACTIVATION, EXTENSION, APPLICATION, SHIELD_USE
activeBuffIdString?Ссылка на UserActiveBuff
multiplierFloat?Множитель (для ACTIVATION/EXTENSION)
expiresAtDateTime?Время истечения (для ACTIVATION/EXTENSION)
sourceTypeString?'case' или 'spin' (для APPLICATION)
sourceIdString?ID caseOpening или spinResult (для APPLICATION)
baseAmountInt?Базовая награда до баффа (для APPLICATION)
bonusAmountInt?Бонус от баффа (для APPLICATION)
daysProtectedInt?Сколько дней защитил shield (для SHIELD_USE)
streakBeforeInt?Стрик до использования shield (для SHIELD_USE)

Relationships


6. API Endpoints

МетодЭндпоинтОписаниеDocs
GET/api/buffs/activeСписок активных баффов
GET/api/buffs/historyИстория событий баффов с пагинацией
POST/api/buffs/activateАктивировать бафф из инвентаря

Response Examples

GET /api/buffs/active
{
"success": true,
"data": [
{
"id": "clx...",
"buffType": "XP_BUFF",
"multiplier": 1.5,
"activatedAt": "2024-01-15T10:00:00.000Z",
"expiresAt": "2024-01-15T10:30:00.000Z",
"usesLeft": null,
"remainingSeconds": 1234
},
{
"id": "cly...",
"buffType": "STREAK_SHIELD",
"multiplier": null,
"activatedAt": "2024-01-14T08:00:00.000Z",
"expiresAt": null,
"usesLeft": 2,
"remainingSeconds": null
}
]
}
GET /api/buffs/history?page=1&limit=20&buffType=XP_BUFF
{
"success": true,
"data": {
"events": [
{
"id": "evt...",
"buffType": "XP_BUFF",
"eventType": "APPLICATION",
"multiplier": 1.5,
"sourceType": "case",
"sourceId": "case123",
"baseAmount": 100,
"bonusAmount": 50,
"createdAt": "2024-01-15T10:15:00.000Z"
}
],
"totalCount": 45,
"page": 1,
"limit": 20,
"totalPages": 3
}
}

Data Lifecycle

  • Активные баффы: Фильтруются по expiresAt > now или usesLeft > 0 в getActiveBuffs()
  • Истёкшие баффы: Остаются в UserActiveBuff как история текущего сезона
  • BuffEvent: Immutable log, не удаляется (кроме сезонного reset)
  • Очистка: Все записи удаляются при смене сезона в SeasonResetService
Нет отдельного cleanup job

Баффы очищаются вместе с остальными данными (inventory, quests и т.д.) при сезонном reset.


  • Streaks — STREAK_SHIELD защищает стрик от сброса
  • Cases — Баффы выпадают из кейсов как награды
  • Daily Spins — Множители XP/SCRAP применяются к наградам
  • Inventory — Баффы хранятся как предметы до активации
  • Security Matrix — Матрица защит для buffs endpoints