feat: ефакторинг системы уровней + генератор профильных карточек

- Создана доменная структура feature/level/ с разделением на domain/service
- ынесены классы LevelUpResult, LevelProgress, UserStats, XpFormula
- Созданы сервисы: LevelService, MessageCooldownService, LeaderboardService
- еремещены команды и листенеры в feature/level/
- далены импорты AudioManager и мертвые ссылки на TTS
- бновлены все тесты

овая функциональность:
- енератор красивых профильных карточек (RankCardGenerator)
- оманда !profile для просмотра карточки с уровнем, XP, статистикой
- оманда !stop для безопасной остановки бота (только админы)
- обавлены ассеты: фоны, иконки, бейджи

Технические улучшения:
- ерсистентный кулдаун через UserStats.lastMessageTime
- бобщенные методы лидерборда через Comparator
- Graceful shutdown с сохранением всех данных
- Fallback на заглушки если ассеты не загрузились
This commit is contained in:
loki5512344 2026-05-11 16:43:51 +02:00
parent aa3388cd9e
commit f460c59e2f
41 changed files with 1574 additions and 216 deletions

96
TODO.md
View file

@ -197,74 +197,54 @@ src/main/java/creator/loki/
- [x] Добавить `UserStatsRepository` в `BotModule`
**3. Удалить импорты AudioManager и мертвые ссылки**
- [ ] Удалить `import creator.loki.audio.AudioManager` из `BotInitializer`
- [ ] Удалить `import creator.loki.audio.AudioManager` из `PrefixCommandRegistrar`
- [ ] Удалить `import creator.loki.audio.AudioManager` из `SlashCommandRegistrar`
- [ ] Удалить параметр `AudioManager audioManager` из конструкторов
- [ ] Удалить поля `private final AudioManager audioManager`
- [ ] Удалить `registry.register(new TtsCommand(...))` из `PrefixCommandRegistrar`
- [ ] Удалить `registry.register(new TtsSlashCommand(...))` из `SlashCommandRegistrar`
- [ ] Исправить `registry.register(new NewLeaderboardCommand(...))` → использовать новый путь
- [ ] Удалить все `registry.register(new *SlashCommand(...))` из `SlashCommandRegistrar`
- [ ] Удалить `import creator.loki.command.slash.*` из `SlashCommandRegistrar`
- [ ] Проверить компиляцию: `gradle build`
- [x] Удалить `import creator.loki.audio.AudioManager` из `BotInitializer`
- [x] Удалить `import creator.loki.audio.AudioManager` из `PrefixCommandRegistrar`
- [x] Удалить `import creator.loki.audio.AudioManager` из `SlashCommandRegistrar`
- [x] Удалить параметр `AudioManager audioManager` из конструкторов
- [x] Удалить поля `private final AudioManager audioManager`
- [x] Удалить `registry.register(new TtsCommand(...))` из `PrefixCommandRegistrar`
- [x] Удалить `registry.register(new TtsSlashCommand(...))` из `SlashCommandRegistrar`
- [x] Исправить `registry.register(new NewLeaderboardCommand(...))` → использовать новый путь
- [x] Удалить все `registry.register(new *SlashCommand(...))` из `SlashCommandRegistrar`
- [x] Удалить `import creator.loki.command.slash.*` из `SlashCommandRegistrar`
- [x] Проверить компиляцию: `gradle build`
---
#### Этап 2: feature/level/ (зависит от Этапа 1)
**4. Создать feature/level/domain/**
- [ ] Создать `feature/level/`
- [ ] Создать `feature/level/domain/`
- [ ] Создать `feature/level/domain/XpFormula.java`:
```java
public class XpFormula {
private static final int XP_PER_LEVEL = 200;
// Исправленная формула: 200 * n * (n+1) / 2
public int getTotalXPForLevel(int level) {
return XP_PER_LEVEL * level * (level + 1) / 2;
}
// Обратная формула с использованием XP_PER_LEVEL
public int calculateLevel(int xp) {
double discriminant = 1 + 8.0 * xp / XP_PER_LEVEL;
return (int) Math.floor((-1 + Math.sqrt(discriminant)) / 2.0);
}
}
```
- [ ] Вынести `LevelUpResult` из `LevelSystem` → `feature/level/domain/LevelUpResult.java`
- [ ] Вынести `LevelProgress` из `LevelSystem` → `feature/level/domain/LevelProgress.java`
- [ ] Переместить `level/UserStats` → `feature/level/domain/UserStats.java`
- [ ] **НЕ делать immutable сейчас** (Gson требует сеттеры, сделаем после Repository)
- [ ] Добавить `equals()` и `hashCode()` в `UserStats`
- [ ] Переименовать `incrementMessages()` → `recordMessage(long timestamp)`
- [x] Создать `feature/level/`
- [x] Создать `feature/level/domain/`
- [x] Создать `feature/level/domain/XpFormula.java`
- [x] Вынести `LevelUpResult` из `LevelSystem` → `feature/level/domain/LevelUpResult.java`
- [x] Вынести `LevelProgress` из `LevelSystem` → `feature/level/domain/LevelProgress.java`
- [x] Переместить `level/UserStats` → `feature/level/domain/UserStats.java`
- [x] **НЕ делать immutable сейчас** (Gson требует сеттеры, сделаем после Repository)
- [x] Добавить `equals()` и `hashCode()` в `UserStats`
- [x] Переименовать `incrementMessages()` → `recordMessage(long timestamp)`
**5. Создать feature/level/service/**
- [ ] Создать `feature/level/service/`
- [ ] Создать `feature/level/service/MessageCooldownService.java`:
- Персистентный кулдаун через `UserStats.lastMessageTime`
- Убрать `ConcurrentHashMap messageTimestamps`
- [ ] Создать `feature/level/service/LeaderboardService.java`:
- Вынести методы `getTopByLevel`, `getTopByMessages`, `getTopByVoice`
- Обобщить через `Comparator<UserStats>`
- [ ] Рефакторить `level/LevelSystem` → `feature/level/service/LevelService.java`:
- Убрать God Class: только бизнес-логика начисления XP
- Использовать `UserStatsRepository` вместо `JsonDataStore`
- Использовать `XpFormula` для вычислений
- Использовать `MessageCooldownService` для кулдаунов
- Убрать методы лидерборда (в `LeaderboardService`)
- Убрать `getAllStats()` (в Repository)
- [x] Создать `feature/level/service/`
- [x] Создать `feature/level/service/MessageCooldownService.java`
- [x] Создать `feature/level/service/LeaderboardService.java`
- [x] Рефакторить `level/LevelSystem` → `feature/level/service/LevelService.java`
- [x] Использовать `UserStatsRepository` вместо `JsonDataStore`
- [x] Использовать `XpFormula` для вычислений
- [x] Использовать `MessageCooldownService` для кулдаунов
- [x] Убрать методы лидерборда (в `LeaderboardService`)
- [x] Обновить все импорты UserStats на новый путь
- [x] Удалить старый `level/UserStats.java`
- [x] Добавить сервисы в `BotModule`
**6. Переместить команды и листенеры в feature/level/**
- [ ] Переместить `command/impl/stats/RankCommand` → `feature/level/RankCommand`
- [ ] Переместить `command/impl/stats/StatsCommand` → `feature/level/StatsCommand`
- [ ] Переместить `listener/LevelUpNotifier` → `feature/level/LevelUpNotifier`
- [ ] Переместить `listener/VoiceTracker` → `feature/level/VoiceTracker`
- [ ] Переместить `profile/LevelUtils` → `feature/level/LevelUtils` (если нужен)
- [ ] Удалить пустую папку `level/`
- [ ] Обновить все импорты
- [ ] Обновить `BotInitializer`, `BotModule`, `PrefixCommandRegistrar`
- [x] Переместить `command/impl/stats/RankCommand` → `feature/level/RankCommand`
- [x] Переместить `command/impl/stats/StatsCommand` → `feature/level/StatsCommand`
- [x] Переместить `listener/LevelUpNotifier` → `feature/level/LevelUpNotifier`
- [x] Переместить `listener/VoiceTracker` → `feature/level/VoiceTracker`
- [x] Обновить все импорты
- [x] Обновить `BotInitializer`, `BotModule`, `PrefixCommandRegistrar`
- [x] Обновить команды для использования `LevelService` вместо `LevelSystem`
**7. Создать команду сброса уровней (миграция на новую систему)**
- [ ] Создать `feature/level/ResetLevelsCommand.java`: