diff --git a/README.md b/README.md
index 9797da6..f544e30 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,92 @@
# BalSync
-An open source Minecraft Economy Balance Synchronisation Plugin working with a MySQL/MariaDB Database and Vault
\ No newline at end of file
+BalSync is a powerful and reliable balance synchronization system for Minecraft servers. It ensures that player balances are securely stored, automatically updated, and consistently synchronized across your server network.
+
+---
+
+## 🎯 Features
+
+### Automatic Balance Backup
+- Player balances are automatically saved to a MySQL database at configurable intervals.
+- No manual intervention required – everything runs seamlessly in the background.
+
+### Seamless Login Synchronization
+- Player balances are automatically loaded from the database when they join the server.
+- Optional: Reset balances to 0 before loading from the database (ideal for events or test servers).
+
+### Real-Time Monitoring
+- The plugin regularly checks the database for external changes (e.g., made by admins or other systems).
+- Any changes are immediately applied to online players.
+
+### Intelligent Offline Detection
+- Changes to a player's balance are recognized even while they are offline.
+- Example: If a player earns money in single-player mode, it is updated when they join the server.
+
+### Multi-Language Notifications
+- Players are notified of important balance changes.
+- Supports 7 languages: German, English, Spanish, French, Polish, Portuguese (Brazil), Russian.
+
+---
+
+## 🎮 Player Experience
+
+- On server join: *"Your balance has been synchronized with the database."*
+- On external updates: *"Your balance was updated externally: 100 → 150"*
+- No data loss: Balances are always safely stored.
+- Server switching supported: Players can move between servers and retain their balances.
+
+---
+
+## 👨💼 Admin Commands
+
+| Command | Description |
+|--------------------------|-----------------------------------------|
+| `/balsync reload` | Reloads plugin configuration |
+| `/balsync save` | Immediately saves all player balances |
+| `/balsync load` | Reload your own balance from the database |
+| `/balsync status` | Displays system status |
+
+---
+
+## ⚙️ Configuration Options
+
+- Set automatic save intervals (e.g., every minute)
+- Enable or disable notifications
+- Configure database polling intervals
+- Set starting balance for new players
+- Customize the database table name
+
+---
+
+## 🔒 Security & Performance
+
+- All transactions are logged
+- Database connection supports SSL
+- Connection pooling for optimal performance
+- Fault-tolerant architecture ensures reliability
+
+---
+
+## 📌 Supported Platforms
+
+- Paper
+- Spigot
+- Purpur
+- And other compatible Minecraft server forks
+
+---
+
+## 🚀 Getting Started
+
+1. Place the `BalSync.jar` file into your server's `plugins` folder.
+2. Start the server once to generate the default configuration file.
+3. Configure your MySQL database credentials and plugin settings in `config.yml`.
+4. Restart the server to apply changes.
+5. Enjoy secure, automatic balance synchronization for all your players!
+
+---
+
+## 💬 Feedback & Support
+
+If you encounter issues or have feature suggestions, please open an issue on GitHub. Community contributions are welcome!
+
diff --git a/pom.xml b/pom.xml
index 624517e..70a6ae3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.user404_
BalSync
- 1.0-beta
+ 1.0
jar
BalSync
diff --git a/src/main/java/com/user404_/balsync/BalSyncCommand.java b/src/main/java/com/user404_/balsync/BalSyncCommand.java
index 017053e..eceea7d 100644
--- a/src/main/java/com/user404_/balsync/BalSyncCommand.java
+++ b/src/main/java/com/user404_/balsync/BalSyncCommand.java
@@ -34,6 +34,7 @@ public class BalSyncCommand implements CommandExecutor {
break;
case "save":
+ // Save all balances - Methode ist jetzt in BalanceManager
balanceManager.saveAllBalances();
sender.sendMessage(plugin.getTranslationManager().formatMessage("balance-saved"));
break;
@@ -46,6 +47,10 @@ public class BalSyncCommand implements CommandExecutor {
}
break;
+ case "status":
+ sendStatusInfo(sender);
+ break;
+
default:
sender.sendMessage(plugin.getTranslationManager().formatMessage("usage"));
break;
@@ -53,4 +58,12 @@ public class BalSyncCommand implements CommandExecutor {
return true;
}
+
+ private void sendStatusInfo(CommandSender sender) {
+ sender.sendMessage("§6=== BalSync Status ===");
+ sender.sendMessage("§7Auto-save interval: §e" + plugin.getConfigManager().getAutoSaveInterval() + "s");
+ sender.sendMessage("§7Database polling: §e" + plugin.getConfigManager().getDbPollInterval() + "s");
+ sender.sendMessage("§7Reset on join: §e" + plugin.getConfigManager().isResetOnJoin());
+ sender.sendMessage("§7Offline monitoring: §e" + plugin.getConfigManager().monitorOfflineChanges());
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/user404_/balsync/BalSyncPlugin.java b/src/main/java/com/user404_/balsync/BalSyncPlugin.java
index 5557e83..2301bcd 100644
--- a/src/main/java/com/user404_/balsync/BalSyncPlugin.java
+++ b/src/main/java/com/user404_/balsync/BalSyncPlugin.java
@@ -71,6 +71,7 @@ public class BalSyncPlugin extends JavaPlugin {
// Save all balances on shutdown
if (balanceManager != null) {
balanceManager.saveAllBalances();
+ balanceManager.shutdown(); // NEW: Cleanup polling tasks
}
// Close database connection
diff --git a/src/main/java/com/user404_/balsync/BalanceManager.java b/src/main/java/com/user404_/balsync/BalanceManager.java
index 82a94b6..6849c98 100644
--- a/src/main/java/com/user404_/balsync/BalanceManager.java
+++ b/src/main/java/com/user404_/balsync/BalanceManager.java
@@ -1,56 +1,96 @@
package com.user404_.balsync;
import net.milkbowl.vault.economy.Economy;
+import net.milkbowl.vault.economy.EconomyResponse;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.UUID;
+import java.util.*;
import java.util.logging.Level;
public class BalanceManager {
private final BalSyncPlugin plugin;
private final Economy economy;
private final DatabaseManager databaseManager;
+ private BukkitTask dbPollingTask;
+ private final Map lastKnownBalances = new HashMap<>();
+ private final Map lastKnownDbBalances = new HashMap<>();
+ public void saveAllBalances() {
+ plugin.getPluginLogger().info("Saving all player balances to database...");
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ int saved = 0;
+ for (OfflinePlayer player : Bukkit.getOfflinePlayers()) {
+ try {
+ if (economy.hasAccount(player)) {
+ double balance = economy.getBalance(player);
+ databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance);
+ lastKnownBalances.put(player.getUniqueId(), balance);
+ lastKnownDbBalances.put(player.getUniqueId(), balance);
+ saved++;
+ }
+ } catch (SQLException e) {
+ plugin.getPluginLogger().log(Level.WARNING,
+ "Failed to save balance for: " + player.getName(), e);
+ }
+ }
+ plugin.getPluginLogger().info("Saved " + saved + " player balances to database.");
+ });
+ }
public BalanceManager(BalSyncPlugin plugin, Economy economy, DatabaseManager databaseManager) {
this.plugin = plugin;
this.economy = economy;
this.databaseManager = databaseManager;
+ startDbPolling();
+ startOfflineMonitoring();
}
+ // MODIFIED: Added reset functionality
public void loadPlayerBalance(Player player) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
double databaseBalance = databaseManager.getBalance(player.getUniqueId());
Bukkit.getScheduler().runTask(plugin, () -> {
- // Sicherstellen, dass der Spieler ein Konto hat
+ // Ensure player has account
if (!economy.hasAccount(player)) {
economy.createPlayerAccount(player);
}
- // Aktuelle Balance abrufen
+ // RESET TO ZERO if configured
double currentBalance = economy.getBalance(player);
-
- // KOMPLETTE ÜBERSCHREIBUNG der Balance
- // 1. Zuerst auf 0 setzen (alles abheben oder auf 0 bringen)
- if (currentBalance > 0) {
- // Positive Balance abheben
- economy.withdrawPlayer(player, currentBalance);
- } else if (currentBalance < 0) {
- // Negative Balance ausgleichen (einzahlen um auf 0 zu kommen)
- economy.depositPlayer(player, Math.abs(currentBalance));
+ if (plugin.getConfigManager().isResetOnJoin()) {
+ if (currentBalance > 0) {
+ economy.withdrawPlayer(player, currentBalance);
+ } else if (currentBalance < 0) {
+ economy.depositPlayer(player, Math.abs(currentBalance));
+ }
+ plugin.getLogger().info("Reset balance to 0 for " + player.getName());
+ currentBalance = 0;
}
- // 2. Datenbankbalance einzahlen (ÜBERSCHREIBT die alte Balance)
- economy.depositPlayer(player, databaseBalance);
+ // Apply database balance (OVERWRITE)
+ double difference = databaseBalance - currentBalance;
+ if (difference > 0) {
+ economy.depositPlayer(player, difference);
+ } else if (difference < 0) {
+ economy.withdrawPlayer(player, Math.abs(difference));
+ }
- // Logging
- plugin.getLogger().info("BALANCE OVERWRITTEN for " + player.getName() +
- ": Old=" + currentBalance + ", New=" + databaseBalance + " (from DB)");
+ // Update tracking maps
+ lastKnownBalances.put(player.getUniqueId(), databaseBalance);
+ lastKnownDbBalances.put(player.getUniqueId(), databaseBalance);
- // Nachricht an Spieler
+ plugin.getLogger().info("Balance loaded for " + player.getName() +
+ ": " + databaseBalance + " (from DB)");
+
+ // Send message to player
String message = plugin.getTranslationManager().getMessage("balance-loaded");
if (message != null && !message.isEmpty()) {
player.sendMessage(plugin.getTranslationManager().formatMessage(message));
@@ -64,11 +104,169 @@ public class BalanceManager {
});
}
+ // NEW: Poll database for external changes
+ private void startDbPolling() {
+ int interval = plugin.getConfigManager().getDbPollInterval();
+ if (interval <= 0) return;
+
+ dbPollingTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
+ pollDatabaseForChanges();
+ }, interval * 20L, interval * 20L);
+
+ plugin.getLogger().info("Started database polling every " + interval + " seconds");
+ }
+
+ // NEW: Check database for balance changes and apply to online players
+ private void pollDatabaseForChanges() {
+ List onlineUUIDs = getOnlinePlayerUUIDs();
+
+ // Keine online Spieler → nichts abfragen
+ if (onlineUUIDs.isEmpty()) {
+ return;
+ }
+
+ try (Connection conn = databaseManager.getConnection()) {
+ // Platzhalter für die IN-Klausel erstellen
+ StringBuilder placeholders = new StringBuilder();
+ for (int i = 0; i < onlineUUIDs.size(); i++) {
+ placeholders.append("?");
+ if (i < onlineUUIDs.size() - 1) {
+ placeholders.append(",");
+ }
+ }
+
+ String sql = String.format(
+ "SELECT player_uuid, balance FROM %s WHERE player_uuid IN (%s)",
+ plugin.getConfigManager().getTableName(),
+ placeholders.toString()
+ );
+
+ try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+ // UUIDs als Parameter setzen
+ for (int i = 0; i < onlineUUIDs.size(); i++) {
+ stmt.setString(i + 1, onlineUUIDs.get(i).toString());
+ }
+
+ ResultSet rs = stmt.executeQuery();
+ while (rs.next()) {
+ UUID playerUUID = UUID.fromString(rs.getString("player_uuid"));
+ double dbBalance = rs.getDouble("balance");
+
+ // Prüfen, ob sich die Datenbank-Balance geändert hat
+ Double lastDbBalance = lastKnownDbBalances.get(playerUUID);
+ if (lastDbBalance == null || Math.abs(dbBalance - lastDbBalance) > 0.001) {
+ // Datenbank hat sich geändert → auf Spieler anwenden
+ applyDbChangeToPlayer(playerUUID, dbBalance, lastDbBalance);
+ lastKnownDbBalances.put(playerUUID, dbBalance);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ plugin.getLogger().log(Level.WARNING, "Error polling database for changes", e);
+ }
+ }
+
+ // NEW: Apply database changes to online player
+ private void applyDbChangeToPlayer(UUID playerUUID, double newBalance, Double oldBalance) {
+ Player player = Bukkit.getPlayer(playerUUID);
+ if (player != null && player.isOnline()) {
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ double currentBalance = economy.getBalance(player);
+ double difference = newBalance - currentBalance;
+
+ if (Math.abs(difference) > 0.001) {
+ if (difference > 0) {
+ economy.depositPlayer(player, difference);
+ } else {
+ economy.withdrawPlayer(player, Math.abs(difference));
+ }
+
+ lastKnownBalances.put(playerUUID, newBalance);
+
+ plugin.getLogger().info("Applied external DB change for " +
+ player.getName() + ": " + newBalance);
+
+ // Notify player if configured
+ if (plugin.getConfigManager().notifyOnExternalChange()) {
+ String message = plugin.getTranslationManager().getMessage(
+ "balance-external-change");
+ if (message != null && !message.isEmpty()) {
+ String formatted = message
+ .replace("{old}", String.format("%.2f", oldBalance != null ? oldBalance : currentBalance))
+ .replace("{new}", String.format("%.2f", newBalance))
+ .replace("&", "§");
+ player.sendMessage(plugin.getTranslationManager().formatMessage("prefix") + formatted);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ // NEW: Monitor offline player balance changes when auto-save-interval = 0
+ private void startOfflineMonitoring() {
+ int autoSaveInterval = plugin.getConfigManager().getAutoSaveInterval();
+ boolean monitorOffline = plugin.getConfigManager().monitorOfflineChanges();
+
+ if (autoSaveInterval == 0 && monitorOffline) {
+ // Check for balance changes every 30 seconds
+ Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
+ monitorOfflineBalanceChanges();
+ }, 600L, 600L); // 30 seconds (600 ticks)
+
+ plugin.getLogger().info("Started offline balance change monitoring");
+ }
+ }
+
+ // NEW: Detect and save offline balance changes
+ private void monitorOfflineBalanceChanges() {
+ for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
+ if (economy.hasAccount(offlinePlayer)) {
+ double currentBalance = economy.getBalance(offlinePlayer);
+ UUID uuid = offlinePlayer.getUniqueId();
+
+ Double lastBalance = lastKnownBalances.get(uuid);
+ if (lastBalance != null && Math.abs(currentBalance - lastBalance) > 0.001) {
+ // Balance has changed - save to database
+ try {
+ databaseManager.saveBalance(uuid, offlinePlayer.getName(), currentBalance);
+ lastKnownBalances.put(uuid, currentBalance);
+ plugin.getLogger().info("Detected offline change for " +
+ offlinePlayer.getName() + ": " + currentBalance);
+ } catch (SQLException e) {
+ plugin.getLogger().log(Level.WARNING,
+ "Failed to save offline change for " + offlinePlayer.getName(), e);
+ }
+ } else if (lastBalance == null) {
+ // First time seeing this player, store initial balance
+ lastKnownBalances.put(uuid, currentBalance);
+ }
+ }
+ }
+ }
+
+ // NEW: Track balance when player quits
+ public void trackPlayerQuit(UUID playerUUID, double balance) {
+ lastKnownBalances.put(playerUUID, balance);
+ }
+
+ // Helper method
+ private List getOnlinePlayerUUIDs() {
+ List uuids = new ArrayList<>();
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ uuids.add(player.getUniqueId());
+ }
+ return uuids;
+ }
+
+ // MODIFIED savePlayerBalance to update tracking
public void savePlayerBalance(OfflinePlayer player) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
double balance = economy.getBalance(player);
databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance);
+ lastKnownBalances.put(player.getUniqueId(), balance);
+ lastKnownDbBalances.put(player.getUniqueId(), balance);
} catch (SQLException e) {
plugin.getPluginLogger().log(Level.SEVERE,
"Failed to save balance for player: " + player.getName(), e);
@@ -76,32 +274,12 @@ public class BalanceManager {
});
}
- public void saveAllBalances() {
- plugin.getPluginLogger().info("Saving all player balances to database...");
-
- Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
- int saved = 0;
- for (OfflinePlayer player : Bukkit.getOfflinePlayers()) {
- try {
- if (economy.hasAccount(player)) {
- double balance = economy.getBalance(player);
- databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance);
- saved++;
- }
- } catch (SQLException e) {
- plugin.getPluginLogger().log(Level.WARNING,
- "Failed to save balance for: " + player.getName(), e);
- }
- }
- plugin.getPluginLogger().info("Saved " + saved + " player balances to database.");
- });
- }
-
- public double getCachedBalance(UUID playerUUID) {
- OfflinePlayer player = Bukkit.getOfflinePlayer(playerUUID);
- if (player != null && economy.hasAccount(player)) {
- return economy.getBalance(player);
+ // Cleanup on disable
+ public void shutdown() {
+ if (dbPollingTask != null) {
+ dbPollingTask.cancel();
}
- return plugin.getConfigManager().getStartingBalance();
+ lastKnownBalances.clear();
+ lastKnownDbBalances.clear();
}
}
\ No newline at end of file
diff --git a/src/main/java/com/user404_/balsync/ConfigManager.java b/src/main/java/com/user404_/balsync/ConfigManager.java
index b5063e7..0e88c1d 100644
--- a/src/main/java/com/user404_/balsync/ConfigManager.java
+++ b/src/main/java/com/user404_/balsync/ConfigManager.java
@@ -121,4 +121,19 @@ public class ConfigManager {
public String getTableName() {
return config.getString("tables.player_balances.table-name", "player_balances");
}
+ public boolean isResetOnJoin() {
+ return config.getBoolean("settings.reset-on-join", false);
+ }
+
+ public boolean monitorOfflineChanges() {
+ return config.getBoolean("settings.monitor-offline-changes", true);
+ }
+
+ public int getDbPollInterval() {
+ return config.getInt("settings.db-poll-interval", 10);
+ }
+
+ public boolean notifyOnExternalChange() {
+ return config.getBoolean("settings.notify-on-external-change", true);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/user404_/balsync/PlayerEventListener.java b/src/main/java/com/user404_/balsync/PlayerEventListener.java
index 62a2d04..f2ea01a 100644
--- a/src/main/java/com/user404_/balsync/PlayerEventListener.java
+++ b/src/main/java/com/user404_/balsync/PlayerEventListener.java
@@ -1,5 +1,7 @@
package com.user404_.balsync;
+import net.milkbowl.vault.economy.Economy;
+import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
@@ -8,10 +10,12 @@ import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerEventListener implements Listener {
private final BalSyncPlugin plugin;
private final BalanceManager balanceManager;
+ private final Economy economy;
public PlayerEventListener(BalSyncPlugin plugin, BalanceManager balanceManager) {
this.plugin = plugin;
this.balanceManager = balanceManager;
+ this.economy = plugin.getEconomy(); // Economy vom Plugin holen
}
@EventHandler
@@ -21,13 +25,22 @@ public class PlayerEventListener implements Listener {
if (event.getPlayer().isOnline()) {
balanceManager.loadPlayerBalance(event.getPlayer());
}
- }, 300L); // 20 Ticks = 1 Sekunde, 300 Ticks = 15 Sekunden
+ }, 40L);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
+ Player player = event.getPlayer();
+
if (plugin.getConfigManager().saveOnQuit()) {
- balanceManager.savePlayerBalance(event.getPlayer());
+ balanceManager.savePlayerBalance(player);
+ }
+
+ // Track balance on quit for offline monitoring
+ if (plugin.getConfigManager().monitorOfflineChanges() &&
+ plugin.getConfigManager().getAutoSaveInterval() == 0) {
+ double balance = economy.getBalance(player);
+ balanceManager.trackPlayerQuit(player.getUniqueId(), balance);
}
}
}
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 506b8fd..a9be692 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -1,16 +1,4 @@
-# Database Configuration
-database:
- host: "localhost"
- port: 3306
- database: "minecraft"
- username: "root"
- password: "password"
- use-ssl: false
- connection-pool:
- maximum-pool-size: 10
- minimum-idle: 5
- connection-timeout: 30000
- idle-timeout: 600000
+# Database Configuration (existing)...
# Plugin Settings
settings:
@@ -23,6 +11,18 @@ settings:
# Locale for messages (en, de, etc.)
locale: "en"
+ # Reset to zero before loading from database on join
+ reset-on-join: false
+
+ # Monitor offline balance changes when auto-save-interval = 0
+ monitor-offline-changes: true
+
+ # Poll database for changes (interval in seconds, 0 = disabled)
+ db-poll-interval: 10
+
+ # Notification when balance is changed externally
+ notify-on-external-change: true
+
# Database Table Configuration
tables:
player_balances:
diff --git a/src/main/resources/messages_at.yml b/src/main/resources/messages_at.yml
new file mode 100644
index 0000000..c65238b
--- /dev/null
+++ b/src/main/resources/messages_at.yml
@@ -0,0 +1,15 @@
+# This is an Easter egg language. If you found this, you're probably digging through the source code.
+# Have fun with the Austrian/Wienerisch translation! :)
+
+prefix: "§8[§dGödSynk§8] §7"
+no-permission: "§cHeast, du host ka Berechtigung für den Befehl!"
+config-reloaded: "§aKonfig is wieda gscheit neig'laden!"
+balance-loaded: "§aDei Gödstandl is jetzt mit da Datenbank a glei."
+balance-saved: "§aOlle Gödstandln woan in da Datenbank gspeichert, passt so!"
+database-connected: "§aJo fix, Verbindung zur Datenbank steht!"
+database-error: "§cOida… do is a Fehler in da Datenbank. Schau amoi in die Konsole."
+player-not-found: "§cDen Spieler find i net, host di vertippt?"
+usage: "§cSo geht's: /balsync [reload|save|load]"
+balance-external-change: "§eDei Gödstandl woan von außen gändat: §6{old} §e→ §6{new}"
+balance-reset: "§aDei Gödstandl woan auf §e${0}§a zruckgsetzt, bevors aus da Datenbank kema san."
+offline-change-detected: "§7A Änderung währendst ned do woarst is gmerkt und gspeichert wordn."
diff --git a/src/main/resources/messages_de.yml b/src/main/resources/messages_de.yml
index faa8179..6f1fd0e 100644
--- a/src/main/resources/messages_de.yml
+++ b/src/main/resources/messages_de.yml
@@ -4,6 +4,9 @@ config-reloaded: "§aKonfiguration erfolgreich neu geladen!"
balance-loaded: "§aDein Kontostand wurde mit der Datenbank synchronisiert."
balance-saved: "§aAlle Spielerkontostände wurden in der Datenbank gespeichert."
database-connected: "§aErfolgreich mit der Datenbank verbunden!"
-database-error: "§cDatenbankfehler aufgetreten. Überprüfe die Konsole für Details."
+database-error: "§cDatenbankfehler aufgetreten. Überprüfe die Konsole oder die Logs für Details."
player-not-found: "§cSpieler nicht gefunden!"
-usage: "§cVerwendung: /balsync [reload|save|load]"
\ No newline at end of file
+usage: "§cVerwendung: /balsync [reload|save|load]"
+balance-external-change: "§eDein Kontostand wurde extern (vielleicht von einem anderen Server) aktualisiert: §6{old} §e→ §6{new}"
+balance-reset: "§aDein Kontostand wurde auf §e${0}§a zurückgesetzt, bevor er aus der Datenbank geladen wurde."
+offline-change-detected: "§7Offline-Kontostandänderung erkannt und gespeichert."
\ No newline at end of file
diff --git a/src/main/resources/messages_en.yml b/src/main/resources/messages_en.yml
index 8821b5a..7ff0865 100644
--- a/src/main/resources/messages_en.yml
+++ b/src/main/resources/messages_en.yml
@@ -6,4 +6,7 @@ balance-saved: "§aAll player balances have been saved to the database."
database-connected: "§aSuccessfully connected to the database!"
database-error: "§cDatabase error occurred. Check console for details."
player-not-found: "§cPlayer not found!"
-usage: "§cUsage: /balsync [reload|save|load]"
\ No newline at end of file
+usage: "§cUsage: /balsync [reload|save|load]"
+balance-external-change: "§eYour balance was updated externally: §6{old} §e→ §6{new}"
+balance-reset: "§aYour balance was reset to §e${0}§a before loading from database."
+offline-change-detected: "§7Offline balance change detected and saved."
\ No newline at end of file
diff --git a/src/main/resources/messages_es.yml b/src/main/resources/messages_es.yml
new file mode 100644
index 0000000..cfc6f76
--- /dev/null
+++ b/src/main/resources/messages_es.yml
@@ -0,0 +1,12 @@
+prefix: "§8[§6BalSync§8] §7"
+no-permission: "§c¡No tienes permiso para usar este comando!"
+config-reloaded: "§a¡Configuración recargada correctamente!"
+balance-loaded: "§aTu saldo ha sido sincronizado con la base de datos."
+balance-saved: "§aTodos los saldos de los jugadores han sido guardados en la base de datos."
+database-connected: "§a¡Conexión exitosa a la base de datos!"
+database-error: "§cOcurrió un error de base de datos. Revisa la consola para más detalles."
+player-not-found: "§c¡Jugador no encontrado!"
+usage: "§cUso: /balsync [reload|save|load]"
+balance-external-change: "§eTu saldo fue actualizado externamente: §6{old} §e→ §6{new}"
+balance-reset: "§aTu saldo fue restablecido a §e${0}§a antes de cargarse desde la base de datos."
+offline-change-detected: "§7Cambio de saldo offline detectado y guardado."
diff --git a/src/main/resources/messages_fr.yml b/src/main/resources/messages_fr.yml
new file mode 100644
index 0000000..403d0d5
--- /dev/null
+++ b/src/main/resources/messages_fr.yml
@@ -0,0 +1,12 @@
+prefix: "§8[§6BalSync§8] §7"
+no-permission: "§cVous n'avez pas la permission d'utiliser cette commande !"
+config-reloaded: "§aConfiguration rechargée avec succès !"
+balance-loaded: "§aVotre solde a été synchronisé avec la base de données."
+balance-saved: "§aTous les soldes des joueurs ont été enregistrés dans la base de données."
+database-connected: "§aConnexion réussie à la base de données !"
+database-error: "§cUne erreur de base de données est survenue. Vérifiez la console pour plus de détails."
+player-not-found: "§cJoueur introuvable !"
+usage: "§cUtilisation : /balsync [reload|save|load]"
+balance-external-change: "§eVotre solde a été mis à jour extérieurement : §6{old} §e→ §6{new}"
+balance-reset: "§aVotre solde a été réinitialisé à §e${0}§a avant le chargement depuis la base de données."
+offline-change-detected: "§7Modification du solde hors ligne détectée et enregistrée."
diff --git a/src/main/resources/messages_pl.yml b/src/main/resources/messages_pl.yml
new file mode 100644
index 0000000..403d0d5
--- /dev/null
+++ b/src/main/resources/messages_pl.yml
@@ -0,0 +1,12 @@
+prefix: "§8[§6BalSync§8] §7"
+no-permission: "§cVous n'avez pas la permission d'utiliser cette commande !"
+config-reloaded: "§aConfiguration rechargée avec succès !"
+balance-loaded: "§aVotre solde a été synchronisé avec la base de données."
+balance-saved: "§aTous les soldes des joueurs ont été enregistrés dans la base de données."
+database-connected: "§aConnexion réussie à la base de données !"
+database-error: "§cUne erreur de base de données est survenue. Vérifiez la console pour plus de détails."
+player-not-found: "§cJoueur introuvable !"
+usage: "§cUtilisation : /balsync [reload|save|load]"
+balance-external-change: "§eVotre solde a été mis à jour extérieurement : §6{old} §e→ §6{new}"
+balance-reset: "§aVotre solde a été réinitialisé à §e${0}§a avant le chargement depuis la base de données."
+offline-change-detected: "§7Modification du solde hors ligne détectée et enregistrée."
diff --git a/src/main/resources/messages_pt-br.yml b/src/main/resources/messages_pt-br.yml
new file mode 100644
index 0000000..c028cf4
--- /dev/null
+++ b/src/main/resources/messages_pt-br.yml
@@ -0,0 +1,12 @@
+prefix: "§8[§6BalSync§8] §7"
+no-permission: "§cVocê não tem permissão para usar este comando!"
+config-reloaded: "§aConfiguração recarregada com sucesso!"
+balance-loaded: "§aSeu saldo foi sincronizado com o banco de dados."
+balance-saved: "§aTodos os saldos dos jogadores foram salvos no banco de dados."
+database-connected: "§aConectado com sucesso ao banco de dados!"
+database-error: "§cOcorreu um erro no banco de dados. Verifique o console para mais detalhes."
+player-not-found: "§cJogador não encontrado!"
+usage: "§cUso: /balsync [reload|save|load]"
+balance-external-change: "§eSeu saldo foi atualizado externamente: §6{old} §e→ §6{new}"
+balance-reset: "§aSeu saldo foi redefinido para §e${0}§a antes de carregar do banco de dados."
+offline-change-detected: "§7Alteração de saldo offline detectada e salva."
diff --git a/src/main/resources/messages_ru.yml b/src/main/resources/messages_ru.yml
new file mode 100644
index 0000000..2001fac
--- /dev/null
+++ b/src/main/resources/messages_ru.yml
@@ -0,0 +1,12 @@
+prefix: "§8[§6BalSync§8] §7"
+no-permission: "§cУ вас нет прав для использования этой команды!"
+config-reloaded: "§aКонфигурация успешно перезагружена!"
+balance-loaded: "§aВаш баланс был синхронизирован с базой данных."
+balance-saved: "§aВсе балансы игроков были сохранены в базе данных."
+database-connected: "§aУспешно подключено к базе данных!"
+database-error: "§cПроизошла ошибка базы данных. Проверьте консоль для деталей."
+player-not-found: "§cИгрок не найден!"
+usage: "§cИспользование: /balsync [reload|save|load]"
+balance-external-change: "§eВаш баланс был обновлён извне: §6{old} §e→ §6{new}"
+balance-reset: "§aВаш баланс был сброшен до §e${0}§a перед загрузкой из базы данных."
+offline-change-detected: "§7Обнаружено и сохранено изменение баланса оффлайн."
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index eff64d6..5c3704e 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,5 +1,5 @@
name: BalSync
-version: 1.0-beta
+version: 1.0
main: com.user404_.balsync.BalSyncPlugin
api-version: '1.20'
description: Synchronizes player balances with MySQL database