diff --git a/src/main/java/org/pearlbot/PearlBotMessages.java b/src/main/java/org/pearlbot/PearlBotMessages.java
new file mode 100644
index 0000000..0977336
--- /dev/null
+++ b/src/main/java/org/pearlbot/PearlBotMessages.java
@@ -0,0 +1,71 @@
+/*
+ * PearlBot — a ZenithProxy plugin for on-demand stasis chamber pulls.
+ * Copyright (C) 2026 Leonetic
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.pearlbot;
+
+public class PearlBotMessages {
+
+ // --- In-game whispers ---
+
+ public String noPearlFound = "No pearl found for you.";
+
+ // {remaining} = e.g. "2 pearls" or "1 pearl"
+ public String pulled = "Pulled. You have {remaining} left.";
+
+ // {count} = current pearl count, {max} = configured max
+ public String maxPearlsExceeded = "You have {count} pearl(s) but the max is {max}! Automatically pulling your oldest pearl.";
+
+ // {count} = e.g. "2" or "2/3" when a max is set
+ public String pearlCount = "You have {count} pearl(s) stasised.";
+
+ // {timeout} = seconds configured for pull timeout
+ public String pullTimedOut = "Positioning timed out after {timeout}s.";
+
+ // {timeout} = seconds configured for owner-online timeout
+ public String ownerTimedOut = "Expired - you did not log on within {timeout}s.";
+
+ public String authUsage = "Usage: !auth - get a code by typing !auth in Discord first.";
+ public String authInvalidCode = "Invalid or expired code.";
+
+ // {discordUsername} = the Discord username that was linked
+ public String authLinked = "Linked to Discord {discordUsername}.";
+
+ // --- Discord messages (the @mention is prepended automatically) ---
+
+ public String discordNoAccountsLinked = "No MC accounts linked. Type `!auth` to link one.";
+
+ // {accounts} = comma-separated account names, {trigger} = e.g. "!warp"
+ public String discordMultipleAccounts = "Accounts linked: {accounts}. Please type `{trigger} ` to pull a specific account.";
+
+ // {name} = the username that wasn't found, {accounts} = comma-separated linked account names
+ public String discordAccountNotFound = "No linked account named `{name}`. Accounts linked: {accounts}.";
+
+ // {code} = auth code, {ttl} = minutes until expiry
+ public String discordAuthCode = "Whisper me `!auth {code}` in-game from each MC account you want to link. Expires in {ttl} minutes.";
+
+ // {mcUsername} = the MC account that was linked
+ public String discordAuthLinked = "Linked MC account `{mcUsername}`.";
+
+ // Replaces {key} placeholders — pass alternating key, value pairs
+ public String format(String template, Object... keyValues) {
+ String result = template;
+ for (int i = 0; i + 1 < keyValues.length; i += 2) {
+ result = result.replace("{" + keyValues[i] + "}", String.valueOf(keyValues[i + 1]));
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/pearlbot/PearlBotPlugin.java b/src/main/java/org/pearlbot/PearlBotPlugin.java
index ad3dcdb..e06ef17 100644
--- a/src/main/java/org/pearlbot/PearlBotPlugin.java
+++ b/src/main/java/org/pearlbot/PearlBotPlugin.java
@@ -36,6 +36,7 @@
public class PearlBotPlugin implements ZenithProxyPlugin {
public static PluginAPI API;
public static PearlBotConfig PLUGIN_CONFIG;
+ public static PearlBotMessages PLUGIN_MESSAGES;
public static ComponentLogger LOG;
@Override
@@ -44,6 +45,7 @@ public void onLoad(PluginAPI pluginAPI) {
LOG = pluginAPI.getLogger();
LOG.info("PearlBot loading...");
PLUGIN_CONFIG = API.registerConfig(BuildConstants.PLUGIN_ID, PearlBotConfig.class);
+ PLUGIN_MESSAGES = API.registerConfig(BuildConstants.PLUGIN_ID + "-messages", PearlBotMessages.class);
API.registerModule(new EnderPearlTrackerModule());
API.registerModule(new AutoPearlModule());
API.registerCommand(new PearlBotCommand());
diff --git a/src/main/java/org/pearlbot/module/AutoPearlModule.java b/src/main/java/org/pearlbot/module/AutoPearlModule.java
index 8fff82f..d6f1103 100644
--- a/src/main/java/org/pearlbot/module/AutoPearlModule.java
+++ b/src/main/java/org/pearlbot/module/AutoPearlModule.java
@@ -46,6 +46,7 @@
import static com.zenith.Globals.CONFIG;
import static com.zenith.Globals.DISCORD;
import static org.pearlbot.PearlBotPlugin.PLUGIN_CONFIG;
+import static org.pearlbot.PearlBotPlugin.PLUGIN_MESSAGES;
public class AutoPearlModule extends Module {
private static final long PULL_RETRY_INTERVAL_MS = 1_000L;
@@ -138,7 +139,7 @@ private void onWhisper(WhisperChatEvent event) {
.count();
int max = PLUGIN_CONFIG.maxChambersPerPlayer;
String countStr = max > 0 ? count + "/" + max : String.valueOf(count);
- sendWhisper(name, "You have " + countStr + " pearl(s) set.");
+ sendWhisper(name, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.pearlCount, "count", countStr));
return;
}
@@ -150,7 +151,7 @@ private void onWhisper(WhisperChatEvent event) {
PearlBotConfig.StasisChamber chamber = findChamberFor(uuid);
if (chamber == null) {
- sendWhisper(name, "No pearl found for you.");
+ sendWhisper(name, PLUGIN_MESSAGES.noPearlFound);
return;
}
@@ -159,19 +160,19 @@ private void onWhisper(WhisperChatEvent event) {
private void handleAuthWhisper(UUID mcUuid, String mcUsername, String code) {
if (code.isBlank()) {
- sendWhisper(mcUsername, "Usage: !auth - get a code with !auth in Discord first.");
+ sendWhisper(mcUsername, PLUGIN_MESSAGES.authUsage);
return;
}
purgeExpiredAuthCodes();
PendingAuth pending = pendingAuthCodes.remove(code.toUpperCase());
if (pending == null) {
- sendWhisper(mcUsername, "Invalid or expired code.");
+ sendWhisper(mcUsername, PLUGIN_MESSAGES.authInvalidCode);
return;
}
PLUGIN_CONFIG.linkedAccounts.put(mcUuid, new PearlBotConfig.LinkedAccount(
pending.discordUserId, pending.discordUsername, mcUsername, System.currentTimeMillis()));
info("Linked MC {} ({}) to Discord {} ({})", mcUsername, mcUuid, pending.discordUsername, pending.discordUserId);
- sendWhisper(mcUsername, "Linked to Discord " + pending.discordUsername + ".");
+ sendWhisper(mcUsername, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.authLinked, "discordUsername", pending.discordUsername));
notifyAuthSuccess(pending.discordUserId, mcUsername);
}
@@ -184,7 +185,7 @@ private void notifyAuthSuccess(String discordUserId, String mcUsername) {
warn("Cannot send auth-success ping: channel {} not found", channelId);
return;
}
- channel.sendMessage("<@" + discordUserId + "> Linked MC account `" + mcUsername + "`.").queue();
+ channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordAuthLinked, "mcUsername", mcUsername)).queue();
}
private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
@@ -205,8 +206,7 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
if (firstWord.equals(DISCORD_AUTH_CMD)) {
String code = newAuthCode(discordUserId, discordUsername);
long ttl = (long) PLUGIN_CONFIG.discordTrigger.authCodeTtlMinutes;
- channel.sendMessage("<@" + discordUserId + "> Whisper me `!auth " + code
- + "` in-game from each MC account you want to link. Expires in " + ttl + " minutes.").queue();
+ channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordAuthCode, "code", code, "ttl", ttl)).queue();
return;
}
@@ -221,7 +221,7 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
.map(Map.Entry::getValue)
.toList();
if (linked.isEmpty()) {
- channel.sendMessage("<@" + discordUserId + "> No MC accounts linked. Type `!auth` to link one.").queue();
+ channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.discordNoAccountsLinked).queue();
return;
}
@@ -229,8 +229,7 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
String names = linked.stream()
.map(a -> a.mcUsername != null ? a.mcUsername : "?")
.collect(java.util.stream.Collectors.joining(", "));
- channel.sendMessage("<@" + discordUserId + "> Accounts linked: " + names
- + ". Please type `" + triggerWordDiscord() + " ` to pull a specific account.").queue();
+ channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordMultipleAccounts, "accounts", names, "trigger", triggerWordDiscord())).queue();
return;
}
@@ -258,8 +257,7 @@ private void onDiscordMessage(MessageReceivedEvent jdaEvent) {
String names = linked.stream()
.map(a -> a.mcUsername != null ? a.mcUsername : "?")
.collect(java.util.stream.Collectors.joining(", "));
- channel.sendMessage("<@" + discordUserId + "> No linked account named `" + targetName
- + "`. Accounts linked: " + names + ".").queue();
+ channel.sendMessage("<@" + discordUserId + "> " + PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.discordAccountNotFound, "name", targetName, "accounts", names)).queue();
return;
}
@@ -326,8 +324,7 @@ public void checkAndEnforceMaxChambers(UUID ownerUuid) {
PearlBotConfig.StasisChamber chamber = findChamberFor(ownerUuid);
if (chamber == null) return;
if (name != null) {
- sendWhisper(name, "You have " + count + " pearl(s) but the max is " + max
- + "! Pulling your oldest pearl.");
+ sendWhisper(name, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.maxPearlsExceeded, "count", count, "max", max));
}
enqueuePull(ownerUuid, name, chamber);
}
@@ -387,7 +384,7 @@ private void tickPending() {
if (timeoutMs > 0 && now - activePullStartMs > timeoutMs) {
warn("Positioning for {} timed out after {}s; cancelling",
labelOf(activePull), PLUGIN_CONFIG.pullTimeoutSeconds);
- abortActivePull("Positioning timed out after " + PLUGIN_CONFIG.pullTimeoutSeconds + "s.");
+ abortActivePull(PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.pullTimedOut, "timeout", PLUGIN_CONFIG.pullTimeoutSeconds));
return;
}
} else if (isOwnerOnline(activePull.ownerUuid)) {
@@ -397,8 +394,7 @@ private void tickPending() {
if (waitMs > 0 && now - readyAtMs > waitMs) {
warn("{} did not come online within {}s; expiring pull",
labelOf(activePull), PLUGIN_CONFIG.waitForOwnerSeconds);
- abortActivePull("Expired - you did not log on within "
- + PLUGIN_CONFIG.waitForOwnerSeconds + "s.");
+ abortActivePull(PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.ownerTimedOut, "timeout", PLUGIN_CONFIG.waitForOwnerSeconds));
return;
}
}
@@ -540,7 +536,7 @@ private void fireClick() {
int remaining = Math.max(0, remainingPearlsFor(pull.ownerUuid) - 1);
if (pull.ownerName != null) {
String tail = remaining == 1 ? "1 pearl" : remaining + " pearls";
- sendWhisper(pull.ownerName, "Pulled. You have " + tail + " left.");
+ sendWhisper(pull.ownerName, PLUGIN_MESSAGES.format(PLUGIN_MESSAGES.pulled, "remaining", tail));
}
if (PLUGIN_CONFIG.idleGoal.enabled) {