dev #2

Merged
deutschich merged 4 commits from dev into main 2025-12-04 17:31:07 +01:00
7 changed files with 87 additions and 116 deletions

29
LICENSE
View File

@@ -1,21 +1,18 @@
MIT License
Copyright (c) 2025 D.L.
Copyright (c) 2025 deutschich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ public class BalanceManager {
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...");
@@ -43,6 +44,7 @@ public class BalanceManager {
plugin.getPluginLogger().info("Saved " + saved + " player balances to database.");
});
}
public BalanceManager(BalSyncPlugin plugin, Economy economy, DatabaseManager databaseManager) {
this.plugin = plugin;
this.economy = economy;
@@ -227,18 +229,32 @@ public class BalanceManager {
Double lastBalance = lastKnownBalances.get(uuid);
if (lastBalance != null && Math.abs(currentBalance - lastBalance) > 0.001) {
// Balance has changed - save to database
// Balance has changed on this server. Instead of overwriting the DB with
// a stale server value, compute the delta and apply that to the DB.
double delta = currentBalance - lastBalance;
try {
databaseManager.saveBalance(uuid, offlinePlayer.getName(), currentBalance);
databaseManager.addBalanceDelta(uuid, offlinePlayer.getName(), delta);
// Refresh DB snapshot for tracking
double newDbBalance = databaseManager.getBalance(uuid);
lastKnownBalances.put(uuid, currentBalance);
lastKnownDbBalances.put(uuid, newDbBalance);
plugin.getLogger().info("Detected offline change for " +
offlinePlayer.getName() + ": " + currentBalance);
offlinePlayer.getName() + ": serverDelta=" + delta + ", newDB=" + newDbBalance);
// Optional: send configured offline-change message to console/log
String msg = plugin.getTranslationManager().getMessage("offline-change-detected");
if (msg != null && !msg.isEmpty()) {
plugin.getLogger().info(plugin.getTranslationManager().formatMessage("prefix") + msg);
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING,
plugin.getPluginLogger().log(Level.WARNING,
"Failed to save offline change for " + offlinePlayer.getName(), e);
}
} else if (lastBalance == null) {
// First time seeing this player, store initial balance
// First time seeing this player on this server, store initial balance
lastKnownBalances.put(uuid, currentBalance);
}
}
@@ -264,6 +280,23 @@ public class BalanceManager {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
double balance = economy.getBalance(player);
UUID uuid = player.getUniqueId();
Double lastBalance = lastKnownBalances.get(uuid);
if (lastBalance != null) {
// There is a last-known server-side snapshot => compute delta and apply it
double delta = balance - lastBalance;
if (Math.abs(delta) > 0.001) {
databaseManager.addBalanceDelta(uuid, player.getName(), delta);
double newDb = databaseManager.getBalance(uuid);
lastKnownBalances.put(uuid, balance);
lastKnownDbBalances.put(uuid, newDb);
return;
}
}
// Fallback: overwrite DB with current balance
databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance);
lastKnownBalances.put(player.getUniqueId(), balance);
lastKnownDbBalances.put(player.getUniqueId(), balance);

View File

@@ -64,7 +64,7 @@ public class DatabaseManager {
"`id` INT AUTO_INCREMENT PRIMARY KEY, " +
"`player_uuid` CHAR(36) UNIQUE NOT NULL, " +
"`player_name` VARCHAR(16), " +
"`balance` DECIMAL(15, 2) NOT NULL DEFAULT %.2f, " + // DECIMAL für bessere Präzision
"`balance` DECIMAL(15, 2) NOT NULL DEFAULT %.2f, " +
"`last_updated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"INDEX `idx_uuid` (`player_uuid`)" +
") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB",
@@ -111,7 +111,7 @@ public class DatabaseManager {
"`id` INT AUTO_INCREMENT PRIMARY KEY, " +
"`player_uuid` CHAR(36) UNIQUE NOT NULL, " +
"`player_name` VARCHAR(16), " +
"`balance` DECIMAL(15, 2) NOT NULL, " + // Kein DEFAULT hier
"`balance` DECIMAL(15, 2) NOT NULL, " +
"`last_updated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"INDEX `idx_uuid` (`player_uuid`)" +
") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB",
@@ -158,6 +158,36 @@ public class DatabaseManager {
}
}
/**
* Atomically add a delta to the stored balance. This avoids overwriting the DB with stale
* values when multiple servers write concurrently. If no row exists, insert one with
* (startingBalance + delta).
*/
public void addBalanceDelta(UUID playerUUID, String playerName, double delta) throws SQLException {
String updateSql = String.format("UPDATE %s SET balance = balance + ? WHERE player_uuid = ?", tableName);
try (Connection conn = dataSource.getConnection()) {
// Try atomic update first
try (PreparedStatement update = conn.prepareStatement(updateSql)) {
update.setDouble(1, delta);
update.setString(2, playerUUID.toString());
int affected = update.executeUpdate();
if (affected == 0) {
// No row existed — insert with startingBalance + delta
double starting = plugin.getConfigManager().getStartingBalance();
String insertSql = String.format("INSERT INTO %s (player_uuid, player_name, balance) VALUES (?, ?, ?)", tableName);
try (PreparedStatement insert = conn.prepareStatement(insertSql)) {
insert.setString(1, playerUUID.toString());
insert.setString(2, playerName);
insert.setDouble(3, starting + delta);
insert.executeUpdate();
}
}
}
}
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}

View File

@@ -8,7 +8,7 @@ settings:
save-on-quit: true
# Starting balance for new players
starting-balance: 100.0
# Locale for messages (en, de, etc.)
# Locale for messages (en, de, etc.), List of supported languages/locales can be found at https://github.com/deutschich/BalSync/wiki/Languages#supported-languages.
locale: "en"
# Reset to zero before loading from database on join

View File

@@ -1,5 +1,5 @@
name: BalSync
version: 1.0
version: 1.1
main: com.user404_.balsync.BalSyncPlugin
api-version: '1.20'
description: Synchronizes player balances with MySQL database