Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-28 22:17:42 +02:00
parent 10f5fb03a7
commit 4ff55a3736
2 changed files with 312 additions and 85 deletions

View File

@@ -921,13 +921,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
anyCode = true;
} else if ("fly_abo".equals(type)) {
// Fly-Abo: Tage-basiertes Abonnement mit tägl. Stunden-Limit
int days = cmdObj.has("days")
? cmdObj.get("days").getAsInt() : 30;
// Fly-Abo: monatliche Abbuchung, Preis des Shop-Artikels = Monatsbeitrag
String label = cmdObj.has("label")
? cmdObj.get("label").getAsString()
: "Fly-Abo " + days + " Tage";
flyAboManager.grantAbo(player, days, label);
? cmdObj.get("label").getAsString() : "Fly-Abo";
int monthlyPrice = (int) data.price;
flyAboManager.grantAbo(player, label, monthlyPrice);
anyCode = true;
} else if ("generic".equals(type) && cmdObj.has("cmd")) {
@@ -1329,11 +1327,20 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
p.sendMessage(ChatColor.GRAY + " Kein aktives Fly-Abo.");
p.sendMessage(ChatColor.GRAY + " Im Shop erhältlich: " + ChatColor.AQUA + "viper-network.de");
} else {
p.sendMessage(ChatColor.AQUA + " Status: " + ChatColor.GREEN + "✔ Aktiv");
p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label);
p.sendMessage(ChatColor.AQUA + " Läuft ab: " + ChatColor.YELLOW + abo.expiresAt);
if (abo.cancelled)
p.sendMessage(ChatColor.RED + " ⚠ Kündigung vorgemerkt läuft zum o.g. Datum aus");
p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label);
p.sendMessage(ChatColor.AQUA + " Preis: " + ChatColor.WHITE + abo.monthlyPrice + " " + currency + " / Monat");
if (abo.cancelled) {
String reason = "payment_failed".equals(abo.cancellationReason)
? " (Zahlung fehlgeschlagen)" : " (durch dich)";
p.sendMessage(ChatColor.RED + " Status: ⚠ Gekündigt" + reason);
p.sendMessage(ChatColor.AQUA + " Aktiv bis: " + ChatColor.YELLOW + abo.periodEnd);
p.sendMessage(ChatColor.GRAY + " Danach kein Fly-Abo mehr aktiv.");
} else {
p.sendMessage(ChatColor.AQUA + " Status: " + ChatColor.GREEN + "✔ Aktiv");
p.sendMessage(ChatColor.AQUA + " Aktiv bis: " + ChatColor.YELLOW + abo.periodEnd);
p.sendMessage(ChatColor.AQUA + " Nächste Abbuchung: " + ChatColor.WHITE + abo.nextBillingDate);
p.sendMessage(ChatColor.GRAY + " Kündigen zum Monatsende: " + ChatColor.WHITE + "/flyabocancel");
}
int usedSec = flyAboManager.getUsedTodaySec(p.getName());
int maxSec = flyAboMaxDailySec;
int remSec = Math.max(0, maxSec - usedSec);
@@ -1353,17 +1360,24 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
/** /flyabocancel Abo zum Ablauf kündigen */
/** /flyabocancel Abo zum Ende des laufenden Monats kündigen */
private class FlyAboCancelCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!(sender instanceof Player)) { sender.sendMessage("Nur für Spieler."); return true; }
Player p = (Player) sender;
// Letzten Tag des laufenden Monats berechnen
java.time.LocalDate today = java.time.LocalDate.now();
java.time.LocalDate monthEnd = today.withDayOfMonth(today.lengthOfMonth());
String endDateStr = monthEnd.format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy"));
// Bestätigung erforderlich
if (args.length == 0 || !args[0].equalsIgnoreCase("confirm")) {
p.sendMessage(ChatColor.YELLOW + "⚠ Möchtest du dein Fly-Abo wirklich kündigen?");
p.sendMessage(ChatColor.GRAY + " Das Abo läuft bis zum Ablaufdatum weiter.");
p.sendMessage(ChatColor.GRAY + " Das Abo läuft noch bis zum " + ChatColor.WHITE + endDateStr + ChatColor.GRAY + " weiter.");
p.sendMessage(ChatColor.GRAY + " Ab dem 1. des Folgemonats wird es nicht mehr verlängert.");
p.sendMessage(ChatColor.WHITE + " Bestätigen: " + ChatColor.RED + "/flyabocancel confirm");
return true;
}
@@ -1372,11 +1386,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
boolean ok = flyAboManager.cancelAbo(p.getName());
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
if (ok) {
p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo wurde zur Kündigung vorgemerkt.");
p.sendMessage(ChatColor.GRAY + " Es bleibt bis zum Ablaufdatum aktiv.");
p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo wurde zum " + ChatColor.WHITE + endDateStr + ChatColor.YELLOW + " gekündigt.");
p.sendMessage(ChatColor.GRAY + " Bis dahin kannst du es wie gewohnt nutzen.");
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.8F);
} else {
p.sendMessage(ChatColor.RED + "✗ Kein aktives Fly-Abo gefunden.");
p.sendMessage(ChatColor.RED + "✗ Kein aktives Fly-Abo gefunden oder bereits gekündigt.");
}
});
}
@@ -1385,7 +1399,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
/** /flyabogive <Spieler> <Tage> [Label] Admin gibt Abo manuell */
/** /flyabogive <Spieler> <Monatspreis> [Label] Admin gibt Abo manuell */
private class FlyAboGiveCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command,
@@ -1395,33 +1409,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
return true;
}
if (args.length < 2) {
sender.sendMessage(ChatColor.YELLOW + "Verwendung: /flyabogive <Spieler> <Tage> [Label]");
sender.sendMessage(ChatColor.YELLOW + "Verwendung: /flyabogive <Spieler> <Monatspreis> [Label]");
sender.sendMessage(ChatColor.GRAY + " Preis = 0 → kostenloses Abo (kein Vault-Abzug)");
return true;
}
String targetName = args[0];
int days;
try { days = Integer.parseInt(args[1]); }
int monthlyPrice;
try { monthlyPrice = Integer.parseInt(args[1]); }
catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Tage muss eine Zahl sein.");
sender.sendMessage(ChatColor.RED + "Monatspreis muss eine Zahl sein.");
return true;
}
String aboLabel = args.length >= 3
? String.join(" ", Arrays.copyOfRange(args, 2, args.length))
: "Fly-Abo " + days + " Tage";
: "Fly-Abo";
Player target = Bukkit.getPlayer(targetName);
new BukkitRunnable() {
@Override public void run() {
flyAboManager.grantAboByName(targetName, days, aboLabel);
flyAboManager.grantAboByName(targetName, aboLabel, monthlyPrice);
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo (" + days + " Tage) an "
+ ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben.");
sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo an "
+ ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben."
+ ChatColor.GRAY + " (Monatspreis: " + monthlyPrice + " " + currency + ")");
if (target != null && target.isOnline()) {
target.sendMessage("");
target.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════");
target.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!");
target.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + aboLabel);
target.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + aboLabel);
target.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + monthlyPrice + " " + currency);
target.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE
+ flyManager.formatTime(flyAboMaxDailySec));
target.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats");
target.sendMessage(ChatColor.GRAY + " Status: /flyabo");
target.sendMessage(ChatColor.GOLD + " ════════════════════════════");
target.sendMessage("");
@@ -2191,16 +2209,27 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
// ── Fly-Abo CRUD ──────────────────────────────────────────────────
void createAboTable() {
// Migration: alte expires_at-Tabelle auf neues Schema upgraden
String sqlAbo =
"CREATE TABLE IF NOT EXISTS wis_fly_abos ("
+ " id INT AUTO_INCREMENT PRIMARY KEY,"
+ " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,"
+ " label VARCHAR(128) NOT NULL,"
+ " cancelled TINYINT(1) NOT NULL DEFAULT 0,"
+ " expires_at DATETIME NOT NULL,"
+ " granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ " id INT AUTO_INCREMENT PRIMARY KEY,"
+ " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,"
+ " label VARCHAR(128) NOT NULL,"
+ " monthly_price INT NOT NULL DEFAULT 0,"
+ " cancelled TINYINT(1) NOT NULL DEFAULT 0,"
+ " cancellation_reason VARCHAR(32) DEFAULT NULL,"
+ " next_billing_date DATE NOT NULL,"
+ " period_end DATE NOT NULL,"
+ " granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ " UNIQUE KEY uq_player (player_name)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
// Migrations: Spalten ergänzen falls Tabelle schon in alter Form existiert
String[] migrations = {
"ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS monthly_price INT NOT NULL DEFAULT 0 AFTER label",
"ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS cancellation_reason VARCHAR(32) DEFAULT NULL AFTER cancelled",
"ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS next_billing_date DATE NOT NULL DEFAULT (CURDATE()) AFTER cancellation_reason",
"ALTER TABLE wis_fly_abos ADD COLUMN IF NOT EXISTS period_end DATE NOT NULL DEFAULT (LAST_DAY(CURDATE())) AFTER next_billing_date",
};
String sqlUsage =
"CREATE TABLE IF NOT EXISTS wis_fly_abo_usage ("
+ " id INT AUTO_INCREMENT PRIMARY KEY,"
@@ -2212,38 +2241,53 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
try (Statement stmt = connection.createStatement()) {
stmt.execute(sqlAbo);
stmt.execute(sqlUsage);
for (String m : migrations) {
try { stmt.execute(m); } catch (SQLException ignored) {}
}
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Abo-Tabellen-Erstellung fehlgeschlagen", e);
}
}
void saveAbo(String playerName, int days, String label) {
/**
* Neues Abo aktivieren oder bestehendes verlängern.
* next_billing_date = 1. des Folgemonats
* period_end = letzter Tag des laufenden Monats (oder des aktuellen Abomonats)
*/
void saveAbo(String playerName, String label, int monthlyPrice) {
String sql =
"INSERT INTO wis_fly_abos (player_name, label, cancelled, expires_at)"
+ " VALUES (?, ?, 0, DATE_ADD(NOW(), INTERVAL ? DAY))"
"INSERT INTO wis_fly_abos"
+ " (player_name, label, monthly_price, cancelled, cancellation_reason,"
+ " next_billing_date, period_end)"
+ " VALUES (?, ?, ?, 0, NULL,"
+ " DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01'),"
+ " LAST_DAY(CURDATE()))"
+ " ON DUPLICATE KEY UPDATE"
+ " label = ?, cancelled = 0,"
+ " expires_at = IF(expires_at > NOW(),"
+ " DATE_ADD(expires_at, INTERVAL ? DAY),"
+ " DATE_ADD(NOW(), INTERVAL ? DAY)),"
+ " label = ?, monthly_price = ?, cancelled = 0, cancellation_reason = NULL,"
+ " next_billing_date = DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01'),"
+ " period_end = LAST_DAY(CURDATE()),"
+ " granted_at = NOW()";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.setString(2, label);
ps.setInt(3, days);
ps.setInt(3, monthlyPrice);
ps.setString(4, label);
ps.setInt(5, days);
ps.setInt(6, days);
ps.setInt(5, monthlyPrice);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "saveAbo SQL Fehler", e);
}
}
/**
* Abo durch Spieler kündigen bleibt bis period_end aktiv.
* period_end wird auf LAST_DAY(CURDATE()) gesetzt (Ende des laufenden Monats).
*/
boolean cancelAbo(String playerName) {
String sql =
"UPDATE wis_fly_abos SET cancelled = 1"
+ " WHERE player_name = ? AND expires_at > NOW() AND cancelled = 0";
"UPDATE wis_fly_abos SET cancelled = 1, cancellation_reason = 'user',"
+ " period_end = LAST_DAY(CURDATE())"
+ " WHERE player_name = ? AND period_end >= CURDATE() AND cancelled = 0";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
return ps.executeUpdate() == 1;
@@ -2253,20 +2297,88 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
/**
* Abo wegen Zahlungsausfall kündigen sofort zum period_end des laufenden Monats.
*/
void cancelAboPaymentFailed(String playerName) {
String sql =
"UPDATE wis_fly_abos SET cancelled = 1, cancellation_reason = 'payment_failed',"
+ " period_end = LAST_DAY(CURDATE())"
+ " WHERE player_name = ? AND cancelled = 0";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "cancelAboPaymentFailed SQL Fehler", e);
}
}
/**
* Billing verlängern: next_billing_date auf 1. des übernächsten Monats,
* period_end auf letzten Tag des neuen Monats setzen.
*/
void renewAbo(String playerName) {
String sql =
"UPDATE wis_fly_abos"
+ " SET next_billing_date = DATE_FORMAT(DATE_ADD(next_billing_date, INTERVAL 1 MONTH), '%Y-%m-01'),"
+ " period_end = LAST_DAY(next_billing_date)"
+ " WHERE player_name = ? AND cancelled = 0";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "renewAbo SQL Fehler", e);
}
}
/**
* Alle Abos holen die heute fällig sind (next_billing_date = heute, nicht gekündigt).
*/
List<FlyAboManager.AboEntry> getDueBillings() {
List<FlyAboManager.AboEntry> list = new ArrayList<>();
String sql =
"SELECT player_name, label, monthly_price, cancelled, cancellation_reason,"
+ " DATE_FORMAT(next_billing_date, '%d.%m.%Y') AS billing_fmt,"
+ " DATE_FORMAT(period_end, '%d.%m.%Y') AS period_fmt"
+ " FROM wis_fly_abos"
+ " WHERE next_billing_date <= CURDATE() AND cancelled = 0";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next())
list.add(new FlyAboManager.AboEntry(
rs.getString("player_name"),
rs.getString("label"),
rs.getInt("monthly_price"),
false,
null,
rs.getString("billing_fmt"),
rs.getString("period_fmt")));
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getDueBillings SQL Fehler", e);
}
return list;
}
FlyAboManager.AboEntry getActiveAbo(String playerName) {
String sql =
"SELECT label, cancelled,"
+ " DATE_FORMAT(expires_at, '%d.%m.%Y') AS expires_fmt"
"SELECT label, monthly_price, cancelled, cancellation_reason,"
+ " DATE_FORMAT(next_billing_date, '%d.%m.%Y') AS billing_fmt,"
+ " DATE_FORMAT(period_end, '%d.%m.%Y') AS period_fmt"
+ " FROM wis_fly_abos"
+ " WHERE player_name = ? AND expires_at > NOW()";
+ " WHERE player_name = ? AND period_end >= CURDATE()";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next())
return new FlyAboManager.AboEntry(
rs.getString("player_name"),
rs.getString("label"),
rs.getString("expires_fmt"),
rs.getInt("cancelled") == 1);
rs.getInt("monthly_price"),
rs.getInt("cancelled") == 1,
rs.getString("cancellation_reason"),
rs.getString("billing_fmt"),
rs.getString("period_fmt"));
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getActiveAbo SQL Fehler", e);
@@ -2275,7 +2387,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
void cleanupExpiredAbos(String playerName) {
String sql = "DELETE FROM wis_fly_abos WHERE player_name = ? AND expires_at <= NOW()";
String sql = "DELETE FROM wis_fly_abos WHERE player_name = ? AND period_end < CURDATE()";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.executeUpdate();
@@ -2957,36 +3069,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
db.createAboTable();
}
// ── Abo vergeben (über Order) ──────────────────────────────────
// ── Abo aktivieren (über Order) ────────────────────────────────
void grantAbo(Player player, int days, String label) {
grantAboByName(player.getName(), days, label);
void grantAbo(Player player, String label, int monthlyPrice) {
db.saveAbo(player.getName(), label, monthlyPrice);
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage("");
player.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════");
player.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!");
player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label);
player.sendMessage(ChatColor.GRAY + " Laufzeit: " + ChatColor.WHITE + days + " Tag(e)");
player.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE
player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label);
player.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + monthlyPrice + " " + currency);
player.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE
+ flyManager.formatTime(flyAboMaxDailySec));
player.sendMessage(ChatColor.GRAY + " Status: " + ChatColor.WHITE + "/flyabo");
player.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats");
player.sendMessage(ChatColor.GRAY + " Status: " + ChatColor.WHITE + "/flyabo");
player.sendMessage(ChatColor.GOLD + " ════════════════════════════");
player.sendMessage("");
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.2F);
});
}
void grantAboByName(String playerName, int days, String label) {
db.saveAbo(playerName, days, label);
void grantAboByName(String playerName, String label, int monthlyPrice) {
db.saveAbo(playerName, label, monthlyPrice);
}
// ── Abo kündigen ───────────────────────────────────────────────
// ── Kündigung durch Spieler ────────────────────────────────────
boolean cancelAbo(String playerName) {
return db.cancelAbo(playerName);
}
// ── Abo-Status lesen ──────────────────────────────────────────
// ── Status lesen ──────────────────────────────────────────────
AboEntry getActiveAbo(String playerName) {
return db.getActiveAbo(playerName);
@@ -2996,21 +3109,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
return db.getUsedTodaySec(playerName);
}
// ── Beim Join: Abo prüfen ─────────────────────────────────────
// ── Beim Join: Abo prüfen + ggf. Zahlung-fehlgeschlagen-Meldung ──
void onPlayerJoin(Player player) {
AboEntry abo = db.getActiveAbo(player.getName());
if (abo == null) return;
// Abgelaufenes Abo bereinigen
// Abgelaufene Einträge bereinigen
db.cleanupExpiredAbos(player.getName());
abo = db.getActiveAbo(player.getName());
if (abo == null) {
Bukkit.getScheduler().runTask(plugin, () ->
player.sendMessage(ChatColor.RED + "✈ Dein Fly-Abo ist abgelaufen."));
return;
if (abo == null) return;
// Zahlungsausfall-Hinweis
final AboEntry finalAbo = abo;
if ("payment_failed".equals(abo.cancellationReason)) {
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage("");
player.sendMessage(ChatColor.RED + "✈ ════════════════════════════");
player.sendMessage(ChatColor.RED + " Fly-Abo: Zahlung fehlgeschlagen!");
player.sendMessage(ChatColor.GRAY + " Dein Abo konnte nicht verlängert werden,");
player.sendMessage(ChatColor.GRAY + " da dein Kontostand nicht ausreichte.");
player.sendMessage(ChatColor.GRAY + " Es läuft am " + ChatColor.YELLOW + finalAbo.periodEnd
+ ChatColor.GRAY + " aus.");
player.sendMessage(ChatColor.WHITE + " Neues Abo: " + ChatColor.AQUA + "viper-network.de");
player.sendMessage(ChatColor.RED + " ════════════════════════════");
player.sendMessage("");
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.5F);
});
}
if (abo.cancelled) return; // Gekündigtes Abo: kein Fly mehr gewähren wenn period_end heute
int usedToday = db.getUsedTodaySec(player.getName());
int remaining = Math.max(0, flyAboMaxDailySec - usedToday);
if (remaining <= 0) {
@@ -3023,27 +3152,23 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
return;
}
// Fly mit verbleibender Tageszeit gewähren, aber in Abo-Modus
final int grantSec = remaining;
final String aboLabel = abo.label;
Bukkit.getScheduler().runTask(plugin, () -> {
flyManager.grantFlyWithAboTracking(player, grantSec, aboLabel,
player.getName(), flyAboMaxDailySec);
});
final int grantSec = remaining;
Bukkit.getScheduler().runTask(plugin, () ->
flyManager.grantFlyWithAboTracking(player, grantSec, finalAbo.label,
player.getName(), flyAboMaxDailySec));
}
// ── Täglicher Reset-Check ─────────────────────────────────────
// ── Monatlicher Billing-Check (läuft täglich) ─────────────────
void startDailyResetChecker() {
// Alle 5 Minuten prüfen ob Tagesdatum gewechselt hat → online Spieler neu versorgen
// Alle 5 Minuten: online Spieler mit Abo ohne aktiven Fly neu versorgen
new BukkitRunnable() {
@Override public void run() {
for (Player p : Bukkit.getOnlinePlayers()) {
new BukkitRunnable() {
@Override public void run() {
AboEntry abo = db.getActiveAbo(p.getName());
if (abo == null) return;
// Wenn Tageslimit noch nicht erreicht und kein Fly aktiv → erneut gewähren
if (abo == null || abo.cancelled) return;
int usedToday = db.getUsedTodaySec(p.getName());
int remaining = Math.max(0, flyAboMaxDailySec - usedToday);
if (remaining > 0
@@ -3056,18 +3181,119 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}.runTaskAsynchronously(plugin);
}
}
}.runTaskTimer(plugin, 20L * 300, 20L * 300); // alle 5 Minuten
}.runTaskTimer(plugin, 20L * 300, 20L * 300);
// Täglich um ~00:05 Uhr: Billing-Check für den 1. des Monats
// Berechne Ticks bis 00:05 Uhr (approximiert: sofort starten, dann täglich wiederholen)
new BukkitRunnable() {
@Override public void run() {
java.time.LocalDate today = java.time.LocalDate.now();
if (today.getDayOfMonth() != 1) return; // Nur am 1. des Monats ausführen
new BukkitRunnable() {
@Override public void run() {
List<FlyAboManager.AboEntry> due = db.getDueBillings();
if (due.isEmpty()) return;
getLogger().info("[FlyAbo] Billing-Tag: " + due.size() + " Abo(s) zu verlängern.");
for (FlyAboManager.AboEntry entry : due) {
String playerName = entry.playerName;
int price = entry.monthlyPrice;
// Vault-Abbuchung auf Main-Thread
Bukkit.getScheduler().runTask(plugin, () -> {
@SuppressWarnings("deprecation")
org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(playerName);
if (op == null || !op.hasPlayedBefore()) {
// Spieler unbekannt → überspringen (nächste Nacht nochmal)
return;
}
boolean hasBalance = (price == 0) || econ.getBalance(op) >= price;
if (hasBalance) {
if (price > 0) {
econ.withdrawPlayer(op, price);
if (!incomeReceiver.isEmpty()) {
@SuppressWarnings("deprecation")
org.bukkit.OfflinePlayer recv =
Bukkit.getOfflinePlayer(incomeReceiver);
if (recv != null && recv.hasPlayedBefore())
econ.depositPlayer(recv, price);
}
}
// DB-Verlängerung async
new BukkitRunnable() {
@Override public void run() {
db.renewAbo(playerName);
getLogger().info("[FlyAbo] ✔ Abo verlängert: "
+ playerName + " (" + price + " " + currency + ")");
}
}.runTaskAsynchronously(plugin);
// Online-Benachrichtigung
Player online = Bukkit.getPlayer(playerName);
if (online != null && online.isOnline()) {
online.sendMessage("");
online.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════");
online.sendMessage(ChatColor.GREEN + " Fly-Abo verlängert!");
online.sendMessage(ChatColor.GRAY + " Abgebucht: "
+ ChatColor.WHITE + price + " " + currency);
online.sendMessage(ChatColor.GRAY + " Läuft bis: "
+ ChatColor.YELLOW + "Ende des Monats");
online.sendMessage(ChatColor.GOLD + " ════════════════════════════");
online.sendMessage("");
online.playSound(online.getLocation(),
Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
}
} else {
// Zahlung fehlgeschlagen → Abo kündigen
new BukkitRunnable() {
@Override public void run() {
db.cancelAboPaymentFailed(playerName);
getLogger().warning("[FlyAbo] ✘ Zahlung fehlgeschlagen: "
+ playerName + " (benötigt: " + price + " " + currency + ")");
}
}.runTaskAsynchronously(plugin);
Player online = Bukkit.getPlayer(playerName);
if (online != null && online.isOnline()) {
online.sendMessage("");
online.sendMessage(ChatColor.RED + "✈ ════════════════════════════");
online.sendMessage(ChatColor.RED + " Fly-Abo: Zahlung fehlgeschlagen!");
online.sendMessage(ChatColor.GRAY + " Benötigt: "
+ ChatColor.WHITE + price + " " + currency);
online.sendMessage(ChatColor.GRAY + " Dein Abo läuft Ende des Monats aus.");
online.sendMessage(ChatColor.WHITE + " Verlängern: " + ChatColor.AQUA + "viper-network.de");
online.sendMessage(ChatColor.RED + " ════════════════════════════");
online.sendMessage("");
online.playSound(online.getLocation(),
Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.5F);
}
}
});
}
}
}.runTaskAsynchronously(plugin);
}
}.runTaskTimer(plugin, 20L * 60 * 5, 20L * 60 * 60 * 24); // Start nach 5 min, dann täglich
}
// ── Data class ────────────────────────────────────────────────
static class AboEntry {
final String label, expiresAt;
final String playerName, label, cancellationReason, nextBillingDate, periodEnd;
final int monthlyPrice;
final boolean cancelled;
AboEntry(String label, String expiresAt, boolean cancelled) {
this.label = label;
this.expiresAt = expiresAt;
this.cancelled = cancelled;
AboEntry(String playerName, String label, int monthlyPrice,
boolean cancelled, String cancellationReason,
String nextBillingDate, String periodEnd) {
this.playerName = playerName;
this.label = label;
this.monthlyPrice = monthlyPrice;
this.cancelled = cancelled;
this.cancellationReason = cancellationReason;
this.nextBillingDate = nextBillingDate;
this.periodEnd = periodEnd;
}
}
}

View File

@@ -52,6 +52,7 @@ sell:
# Hinweis: Den Basis-Ankaufspreis konfigurierst du im WP-Admin
# unter Items → <Item bearbeiten> → "Ankauf aktivieren"
price-offset: -10.0
# ──────────────────────────────────────────────
# Fly-Abo Einstellungen
# ──────────────────────────────────────────────