Beta improvements and tests completed, this will be the initial non-beta release.

This commit is contained in:
2025-12-02 17:55:12 +01:00
parent 391bfe7d01
commit 0613270c4d
17 changed files with 455 additions and 65 deletions

View File

@@ -1,3 +1,92 @@
# BalSync
An open source Minecraft Economy Balance Synchronisation Plugin working with a MySQL/MariaDB Database and Vault
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!

View File

@@ -6,7 +6,7 @@
<groupId>com.user404_</groupId>
<artifactId>BalSync</artifactId>
<version>1.0-beta</version>
<version>1.0</version>
<packaging>jar</packaging>
<name>BalSync</name>

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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<UUID, Double> lastKnownBalances = new HashMap<>();
private final Map<UUID, Double> 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 (plugin.getConfigManager().isResetOnJoin()) {
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));
}
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<UUID> 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<UUID> getOnlinePlayerUUIDs() {
List<UUID> 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++;
// Cleanup on disable
public void shutdown() {
if (dbPollingTask != null) {
dbPollingTask.cancel();
}
} 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);
}
return plugin.getConfigManager().getStartingBalance();
lastKnownBalances.clear();
lastKnownDbBalances.clear();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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:

View File

@@ -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."

View File

@@ -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]"
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."

View File

@@ -7,3 +7,6 @@ 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]"
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."

View File

@@ -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."

View File

@@ -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."

View File

@@ -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."

View File

@@ -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."

View File

@@ -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Обнаружено и сохранено изменение баланса оффлайн."

View File

@@ -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