Skip to content

Commit 837ddb0

Browse files
committed
feat: offline reward queue + thread-safe DB and async placeholders
1 parent d72a293 commit 837ddb0

29 files changed

Lines changed: 556 additions & 111 deletions

‎src/main/java/onevnl/ru/elytrya/BoostyBridge.java‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
importonevnl.ru.elytrya.listeners.ChatListener;
88
importorg.bstats.bukkit.Metrics;
99
importorg.bukkit.Bukkit;
10+
importorg.bukkit.command.PluginCommand;
1011
importorg.bukkit.plugin.java.JavaPlugin;
1112

1213
publicclassBoostyBridgeextendsJavaPlugin {
@@ -21,7 +22,14 @@ public void onEnable() {
2122
this.boostyClient = newBoostyClient(this);
2223
this.discordBotManager = newDiscordBotManager(this.boostyClient);
2324

24-
getCommand("boosty").setExecutor(newBoostyCommand(this.boostyClient));
25+
PluginCommandcommand = getCommand("boosty");
26+
if (command != null) {
27+
command.setExecutor(newBoostyCommand(this.boostyClient));
28+
} else {
29+
getLogger().severe(
30+
"Command /boosty is missing from plugin.yml, commands will not work."
31+
);
32+
}
2533
getServer()
2634
.getPluginManager()
2735
.registerEvents(newChatListener(this.boostyClient), this);

‎src/main/java/onevnl/ru/elytrya/api/BoostyClient.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
importonevnl.ru.elytrya.api.managers.BoostyDMManager;
1515
importonevnl.ru.elytrya.api.managers.DiscordManager;
1616
importonevnl.ru.elytrya.api.managers.MessageManager;
17+
importonevnl.ru.elytrya.api.managers.RewardManager;
1718
importonevnl.ru.elytrya.database.Database;
1819
importonevnl.ru.elytrya.database.MySQL;
1920
importonevnl.ru.elytrya.database.SQLite;
@@ -33,6 +34,7 @@ public class BoostyClient {
3334
privateAuthManagerauthManager;
3435
privateBlogManagerblogManager;
3536
privateDatabasedatabase;
37+
privateRewardManagerrewardManager;
3638
privateTokenCiphertokenCipher;
3739

3840
privatefinalMap<UUID, PendingLink> pendingLinks;
@@ -60,6 +62,7 @@ private void loadManagers() {
6062
this.blogManager = newBlogManager(this);
6163
this.discordManager = newDiscordManager(this);
6264
this.dmManager = newBoostyDMManager(this);
65+
this.rewardManager = newRewardManager(this);
6366

6467
if (this.database != null) {
6568
this.database.disconnect();
@@ -163,6 +166,10 @@ public BlogManager getBlogManager() {
163166
returnblogManager;
164167
}
165168

169+
publicRewardManagergetRewardManager() {
170+
returnrewardManager;
171+
}
172+
166173
publicDatabasegetDatabase() {
167174
returndatabase;
168175
}

‎src/main/java/onevnl/ru/elytrya/api/managers/DiscordBotManager.java‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,18 @@ public void shutdown() {
141141
}
142142

143143
publicvoidstop() {
144-
if (jda != null) {
145-
jda.shutdown();
146-
jda = null;
144+
if (jda == null) return;
145+
146+
net.dv8tion.jda.api.JDAcurrent = jda;
147+
jda = null;
148+
149+
try {
150+
current.shutdown();
151+
} catch (Exceptione) {
152+
client
153+
.getPlugin()
154+
.getLogger()
155+
.warning("Failed to stop Discord bot gracefully: " + e.getClass().getSimpleName());
147156
}
148157
}
149158

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
packageonevnl.ru.elytrya.api.managers;
2+
3+
importjava.util.ArrayList;
4+
importjava.util.LinkedHashSet;
5+
importjava.util.List;
6+
importjava.util.Set;
7+
importjava.util.UUID;
8+
importonevnl.ru.elytrya.api.BoostyClient;
9+
importonevnl.ru.elytrya.models.QueuedReward;
10+
importonevnl.ru.elytrya.util.BoostyNameValidator;
11+
importorg.bukkit.Bukkit;
12+
importorg.bukkit.entity.Player;
13+
14+
publicclassRewardManager {
15+
16+
publicstaticfinalStringACTION_GIVE = "give";
17+
publicstaticfinalStringACTION_TAKE = "take";
18+
19+
privatefinalBoostyClientclient;
20+
21+
publicRewardManager(BoostyClientclient) {
22+
this.client = client;
23+
}
24+
25+
publicbooleanisQueueEnabled() {
26+
returnclient.getPlugin().getConfig().getBoolean("rewards_queue.enabled", true);
27+
}
28+
29+
publiclonggetMaxAgeMillis() {
30+
longdays = client.getPlugin().getConfig().getLong("rewards_queue.max_age_days", 30);
31+
returndays <= 0 ? 0L : days * 24L * 60L * 60L * 1000L;
32+
}
33+
34+
publiclonggetDeliveryDelayTicks() {
35+
returnclient.getPlugin().getConfig().getLong("rewards_queue.delay_ticks", 40L);
36+
}
37+
38+
publicvoiddispatch(UUIDuuid, StringplayerName, StringboostyName, StringlevelName, Stringaction) {
39+
if (!BoostyNameValidator.isValid(boostyName)) {
40+
client.getPlugin().getLogger().warning("Reward commands (" + action + ") skipped for " + playerName + ": stored Boosty name failed validation.");
41+
return;
42+
}
43+
44+
if (isOnline(uuid, playerName) || !isQueueEnabled() || uuid == null) {
45+
runNow(playerName, boostyName, levelName, action);
46+
return;
47+
}
48+
49+
client.getDatabase().queueReward(uuid, action, levelName, boostyName);
50+
client.debug("Queued " + action + " rewards for offline player " + playerName);
51+
}
52+
53+
publicvoidrunNow(StringplayerName, StringboostyName, StringlevelName, Stringaction) {
54+
StringsafeBoostyName = BoostyNameValidator.sanitize(boostyName);
55+
56+
Bukkit.getScheduler().runTask(client.getPlugin(), () -> {
57+
List<String> commands = client.getPlugin().getConfig().getStringList("rewards." + levelName + "." + action);
58+
if (commands == null) return;
59+
for (Stringcmd : commands) {
60+
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", playerName).replace("%boosty_name%", safeBoostyName));
61+
}
62+
});
63+
}
64+
65+
publicvoidflush(UUIDuuid, StringplayerName) {
66+
if (uuid == null || !isQueueEnabled()) return;
67+
68+
Bukkit.getScheduler().runTaskAsynchronously(client.getPlugin(), () -> {
69+
List<QueuedReward> queued = client.getDatabase().getQueuedRewards(uuid);
70+
if (queued == null || queued.isEmpty()) return;
71+
72+
client.getDatabase().clearQueuedRewards(uuid);
73+
74+
longmaxAge = getMaxAgeMillis();
75+
List<QueuedReward> valid = newArrayList<>();
76+
for (QueuedRewardreward : queued) {
77+
if (reward.isOlderThan(maxAge)) {
78+
client.debug("Dropped expired queued reward for " + playerName + " (" + reward.levelName() + ")");
79+
continue;
80+
}
81+
valid.add(reward);
82+
}
83+
84+
if (valid.isEmpty()) return;
85+
86+
for (QueuedRewardreward : valid) {
87+
runNow(playerName, reward.boostyName(), reward.levelName(), reward.action());
88+
}
89+
90+
notifyDelivered(uuid, valid);
91+
});
92+
}
93+
94+
privatevoidnotifyDelivered(UUIDuuid, List<QueuedReward> delivered) {
95+
Set<String> levels = newLinkedHashSet<>();
96+
for (QueuedRewardreward : delivered) {
97+
if (ACTION_GIVE.equalsIgnoreCase(reward.action()) && reward.levelName() != null && !reward.levelName().equalsIgnoreCase("none")) {
98+
levels.add(reward.levelName());
99+
}
100+
}
101+
102+
if (levels.isEmpty()) return;
103+
104+
StringlevelList = String.join(", ", levels);
105+
intcount = delivered.size();
106+
MessageManagermsg = client.getMessageManager();
107+
108+
Bukkit.getScheduler().runTask(client.getPlugin(), () -> {
109+
Playerplayer = Bukkit.getPlayer(uuid);
110+
if (player == null || !player.isOnline()) return;
111+
player.sendMessage(msg.getMessage("reward_queue_delivered", "&f\u041f\u043e\u043a\u0430\u0432\u0430\u0441\u043d\u0435\u0431\u044b\u043b\u043e\u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0432\u0430\u043c\u043d\u0430\u0447\u0438\u0441\u043b\u0435\u043d\u044b\u043d\u0430\u0433\u0440\u0430\u0434\u044b: &#FFB6C1%level%").replace("%level%", levelList).replace("%count%", String.valueOf(count)));
112+
});
113+
}
114+
115+
privatebooleanisOnline(UUIDuuid, StringplayerName) {
116+
Playerplayer = uuid != null ? Bukkit.getPlayer(uuid) : Bukkit.getPlayerExact(playerName);
117+
returnplayer != null && player.isOnline();
118+
}
119+
}

‎src/main/java/onevnl/ru/elytrya/commands/subcommands/DiscordSubCommand.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
importonevnl.ru.elytrya.models.PendingDiscordConfirm;
1111
importonevnl.ru.elytrya.models.PendingDiscordLink;
1212
importonevnl.ru.elytrya.util.DiscordNameValidator;
13+
importorg.bukkit.Bukkit;
1314
importorg.bukkit.command.CommandSender;
1415
importorg.bukkit.entity.Player;
1516

@@ -68,6 +69,22 @@ public void execute(CommandSender sender, String[] args) {
6869
return;
6970
}
7071

72+
Bukkit.getScheduler()
73+
.runTaskAsynchronously(
74+
client.getPlugin(),
75+
() -> startLinking(player, uuid, bot, args, msg)
76+
);
77+
}
78+
79+
privatevoidstartLinking(
80+
Playerplayer,
81+
UUIDuuid,
82+
DiscordBotManagerbot,
83+
String[] args,
84+
MessageManagermsg
85+
) {
86+
if (!player.isOnline()) return;
87+
7188
Stringlinked = client.getDatabase().getDiscordUser(uuid);
7289
if (linked != null && !linked.isEmpty()) {
7390
player.sendMessage(msg.getMessage("discord_already_linked"));

‎src/main/java/onevnl/ru/elytrya/commands/subcommands/InfoSubCommand.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
importonevnl.ru.elytrya.api.managers.DiscordBotManager;
66
importonevnl.ru.elytrya.api.managers.MessageManager;
77
importonevnl.ru.elytrya.models.BoostyUser;
8+
importorg.bukkit.Bukkit;
89
importorg.bukkit.command.CommandSender;
910
importorg.bukkit.entity.Player;
1011

@@ -35,8 +36,15 @@ public void execute(CommandSender sender, String[] args) {
3536
return;
3637
}
3738

39+
Bukkit.getScheduler()
40+
.runTaskAsynchronously(client.getPlugin(), () -> showInfo(player, msg));
41+
}
42+
43+
privatevoidshowInfo(Playerplayer, MessageManagermsg) {
3844
BoostyUseruser = client.getDatabase().getUser(player.getUniqueId());
3945

46+
if (!player.isOnline()) return;
47+
4048
if (user == null) {
4149
player.sendMessage(msg.getMessage("info_not_linked"));
4250
return;

‎src/main/java/onevnl/ru/elytrya/commands/subcommands/LinkSubCommand.java‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,15 @@ public void execute(CommandSender sender, String[] args) {
7575
"'"
7676
);
7777

78-
if (!validateLink(player, boostyName, msg)) {
79-
return;
80-
}
81-
82-
loadSubscriber(player, boostyName, msg);
78+
Bukkit.getScheduler()
79+
.runTaskAsynchronously(
80+
client.getPlugin(),
81+
() -> {
82+
if (!player.isOnline()) return;
83+
if (!validateLink(player, boostyName, msg)) return;
84+
loadSubscriber(player, boostyName, msg);
85+
}
86+
);
8387
}
8488

8589
privatebooleanvalidateLink(

0 commit comments

Comments
 (0)