From 2baf77e7f9d33c1069f03f0cf2f39bda27279a18 Mon Sep 17 00:00:00 2001 From: "D.L." <140229250+deutschich@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:17:57 +0100 Subject: [PATCH 1/8] Initial commit 2 --- LICENSE | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index 2c87bac..b9cd78d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,18 +1,21 @@ MIT License -Copyright (c) 2025 deutschich +Copyright (c) 2025 D.L. -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. From da4c38c7ab07c7655ea2185542a743cfe614fbb2 Mon Sep 17 00:00:00 2001 From: "D.L." <140229250+deutschich@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:19:29 +0100 Subject: [PATCH 2/8] Add GitHub Actions workflow for preview releases --- .github/workflows/preview-release.yml | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/preview-release.yml diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml new file mode 100644 index 0000000..0876b20 --- /dev/null +++ b/.github/workflows/preview-release.yml @@ -0,0 +1,53 @@ +name: Preview Release + +on: + push: + branches: + - dev # Trigger on push to dev branch + +jobs: + build: + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout code + - name: Checkout Repository + uses: actions/checkout@v4 + + # Step 2: Setup Java (required for building the plugin) + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + # Step 3: Build the plugin with Maven + - name: Build Plugin + run: mvn clean package -DskipTests + + # Step 4: Create a preview release + - name: Create Preview Release + uses: softprops/action-gh-release@v2 + with: + tag_name: preview-${{ github.run_number }} + name: "Preview Build #${{ github.run_number }}" + body: | + 🚀 Automatic Preview Build + Commit: ${{ github.sha }} + Branch: ${{ github.ref_name }} + prerelease: true + files: target/*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Step 5: Cleanup old preview releases (keep only 10 newest) + - name: Cleanup old Preview Releases + run: | + echo "Deleting old Preview Releases, keeping only the 10 newest..." + gh release list --limit 100 --repo $GITHUB_REPOSITORY \ + | grep preview \ + | sort -rk2 \ + | awk 'NR>10 {print $1}' \ + | xargs -I {} gh release delete {} --repo $GITHUB_REPOSITORY --yes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6e6c0e4e8506201fa1970aa65ab332f28e2d1e37 Mon Sep 17 00:00:00 2001 From: "D.L." <140229250+deutschich@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:20:01 +0100 Subject: [PATCH 3/8] Add GitHub Actions workflow for release on merge --- .github/workflows/release-on-merge.yml | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/release-on-merge.yml diff --git a/.github/workflows/release-on-merge.yml b/.github/workflows/release-on-merge.yml new file mode 100644 index 0000000..8a1c2f5 --- /dev/null +++ b/.github/workflows/release-on-merge.yml @@ -0,0 +1,47 @@ +name: Release on Merge to Master + +on: + push: + branches: + - master # oder "main", je nach Repo + paths-ignore: + - '**.md' # ignoriert reine Dokumentationsänderungen + - '.github/**' # ignoriert Änderungen an Actions selbst + +jobs: + build-and-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Build with Maven + run: mvn clean package -DskipTests + + - name: Get version + id: get_version + run: | + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.get_version.outputs.version }} + name: "Release v${{ steps.get_version.outputs.version }}" + body: | + **v${{ steps.get_version.outputs.version }}** + - Commit: ${{ github.sha }} + - Branch: ${{ github.ref_name }} + - Version: v${{ steps.get_version.outputs.version }} + files: | + target/*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2065559155673fbb6d3cad280348bc6f8c696fcd Mon Sep 17 00:00:00 2001 From: deutschich Date: Mon, 1 Dec 2025 19:21:26 +0100 Subject: [PATCH 4/8] Initial Commit --- .gitignore | 113 ++++++++++++ pom.xml | 97 ++++++++++ .../com/user404_/balsync/BalSyncCommand.java | 56 ++++++ .../com/user404_/balsync/BalSyncPlugin.java | 122 +++++++++++++ .../com/user404_/balsync/BalanceManager.java | 107 +++++++++++ .../com/user404_/balsync/ConfigManager.java | 124 +++++++++++++ .../com/user404_/balsync/DatabaseManager.java | 168 ++++++++++++++++++ .../user404_/balsync/PlayerEventListener.java | 33 ++++ .../user404_/balsync/TranslationManager.java | 93 ++++++++++ src/main/resources/config.yml | 32 ++++ src/main/resources/messages_de.yml | 9 + src/main/resources/messages_en.yml | 9 + src/main/resources/plugin.yml | 20 +++ 13 files changed, 983 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/user404_/balsync/BalSyncCommand.java create mode 100644 src/main/java/com/user404_/balsync/BalSyncPlugin.java create mode 100644 src/main/java/com/user404_/balsync/BalanceManager.java create mode 100644 src/main/java/com/user404_/balsync/ConfigManager.java create mode 100644 src/main/java/com/user404_/balsync/DatabaseManager.java create mode 100644 src/main/java/com/user404_/balsync/PlayerEventListener.java create mode 100644 src/main/java/com/user404_/balsync/TranslationManager.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/messages_de.yml create mode 100644 src/main/resources/messages_en.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4561ce1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + com.user404_ + BalSync + 0.1-alpha + jar + + BalSync + + + 21 + UTF-8 + + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + jitpack.io + https://jitpack.io + + + + + + io.papermc.paper + paper-api + 1.21-R0.1-SNAPSHOT + provided + + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + + mysql + mysql-connector-java + 8.0.33 + + + + com.zaxxer + HikariCP + 5.0.1 + + + diff --git a/src/main/java/com/user404_/balsync/BalSyncCommand.java b/src/main/java/com/user404_/balsync/BalSyncCommand.java new file mode 100644 index 0000000..017053e --- /dev/null +++ b/src/main/java/com/user404_/balsync/BalSyncCommand.java @@ -0,0 +1,56 @@ +package com.user404_.balsync; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class BalSyncCommand implements CommandExecutor { + private final BalSyncPlugin plugin; + private final BalanceManager balanceManager; + + public BalSyncCommand(BalSyncPlugin plugin, BalanceManager balanceManager) { + this.plugin = plugin; + this.balanceManager = balanceManager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("balsync.admin")) { + sender.sendMessage(plugin.getTranslationManager().formatMessage("no-permission")); + return true; + } + + if (args.length == 0) { + sender.sendMessage(plugin.getTranslationManager().formatMessage("usage")); + return true; + } + + switch (args[0].toLowerCase()) { + case "reload": + plugin.getConfigManager().reload(); + plugin.getTranslationManager().loadMessages(); + sender.sendMessage(plugin.getTranslationManager().formatMessage("config-reloaded")); + break; + + case "save": + balanceManager.saveAllBalances(); + sender.sendMessage(plugin.getTranslationManager().formatMessage("balance-saved")); + break; + + case "load": + if (sender instanceof Player) { + balanceManager.loadPlayerBalance((Player) sender); + } else { + sender.sendMessage(plugin.getTranslationManager().formatMessage("player-not-found")); + } + break; + + default: + sender.sendMessage(plugin.getTranslationManager().formatMessage("usage")); + break; + } + + return true; + } +} \ 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 new file mode 100644 index 0000000..5557e83 --- /dev/null +++ b/src/main/java/com/user404_/balsync/BalSyncPlugin.java @@ -0,0 +1,122 @@ +package com.user404_.balsync; + +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import net.milkbowl.vault.economy.Economy; +import java.util.logging.Logger; + +public class BalSyncPlugin extends JavaPlugin { + private static BalSyncPlugin instance; + private Economy economy; + private DatabaseManager databaseManager; + private BalanceManager balanceManager; + private TranslationManager translationManager; + private ConfigManager configManager; + private Logger logger; + + @Override + public void onEnable() { + instance = this; + logger = getLogger(); + + // Load configuration + configManager = new ConfigManager(this); + configManager.loadConfig(); + + // Load translations + translationManager = new TranslationManager(this); + translationManager.loadMessages(); + + // Setup Vault economy + if (!setupEconomy()) { + logger.severe("Vault economy not found! Disabling plugin..."); + getServer().getPluginManager().disablePlugin(this); + return; + } + + // Initialize database + databaseManager = new DatabaseManager(this); + if (!databaseManager.connect()) { + logger.severe("Failed to connect to database! Disabling plugin..."); + getServer().getPluginManager().disablePlugin(this); + return; + } + + // Setup database tables + databaseManager.setupTables(); + + // Initialize balance manager + balanceManager = new BalanceManager(this, economy, databaseManager); + + // Register event listeners + getServer().getPluginManager().registerEvents(new PlayerEventListener(this, balanceManager), this); + + // Register commands + getCommand("balsync").setExecutor(new BalSyncCommand(this, balanceManager)); + + // Start auto-save task if enabled + int interval = configManager.getAutoSaveInterval(); + if (interval > 0) { + getServer().getScheduler().runTaskTimerAsynchronously(this, + () -> balanceManager.saveAllBalances(), + interval * 20L, interval * 20L); + } + + logger.info("BalSync v" + getDescription().getVersion() + " enabled successfully!"); + } + + @Override + public void onDisable() { + // Save all balances on shutdown + if (balanceManager != null) { + balanceManager.saveAllBalances(); + } + + // Close database connection + if (databaseManager != null) { + databaseManager.disconnect(); + } + + logger.info("BalSync disabled."); + } + + private boolean setupEconomy() { + if (getServer().getPluginManager().getPlugin("Vault") == null) { + return false; + } + + RegisteredServiceProvider rsp = getServer().getServicesManager() + .getRegistration(Economy.class); + if (rsp == null) { + return false; + } + + economy = rsp.getProvider(); + return economy != null; + } + + public static BalSyncPlugin getInstance() { + return instance; + } + + public Economy getEconomy() { + return economy; + } + + public DatabaseManager getDatabaseManager() { + return databaseManager; + } + + public TranslationManager getTranslationManager() { + return translationManager; + } + + public ConfigManager getConfigManager() { + return configManager; + } + + public Logger getPluginLogger() { + return logger; + } +} \ No newline at end of file diff --git a/src/main/java/com/user404_/balsync/BalanceManager.java b/src/main/java/com/user404_/balsync/BalanceManager.java new file mode 100644 index 0000000..82a94b6 --- /dev/null +++ b/src/main/java/com/user404_/balsync/BalanceManager.java @@ -0,0 +1,107 @@ +package com.user404_.balsync; + +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import java.sql.SQLException; +import java.util.UUID; +import java.util.logging.Level; + +public class BalanceManager { + private final BalSyncPlugin plugin; + private final Economy economy; + private final DatabaseManager databaseManager; + + public BalanceManager(BalSyncPlugin plugin, Economy economy, DatabaseManager databaseManager) { + this.plugin = plugin; + this.economy = economy; + this.databaseManager = databaseManager; + } + + 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 + if (!economy.hasAccount(player)) { + economy.createPlayerAccount(player); + } + + // Aktuelle Balance abrufen + 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)); + } + + // 2. Datenbankbalance einzahlen (ÜBERSCHREIBT die alte Balance) + economy.depositPlayer(player, databaseBalance); + + // Logging + plugin.getLogger().info("BALANCE OVERWRITTEN for " + player.getName() + + ": Old=" + currentBalance + ", New=" + databaseBalance + " (from DB)"); + + // Nachricht an Spieler + String message = plugin.getTranslationManager().getMessage("balance-loaded"); + if (message != null && !message.isEmpty()) { + player.sendMessage(plugin.getTranslationManager().formatMessage(message)); + } + }); + + } catch (SQLException e) { + plugin.getPluginLogger().log(Level.SEVERE, + "Failed to load balance for player: " + player.getName(), e); + } + }); + } + + public void savePlayerBalance(OfflinePlayer player) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + double balance = economy.getBalance(player); + databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance); + } catch (SQLException e) { + plugin.getPluginLogger().log(Level.SEVERE, + "Failed to save balance for player: " + player.getName(), e); + } + }); + } + + 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); + } + return plugin.getConfigManager().getStartingBalance(); + } +} \ 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 new file mode 100644 index 0000000..b5063e7 --- /dev/null +++ b/src/main/java/com/user404_/balsync/ConfigManager.java @@ -0,0 +1,124 @@ +package com.user404_.balsync; + +import org.bukkit.configuration.file.FileConfiguration; +import java.io.File; + +public class ConfigManager { + private final BalSyncPlugin plugin; + private FileConfiguration config; + + public ConfigManager(BalSyncPlugin plugin) { + this.plugin = plugin; + } + + public void loadConfig() { + // Create plugin folder if it doesn't exist + if (!plugin.getDataFolder().exists()) { + plugin.getDataFolder().mkdirs(); + } + + // Save default config from resources + plugin.saveDefaultConfig(); + + // Reload configuration + plugin.reloadConfig(); + config = plugin.getConfig(); + + // Set default values if not present + setDefaults(); + } + + private void setDefaults() { + config.addDefault("database.host", "localhost"); + config.addDefault("database.port", 3306); + config.addDefault("database.database", "minecraft"); + config.addDefault("database.username", "root"); + config.addDefault("database.password", "password"); + config.addDefault("database.use-ssl", false); + config.addDefault("database.connection-pool.maximum-pool-size", 10); + config.addDefault("database.connection-pool.minimum-idle", 5); + config.addDefault("database.connection-pool.connection-timeout", 30000); + config.addDefault("database.connection-pool.idle-timeout", 600000); + + config.addDefault("settings.auto-save-interval", 60); + config.addDefault("settings.save-on-quit", true); + config.addDefault("settings.starting-balance", 100.0); + config.addDefault("settings.locale", "en"); + + config.addDefault("tables.player_balances.table-name", "player_balances"); + config.addDefault("tables.player_balances.uuid-column", "player_uuid"); + config.addDefault("tables.player_balances.balance-column", "balance"); + config.addDefault("tables.player_balances.last-updated-column", "last_updated"); + + config.options().copyDefaults(true); + plugin.saveConfig(); + } + + public void reload() { + plugin.reloadConfig(); + config = plugin.getConfig(); + } + + // Database getters + public String getDatabaseHost() { + return config.getString("database.host", "localhost"); + } + + public int getDatabasePort() { + return config.getInt("database.port", 3306); + } + + public String getDatabaseName() { + return config.getString("database.database", "minecraft"); + } + + public String getDatabaseUsername() { + return config.getString("database.username", "root"); + } + + public String getDatabasePassword() { + return config.getString("database.password", "password"); + } + + public boolean useSSL() { + return config.getBoolean("database.use-ssl", false); + } + + public int getMaxPoolSize() { + return config.getInt("database.connection-pool.maximum-pool-size", 10); + } + + public int getMinIdle() { + return config.getInt("database.connection-pool.minimum-idle", 5); + } + + public int getConnectionTimeout() { + return config.getInt("database.connection-pool.connection-timeout", 30000); + } + + public int getIdleTimeout() { + return config.getInt("database.connection-pool.idle-timeout", 600000); + } + + // Settings getters + public int getAutoSaveInterval() { + return config.getInt("settings.auto-save-interval", 60); + } + + public boolean saveOnQuit() { + return config.getBoolean("settings.save-on-quit", true); + } + + public double getStartingBalance() { + return config.getDouble("settings.starting-balance", 100.0); + } + + public String getLocale() { + return config.getString("settings.locale", "en"); + } + + // Table getters + public String getTableName() { + return config.getString("tables.player_balances.table-name", "player_balances"); + } +} \ No newline at end of file diff --git a/src/main/java/com/user404_/balsync/DatabaseManager.java b/src/main/java/com/user404_/balsync/DatabaseManager.java new file mode 100644 index 0000000..c937421 --- /dev/null +++ b/src/main/java/com/user404_/balsync/DatabaseManager.java @@ -0,0 +1,168 @@ +package com.user404_.balsync; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.sql.*; +import java.util.UUID; +import java.util.logging.Level; + +public class DatabaseManager { + private final BalSyncPlugin plugin; + private HikariDataSource dataSource; + private final String tableName; + + public DatabaseManager(BalSyncPlugin plugin) { + this.plugin = plugin; + this.tableName = plugin.getConfigManager().getTableName(); + } + + public boolean connect() { + try { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(String.format("jdbc:mysql://%s:%d/%s", + plugin.getConfigManager().getDatabaseHost(), + plugin.getConfigManager().getDatabasePort(), + plugin.getConfigManager().getDatabaseName())); + config.setUsername(plugin.getConfigManager().getDatabaseUsername()); + config.setPassword(plugin.getConfigManager().getDatabasePassword()); + config.addDataSourceProperty("useSSL", + plugin.getConfigManager().useSSL()); + + // Connection pool settings + config.setMaximumPoolSize(plugin.getConfigManager().getMaxPoolSize()); + config.setMinimumIdle(plugin.getConfigManager().getMinIdle()); + config.setConnectionTimeout(plugin.getConfigManager().getConnectionTimeout()); + config.setIdleTimeout(plugin.getConfigManager().getIdleTimeout()); + config.setLeakDetectionThreshold(30000); + + dataSource = new HikariDataSource(config); + + // Test connection + try (Connection conn = dataSource.getConnection()) { + plugin.getPluginLogger().info("Successfully connected to MySQL database!"); + return true; + } + } catch (SQLException e) { + plugin.getPluginLogger().log(Level.SEVERE, "Failed to connect to database!", e); + return false; + } + } + + public void disconnect() { + if (dataSource != null && !dataSource.isClosed()) { + dataSource.close(); + plugin.getPluginLogger().info("Database connection closed."); + } + } + + public void setupTables() { + // Verwende DECIMAL statt DOUBLE für genauere Währungswerte + double startingBalance = plugin.getConfigManager().getStartingBalance(); + + String createTableSQL = String.format( + "CREATE TABLE IF NOT EXISTS `%s` (" + + "`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 + "`last_updated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " + + "INDEX `idx_uuid` (`player_uuid`)" + + ") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB", + tableName, startingBalance + ); + + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(createTableSQL); + plugin.getPluginLogger().info("Database tables checked/created successfully!"); + + // Optional: Protokolliere die erstellte Tabelle + logTableInfo(conn); + } catch (SQLException e) { + plugin.getPluginLogger().log(Level.SEVERE, "Failed to create database tables!", e); + + // Fallback-SQL ohne DEFAULT-Wert + tryFallbackTableCreation(); + } + } + + private void logTableInfo(Connection conn) throws SQLException { + String checkSQL = String.format("DESCRIBE `%s`", tableName); + try (Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(checkSQL)) { + plugin.getPluginLogger().info("Table structure for '" + tableName + "':"); + while (rs.next()) { + plugin.getPluginLogger().info(String.format( + "Column: %s, Type: %s, Default: %s", + rs.getString("Field"), + rs.getString("Type"), + rs.getString("Default") + )); + } + } + } + + private void tryFallbackTableCreation() { + plugin.getPluginLogger().warning("Trying fallback table creation..."); + + // Fallback: Tabelle ohne DEFAULT-Wert, dann Standardwert über die Anwendung + String fallbackSQL = String.format( + "CREATE TABLE IF NOT EXISTS `%s` (" + + "`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 + "`last_updated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " + + "INDEX `idx_uuid` (`player_uuid`)" + + ") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB", + tableName + ); + + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(fallbackSQL); + plugin.getPluginLogger().info("Fallback table created successfully!"); + } catch (SQLException ex) { + plugin.getPluginLogger().log(Level.SEVERE, "Fallback creation also failed!", ex); + } + } + + public double getBalance(UUID playerUUID) throws SQLException { + String sql = String.format("SELECT balance FROM %s WHERE player_uuid = ?", tableName); + + try (Connection conn = dataSource.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, playerUUID.toString()); + + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return rs.getDouble("balance"); + } + } + return plugin.getConfigManager().getStartingBalance(); + } + + public void saveBalance(UUID playerUUID, String playerName, double balance) throws SQLException { + String sql = String.format( + "INSERT INTO %s (player_uuid, player_name, balance) VALUES (?, ?, ?) " + + "ON DUPLICATE KEY UPDATE player_name = VALUES(player_name), balance = VALUES(balance)", + tableName + ); + + try (Connection conn = dataSource.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, playerUUID.toString()); + stmt.setString(2, playerName); + stmt.setDouble(3, balance); + stmt.executeUpdate(); + } + } + + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + public boolean isConnected() { + return dataSource != null && !dataSource.isClosed(); + } +} \ 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 new file mode 100644 index 0000000..cdad2dc --- /dev/null +++ b/src/main/java/com/user404_/balsync/PlayerEventListener.java @@ -0,0 +1,33 @@ +package com.user404_.balsync; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class PlayerEventListener implements Listener { + private final BalSyncPlugin plugin; + private final BalanceManager balanceManager; + + public PlayerEventListener(BalSyncPlugin plugin, BalanceManager balanceManager) { + this.plugin = plugin; + this.balanceManager = balanceManager; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + // 2 Sekunden (40 Ticks) Verzögerung + plugin.getServer().getScheduler().runTaskLater(plugin, () -> { + if (event.getPlayer().isOnline()) { + balanceManager.loadPlayerBalance(event.getPlayer()); + } + }, 40L); // 20 Ticks = 1 Sekunde, 40 Ticks = 2 Sekunden + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + if (plugin.getConfigManager().saveOnQuit()) { + balanceManager.savePlayerBalance(event.getPlayer()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/user404_/balsync/TranslationManager.java b/src/main/java/com/user404_/balsync/TranslationManager.java new file mode 100644 index 0000000..bb1283e --- /dev/null +++ b/src/main/java/com/user404_/balsync/TranslationManager.java @@ -0,0 +1,93 @@ +package com.user404_.balsync; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.file.FileConfiguration; +import java.io.File; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +public class TranslationManager { + private final BalSyncPlugin plugin; + private final Map messages; + private String locale; + + public TranslationManager(BalSyncPlugin plugin) { + this.plugin = plugin; + this.messages = new HashMap<>(); + } + + public void loadMessages() { + messages.clear(); + locale = plugin.getConfigManager().getLocale(); + + // First, load default messages from JAR + try (Reader reader = new InputStreamReader( + plugin.getResource("messages_en.yml"), StandardCharsets.UTF_8)) { + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(reader); + loadConfigIntoMap(defaultConfig); + } catch (Exception e) { + plugin.getPluginLogger().warning("Failed to load default messages!"); + } + + // Load locale-specific file if different from English + if (!locale.equalsIgnoreCase("en")) { + String fileName = "messages_" + locale.toLowerCase() + ".yml"; + File localeFile = new File(plugin.getDataFolder(), fileName); + + // Copy from resources if doesn't exist + if (!localeFile.exists()) { + plugin.saveResource(fileName, false); + } + + // Load the file + if (localeFile.exists()) { + YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(localeFile); + loadConfigIntoMap(localeConfig); + } + } + + // Finally, load user overrides from data folder + File userFile = new File(plugin.getDataFolder(), "messages.yml"); + if (userFile.exists()) { + YamlConfiguration userConfig = YamlConfiguration.loadConfiguration(userFile); + loadConfigIntoMap(userConfig); + } + + plugin.getPluginLogger().info("Loaded messages for locale: " + locale); + } + + private void loadConfigIntoMap(YamlConfiguration config) { + for (String key : config.getKeys(true)) { + if (config.isString(key)) { + messages.put(key, config.getString(key)); + } + } + } + + public String getMessage(String key) { + return messages.getOrDefault(key, key); + } + + public String formatMessage(String key, Object... args) { + String message = getMessage(key); + String prefix = getMessage("prefix"); + + if (args.length > 0) { + try { + message = MessageFormat.format(message, args); + } catch (Exception e) { + plugin.getPluginLogger().warning("Failed to format message: " + key); + } + } + + return prefix + message.replace('&', '§'); + } + + public String getLocale() { + return locale; + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..506b8fd --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,32 @@ +# 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 + +# Plugin Settings +settings: + # Auto-save interval in seconds (0 to disable) + auto-save-interval: 60 + # Whether to save on player quit + save-on-quit: true + # Starting balance for new players + starting-balance: 100.0 + # Locale for messages (en, de, etc.) + locale: "en" + +# Database Table Configuration +tables: + player_balances: + table-name: "player_balances" + uuid-column: "player_uuid" + balance-column: "balance" + last-updated-column: "last_updated" \ No newline at end of file diff --git a/src/main/resources/messages_de.yml b/src/main/resources/messages_de.yml new file mode 100644 index 0000000..7da0924 --- /dev/null +++ b/src/main/resources/messages_de.yml @@ -0,0 +1,9 @@ +prefix: "&8[&6BalSync&8] &7" +no-permission: "&cDu hast keine Berechtigung diesen Befehl zu verwenden!" +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." +player-not-found: "&cSpieler nicht gefunden!" +usage: "&cVerwendung: /balsync [reload|save|load]" \ No newline at end of file diff --git a/src/main/resources/messages_en.yml b/src/main/resources/messages_en.yml new file mode 100644 index 0000000..a00d43b --- /dev/null +++ b/src/main/resources/messages_en.yml @@ -0,0 +1,9 @@ +prefix: "&8[&6BalSync&8] &7" +no-permission: "&cYou don't have permission to use this command!" +config-reloaded: "&aConfiguration reloaded successfully!" +balance-loaded: "&aYour balance has been synchronized with the database." +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 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..e50245e --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,20 @@ +name: BalSync +version: 0.1-alpha +main: com.user404_.balsync.BalSyncPlugin +api-version: '1.20' +description: Synchronizes player balances with MySQL database +author: user404_ +website: https://deutschich.github.io/BalSync + +depend: [Vault] + +commands: + balsync: + description: Main command for BalSync + usage: / [reload|save|load] + permission: balsync.admin + +permissions: + balsync.admin: + description: Allows access to all BalSync commands + default: op \ No newline at end of file From ec7f204eec96cf63015028994e09c0470a197ce5 Mon Sep 17 00:00:00 2001 From: deutschich Date: Mon, 1 Dec 2025 19:27:01 +0100 Subject: [PATCH 5/8] Switch to beta --- pom.xml | 2 +- src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4561ce1..624517e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.user404_ BalSync - 0.1-alpha + 1.0-beta jar BalSync diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e50245e..eff64d6 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: BalSync -version: 0.1-alpha +version: 1.0-beta main: com.user404_.balsync.BalSyncPlugin api-version: '1.20' description: Synchronizes player balances with MySQL database From 28f127432cb96c2fefedea1c5471e6081c2db599 Mon Sep 17 00:00:00 2001 From: deutschich Date: Mon, 1 Dec 2025 19:55:44 +0100 Subject: [PATCH 6/8] Set time to 15 Seconds to optimize the plugin on lags. --- src/main/java/com/user404_/balsync/PlayerEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/user404_/balsync/PlayerEventListener.java b/src/main/java/com/user404_/balsync/PlayerEventListener.java index cdad2dc..62a2d04 100644 --- a/src/main/java/com/user404_/balsync/PlayerEventListener.java +++ b/src/main/java/com/user404_/balsync/PlayerEventListener.java @@ -21,7 +21,7 @@ public class PlayerEventListener implements Listener { if (event.getPlayer().isOnline()) { balanceManager.loadPlayerBalance(event.getPlayer()); } - }, 40L); // 20 Ticks = 1 Sekunde, 40 Ticks = 2 Sekunden + }, 300L); // 20 Ticks = 1 Sekunde, 300 Ticks = 15 Sekunden } @EventHandler From 391bfe7d01720a2aec631e23e66e56131389873e Mon Sep 17 00:00:00 2001 From: deutschich Date: Mon, 1 Dec 2025 20:11:06 +0100 Subject: [PATCH 7/8] =?UTF-8?q?Changed=20the=20&=20to=20=C2=A7=20to=20show?= =?UTF-8?q?=20messages=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/messages_de.yml | 18 +++++++++--------- src/main/resources/messages_en.yml | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/resources/messages_de.yml b/src/main/resources/messages_de.yml index 7da0924..faa8179 100644 --- a/src/main/resources/messages_de.yml +++ b/src/main/resources/messages_de.yml @@ -1,9 +1,9 @@ -prefix: "&8[&6BalSync&8] &7" -no-permission: "&cDu hast keine Berechtigung diesen Befehl zu verwenden!" -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." -player-not-found: "&cSpieler nicht gefunden!" -usage: "&cVerwendung: /balsync [reload|save|load]" \ No newline at end of file +prefix: "§8[§6BalSync§8] §7" +no-permission: "§cDu hast keine Berechtigung diesen Befehl zu verwenden!" +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." +player-not-found: "§cSpieler nicht gefunden!" +usage: "§cVerwendung: /balsync [reload|save|load]" \ No newline at end of file diff --git a/src/main/resources/messages_en.yml b/src/main/resources/messages_en.yml index a00d43b..8821b5a 100644 --- a/src/main/resources/messages_en.yml +++ b/src/main/resources/messages_en.yml @@ -1,9 +1,9 @@ -prefix: "&8[&6BalSync&8] &7" -no-permission: "&cYou don't have permission to use this command!" -config-reloaded: "&aConfiguration reloaded successfully!" -balance-loaded: "&aYour balance has been synchronized with the database." -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 +prefix: "§8[§6BalSync§8] §7" +no-permission: "§cYou don't have permission to use this command!" +config-reloaded: "§aConfiguration reloaded successfully!" +balance-loaded: "§aYour balance has been synchronized with the database." +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 From 0613270c4deba6435b7f4ca5f16e2d35e480e5ac Mon Sep 17 00:00:00 2001 From: deutschich Date: Tue, 2 Dec 2025 17:55:12 +0100 Subject: [PATCH 8/8] Beta improvements and tests completed, this will be the initial non-beta release. --- README.md | 91 +++++- pom.xml | 2 +- .../com/user404_/balsync/BalSyncCommand.java | 13 + .../com/user404_/balsync/BalSyncPlugin.java | 1 + .../com/user404_/balsync/BalanceManager.java | 266 +++++++++++++++--- .../com/user404_/balsync/ConfigManager.java | 15 + .../user404_/balsync/PlayerEventListener.java | 17 +- src/main/resources/config.yml | 26 +- src/main/resources/messages_at.yml | 15 + src/main/resources/messages_de.yml | 7 +- src/main/resources/messages_en.yml | 5 +- src/main/resources/messages_es.yml | 12 + src/main/resources/messages_fr.yml | 12 + src/main/resources/messages_pl.yml | 12 + src/main/resources/messages_pt-br.yml | 12 + src/main/resources/messages_ru.yml | 12 + src/main/resources/plugin.yml | 2 +- 17 files changed, 455 insertions(+), 65 deletions(-) create mode 100644 src/main/resources/messages_at.yml create mode 100644 src/main/resources/messages_es.yml create mode 100644 src/main/resources/messages_fr.yml create mode 100644 src/main/resources/messages_pl.yml create mode 100644 src/main/resources/messages_pt-br.yml create mode 100644 src/main/resources/messages_ru.yml 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