Compare commits
10 Commits
951609d6fe
...
2f5a517f6a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f5a517f6a | |||
| 52dfcf99e4 | |||
| 0613270c4d | |||
| 391bfe7d01 | |||
| 28f127432c | |||
| ec7f204eec | |||
| 2065559155 | |||
|
|
6e6c0e4e85 | ||
|
|
da4c38c7ab | ||
|
|
2baf77e7f9 |
53
.github/workflows/preview-release.yml
vendored
Normal file
53
.github/workflows/preview-release.yml
vendored
Normal file
@@ -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 }}
|
||||
47
.github/workflows/release-on-merge.yml
vendored
Normal file
47
.github/workflows/release-on-merge.yml
vendored
Normal file
@@ -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 }}
|
||||
113
.gitignore
vendored
Normal file
113
.gitignore
vendored
Normal file
@@ -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/
|
||||
29
LICENSE
29
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.
|
||||
|
||||
97
pom.xml
Normal file
97
pom.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.user404_</groupId>
|
||||
<artifactId>BalSync</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BalSync</name>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean package</defaultGoal>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc-repo</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<!-- PaperMC Repository -->
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<!-- VaultAPI Repository (JitPack) -->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- VaultAPI -->
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- MySQL Connector -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
<!-- HikariCP for Connection Pooling -->
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>5.0.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
69
src/main/java/com/user404_/balsync/BalSyncCommand.java
Normal file
69
src/main/java/com/user404_/balsync/BalSyncCommand.java
Normal file
@@ -0,0 +1,69 @@
|
||||
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":
|
||||
// Save all balances - Methode ist jetzt in BalanceManager
|
||||
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;
|
||||
|
||||
case "status":
|
||||
sendStatusInfo(sender);
|
||||
break;
|
||||
|
||||
default:
|
||||
sender.sendMessage(plugin.getTranslationManager().formatMessage("usage"));
|
||||
break;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
123
src/main/java/com/user404_/balsync/BalSyncPlugin.java
Normal file
123
src/main/java/com/user404_/balsync/BalSyncPlugin.java
Normal file
@@ -0,0 +1,123 @@
|
||||
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();
|
||||
balanceManager.shutdown(); // NEW: Cleanup polling tasks
|
||||
}
|
||||
|
||||
// Close database connection
|
||||
if (databaseManager != null) {
|
||||
databaseManager.disconnect();
|
||||
}
|
||||
|
||||
logger.info("BalSync disabled.");
|
||||
}
|
||||
|
||||
private boolean setupEconomy() {
|
||||
if (getServer().getPluginManager().getPlugin("Vault") == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisteredServiceProvider<Economy> 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;
|
||||
}
|
||||
}
|
||||
285
src/main/java/com/user404_/balsync/BalanceManager.java
Normal file
285
src/main/java/com/user404_/balsync/BalanceManager.java
Normal file
@@ -0,0 +1,285 @@
|
||||
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.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class BalanceManager {
|
||||
private final BalSyncPlugin plugin;
|
||||
private final Economy economy;
|
||||
private final DatabaseManager databaseManager;
|
||||
private BukkitTask dbPollingTask;
|
||||
private final Map<UUID, Double> lastKnownBalances = new HashMap<>();
|
||||
private final Map<UUID, Double> lastKnownDbBalances = new HashMap<>();
|
||||
public void saveAllBalances() {
|
||||
plugin.getPluginLogger().info("Saving all player balances to database...");
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int saved = 0;
|
||||
for (OfflinePlayer player : Bukkit.getOfflinePlayers()) {
|
||||
try {
|
||||
if (economy.hasAccount(player)) {
|
||||
double balance = economy.getBalance(player);
|
||||
databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance);
|
||||
lastKnownBalances.put(player.getUniqueId(), balance);
|
||||
lastKnownDbBalances.put(player.getUniqueId(), balance);
|
||||
saved++;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().log(Level.WARNING,
|
||||
"Failed to save balance for: " + player.getName(), e);
|
||||
}
|
||||
}
|
||||
plugin.getPluginLogger().info("Saved " + saved + " player balances to database.");
|
||||
});
|
||||
}
|
||||
public BalanceManager(BalSyncPlugin plugin, Economy economy, DatabaseManager databaseManager) {
|
||||
this.plugin = plugin;
|
||||
this.economy = economy;
|
||||
this.databaseManager = databaseManager;
|
||||
startDbPolling();
|
||||
startOfflineMonitoring();
|
||||
}
|
||||
|
||||
// MODIFIED: Added reset functionality
|
||||
public void loadPlayerBalance(Player player) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
double databaseBalance = databaseManager.getBalance(player.getUniqueId());
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
// Ensure player has account
|
||||
if (!economy.hasAccount(player)) {
|
||||
economy.createPlayerAccount(player);
|
||||
}
|
||||
|
||||
// RESET TO ZERO if configured
|
||||
double currentBalance = economy.getBalance(player);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// Update tracking maps
|
||||
lastKnownBalances.put(player.getUniqueId(), databaseBalance);
|
||||
lastKnownDbBalances.put(player.getUniqueId(), databaseBalance);
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().log(Level.SEVERE,
|
||||
"Failed to load balance for player: " + player.getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// NEW: Poll database for external changes
|
||||
private void startDbPolling() {
|
||||
int interval = plugin.getConfigManager().getDbPollInterval();
|
||||
if (interval <= 0) return;
|
||||
|
||||
dbPollingTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
|
||||
pollDatabaseForChanges();
|
||||
}, interval * 20L, interval * 20L);
|
||||
|
||||
plugin.getLogger().info("Started database polling every " + interval + " seconds");
|
||||
}
|
||||
|
||||
// NEW: Check database for balance changes and apply to online players
|
||||
private void pollDatabaseForChanges() {
|
||||
List<UUID> onlineUUIDs = getOnlinePlayerUUIDs();
|
||||
|
||||
// Keine online Spieler → nichts abfragen
|
||||
if (onlineUUIDs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection conn = databaseManager.getConnection()) {
|
||||
// Platzhalter für die IN-Klausel erstellen
|
||||
StringBuilder placeholders = new StringBuilder();
|
||||
for (int i = 0; i < onlineUUIDs.size(); i++) {
|
||||
placeholders.append("?");
|
||||
if (i < onlineUUIDs.size() - 1) {
|
||||
placeholders.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
String sql = String.format(
|
||||
"SELECT player_uuid, balance FROM %s WHERE player_uuid IN (%s)",
|
||||
plugin.getConfigManager().getTableName(),
|
||||
placeholders.toString()
|
||||
);
|
||||
|
||||
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
// UUIDs als Parameter setzen
|
||||
for (int i = 0; i < onlineUUIDs.size(); i++) {
|
||||
stmt.setString(i + 1, onlineUUIDs.get(i).toString());
|
||||
}
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
UUID playerUUID = UUID.fromString(rs.getString("player_uuid"));
|
||||
double dbBalance = rs.getDouble("balance");
|
||||
|
||||
// Prüfen, ob sich die Datenbank-Balance geändert hat
|
||||
Double lastDbBalance = lastKnownDbBalances.get(playerUUID);
|
||||
if (lastDbBalance == null || Math.abs(dbBalance - lastDbBalance) > 0.001) {
|
||||
// Datenbank hat sich geändert → auf Spieler anwenden
|
||||
applyDbChangeToPlayer(playerUUID, dbBalance, lastDbBalance);
|
||||
lastKnownDbBalances.put(playerUUID, dbBalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Error polling database for changes", e);
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Apply database changes to online player
|
||||
private void applyDbChangeToPlayer(UUID playerUUID, double newBalance, Double oldBalance) {
|
||||
Player player = Bukkit.getPlayer(playerUUID);
|
||||
if (player != null && player.isOnline()) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
double currentBalance = economy.getBalance(player);
|
||||
double difference = newBalance - currentBalance;
|
||||
|
||||
if (Math.abs(difference) > 0.001) {
|
||||
if (difference > 0) {
|
||||
economy.depositPlayer(player, difference);
|
||||
} else {
|
||||
economy.withdrawPlayer(player, Math.abs(difference));
|
||||
}
|
||||
|
||||
lastKnownBalances.put(playerUUID, newBalance);
|
||||
|
||||
plugin.getLogger().info("Applied external DB change for " +
|
||||
player.getName() + ": " + newBalance);
|
||||
|
||||
// Notify player if configured
|
||||
if (plugin.getConfigManager().notifyOnExternalChange()) {
|
||||
String message = plugin.getTranslationManager().getMessage(
|
||||
"balance-external-change");
|
||||
if (message != null && !message.isEmpty()) {
|
||||
String formatted = message
|
||||
.replace("{old}", String.format("%.2f", oldBalance != null ? oldBalance : currentBalance))
|
||||
.replace("{new}", String.format("%.2f", newBalance))
|
||||
.replace("&", "§");
|
||||
player.sendMessage(plugin.getTranslationManager().formatMessage("prefix") + formatted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Monitor offline player balance changes when auto-save-interval = 0
|
||||
private void startOfflineMonitoring() {
|
||||
int autoSaveInterval = plugin.getConfigManager().getAutoSaveInterval();
|
||||
boolean monitorOffline = plugin.getConfigManager().monitorOfflineChanges();
|
||||
|
||||
if (autoSaveInterval == 0 && monitorOffline) {
|
||||
// Check for balance changes every 30 seconds
|
||||
Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
|
||||
monitorOfflineBalanceChanges();
|
||||
}, 600L, 600L); // 30 seconds (600 ticks)
|
||||
|
||||
plugin.getLogger().info("Started offline balance change monitoring");
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Detect and save offline balance changes
|
||||
private void monitorOfflineBalanceChanges() {
|
||||
for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
|
||||
if (economy.hasAccount(offlinePlayer)) {
|
||||
double currentBalance = economy.getBalance(offlinePlayer);
|
||||
UUID uuid = offlinePlayer.getUniqueId();
|
||||
|
||||
Double lastBalance = lastKnownBalances.get(uuid);
|
||||
if (lastBalance != null && Math.abs(currentBalance - lastBalance) > 0.001) {
|
||||
// Balance has changed - save to database
|
||||
try {
|
||||
databaseManager.saveBalance(uuid, offlinePlayer.getName(), currentBalance);
|
||||
lastKnownBalances.put(uuid, currentBalance);
|
||||
plugin.getLogger().info("Detected offline change for " +
|
||||
offlinePlayer.getName() + ": " + currentBalance);
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to save offline change for " + offlinePlayer.getName(), e);
|
||||
}
|
||||
} else if (lastBalance == null) {
|
||||
// First time seeing this player, store initial balance
|
||||
lastKnownBalances.put(uuid, currentBalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Track balance when player quits
|
||||
public void trackPlayerQuit(UUID playerUUID, double balance) {
|
||||
lastKnownBalances.put(playerUUID, balance);
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private List<UUID> getOnlinePlayerUUIDs() {
|
||||
List<UUID> uuids = new ArrayList<>();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
uuids.add(player.getUniqueId());
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
|
||||
// MODIFIED savePlayerBalance to update tracking
|
||||
public void savePlayerBalance(OfflinePlayer player) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
double balance = economy.getBalance(player);
|
||||
databaseManager.saveBalance(player.getUniqueId(), player.getName(), balance);
|
||||
lastKnownBalances.put(player.getUniqueId(), balance);
|
||||
lastKnownDbBalances.put(player.getUniqueId(), balance);
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().log(Level.SEVERE,
|
||||
"Failed to save balance for player: " + player.getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup on disable
|
||||
public void shutdown() {
|
||||
if (dbPollingTask != null) {
|
||||
dbPollingTask.cancel();
|
||||
}
|
||||
lastKnownBalances.clear();
|
||||
lastKnownDbBalances.clear();
|
||||
}
|
||||
}
|
||||
139
src/main/java/com/user404_/balsync/ConfigManager.java
Normal file
139
src/main/java/com/user404_/balsync/ConfigManager.java
Normal file
@@ -0,0 +1,139 @@
|
||||
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");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
168
src/main/java/com/user404_/balsync/DatabaseManager.java
Normal file
168
src/main/java/com/user404_/balsync/DatabaseManager.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
46
src/main/java/com/user404_/balsync/PlayerEventListener.java
Normal file
46
src/main/java/com/user404_/balsync/PlayerEventListener.java
Normal file
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (plugin.getConfigManager().saveOnQuit()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/main/java/com/user404_/balsync/TranslationManager.java
Normal file
93
src/main/java/com/user404_/balsync/TranslationManager.java
Normal file
@@ -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<String, String> 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;
|
||||
}
|
||||
}
|
||||
32
src/main/resources/config.yml
Normal file
32
src/main/resources/config.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
# Database Configuration (existing)...
|
||||
|
||||
# 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"
|
||||
|
||||
# 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:
|
||||
table-name: "player_balances"
|
||||
uuid-column: "player_uuid"
|
||||
balance-column: "balance"
|
||||
last-updated-column: "last_updated"
|
||||
15
src/main/resources/messages_at.yml
Normal file
15
src/main/resources/messages_at.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# This is an Easter egg language. If you found this, you're probably digging through the source code.
|
||||
# Have fun with the Austrian/Wienerisch translation! :)
|
||||
|
||||
prefix: "§8[§dGödSynk§8] §7"
|
||||
no-permission: "§cHeast, du host ka Berechtigung für den Befehl!"
|
||||
config-reloaded: "§aKonfig is wieda gscheit neig'laden!"
|
||||
balance-loaded: "§aDei Gödstandl is jetzt mit da Datenbank a glei."
|
||||
balance-saved: "§aOlle Gödstandln woan in da Datenbank gspeichert, passt so!"
|
||||
database-connected: "§aJo fix, Verbindung zur Datenbank steht!"
|
||||
database-error: "§cOida… do is a Fehler in da Datenbank. Schau amoi in die Konsole."
|
||||
player-not-found: "§cDen Spieler find i net, host di vertippt?"
|
||||
usage: "§cSo geht's: /balsync [reload|save|load]"
|
||||
balance-external-change: "§eDei Gödstandl woan von außen gändat: §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aDei Gödstandl woan auf §e${0}§a zruckgsetzt, bevors aus da Datenbank kema san."
|
||||
offline-change-detected: "§7A Änderung währendst ned do woarst is gmerkt und gspeichert wordn."
|
||||
12
src/main/resources/messages_de.yml
Normal file
12
src/main/resources/messages_de.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
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 oder die Logs für Details."
|
||||
player-not-found: "§cSpieler nicht gefunden!"
|
||||
usage: "§cVerwendung: /balsync [reload|save|load]"
|
||||
balance-external-change: "§eDein Kontostand wurde extern (vielleicht von einem anderen Server) aktualisiert: §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aDein Kontostand wurde auf §e${0}§a zurückgesetzt, bevor er aus der Datenbank geladen wurde."
|
||||
offline-change-detected: "§7Offline-Kontostandänderung erkannt und gespeichert."
|
||||
12
src/main/resources/messages_en.yml
Normal file
12
src/main/resources/messages_en.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
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]"
|
||||
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."
|
||||
12
src/main/resources/messages_es.yml
Normal file
12
src/main/resources/messages_es.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
prefix: "§8[§6BalSync§8] §7"
|
||||
no-permission: "§c¡No tienes permiso para usar este comando!"
|
||||
config-reloaded: "§a¡Configuración recargada correctamente!"
|
||||
balance-loaded: "§aTu saldo ha sido sincronizado con la base de datos."
|
||||
balance-saved: "§aTodos los saldos de los jugadores han sido guardados en la base de datos."
|
||||
database-connected: "§a¡Conexión exitosa a la base de datos!"
|
||||
database-error: "§cOcurrió un error de base de datos. Revisa la consola para más detalles."
|
||||
player-not-found: "§c¡Jugador no encontrado!"
|
||||
usage: "§cUso: /balsync [reload|save|load]"
|
||||
balance-external-change: "§eTu saldo fue actualizado externamente: §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aTu saldo fue restablecido a §e${0}§a antes de cargarse desde la base de datos."
|
||||
offline-change-detected: "§7Cambio de saldo offline detectado y guardado."
|
||||
12
src/main/resources/messages_fr.yml
Normal file
12
src/main/resources/messages_fr.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
prefix: "§8[§6BalSync§8] §7"
|
||||
no-permission: "§cVous n'avez pas la permission d'utiliser cette commande !"
|
||||
config-reloaded: "§aConfiguration rechargée avec succès !"
|
||||
balance-loaded: "§aVotre solde a été synchronisé avec la base de données."
|
||||
balance-saved: "§aTous les soldes des joueurs ont été enregistrés dans la base de données."
|
||||
database-connected: "§aConnexion réussie à la base de données !"
|
||||
database-error: "§cUne erreur de base de données est survenue. Vérifiez la console pour plus de détails."
|
||||
player-not-found: "§cJoueur introuvable !"
|
||||
usage: "§cUtilisation : /balsync [reload|save|load]"
|
||||
balance-external-change: "§eVotre solde a été mis à jour extérieurement : §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aVotre solde a été réinitialisé à §e${0}§a avant le chargement depuis la base de données."
|
||||
offline-change-detected: "§7Modification du solde hors ligne détectée et enregistrée."
|
||||
12
src/main/resources/messages_pl.yml
Normal file
12
src/main/resources/messages_pl.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
prefix: "§8[§6BalSync§8] §7"
|
||||
no-permission: "§cVous n'avez pas la permission d'utiliser cette commande !"
|
||||
config-reloaded: "§aConfiguration rechargée avec succès !"
|
||||
balance-loaded: "§aVotre solde a été synchronisé avec la base de données."
|
||||
balance-saved: "§aTous les soldes des joueurs ont été enregistrés dans la base de données."
|
||||
database-connected: "§aConnexion réussie à la base de données !"
|
||||
database-error: "§cUne erreur de base de données est survenue. Vérifiez la console pour plus de détails."
|
||||
player-not-found: "§cJoueur introuvable !"
|
||||
usage: "§cUtilisation : /balsync [reload|save|load]"
|
||||
balance-external-change: "§eVotre solde a été mis à jour extérieurement : §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aVotre solde a été réinitialisé à §e${0}§a avant le chargement depuis la base de données."
|
||||
offline-change-detected: "§7Modification du solde hors ligne détectée et enregistrée."
|
||||
12
src/main/resources/messages_pt-br.yml
Normal file
12
src/main/resources/messages_pt-br.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
prefix: "§8[§6BalSync§8] §7"
|
||||
no-permission: "§cVocê não tem permissão para usar este comando!"
|
||||
config-reloaded: "§aConfiguração recarregada com sucesso!"
|
||||
balance-loaded: "§aSeu saldo foi sincronizado com o banco de dados."
|
||||
balance-saved: "§aTodos os saldos dos jogadores foram salvos no banco de dados."
|
||||
database-connected: "§aConectado com sucesso ao banco de dados!"
|
||||
database-error: "§cOcorreu um erro no banco de dados. Verifique o console para mais detalhes."
|
||||
player-not-found: "§cJogador não encontrado!"
|
||||
usage: "§cUso: /balsync [reload|save|load]"
|
||||
balance-external-change: "§eSeu saldo foi atualizado externamente: §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aSeu saldo foi redefinido para §e${0}§a antes de carregar do banco de dados."
|
||||
offline-change-detected: "§7Alteração de saldo offline detectada e salva."
|
||||
12
src/main/resources/messages_ru.yml
Normal file
12
src/main/resources/messages_ru.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
prefix: "§8[§6BalSync§8] §7"
|
||||
no-permission: "§cУ вас нет прав для использования этой команды!"
|
||||
config-reloaded: "§aКонфигурация успешно перезагружена!"
|
||||
balance-loaded: "§aВаш баланс был синхронизирован с базой данных."
|
||||
balance-saved: "§aВсе балансы игроков были сохранены в базе данных."
|
||||
database-connected: "§aУспешно подключено к базе данных!"
|
||||
database-error: "§cПроизошла ошибка базы данных. Проверьте консоль для деталей."
|
||||
player-not-found: "§cИгрок не найден!"
|
||||
usage: "§cИспользование: /balsync [reload|save|load]"
|
||||
balance-external-change: "§eВаш баланс был обновлён извне: §6{old} §e→ §6{new}"
|
||||
balance-reset: "§aВаш баланс был сброшен до §e${0}§a перед загрузкой из базы данных."
|
||||
offline-change-detected: "§7Обнаружено и сохранено изменение баланса оффлайн."
|
||||
20
src/main/resources/plugin.yml
Normal file
20
src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: BalSync
|
||||
version: 1.0
|
||||
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: /<command> [reload|save|load]
|
||||
permission: balsync.admin
|
||||
|
||||
permissions:
|
||||
balsync.admin:
|
||||
description: Allows access to all BalSync commands
|
||||
default: op
|
||||
Reference in New Issue
Block a user