Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-29 16:40:53 +02:00
parent 8e28d518c3
commit f5b14ffd6f
3 changed files with 804 additions and 22 deletions

View File

@@ -92,10 +92,15 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
private Map<Integer, OrderData> orderCache = new HashMap<>();
private Map<UUID, Integer> activeOrderIds = new ConcurrentHashMap<>();
private FlyManager flyManager;
private FlyCodeManager flyCodeManager;
private RankManager rankManager;
private FlyAboManager flyAboManager;
private FlyManager flyManager;
private FlyCodeManager flyCodeManager;
private RankManager rankManager;
private FlyAboManager flyAboManager;
private PlotSlotManager plotSlotManager;
// Plot-Slot Konfiguration
private String plotServer = "citybuild";
private Map<String, Integer> plotRankDefaults = new HashMap<>();
private static final String GUI_FLYCODES = ChatColor.GOLD + "✈ Deine Fly-Gutscheine";
private final Map<UUID, Integer> flyCodesPage = new HashMap<>();
@@ -140,6 +145,22 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
sellPriceOffset = getConfig().getDouble("sell.price-offset", 0.0);
flyAboMaxDailySec = getConfig().getInt("fly-abo.max-daily-hours", 6) * 3600;
// Plot-Slot Konfiguration laden
plotServer = getConfig().getString("plot-slots.server", "citybuild").toLowerCase();
plotRankDefaults.clear();
org.bukkit.configuration.ConfigurationSection rankSec =
getConfig().getConfigurationSection("plot-slots.rank-defaults");
if (rankSec != null) {
for (String key : rankSec.getKeys(false))
plotRankDefaults.put(key.toLowerCase(), rankSec.getInt(key, 2));
} else {
// Viper Network Defaults
plotRankDefaults.put("member", 2);
plotRankDefaults.put("vip", 3);
plotRankDefaults.put("scout", 4);
plotRankDefaults.put("primo", 5);
}
if (apiKey.isEmpty()) {
getLogger().warning("⚠️ Kein api-key in config.yml gesetzt!");
}
@@ -156,6 +177,8 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
rankManager.startExpiryChecker();
flyAboManager = new FlyAboManager(this, flyCodeManager);
flyAboManager.startDailyResetChecker();
plotSlotManager = new PlotSlotManager(this, flyCodeManager);
plotSlotManager.startBillingChecker();
int intervalSeconds = getConfig().getInt("check-interval", 10);
long pollInterval = intervalSeconds * 20L;
@@ -171,6 +194,11 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
getCommand("flyabo").setExecutor(new FlyAboCommand());
getCommand("flyabocancel").setExecutor(new FlyAboCancelCommand());
getCommand("flyabogive").setExecutor(new FlyAboGiveCommand());
getCommand("plotslots").setExecutor(new PlotSlotsCommand());
getCommand("plotabo").setExecutor(new PlotAboCommand());
getCommand("plotabocancel").setExecutor(new PlotAboCancelCommand());
getCommand("plotabogive").setExecutor(new PlotAboGiveCommand());
getCommand("plotslotsgive").setExecutor(new PlotSlotsGiveCommand());
getCommand("sell").setExecutor(new SellCommand());
getCommand("wpis").setExecutor(new ReloadCommand());
@@ -358,6 +386,13 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
@Override public void run() { flyAboManager.onPlayerJoin(p); }
}.runTaskAsynchronously(this);
// Plot-Slot Limit anwenden (nur auf Plot-Server)
if (targetServer.equalsIgnoreCase(plotServer)) {
new BukkitRunnable() {
@Override public void run() { plotSlotManager.onPlayerJoin(p); }
}.runTaskAsynchronously(this);
}
// Ausstehende Fly-Codes anzeigen
new BukkitRunnable() {
@Override public void run() {
@@ -928,6 +963,25 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
flyAboManager.grantAbo(player, label, monthlyPrice);
anyCode = true;
} else if ("plot_slots".equals(type)) {
// Einmalige Plot-Slot Erweiterung (permanent)
int slots = cmdObj.has("slots")
? cmdObj.get("slots").getAsInt() : 1;
String label = cmdObj.has("label")
? cmdObj.get("label").getAsString() : slots + " Extra-Plot-Slot(s)";
plotSlotManager.grantExtraSlots(player, slots, label);
anyCode = true;
} else if ("plot_abo".equals(type)) {
// Monatliches Plot-Abo
int slots = cmdObj.has("slots")
? cmdObj.get("slots").getAsInt() : 1;
String label = cmdObj.has("label")
? cmdObj.get("label").getAsString() : "Plot-Abo +" + slots;
int monthlyPrice = (int) data.price;
plotSlotManager.grantAbo(player, slots, label, monthlyPrice);
anyCode = true;
} else if ("generic".equals(type) && cmdObj.has("cmd")) {
String cmd = cmdObj.get("cmd").getAsString()
.replace("{player}", player.getName());
@@ -1453,6 +1507,208 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
// ===========================================================
// PLOT-SLOT COMMANDS
// ===========================================================
/** /plotslots Gesamtstatus: Basis + extra + Abo */
private class PlotSlotsCommand 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() {
int base = plotSlotManager.getRankBase(p);
int extra = plotSlotManager.getExtraSlots(p.getName());
int abo = plotSlotManager.getAboSlots(p.getName());
int total = base + extra + abo;
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
p.sendMessage(ChatColor.YELLOW + "📦 Deine Plot-Slots:");
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
p.sendMessage(ChatColor.AQUA + " Rang-Basis: " + ChatColor.WHITE + base);
p.sendMessage(ChatColor.AQUA + " Einmalig: " + ChatColor.WHITE + extra);
p.sendMessage(ChatColor.AQUA + " Abo: " + ChatColor.WHITE + abo);
p.sendMessage(ChatColor.AQUA + " Gesamt: " + ChatColor.GREEN + total);
p.sendMessage(ChatColor.GRAY + " Mehr Slots: " + ChatColor.AQUA + "viper-network.de");
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
});
}
}.runTaskAsynchronously(IngameShopSpigot.this);
return true;
}
}
/** /plotabo Abo-Status anzeigen */
private class PlotAboCommand 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() {
PlotSlotManager.PlotAboEntry abo = plotSlotManager.getActiveAbo(p.getName());
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
p.sendMessage(ChatColor.YELLOW + "📦 Dein Plot-Abo:");
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
if (abo == null) {
p.sendMessage(ChatColor.GRAY + " Kein aktives Plot-Abo.");
p.sendMessage(ChatColor.GRAY + " Im Shop erhältlich: " + ChatColor.AQUA + "viper-network.de");
} else {
p.sendMessage(ChatColor.AQUA + " Paket: " + ChatColor.WHITE + abo.label);
p.sendMessage(ChatColor.AQUA + " Slots: " + ChatColor.WHITE + "+" + abo.aboSlots);
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);
} 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: " + ChatColor.WHITE + "/plotabocancel");
}
}
p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
});
}
}.runTaskAsynchronously(IngameShopSpigot.this);
return true;
}
}
/** /plotabocancel [confirm] Abo zum Monatsende kündigen */
private class PlotAboCancelCommand 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;
if (args.length == 0 || !args[0].equalsIgnoreCase("confirm")) {
p.sendMessage(ChatColor.YELLOW + "⚠ Möchtest du dein Plot-Abo wirklich kündigen?");
p.sendMessage(ChatColor.GRAY + " Die Abo-Slots bleiben bis Monatsende erhalten.");
p.sendMessage(ChatColor.GRAY + " Plots mit überzähligen Slots werden eingefroren.");
p.sendMessage(ChatColor.WHITE + " Bestätigen: " + ChatColor.RED + "/plotabocancel confirm");
return true;
}
new BukkitRunnable() {
@Override public void run() {
boolean ok = plotSlotManager.cancelAbo(p.getName());
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
if (ok) {
p.sendMessage(ChatColor.YELLOW + "📦 Plot-Abo zur Kündigung vorgemerkt.");
p.sendMessage(ChatColor.GRAY + " Deine Abo-Slots bleiben bis Ende des Monats aktiv.");
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.8F);
} else {
p.sendMessage(ChatColor.RED + "✗ Kein aktives Plot-Abo gefunden.");
}
});
}
}.runTaskAsynchronously(IngameShopSpigot.this);
return true;
}
}
/** /plotabogive <Spieler> <Slots> <Monatspreis> [Label] Admin */
private class PlotAboGiveCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!sender.hasPermission("ingameshop.plotabogive")) {
sender.sendMessage(ChatColor.RED + "✗ Keine Berechtigung.");
return true;
}
if (args.length < 3) {
sender.sendMessage(ChatColor.YELLOW + "Verwendung: /plotabogive <Spieler> <Slots> <Monatspreis> [Label]");
return true;
}
String targetName = args[0];
int slots, price;
try { slots = Integer.parseInt(args[1]); price = Integer.parseInt(args[2]); }
catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "✗ Slots und Preis müssen Zahlen sein.");
return true;
}
String aboLabel = args.length >= 4
? String.join(" ", Arrays.copyOfRange(args, 3, args.length))
: "Plot-Abo +" + slots;
Player target = Bukkit.getPlayer(targetName);
new BukkitRunnable() {
@Override public void run() {
plotSlotManager.grantAboByName(targetName, slots, aboLabel, price);
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
sender.sendMessage(ChatColor.GREEN + "✔ Plot-Abo (+" + slots + " Slots) an "
+ ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben.");
if (target != null && target.isOnline()) {
target.sendMessage("");
target.sendMessage(ChatColor.GOLD + "📦 ════════════════════════════");
target.sendMessage(ChatColor.YELLOW + " Plot-Abo aktiviert!");
target.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + aboLabel);
target.sendMessage(ChatColor.GRAY + " Slots: " + ChatColor.WHITE + "+" + slots);
target.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + price + " " + currency);
target.sendMessage(ChatColor.GRAY + " Status: /plotabo");
target.sendMessage(ChatColor.GOLD + " ════════════════════════════");
target.playSound(target.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.2F);
}
});
}
}.runTaskAsynchronously(IngameShopSpigot.this);
return true;
}
}
/** /plotslotsgive <Spieler> <Slots> [Label] Admin gibt permanente Slots */
private class PlotSlotsGiveCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!sender.hasPermission("ingameshop.plotslotsgive")) {
sender.sendMessage(ChatColor.RED + "✗ Keine Berechtigung.");
return true;
}
if (args.length < 2) {
sender.sendMessage(ChatColor.YELLOW + "Verwendung: /plotslotsgive <Spieler> <Slots> [Label]");
return true;
}
String targetName = args[0];
int slots;
try { slots = Integer.parseInt(args[1]); }
catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "✗ Slots muss eine Zahl sein.");
return true;
}
String slotLabel = args.length >= 3
? String.join(" ", Arrays.copyOfRange(args, 2, args.length))
: "+" + slots + " Plot-Slots";
Player target = Bukkit.getPlayer(targetName);
new BukkitRunnable() {
@Override public void run() {
plotSlotManager.grantExtraSlotsByName(targetName, slots, slotLabel);
Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> {
sender.sendMessage(ChatColor.GREEN + "" + slots + " permanente Plot-Slots an "
+ ChatColor.YELLOW + targetName + ChatColor.GREEN + " vergeben.");
if (target != null && target.isOnline()) {
target.sendMessage("");
target.sendMessage(ChatColor.GOLD + "📦 ════════════════════════════");
target.sendMessage(ChatColor.YELLOW + " Plot-Slots erweitert!");
target.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + slotLabel);
target.sendMessage(ChatColor.GRAY + " Dauerhaft: +" + slots + " Slots");
target.sendMessage(ChatColor.GRAY + " Status: /plotslots");
target.sendMessage(ChatColor.GOLD + " ════════════════════════════");
target.playSound(target.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
}
});
}
}.runTaskAsynchronously(IngameShopSpigot.this);
return true;
}
}
// ===========================================================
// HTTP HILFSMETHODEN
// ===========================================================
@@ -2362,7 +2618,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
FlyAboManager.AboEntry getActiveAbo(String playerName) {
String sql =
"SELECT label, monthly_price, cancelled, cancellation_reason,"
"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"
@@ -2426,6 +2682,235 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
// ── Plot-Slot CRUD ────────────────────────────────────────────────
void createPlotSlotTables() {
String sqlExtra =
"CREATE TABLE IF NOT EXISTS wis_plot_extra_slots ("
+ " id INT AUTO_INCREMENT PRIMARY KEY,"
+ " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,"
+ " extra_slots INT NOT NULL DEFAULT 0,"
+ " last_perm INT NOT NULL DEFAULT 0,"
+ " granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ " UNIQUE KEY uq_player (player_name)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
String sqlAbo =
"CREATE TABLE IF NOT EXISTS wis_plot_abos ("
+ " id INT AUTO_INCREMENT PRIMARY KEY,"
+ " player_name VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,"
+ " label VARCHAR(128) NOT NULL,"
+ " abo_slots INT NOT NULL DEFAULT 1,"
+ " 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;";
try (Statement stmt = connection.createStatement()) {
stmt.execute(sqlExtra);
stmt.execute(sqlAbo);
// Migration: last_perm Spalte ergänzen falls Tabelle schon existiert
try { stmt.execute("ALTER TABLE wis_plot_extra_slots ADD COLUMN IF NOT EXISTS last_perm INT NOT NULL DEFAULT 0 AFTER extra_slots"); }
catch (SQLException ignored) {}
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Plot-Slot-Tabellen Erstellung fehlgeschlagen", e);
}
}
// Letzten Permission-Wert lesen/schreiben (für sauberes Entfernen)
int getLastPerm(String playerName) {
String sql = "SELECT last_perm FROM wis_plot_extra_slots WHERE player_name = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("last_perm");
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getLastPerm SQL Fehler", e);
}
return 0;
}
void saveLastPerm(String playerName, int perm) {
String sql =
"INSERT INTO wis_plot_extra_slots (player_name, extra_slots, last_perm)"
+ " VALUES (?, 0, ?)"
+ " ON DUPLICATE KEY UPDATE last_perm = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.setInt(2, perm);
ps.setInt(3, perm);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "saveLastPerm SQL Fehler", e);
}
}
// Extra-Slots (permanent)
void addExtraSlots(String playerName, int slots) {
String sql =
"INSERT INTO wis_plot_extra_slots (player_name, extra_slots)"
+ " VALUES (?, ?)"
+ " ON DUPLICATE KEY UPDATE extra_slots = extra_slots + ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
ps.setInt(2, slots);
ps.setInt(3, slots);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "addExtraSlots SQL Fehler", e);
}
}
int getExtraSlots(String playerName) {
String sql = "SELECT extra_slots FROM wis_plot_extra_slots WHERE player_name = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("extra_slots");
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getExtraSlots SQL Fehler", e);
}
return 0;
}
// Plot-Abo
void savePlotAbo(String playerName, int slots, String label, int monthlyPrice) {
String sql =
"INSERT INTO wis_plot_abos"
+ " (player_name, label, abo_slots, 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 = ?, abo_slots = ?, 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, slots); ps.setInt(4, monthlyPrice);
ps.setString(5, label); ps.setInt(6, slots);
ps.setInt(7, monthlyPrice);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "savePlotAbo SQL Fehler", e);
}
}
boolean cancelPlotAbo(String playerName) {
String sql =
"UPDATE wis_plot_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;
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "cancelPlotAbo SQL Fehler", e);
return false;
}
}
void cancelPlotAboPaymentFailed(String playerName) {
String sql =
"UPDATE wis_plot_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, "cancelPlotAboPaymentFailed SQL Fehler", e);
}
}
void renewPlotAbo(String playerName) {
String sql =
"UPDATE wis_plot_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, "renewPlotAbo SQL Fehler", e);
}
}
int getPlotAboSlots(String playerName) {
String sql =
"SELECT abo_slots FROM wis_plot_abos"
+ " WHERE player_name = ? AND period_end >= CURDATE() AND cancelled = 0";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, playerName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("abo_slots");
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getPlotAboSlots SQL Fehler", e);
}
return 0;
}
PlotSlotManager.PlotAboEntry getActivePlotAbo(String playerName) {
String sql =
"SELECT label, abo_slots, 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_plot_abos"
+ " 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 PlotSlotManager.PlotAboEntry(
playerName,
rs.getString("label"),
rs.getInt("abo_slots"),
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, "getActivePlotAbo SQL Fehler", e);
}
return null;
}
List<PlotSlotManager.PlotAboEntry> getDuePlotBillings() {
List<PlotSlotManager.PlotAboEntry> list = new ArrayList<>();
String sql =
"SELECT player_name, label, abo_slots, monthly_price,"
+ " DATE_FORMAT(next_billing_date, '%d.%m.%Y') AS billing_fmt,"
+ " DATE_FORMAT(period_end, '%d.%m.%Y') AS period_fmt"
+ " FROM wis_plot_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 PlotSlotManager.PlotAboEntry(
rs.getString("player_name"),
rs.getString("label"),
rs.getInt("abo_slots"),
rs.getInt("monthly_price"),
false, null,
rs.getString("billing_fmt"),
rs.getString("period_fmt")));
}
} catch (SQLException e) {
plugin.getLogger().log(Level.WARNING, "getDuePlotBillings SQL Fehler", e);
}
return list;
}
// ── Fly-Code CRUD ────────────────────────────────────────────────
String generateCode(String playerName, int durationSec, String label) {
@@ -3298,6 +3783,256 @@ public class IngameShopSpigot extends JavaPlugin implements Listener {
}
}
// ===========================================================
// PLOT SLOT MANAGER
// ===========================================================
private class PlotSlotManager {
private final IngameShopSpigot plugin;
private final FlyCodeManager db;
PlotSlotManager(IngameShopSpigot plugin, FlyCodeManager db) {
this.plugin = plugin;
this.db = db;
db.createPlotSlotTables();
}
// ── Rang-Basis aus Config ──────────────────────────────────────
int getRankBase(Player player) {
int highest = plotRankDefaults.getOrDefault("member", 2);
for (Map.Entry<String, Integer> entry : plotRankDefaults.entrySet()) {
if (player.hasPermission("group." + entry.getKey())) {
if (entry.getValue() > highest) highest = entry.getValue();
}
}
return highest;
}
int getRankBaseByName(String playerName) {
Player online = Bukkit.getPlayer(playerName);
if (online != null) return getRankBase(online);
return plotRankDefaults.getOrDefault("member", 2);
}
// ── Gesamtlimit berechnen und LuckPerms setzen ─────────────────
void applyLimit(String playerName) {
int extra = db.getExtraSlots(playerName);
int abo = db.getPlotAboSlots(playerName);
int base = getRankBaseByName(playerName);
int total = base + extra + abo;
int last = db.getLastPerm(playerName);
Bukkit.getScheduler().runTask(plugin, () -> {
// Nur die tatsächlich gesetzte alte Permission entfernen
if (last > 0 && last != total) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"lp user " + playerName + " permission unset plots.plot." + last);
}
// Neue Permission setzen
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"lp user " + playerName + " permission set plots.plot." + total + " true");
});
// last_perm async speichern
new BukkitRunnable() {
@Override public void run() { db.saveLastPerm(playerName, total); }
}.runTaskAsynchronously(plugin);
if (debug) plugin.getLogger().info("[PlotSlots] " + playerName
+ " → Basis=" + base + " Extra=" + extra + " Abo=" + abo
+ " Gesamt=" + total + " (vorher: " + last + ") → plots.plot." + total);
}
// ── Einmalige Extra-Slots ─────────────────────────────────────
void grantExtraSlots(Player player, int slots, String label) {
db.addExtraSlots(player.getName(), slots);
applyLimit(player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
int total = getRankBase(player)
+ db.getExtraSlots(player.getName())
+ db.getPlotAboSlots(player.getName());
player.sendMessage("");
player.sendMessage(ChatColor.GOLD + "📦 ════════════════════════════");
player.sendMessage(ChatColor.YELLOW + " Plot-Slots erweitert!");
player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label);
player.sendMessage(ChatColor.GRAY + " Neu: " + ChatColor.WHITE + "+" + slots + " permanente Slots");
player.sendMessage(ChatColor.GRAY + " Gesamt: " + ChatColor.GREEN + total + " Plot-Slots");
player.sendMessage(ChatColor.GOLD + " ════════════════════════════");
player.sendMessage("");
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
});
}
int getExtraSlots(String playerName) { return db.getExtraSlots(playerName); }
void grantExtraSlotsByName(String playerName, int slots, String label) {
db.addExtraSlots(playerName, slots);
applyLimit(playerName);
}
// ── Monatliches Abo ───────────────────────────────────────────
void grantAbo(Player player, int slots, String label, int monthlyPrice) {
db.savePlotAbo(player.getName(), slots, label, monthlyPrice);
applyLimit(player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage("");
player.sendMessage(ChatColor.GOLD + "📦 ════════════════════════════");
player.sendMessage(ChatColor.YELLOW + " Plot-Abo aktiviert!");
player.sendMessage(ChatColor.GRAY + " Paket: " + ChatColor.WHITE + label);
player.sendMessage(ChatColor.GRAY + " Slots: " + ChatColor.WHITE + "+" + slots);
player.sendMessage(ChatColor.GRAY + " Monatspreis: " + ChatColor.WHITE + monthlyPrice + " " + currency);
player.sendMessage(ChatColor.GRAY + " Nächste Abbuchung: " + ChatColor.YELLOW + "1. des Folgemonats");
player.sendMessage(ChatColor.GRAY + " Status: /plotabo");
player.sendMessage(ChatColor.GOLD + " ════════════════════════════");
player.sendMessage("");
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.2F);
});
}
void grantAboByName(String playerName, int slots, String label, int monthlyPrice) {
db.savePlotAbo(playerName, slots, label, monthlyPrice);
applyLimit(playerName);
}
boolean cancelAbo(String playerName) {
boolean ok = db.cancelPlotAbo(playerName);
if (ok) applyLimit(playerName);
return ok;
}
int getAboSlots(String playerName) { return db.getPlotAboSlots(playerName); }
PlotAboEntry getActiveAbo(String playerName) { return db.getActivePlotAbo(playerName); }
// ── Einfrieren überzähliger Plots ─────────────────────────────
void freezeExcessPlots(String playerName, int newLimit) {
Bukkit.getScheduler().runTask(plugin, () -> {
if (Bukkit.getPluginManager().getPlugin("PlotSquared") == null) return;
Player online = Bukkit.getPlayer(playerName);
if (online != null) {
online.sendMessage("");
online.sendMessage(ChatColor.RED + "📦 ════════════════════════════");
online.sendMessage(ChatColor.RED + " Plot-Abo abgelaufen!");
online.sendMessage(ChatColor.GRAY + " Limit reduziert auf: "
+ ChatColor.WHITE + newLimit);
online.sendMessage(ChatColor.YELLOW + " Überzählige Plots sind eingefroren.");
online.sendMessage(ChatColor.GRAY + " Abo erneuern: " + ChatColor.AQUA + "viper-network.de");
online.sendMessage(ChatColor.RED + " ════════════════════════════");
online.sendMessage("");
online.playSound(online.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.5F);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"lp user " + playerName + " meta set frozen_excess_plots true");
});
}
void unfreezeExcessPlots(String playerName) {
Bukkit.getScheduler().runTask(plugin, () ->
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"lp user " + playerName + " meta unset frozen_excess_plots"));
}
// ── Monatlicher Billing-Check ─────────────────────────────────
void startBillingChecker() {
new BukkitRunnable() {
@Override public void run() {
if (java.time.LocalDate.now().getDayOfMonth() != 1) return;
new BukkitRunnable() {
@Override public void run() {
List<PlotAboEntry> due = db.getDuePlotBillings();
if (due.isEmpty()) return;
plugin.getLogger().info("[PlotSlots] Billing: " + due.size() + " Abo(s).");
for (PlotAboEntry entry : due) {
final String pName = entry.playerName;
final int price = entry.monthlyPrice;
final int slots = entry.aboSlots;
Bukkit.getScheduler().runTask(plugin, () -> {
@SuppressWarnings("deprecation")
org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(pName);
if (op == null || !op.hasPlayedBefore()) return;
if ((price == 0) || econ.getBalance(op) >= price) {
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);
}
}
new BukkitRunnable() {
@Override public void run() {
db.renewPlotAbo(pName);
}
}.runTaskAsynchronously(plugin);
unfreezeExcessPlots(pName);
applyLimit(pName);
Player online = Bukkit.getPlayer(pName);
if (online != null) {
online.sendMessage(ChatColor.GOLD + "📦 Plot-Abo verlängert! +"
+ slots + " Slots | " + price + " " + currency + " abgebucht.");
online.playSound(online.getLocation(),
Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F);
}
} else {
new BukkitRunnable() {
@Override public void run() {
db.cancelPlotAboPaymentFailed(pName);
}
}.runTaskAsynchronously(plugin);
new BukkitRunnable() {
@Override public void run() {
int newLimit = getRankBaseByName(pName) + db.getExtraSlots(pName);
freezeExcessPlots(pName, newLimit);
// applyLimit kümmert sich um die Permission
applyLimit(pName);
}
}.runTaskAsynchronously(plugin);
}
});
}
}
}.runTaskAsynchronously(plugin);
}
}.runTaskTimer(plugin, 20L * 60 * 5, 20L * 60 * 60 * 24);
}
// ── PlayerJoin ────────────────────────────────────────────────
void onPlayerJoin(Player player) {
applyLimit(player.getName());
if (player.hasPermission("meta.frozen_excess_plots.true")) {
player.sendMessage(ChatColor.RED + "📦 Einige deiner Plots sind eingefroren!");
player.sendMessage(ChatColor.GRAY + " Grund: Plot-Abo abgelaufen.");
player.sendMessage(ChatColor.WHITE + " Erneuern: " + ChatColor.AQUA + "viper-network.de");
}
}
// ── Data class ────────────────────────────────────────────────
static class PlotAboEntry {
final String playerName, label, cancellationReason, nextBillingDate, periodEnd;
final int aboSlots, monthlyPrice;
final boolean cancelled;
PlotAboEntry(String playerName, String label, int aboSlots, int monthlyPrice,
boolean cancelled, String cancellationReason,
String nextBillingDate, String periodEnd) {
this.playerName = playerName;
this.label = label;
this.aboSlots = aboSlots;
this.monthlyPrice = monthlyPrice;
this.cancelled = cancelled;
this.cancellationReason = cancellationReason;
this.nextBillingDate = nextBillingDate;
this.periodEnd = periodEnd;
}
}
}
// ===========================================================
// SELL MANAGER
// ===========================================================

View File

@@ -29,7 +29,7 @@ fly-redeem-disabled: false
# Leer lassen ("") wenn niemand die Einnahmen erhalten soll
income-receiver: ""
# MySQL-Verbindung (für Fly-Code-System und Rang-Sessions)
# MySQL-Verbindung (für Fly-Code-System, Rang-Sessions, Fly-Abo und Plot-Slots)
mysql:
host: "localhost"
port: "3306"
@@ -43,20 +43,32 @@ mysql:
sell:
# Ankauf auf diesem Server aktivieren
enabled: true
# Preiskorrektur relativ zum WP-Ankaufspreis (in Prozent)
# Beispiele:
# 0.0 → exakt den WP-Ankaufspreis zahlen
# -10.0 → 10 % weniger als der WP-Ankaufspreis
# +5.0 → 5 % mehr als der WP-Ankaufspreis
# Hinweis: Den Basis-Ankaufspreis konfigurierst du im WP-Admin
# unter Items → <Item bearbeiten> → "Ankauf aktivieren"
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
# Maximale Fly-Zeit pro Tag in Stunden (Standard: 6)
max-daily-hours: 6
# ──────────────────────────────────────────────
# Plot-Slot Einstellungen (nur Citybuild)
# ──────────────────────────────────────────────
plot-slots:
# Auf welchem Server greift das Plot-Slot-System?
# Muss mit server-name des Citybuild-Servers übereinstimmen
server: "citybuild"
# Standard-Plot-Slots pro LuckPerms-Gruppe
# LuckPerms Meta "plotlimit" wird automatisch gesetzt und von
# PlotSquared über die Option "max-plots-per-player: -1" + Meta gelesen
rank-defaults:
member: 2
vip: 3
scout: 4
primo: 5

View File

@@ -1,5 +1,5 @@
name: IngameShopSpigot
version: 1.1
version: 1.2
main: de.mviper.spigot.IngameShopSpigot
api-version: 1.19
depend: [Vault]
@@ -20,11 +20,11 @@ commands:
usage: /flyredeem <Code>
permission: ingameshop.flyredeem
flycodes:
description: Öffnet ein GUI mit deinen ungenutzten Fly-Gutscheinen (einlösen oder weitergeben)
description: Öffnet ein GUI mit deinen ungenutzten Fly-Gutscheinen
usage: /flycodes
permission: ingameshop.flycodes
flygive:
description: Gibt einem Spieler einen Fly-Gutschein-Code
description: Gibt einem Spieler einen Fly-Gutschein-Code (Admin)
usage: /flygive <Spieler> <Sekunden> [Label]
permission: ingameshop.flygive
flypause:
@@ -32,7 +32,7 @@ commands:
usage: /flypause
permission: ingameshop.flypause
rankinfo:
description: Zeigt deine aktiven (zeitbasierten) Ränge an
description: Zeigt deine aktiven zeitbasierten Ränge an
usage: /rankinfo
permission: ingameshop.rankinfo
sell:
@@ -44,17 +44,37 @@ commands:
usage: /wpis <reload>
permission: ingameshop.reload
flyabo:
description: Zeigt deinen Fly-Abo Status und Tageslimit an
description: Zeigt deinen Fly-Abo Status und heutiges Tageslimit
usage: /flyabo
permission: ingameshop.flyabo
flyabocancel:
description: Kündigt dein Fly-Abo zum Ablaufdatum
description: Kündigt dein Fly-Abo zum Monatsende
usage: /flyabocancel [confirm]
permission: ingameshop.flyabocancel
flyabogive:
description: Gibt einem Spieler ein Fly-Abo (Admin)
usage: /flyabogive <Spieler> <Tage> [Label]
description: Gibt einem Spieler ein Fly-Abo manuell (Admin)
usage: /flyabogive <Spieler> <Monatspreis> [Label]
permission: ingameshop.flyabogive
plotslots:
description: Zeigt deine aktuellen Plot-Slots (Basis + Extra + Abo)
usage: /plotslots
permission: ingameshop.plotslots
plotabo:
description: Zeigt deinen Plot-Abo Status
usage: /plotabo
permission: ingameshop.plotabo
plotabocancel:
description: Kündigt dein Plot-Abo zum Monatsende
usage: /plotabocancel [confirm]
permission: ingameshop.plotabocancel
plotabogive:
description: Gibt einem Spieler ein Plot-Abo manuell (Admin)
usage: /plotabogive <Spieler> <Slots> <Monatspreis> [Label]
permission: ingameshop.plotabogive
plotslotsgive:
description: Gibt einem Spieler permanente Plot-Slots (Admin)
usage: /plotslotsgive <Spieler> <Slots> [Label]
permission: ingameshop.plotslotsgive
permissions:
ingameshop.orders:
@@ -70,7 +90,7 @@ permissions:
description: Kann eigene ungenutzte Fly-Codes auflisten
default: true
ingameshop.flygive:
description: Kann Spielern Fly-Gutscheine geben (Admin/OP/Mod)
description: Kann Spielern Fly-Gutscheine geben (Admin)
default: op
ingameshop.flypause:
description: Kann die eigene Fly-Zeit pausieren
@@ -92,4 +112,19 @@ permissions:
default: true
ingameshop.flyabogive:
description: Kann Spielern Fly-Abos manuell vergeben (Admin)
default: op
ingameshop.plotslots:
description: Kann eigene Plot-Slot Übersicht einsehen
default: true
ingameshop.plotabo:
description: Kann eigenen Plot-Abo-Status einsehen
default: true
ingameshop.plotabocancel:
description: Kann eigenes Plot-Abo kündigen
default: true
ingameshop.plotabogive:
description: Kann Spielern Plot-Abos manuell vergeben (Admin)
default: op
ingameshop.plotslotsgive:
description: Kann Spielern permanente Plot-Slots vergeben (Admin)
default: op