diff --git a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java index 42df21b..11262e2 100644 --- a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java +++ b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java @@ -50,6 +50,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import java.time.LocalDate; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -78,6 +79,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { private String apiKey = ""; private boolean flyRedeemDisabled = false; private String incomeReceiver = ""; + private int flyAboMaxDailySec = 21600; // 6 Stunden Standardlimit // Sell-Feature private double sellPriceOffset = 0.0; // z.B. -10.0 = -10 % vom WordPress-Ankaufspreis @@ -93,6 +95,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { private FlyManager flyManager; private FlyCodeManager flyCodeManager; private RankManager rankManager; + private FlyAboManager flyAboManager; private static final String GUI_FLYCODES = ChatColor.GOLD + "✈ Deine Fly-Gutscheine"; private final Map flyCodesPage = new HashMap<>(); @@ -135,6 +138,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { incomeReceiver = getConfig().getString("income-receiver", ""); sellEnabled = getConfig().getBoolean("sell.enabled", true); sellPriceOffset = getConfig().getDouble("sell.price-offset", 0.0); + flyAboMaxDailySec = getConfig().getInt("fly-abo.max-daily-hours", 6) * 3600; if (apiKey.isEmpty()) { getLogger().warning("⚠️ Kein api-key in config.yml gesetzt!"); @@ -150,6 +154,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { flyManager = new FlyManager(this); rankManager = new RankManager(this, flyCodeManager); rankManager.startExpiryChecker(); + flyAboManager = new FlyAboManager(this, flyCodeManager); + flyAboManager.startDailyResetChecker(); int intervalSeconds = getConfig().getInt("check-interval", 10); long pollInterval = intervalSeconds * 20L; @@ -162,6 +168,9 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { getCommand("flygive").setExecutor(new FlyGiveCommand()); getCommand("flypause").setExecutor(new FlyPauseCommand()); getCommand("rankinfo").setExecutor(new RankInfoCommand()); + getCommand("flyabo").setExecutor(new FlyAboCommand()); + getCommand("flyabocancel").setExecutor(new FlyAboCancelCommand()); + getCommand("flyabogive").setExecutor(new FlyAboGiveCommand()); getCommand("sell").setExecutor(new SellCommand()); getCommand("wpis").setExecutor(new ReloadCommand()); @@ -344,6 +353,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { @Override public void run() { rankManager.checkExpiredRanksForPlayer(p); } }.runTaskAsynchronously(this); + // Fly-Abo prüfen und ggf. Fly für heute bereitstellen + new BukkitRunnable() { + @Override public void run() { flyAboManager.onPlayerJoin(p); } + }.runTaskAsynchronously(this); + // Ausstehende Fly-Codes anzeigen new BukkitRunnable() { @Override public void run() { @@ -906,6 +920,16 @@ 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; + String label = cmdObj.has("label") + ? cmdObj.get("label").getAsString() + : "Fly-Abo " + days + " Tage"; + flyAboManager.grantAbo(player, days, label); + anyCode = true; + } else if ("generic".equals(type) && cmdObj.has("cmd")) { String cmd = cmdObj.get("cmd").getAsString() .replace("{player}", player.getName()); @@ -1283,6 +1307,134 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // =========================================================== + // FLY-ABO COMMANDS + // =========================================================== + + /** /flyabo – Abo-Status anzeigen */ + private class FlyAboCommand 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; + new BukkitRunnable() { + @Override public void run() { + FlyAboManager.AboEntry abo = flyAboManager.getActiveAbo(p.getName()); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { + p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + p.sendMessage(ChatColor.YELLOW + "✈ Dein Fly-Abo:"); + p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + if (abo == null) { + 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"); + int usedSec = flyAboManager.getUsedTodaySec(p.getName()); + int maxSec = flyAboMaxDailySec; + int remSec = Math.max(0, maxSec - usedSec); + p.sendMessage(ChatColor.AQUA + " Heute geflogen: " + + ChatColor.WHITE + flyManager.formatTime(usedSec) + + ChatColor.GRAY + " / " + + ChatColor.WHITE + flyManager.formatTime(maxSec)); + p.sendMessage(ChatColor.AQUA + " Heute noch verfügbar: " + + (remSec > 0 ? ChatColor.GREEN : ChatColor.RED) + + flyManager.formatTime(remSec)); + } + p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + }); + } + }.runTaskAsynchronously(IngameShopSpigot.this); + return true; + } + } + + /** /flyabocancel – Abo zum Ablauf 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; + // 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.WHITE + " Bestätigen: " + ChatColor.RED + "/flyabocancel confirm"); + return true; + } + new BukkitRunnable() { + @Override public void run() { + 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.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.8F); + } else { + p.sendMessage(ChatColor.RED + "✗ Kein aktives Fly-Abo gefunden."); + } + }); + } + }.runTaskAsynchronously(IngameShopSpigot.this); + return true; + } + } + + /** /flyabogive [Label] – Admin gibt Abo manuell */ + private class FlyAboGiveCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, + String label, String[] args) { + if (!sender.hasPermission("ingameshop.flyabogive")) { + sender.sendMessage(ChatColor.RED + "✗ Keine Berechtigung."); + return true; + } + if (args.length < 2) { + sender.sendMessage(ChatColor.YELLOW + "Verwendung: /flyabogive [Label]"); + return true; + } + String targetName = args[0]; + int days; + try { days = Integer.parseInt(args[1]); } + catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "✗ Tage muss eine Zahl sein."); + return true; + } + String aboLabel = args.length >= 3 + ? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) + : "Fly-Abo " + days + " Tage"; + Player target = Bukkit.getPlayer(targetName); + new BukkitRunnable() { + @Override public void run() { + flyAboManager.grantAboByName(targetName, days, aboLabel); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { + sender.sendMessage(ChatColor.GREEN + "✔ Fly-Abo (" + days + " Tage) an " + + ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben."); + 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 + " Tägl. Limit: " + ChatColor.WHITE + + flyManager.formatTime(flyAboMaxDailySec)); + target.sendMessage(ChatColor.GRAY + " Status: /flyabo"); + target.sendMessage(ChatColor.GOLD + " ════════════════════════════"); + target.sendMessage(""); + target.playSound(target.getLocation(), + Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.2F); + } + }); + } + }.runTaskAsynchronously(IngameShopSpigot.this); + return true; + } + } + // =========================================================== // HTTP HILFSMETHODEN // =========================================================== @@ -1667,6 +1819,133 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } return seconds + "s"; } + + /** + * Wie grantFly, aber schreibt jede Sekunde die Nutzung in die Abo-Usage-Tabelle. + * Stoppt automatisch wenn dailyLimitSec gesamt verbraucht ist. + */ + void grantFlyWithAboTracking(Player player, int seconds, String aboLabel, + String playerName, int dailyLimitSec) { + UUID uuid = player.getUniqueId(); + int existing = flySeconds.getOrDefault(uuid, 0); + int total = existing + seconds; + flySeconds.put(uuid, total); + flyStartSecs.put(uuid, total); + paused.remove(uuid); + + player.setAllowFlight(true); + player.setFlying(true); + player.sendMessage(ChatColor.AQUA + "✈ Fly-Abo aktiv! Heute noch: " + + ChatColor.YELLOW + formatTime(seconds)); + + BossBar bar = flyBossBars.get(uuid); + if (bar == null) { + bar = Bukkit.createBossBar( + buildAboBarTitle(seconds, dailyLimitSec, false), + BarColor.GREEN, BarStyle.SEGMENTED_10); + bar.addPlayer(player); + flyBossBars.put(uuid, bar); + } else { + bar.setTitle(buildAboBarTitle(seconds, dailyLimitSec, false)); + bar.setProgress(1.0); + bar.setColor(BarColor.GREEN); + } + + if (flyTasks.containsKey(uuid)) flyTasks.get(uuid).cancel(); + + final BossBar finalBar = bar; + // Jede 30s Usage in DB schreiben + final int[] dbBuffer = {0}; + + BukkitTask t = new BukkitRunnable() { + @Override public void run() { + Player p = Bukkit.getPlayer(uuid); + if (p == null || !p.isOnline()) { + // DB-Puffer flushen + if (dbBuffer[0] > 0) { + final int toFlush = dbBuffer[0]; + dbBuffer[0] = 0; + new BukkitRunnable() { + @Override public void run() { + flyCodeManager.addUsedTodaySec(playerName, toFlush); + } + }.runTaskAsynchronously(plugin); + } + removeBossBar(uuid); + flyTasks.remove(uuid); + paused.remove(uuid); + cancel(); + return; + } + if (paused.contains(uuid)) { + int rem = flySeconds.getOrDefault(uuid, 0); + finalBar.setTitle(buildAboBarTitle(rem, dailyLimitSec, true)); + finalBar.setColor(BarColor.WHITE); + return; + } + int remaining = flySeconds.getOrDefault(uuid, 0) - 1; + dbBuffer[0]++; + // Alle 30s in DB flushen + if (dbBuffer[0] >= 30) { + final int toFlush = dbBuffer[0]; + dbBuffer[0] = 0; + new BukkitRunnable() { + @Override public void run() { + flyCodeManager.addUsedTodaySec(playerName, toFlush); + } + }.runTaskAsynchronously(plugin); + } + if (remaining <= 0) { + // Letzten Rest flushen + if (dbBuffer[0] > 0) { + final int toFlush = dbBuffer[0]; + new BukkitRunnable() { + @Override public void run() { + flyCodeManager.addUsedTodaySec(playerName, toFlush); + } + }.runTaskAsynchronously(plugin); + } + flySeconds.remove(uuid); + flyTasks.remove(uuid); + flyStartSecs.remove(uuid); + removeBossBar(uuid); + p.setFlying(false); + p.setAllowFlight(false); + p.sendMessage(ChatColor.RED + "✈ Fly-Abo Tageslimit erreicht!"); + p.sendMessage(ChatColor.GRAY + " Morgen stehen dir wieder " + + ChatColor.WHITE + formatTime(dailyLimitSec) + + ChatColor.GRAY + " zur Verfügung."); + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.5F); + cancel(); + return; + } + flySeconds.put(uuid, remaining); + int start = flyStartSecs.getOrDefault(uuid, remaining); + double progress = Math.max(0.0, Math.min(1.0, (double) remaining / start)); + finalBar.setProgress(progress); + finalBar.setTitle(buildAboBarTitle(remaining, dailyLimitSec, false)); + if (remaining <= 300) finalBar.setColor(BarColor.RED); + else if (remaining <= 1800) finalBar.setColor(BarColor.YELLOW); + else finalBar.setColor(BarColor.GREEN); + if (remaining == 3600 || remaining == 1800 + || remaining == 600 || remaining == 300 + || remaining == 60 || remaining == 30 || remaining == 10) { + p.sendMessage(ChatColor.YELLOW + "✈ Fly-Abo noch " + formatTime(remaining) + " heute!"); + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.5F, 1.2F); + } + } + }.runTaskTimer(plugin, 20L, 20L); + flyTasks.put(uuid, t); + } + + private String buildAboBarTitle(int seconds, int dailyMax, boolean isPaused) { + if (isPaused) + return ChatColor.GRAY + "⏸ Fly-Abo pausiert: " + + ChatColor.WHITE + formatTime(seconds); + return ChatColor.GREEN + "✈ Fly-Abo: " + + ChatColor.YELLOW + formatTime(seconds) + + ChatColor.GRAY + " / " + formatTime(dailyMax); + } } // =========================================================== @@ -1909,6 +2188,132 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // ── Fly-Abo CRUD ────────────────────────────────────────────────── + + void createAboTable() { + 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," + + " UNIQUE KEY uq_player (player_name)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; + String sqlUsage = + "CREATE TABLE IF NOT EXISTS wis_fly_abo_usage (" + + " id INT AUTO_INCREMENT PRIMARY KEY," + + " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci," + + " usage_date DATE NOT NULL," + + " used_sec INT NOT NULL DEFAULT 0," + + " UNIQUE KEY uq_player_date (player_name, usage_date)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; + try (Statement stmt = connection.createStatement()) { + stmt.execute(sqlAbo); + stmt.execute(sqlUsage); + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "Abo-Tabellen-Erstellung fehlgeschlagen", e); + } + } + + void saveAbo(String playerName, int days, String label) { + String sql = + "INSERT INTO wis_fly_abos (player_name, label, cancelled, expires_at)" + + " VALUES (?, ?, 0, DATE_ADD(NOW(), INTERVAL ? DAY))" + + " ON DUPLICATE KEY UPDATE" + + " label = ?, cancelled = 0," + + " expires_at = IF(expires_at > NOW()," + + " DATE_ADD(expires_at, INTERVAL ? DAY)," + + " DATE_ADD(NOW(), INTERVAL ? DAY))," + + " granted_at = NOW()"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + ps.setString(2, label); + ps.setInt(3, days); + ps.setString(4, label); + ps.setInt(5, days); + ps.setInt(6, days); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "saveAbo SQL Fehler", e); + } + } + + boolean cancelAbo(String playerName) { + String sql = + "UPDATE wis_fly_abos SET cancelled = 1" + + " WHERE player_name = ? AND expires_at > NOW() AND cancelled = 0"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + return ps.executeUpdate() == 1; + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "cancelAbo SQL Fehler", e); + return false; + } + } + + FlyAboManager.AboEntry getActiveAbo(String playerName) { + String sql = + "SELECT label, cancelled," + + " DATE_FORMAT(expires_at, '%d.%m.%Y') AS expires_fmt" + + " FROM wis_fly_abos" + + " WHERE player_name = ? AND expires_at > NOW()"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) + return new FlyAboManager.AboEntry( + rs.getString("label"), + rs.getString("expires_fmt"), + rs.getInt("cancelled") == 1); + } + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "getActiveAbo SQL Fehler", e); + } + return null; + } + + void cleanupExpiredAbos(String playerName) { + String sql = "DELETE FROM wis_fly_abos WHERE player_name = ? AND expires_at <= NOW()"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "cleanupExpiredAbos SQL Fehler", e); + } + } + + int getUsedTodaySec(String playerName) { + String sql = + "SELECT used_sec FROM wis_fly_abo_usage" + + " WHERE player_name = ? AND usage_date = CURDATE()"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) return rs.getInt("used_sec"); + } + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "getUsedTodaySec SQL Fehler", e); + } + return 0; + } + + void addUsedTodaySec(String playerName, int seconds) { + String sql = + "INSERT INTO wis_fly_abo_usage (player_name, usage_date, used_sec)" + + " VALUES (?, CURDATE(), ?)" + + " ON DUPLICATE KEY UPDATE used_sec = used_sec + ?"; + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.setString(1, playerName); + ps.setInt(2, seconds); + ps.setInt(3, seconds); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "addUsedTodaySec SQL Fehler", e); + } + } + // ── Fly-Code CRUD ──────────────────────────────────────────────── String generateCode(String playerName, int durationSec, String label) { @@ -2537,6 +2942,136 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { return removed; } + // =========================================================== + // FLY ABO MANAGER + // =========================================================== + + private class FlyAboManager { + + private final IngameShopSpigot plugin; + private final FlyCodeManager db; + + FlyAboManager(IngameShopSpigot plugin, FlyCodeManager db) { + this.plugin = plugin; + this.db = db; + db.createAboTable(); + } + + // ── Abo vergeben (über Order) ────────────────────────────────── + + void grantAbo(Player player, int days, String label) { + grantAboByName(player.getName(), days, label); + 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 + + flyManager.formatTime(flyAboMaxDailySec)); + 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); + } + + // ── Abo kündigen ─────────────────────────────────────────────── + + boolean cancelAbo(String playerName) { + return db.cancelAbo(playerName); + } + + // ── Abo-Status lesen ────────────────────────────────────────── + + AboEntry getActiveAbo(String playerName) { + return db.getActiveAbo(playerName); + } + + int getUsedTodaySec(String playerName) { + return db.getUsedTodaySec(playerName); + } + + // ── Beim Join: Abo prüfen ───────────────────────────────────── + + void onPlayerJoin(Player player) { + AboEntry abo = db.getActiveAbo(player.getName()); + if (abo == null) return; + + // Abgelaufenes Abo 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; + } + + int usedToday = db.getUsedTodaySec(player.getName()); + int remaining = Math.max(0, flyAboMaxDailySec - usedToday); + if (remaining <= 0) { + Bukkit.getScheduler().runTask(plugin, () -> { + player.sendMessage(ChatColor.YELLOW + "✈ Fly-Abo aktiv – Tageslimit heute bereits aufgebraucht."); + player.sendMessage(ChatColor.GRAY + " Morgen stehen dir wieder " + + ChatColor.WHITE + flyManager.formatTime(flyAboMaxDailySec) + + ChatColor.GRAY + " zur Verfügung."); + }); + 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); + }); + } + + // ── Täglicher Reset-Check ───────────────────────────────────── + + void startDailyResetChecker() { + // Alle 5 Minuten prüfen ob Tagesdatum gewechselt hat → online Spieler 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 + int usedToday = db.getUsedTodaySec(p.getName()); + int remaining = Math.max(0, flyAboMaxDailySec - usedToday); + if (remaining > 0 + && flyManager.getRemainingSeconds(p.getUniqueId()) <= 0) { + Bukkit.getScheduler().runTask(plugin, () -> + flyManager.grantFlyWithAboTracking(p, remaining, abo.label, + p.getName(), flyAboMaxDailySec)); + } + } + }.runTaskAsynchronously(plugin); + } + } + }.runTaskTimer(plugin, 20L * 300, 20L * 300); // alle 5 Minuten + } + + // ── Data class ──────────────────────────────────────────────── + + static class AboEntry { + final String label, expiresAt; + final boolean cancelled; + AboEntry(String label, String expiresAt, boolean cancelled) { + this.label = label; + this.expiresAt = expiresAt; + this.cancelled = cancelled; + } + } + } + // =========================================================== // SELL MANAGER // =========================================================== diff --git a/IngameShopSpigot/src/main/resources/config.yml b/IngameShopSpigot/src/main/resources/config.yml index 422e14b..8cd1533 100644 --- a/IngameShopSpigot/src/main/resources/config.yml +++ b/IngameShopSpigot/src/main/resources/config.yml @@ -51,4 +51,11 @@ sell: # +5.0 → 5 % mehr als der WP-Ankaufspreis # Hinweis: Den Basis-Ankaufspreis konfigurierst du im WP-Admin # unter Items → → "Ankauf aktivieren" - price-offset: -10.0 \ No newline at end of file + price-offset: -10.0 +# ────────────────────────────────────────────── +# Fly-Abo Einstellungen +# ────────────────────────────────────────────── +fly-abo: + # Maximale Fly-Zeit pro Tag in Stunden + # Standard: 6 (= 6 Stunden = 21.600 Sekunden) + max-daily-hours: 6 \ No newline at end of file diff --git a/IngameShopSpigot/src/main/resources/plugin.yml b/IngameShopSpigot/src/main/resources/plugin.yml index b8d8614..2f38ec5 100644 --- a/IngameShopSpigot/src/main/resources/plugin.yml +++ b/IngameShopSpigot/src/main/resources/plugin.yml @@ -43,6 +43,18 @@ commands: description: IngameShop Admin-Befehle usage: /wpis permission: ingameshop.reload + flyabo: + description: Zeigt deinen Fly-Abo Status und Tageslimit an + usage: /flyabo + permission: ingameshop.flyabo + flyabocancel: + description: Kündigt dein Fly-Abo zum Ablaufdatum + usage: /flyabocancel [confirm] + permission: ingameshop.flyabocancel + flyabogive: + description: Gibt einem Spieler ein Fly-Abo (Admin) + usage: /flyabogive [Label] + permission: ingameshop.flyabogive permissions: ingameshop.orders: @@ -71,4 +83,13 @@ permissions: default: true ingameshop.reload: description: Kann die IngameShop-Config live neu laden + default: op + ingameshop.flyabo: + description: Kann eigenen Fly-Abo-Status einsehen + default: true + ingameshop.flyabocancel: + description: Kann eigenes Fly-Abo kündigen + default: true + ingameshop.flyabogive: + description: Kann Spielern Fly-Abos manuell vergeben (Admin) default: op \ No newline at end of file