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; anyCode = true;
} else if ("fly_abo".equals(type)) { } else if ("fly_abo".equals(type)) {
// Fly-Abo: Tage-basiertes Abonnement mit tägl. Stunden-Limit // Fly-Abo: monatliche Abbuchung, Preis des Shop-Artikels = Monatsbeitrag
int days = cmdObj.has("days")
? cmdObj.get("days").getAsInt() : 30;
String label = cmdObj.has("label") String label = cmdObj.has("label")
? cmdObj.get("label").getAsString() ? cmdObj.get("label").getAsString() : "Fly-Abo";
: "Fly-Abo " + days + " Tage"; int monthlyPrice = (int) data.price;
flyAboManager.grantAbo(player, days, label); flyAboManager.grantAbo(player, label, monthlyPrice);
anyCode = true; anyCode = true;
} else if ("generic".equals(type) && cmdObj.has("cmd")) { } 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 + " Kein aktives Fly-Abo.");
p.sendMessage(ChatColor.GRAY + " Im Shop erhältlich: " + ChatColor.AQUA + "viper-network.de"); p.sendMessage(ChatColor.GRAY + " Im Shop erhältlich: " + ChatColor.AQUA + "viper-network.de");
} else { } else {
p.sendMessage(ChatColor.AQUA + " Status: " + ChatColor.GREEN + "✔ Aktiv");
p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label); p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label);
p.sendMessage(ChatColor.AQUA + " Läuft ab: " + ChatColor.YELLOW + abo.expiresAt); p.sendMessage(ChatColor.AQUA + " Preis: " + ChatColor.WHITE + abo.monthlyPrice + " " + currency + " / Monat");
if (abo.cancelled) if (abo.cancelled) {
p.sendMessage(ChatColor.RED + " ⚠ Kündigung vorgemerkt läuft zum o.g. Datum aus"); 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 usedSec = flyAboManager.getUsedTodaySec(p.getName());
int maxSec = flyAboMaxDailySec; int maxSec = flyAboMaxDailySec;
int remSec = Math.max(0, maxSec - usedSec); 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 { private class FlyAboCancelCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) { String label, String[] args) {
if (!(sender instanceof Player)) { sender.sendMessage("Nur für Spieler."); return true; } if (!(sender instanceof Player)) { sender.sendMessage("Nur für Spieler."); return true; }
Player p = (Player) sender; 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 // Bestätigung erforderlich
if (args.length == 0 || !args[0].equalsIgnoreCase("confirm")) { if (args.length == 0 || !args[0].equalsIgnoreCase("confirm")) {
p.sendMessage(ChatColor.YELLOW + "⚠ Möchtest du dein Fly-Abo wirklich kündigen?"); 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"); p.sendMessage(ChatColor.WHITE + " Bestätigen: " + ChatColor.RED + "/flyabocancel confirm");
return true; return true;
} }
@@ -1372,11 +1386,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
boolean ok = flyAboManager.cancelAbo(p.getName()); boolean ok = flyAboManager.cancelAbo(p.getName());
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
if (ok) { if (ok) {
p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo wurde zur Kündigung vorgemerkt."); p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo wurde zum " + ChatColor.WHITE + endDateStr + ChatColor.YELLOW + " gekündigt.");
p.sendMessage(ChatColor.GRAY + " Es bleibt bis zum Ablaufdatum aktiv."); 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); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.8F);
} else { } 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 { private class FlyAboGiveCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, public boolean onCommand(CommandSender sender, Command command,
@@ -1395,33 +1409,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
return true; return true;
} }
if (args.length < 2) { 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; return true;
} }
String targetName = args[0]; String targetName = args[0];
int days; int monthlyPrice;
try { days = Integer.parseInt(args[1]); } try { monthlyPrice = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Tage muss eine Zahl sein."); sender.sendMessage(ChatColor.RED + "Monatspreis muss eine Zahl sein.");
return true; return true;
} }
String aboLabel = args.length >= 3 String aboLabel = args.length >= 3
? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) ? String.join(" ", Arrays.copyOfRange(args, 2, args.length))
: "Fly-Abo " + days + " Tage"; : "Fly-Abo";
Player target = Bukkit.getPlayer(targetName); Player target = Bukkit.getPlayer(targetName);
new BukkitRunnable() { new BukkitRunnable() {
@Override public void run() { @Override public void run() {
flyAboManager.grantAboByName(targetName, days, aboLabel); flyAboManager.grantAboByName(targetName, aboLabel, monthlyPrice);
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo (" + days + " Tage) an " sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo an "
+ ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben."); + ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben."
+ ChatColor.GRAY + " (Monatspreis: " + monthlyPrice + " " + currency + ")");
if (target != null && target.isOnline()) { if (target != null && target.isOnline()) {
target.sendMessage(""); target.sendMessage("");
target.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════"); target.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════");
target.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!"); 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 target.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE
+ flyManager.formatTime(flyAboMaxDailySec)); + flyManager.formatTime(flyAboMaxDailySec));
target.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats");
target.sendMessage(ChatColor.GRAY + " Status: /flyabo"); target.sendMessage(ChatColor.GRAY + " Status: /flyabo");
target.sendMessage(ChatColor.GOLD + " ════════════════════════════"); target.sendMessage(ChatColor.GOLD + " ════════════════════════════");
target.sendMessage(""); target.sendMessage("");
@@ -2191,16 +2209,27 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
// ── Fly-Abo CRUD ────────────────────────────────────────────────── // ── Fly-Abo CRUD ──────────────────────────────────────────────────
void createAboTable() { void createAboTable() {
// Migration: alte expires_at-Tabelle auf neues Schema upgraden
String sqlAbo = String sqlAbo =
"CREATE TABLE IF NOT EXISTS wis_fly_abos (" "CREATE TABLE IF NOT EXISTS wis_fly_abos ("
+ " id INT AUTO_INCREMENT PRIMARY KEY," + " id INT AUTO_INCREMENT PRIMARY KEY,"
+ " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci," + " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,"
+ " label VARCHAR(128) NOT NULL," + " label VARCHAR(128) NOT NULL,"
+ " monthly_price INT NOT NULL DEFAULT 0,"
+ " cancelled TINYINT(1) NOT NULL DEFAULT 0," + " cancelled TINYINT(1) NOT NULL DEFAULT 0,"
+ " expires_at DATETIME NOT NULL," + " 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," + " granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ " UNIQUE KEY uq_player (player_name)" + " UNIQUE KEY uq_player (player_name)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; + ") 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 = String sqlUsage =
"CREATE TABLE IF NOT EXISTS wis_fly_abo_usage (" "CREATE TABLE IF NOT EXISTS wis_fly_abo_usage ("
+ " id INT AUTO_INCREMENT PRIMARY KEY," + " id INT AUTO_INCREMENT PRIMARY KEY,"
@@ -2212,38 +2241,53 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
stmt.execute(sqlAbo); stmt.execute(sqlAbo);
stmt.execute(sqlUsage); stmt.execute(sqlUsage);
for (String m : migrations) {
try { stmt.execute(m); } catch (SQLException ignored) {}
}
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Abo-Tabellen-Erstellung fehlgeschlagen", 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 = String sql =
"INSERT INTO wis_fly_abos (player_name, label, cancelled, expires_at)" "INSERT INTO wis_fly_abos"
+ " VALUES (?, ?, 0, DATE_ADD(NOW(), INTERVAL ? DAY))" + " (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" + " ON DUPLICATE KEY UPDATE"
+ " label = ?, cancelled = 0," + " label = ?, monthly_price = ?, cancelled = 0, cancellation_reason = NULL,"
+ " expires_at = IF(expires_at > NOW()," + " next_billing_date = DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01'),"
+ " DATE_ADD(expires_at, INTERVAL ? DAY)," + " period_end = LAST_DAY(CURDATE()),"
+ " DATE_ADD(NOW(), INTERVAL ? DAY)),"
+ " granted_at = NOW()"; + " granted_at = NOW()";
try (PreparedStatement ps = connection.prepareStatement(sql)) { try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName); ps.setString(1, playerName);
ps.setString(2, label); ps.setString(2, label);
ps.setInt(3, days); ps.setInt(3, monthlyPrice);
ps.setString(4, label); ps.setString(4, label);
ps.setInt(5, days); ps.setInt(5, monthlyPrice);
ps.setInt(6, days);
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "saveAbo SQL Fehler", 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) { boolean cancelAbo(String playerName) {
String sql = String sql =
"UPDATE wis_fly_abos SET cancelled = 1" "UPDATE wis_fly_abos SET cancelled = 1, cancellation_reason = 'user',"
+ " WHERE player_name = ? AND expires_at > NOW() AND cancelled = 0"; + " period_end = LAST_DAY(CURDATE())"
+ " WHERE player_name = ? AND period_end >= CURDATE() AND cancelled = 0";
try (PreparedStatement ps = connection.prepareStatement(sql)) { try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName); ps.setString(1, playerName);
return ps.executeUpdate() == 1; 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) { FlyAboManager.AboEntry getActiveAbo(String playerName) {
String sql = String sql =
"SELECT label, cancelled," "SELECT label, monthly_price, cancelled, cancellation_reason,"
+ " DATE_FORMAT(expires_at, '%d.%m.%Y') AS expires_fmt" + " 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" + " FROM wis_fly_abos"
+ " WHERE player_name = ? AND expires_at > NOW()"; + " WHERE player_name = ? AND period_end >= CURDATE()";
try (PreparedStatement ps = connection.prepareStatement(sql)) { try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName); ps.setString(1, playerName);
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) if (rs.next())
return new FlyAboManager.AboEntry( return new FlyAboManager.AboEntry(
rs.getString("player_name"),
rs.getString("label"), rs.getString("label"),
rs.getString("expires_fmt"), rs.getInt("monthly_price"),
rs.getInt("cancelled") == 1); rs.getInt("cancelled") == 1,
rs.getString("cancellation_reason"),
rs.getString("billing_fmt"),
rs.getString("period_fmt"));
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getActiveAbo SQL Fehler", 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) { 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)) { try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName); ps.setString(1, playerName);
ps.executeUpdate(); ps.executeUpdate();
@@ -2957,18 +3069,19 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
db.createAboTable(); db.createAboTable();
} }
// ── Abo vergeben (über Order) ────────────────────────────────── // ── Abo aktivieren (über Order) ────────────────────────────────
void grantAbo(Player player, int days, String label) { void grantAbo(Player player, String label, int monthlyPrice) {
grantAboByName(player.getName(), days, label); db.saveAbo(player.getName(), label, monthlyPrice);
Bukkit.getScheduler().runTask(plugin, () -> { Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage(""); player.sendMessage("");
player.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════"); player.sendMessage(ChatColor.GOLD + "✈ ════════════════════════════");
player.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!"); player.sendMessage(ChatColor.YELLOW + " Fly-Abo aktiviert!");
player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label); player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label);
player.sendMessage(ChatColor.GRAY + " Laufzeit: " + ChatColor.WHITE + days + " Tag(e)"); player.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + monthlyPrice + " " + currency);
player.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE player.sendMessage(ChatColor.GRAY + " Tägl. Limit: " + ChatColor.WHITE
+ flyManager.formatTime(flyAboMaxDailySec)); + flyManager.formatTime(flyAboMaxDailySec));
player.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats");
player.sendMessage(ChatColor.GRAY + " Status: " + ChatColor.WHITE + "/flyabo"); player.sendMessage(ChatColor.GRAY + " Status: " + ChatColor.WHITE + "/flyabo");
player.sendMessage(ChatColor.GOLD + " ════════════════════════════"); player.sendMessage(ChatColor.GOLD + " ════════════════════════════");
player.sendMessage(""); player.sendMessage("");
@@ -2976,17 +3089,17 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}); });
} }
void grantAboByName(String playerName, int days, String label) { void grantAboByName(String playerName, String label, int monthlyPrice) {
db.saveAbo(playerName, days, label); db.saveAbo(playerName, label, monthlyPrice);
} }
// ── Abo kündigen ─────────────────────────────────────────────── // ── Kündigung durch Spieler ────────────────────────────────────
boolean cancelAbo(String playerName) { boolean cancelAbo(String playerName) {
return db.cancelAbo(playerName); return db.cancelAbo(playerName);
} }
// ── Abo-Status lesen ────────────────────────────────────────── // ── Status lesen ──────────────────────────────────────────────
AboEntry getActiveAbo(String playerName) { AboEntry getActiveAbo(String playerName) {
return db.getActiveAbo(playerName); return db.getActiveAbo(playerName);
@@ -2996,21 +3109,37 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
return db.getUsedTodaySec(playerName); return db.getUsedTodaySec(playerName);
} }
// ── Beim Join: Abo prüfen ───────────────────────────────────── // ── Beim Join: Abo prüfen + ggf. Zahlung-fehlgeschlagen-Meldung ──
void onPlayerJoin(Player player) { void onPlayerJoin(Player player) {
AboEntry abo = db.getActiveAbo(player.getName()); AboEntry abo = db.getActiveAbo(player.getName());
if (abo == null) return;
// Abgelaufenes Abo bereinigen // Abgelaufene Einträge bereinigen
db.cleanupExpiredAbos(player.getName()); db.cleanupExpiredAbos(player.getName());
abo = db.getActiveAbo(player.getName()); abo = db.getActiveAbo(player.getName());
if (abo == null) {
Bukkit.getScheduler().runTask(plugin, () -> if (abo == null) return;
player.sendMessage(ChatColor.RED + "✈ Dein Fly-Abo ist abgelaufen."));
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 usedToday = db.getUsedTodaySec(player.getName());
int remaining = Math.max(0, flyAboMaxDailySec - usedToday); int remaining = Math.max(0, flyAboMaxDailySec - usedToday);
if (remaining <= 0) { if (remaining <= 0) {
@@ -3023,27 +3152,23 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
return; return;
} }
// Fly mit verbleibender Tageszeit gewähren, aber in Abo-Modus
final int grantSec = remaining; final int grantSec = remaining;
final String aboLabel = abo.label; Bukkit.getScheduler().runTask(plugin, () ->
Bukkit.getScheduler().runTask(plugin, () -> { flyManager.grantFlyWithAboTracking(player, grantSec, finalAbo.label,
flyManager.grantFlyWithAboTracking(player, grantSec, aboLabel, player.getName(), flyAboMaxDailySec));
player.getName(), flyAboMaxDailySec);
});
} }
// ── Täglicher Reset-Check ───────────────────────────────────── // ── Monatlicher Billing-Check (läuft täglich) ─────────────────
void startDailyResetChecker() { 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() { new BukkitRunnable() {
@Override public void run() { @Override public void run() {
for (Player p : Bukkit.getOnlinePlayers()) { for (Player p : Bukkit.getOnlinePlayers()) {
new BukkitRunnable() { new BukkitRunnable() {
@Override public void run() { @Override public void run() {
AboEntry abo = db.getActiveAbo(p.getName()); AboEntry abo = db.getActiveAbo(p.getName());
if (abo == null) return; if (abo == null || abo.cancelled) return;
// Wenn Tageslimit noch nicht erreicht und kein Fly aktiv → erneut gewähren
int usedToday = db.getUsedTodaySec(p.getName()); int usedToday = db.getUsedTodaySec(p.getName());
int remaining = Math.max(0, flyAboMaxDailySec - usedToday); int remaining = Math.max(0, flyAboMaxDailySec - usedToday);
if (remaining > 0 if (remaining > 0
@@ -3056,18 +3181,119 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}.runTaskAsynchronously(plugin); }.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 ──────────────────────────────────────────────── // ── Data class ────────────────────────────────────────────────
static class AboEntry { static class AboEntry {
final String label, expiresAt; final String playerName, label, cancellationReason, nextBillingDate, periodEnd;
final int monthlyPrice;
final boolean cancelled; final boolean cancelled;
AboEntry(String label, String expiresAt, boolean cancelled) { AboEntry(String playerName, String label, int monthlyPrice,
boolean cancelled, String cancellationReason,
String nextBillingDate, String periodEnd) {
this.playerName = playerName;
this.label = label; this.label = label;
this.expiresAt = expiresAt; this.monthlyPrice = monthlyPrice;
this.cancelled = cancelled; 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 # Hinweis: Den Basis-Ankaufspreis konfigurierst du im WP-Admin
# unter Items → <Item bearbeiten> → "Ankauf aktivieren" # unter Items → <Item bearbeiten> → "Ankauf aktivieren"
price-offset: -10.0 price-offset: -10.0
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
# Fly-Abo Einstellungen # Fly-Abo Einstellungen
# ────────────────────────────────────────────── # ──────────────────────────────────────────────