+ economy domain (EconomyAccount, Job, SlotCombination, EconomyTransaction) + EconomyRepository + SqlEconomyRepository + DB schema + EconomyService (transfer, daily, deposit/withdraw, stat upgrades) + FishingService (fish grid with difficulty levels, speed bonus) + BlackjackService (dealer stands on 16, ties lose) + SlotService (weighted combos up to x50) + JobService (6 jobs with level reqs and efficiency stat) + 11 commands: balance, baltop, pay, daily, bank, rob, fish, blackjack, slot, job, work + Tests for SqlEconomyRepository + EconomyModule + EconomyCommandRegistrar wired into BotModule
254 lines
12 KiB
Markdown
254 lines
12 KiB
Markdown
# TODO — Loki Bot v2.1
|
||
|
||
## ✅ Что уже сделано
|
||
|
||
| # | Статус | Проблема | Изменения |
|
||
|---|--------|----------|-----------|
|
||
| C1–C15 | ✅ | Предыдущие раунды | См. git history |
|
||
| J21 | ✅ | Java 17 → 21 | `build.gradle`: source/target 21 |
|
||
| A05 | ✅ | EventDispatcher: CachedThreadPool → VirtualThreadPool | `Executors.newVirtualThreadPerTaskExecutor()` |
|
||
| A06 | ✅ | SwapCommand + RepTopCommand: `new Thread()` → `Thread.startVirtualThread()` | |
|
||
| A25 | ✅ | CommandExceptionHandler: instanceof pattern matching | |
|
||
| A26 | ✅ | ClearCommand: instanceof pattern matching | |
|
||
| A27 | ✅ | DatabaseMigration: instanceof pattern matching | |
|
||
| A28 | ✅ | InfoCommand: switch → arrow-switch | |
|
||
| A29 | ✅ | RateCommand: `"⭐".repeat(rating)` + `"☆".repeat()` | |
|
||
| A32 | ✅ | .collect(Collectors.toList()) → .toList() | SwapCommand, RepTopCommand, ChoiceOptionParser |
|
||
| A33 | ✅ | HelpCategories: Map.ofEntries() вместо HashMap + unmodifiable | |
|
||
| A02 | ✅ | RankCardGenerator: блокировка Event Thread | Исправлено (команды на virtual threads) |
|
||
| A10 | ✅ | RankCardGenerator: утечка InputStream | try-with-resources в loadFont + loadBackground |
|
||
| A11 | ✅ | RankCardGenerator: шрифты не кэшировались | ConcurrentHashMap cache |
|
||
| A12 | ✅ | RankCardGenerator: SVG-иконки не работали | Заменены на геометрические фигуры (Java2D) |
|
||
| A23 | ✅ | RankCardGenerator: throws Exception | loadFont безопасный |
|
||
| A03 | ✅ | Database: race condition на connection | synchronized методы |
|
||
| A13 | ✅ | NekosLifeApi: Thread.sleep в catch | Сон вынесен в retry loop |
|
||
| A14 | ✅ | GenerateRatingsCommand: блокировка event-потока | `complete()` вместо `queue()` |
|
||
| A35 | ✅ | GenerateRatingsCommand: опечатка | `Пропускаемботов` → `Пропускаем ботов` |
|
||
| A04 | ✅ | PrefixCommandRegistrar: God Constructor (22 param) | Разбит на 4 per-feature CommandRegistrar, Multibinder, 1 param |
|
||
| A21 | ✅ | BotModule: путь к БД хардкодом | Вынесен в .env (`DB_PATH`) + BotConfig |
|
||
| A34 | ✅ | CommandCategories: мёртвый код | Уже удалён (старый пакет) |
|
||
| A37 | ✅ | ProfileCommand: регистрация в PrefixCommandRegistrar | LevelCommandRegistrar |
|
||
|
||
---
|
||
|
||
## ♻️ Правила разработки
|
||
|
||
- KISS, DRY, SOLID
|
||
- Файлы ≤150–200 строк (в идеале ≤100)
|
||
- В папке ≤6 файлов
|
||
- Пакет = домен/фича, а не тип файла (`feature/level/`, а не `commands/listeners/`)
|
||
- Каждая фича содержит свои команды, сервисы, репозитории
|
||
- Единый слой персистентности через `repository/`
|
||
- Интеграции в `integration/`
|
||
|
||
---
|
||
|
||
## 🔴 Проблемы (результаты аудита, которые ещё не исправлены)
|
||
|
||
### HIGH
|
||
|
||
| # | Приоритет | Проблема | Статус |
|
||
|---|-----------|----------|--------|
|
||
| A07 | 🟠 **HIGH** | `HealthHttpServer` — single thread executor | ✅ Virtual threads |
|
||
| A08 | 🟠 **HIGH** | Тестовое покрытие ~5% (~34 теста на 149 продакшен-файлов). Нужно 20-30% | ⏳ 268 тестов в 22 файлах |
|
||
| A09 | 🟠 **HIGH** | Нет Docker / docker-compose | ✅ Dockerfile + docker-compose + .env.example |
|
||
| A01 | ⚪ **INFO** | `.env` с токеном в git-истории (локальный репозиторий) | ➖ |
|
||
|
||
### MEDIUM
|
||
|
||
| # | Приоритет | Проблема | Статус |
|
||
|---|-----------|----------|--------|
|
||
| A15 | 🟡 **MEDIUM** | ~33 файла команд в `command/impl/` — не перенесены в `feature/*/commands/` | ✅ 33 файла перенесены |
|
||
| A16 | 🟡 **MEDIUM** | ~16 репозиториев в `database/` — не разнесены в `feature/*/repository/` | ✅ 13+3 репозитория перенесены |
|
||
| A18 | 🟡 **MEDIUM** | `DatabaseMigration` — устаревший код для JSON→SQLite. Нужно оценить | ✅ Harmless, idempotent — keep as-is |
|
||
| A19 | 🟡 **MEDIUM** | Нет статического анализа (checkstyle / spotbugs) | ✅ checkstyle.xml + spotbugs + 169 known violations |
|
||
| A20 | 🟡 **MEDIUM** | Нет версионного каталога (`libs.versions.toml`) | ✅ gradle/libs.versions.toml, build.gradle переписан |
|
||
| A22 | 🟡 **MEDIUM** | Нет пула соединений (HikariCP). Голый JDBC | ✅ HikariCP 5.1.0 + proxy-based prepare() |
|
||
| A24 | 🟡 **MEDIUM** | VoiceTracker, RateLimiter, CooldownManager, MuteScheduler — ScheduledExecutorService. virtual threads? | ✅ Virtual threads |
|
||
| A40 | 🟡 **MEDIUM** | `HelpMenuHandler` в `event/interaction/` — должен быть в `feature/help/` | ✅ Перенесён |
|
||
|
||
### LOW
|
||
|
||
| # | Приоритет | Проблема | Статус |
|
||
|---|-----------|----------|--------|
|
||
| A30 | 🟢 **LOW** | SlowmodeCommand: ручная проверка границ → Math.clamp (другая семантика) | ➖ |
|
||
| A31 | 🟢 **LOW** | ClearCommand: ручная проверка границ → Math.clamp (другая семантика) | ➖ |
|
||
| A39 | 🟢 **LOW** | EmbedFactory: `set*` вместо `with*` для builder | ✅ Renamed in 43 files |
|
||
| A36 | 🟢 **LOW** | RankCardGenerator: URI → URL, deprecated API | ✅ `URI.toURL()` → `new URL()` |
|
||
|
||
---
|
||
|
||
## 📋 План рефакторинга
|
||
|
||
### Этап 1: Java 21 ✅ (14 items)
|
||
- [x] EventDispatcher: VirtualThreadPool
|
||
- [x] instanceof pattern matching (3 файла)
|
||
- [x] arrow-switch (InfoCommand)
|
||
- [x] String.repeat() (RateCommand)
|
||
- [x] .toList() (3 файла)
|
||
- [x] Map.ofEntries() (HelpCategories)
|
||
|
||
### Этап 2: Рефакторинг PrefixCommandRegistrar ✅
|
||
- [x] Создан интерфейс `CommandRegistrar`
|
||
- [x] 4 per-feature регистратора
|
||
- [x] Guice Multibinder для сбора регистраторов
|
||
- [x] 22 параметра → 1 (`Set<CommandRegistrar>`)
|
||
|
||
### Этап 3: Critical-баги ✅
|
||
- [x] RankCardGenerator: async + try-with-resources + cache + SVG
|
||
- [x] Database: synchronized connection
|
||
- [x] NekosLifeApi: чистый retry loop
|
||
- [x] GenerateRatingsCommand: async через complete()
|
||
- [x] DB path: вынесен в .env
|
||
|
||
### Этап 4: Перемещение файлов в feature-пакеты ✅
|
||
- [x] `command/impl/utility/` → `feature/*/commands/`
|
||
- [x] `command/impl/entertainment/` → `feature/*/commands/`
|
||
- [x] `command/entertainment/social/` → `feature/social/commands/`
|
||
- [x] `database/*/` → `feature/*/repository/`
|
||
- [x] `event/interaction/HelpMenuHandler` → `feature/help/`
|
||
|
||
### Этап 5: Тесты (цель: 20-30% покрытие)
|
||
- [x] Тесты на репозитории (in-memory SQLite) — Config, ShipPair, UserStats, Ratings, Warnings, ActiveMutes
|
||
- [x] Unit-тесты сервисов — MessageCooldownService, LevelService (был), XpFormula, ChoiceOptionParser
|
||
- [x] Unit-тесты value objects — UserStats (был), LevelProgress, LevelUpResult, BotConfig
|
||
- [x] Тесты embed — EmbedFactory (builder chaining), EmbedTemplates (factory methods)
|
||
- [x] Тесты форматирования — LeaderboardFormatter
|
||
- [ ] Unit-тесты на команды (с моками JDA)
|
||
- [ ] Smoke-тест: все команды регистрируются без коллизий
|
||
- [ ] Тесты на RankCardGenerator (изолированные, без сети)
|
||
- [ ] Тесты на EventDispatcher (маршрутизация, rate limiting)
|
||
|
||
### Этап 6: Инфраструктура ✅
|
||
- [x] Dockerfile (multi-stage)
|
||
- [x] docker-compose.yml
|
||
- [x] Статический анализ: checkstyle / spotbugs
|
||
- [x] Версионный каталог: `gradle/libs.versions.toml`
|
||
- [x] HikariCP вместо голого JDBC
|
||
|
||
### Этап 7: Кодстайл
|
||
- [x] EmbedFactory: `set*` → `with*` для builder
|
||
- [ ] DatabaseMigration: оценить нужность
|
||
- [x] HelpMenuHandler: переместить в feature/help/
|
||
|
||
---
|
||
|
||
## 🚀 Новые фичи (Roadmap)
|
||
|
||
## 🚀 Экономика — план реализации (в работе)
|
||
|
||
### Модель данных
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS economy (
|
||
user_id TEXT PRIMARY KEY,
|
||
cash INTEGER NOT NULL DEFAULT 0, -- на руках
|
||
bank INTEGER NOT NULL DEFAULT 0, -- в банке
|
||
total_earned INTEGER NOT NULL DEFAULT 0,
|
||
total_spent INTEGER NOT NULL DEFAULT 0,
|
||
last_daily TEXT, -- ISO date
|
||
daily_streak INTEGER DEFAULT 0,
|
||
fishing_luck INTEGER DEFAULT 0, -- stat lvl 0-5
|
||
efficiency INTEGER DEFAULT 0, -- stat lvl 0-5
|
||
last_fish TEXT, -- ISO datetime
|
||
job TEXT, -- название работы
|
||
last_work TEXT -- ISO datetime
|
||
);
|
||
```
|
||
|
||
### Заработок
|
||
|
||
| Механика | Доход | Кулдаун |
|
||
|---|---|---|
|
||
| Сообщение | 1–3 LC | 60с |
|
||
| Голос | 1 LC / 2 мин | 2 мин |
|
||
| Ежедневно | 50 LC + streak ×1.5 на 7й день | 24ч |
|
||
| +rep (полученная) | 10 LC | — |
|
||
| Рыбалка | base × speed_mul × (1 + fishing_luck×0.01) | 2ч |
|
||
| Работа | random(range) × (1 + efficiency×0.01) | 6ч |
|
||
| Блэкджек | ×1.5 при победе | — |
|
||
| Слоты | ×0–×50 | — |
|
||
|
||
### Миниигры
|
||
|
||
**Рыбалка** (`!fish easy/normal/hard`)
|
||
- Сетка N×N (emoji + 🌊), бесплатно, неправильный ответ = 0
|
||
- Множитель скорости: <50% времени = ×1.5, <30% = ×2.0
|
||
- Стат `fishing_luck` (макс 5, стоимость 800×lvl)
|
||
|
||
**Блэкджек** (`!bj <bet>`)
|
||
- Дилер стоит на 16, ничья = проигрыш, BJ = ×1.5
|
||
- Кнопки Hit/Stand
|
||
|
||
**Слоты** (`!slot <bet>`)
|
||
- 🍒🍒🍒 ×3 (10%), 🔔🔔🔔 ×5 (5%), 💎💎💎 ×10 (2%), 👑👑👑 ×15 (1%), ⚡⚡⚡ ×50 (0.1%)
|
||
|
||
**Работы** (`!job list`, `!job join`, `!work`)
|
||
| Работа | Ур. | Доход |
|
||
|---|---|---|
|
||
| 🪓 Лесоруб | — | 15–30 |
|
||
| ⛏️ Шахтёр | — | 20–40 |
|
||
| 🎣 Рыбак | — | 25–50 |
|
||
| 🍳 Повар | 15 | 30–60 |
|
||
| 💻 Программист | 20 | 40–80 |
|
||
| 🧑🚀 Космонавт | 35 | 50–100 |
|
||
|
||
Стат `efficiency` (макс 5, стоимость 800×lvl)
|
||
|
||
**Rob** (`!rob @user`)
|
||
- 24ч кулдаун, 40% успех, крадёт до 15% cash (макс 10к)
|
||
- Провал: штраф 10% cash (макс 5к) → жертве
|
||
- Банк не трогается
|
||
|
||
**Банк** (`!bank deposit/withdraw/balance`)
|
||
- Без процентов, защита от rob
|
||
|
||
### Структура пакета
|
||
|
||
```
|
||
feature/economy/
|
||
EconomyModule.java
|
||
EconomyCommandRegistrar.java
|
||
domain/
|
||
EconomyAccount.java
|
||
EconomyTransaction.java
|
||
Job.java
|
||
SlotCombination.java
|
||
repository/
|
||
EconomyRepository.java
|
||
SqlEconomyRepository.java
|
||
service/
|
||
EconomyService.java
|
||
FishingService.java
|
||
BlackjackService.java
|
||
SlotService.java
|
||
JobService.java
|
||
command/
|
||
core/ (Balance, Baltop, Pay, Daily)
|
||
minigame/ (Fish, Blackjack, Slot, Job, Work)
|
||
social/ (Rob)
|
||
bank/ (Bank)
|
||
```
|
||
|
||
---
|
||
|
||
### Приоритет 1
|
||
- [ ] Система достижений (Achievements)
|
||
- [x] Система экономики (Economy) — **в работе**
|
||
|
||
### Приоритет 2
|
||
- [x] Авто-модерация (3 варна = авто-мут на 1ч)
|
||
- [x] Музыка (lavaplayer: play, skip, stop, queue, np, volume, leave)
|
||
- [x] ~~Музыка — поиск с выбором (yt/sc/list + выбор номера)~~
|
||
- [x] Server Stats (участники, каналы, роли, бусты)
|
||
- [ ] Level Roles (роли за уровни)
|
||
- [ ] Красивые level-up embed с прогресс-баром
|
||
|
||
### Приоритет 3
|
||
- [ ] Статистика сервера — графики, CSV export
|
||
- [ ] Система квестов
|
||
- [ ] Wiki по установке и командам
|
||
|
||
---
|
||
|
||
**Версия:** 2.2 | **Последнее обновление:** 2026-06-20 | **Java 21 ✅, God Constructor ✅, Critical баги ✅, Economy 🚧**
|