diff --git a/src/main/java/de/viper/survivalplus/SurvivalPlus.java b/src/main/java/de/viper/survivalplus/SurvivalPlus.java index 736291a..3048f04 100644 --- a/src/main/java/de/viper/survivalplus/SurvivalPlus.java +++ b/src/main/java/de/viper/survivalplus/SurvivalPlus.java @@ -34,6 +34,9 @@ import de.viper.survivalplus.commands.TradeCommand; import de.viper.survivalplus.commands.ReportCommand; import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.commands.ShopCommand; +import de.viper.survivalplus.commands.JobCommand; +import de.viper.survivalplus.jobs.JobManager; +import de.viper.survivalplus.jobs.JobListener; import de.viper.survivalplus.commands.HealCommand; import de.viper.survivalplus.Manager.TablistManager; import de.viper.survivalplus.Manager.CommandBlocker; @@ -158,6 +161,12 @@ public class SurvivalPlus extends JavaPlugin { private final String VANISH_META_KEY = "vanished"; // --------------------------------------- + // --- Shop & Economy --- + private ShopManager shopManager; + private JobManager jobManager; + private de.viper.survivalplus.economy.BalanceManager balanceManager; + // ---------------------- + public void reloadTablistConfig() { if (tablistFile == null) tablistFile = new File(getDataFolder(), "tablist.yml"); if (!tablistFile.exists()) { @@ -274,17 +283,32 @@ public class SurvivalPlus extends JavaPlugin { reloadBlockedCommandsConfig(); loadClaims(); - // --- NEU: Vault Economy Setup --- + // --- Economy Setup --- + // BalanceManager sofort initialisieren. + balanceManager = new de.viper.survivalplus.economy.BalanceManager(this); + if (Bukkit.getPluginManager().getPlugin("Vault") != null) { + // 1. Prüfen ob bereits ein externes Economy-Plugin registriert ist (z.B. EssentialsX) RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); if (rsp != null) { + // Externes Plugin gefunden – bevorzugen economy = rsp.getProvider(); - getLogger().info("Vault Economy erfolgreich verbunden."); + getLogger().info("Externes Economy-Plugin verbunden: " + economy.getName()); } else { - getLogger().warning("Vault Plugin gefunden, aber kein Economy Provider!"); + // SOFORT eigenen Provider registrieren, damit andere Plugins ihn beim Start finden + de.viper.survivalplus.economy.SurvivalPlusEconomy ownEconomy = + new de.viper.survivalplus.economy.SurvivalPlusEconomy(this, balanceManager); + getServer().getServicesManager().register( + Economy.class, ownEconomy, this, + org.bukkit.plugin.ServicePriority.Normal + ); + economy = ownEconomy; + getLogger().info("SurvivalPlusEconomy als Vault-Provider registriert."); } + } else { + getLogger().warning("Vault nicht gefunden! Economy-Funktionen werden deaktiviert."); } - // ------------------------------------------------- + // --------------------- PluginManager pluginManager = getServer().getPluginManager(); try { @@ -358,7 +382,14 @@ public class SurvivalPlus extends JavaPlugin { getCommand("workbench").setExecutor(new WorkbenchCommand()); getCommand("anvil").setExecutor(new AnvilCommand()); getCommand("nick").setExecutor(new NickCommand(this)); + // ShopManager zentral initialisieren – wird von ShopCommand und ShopGui gemeinsam genutzt + shopManager = new ShopManager(this); getCommand("shop").setExecutor(new ShopCommand(this)); + getCommand("money").setExecutor(new MoneyCommand(this)); + // --- Jobs --- + jobManager = new JobManager(this); + getCommand("job").setExecutor(new JobCommand(this, jobManager)); + pluginManager.registerEvents(new JobListener(this, jobManager), this); getCommand("stats").setExecutor(new StatsCommand(this, statsManager)); getCommand("spawn").setExecutor(new SpawnCommand(this)); getCommand("setwarp").setExecutor(new SetWarpCommand(this, warpManager)); @@ -1331,7 +1362,7 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver "tpaccept", "tpdeny", "block", "unblock", "blocklist", "kit", "nick", "ride", "vanish", "freeze", "lootchests", "tploot", "day", "night", "trade", "tradeaccept", "report", "showreport", "clearreport", "shop", "spawn", "setwarp", "delwarp", - "warps", "startchallenge", "heal", "claim", "head" + "warps", "startchallenge", "heal", "claim", "head", "money", "job", "jobs" }; for (String name : commands) { if (getCommand(name) != null) { @@ -1391,6 +1422,21 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver return economy; } + // ShopManager + public ShopManager getShopManager() { + return shopManager; + } + + // BalanceManager (für direkte Admin-Zugriffe, z.B. /money set) + public de.viper.survivalplus.economy.BalanceManager getBalanceManager() { + return balanceManager; + } + + // JobManager + public JobManager getJobManager() { + return jobManager; + } + // ProtocolLib Bridge (für Silent Vanish) public ProtocolManager getProtocolManager() { return ProtocolLibrary.getProtocolManager(); diff --git a/src/main/java/de/viper/survivalplus/commands/JobCommand.java b/src/main/java/de/viper/survivalplus/commands/JobCommand.java new file mode 100644 index 0000000..3cb4adf --- /dev/null +++ b/src/main/java/de/viper/survivalplus/commands/JobCommand.java @@ -0,0 +1,189 @@ +package de.viper.survivalplus.commands; + +import de.viper.survivalplus.SurvivalPlus; +import de.viper.survivalplus.jobs.JobGui; +import de.viper.survivalplus.jobs.JobManager; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +public class JobCommand implements CommandExecutor { + + private final SurvivalPlus plugin; + private final JobManager jobManager; + + public JobCommand(SurvivalPlus plugin, JobManager jobManager) { + this.plugin = plugin; + this.jobManager = jobManager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler."); + return true; + } + Player player = (Player) sender; + + // Kein Argument oder "gui" / "list" → GUI öffnen + if (args.length == 0 + || args[0].equalsIgnoreCase("gui") + || args[0].equalsIgnoreCase("list")) { + new JobGui(plugin, player, jobManager); + return true; + } + + switch (args[0].toLowerCase()) { + + // /job info + case "info": { + if (args.length < 2) { + player.sendMessage(ChatColor.RED + "Benutzung: /job info "); + return true; + } + cmdInfo(player, args[1].toLowerCase()); + break; + } + + // /job join + case "join": { + if (args.length < 2) { + player.sendMessage(ChatColor.RED + "Benutzung: /job join "); + return true; + } + cmdJoin(player, args[1].toLowerCase()); + break; + } + + // /job leave + case "leave": { + if (args.length < 2) { + player.sendMessage(ChatColor.RED + "Benutzung: /job leave "); + return true; + } + cmdLeave(player, args[1].toLowerCase()); + break; + } + + // /job myjobs + case "myjobs": { + cmdMyJobs(player); + break; + } + + default: + sendHelp(player); + break; + } + return true; + } + + // ------------------------------------------------------------------------- + // Subcommands (Textfallback, GUI ist der bevorzugte Weg) + // ------------------------------------------------------------------------- + + private void cmdInfo(Player player, String jobId) { + if (!jobManager.jobExists(jobId)) { + player.sendMessage(ChatColor.RED + "Job '" + jobId + "' nicht gefunden. /job list"); + return; + } + boolean hasJob = jobManager.hasJob(player.getUniqueId(), jobId); + int level = jobManager.getLevel(player.getUniqueId(), jobId); + int xp = jobManager.getXp(player.getUniqueId(), jobId); + int xpReq = jobManager.getXpRequired(jobId, level); + + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + player.sendMessage(ChatColor.YELLOW + " " + jobManager.getJobDisplayName(jobId)); + player.sendMessage(ChatColor.GRAY + " " + jobManager.getJobDescription(jobId)); + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + if (hasJob) { + player.sendMessage(ChatColor.WHITE + " Level: " + ChatColor.AQUA + level + + ChatColor.GRAY + " / " + jobManager.getMaxLevel()); + if (level < jobManager.getMaxLevel()) { + player.sendMessage(ChatColor.WHITE + " XP: " + ChatColor.AQUA + xp + + ChatColor.GRAY + " / " + xpReq); + player.sendMessage(" " + xpBar(xp, xpReq)); + } else { + player.sendMessage(ChatColor.GOLD + " ⭐ Maximales Level erreicht!"); + } + } else { + player.sendMessage(ChatColor.GRAY + " Nicht aktiv."); + player.sendMessage(ChatColor.GREEN + " /job join " + jobId); + } + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + } + + private void cmdJoin(Player player, String jobId) { + if (!jobManager.jobExists(jobId)) { + player.sendMessage(ChatColor.RED + "Job '" + jobId + "' nicht gefunden. /job list"); + return; + } + if (jobManager.hasJob(player.getUniqueId(), jobId)) { + player.sendMessage(ChatColor.RED + "Du hast diesen Job bereits."); + return; + } + if (jobManager.getPlayerJobs(player.getUniqueId()).size() >= jobManager.getMaxJobs()) { + player.sendMessage(ChatColor.RED + "Du kannst maximal " + jobManager.getMaxJobs() + + " Jobs gleichzeitig haben. Verlasse zuerst einen Job."); + return; + } + jobManager.joinJob(player.getUniqueId(), jobId); + player.sendMessage(ChatColor.GREEN + "Du hast den Job " + + ChatColor.YELLOW + jobManager.getJobDisplayName(jobId) + + ChatColor.GREEN + " angenommen!"); + } + + private void cmdLeave(Player player, String jobId) { + if (!jobManager.hasJob(player.getUniqueId(), jobId)) { + player.sendMessage(ChatColor.RED + "Du hast den Job '" + jobId + "' nicht."); + return; + } + jobManager.leaveJob(player.getUniqueId(), jobId); + player.sendMessage(ChatColor.YELLOW + "Du hast den Job " + + ChatColor.WHITE + jobManager.getJobDisplayName(jobId) + + ChatColor.YELLOW + " verlassen. Level bleibt gespeichert."); + } + + private void cmdMyJobs(Player player) { + List jobs = jobManager.getPlayerJobs(player.getUniqueId()); + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + player.sendMessage(ChatColor.YELLOW + " Deine Jobs (" + + jobs.size() + "/" + jobManager.getMaxJobs() + "):"); + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + if (jobs.isEmpty()) { + player.sendMessage(ChatColor.GRAY + " Noch kein Job aktiv. /job"); + } else { + for (String jobId : jobs) { + int level = jobManager.getLevel(player.getUniqueId(), jobId); + int xp = jobManager.getXp(player.getUniqueId(), jobId); + int xpReq = jobManager.getXpRequired(jobId, level); + player.sendMessage(ChatColor.WHITE + " " + jobManager.getJobDisplayName(jobId) + + ChatColor.GRAY + " Lv." + ChatColor.AQUA + level + + ChatColor.GRAY + " XP: " + xp + "/" + xpReq); + } + } + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + } + + private void sendHelp(Player player) { + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + player.sendMessage(ChatColor.YELLOW + " /job " + ChatColor.GRAY + "GUI öffnen"); + player.sendMessage(ChatColor.YELLOW + " /job info " + ChatColor.GRAY + "Job-Details & Level"); + player.sendMessage(ChatColor.YELLOW + " /job join " + ChatColor.GRAY + "Job annehmen"); + player.sendMessage(ChatColor.YELLOW + " /job leave " + ChatColor.GRAY + "Job verlassen"); + player.sendMessage(ChatColor.YELLOW + " /job myjobs " + ChatColor.GRAY + "Deine aktiven Jobs"); + player.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + } + + private String xpBar(int current, int max) { + int len = 20; + int filled = max == 0 ? 0 : Math.min((int) ((double) current / max * len), len); + return ChatColor.GREEN + "█".repeat(filled) + + ChatColor.DARK_GRAY + "█".repeat(len - filled) + + ChatColor.GRAY + " " + current + "/" + max; + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/MoneyCommand.java b/src/main/java/de/viper/survivalplus/commands/MoneyCommand.java new file mode 100644 index 0000000..e1f8ee6 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/commands/MoneyCommand.java @@ -0,0 +1,294 @@ +package de.viper.survivalplus.commands; + +import de.viper.survivalplus.SurvivalPlus; +import de.viper.survivalplus.economy.BalanceManager; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.*; + +public class MoneyCommand implements CommandExecutor { + + private final SurvivalPlus plugin; + + public MoneyCommand(SurvivalPlus plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + Economy economy = plugin.getEconomy(); + if (economy == null) { + sender.sendMessage(ChatColor.RED + "Economy-System ist nicht verfügbar."); + return true; + } + + // /money oder /money → Guthaben anzeigen + if (args.length == 0) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Als Konsole bitte /money benutzen."); + return true; + } + Player player = (Player) sender; + double bal = economy.getBalance(player); + player.sendMessage(ChatColor.GOLD + "Dein Guthaben: " + ChatColor.GREEN + + String.format("%.2f", bal) + " " + economy.currencyNamePlural()); + return true; + } + + String sub = args[0].toLowerCase(); + + // /money → Guthaben eines anderen Spielers anzeigen (nur OP) + if (args.length == 1 && !isSubCommand(sub)) { + if (!sender.hasPermission("survivalplus.money.admin")) { + sender.sendMessage(ChatColor.RED + "Du hast keine Berechtigung, das Guthaben anderer Spieler zu sehen."); + return true; + } + OfflinePlayer target = getOfflinePlayer(args[0]); + if (target == null) { + sender.sendMessage(ChatColor.RED + "Spieler '" + args[0] + "' nicht gefunden."); + return true; + } + double bal = economy.getBalance(target); + sender.sendMessage(ChatColor.GOLD + "Guthaben von " + ChatColor.YELLOW + target.getName() + + ChatColor.GOLD + ": " + ChatColor.GREEN + + String.format("%.2f", bal) + " " + economy.currencyNamePlural()); + return true; + } + + switch (sub) { + + // /money pay + case "pay": { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler."); + return true; + } + if (!sender.hasPermission("survivalplus.money.pay")) { + sender.sendMessage(ChatColor.RED + "Du hast keine Berechtigung für diesen Befehl."); + return true; + } + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "Benutzung: /money pay "); + return true; + } + Player payer = (Player) sender; + OfflinePlayer target = getOfflinePlayer(args[1]); + if (target == null || !target.isOnline()) { + sender.sendMessage(ChatColor.RED + "Spieler '" + args[1] + "' ist nicht online."); + return true; + } + if (target.getUniqueId().equals(payer.getUniqueId())) { + sender.sendMessage(ChatColor.RED + "Du kannst dir nicht selbst Geld überweisen."); + return true; + } + double amount = parseAmount(sender, args[2]); + if (amount < 0) return true; + if (amount == 0) { + sender.sendMessage(ChatColor.RED + "Der Betrag muss größer als 0 sein."); + return true; + } + + EconomyResponse withdraw = economy.withdrawPlayer(payer, amount); + if (!withdraw.transactionSuccess()) { + sender.sendMessage(ChatColor.RED + "Nicht genug Guthaben! Du hast " + + String.format("%.2f", economy.getBalance(payer)) + " " + + economy.currencyNamePlural() + "."); + return true; + } + economy.depositPlayer(target, amount); + + payer.sendMessage(ChatColor.GREEN + "Du hast " + ChatColor.YELLOW + + String.format("%.2f", amount) + " " + economy.currencyNamePlural() + + ChatColor.GREEN + " an " + ChatColor.YELLOW + target.getName() + + ChatColor.GREEN + " überwiesen."); + if (target.getPlayer() != null) { + target.getPlayer().sendMessage(ChatColor.GREEN + ChatColor.BOLD.toString() + "+" + + String.format("%.2f", amount) + " " + economy.currencyNamePlural() + + ChatColor.RESET + ChatColor.GREEN + " von " + ChatColor.YELLOW + payer.getName()); + } + return true; + } + + // /money set + case "set": { + if (!sender.hasPermission("survivalplus.money.admin")) { + sender.sendMessage(ChatColor.RED + "Du hast keine Berechtigung für diesen Befehl."); + return true; + } + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "Benutzung: /money set "); + return true; + } + OfflinePlayer target = getOfflinePlayer(args[1]); + if (target == null) { + sender.sendMessage(ChatColor.RED + "Spieler '" + args[1] + "' nicht gefunden."); + return true; + } + double amount = parseAmount(sender, args[2]); + if (amount < 0) return true; + + plugin.getBalanceManager().setBalance(target.getUniqueId(), amount); + sender.sendMessage(ChatColor.GREEN + "Guthaben von " + ChatColor.YELLOW + target.getName() + + ChatColor.GREEN + " wurde auf " + ChatColor.YELLOW + + String.format("%.2f", amount) + " " + economy.currencyNamePlural() + + ChatColor.GREEN + " gesetzt."); + notifyTarget(target, ChatColor.YELLOW + "Dein Guthaben wurde auf " + + String.format("%.2f", amount) + " " + economy.currencyNamePlural() + " gesetzt."); + return true; + } + + // /money add + case "add": { + if (!sender.hasPermission("survivalplus.money.admin")) { + sender.sendMessage(ChatColor.RED + "Du hast keine Berechtigung für diesen Befehl."); + return true; + } + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "Benutzung: /money add "); + return true; + } + OfflinePlayer target = getOfflinePlayer(args[1]); + if (target == null) { + sender.sendMessage(ChatColor.RED + "Spieler '" + args[1] + "' nicht gefunden."); + return true; + } + double amount = parseAmount(sender, args[2]); + if (amount < 0) return true; + + economy.depositPlayer(target, amount); + sender.sendMessage(ChatColor.GREEN + "Du hast " + ChatColor.YELLOW + + String.format("%.2f", amount) + " " + economy.currencyNamePlural() + + ChatColor.GREEN + " zu " + ChatColor.YELLOW + target.getName() + + ChatColor.GREEN + " hinzugefügt. Neues Guthaben: " + ChatColor.YELLOW + + String.format("%.2f", economy.getBalance(target))); + notifyTarget(target, ChatColor.GREEN + "+" + String.format("%.2f", amount) + + " " + economy.currencyNamePlural() + " von einem Admin gutgeschrieben."); + return true; + } + + // /money remove + case "remove": { + if (!sender.hasPermission("survivalplus.money.admin")) { + sender.sendMessage(ChatColor.RED + "Du hast keine Berechtigung für diesen Befehl."); + return true; + } + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "Benutzung: /money remove "); + return true; + } + OfflinePlayer target = getOfflinePlayer(args[1]); + if (target == null) { + sender.sendMessage(ChatColor.RED + "Spieler '" + args[1] + "' nicht gefunden."); + return true; + } + double amount = parseAmount(sender, args[2]); + if (amount < 0) return true; + + EconomyResponse response = economy.withdrawPlayer(target, amount); + if (!response.transactionSuccess()) { + sender.sendMessage(ChatColor.RED + target.getName() + " hat nicht genug Guthaben. " + + "Aktuell: " + String.format("%.2f", economy.getBalance(target))); + return true; + } + sender.sendMessage(ChatColor.GREEN + "Du hast " + ChatColor.YELLOW + + String.format("%.2f", amount) + " " + economy.currencyNamePlural() + + ChatColor.GREEN + " von " + ChatColor.YELLOW + target.getName() + + ChatColor.GREEN + " abgezogen. Neues Guthaben: " + ChatColor.YELLOW + + String.format("%.2f", economy.getBalance(target))); + notifyTarget(target, ChatColor.RED + "-" + String.format("%.2f", amount) + + " " + economy.currencyNamePlural() + " von einem Admin abgezogen."); + return true; + } + + // /money top → Top 10 reichste Spieler + case "top": { + if (!sender.hasPermission("survivalplus.baltop")) { + sender.sendMessage(ChatColor.RED + "Du hast keine Berechtigung für diesen Befehl."); + return true; + } + BalanceManager bm = plugin.getBalanceManager(); + // Alle Einträge aus dem BalanceManager holen und sortieren + Map allBalances = bm.getAllBalances(); + List> sorted = new ArrayList<>(allBalances.entrySet()); + sorted.sort((a, b) -> Double.compare(b.getValue(), a.getValue())); + + sender.sendMessage(ChatColor.GOLD + "--- " + ChatColor.YELLOW + "Top 10 Reichste Spieler" + + ChatColor.GOLD + " ---"); + int shown = 0; + for (Map.Entry entry : sorted) { + if (shown >= 10) break; + OfflinePlayer p = Bukkit.getOfflinePlayer(entry.getKey()); + String name = (p.getName() != null) ? p.getName() : entry.getKey().toString(); + shown++; + sender.sendMessage(ChatColor.YELLOW + "#" + shown + " " + ChatColor.WHITE + name + + ChatColor.GRAY + " - " + ChatColor.GREEN + + String.format("%.2f", entry.getValue()) + " " + economy.currencyNamePlural()); + } + if (shown == 0) { + sender.sendMessage(ChatColor.GRAY + "Noch keine Kontodaten vorhanden."); + } + return true; + } + + default: + sender.sendMessage(ChatColor.RED + "Unbekannter Unterbefehl!"); + sender.sendMessage(ChatColor.GRAY + "/money [pay ]"); + sender.sendMessage(ChatColor.GRAY + "/money [set|add|remove ]"); + sender.sendMessage(ChatColor.GRAY + "/money top"); + return true; + } + } + + // ---- Hilfsmethoden ---- + + private boolean isSubCommand(String s) { + return s.equals("pay") || s.equals("set") || s.equals("add") + || s.equals("remove") || s.equals("top"); + } + + /** + * Parst einen Betrag. Gibt -1 zurück und sendet eine Fehlermeldung bei ungültigem Wert. + */ + private double parseAmount(CommandSender sender, String raw) { + try { + double val = Double.parseDouble(raw); + if (val < 0) { + sender.sendMessage(ChatColor.RED + "Der Betrag darf nicht negativ sein."); + return -1; + } + return val; + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "'" + raw + "' ist keine gültige Zahl!"); + return -1; + } + } + + /** + * Sucht zuerst online, dann offline. + */ + @SuppressWarnings("deprecation") + private OfflinePlayer getOfflinePlayer(String name) { + Player online = Bukkit.getPlayer(name); + if (online != null) return online; + OfflinePlayer offline = Bukkit.getOfflinePlayer(name); + // hasPlayedBefore() verhindert, dass komplett unbekannte Namen akzeptiert werden + return offline.hasPlayedBefore() ? offline : null; + } + + /** + * Schickt einer OfflinePlayer-Benachrichtigung, falls sie online sind. + */ + private void notifyTarget(OfflinePlayer target, String message) { + if (target.isOnline() && target.getPlayer() != null) { + target.getPlayer().sendMessage(message); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/ShopCommand.java b/src/main/java/de/viper/survivalplus/commands/ShopCommand.java index 79647f0..fc3dd55 100644 --- a/src/main/java/de/viper/survivalplus/commands/ShopCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/ShopCommand.java @@ -3,6 +3,8 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.SurvivalPlus; import de.viper.survivalplus.gui.ShopGui; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -12,32 +14,17 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.RegisteredServiceProvider; -import net.milkbowl.vault.economy.Economy; -import net.milkbowl.vault.economy.EconomyResponse; public class ShopCommand implements CommandExecutor { - private final ShopManager shopManager; private final SurvivalPlus plugin; - private Economy economy; + // ShopManager und Economy kommen zentral aus dem Plugin + private final ShopManager shopManager; public ShopCommand(SurvivalPlus plugin) { this.plugin = plugin; - this.shopManager = new ShopManager(plugin); - - // Economy laden (für Admin-Verkauf an Server) - try { - if (Bukkit.getPluginManager().isPluginEnabled("Vault")) { - RegisteredServiceProvider rsp = Bukkit.getServicesManager().getRegistration(Economy.class); - if (rsp != null) { - this.economy = rsp.getProvider(); - plugin.getLogger().info("Vault Economy für ShopCommand gefunden."); - } - } - } catch (Exception e) { - // Ignorieren wenn Vault fehlt - } + // ShopManager zentral aus dem Plugin holen, KEINE eigene Instanz erstellen + this.shopManager = plugin.getShopManager(); } private String getMessage(String key) { @@ -53,28 +40,33 @@ public class ShopCommand implements CommandExecutor { Player player = (Player) sender; // Shop-GUI öffnen - if (args.length == 0 || (args.length > 0 && args[0].equalsIgnoreCase("gui"))) { + if (args.length == 0 || args[0].equalsIgnoreCase("gui")) { ShopGui shopGui = new ShopGui(plugin, player, shopManager); Bukkit.getPluginManager().registerEvents(shopGui, plugin); player.openInventory(shopGui.getInventory()); return true; } - // --- SHOP ADD LOGIK --- - if (args.length >= 4 && args[0].equalsIgnoreCase("add")) { - - // Economy-Check + // --- SHOP ADD --- + if (args[0].equalsIgnoreCase("add")) { + if (args.length < 4) { + sender.sendMessage(ChatColor.RED + "Falsche Benutzung!"); + sender.sendMessage(ChatColor.GRAY + "/shop add "); + return true; + } + + // Economy zentral aus Plugin holen + Economy economy = plugin.getEconomy(); if (economy == null) { sender.sendMessage(ChatColor.RED + "Vault Economy ist nicht verfügbar!"); return true; } String materialName = args[1]; - String priceStr = args[2]; - String amountStr = args[3]; + String priceStr = args[2]; + String amountStr = args[3]; Material mat = Material.matchMaterial(materialName); - if (mat == null) { sender.sendMessage(ChatColor.RED + "Das Material '" + materialName + "' wurde nicht gefunden!"); return true; @@ -82,12 +74,11 @@ public class ShopCommand implements CommandExecutor { double price; int amount; - try { - price = Double.parseDouble(priceStr); + price = Double.parseDouble(priceStr); amount = Integer.parseInt(amountStr); } catch (NumberFormatException e) { - sender.sendMessage(ChatColor.RED + "Ungültige Preis oder Bestandszahl! Beispiel: /shop add Diamond 20 64"); + sender.sendMessage(ChatColor.RED + "Ungültige Preis- oder Bestandszahl! Beispiel: /shop add Diamond 20 64"); return true; } @@ -96,42 +87,37 @@ public class ShopCommand implements CommandExecutor { return true; } - // 1. Prüfen: Hat der Spieler die Items? + // Hat der Spieler die Items? int hasAmount = countItems(player.getInventory(), mat); - if (hasAmount < amount) { - sender.sendMessage(ChatColor.RED + "Du hast nicht genug Items! (Benötigt: " + amount + ", Vorhanden: " + hasAmount + ")"); + sender.sendMessage(ChatColor.RED + "Du hast nicht genug Items! (Benötigt: " + amount + + ", Vorhanden: " + hasAmount + ")"); return true; } - // 2. Items aus Inventar entfernen + // Items entfernen player.getInventory().removeItem(new ItemStack(mat, amount)); - player.updateInventory(); // Wichtig für Client-Update + player.updateInventory(); - // 3. Spieler Geld geben (Verkauf an Server) + // Geld auszahlen (Verkauf an Server) double totalCost = price * amount; EconomyResponse response = economy.depositPlayer(player, totalCost); if (response.transactionSuccess()) { - sender.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + mat.name() + " für " + totalCost + " Coins an den Server verkauft!"); - - // 4. Shop-Config aktualisieren (Lagerbestand erhöhen) + sender.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + mat.name() + + " für " + String.format("%.2f", totalCost) + " Coins an den Server verkauft!"); + + // Shop-Config aktualisieren String itemKey = mat.name().toLowerCase(); shopManager.addOrUpdateItem(itemKey, price, shopManager.getStock(itemKey) + amount); } else { - sender.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung!"); - return true; + // Rollback: Items zurückgeben + player.getInventory().addItem(new ItemStack(mat, amount)); + player.updateInventory(); + sender.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung: " + response.errorMessage); } return true; - } - - // Fallback für falsche Argumente - if (args.length > 0 && args[0].equalsIgnoreCase("add")) { - sender.sendMessage(ChatColor.RED + "Falsche Benutzung!"); - sender.sendMessage(ChatColor.GRAY + "Mit Item in Hand: /shop add "); - sender.sendMessage(ChatColor.GRAY + "Mit Namen: /shop add "); - return true; } sender.sendMessage(ChatColor.RED + "Unbekannter Befehl! Benutze /shop [add|gui]"); @@ -139,13 +125,12 @@ public class ShopCommand implements CommandExecutor { } /** - * Zählt die Anzahl der Items eines bestimmten Materials im Inventar. - * Nutzt StorageContents, um auch Rüstung/Shulker-Slots zu prüfen. + * Zählt alle Items eines Materials im Spieler-Inventar (nur Storage-Slots). */ private int countItems(PlayerInventory inv, Material mat) { int count = 0; for (ItemStack item : inv.getStorageContents()) { - if (item != null && item.getType().equals(mat)) { + if (item != null && item.getType() == mat) { count += item.getAmount(); } } diff --git a/src/main/java/de/viper/survivalplus/commands/SurvivalPlusTabCompleter.java b/src/main/java/de/viper/survivalplus/commands/SurvivalPlusTabCompleter.java index e0b83dd..fd3ccb4 100644 --- a/src/main/java/de/viper/survivalplus/commands/SurvivalPlusTabCompleter.java +++ b/src/main/java/de/viper/survivalplus/commands/SurvivalPlusTabCompleter.java @@ -34,6 +34,8 @@ public class SurvivalPlusTabCompleter implements TabCompleter { private static final List CLAIM_SUBCOMMANDS = List.of("mark", "unclaim", "del", "delete", "trust", "untrust", "info", "kick", "ban", "unban"); private static final List SPLOCK_SUBCOMMANDS = List.of("lock", "unlock", "friendadd", "friendremove"); private static final List SHOP_SUBCOMMANDS = List.of("gui", "add"); + private static final List MONEY_SUBCOMMANDS = List.of("pay", "set", "add", "remove", "top"); + private static final List JOB_SUBCOMMANDS = List.of("info", "join", "leave", "myjobs"); private static final List NICK_SUBCOMMANDS = List.of("off", "remove", "reset"); private static final List GAMEMODE_SUBCOMMANDS = List.of("0", "1", "2", "3", "survival", "creative", "adventure", "spectator"); @@ -61,6 +63,14 @@ public class SurvivalPlusTabCompleter implements TabCompleter { return completeSplock(sender, args); case "shop": return completeShop(sender, args); + case "money": + case "bal": + case "balance": + case "geld": + return completeMoney(sender, args); + case "job": + case "jobs": + return completeJob(sender, args); case "nick": return filterPrefix(NICK_SUBCOMMANDS, args[0]); case "gm": @@ -159,6 +169,42 @@ public class SurvivalPlusTabCompleter implements TabCompleter { return Collections.emptyList(); } + private List completeMoney(CommandSender sender, String[] args) { + if (args.length == 1) { + // Spieler sehen nur pay + top, Admins alle Subcommands + if (sender.hasPermission("survivalplus.money.admin")) { + return filterPrefix(MONEY_SUBCOMMANDS, args[0]); + } + return filterPrefix(List.of("pay", "top"), args[0]); + } + if (args.length == 2) { + String sub = args[0].toLowerCase(Locale.ROOT); + // Spielername-Vorschlag für alle Subcommands die einen Spieler erwarten + if (sub.equals("pay") || sub.equals("set") || sub.equals("add") || sub.equals("remove")) { + return filterPrefix(getOnlinePlayerNames(sender), args[1]); + } + } + // args.length == 3: Betrag – keine sinnvollen Vorschläge möglich + return Collections.emptyList(); + } + + private List completeJob(CommandSender sender, String[] args) { + if (args.length == 1) { + // Arg 1: Subcommand-Vorschläge + return filterPrefix(JOB_SUBCOMMANDS, args[0]); + } + if (args.length == 2) { + String sub = args[0].toLowerCase(Locale.ROOT); + // Arg 2: Job-Namen aus der jobs.yml (über JobManager) + if (sub.equals("info") || sub.equals("join") || sub.equals("leave")) { + de.viper.survivalplus.jobs.JobManager jobManager = plugin.getJobManager(); + if (jobManager == null) return Collections.emptyList(); + return filterPrefix(new java.util.ArrayList<>(jobManager.getJobIds()), args[1]); + } + } + return Collections.emptyList(); + } + private List completeShop(CommandSender sender, String[] args) { if (args.length == 1) { return filterPrefix(SHOP_SUBCOMMANDS, args[0]); @@ -366,4 +412,4 @@ public class SurvivalPlusTabCompleter implements TabCompleter { Collections.sort(results); return results; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/economy/BalanceManager.java b/src/main/java/de/viper/survivalplus/economy/BalanceManager.java new file mode 100644 index 0000000..e5461d5 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/economy/BalanceManager.java @@ -0,0 +1,135 @@ +package de.viper.survivalplus.economy; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Verwaltet alle Spieler-Kontostände und speichert sie in balances.yml. + */ +public class BalanceManager { + + private final SurvivalPlus plugin; + private final File balancesFile; + private FileConfiguration balancesConfig; + + // In-Memory Cache für schnellen Zugriff + private final Map balances = new HashMap<>(); + + public BalanceManager(SurvivalPlus plugin) { + this.plugin = plugin; + this.balancesFile = new File(plugin.getDataFolder(), "balances.yml"); + load(); + } + + // ---- Laden & Speichern ---- + + private void load() { + if (!balancesFile.exists()) { + try { + balancesFile.createNewFile(); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Erstellen von balances.yml: " + e.getMessage()); + } + } + balancesConfig = YamlConfiguration.loadConfiguration(balancesFile); + + // Alle gespeicherten Kontostände in den Cache laden + for (String key : balancesConfig.getKeys(false)) { + try { + UUID uuid = UUID.fromString(key); + double balance = balancesConfig.getDouble(key); + balances.put(uuid, balance); + } catch (IllegalArgumentException ignored) { + // Ungültiger UUID-Eintrag, überspringen + } + } + plugin.getLogger().info("BalanceManager: " + balances.size() + " Konten geladen."); + } + + public void save() { + for (Map.Entry entry : balances.entrySet()) { + balancesConfig.set(entry.getKey().toString(), entry.getValue()); + } + try { + balancesConfig.save(balancesFile); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Speichern von balances.yml: " + e.getMessage()); + } + } + + // Einzelnen Eintrag sofort auf Disk persistieren (nach jeder Transaktion) + private void saveEntry(UUID uuid, double amount) { + balancesConfig.set(uuid.toString(), amount); + try { + balancesConfig.save(balancesFile); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Speichern von balances.yml: " + e.getMessage()); + } + } + + // ---- Konto-Operationen ---- + + public boolean hasAccount(UUID uuid) { + return balances.containsKey(uuid); + } + + /** + * Erstellt ein neues Konto mit dem Startsaldo aus der config.yml. + */ + public void createAccount(UUID uuid) { + if (!balances.containsKey(uuid)) { + double startBalance = plugin.getConfig().getDouble("economy.start-balance", 500.0); + balances.put(uuid, startBalance); + saveEntry(uuid, startBalance); + } + } + + public double getBalance(UUID uuid) { + return balances.getOrDefault(uuid, 0.0); + } + + /** + * Setzt den Kontostand direkt (z.B. für Admin-Befehle). + */ + public void setBalance(UUID uuid, double amount) { + balances.put(uuid, Math.max(0.0, amount)); + saveEntry(uuid, balances.get(uuid)); + } + + /** + * Zieht einen Betrag ab. Gibt false zurück wenn das Guthaben nicht ausreicht. + */ + public boolean withdraw(UUID uuid, double amount) { + double current = getBalance(uuid); + if (current < amount) return false; + double newBalance = current - amount; + balances.put(uuid, newBalance); + saveEntry(uuid, newBalance); + return true; + } + + /** + * Gibt eine Kopie aller Kontostände zurück (für /money top). + */ + public Map getAllBalances() { + return Collections.unmodifiableMap(balances); + } + + /** + * Schreibt einen Betrag gut. Gibt immer true zurück. + */ + public boolean deposit(UUID uuid, double amount) { + double newBalance = getBalance(uuid) + amount; + balances.put(uuid, newBalance); + saveEntry(uuid, newBalance); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/economy/SurvivalPlusEconomy.java b/src/main/java/de/viper/survivalplus/economy/SurvivalPlusEconomy.java new file mode 100644 index 0000000..ffc25f5 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/economy/SurvivalPlusEconomy.java @@ -0,0 +1,296 @@ +package de.viper.survivalplus.economy; + +import de.viper.survivalplus.SurvivalPlus; +import net.milkbowl.vault.economy.AbstractEconomy; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Eigener Vault-Economy-Provider für SurvivalPlus. + * Wird bei Vault registriert, sodass kein externes Economy-Plugin nötig ist. + */ +public class SurvivalPlusEconomy extends AbstractEconomy { + + private final SurvivalPlus plugin; + private final BalanceManager balanceManager; + + public SurvivalPlusEconomy(SurvivalPlus plugin, BalanceManager balanceManager) { + this.plugin = plugin; + this.balanceManager = balanceManager; + } + + // ---- Pflicht-Methoden von AbstractEconomy ---- + + @Override + public boolean isEnabled() { + return plugin.isEnabled(); + } + + @Override + public String getName() { + return "SurvivalPlusEconomy"; + } + + @Override + public String currencyNamePlural() { + return plugin.getConfig().getString("economy.currency-plural", "Coins"); + } + + @Override + public String currencyNameSingular() { + return plugin.getConfig().getString("economy.currency-singular", "Coin"); + } + + @Override + public boolean hasBankSupport() { + return false; // Kein Bank-System + } + + @Override + public int fractionalDigits() { + return 2; + } + + @Override + public String format(double amount) { + return String.format("%.2f %s", amount, currencyNamePlural()); + } + + // ---- Account-Verwaltung ---- + + @Override + public boolean hasAccount(String playerName) { + OfflinePlayer p = Bukkit.getOfflinePlayer(playerName); + return p != null && balanceManager.hasAccount(p.getUniqueId()); + } + + @Override + public boolean hasAccount(OfflinePlayer player) { + return balanceManager.hasAccount(player.getUniqueId()); + } + + @Override + public boolean hasAccount(String playerName, String worldName) { + return hasAccount(playerName); // Keine World-spezifischen Konten + } + + @Override + public boolean hasAccount(OfflinePlayer player, String worldName) { + return hasAccount(player); + } + + @Override + public boolean createPlayerAccount(String playerName) { + OfflinePlayer p = Bukkit.getOfflinePlayer(playerName); + if (p == null) return false; + balanceManager.createAccount(p.getUniqueId()); + return true; + } + + @Override + public boolean createPlayerAccount(OfflinePlayer player) { + balanceManager.createAccount(player.getUniqueId()); + return true; + } + + @Override + public boolean createPlayerAccount(String playerName, String worldName) { + return createPlayerAccount(playerName); + } + + @Override + public boolean createPlayerAccount(OfflinePlayer player, String worldName) { + return createPlayerAccount(player); + } + + // ---- Kontostand ---- + + @Override + public double getBalance(String playerName) { + OfflinePlayer p = Bukkit.getOfflinePlayer(playerName); + if (p == null) return 0; + ensureAccount(p.getUniqueId()); + return balanceManager.getBalance(p.getUniqueId()); + } + + @Override + public double getBalance(OfflinePlayer player) { + ensureAccount(player.getUniqueId()); + return balanceManager.getBalance(player.getUniqueId()); + } + + @Override + public double getBalance(String playerName, String world) { + return getBalance(playerName); + } + + @Override + public double getBalance(OfflinePlayer player, String world) { + return getBalance(player); + } + + @Override + public boolean has(String playerName, double amount) { + return getBalance(playerName) >= amount; + } + + @Override + public boolean has(OfflinePlayer player, double amount) { + return getBalance(player) >= amount; + } + + @Override + public boolean has(String playerName, String worldName, double amount) { + return has(playerName, amount); + } + + @Override + public boolean has(OfflinePlayer player, String worldName, double amount) { + return has(player, amount); + } + + // ---- Transaktionen ---- + + @Override + public EconomyResponse withdrawPlayer(String playerName, double amount) { + OfflinePlayer p = Bukkit.getOfflinePlayer(playerName); + if (p == null) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Spieler nicht gefunden."); + } + return withdrawPlayer(p, amount); + } + + @Override + public EconomyResponse withdrawPlayer(OfflinePlayer player, double amount) { + if (amount < 0) { + return new EconomyResponse(0, getBalance(player), + EconomyResponse.ResponseType.FAILURE, "Betrag darf nicht negativ sein."); + } + ensureAccount(player.getUniqueId()); + boolean success = balanceManager.withdraw(player.getUniqueId(), amount); + if (success) { + return new EconomyResponse(amount, getBalance(player), EconomyResponse.ResponseType.SUCCESS, null); + } else { + return new EconomyResponse(0, getBalance(player), + EconomyResponse.ResponseType.FAILURE, "Nicht genug Guthaben."); + } + } + + @Override + public EconomyResponse withdrawPlayer(String playerName, String worldName, double amount) { + return withdrawPlayer(playerName, amount); + } + + @Override + public EconomyResponse withdrawPlayer(OfflinePlayer player, String worldName, double amount) { + return withdrawPlayer(player, amount); + } + + @Override + public EconomyResponse depositPlayer(String playerName, double amount) { + OfflinePlayer p = Bukkit.getOfflinePlayer(playerName); + if (p == null) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Spieler nicht gefunden."); + } + return depositPlayer(p, amount); + } + + @Override + public EconomyResponse depositPlayer(OfflinePlayer player, double amount) { + if (amount < 0) { + return new EconomyResponse(0, getBalance(player), + EconomyResponse.ResponseType.FAILURE, "Betrag darf nicht negativ sein."); + } + ensureAccount(player.getUniqueId()); + balanceManager.deposit(player.getUniqueId(), amount); + return new EconomyResponse(amount, getBalance(player), EconomyResponse.ResponseType.SUCCESS, null); + } + + @Override + public EconomyResponse depositPlayer(String playerName, String worldName, double amount) { + return depositPlayer(playerName, amount); + } + + @Override + public EconomyResponse depositPlayer(OfflinePlayer player, String worldName, double amount) { + return depositPlayer(player, amount); + } + + // ---- Bank-Methoden (nicht unterstützt) ---- + + @Override + public EconomyResponse createBank(String name, String player) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse createBank(String name, OfflinePlayer player) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse deleteBank(String name) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse bankBalance(String name) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse bankHas(String name, double amount) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse bankWithdraw(String name, double amount) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse bankDeposit(String name, double amount) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse isBankOwner(String name, String playerName) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse isBankOwner(String name, OfflinePlayer player) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse isBankMember(String name, String playerName) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public EconomyResponse isBankMember(String name, OfflinePlayer player) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Banken nicht unterstützt."); + } + + @Override + public List getBanks() { + return new ArrayList<>(); + } + + // ---- Hilfsmethode ---- + + /** + * Erstellt automatisch ein Konto falls noch keines existiert. + */ + private void ensureAccount(UUID uuid) { + if (!balanceManager.hasAccount(uuid)) { + balanceManager.createAccount(uuid); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/gui/ShopGui.java b/src/main/java/de/viper/survivalplus/gui/ShopGui.java index 08a1eb4..a8f2855 100644 --- a/src/main/java/de/viper/survivalplus/gui/ShopGui.java +++ b/src/main/java/de/viper/survivalplus/gui/ShopGui.java @@ -2,6 +2,8 @@ package de.viper.survivalplus.gui; import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.SurvivalPlus; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -22,11 +24,14 @@ public class ShopGui implements Listener { private final Player player; private Inventory inv; private final SurvivalPlus plugin; + // Economy wird zentral aus dem Plugin geholt – keine eigene Initialisierung nötig + private final Economy economy; public ShopGui(SurvivalPlus plugin, Player player, ShopManager shopManager) { this.plugin = plugin; this.player = player; this.shopManager = shopManager; + this.economy = plugin.getEconomy(); // <-- zentral aus SurvivalPlus createInventory(); } @@ -38,12 +43,9 @@ public class ShopGui implements Listener { return; } - // Alle Items aus shop.yml einlesen for (String itemKey : shopManager.getShopConfig().getConfigurationSection("items").getKeys(false)) { Material mat = getMaterialFromKey(itemKey); - if (mat == null) { - continue; // Unbekannte Materialien einfach überspringen - } + if (mat == null) continue; addShopItem(itemKey, mat, 64); addShopItem(itemKey, mat, 16); @@ -72,8 +74,8 @@ public class ShopGui implements Listener { meta.setDisplayName(ChatColor.GREEN.toString() + amount + "x " + material.name()); meta.setLore(Arrays.asList( ChatColor.GRAY + "Lagerbestand: " + ChatColor.YELLOW + currentStock, - ChatColor.YELLOW + "Preis pro Stück: " + pricePerUnit, - ChatColor.YELLOW + "Gesamtpreis: " + totalPrice, + ChatColor.YELLOW + "Preis pro Stück: " + String.format("%.2f", pricePerUnit), + ChatColor.YELLOW + "Gesamtpreis: " + String.format("%.2f", totalPrice), "", ChatColor.GREEN + "Linksklick zum Kaufen", ChatColor.RED + "Rechtsklick zum Verkaufen" @@ -100,58 +102,85 @@ public class ShopGui implements Listener { ItemStack clicked = e.getCurrentItem(); if (clicked == null || clicked.getType() == Material.AIR) return; + // Economy-Check: Wenn Vault nicht verfügbar ist, Shop sperren + if (economy == null) { + player.sendMessage(ChatColor.RED + "Das Economy-System ist aktuell nicht verfügbar."); + player.closeInventory(); + return; + } + int amount = clicked.getAmount(); Material mat = clicked.getType(); String itemKey = mat.name().toLowerCase(); if (e.isLeftClick()) { // --- KAUFEN --- + double totalPrice = shopManager.getCurrentPrice(itemKey) * amount; + + // 1. Prüfen ob Spieler genug Geld hat + if (economy.getBalance(player) < totalPrice) { + player.sendMessage(ChatColor.RED + "Du hast nicht genug Geld! Benötigt: " + + String.format("%.2f", totalPrice) + " Coins."); + return; + } + + // 2. Shop-Logik (Preis erhöhen, ggf. Bestand prüfen) if (!shopManager.buyItem(itemKey, amount)) { player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop."); return; } - double totalPrice = shopManager.getCurrentPrice(itemKey) * amount; - - // TODO: Economy Abzug hier einfügen! - // Beispiel: if (!economy.withdrawPlayer(player, totalPrice)) { ... } + // 3. Geld abziehen + EconomyResponse response = economy.withdrawPlayer(player, totalPrice); + if (!response.transactionSuccess()) { + player.sendMessage(ChatColor.RED + "Fehler bei der Zahlung: " + response.errorMessage); + return; + } + // 4. Items geben player.getInventory().addItem(new ItemStack(mat, amount)); - player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", totalPrice) + " gekauft."); + player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + + " für " + String.format("%.2f", totalPrice) + " Coins gekauft."); } else if (e.isRightClick()) { // --- VERKAUFEN --- - - // Prüfen ob Spieler genug Items hat + + // 1. Prüfen ob Spieler genug Items hat if (!player.getInventory().containsAtLeast(new ItemStack(mat), amount)) { player.sendMessage(ChatColor.RED + "Du hast nicht genug Items zum Verkaufen."); return; } - // Verkaufslogik - shopManager.sellItem(itemKey, amount); - - double sellPrice = shopManager.getCurrentPrice(itemKey) * amount; // Neuer Preis nach dem Verkauf wird genutzt - // Hinweis: In echten Systemen bekommt man oft den Preis *vor* dem Preisverfall, hier nehmen wir den neuen für Einfachheit oder den gespeicherten: - // Besser wäre: double oldPrice = price * amount; ... player.giveMoney(oldPrice); - - // TODO: Economy Gutschrift hier einfügen! - + // 2. Preis VOR dem Preisverfall festhalten + double sellPrice = shopManager.getCurrentPrice(itemKey) * amount; + + // 3. Items entfernen player.getInventory().removeItem(new ItemStack(mat, amount)); - player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", sellPrice) + " verkauft."); + + // 4. Preis im Shop anpassen (Lager erhöhen, Preis senken) + shopManager.sellItem(itemKey, amount); + + // 5. Geld gutschreiben + EconomyResponse response = economy.depositPlayer(player, sellPrice); + if (!response.transactionSuccess()) { + // Rollback: Items zurückgeben wenn Zahlung fehlschlägt + player.getInventory().addItem(new ItemStack(mat, amount)); + player.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung: " + response.errorMessage); + return; + } + + player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + + " für " + String.format("%.2f", sellPrice) + " Coins verkauft."); } - // Inventory schließen, um Preise neu zu laden und Bugs zu vermeiden + player.updateInventory(); player.closeInventory(); - // Optional: direkt neu öffnen mit createInventory() wenn es flüssig sein soll - // createInventory(); } @EventHandler public void onInventoryClose(InventoryCloseEvent e) { if (e.getView().getTitle().equals("Shop") && e.getPlayer().equals(player)) { - // Listener kann entladen werden, wenn nicht mehr gebraucht - // HandlerList.unregisterAll(this); // Vorsicht: falls Singleton, sonst fliegt allen der GUI weg + // Listener-Cleanup hier möglich, wenn ShopGui nicht als Singleton genutzt wird } } diff --git a/src/main/java/de/viper/survivalplus/jobs/JobGui.java b/src/main/java/de/viper/survivalplus/jobs/JobGui.java new file mode 100644 index 0000000..72112a2 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/jobs/JobGui.java @@ -0,0 +1,394 @@ +package de.viper.survivalplus.jobs; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.*; + +/** + * Dreistufiges Job-GUI: + * Screen 1 – Übersicht aller Jobs + * Screen 2 – Job-Detail (Info, Level, XP-Balken, Join/Leave-Button) + * Screen 3 – Bestätigung (Ja / Nein) + * + * Öffnen: new JobGui(plugin, player, jobManager); + * Der Listener registriert/deregistriert sich selbst automatisch. + */ +public class JobGui implements Listener { + + // ---- GUI-Titel (werden zur Erkennung im ClickEvent benutzt) ---- + private static final String TITLE_OVERVIEW = ChatColor.DARK_AQUA + "✦ " + ChatColor.WHITE + "Jobs" + ChatColor.DARK_AQUA + " ✦"; + private static final String TITLE_DETAIL = ChatColor.DARK_AQUA + "✦ " + ChatColor.WHITE + "Job-Info" + ChatColor.DARK_AQUA + " ✦"; + private static final String TITLE_CONFIRM_JOIN = ChatColor.DARK_GREEN + "Job annehmen?"; + private static final String TITLE_CONFIRM_LEAVE = ChatColor.DARK_RED + "Job kündigen?"; + + private final SurvivalPlus plugin; + private final JobManager jobManager; + private final Player player; + + /** Der aktuell angezeigte / ausgewählte Job. */ + private String currentJobId = null; + + /** + * FIX Bug 1: Flag, das gesetzt wird wenn wir absichtlich zwischen + * Screens wechseln. So wird der Listener beim Screen-Wechsel NICHT + * abgemeldet – nur wenn der Spieler die GUI wirklich schließt. + */ + private boolean transitioning = false; + + public JobGui(SurvivalPlus plugin, Player player, JobManager jobManager) { + this.plugin = plugin; + this.player = player; + this.jobManager = jobManager; + Bukkit.getPluginManager().registerEvents(this, plugin); + openOverview(); + } + + // ========================================================================= + // Screen 1 – Übersicht + // ========================================================================= + + private void openOverview() { + transitioning = true; + Inventory inv = Bukkit.createInventory(null, 27, TITLE_OVERVIEW); + fillBorder(inv); + + List jobIds = new ArrayList<>(jobManager.getJobIds()); + int[] slots = centeredSlots(jobIds.size(), 10, 16); // Reihe 2, Slots 10–16 + + for (int i = 0; i < jobIds.size() && i < slots.length; i++) { + inv.setItem(slots[i], buildOverviewItem(jobIds.get(i))); + } + + // Statuszeile unten (Slot 22) + int active = jobManager.getPlayerJobs(player.getUniqueId()).size(); + inv.setItem(22, buildSimpleItem( + Material.PAPER, + ChatColor.GRAY + "Aktive Jobs: " + ChatColor.WHITE + active + ChatColor.GRAY + " / " + jobManager.getMaxJobs(), + ChatColor.DARK_GRAY + "Klicke einen Job für Details." + )); + + player.openInventory(inv); + transitioning = false; + } + + private ItemStack buildOverviewItem(String jobId) { + boolean hasJob = jobManager.hasJob(player.getUniqueId(), jobId); + int level = jobManager.getLevel(player.getUniqueId(), jobId); + + ItemStack item = new ItemStack(iconFor(jobId)); + ItemMeta meta = item.getItemMeta(); + + meta.setDisplayName( + (hasJob ? ChatColor.GREEN : ChatColor.YELLOW) + "" + ChatColor.BOLD + + jobManager.getJobDisplayName(jobId) + ); + + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + jobManager.getJobDescription(jobId)); + lore.add(""); + if (hasJob) { + lore.add(ChatColor.GREEN + "✔ Aktiv " + ChatColor.GRAY + "│ " + ChatColor.WHITE + "Level " + ChatColor.AQUA + level); + } else { + lore.add(ChatColor.GRAY + "Noch nicht aktiv"); + } + lore.add(""); + lore.add(ChatColor.DARK_GRAY + "» Klicken für Details"); + + meta.setLore(lore); + item.setItemMeta(meta); + return item; + } + + // ========================================================================= + // Screen 2 – Detail + // ========================================================================= + + private void openDetail(String jobId) { + transitioning = true; + currentJobId = jobId; + Inventory inv = Bukkit.createInventory(null, 27, TITLE_DETAIL); + fillBorder(inv); + + boolean hasJob = jobManager.hasJob(player.getUniqueId(), jobId); + int level = jobManager.getLevel(player.getUniqueId(), jobId); + int xp = jobManager.getXp(player.getUniqueId(), jobId); + int xpReq = jobManager.getXpRequired(jobId, level); + + // ---- Mitte (Slot 13): Job-Info ---- + ItemStack info = new ItemStack(iconFor(jobId)); + ItemMeta im = info.getItemMeta(); + im.setDisplayName(ChatColor.YELLOW + "" + ChatColor.BOLD + jobManager.getJobDisplayName(jobId)); + + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + jobManager.getJobDescription(jobId)); + lore.add(""); + if (hasJob) { + lore.add(ChatColor.WHITE + "Level: " + ChatColor.AQUA + level + + ChatColor.GRAY + " / " + jobManager.getMaxLevel()); + if (level < jobManager.getMaxLevel()) { + lore.add(ChatColor.WHITE + "XP: " + ChatColor.AQUA + xp + + ChatColor.GRAY + " / " + xpReq); + lore.add(xpBar(xp, xpReq)); + } else { + lore.add(ChatColor.GOLD + "⭐ Maximales Level erreicht!"); + } + } else { + lore.add(ChatColor.GRAY + "Du hast diesen Job noch nicht."); + } + im.setLore(lore); + info.setItemMeta(im); + inv.setItem(13, info); + + // ---- Zurück (Slot 18) ---- + inv.setItem(18, buildSimpleItem(Material.ARROW, + ChatColor.GRAY + "◀ Zurück", null)); + + // ---- Aktions-Button (Slot 16) ---- + if (hasJob) { + inv.setItem(16, buildActionItem( + Material.BARRIER, + ChatColor.RED + "" + ChatColor.BOLD + "✖ Job kündigen", + Arrays.asList( + ChatColor.GRAY + "Verlasse den Job", + ChatColor.GRAY + jobManager.getJobDisplayName(jobId) + ".", + "", + ChatColor.DARK_GRAY + "Level & XP bleiben gespeichert." + ) + )); + } else { + boolean canJoin = jobManager.getPlayerJobs(player.getUniqueId()).size() < jobManager.getMaxJobs(); + if (canJoin) { + inv.setItem(16, buildActionItem( + Material.LIME_DYE, + ChatColor.GREEN + "" + ChatColor.BOLD + "✔ Job annehmen", + Arrays.asList( + ChatColor.GRAY + "Nimm den Job", + ChatColor.GRAY + jobManager.getJobDisplayName(jobId) + " an." + ) + )); + } else { + inv.setItem(16, buildActionItem( + Material.GRAY_DYE, + ChatColor.DARK_GRAY + "" + ChatColor.BOLD + "✖ Keine freien Slots", + Arrays.asList( + ChatColor.GRAY + "Du hast bereits " + jobManager.getMaxJobs() + " Jobs.", + ChatColor.GRAY + "Kündige zuerst einen Job." + ) + )); + } + } + + player.openInventory(inv); + transitioning = false; + } + + // ========================================================================= + // Screen 3 – Bestätigung + // ========================================================================= + + private void openConfirm(String jobId, boolean joining) { + transitioning = true; + String title = joining ? TITLE_CONFIRM_JOIN : TITLE_CONFIRM_LEAVE; + Inventory inv = Bukkit.createInventory(null, 27, title); + fillBorder(inv); + + // Info-Item (Slot 13) + inv.setItem(13, buildSimpleItem( + iconFor(jobId), + (joining ? ChatColor.GREEN : ChatColor.RED) + jobManager.getJobDisplayName(jobId), + joining + ? ChatColor.GRAY + "Dem Job wirklich beitreten?" + : ChatColor.GRAY + "Job wirklich kündigen?" + )); + + // Ja (Slot 11) + inv.setItem(11, buildActionItem( + Material.LIME_DYE, + ChatColor.GREEN + "" + ChatColor.BOLD + "✔ Ja", + Collections.singletonList(ChatColor.GRAY + "Bestätigen") + )); + + // Nein (Slot 15) + inv.setItem(15, buildActionItem( + Material.RED_DYE, + ChatColor.RED + "" + ChatColor.BOLD + "✖ Nein", + Collections.singletonList(ChatColor.GRAY + "Abbrechen") + )); + + player.openInventory(inv); + transitioning = false; + } + + // ========================================================================= + // Click-Handler + // ========================================================================= + + @EventHandler + public void onInventoryClick(InventoryClickEvent e) { + if (!e.getWhoClicked().equals(player)) return; + + String title = e.getView().getTitle(); + if (!isOurGui(title)) return; + + // FIX Bug 1: Immer canceln – egal welcher Slot angeklickt wird. + // Verhindert das Entnehmen von Items aus der GUI (auch Shift-Click + // in den eigenen Inventarbereich während die GUI offen ist). + e.setCancelled(true); + + ItemStack clicked = e.getCurrentItem(); + if (clicked == null || clicked.getType() == Material.AIR) return; + if (clicked.getType() == Material.BLACK_STAINED_GLASS_PANE) return; + + String name = clicked.hasItemMeta() && clicked.getItemMeta().hasDisplayName() + ? ChatColor.stripColor(clicked.getItemMeta().getDisplayName()) : ""; + + // ---- Screen 1 ---- + if (title.equals(TITLE_OVERVIEW)) { + for (String jobId : jobManager.getJobIds()) { + if (name.contains(ChatColor.stripColor(jobManager.getJobDisplayName(jobId)))) { + openDetail(jobId); + return; + } + } + } + + // ---- Screen 2 ---- + if (title.equals(TITLE_DETAIL)) { + if (name.equals("◀ Zurück")) { + openOverview(); + return; + } + if (currentJobId == null) return; + if (name.equals("✔ Job annehmen")) { + openConfirm(currentJobId, true); + } else if (name.equals("✖ Job kündigen")) { + openConfirm(currentJobId, false); + } + } + + // ---- Screen 3 ---- + if (title.equals(TITLE_CONFIRM_JOIN) || title.equals(TITLE_CONFIRM_LEAVE)) { + boolean joining = title.equals(TITLE_CONFIRM_JOIN); + if (name.equals("✔ Ja")) { + if (joining) { + if (jobManager.joinJob(player.getUniqueId(), currentJobId)) { + player.sendMessage(ChatColor.GREEN + "Du hast den Job " + + ChatColor.YELLOW + jobManager.getJobDisplayName(currentJobId) + + ChatColor.GREEN + " angenommen!"); + } else { + player.sendMessage(ChatColor.RED + "Beitreten nicht möglich (Limit erreicht oder Job aktiv)."); + } + } else { + if (jobManager.leaveJob(player.getUniqueId(), currentJobId)) { + player.sendMessage(ChatColor.YELLOW + "Du hast den Job " + + ChatColor.WHITE + jobManager.getJobDisplayName(currentJobId) + + ChatColor.YELLOW + " gekündigt. Level bleibt gespeichert."); + } + } + // Nach Aktion direkt Übersicht aktualisiert anzeigen. + // transitioning wird in openOverview() gesetzt – kein Extra-Flag nötig. + Bukkit.getScheduler().runTask(plugin, this::openOverview); + + } else if (name.equals("✖ Nein")) { + openDetail(currentJobId); + } + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent e) { + if (!e.getPlayer().equals(player)) return; + // FIX Bug 1: Listener NUR abmelden wenn der Spieler die GUI + // wirklich schließt – nicht bei internem Screen-Wechsel. + if (isOurGui(e.getView().getTitle()) && !transitioning) { + HandlerList.unregisterAll(this); + } + } + + // ========================================================================= + // Builder-Hilfsmethoden + // ========================================================================= + + private void fillBorder(Inventory inv) { + ItemStack pane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE); + ItemMeta m = pane.getItemMeta(); + m.setDisplayName(" "); + pane.setItemMeta(m); + int size = inv.getSize(); + for (int i = 0; i < size; i++) { + if (i < 9 || i >= size - 9 || i % 9 == 0 || i % 9 == 8) { + inv.setItem(i, pane); + } + } + } + + private ItemStack buildSimpleItem(Material mat, String name, String loreLine) { + ItemStack item = new ItemStack(mat); + ItemMeta m = item.getItemMeta(); + m.setDisplayName(name); + if (loreLine != null) m.setLore(Collections.singletonList(loreLine)); + item.setItemMeta(m); + return item; + } + + private ItemStack buildActionItem(Material mat, String name, List lore) { + ItemStack item = new ItemStack(mat); + ItemMeta m = item.getItemMeta(); + m.setDisplayName(name); + m.setLore(lore); + item.setItemMeta(m); + return item; + } + + /** Passendes Icon-Material pro Job. */ + private Material iconFor(String jobId) { + switch (jobId.toLowerCase()) { + case "miner": return Material.DIAMOND_PICKAXE; + case "farmer": return Material.WHEAT; + case "hunter": return Material.IRON_SWORD; + case "woodcutter": return Material.IRON_AXE; + case "fischer": return Material.FISHING_ROD; + case "builder": return Material.BRICKS; + default: return Material.BOOK; + } + } + + /** + * Berechnet zentrierte Slot-Positionen für n Items zwischen minSlot und maxSlot. + */ + private int[] centeredSlots(int count, int minSlot, int maxSlot) { + int available = maxSlot - minSlot + 1; + int start = minSlot + (available - Math.min(count, available)) / 2; + int[] slots = new int[Math.min(count, available)]; + for (int i = 0; i < slots.length; i++) slots[i] = start + i; + return slots; + } + + /** XP-Fortschrittsbalken als farbigen String. */ + private String xpBar(int current, int max) { + int len = 16; + int filled = max == 0 ? 0 : Math.min((int) ((double) current / max * len), len); + return ChatColor.GREEN + "█".repeat(filled) + + ChatColor.DARK_GRAY + "█".repeat(len - filled) + + ChatColor.GRAY + " " + current + "/" + max; + } + + private boolean isOurGui(String title) { + return title.equals(TITLE_OVERVIEW) + || title.equals(TITLE_DETAIL) + || title.equals(TITLE_CONFIRM_JOIN) + || title.equals(TITLE_CONFIRM_LEAVE); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/jobs/JobListener.java b/src/main/java/de/viper/survivalplus/jobs/JobListener.java new file mode 100644 index 0000000..93860a7 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/jobs/JobListener.java @@ -0,0 +1,170 @@ +package de.viper.survivalplus.jobs; + +import de.viper.survivalplus.SurvivalPlus; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.ChatColor; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerFishEvent; + +import java.util.UUID; + +public class JobListener implements Listener { + + private final SurvivalPlus plugin; + private final JobManager jobManager; + + public JobListener(SurvivalPlus plugin, JobManager jobManager) { + this.plugin = plugin; + this.jobManager = jobManager; + } + + // ========================================================================= + // MINER + WOODCUTTER + FARMER (alle über BlockBreak) + // ========================================================================= + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + Block block = event.getBlock(); + String key = block.getType().name(); + + if (isCrop(block)) { + if (!isFullyGrown(block)) return; + reward(player, "farmer", key); + return; + } + + // Pumpkin & Melon sind keine Ageable-Crops + if (key.equals("PUMPKIN") || key.equals("MELON")) { + reward(player, "farmer", key); + return; + } + + reward(player, "miner", key); + reward(player, "woodcutter", key); + } + + // ========================================================================= + // BUILDER + // ========================================================================= + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + reward(event.getPlayer(), "builder", event.getBlock().getType().name()); + } + + // ========================================================================= + // HUNTER + // ========================================================================= + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityDeath(EntityDeathEvent event) { + org.bukkit.entity.LivingEntity entity = event.getEntity(); + if (!(entity.getKiller() instanceof Player)) return; + reward((Player) entity.getKiller(), "hunter", entity.getType().name()); + } + + // ========================================================================= + // FISCHER + // ========================================================================= + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerFish(PlayerFishEvent event) { + if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) return; + if (!(event.getCaught() instanceof org.bukkit.entity.Item)) return; + + org.bukkit.entity.Item caughtItem = (org.bukkit.entity.Item) event.getCaught(); + String itemKey = caughtItem.getItemStack().getType().name(); + reward(event.getPlayer(), "fischer", itemKey); + } + + // ========================================================================= + // Kern-Logik + // ========================================================================= + + private void reward(Player player, String jobId, String actionKey) { + UUID uuid = player.getUniqueId(); + + if (!jobManager.hasJob(uuid, jobId)) return; + if (!jobManager.hasAction(jobId, actionKey)) return; // Block einfach nicht konfiguriert → still ignorieren + + int level = jobManager.getLevel(uuid, jobId); + + // --------------------------------------------------------------- + // GELD – unabhängig von XP; Fehler hier bricht XP NICHT ab + // --------------------------------------------------------------- + double money = jobManager.getMoneyForAction(jobId, actionKey, level); + if (money > 0) { + Economy economy = plugin.getEconomy(); + if (economy == null) { + plugin.getLogger().warning( + "[Jobs] Economy = null! Vault installiert? Economy-Plugin aktiv?"); + } else { + EconomyResponse response = economy.depositPlayer(player, money); + if (response != null && response.transactionSuccess()) { + player.sendMessage( + ChatColor.GREEN + "+" + String.format("%.2f", money) + " Coins " + + ChatColor.GRAY + "(" + jobManager.getJobDisplayName(jobId) + + " Lv." + level + ")" + ); + } else { + String reason = (response != null) ? response.errorMessage : "response=null"; + plugin.getLogger().warning( + "[Jobs] depositPlayer fehlgeschlagen für " + player.getName() + + " | +" + money + " Coins | Grund: " + reason); + } + } + } + + // --------------------------------------------------------------- + // XP – läuft IMMER, egal ob Economy funktioniert oder nicht + // --------------------------------------------------------------- + int xp = jobManager.getXpForAction(jobId, actionKey); + if (xp > 0) { + boolean leveledUp = jobManager.addXp(uuid, jobId, xp); + if (leveledUp) { + int newLevel = jobManager.getLevel(uuid, jobId); + player.sendMessage( + ChatColor.GOLD + "⭐ " + ChatColor.YELLOW + + jobManager.getJobDisplayName(jobId) + + ChatColor.GOLD + " Level Up! → " + + ChatColor.WHITE + "Level " + newLevel + ); + } + } + } + + // ========================================================================= + // Hilfsmethoden + // ========================================================================= + + private boolean isCrop(Block block) { + switch (block.getType()) { + case WHEAT: + case POTATOES: + case CARROTS: + case BEETROOTS: + case NETHER_WART: + case COCOA: + case SWEET_BERRY_BUSH: + return true; + default: + return false; + } + } + + private boolean isFullyGrown(Block block) { + if (!(block.getBlockData() instanceof Ageable)) return true; + Ageable ageable = (Ageable) block.getBlockData(); + return ageable.getAge() == ageable.getMaximumAge(); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/jobs/JobManager.java b/src/main/java/de/viper/survivalplus/jobs/JobManager.java new file mode 100644 index 0000000..2d4aae8 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/jobs/JobManager.java @@ -0,0 +1,212 @@ +package de.viper.survivalplus.jobs; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * Verwaltet alle Job-Daten: + * jobs.yml → Job-Definitionen (Aktionen, Bezahlung, XP) + * playerjobs.yml → Spieler-Fortschritt (aktive Jobs, Level, XP) + */ +public class JobManager { + + private final SurvivalPlus plugin; + + private final File jobsFile; + private FileConfiguration jobsConfig; + + private final File playerJobsFile; + private FileConfiguration playerJobsConfig; + + private int maxJobs; + private int maxLevel; + private double levelMultiplier; + + public JobManager(SurvivalPlus plugin) { + this.plugin = plugin; + this.jobsFile = new File(plugin.getDataFolder(), "jobs.yml"); + this.playerJobsFile = new File(plugin.getDataFolder(), "playerjobs.yml"); + loadJobsConfig(); + loadPlayerJobsConfig(); + } + + // ------------------------------------------------------------------------- + // Config-Verwaltung + // ------------------------------------------------------------------------- + + private void loadJobsConfig() { + if (!jobsFile.exists()) { + plugin.saveResource("jobs.yml", false); + } + jobsConfig = YamlConfiguration.loadConfiguration(jobsFile); + maxJobs = jobsConfig.getInt("settings.max-jobs", 2); + maxLevel = jobsConfig.getInt("settings.max-level", 10); + levelMultiplier = jobsConfig.getDouble("settings.level-multiplier", 0.1); + plugin.getLogger().info("JobManager: " + getJobIds().size() + " Jobs geladen."); + } + + private void loadPlayerJobsConfig() { + if (!playerJobsFile.exists()) { + try { + playerJobsFile.createNewFile(); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Erstellen von playerjobs.yml: " + e.getMessage()); + } + } + playerJobsConfig = YamlConfiguration.loadConfiguration(playerJobsFile); + } + + private void savePlayerJobs() { + try { + playerJobsConfig.save(playerJobsFile); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Speichern von playerjobs.yml: " + e.getMessage()); + } + } + + /** Lädt beide Configs neu (für /sp reload). */ + public void reload() { + loadJobsConfig(); + loadPlayerJobsConfig(); + } + + // ------------------------------------------------------------------------- + // Job-Definitionen (aus jobs.yml) + // ------------------------------------------------------------------------- + + /** Alle konfigurierten Job-IDs in der Reihenfolge der YAML-Datei. */ + public Set getJobIds() { + if (jobsConfig.getConfigurationSection("jobs") == null) return new LinkedHashSet<>(); + return jobsConfig.getConfigurationSection("jobs").getKeys(false); + } + + public boolean jobExists(String jobId) { + return jobsConfig.contains("jobs." + jobId); + } + + /** Anzeigename inkl. Emoji, z.B. "⛏ Miner". */ + public String getJobDisplayName(String jobId) { + return jobsConfig.getString("jobs." + jobId + ".display", jobId); + } + + public String getJobDescription(String jobId) { + return jobsConfig.getString("jobs." + jobId + ".description", ""); + } + + public int getMaxJobs() { return maxJobs; } + public int getMaxLevel() { return maxLevel; } + + /** + * Bezahlung für eine Aktion, skaliert mit dem Level des Spielers. + * Formel: baseMoney * (1 + level * levelMultiplier) + */ + public double getMoneyForAction(String jobId, String actionKey, int level) { + double base = jobsConfig.getDouble("jobs." + jobId + ".actions." + actionKey + ".money", 0); + if (base <= 0) return 0; + return base * (1.0 + level * levelMultiplier); + } + + public int getXpForAction(String jobId, String actionKey) { + return jobsConfig.getInt("jobs." + jobId + ".actions." + actionKey + ".xp", 0); + } + + public boolean hasAction(String jobId, String actionKey) { + return jobsConfig.contains("jobs." + jobId + ".actions." + actionKey); + } + + /** XP-Anforderung für das nächste Level: xpBase * currentLevel. */ + public int getXpRequired(String jobId, int level) { + int base = jobsConfig.getInt("jobs." + jobId + ".xp-base", 100); + return base * level; + } + + // ------------------------------------------------------------------------- + // Spieler-Daten (aus playerjobs.yml) + // ------------------------------------------------------------------------- + + private String p(UUID uuid) { return uuid.toString(); } + + /** Aktive Job-IDs des Spielers. */ + public List getPlayerJobs(UUID uuid) { + return playerJobsConfig.getStringList(p(uuid) + ".jobs"); + } + + public boolean hasJob(UUID uuid, String jobId) { + return getPlayerJobs(uuid).contains(jobId.toLowerCase()); + } + + public int getLevel(UUID uuid, String jobId) { + return playerJobsConfig.getInt(p(uuid) + ".data." + jobId + ".level", 1); + } + + public int getXp(UUID uuid, String jobId) { + return playerJobsConfig.getInt(p(uuid) + ".data." + jobId + ".xp", 0); + } + + /** + * Spieler tritt einem Job bei. + * @return false wenn bereits aktiv oder Job-Limit erreicht. + */ + public boolean joinJob(UUID uuid, String jobId) { + jobId = jobId.toLowerCase(); + if (!jobExists(jobId)) return false; + List jobs = new ArrayList<>(getPlayerJobs(uuid)); + if (jobs.contains(jobId)) return false; + if (jobs.size() >= maxJobs) return false; + + jobs.add(jobId); + playerJobsConfig.set(p(uuid) + ".jobs", jobs); + + if (!playerJobsConfig.contains(p(uuid) + ".data." + jobId + ".level")) { + playerJobsConfig.set(p(uuid) + ".data." + jobId + ".level", 1); + playerJobsConfig.set(p(uuid) + ".data." + jobId + ".xp", 0); + } + savePlayerJobs(); + return true; + } + + /** + * Spieler verlässt einen Job. Level & XP bleiben erhalten. + * @return false wenn er den Job nicht hatte. + */ + public boolean leaveJob(UUID uuid, String jobId) { + jobId = jobId.toLowerCase(); + List jobs = new ArrayList<>(getPlayerJobs(uuid)); + if (!jobs.remove(jobId)) return false; + playerJobsConfig.set(p(uuid) + ".jobs", jobs); + savePlayerJobs(); + return true; + } + + /** + * XP hinzufügen, Level-Aufstiege werden automatisch verarbeitet. + * @return true wenn ein Level-Aufstieg stattfand. + */ + public boolean addXp(UUID uuid, String jobId, int xpGained) { + int level = getLevel(uuid, jobId); + if (level >= maxLevel) return false; + + int xp = getXp(uuid, jobId) + xpGained; + int required = getXpRequired(jobId, level); + boolean leveledUp = false; + + while (xp >= required && level < maxLevel) { + xp -= required; + level++; + leveledUp = true; + required = getXpRequired(jobId, level); + } + + playerJobsConfig.set(p(uuid) + ".data." + jobId + ".xp", xp); + playerJobsConfig.set(p(uuid) + ".data." + jobId + ".level", level); + savePlayerJobs(); + return leveledUp; + } + + public FileConfiguration getJobsConfig() { return jobsConfig; } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/SignShopListener.java b/src/main/java/de/viper/survivalplus/listeners/SignShopListener.java index 26b2489..2ddcc80 100644 --- a/src/main/java/de/viper/survivalplus/listeners/SignShopListener.java +++ b/src/main/java/de/viper/survivalplus/listeners/SignShopListener.java @@ -14,82 +14,81 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.RegisteredServiceProvider; public class SignShopListener implements Listener { + private final SurvivalPlus plugin; - private Economy economy; + // Economy wird zentral aus dem Plugin geholt – keine eigene Initialisierung nötig public SignShopListener(SurvivalPlus plugin) { this.plugin = plugin; - try { - if (plugin.getServer().getPluginManager().isPluginEnabled("Vault")) { - RegisteredServiceProvider rsp = plugin.getServer().getServicesManager().getRegistration(Economy.class); - if (rsp != null) { - this.economy = rsp.getProvider(); - plugin.getLogger().info("Vault Economy für Sign Shops gefunden."); - } else { - plugin.getLogger().warning("Vault Economy Service nicht gefunden!"); - } - } else { - plugin.getLogger().warning("Vault Plugin nicht installiert! Sign Shops funktionieren nicht."); - } - } catch (Exception e) { - plugin.getLogger().warning("Fehler beim Laden der Vault API."); - } + // Kein eigener Vault-Setup hier! Economy kommt über plugin.getEconomy() } - // --- Schild erstellung --- + /** + * Hilfsmethode: Economy aus Plugin holen mit Null-Check und Fehlermeldung. + * Gibt null zurück wenn Economy nicht verfügbar ist. + */ + private Economy getEconomy(Player player) { + Economy economy = plugin.getEconomy(); + if (economy == null) { + player.sendMessage(ChatColor.RED + "Shop System offline (Vault Economy fehlt)."); + } + return economy; + } + + // --- Schilderstellung --- @EventHandler(priority = EventPriority.HIGHEST) public void onSignChange(SignChangeEvent event) { String line0Raw = event.getLine(0); if (!line0Raw.equalsIgnoreCase("[buy]") && !line0Raw.equalsIgnoreCase("[sell]")) { return; } - + Player player = event.getPlayer(); if (!player.hasPermission("survivalplus.shop.create")) { player.sendMessage(ChatColor.RED + "Keine Berechtigung, Shops zu erstellen."); + event.setCancelled(true); return; } - String line2 = event.getLine(1); - String line3 = event.getLine(2); + String line1 = event.getLine(1); // "64 Diamond" + String line2 = event.getLine(2); // "500" - String[] parts = line2.split(" "); + String[] parts = line1.split(" "); if (parts.length < 2) { player.sendMessage(ChatColor.RED + "Format in Zeile 2 falsch! Beispiel: 64 Diamond"); event.setCancelled(true); return; } - + try { Integer.parseInt(parts[0]); if (Material.matchMaterial(parts[1]) == null) { - player.sendMessage(ChatColor.RED + "Das Item '" + parts[1] + "' existiert nicht."); - event.setCancelled(true); - return; + player.sendMessage(ChatColor.RED + "Das Item '" + parts[1] + "' existiert nicht."); + event.setCancelled(true); + return; } - Double.parseDouble(line3); + Double.parseDouble(line2); } catch (Exception e) { - player.sendMessage(ChatColor.RED + "Fehler im Format! Benutze: [Buy] \n 64 Diamond \n 500"); + player.sendMessage(ChatColor.RED + "Fehler im Format! Benutze: [Buy] / 64 Diamond / 500"); event.setCancelled(true); return; } - // --- NEU: Schild farbig machen --- + // Schild farbig formatieren if (line0Raw.equalsIgnoreCase("[buy]")) { event.setLine(0, ChatColor.GREEN + "[BUY]"); event.setLine(1, ChatColor.WHITE + parts[0] + " " + ChatColor.AQUA + parts[1]); - event.setLine(2, ChatColor.GOLD + line3 + " Coins"); - } else if (line0Raw.equalsIgnoreCase("[sell]")) { + event.setLine(2, ChatColor.GOLD + line2 + " Coins"); + } else { event.setLine(0, ChatColor.RED + "[SELL]"); event.setLine(1, ChatColor.WHITE + parts[0] + " " + ChatColor.AQUA + parts[1]); - event.setLine(2, ChatColor.GOLD + line3 + " Coins"); + event.setLine(2, ChatColor.GOLD + line2 + " Coins"); } - + if (event.getLine(3) != null && !event.getLine(3).isEmpty()) { - event.setLine(3, ChatColor.GRAY + event.getLine(3)); + event.setLine(3, ChatColor.GRAY + event.getLine(3)); } } @@ -97,23 +96,14 @@ public class SignShopListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerInteract(PlayerInteractEvent event) { Block block = event.getClickedBlock(); - if (block == null || !(block.getState() instanceof Sign)) { - return; - } + if (block == null || !(block.getState() instanceof Sign)) return; Sign sign = (Sign) block.getState(); String line0 = ChatColor.stripColor(sign.getLine(0)); - if (!line0.equalsIgnoreCase("[buy]") && !line0.equalsIgnoreCase("[sell]")) { - return; - } + if (!line0.equalsIgnoreCase("[buy]") && !line0.equalsIgnoreCase("[sell]")) return; - if (economy == null) { - event.getPlayer().sendMessage(ChatColor.RED + "Shop System offline (Vault fehlt)."); - return; - } - - // --- FEATURE: SHIFT+KLICK LOGIK (LÖSCHEN) --- + // SHIFT+KLICK: Shop entfernen if (event.getPlayer().isSneaking()) { Player player = event.getPlayer(); if (!player.hasPermission("survivalplus.shop.create")) { @@ -127,23 +117,25 @@ public class SignShopListener implements Listener { return; } - // --- ROUTING --- if (line0.equalsIgnoreCase("[buy]")) { handleBuy(event, sign); - } else if (line0.equalsIgnoreCase("[sell]")) { + } else { handleSell(event, sign); } } - // --- KAUFS LOGIK (ROBUST) --- + // --- Kaufs-Logik --- private void handleBuy(PlayerInteractEvent event, Sign sign) { - event.setCancelled(true); // Interaktion stoppen - + event.setCancelled(true); Player player = event.getPlayer(); - String line1Raw = ChatColor.stripColor(sign.getLine(1)); // "64 Diamond" - String line2Raw = ChatColor.stripColor(sign.getLine(2)); // "500" oder "500 Coins" - // 1. Validierung: Zeilen leer? + // Economy zentral aus Plugin holen + Economy economy = getEconomy(player); + if (economy == null) return; + + String line1Raw = ChatColor.stripColor(sign.getLine(1)); + String line2Raw = ChatColor.stripColor(sign.getLine(2)); + if (line1Raw == null || line1Raw.isEmpty()) { player.sendMessage(ChatColor.RED + "Zeile 1 des Schildes ist leer! (Format: 64 Diamond)"); return; @@ -153,92 +145,68 @@ public class SignShopListener implements Listener { return; } - // 2. Parsing: Zeile 1 "64 Diamond" - String[] partsRaw = line1Raw.split(" "); - if (partsRaw.length < 2) { - player.sendMessage(ChatColor.RED + "Falsches Format! Benutze: [Buy] \n 64 Diamond"); - return; - } - - String amountStr = partsRaw[0].trim(); // "64" - String itemNameStr = partsRaw[1].trim(); // "Diamond" - - if (amountStr.isEmpty() || itemNameStr.isEmpty()) { - player.sendMessage(ChatColor.RED + "Format Fehler!"); + String[] parts = line1Raw.split(" "); + if (parts.length < 2) { + player.sendMessage(ChatColor.RED + "Falsches Format! (Format: 64 Diamond)"); return; } int amount; Material material; try { - amount = Integer.parseInt(amountStr); - material = Material.matchMaterial(itemNameStr); + amount = Integer.parseInt(parts[0].trim()); + material = Material.matchMaterial(parts[1].trim()); } catch (NumberFormatException e) { - player.sendMessage(ChatColor.RED + "Angebot '" + amountStr + "' ist keine Zahl! (Beispiel: 64)"); + player.sendMessage(ChatColor.RED + "Menge '" + parts[0] + "' ist keine Zahl!"); return; } if (material == null) { - player.sendMessage(ChatColor.RED + "Item '" + itemNameStr + "' existiert nicht!"); - return; - } - - // 3. Parsing: Zeile 2 "500 Coins" (Hier passierte der Fehler!) - String[] priceParts = line2Raw.split(" "); - if (priceParts.length == 0) { - player.sendMessage(ChatColor.RED + "Preiszeile ist leer! (Beispiel: 500)"); - return; - } - - String priceStr = priceParts[0].trim(); - if (priceStr.isEmpty()) { - player.sendMessage(ChatColor.RED + "Kein Preis gefunden! (Beispiel: 500)"); + player.sendMessage(ChatColor.RED + "Item '" + parts[1] + "' existiert nicht!"); return; } + // Preis parsen ("500 Coins" → "500") double price; try { - price = Double.parseDouble(priceStr); + price = Double.parseDouble(line2Raw.split(" ")[0].trim()); } catch (NumberFormatException e) { - player.sendMessage(ChatColor.RED + "Preis '" + priceStr + "' ist keine Zahl! (Beispiel: 500)"); - return; - } catch (ArrayIndexOutOfBoundsException e) { - player.sendMessage(ChatColor.RED + "Fehlerhafter Preis! (Beispiel: 500)"); + player.sendMessage(ChatColor.RED + "Preis '" + line2Raw + "' ist keine gültige Zahl!"); return; } if (amount <= 0 || price < 0) { - player.sendMessage(ChatColor.RED + "Negative Werte sind nicht erlaubt."); + player.sendMessage(ChatColor.RED + "Ungültige Werte auf dem Schild."); return; } - // 4. Transaktion - try { - ItemStack item = new ItemStack(material, amount); + // Genug Geld? + if (economy.getBalance(player) < price) { + player.sendMessage(ChatColor.RED + "Du hast nicht genug Geld! Du brauchst " + + String.format("%.2f", price) + " Coins."); + return; + } - if (economy.getBalance(player) >= price) { - EconomyResponse response = economy.withdrawPlayer(player, price); - if (response.transactionSuccess()) { - player.getInventory().addItem(item); - player.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + material.name().toLowerCase() + " für " + price + " gekauft!"); - } else { - player.sendMessage(ChatColor.RED + "Fehler bei der Transaktion."); - } - } else { - player.sendMessage(ChatColor.RED + "Du hast nicht genug Geld! Du brauchst " + price + "."); - } - } catch (Exception e) { - player.sendMessage(ChatColor.RED + "Ein Fehler ist beim Lesen des Shops aufgetreten."); - // Loggen für Debugging, falls Vault spinnt - e.printStackTrace(); + // Transaktion + EconomyResponse response = economy.withdrawPlayer(player, price); + if (response.transactionSuccess()) { + player.getInventory().addItem(new ItemStack(material, amount)); + player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + + material.name().toLowerCase() + " für " + String.format("%.2f", price) + " Coins gekauft!"); + } else { + player.sendMessage(ChatColor.RED + "Fehler bei der Transaktion: " + response.errorMessage); } } - // --- VERKAUFS LOGIK --- + // --- Verkaufs-Logik --- private void handleSell(PlayerInteractEvent event, Sign sign) { event.setCancelled(true); - Player player = event.getPlayer(); + + // Economy zentral aus Plugin holen + Economy economy = getEconomy(player); + if (economy == null) return; + String line1Raw = ChatColor.stripColor(sign.getLine(1)); String line2Raw = ChatColor.stripColor(sign.getLine(2)); @@ -247,24 +215,21 @@ public class SignShopListener implements Listener { return; } if (line2Raw == null || line2Raw.isEmpty()) { - player.sendMessage(ChatColor.RED + "Zeile 2 ist leer!"); + player.sendMessage(ChatColor.RED + "Preiszeile ist leer!"); return; } - String[] partsRaw = line1Raw.split(" "); - if (partsRaw.length < 2) { - player.sendMessage(ChatColor.RED + "Format falsch! Benutze: [Sell] \n 64 Diamond"); + String[] parts = line1Raw.split(" "); + if (parts.length < 2) { + player.sendMessage(ChatColor.RED + "Format falsch! (Format: 64 Diamond)"); return; } - String amountStr = partsRaw[0].trim(); - String itemNameStr = partsRaw[1].trim(); - int amount; Material material; try { - amount = Integer.parseInt(amountStr); - material = Material.matchMaterial(itemNameStr); + amount = Integer.parseInt(parts[0].trim()); + material = Material.matchMaterial(parts[1].trim()); } catch (NumberFormatException e) { player.sendMessage(ChatColor.RED + "Menge ist keine Zahl!"); return; @@ -275,50 +240,46 @@ public class SignShopListener implements Listener { return; } - // Preis Parsing (wie im Kauf, nur sicherer) - String[] priceParts = line2Raw.split(" "); - if (priceParts.length == 0) { - player.sendMessage(ChatColor.RED + "Preiszeile ist leer!"); - return; - } - String priceStr = priceParts[0].trim(); - if (priceStr.isEmpty()) { - player.sendMessage(ChatColor.RED + "Kein Preis!"); - return; - } - double price; try { - price = Double.parseDouble(priceStr); + price = Double.parseDouble(line2Raw.split(" ")[0].trim()); } catch (NumberFormatException e) { - player.sendMessage(ChatColor.RED + "Preis '" + priceStr + "' ist keine Zahl!"); + player.sendMessage(ChatColor.RED + "Preis '" + line2Raw + "' ist keine gültige Zahl!"); return; } if (amount <= 0 || price < 0) { - player.sendMessage(ChatColor.RED + "Negativer Wert!"); + player.sendMessage(ChatColor.RED + "Ungültige Werte auf dem Schild."); return; } - ItemStack tempItem = new ItemStack(material, amount); + // Hat der Spieler genug Items? int hasAmount = 0; - for (ItemStack item : player.getInventory().getStorageContents()) { - if (item != null && item.isSimilar(tempItem)) { + if (item != null && item.isSimilar(new ItemStack(material))) { hasAmount += item.getAmount(); } } if (hasAmount < amount) { - player.sendMessage(ChatColor.RED + "Du hast nicht genug Items!"); + player.sendMessage(ChatColor.RED + "Du hast nicht genug Items! (Benötigt: " + amount + + ", Vorhanden: " + hasAmount + ")"); return; } - // Verkauf durchführen + // Items entfernen player.getInventory().removeItem(new ItemStack(material, amount)); - economy.depositPlayer(player, price); - player.updateInventory(); - player.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + material.name().toLowerCase() + " für " + price + " Coins verkauft!"); + // Geld gutschreiben + EconomyResponse response = economy.depositPlayer(player, price); + if (response.transactionSuccess()) { + player.updateInventory(); + player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + + material.name().toLowerCase() + " für " + String.format("%.2f", price) + " Coins verkauft!"); + } else { + // Rollback: Items zurückgeben + player.getInventory().addItem(new ItemStack(material, amount)); + player.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung: " + response.errorMessage); + } } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d762d17..9e48bf2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -27,6 +27,11 @@ blocks: # Sollen alle Spieler beim Joinen automatisch in Survival gesetzt werden? force-survival: true +economy: + start-balance: 500.0 + currency-singular: "$" + currency-plural: "$" + # Warp Default Item defaultWarpItem: OAK_SIGN # Anzahl der erlaubten Warps für Member diff --git a/src/main/resources/jobs.yml b/src/main/resources/jobs.yml new file mode 100644 index 0000000..ec59d0b --- /dev/null +++ b/src/main/resources/jobs.yml @@ -0,0 +1,575 @@ +# ============================================= +# SurvivalPlus - Jobs Konfiguration +# ============================================= + +settings: + max-jobs: 2 + max-level: 10 + level-multiplier: 0.1 + +jobs: + + # ----------------------------------------------------------- + miner: + name: "Miner" + display: "⛏ Miner" + description: "Baue Erze und Steine ab und verdiene Coins." + xp-base: 100 + actions: + # Kohle + COAL_ORE: { money: 1.0, xp: 3 } + DEEPSLATE_COAL_ORE: { money: 1.0, xp: 3 } + # Eisen + IRON_ORE: { money: 2.5, xp: 6 } + DEEPSLATE_IRON_ORE: { money: 2.5, xp: 6 } + RAW_IRON_BLOCK: { money: 5.0, xp: 10 } + # Kupfer + COPPER_ORE: { money: 2.0, xp: 5 } + DEEPSLATE_COPPER_ORE: { money: 2.0, xp: 5 } + RAW_COPPER_BLOCK: { money: 4.0, xp: 8 } + # Gold + GOLD_ORE: { money: 4.0, xp: 10 } + DEEPSLATE_GOLD_ORE: { money: 4.0, xp: 10 } + NETHER_GOLD_ORE: { money: 3.5, xp: 9 } + RAW_GOLD_BLOCK: { money: 8.0, xp: 18 } + # Lapis + LAPIS_ORE: { money: 3.0, xp: 8 } + DEEPSLATE_LAPIS_ORE: { money: 3.0, xp: 8 } + # Redstone + REDSTONE_ORE: { money: 3.0, xp: 8 } + DEEPSLATE_REDSTONE_ORE: { money: 3.0, xp: 8 } + # Smaragd + EMERALD_ORE: { money: 8.0, xp: 18 } + DEEPSLATE_EMERALD_ORE: { money: 8.0, xp: 18 } + # Diamant + DIAMOND_ORE: { money: 12.0, xp: 25 } + DEEPSLATE_DIAMOND_ORE: { money: 12.0, xp: 25 } + # Nether + NETHER_QUARTZ_ORE: { money: 2.0, xp: 5 } + ANCIENT_DEBRIS: { money: 25.0, xp: 50 } + # Gewöhnliche Steine / Deepslate + STONE: { money: 0.1, xp: 1 } + COBBLESTONE: { money: 0.1, xp: 1 } + DEEPSLATE: { money: 0.15, xp: 1 } + COBBLED_DEEPSLATE: { money: 0.15, xp: 1 } + GRANITE: { money: 0.1, xp: 1 } + DIORITE: { money: 0.1, xp: 1 } + ANDESITE: { money: 0.1, xp: 1 } + TUFF: { money: 0.1, xp: 1 } + CALCITE: { money: 0.1, xp: 1 } + DRIPSTONE_BLOCK: { money: 0.1, xp: 1 } + POINTED_DRIPSTONE: { money: 0.1, xp: 1 } + # Erde / Sand + GRAVEL: { money: 0.1, xp: 1 } + SAND: { money: 0.1, xp: 1 } + RED_SAND: { money: 0.1, xp: 1 } + CLAY: { money: 0.2, xp: 2 } + # Nether-Steine + NETHERRACK: { money: 0.1, xp: 1 } + NETHER_BRICKS: { money: 0.2, xp: 1 } + BASALT: { money: 0.1, xp: 1 } + BLACKSTONE: { money: 0.2, xp: 2 } + MAGMA_BLOCK: { money: 0.3, xp: 2 } + SOUL_SAND: { money: 0.2, xp: 2 } + SOUL_SOIL: { money: 0.2, xp: 2 } + # End + END_STONE: { money: 0.3, xp: 2 } + OBSIDIAN: { money: 1.0, xp: 5 } + CRYING_OBSIDIAN: { money: 1.5, xp: 6 } + # Eis + ICE: { money: 0.2, xp: 1 } + PACKED_ICE: { money: 0.3, xp: 2 } + BLUE_ICE: { money: 0.5, xp: 3 } + # Sonstiges + AMETHYST_CLUSTER: { money: 2.0, xp: 5 } + BUDDING_AMETHYST: { money: 3.0, xp: 7 } + MOSS_BLOCK: { money: 0.3, xp: 2 } + SCULK: { money: 0.3, xp: 2 } + SCULK_VEIN: { money: 0.2, xp: 1 } + SCULK_CATALYST: { money: 2.0, xp: 8 } + SCULK_SHRIEKER: { money: 3.0, xp: 10 } + SCULK_SENSOR: { money: 2.5, xp: 8 } + + # ----------------------------------------------------------- + farmer: + name: "Farmer" + display: "🌾 Farmer" + description: "Ernte Crops und sammle Naturmaterialien." + xp-base: 80 + actions: + # Anbaubare Crops (nur vollgewachsen) + WHEAT: { money: 0.8, xp: 3 } + POTATOES: { money: 0.8, xp: 3 } + CARROTS: { money: 0.8, xp: 3 } + BEETROOTS: { money: 0.8, xp: 3 } + NETHER_WART: { money: 1.5, xp: 5 } + COCOA: { money: 1.0, xp: 3 } + SWEET_BERRY_BUSH: { money: 1.0, xp: 3 } + # Abbrechen / Ernten + PUMPKIN: { money: 2.0, xp: 6 } + MELON: { money: 1.5, xp: 5 } + SUGAR_CANE: { money: 0.5, xp: 2 } + CACTUS: { money: 0.4, xp: 2 } + BAMBOO: { money: 0.3, xp: 1 } + KELP: { money: 0.3, xp: 1 } + KELP_PLANT: { money: 0.2, xp: 1 } + SEA_PICKLE: { money: 0.5, xp: 2 } + CHORUS_FLOWER: { money: 1.5, xp: 5 } + CHORUS_PLANT: { money: 1.0, xp: 3 } + # Pilze + BROWN_MUSHROOM: { money: 0.5, xp: 2 } + RED_MUSHROOM: { money: 0.5, xp: 2 } + BROWN_MUSHROOM_BLOCK: { money: 0.8, xp: 3 } + RED_MUSHROOM_BLOCK: { money: 0.8, xp: 3 } + MUSHROOM_STEM: { money: 0.6, xp: 2 } + CRIMSON_FUNGUS: { money: 0.8, xp: 3 } + WARPED_FUNGUS: { money: 0.8, xp: 3 } + # Blumen & Dekor-Pflanzen + DANDELION: { money: 0.2, xp: 1 } + POPPY: { money: 0.2, xp: 1 } + BLUE_ORCHID: { money: 0.3, xp: 1 } + ALLIUM: { money: 0.3, xp: 1 } + AZURE_BLUET: { money: 0.3, xp: 1 } + RED_TULIP: { money: 0.3, xp: 1 } + ORANGE_TULIP: { money: 0.3, xp: 1 } + WHITE_TULIP: { money: 0.3, xp: 1 } + PINK_TULIP: { money: 0.3, xp: 1 } + OXEYE_DAISY: { money: 0.3, xp: 1 } + CORNFLOWER: { money: 0.3, xp: 1 } + LILY_OF_THE_VALLEY: { money: 0.3, xp: 1 } + WITHER_ROSE: { money: 1.0, xp: 4 } + SUNFLOWER: { money: 0.4, xp: 2 } + LILAC: { money: 0.4, xp: 2 } + ROSE_BUSH: { money: 0.4, xp: 2 } + PEONY: { money: 0.4, xp: 2 } + PINK_PETALS: { money: 0.3, xp: 1 } + TORCHFLOWER: { money: 1.5, xp: 6 } + PITCHER_PLANT: { money: 1.5, xp: 6 } + SPORE_BLOSSOM: { money: 0.8, xp: 3 } + # Gras & Farne + GRASS: { money: 0.1, xp: 1 } + TALL_GRASS: { money: 0.1, xp: 1 } + FERN: { money: 0.1, xp: 1 } + LARGE_FERN: { money: 0.1, xp: 1 } + DEAD_BUSH: { money: 0.1, xp: 1 } + HANGING_ROOTS: { money: 0.2, xp: 1 } + GLOW_BERRIES: { money: 0.8, xp: 3 } + CAVE_VINES: { money: 0.5, xp: 2 } + CAVE_VINES_PLANT: { money: 0.3, xp: 1 } + VINE: { money: 0.2, xp: 1 } + LILY_PAD: { money: 0.2, xp: 1 } + MOSS_CARPET: { money: 0.2, xp: 1 } + # Nether-Pflanzen + CRIMSON_ROOTS: { money: 0.3, xp: 1 } + WARPED_ROOTS: { money: 0.3, xp: 1 } + NETHER_SPROUTS: { money: 0.2, xp: 1 } + WEEPING_VINES: { money: 0.3, xp: 1 } + TWISTING_VINES: { money: 0.3, xp: 1 } + + # ----------------------------------------------------------- + hunter: + name: "Hunter" + display: "⚔ Hunter" + description: "Töte Mobs und verdiene Coins." + xp-base: 120 + actions: + # Overworld – Standard + ZOMBIE: { money: 1.5, xp: 4 } + ZOMBIE_VILLAGER: { money: 2.0, xp: 5 } + ZOMBIFIED_PIGLIN: { money: 2.0, xp: 5 } + HUSK: { money: 1.5, xp: 4 } + DROWNED: { money: 1.5, xp: 4 } + SKELETON: { money: 1.5, xp: 4 } + STRAY: { money: 2.0, xp: 5 } + CREEPER: { money: 2.0, xp: 6 } + SPIDER: { money: 1.0, xp: 3 } + CAVE_SPIDER: { money: 1.5, xp: 4 } + WITCH: { money: 3.0, xp: 8 } + SLIME: { money: 1.0, xp: 3 } + ENDERMAN: { money: 3.0, xp: 8 } + ENDERMITE: { money: 0.8, xp: 2 } + SILVERFISH: { money: 0.5, xp: 2 } + PHANTOM: { money: 2.5, xp: 7 } + # Overworld – Raider + PILLAGER: { money: 3.0, xp: 8 } + VINDICATOR: { money: 3.5, xp: 9 } + EVOKER: { money: 5.0, xp: 12 } + RAVAGER: { money: 8.0, xp: 20 } + VEX: { money: 1.5, xp: 4 } + # Overworld – Wasser + GUARDIAN: { money: 4.0, xp: 10 } + ELDER_GUARDIAN: { money: 10.0, xp: 25 } + # Nether + BLAZE: { money: 3.5, xp: 9 } + WITHER_SKELETON: { money: 4.0, xp: 10 } + GHAST: { money: 4.0, xp: 10 } + MAGMA_CUBE: { money: 1.5, xp: 4 } + PIGLIN: { money: 2.0, xp: 5 } + PIGLIN_BRUTE: { money: 4.0, xp: 10 } + HOGLIN: { money: 3.0, xp: 8 } + ZOGLIN: { money: 3.5, xp: 9 } + STRIDER: { money: 2.0, xp: 5 } + # End + SHULKER: { money: 4.0, xp: 10 } + # Bosses + WITHER: { money: 50.0, xp: 100 } + # Neutrale / Passive die angegriffen werden können + WOLF: { money: 0.5, xp: 2 } + POLAR_BEAR: { money: 2.0, xp: 5 } + PANDA: { money: 1.5, xp: 4 } + BEE: { money: 0.5, xp: 2 } + LLAMA: { money: 1.0, xp: 3 } + TRADER_LLAMA: { money: 1.0, xp: 3 } + DOLPHIN: { money: 1.5, xp: 4 } + IRON_GOLEM: { money: 5.0, xp: 12 } + SNOW_GOLEM: { money: 1.0, xp: 3 } + + # ----------------------------------------------------------- + woodcutter: + name: "Woodcutter" + display: "🪓 Woodcutter" + description: "Fälle Bäume und verdiene Coins." + xp-base: 80 + actions: + # Normale Stämme + OAK_LOG: { money: 0.5, xp: 2 } + SPRUCE_LOG: { money: 0.5, xp: 2 } + BIRCH_LOG: { money: 0.5, xp: 2 } + JUNGLE_LOG: { money: 0.6, xp: 2 } + ACACIA_LOG: { money: 0.5, xp: 2 } + DARK_OAK_LOG: { money: 0.6, xp: 2 } + MANGROVE_LOG: { money: 0.7, xp: 3 } + CHERRY_LOG: { money: 0.8, xp: 3 } + CRIMSON_STEM: { money: 0.8, xp: 3 } + WARPED_STEM: { money: 0.8, xp: 3 } + # Entrindete Stämme + STRIPPED_OAK_LOG: { money: 0.6, xp: 2 } + STRIPPED_SPRUCE_LOG: { money: 0.6, xp: 2 } + STRIPPED_BIRCH_LOG: { money: 0.6, xp: 2 } + STRIPPED_JUNGLE_LOG: { money: 0.7, xp: 2 } + STRIPPED_ACACIA_LOG: { money: 0.6, xp: 2 } + STRIPPED_DARK_OAK_LOG: { money: 0.7, xp: 2 } + STRIPPED_MANGROVE_LOG: { money: 0.8, xp: 3 } + STRIPPED_CHERRY_LOG: { money: 0.9, xp: 3 } + STRIPPED_CRIMSON_STEM: { money: 0.9, xp: 3 } + STRIPPED_WARPED_STEM: { money: 0.9, xp: 3 } + # Holz-Blöcke (2 Stämme kombiniert) + OAK_WOOD: { money: 0.5, xp: 2 } + SPRUCE_WOOD: { money: 0.5, xp: 2 } + BIRCH_WOOD: { money: 0.5, xp: 2 } + JUNGLE_WOOD: { money: 0.6, xp: 2 } + ACACIA_WOOD: { money: 0.5, xp: 2 } + DARK_OAK_WOOD: { money: 0.6, xp: 2 } + MANGROVE_WOOD: { money: 0.7, xp: 3 } + CHERRY_WOOD: { money: 0.8, xp: 3 } + CRIMSON_HYPHAE: { money: 0.8, xp: 3 } + WARPED_HYPHAE: { money: 0.8, xp: 3 } + # Blätter + OAK_LEAVES: { money: 0.1, xp: 1 } + SPRUCE_LEAVES: { money: 0.1, xp: 1 } + BIRCH_LEAVES: { money: 0.1, xp: 1 } + JUNGLE_LEAVES: { money: 0.1, xp: 1 } + ACACIA_LEAVES: { money: 0.1, xp: 1 } + DARK_OAK_LEAVES: { money: 0.1, xp: 1 } + MANGROVE_LEAVES: { money: 0.1, xp: 1 } + CHERRY_LEAVES: { money: 0.1, xp: 1 } + AZALEA_LEAVES: { money: 0.2, xp: 1 } + FLOWERING_AZALEA_LEAVES: { money: 0.3, xp: 1 } + # Sonstige Holz-artige Blöcke + AZALEA: { money: 0.3, xp: 2 } + FLOWERING_AZALEA: { money: 0.4, xp: 2 } + MANGROVE_ROOTS: { money: 0.3, xp: 1 } + MUDDY_MANGROVE_ROOTS: { money: 0.3, xp: 1 } + + # ----------------------------------------------------------- + fischer: + name: "Fischer" + display: "🎣 Fischer" + description: "Angle Fische und verdiene Coins." + xp-base: 90 + actions: + # Fische + COD: { money: 1.5, xp: 4 } + SALMON: { money: 2.0, xp: 5 } + TROPICAL_FISH: { money: 2.5, xp: 6 } + PUFFERFISH: { money: 3.0, xp: 7 } + # Schätze + BOW: { money: 5.0, xp: 12 } + FISHING_ROD: { money: 4.0, xp: 10 } + NAME_TAG: { money: 8.0, xp: 20 } + SADDLE: { money: 6.0, xp: 15 } + NAUTILUS_SHELL: { money: 5.0, xp: 12 } + ENCHANTED_BOOK: { money: 7.0, xp: 18 } + LILY_PAD: { money: 0.5, xp: 2 } + LEATHER: { money: 1.0, xp: 3 } + INK_SAC: { money: 0.5, xp: 2 } + TRIPWIRE_HOOK: { money: 1.0, xp: 3 } + ROTTEN_FLESH: { money: 0.3, xp: 1 } + STICK: { money: 0.2, xp: 1 } + STRING: { money: 0.3, xp: 1 } + BONE: { money: 0.5, xp: 2 } + BOWL: { money: 0.2, xp: 1 } + + # ----------------------------------------------------------- + builder: + name: "Builder" + display: "🏗 Builder" + description: "Platziere Blöcke und verdiene Coins." + xp-base: 60 + actions: + # Stein-Varianten + STONE: { money: 0.2, xp: 1 } + COBBLESTONE: { money: 0.2, xp: 1 } + MOSSY_COBBLESTONE: { money: 0.3, xp: 1 } + STONE_BRICKS: { money: 0.5, xp: 2 } + MOSSY_STONE_BRICKS: { money: 0.5, xp: 2 } + CHISELED_STONE_BRICKS: { money: 0.6, xp: 2 } + CRACKED_STONE_BRICKS: { money: 0.4, xp: 1 } + SMOOTH_STONE: { money: 0.3, xp: 1 } + STONE_SLAB: { money: 0.15, xp: 1 } + STONE_BRICK_SLAB: { money: 0.3, xp: 1 } + STONE_STAIRS: { money: 0.3, xp: 1 } + STONE_BRICK_STAIRS: { money: 0.4, xp: 1 } + STONE_BRICK_WALL: { money: 0.4, xp: 1 } + # Granit / Diorit / Andesit + GRANITE: { money: 0.2, xp: 1 } + POLISHED_GRANITE: { money: 0.4, xp: 1 } + POLISHED_GRANITE_SLAB: { money: 0.2, xp: 1 } + POLISHED_GRANITE_STAIRS: { money: 0.3, xp: 1 } + DIORITE: { money: 0.2, xp: 1 } + POLISHED_DIORITE: { money: 0.4, xp: 1 } + POLISHED_DIORITE_SLAB: { money: 0.2, xp: 1 } + POLISHED_DIORITE_STAIRS: { money: 0.3, xp: 1 } + ANDESITE: { money: 0.2, xp: 1 } + POLISHED_ANDESITE: { money: 0.4, xp: 1 } + POLISHED_ANDESITE_SLAB: { money: 0.2, xp: 1 } + POLISHED_ANDESITE_STAIRS: { money: 0.3, xp: 1 } + # Deepslate + DEEPSLATE: { money: 0.2, xp: 1 } + COBBLED_DEEPSLATE: { money: 0.2, xp: 1 } + COBBLED_DEEPSLATE_SLAB: { money: 0.15, xp: 1 } + COBBLED_DEEPSLATE_STAIRS: { money: 0.2, xp: 1 } + COBBLED_DEEPSLATE_WALL: { money: 0.2, xp: 1 } + POLISHED_DEEPSLATE: { money: 0.4, xp: 1 } + POLISHED_DEEPSLATE_SLAB: { money: 0.25, xp: 1 } + POLISHED_DEEPSLATE_STAIRS: { money: 0.35, xp: 1 } + POLISHED_DEEPSLATE_WALL: { money: 0.3, xp: 1 } + DEEPSLATE_BRICKS: { money: 0.6, xp: 2 } + DEEPSLATE_BRICK_SLAB: { money: 0.35, xp: 1 } + DEEPSLATE_BRICK_STAIRS: { money: 0.45, xp: 1 } + DEEPSLATE_BRICK_WALL: { money: 0.4, xp: 1 } + DEEPSLATE_TILES: { money: 0.6, xp: 2 } + DEEPSLATE_TILE_SLAB: { money: 0.35, xp: 1 } + DEEPSLATE_TILE_STAIRS: { money: 0.45, xp: 1 } + DEEPSLATE_TILE_WALL: { money: 0.4, xp: 1 } + CHISELED_DEEPSLATE: { money: 0.7, xp: 2 } + # Ziegel + BRICKS: { money: 0.5, xp: 2 } + BRICK_SLAB: { money: 0.3, xp: 1 } + BRICK_STAIRS: { money: 0.4, xp: 1 } + BRICK_WALL: { money: 0.4, xp: 1 } + MUD_BRICKS: { money: 0.4, xp: 1 } + MUD_BRICK_SLAB: { money: 0.25, xp: 1 } + MUD_BRICK_STAIRS: { money: 0.3, xp: 1 } + MUD_BRICK_WALL: { money: 0.3, xp: 1 } + # Holz-Planken & Stufen + OAK_PLANKS: { money: 0.3, xp: 1 } + SPRUCE_PLANKS: { money: 0.3, xp: 1 } + BIRCH_PLANKS: { money: 0.3, xp: 1 } + JUNGLE_PLANKS: { money: 0.3, xp: 1 } + ACACIA_PLANKS: { money: 0.3, xp: 1 } + DARK_OAK_PLANKS: { money: 0.3, xp: 1 } + MANGROVE_PLANKS: { money: 0.3, xp: 1 } + CHERRY_PLANKS: { money: 0.3, xp: 1 } + BAMBOO_PLANKS: { money: 0.3, xp: 1 } + BAMBOO_MOSAIC: { money: 0.4, xp: 1 } + CRIMSON_PLANKS: { money: 0.4, xp: 1 } + WARPED_PLANKS: { money: 0.4, xp: 1 } + OAK_SLAB: { money: 0.15, xp: 1 } + SPRUCE_SLAB: { money: 0.15, xp: 1 } + BIRCH_SLAB: { money: 0.15, xp: 1 } + JUNGLE_SLAB: { money: 0.15, xp: 1 } + ACACIA_SLAB: { money: 0.15, xp: 1 } + DARK_OAK_SLAB: { money: 0.15, xp: 1 } + MANGROVE_SLAB: { money: 0.15, xp: 1 } + CHERRY_SLAB: { money: 0.15, xp: 1 } + BAMBOO_SLAB: { money: 0.15, xp: 1 } + CRIMSON_SLAB: { money: 0.2, xp: 1 } + WARPED_SLAB: { money: 0.2, xp: 1 } + OAK_STAIRS: { money: 0.2, xp: 1 } + SPRUCE_STAIRS: { money: 0.2, xp: 1 } + BIRCH_STAIRS: { money: 0.2, xp: 1 } + JUNGLE_STAIRS: { money: 0.2, xp: 1 } + ACACIA_STAIRS: { money: 0.2, xp: 1 } + DARK_OAK_STAIRS: { money: 0.2, xp: 1 } + MANGROVE_STAIRS: { money: 0.2, xp: 1 } + CHERRY_STAIRS: { money: 0.2, xp: 1 } + BAMBOO_STAIRS: { money: 0.2, xp: 1 } + CRIMSON_STAIRS: { money: 0.25, xp: 1 } + WARPED_STAIRS: { money: 0.25, xp: 1 } + # Quarz + QUARTZ_BLOCK: { money: 0.8, xp: 2 } + SMOOTH_QUARTZ: { money: 0.8, xp: 2 } + CHISELED_QUARTZ_BLOCK: { money: 1.0, xp: 3 } + QUARTZ_PILLAR: { money: 0.9, xp: 2 } + QUARTZ_BRICKS: { money: 0.9, xp: 2 } + QUARTZ_SLAB: { money: 0.4, xp: 1 } + QUARTZ_STAIRS: { money: 0.6, xp: 2 } + # Sandstein + SANDSTONE: { money: 0.4, xp: 1 } + SMOOTH_SANDSTONE: { money: 0.5, xp: 2 } + CHISELED_SANDSTONE: { money: 0.6, xp: 2 } + CUT_SANDSTONE: { money: 0.5, xp: 2 } + SANDSTONE_SLAB: { money: 0.25, xp: 1 } + SANDSTONE_STAIRS: { money: 0.35, xp: 1 } + SANDSTONE_WALL: { money: 0.3, xp: 1 } + RED_SANDSTONE: { money: 0.4, xp: 1 } + SMOOTH_RED_SANDSTONE: { money: 0.5, xp: 2 } + CHISELED_RED_SANDSTONE: { money: 0.6, xp: 2 } + CUT_RED_SANDSTONE: { money: 0.5, xp: 2 } + RED_SANDSTONE_SLAB: { money: 0.25, xp: 1 } + RED_SANDSTONE_STAIRS: { money: 0.35, xp: 1 } + RED_SANDSTONE_WALL: { money: 0.3, xp: 1 } + # Glas + GLASS: { money: 0.4, xp: 1 } + GLASS_PANE: { money: 0.2, xp: 1 } + WHITE_STAINED_GLASS: { money: 0.4, xp: 1 } + ORANGE_STAINED_GLASS: { money: 0.4, xp: 1 } + MAGENTA_STAINED_GLASS: { money: 0.4, xp: 1 } + LIGHT_BLUE_STAINED_GLASS: { money: 0.4, xp: 1 } + YELLOW_STAINED_GLASS: { money: 0.4, xp: 1 } + LIME_STAINED_GLASS: { money: 0.4, xp: 1 } + PINK_STAINED_GLASS: { money: 0.4, xp: 1 } + GRAY_STAINED_GLASS: { money: 0.4, xp: 1 } + LIGHT_GRAY_STAINED_GLASS: { money: 0.4, xp: 1 } + CYAN_STAINED_GLASS: { money: 0.4, xp: 1 } + PURPLE_STAINED_GLASS: { money: 0.4, xp: 1 } + BLUE_STAINED_GLASS: { money: 0.4, xp: 1 } + BROWN_STAINED_GLASS: { money: 0.4, xp: 1 } + GREEN_STAINED_GLASS: { money: 0.4, xp: 1 } + RED_STAINED_GLASS: { money: 0.4, xp: 1 } + BLACK_STAINED_GLASS: { money: 0.4, xp: 1 } + # Terrakotta + TERRACOTTA: { money: 0.3, xp: 1 } + WHITE_TERRACOTTA: { money: 0.3, xp: 1 } + ORANGE_TERRACOTTA: { money: 0.3, xp: 1 } + MAGENTA_TERRACOTTA: { money: 0.3, xp: 1 } + LIGHT_BLUE_TERRACOTTA: { money: 0.3, xp: 1 } + YELLOW_TERRACOTTA: { money: 0.3, xp: 1 } + LIME_TERRACOTTA: { money: 0.3, xp: 1 } + PINK_TERRACOTTA: { money: 0.3, xp: 1 } + GRAY_TERRACOTTA: { money: 0.3, xp: 1 } + LIGHT_GRAY_TERRACOTTA: { money: 0.3, xp: 1 } + CYAN_TERRACOTTA: { money: 0.3, xp: 1 } + PURPLE_TERRACOTTA: { money: 0.3, xp: 1 } + BLUE_TERRACOTTA: { money: 0.3, xp: 1 } + BROWN_TERRACOTTA: { money: 0.3, xp: 1 } + GREEN_TERRACOTTA: { money: 0.3, xp: 1 } + RED_TERRACOTTA: { money: 0.3, xp: 1 } + BLACK_TERRACOTTA: { money: 0.3, xp: 1 } + WHITE_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + ORANGE_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + MAGENTA_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + LIGHT_BLUE_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + YELLOW_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + LIME_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + PINK_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + GRAY_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + LIGHT_GRAY_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + CYAN_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + PURPLE_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + BLUE_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + BROWN_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + GREEN_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + RED_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + BLACK_GLAZED_TERRACOTTA: { money: 0.5, xp: 2 } + # Beton + WHITE_CONCRETE: { money: 0.4, xp: 1 } + ORANGE_CONCRETE: { money: 0.4, xp: 1 } + MAGENTA_CONCRETE: { money: 0.4, xp: 1 } + LIGHT_BLUE_CONCRETE: { money: 0.4, xp: 1 } + YELLOW_CONCRETE: { money: 0.4, xp: 1 } + LIME_CONCRETE: { money: 0.4, xp: 1 } + PINK_CONCRETE: { money: 0.4, xp: 1 } + GRAY_CONCRETE: { money: 0.4, xp: 1 } + LIGHT_GRAY_CONCRETE: { money: 0.4, xp: 1 } + CYAN_CONCRETE: { money: 0.4, xp: 1 } + PURPLE_CONCRETE: { money: 0.4, xp: 1 } + BLUE_CONCRETE: { money: 0.4, xp: 1 } + BROWN_CONCRETE: { money: 0.4, xp: 1 } + GREEN_CONCRETE: { money: 0.4, xp: 1 } + RED_CONCRETE: { money: 0.4, xp: 1 } + BLACK_CONCRETE: { money: 0.4, xp: 1 } + # Wolle + WHITE_WOOL: { money: 0.3, xp: 1 } + ORANGE_WOOL: { money: 0.3, xp: 1 } + MAGENTA_WOOL: { money: 0.3, xp: 1 } + LIGHT_BLUE_WOOL: { money: 0.3, xp: 1 } + YELLOW_WOOL: { money: 0.3, xp: 1 } + LIME_WOOL: { money: 0.3, xp: 1 } + PINK_WOOL: { money: 0.3, xp: 1 } + GRAY_WOOL: { money: 0.3, xp: 1 } + LIGHT_GRAY_WOOL: { money: 0.3, xp: 1 } + CYAN_WOOL: { money: 0.3, xp: 1 } + PURPLE_WOOL: { money: 0.3, xp: 1 } + BLUE_WOOL: { money: 0.3, xp: 1 } + BROWN_WOOL: { money: 0.3, xp: 1 } + GREEN_WOOL: { money: 0.3, xp: 1 } + RED_WOOL: { money: 0.3, xp: 1 } + BLACK_WOOL: { money: 0.3, xp: 1 } + # Nether-Blöcke + NETHER_BRICKS: { money: 0.4, xp: 1 } + NETHER_BRICK_SLAB: { money: 0.25, xp: 1 } + NETHER_BRICK_STAIRS: { money: 0.35, xp: 1 } + NETHER_BRICK_WALL: { money: 0.3, xp: 1 } + RED_NETHER_BRICKS: { money: 0.5, xp: 2 } + RED_NETHER_BRICK_SLAB: { money: 0.3, xp: 1 } + RED_NETHER_BRICK_STAIRS: { money: 0.4, xp: 1 } + RED_NETHER_BRICK_WALL: { money: 0.35, xp: 1 } + CHISELED_NETHER_BRICKS: { money: 0.6, xp: 2 } + CRACKED_NETHER_BRICKS: { money: 0.4, xp: 1 } + BLACKSTONE: { money: 0.3, xp: 1 } + POLISHED_BLACKSTONE: { money: 0.5, xp: 2 } + POLISHED_BLACKSTONE_BRICKS: { money: 0.6, xp: 2 } + CHISELED_POLISHED_BLACKSTONE: { money: 0.7, xp: 2 } + POLISHED_BLACKSTONE_SLAB: { money: 0.3, xp: 1 } + POLISHED_BLACKSTONE_STAIRS: { money: 0.4, xp: 1 } + POLISHED_BLACKSTONE_WALL: { money: 0.35, xp: 1 } + POLISHED_BLACKSTONE_BRICK_SLAB: { money: 0.35, xp: 1 } + POLISHED_BLACKSTONE_BRICK_STAIRS: { money: 0.45, xp: 1 } + POLISHED_BLACKSTONE_BRICK_WALL: { money: 0.4, xp: 1 } + BASALT: { money: 0.2, xp: 1 } + SMOOTH_BASALT: { money: 0.3, xp: 1 } + POLISHED_BASALT: { money: 0.4, xp: 1 } + # End + END_STONE_BRICKS: { money: 0.6, xp: 2 } + END_STONE_BRICK_SLAB: { money: 0.35, xp: 1 } + END_STONE_BRICK_STAIRS: { money: 0.45, xp: 1 } + END_STONE_BRICK_WALL: { money: 0.4, xp: 1 } + PURPUR_BLOCK: { money: 0.6, xp: 2 } + PURPUR_PILLAR: { money: 0.7, xp: 2 } + PURPUR_SLAB: { money: 0.35, xp: 1 } + PURPUR_STAIRS: { money: 0.45, xp: 1 } + # Spezial-Blöcke + OBSIDIAN: { money: 1.5, xp: 5 } + CRYING_OBSIDIAN: { money: 2.0, xp: 6 } + TUFF: { money: 0.2, xp: 1 } + CALCITE: { money: 0.2, xp: 1 } + DRIPSTONE_BLOCK: { money: 0.2, xp: 1 } + MUD: { money: 0.1, xp: 1 } + PACKED_MUD: { money: 0.2, xp: 1 } + DIRT: { money: 0.1, xp: 1 } + COARSE_DIRT: { money: 0.1, xp: 1 } + ROOTED_DIRT: { money: 0.1, xp: 1 } + GRASS_BLOCK: { money: 0.1, xp: 1 } + MYCELIUM: { money: 0.2, xp: 1 } + PODZOL: { money: 0.2, xp: 1 } + GRAVEL: { money: 0.1, xp: 1 } + SAND: { money: 0.1, xp: 1 } + RED_SAND: { money: 0.1, xp: 1 } + SOUL_SAND: { money: 0.2, xp: 1 } + SOUL_SOIL: { money: 0.2, xp: 1 } + NETHERRACK: { money: 0.1, xp: 1 } + AMETHYST_BLOCK: { money: 1.0, xp: 3 } + BUDDING_AMETHYST: { money: 2.0, xp: 5 } + TINTED_GLASS: { money: 0.8, xp: 2 } \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 649aa1a..497a766 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,10 +1,11 @@ name: SurvivalPlus -version: 1.1.3 +version: 1.1.4 main: de.viper.survivalplus.SurvivalPlus api-version: 1.21 softdepend: [LuckPerms, PlaceholderAPI, ProtocolLib, Vault] -author: Viper +loadbefore: [IngameShopSpigot] +author: M_Viper description: A plugin for enhancing survival gameplay in Minecraft. commands: @@ -291,7 +292,23 @@ commands: usage: / permission: survivalplus.head permission-message: "§cDu hast keine Berechtigung für diesen Befehl." - + + # --- NEU: MONEY COMMAND --- + money: + description: Verwaltet das Guthaben von Spielern. + usage: / [pay |set |add |remove |top] + aliases: [bal, balance, geld] + permission: survivalplus.money + permission-message: "§cDu hast keine Berechtigung für diesen Befehl." + + # --- NEU: JOB COMMAND --- + job: + description: Öffnet das Job-System (GUI oder Textbefehle). + usage: / [gui|info|join|leave|myjobs] [job] + aliases: [jobs] + permission: survivalplus.job + permission-message: "§cDu hast keine Berechtigung für diesen Befehl." + permissions: survivalplus.*: description: Gibt Zugriff auf alle SurvivalPlus-Befehle @@ -369,6 +386,11 @@ permissions: survivalplus.claim.kick: true survivalplus.claim.ban: true survivalplus.head: true + survivalplus.money: true + survivalplus.money.admin: true + survivalplus.money.pay: true + survivalplus.baltop: true + survivalplus.job: true survivalplus.claim.admin: true survivalplus.vanish.silent: true survivalplus.vanish.no-pickup: true @@ -596,4 +618,19 @@ permissions: default: op survivalplus.head: description: Erlaubt das Holen von Spielerköpfen + default: true + survivalplus.job: + description: Erlaubt die Nutzung des Job-Systems (/job) + default: true + survivalplus.money: + description: Erlaubt das Anzeigen des eigenen Guthabens mit /money + default: true + survivalplus.money.pay: + description: Erlaubt das Überweisen von Geld an andere Spieler mit /money pay + default: true + survivalplus.money.admin: + description: Erlaubt Admin-Befehle wie /money set, /money add, /money remove + default: op + survivalplus.baltop: + description: Erlaubt das Anzeigen der reichsten Spieler mit /money top default: true \ No newline at end of file