From 283b30f9793075b1624c46f6cd92f38558adfdd8 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sat, 20 Jun 2026 00:31:19 +0200 Subject: [PATCH] feat: wire economy LC to message/voice, add LevelRoleService, improve level-up embed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MessageListener: +1-3 LC per message - VoiceTracker: +1 LC/min periodic, +1 LC/2min on leave - LevelRoleService: auto-assigns roles (Активный/Постоянный/Ветеран/Легенда/Мастер) at levels 5/10/20/30/50 - LevelUpNotifier: progress bar, role reward display, EmbedFactory chain --- .../loki/feature/level/LevelModule.java | 10 +++- .../loki/feature/level/LevelRoleService.java | 46 +++++++++++++++ .../loki/feature/level/LevelUpNotifier.java | 56 +++++++++---------- .../loki/feature/level/MessageListener.java | 10 +++- .../loki/feature/level/VoiceTracker.java | 7 ++- 5 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 src/main/java/creator/loki/feature/level/LevelRoleService.java diff --git a/src/main/java/creator/loki/feature/level/LevelModule.java b/src/main/java/creator/loki/feature/level/LevelModule.java index d90bafa..15963cd 100644 --- a/src/main/java/creator/loki/feature/level/LevelModule.java +++ b/src/main/java/creator/loki/feature/level/LevelModule.java @@ -58,8 +58,14 @@ public class LevelModule extends AbstractModule { @Provides @Singleton - LevelUpNotifier provideLevelUpNotifier(ConfigRepository configRepo, LevelService levelService) { - return new LevelUpNotifier(configRepo, levelService); + LevelRoleService provideLevelRoleService() { + return new LevelRoleService(); + } + + @Provides + @Singleton + LevelUpNotifier provideLevelUpNotifier(ConfigRepository configRepo, LevelService levelService, LevelRoleService levelRoleService) { + return new LevelUpNotifier(configRepo, levelService, levelRoleService); } @Provides diff --git a/src/main/java/creator/loki/feature/level/LevelRoleService.java b/src/main/java/creator/loki/feature/level/LevelRoleService.java new file mode 100644 index 0000000..5088fb6 --- /dev/null +++ b/src/main/java/creator/loki/feature/level/LevelRoleService.java @@ -0,0 +1,46 @@ +package creator.loki.feature.level; + +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +public class LevelRoleService { + private static final Logger logger = LoggerFactory.getLogger(LevelRoleService.class); + private static final Map REWARD_ROLES = Map.of( + 5, "Активный", + 10, "Постоянный", + 20, "Ветеран", + 30, "Легенда", + 50, "Мастер" + ); + + public String assignRewardRole(Member member, int newLevel) { + String roleName = REWARD_ROLES.get(newLevel); + if (roleName == null) return null; + + List roles = member.getGuild().getRolesByName(roleName, true); + if (roles.isEmpty()) { + logger.warn("Role '{}' not found in guild {} for level {} reward", + roleName, member.getGuild().getId(), newLevel); + return null; + } + + Role role = roles.get(0); + if (member.getRoles().contains(role)) { + return null; + } + + member.getGuild().addRoleToMember(member, role).queue( + success -> logger.info("Assigned role '{}' to {} for level {}", + roleName, member.getEffectiveName(), newLevel), + error -> logger.error("Failed to assign role '{}' to {}: {}", + roleName, member.getEffectiveName(), error.getMessage()) + ); + + return roleName; + } +} diff --git a/src/main/java/creator/loki/feature/level/LevelUpNotifier.java b/src/main/java/creator/loki/feature/level/LevelUpNotifier.java index cc69585..192db92 100644 --- a/src/main/java/creator/loki/feature/level/LevelUpNotifier.java +++ b/src/main/java/creator/loki/feature/level/LevelUpNotifier.java @@ -1,9 +1,9 @@ package creator.loki.feature.level; +import creator.loki.embed.EmbedFactory; import creator.loki.feature.config.repository.ConfigRepository; import creator.loki.feature.level.domain.LevelProgress; import creator.loki.feature.level.service.LevelService; -import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; @@ -19,10 +19,12 @@ public class LevelUpNotifier { private static final Logger logger = LoggerFactory.getLogger(LevelUpNotifier.class); private final ConfigRepository configRepo; private final LevelService levelService; + private final LevelRoleService levelRoleService; - public LevelUpNotifier(ConfigRepository configRepo, LevelService levelService) { + public LevelUpNotifier(ConfigRepository configRepo, LevelService levelService, LevelRoleService levelRoleService) { this.configRepo = configRepo; this.levelService = levelService; + this.levelRoleService = levelRoleService; } /** @@ -30,27 +32,26 @@ public class LevelUpNotifier { */ public void sendLevelUpNotification(Member member, int oldLevel, int newLevel, MessageChannel channel) { LevelProgress progress = levelService.getProgress(member.getId()); - - EmbedBuilder embed = new EmbedBuilder() - .setTitle("🎉 Повышение уровня!") - .setDescription(member.getAsMention() + " достиг **уровня " + newLevel + "**!") - .addField("Предыдущий уровень", String.valueOf(oldLevel), true) - .addField("Новый уровень", String.valueOf(newLevel), true) - .addField("Всего XP", String.valueOf(progress.getTotalXP()), true) - .addField("До следующего уровня", - progress.getCurrentLevelXP() + " / " + progress.getXpForNextLevel() + " XP", false) - .setColor(getColorForLevel(newLevel)) - .setThumbnail(member.getEffectiveAvatarUrl()) - .setFooter("Продолжай в том же духе!"); - // Добавляем награды за уровень - String reward = getRewardForLevel(newLevel); - if (reward != null) { - embed.addField("🎁 Награда", reward, false); + String progressBar = buildProgressBar(progress.getPercentage()); + String rewardRole = levelRoleService.assignRewardRole(member, newLevel); + + EmbedFactory embed = EmbedFactory.create() + .withTitle("🎉 Повышение уровня!") + .withDescription(member.getAsMention() + " достиг **" + newLevel + " уровня**!") + .addField("Прогресс", progressBar, false) + .addField("Всего XP", String.valueOf(progress.getTotalXP()), true) + .addField("До следующего", progress.getCurrentLevelXP() + " / " + progress.getXpForNextLevel() + " XP", true) + .withColor(getColorForLevel(newLevel)) + .withThumbnail(member.getEffectiveAvatarUrl()) + .withFooter("Продолжай в том же духе!"); + + if (rewardRole != null) { + embed.addField("🎁 Награда", rewardRole, false); } channel.sendMessageEmbeds(embed.build()).queue( - success -> logger.info("Отправлено уведомление о level up для {}: {} -> {}", + success -> logger.info("Отправлено уведомление о level up для {}: {} -> {}", member.getEffectiveName(), oldLevel, newLevel), error -> logger.error("Ошибка отправки уведомления о level up", error) ); @@ -83,17 +84,10 @@ public class LevelUpNotifier { return Color.decode("#2ECC71"); // Зеленый } - /** - * Получить награду за уровень - */ - private String getRewardForLevel(int level) { - return switch (level) { - case 5 -> "🎖️ Роль **Активный**"; - case 10 -> "⭐ Роль **Постоянный**"; - case 20 -> "💎 Роль **Ветеран**"; - case 30 -> "👑 Роль **Легенда**"; - case 50 -> "🏆 Роль **Мастер** + особый цвет ника"; - default -> null; - }; + private String buildProgressBar(double percentage) { + int filled = (int) (percentage / 10); + filled = Math.min(filled, 10); + int empty = 10 - filled; + return "▓".repeat(filled) + "░".repeat(empty) + " " + String.format("%.0f%%", percentage); } } diff --git a/src/main/java/creator/loki/feature/level/MessageListener.java b/src/main/java/creator/loki/feature/level/MessageListener.java index 0e7f141..35d0594 100644 --- a/src/main/java/creator/loki/feature/level/MessageListener.java +++ b/src/main/java/creator/loki/feature/level/MessageListener.java @@ -3,21 +3,26 @@ package creator.loki.feature.level; import com.google.inject.Inject; import com.google.inject.Singleton; import creator.loki.core.EventDispatcher; +import creator.loki.feature.economy.service.EconomyService; import creator.loki.feature.level.service.LevelService; import creator.loki.feature.level.domain.LevelUpResult; import creator.loki.embed.EmbedTemplates; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import java.util.concurrent.ThreadLocalRandom; + @Singleton public class MessageListener extends ListenerAdapter { private final EventDispatcher dispatcher; private final LevelService levelService; + private final EconomyService economyService; @Inject - public MessageListener(EventDispatcher dispatcher, LevelService levelService) { + public MessageListener(EventDispatcher dispatcher, LevelService levelService, EconomyService economyService) { this.dispatcher = dispatcher; this.levelService = levelService; + this.economyService = economyService; } @Override @@ -38,6 +43,9 @@ public class MessageListener extends ListenerAdapter { ) ).queue(); } + + int lcAmount = ThreadLocalRandom.current().nextInt(1, 4); + economyService.earnCash(userId, lcAmount); } dispatcher.handleMessage(event); diff --git a/src/main/java/creator/loki/feature/level/VoiceTracker.java b/src/main/java/creator/loki/feature/level/VoiceTracker.java index 002180f..b9d06e6 100644 --- a/src/main/java/creator/loki/feature/level/VoiceTracker.java +++ b/src/main/java/creator/loki/feature/level/VoiceTracker.java @@ -2,6 +2,7 @@ package creator.loki.feature.level; import com.google.inject.Inject; import com.google.inject.Singleton; +import creator.loki.feature.economy.service.EconomyService; import creator.loki.feature.level.domain.LevelUpResult; import creator.loki.feature.level.service.LevelService; import net.dv8tion.jda.api.entities.Member; @@ -23,12 +24,14 @@ import java.util.concurrent.TimeUnit; public class VoiceTracker extends ListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(VoiceTracker.class); private final LevelService levelService; + private final EconomyService economyService; private final Map voiceJoinTimes = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @Inject - public VoiceTracker(LevelService levelService) { + public VoiceTracker(LevelService levelService, EconomyService economyService) { this.levelService = levelService; + this.economyService = economyService; // Каждую минуту начисляем XP тем, кто в войсе scheduler.scheduleAtFixedRate(() -> Thread.startVirtualThread(this::updateVoiceTime), 1, 1, TimeUnit.MINUTES); @@ -58,6 +61,7 @@ public class VoiceTracker extends ListenerAdapter { if (minutes > 0) { levelService.addVoiceXP(userId, minutes); + economyService.earnCash(userId, minutes / 2); logger.debug("Пользователь {} провёл в войсе {} минут", member.getEffectiveName(), minutes); } } @@ -84,6 +88,7 @@ public class VoiceTracker extends ListenerAdapter { if (minutes >= 1) { // Начисляем XP за каждую минуту levelService.addVoiceXP(userId, 1); + economyService.earnCash(userId, 1); // Обновляем время входа voiceJoinTimes.put(userId, now); }