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