2 Commits
1.1.4 ... main

Author SHA1 Message Date
3f5499653a Update from Git Manager GUI 2026-03-10 23:20:22 +01:00
f371a9d1d8 Upload file pom.xml via GUI 2026-03-10 23:20:19 +01:00
16 changed files with 2606 additions and 231 deletions

View File

@@ -6,7 +6,7 @@
<groupId>de.viper</groupId> <groupId>de.viper</groupId>
<artifactId>SurvivalPlus</artifactId> <artifactId>SurvivalPlus</artifactId>
<version>1.1.3</version> <version>1.1.4</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>SurvivalPlus</name> <name>SurvivalPlus</name>
@@ -143,6 +143,7 @@
<include>nicknames.yml</include> <include>nicknames.yml</include>
<include>mobadapt.yml</include> <include>mobadapt.yml</include>
<include>heads.yml</include> <include>heads.yml</include>
<include>jobs.yml</include>
</includes> </includes>
</resource> </resource>
</resources> </resources>

View File

@@ -34,6 +34,9 @@ import de.viper.survivalplus.commands.TradeCommand;
import de.viper.survivalplus.commands.ReportCommand; import de.viper.survivalplus.commands.ReportCommand;
import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.Manager.ShopManager;
import de.viper.survivalplus.commands.ShopCommand; 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.commands.HealCommand;
import de.viper.survivalplus.Manager.TablistManager; import de.viper.survivalplus.Manager.TablistManager;
import de.viper.survivalplus.Manager.CommandBlocker; import de.viper.survivalplus.Manager.CommandBlocker;
@@ -158,6 +161,12 @@ public class SurvivalPlus extends JavaPlugin {
private final String VANISH_META_KEY = "vanished"; 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() { public void reloadTablistConfig() {
if (tablistFile == null) tablistFile = new File(getDataFolder(), "tablist.yml"); if (tablistFile == null) tablistFile = new File(getDataFolder(), "tablist.yml");
if (!tablistFile.exists()) { if (!tablistFile.exists()) {
@@ -274,17 +283,32 @@ public class SurvivalPlus extends JavaPlugin {
reloadBlockedCommandsConfig(); reloadBlockedCommandsConfig();
loadClaims(); loadClaims();
// --- NEU: Vault Economy Setup --- // --- Economy Setup ---
// BalanceManager sofort initialisieren.
balanceManager = new de.viper.survivalplus.economy.BalanceManager(this);
if (Bukkit.getPluginManager().getPlugin("Vault") != null) { if (Bukkit.getPluginManager().getPlugin("Vault") != null) {
// 1. Prüfen ob bereits ein externes Economy-Plugin registriert ist (z.B. EssentialsX)
RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class); RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
if (rsp != null) { if (rsp != null) {
// Externes Plugin gefunden bevorzugen
economy = rsp.getProvider(); economy = rsp.getProvider();
getLogger().info("Vault Economy erfolgreich verbunden."); getLogger().info("Externes Economy-Plugin verbunden: " + economy.getName());
} else { } 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(); PluginManager pluginManager = getServer().getPluginManager();
try { try {
@@ -358,7 +382,14 @@ public class SurvivalPlus extends JavaPlugin {
getCommand("workbench").setExecutor(new WorkbenchCommand()); getCommand("workbench").setExecutor(new WorkbenchCommand());
getCommand("anvil").setExecutor(new AnvilCommand()); getCommand("anvil").setExecutor(new AnvilCommand());
getCommand("nick").setExecutor(new NickCommand(this)); 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("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("stats").setExecutor(new StatsCommand(this, statsManager));
getCommand("spawn").setExecutor(new SpawnCommand(this)); getCommand("spawn").setExecutor(new SpawnCommand(this));
getCommand("setwarp").setExecutor(new SetWarpCommand(this, warpManager)); 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", "tpaccept", "tpdeny", "block", "unblock", "blocklist", "kit", "nick", "ride",
"vanish", "freeze", "lootchests", "tploot", "day", "night", "trade", "tradeaccept", "vanish", "freeze", "lootchests", "tploot", "day", "night", "trade", "tradeaccept",
"report", "showreport", "clearreport", "shop", "spawn", "setwarp", "delwarp", "report", "showreport", "clearreport", "shop", "spawn", "setwarp", "delwarp",
"warps", "startchallenge", "heal", "claim", "head" "warps", "startchallenge", "heal", "claim", "head", "money", "job", "jobs"
}; };
for (String name : commands) { for (String name : commands) {
if (getCommand(name) != null) { if (getCommand(name) != null) {
@@ -1391,6 +1422,21 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver
return economy; 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) // ProtocolLib Bridge (für Silent Vanish)
public ProtocolManager getProtocolManager() { public ProtocolManager getProtocolManager() {
return ProtocolLibrary.getProtocolManager(); return ProtocolLibrary.getProtocolManager();

View File

@@ -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 <job>
case "info": {
if (args.length < 2) {
player.sendMessage(ChatColor.RED + "Benutzung: /job info <job>");
return true;
}
cmdInfo(player, args[1].toLowerCase());
break;
}
// /job join <job>
case "join": {
if (args.length < 2) {
player.sendMessage(ChatColor.RED + "Benutzung: /job join <job>");
return true;
}
cmdJoin(player, args[1].toLowerCase());
break;
}
// /job leave <job>
case "leave": {
if (args.length < 2) {
player.sendMessage(ChatColor.RED + "Benutzung: /job leave <job>");
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<String> 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 <job> " + ChatColor.GRAY + "Job-Details & Level");
player.sendMessage(ChatColor.YELLOW + " /job join <job> " + ChatColor.GRAY + "Job annehmen");
player.sendMessage(ChatColor.YELLOW + " /job leave <job> " + 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;
}
}

View File

@@ -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 <Spieler> → Guthaben anzeigen
if (args.length == 0) {
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "Als Konsole bitte /money <Spieler> 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 <Spieler> → 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 <Spieler> <Betrag>
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 <Spieler> <Betrag>");
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 <Spieler> <Betrag>
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 <Spieler> <Betrag>");
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 <Spieler> <Betrag>
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 <Spieler> <Betrag>");
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 <Spieler> <Betrag>
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 <Spieler> <Betrag>");
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<UUID, Double> allBalances = bm.getAllBalances();
List<Map.Entry<UUID, Double>> 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<UUID, Double> 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 <Spieler> <Betrag>]");
sender.sendMessage(ChatColor.GRAY + "/money [set|add|remove <Spieler> <Betrag>]");
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);
}
}
}

View File

@@ -3,6 +3,8 @@ package de.viper.survivalplus.commands;
import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.Manager.ShopManager;
import de.viper.survivalplus.SurvivalPlus; import de.viper.survivalplus.SurvivalPlus;
import de.viper.survivalplus.gui.ShopGui; 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.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
@@ -12,32 +14,17 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; 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 { public class ShopCommand implements CommandExecutor {
private final ShopManager shopManager;
private final SurvivalPlus plugin; private final SurvivalPlus plugin;
private Economy economy; // ShopManager und Economy kommen zentral aus dem Plugin
private final ShopManager shopManager;
public ShopCommand(SurvivalPlus plugin) { public ShopCommand(SurvivalPlus plugin) {
this.plugin = plugin; this.plugin = plugin;
this.shopManager = new ShopManager(plugin); // ShopManager zentral aus dem Plugin holen, KEINE eigene Instanz erstellen
this.shopManager = plugin.getShopManager();
// Economy laden (für Admin-Verkauf an Server)
try {
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Economy> 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
}
} }
private String getMessage(String key) { private String getMessage(String key) {
@@ -53,17 +40,23 @@ public class ShopCommand implements CommandExecutor {
Player player = (Player) sender; Player player = (Player) sender;
// Shop-GUI öffnen // 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); ShopGui shopGui = new ShopGui(plugin, player, shopManager);
Bukkit.getPluginManager().registerEvents(shopGui, plugin); Bukkit.getPluginManager().registerEvents(shopGui, plugin);
player.openInventory(shopGui.getInventory()); player.openInventory(shopGui.getInventory());
return true; return true;
} }
// --- SHOP ADD LOGIK --- // --- SHOP ADD ---
if (args.length >= 4 && args[0].equalsIgnoreCase("add")) { if (args[0].equalsIgnoreCase("add")) {
if (args.length < 4) {
sender.sendMessage(ChatColor.RED + "Falsche Benutzung!");
sender.sendMessage(ChatColor.GRAY + "/shop add <Material> <Preis> <Bestand>");
return true;
}
// Economy-Check // Economy zentral aus Plugin holen
Economy economy = plugin.getEconomy();
if (economy == null) { if (economy == null) {
sender.sendMessage(ChatColor.RED + "Vault Economy ist nicht verfügbar!"); sender.sendMessage(ChatColor.RED + "Vault Economy ist nicht verfügbar!");
return true; return true;
@@ -74,7 +67,6 @@ public class ShopCommand implements CommandExecutor {
String amountStr = args[3]; String amountStr = args[3];
Material mat = Material.matchMaterial(materialName); Material mat = Material.matchMaterial(materialName);
if (mat == null) { if (mat == null) {
sender.sendMessage(ChatColor.RED + "Das Material '" + materialName + "' wurde nicht gefunden!"); sender.sendMessage(ChatColor.RED + "Das Material '" + materialName + "' wurde nicht gefunden!");
return true; return true;
@@ -82,12 +74,11 @@ public class ShopCommand implements CommandExecutor {
double price; double price;
int amount; int amount;
try { try {
price = Double.parseDouble(priceStr); price = Double.parseDouble(priceStr);
amount = Integer.parseInt(amountStr); amount = Integer.parseInt(amountStr);
} catch (NumberFormatException e) { } 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; return true;
} }
@@ -96,56 +87,50 @@ public class ShopCommand implements CommandExecutor {
return true; return true;
} }
// 1. Prüfen: Hat der Spieler die Items? // Hat der Spieler die Items?
int hasAmount = countItems(player.getInventory(), mat); int hasAmount = countItems(player.getInventory(), mat);
if (hasAmount < amount) { 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; return true;
} }
// 2. Items aus Inventar entfernen // Items entfernen
player.getInventory().removeItem(new ItemStack(mat, amount)); 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; double totalCost = price * amount;
EconomyResponse response = economy.depositPlayer(player, totalCost); EconomyResponse response = economy.depositPlayer(player, totalCost);
if (response.transactionSuccess()) { if (response.transactionSuccess()) {
sender.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + mat.name() + " für " + totalCost + " Coins an den Server verkauft!"); sender.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + mat.name()
+ " für " + String.format("%.2f", totalCost) + " Coins an den Server verkauft!");
// 4. Shop-Config aktualisieren (Lagerbestand erhöhen) // Shop-Config aktualisieren
String itemKey = mat.name().toLowerCase(); String itemKey = mat.name().toLowerCase();
shopManager.addOrUpdateItem(itemKey, price, shopManager.getStock(itemKey) + amount); shopManager.addOrUpdateItem(itemKey, price, shopManager.getStock(itemKey) + amount);
} else { } else {
sender.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung!"); // Rollback: Items zurückgeben
return true; player.getInventory().addItem(new ItemStack(mat, amount));
player.updateInventory();
sender.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung: " + response.errorMessage);
} }
return true; 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 <Preis> <Bestand>");
sender.sendMessage(ChatColor.GRAY + "Mit Namen: /shop add <Material> <Preis> <Bestand>");
return true;
}
sender.sendMessage(ChatColor.RED + "Unbekannter Befehl! Benutze /shop [add|gui]"); sender.sendMessage(ChatColor.RED + "Unbekannter Befehl! Benutze /shop [add|gui]");
return true; return true;
} }
/** /**
* Zählt die Anzahl der Items eines bestimmten Materials im Inventar. * Zählt alle Items eines Materials im Spieler-Inventar (nur Storage-Slots).
* Nutzt StorageContents, um auch Rüstung/Shulker-Slots zu prüfen.
*/ */
private int countItems(PlayerInventory inv, Material mat) { private int countItems(PlayerInventory inv, Material mat) {
int count = 0; int count = 0;
for (ItemStack item : inv.getStorageContents()) { for (ItemStack item : inv.getStorageContents()) {
if (item != null && item.getType().equals(mat)) { if (item != null && item.getType() == mat) {
count += item.getAmount(); count += item.getAmount();
} }
} }

View File

@@ -34,6 +34,8 @@ public class SurvivalPlusTabCompleter implements TabCompleter {
private static final List<String> CLAIM_SUBCOMMANDS = List.of("mark", "unclaim", "del", "delete", "trust", "untrust", "info", "kick", "ban", "unban"); private static final List<String> CLAIM_SUBCOMMANDS = List.of("mark", "unclaim", "del", "delete", "trust", "untrust", "info", "kick", "ban", "unban");
private static final List<String> SPLOCK_SUBCOMMANDS = List.of("lock", "unlock", "friendadd", "friendremove"); private static final List<String> SPLOCK_SUBCOMMANDS = List.of("lock", "unlock", "friendadd", "friendremove");
private static final List<String> SHOP_SUBCOMMANDS = List.of("gui", "add"); private static final List<String> SHOP_SUBCOMMANDS = List.of("gui", "add");
private static final List<String> MONEY_SUBCOMMANDS = List.of("pay", "set", "add", "remove", "top");
private static final List<String> JOB_SUBCOMMANDS = List.of("info", "join", "leave", "myjobs");
private static final List<String> NICK_SUBCOMMANDS = List.of("off", "remove", "reset"); private static final List<String> NICK_SUBCOMMANDS = List.of("off", "remove", "reset");
private static final List<String> GAMEMODE_SUBCOMMANDS = List.of("0", "1", "2", "3", "survival", "creative", "adventure", "spectator"); private static final List<String> 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); return completeSplock(sender, args);
case "shop": case "shop":
return completeShop(sender, args); 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": case "nick":
return filterPrefix(NICK_SUBCOMMANDS, args[0]); return filterPrefix(NICK_SUBCOMMANDS, args[0]);
case "gm": case "gm":
@@ -159,6 +169,42 @@ public class SurvivalPlusTabCompleter implements TabCompleter {
return Collections.emptyList(); return Collections.emptyList();
} }
private List<String> 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<String> 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<String> completeShop(CommandSender sender, String[] args) { private List<String> completeShop(CommandSender sender, String[] args) {
if (args.length == 1) { if (args.length == 1) {
return filterPrefix(SHOP_SUBCOMMANDS, args[0]); return filterPrefix(SHOP_SUBCOMMANDS, args[0]);

View File

@@ -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<UUID, Double> 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<UUID, Double> 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<UUID, Double> 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;
}
}

View File

@@ -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<String> 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);
}
}
}

View File

@@ -2,6 +2,8 @@ package de.viper.survivalplus.gui;
import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.Manager.ShopManager;
import de.viper.survivalplus.SurvivalPlus; import de.viper.survivalplus.SurvivalPlus;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.economy.EconomyResponse;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
@@ -22,11 +24,14 @@ public class ShopGui implements Listener {
private final Player player; private final Player player;
private Inventory inv; private Inventory inv;
private final SurvivalPlus plugin; 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) { public ShopGui(SurvivalPlus plugin, Player player, ShopManager shopManager) {
this.plugin = plugin; this.plugin = plugin;
this.player = player; this.player = player;
this.shopManager = shopManager; this.shopManager = shopManager;
this.economy = plugin.getEconomy(); // <-- zentral aus SurvivalPlus
createInventory(); createInventory();
} }
@@ -38,12 +43,9 @@ public class ShopGui implements Listener {
return; return;
} }
// Alle Items aus shop.yml einlesen
for (String itemKey : shopManager.getShopConfig().getConfigurationSection("items").getKeys(false)) { for (String itemKey : shopManager.getShopConfig().getConfigurationSection("items").getKeys(false)) {
Material mat = getMaterialFromKey(itemKey); Material mat = getMaterialFromKey(itemKey);
if (mat == null) { if (mat == null) continue;
continue; // Unbekannte Materialien einfach überspringen
}
addShopItem(itemKey, mat, 64); addShopItem(itemKey, mat, 64);
addShopItem(itemKey, mat, 16); addShopItem(itemKey, mat, 16);
@@ -72,8 +74,8 @@ public class ShopGui implements Listener {
meta.setDisplayName(ChatColor.GREEN.toString() + amount + "x " + material.name()); meta.setDisplayName(ChatColor.GREEN.toString() + amount + "x " + material.name());
meta.setLore(Arrays.asList( meta.setLore(Arrays.asList(
ChatColor.GRAY + "Lagerbestand: " + ChatColor.YELLOW + currentStock, ChatColor.GRAY + "Lagerbestand: " + ChatColor.YELLOW + currentStock,
ChatColor.YELLOW + "Preis pro Stück: " + pricePerUnit, ChatColor.YELLOW + "Preis pro Stück: " + String.format("%.2f", pricePerUnit),
ChatColor.YELLOW + "Gesamtpreis: " + totalPrice, ChatColor.YELLOW + "Gesamtpreis: " + String.format("%.2f", totalPrice),
"", "",
ChatColor.GREEN + "Linksklick zum Kaufen", ChatColor.GREEN + "Linksklick zum Kaufen",
ChatColor.RED + "Rechtsklick zum Verkaufen" ChatColor.RED + "Rechtsklick zum Verkaufen"
@@ -100,58 +102,85 @@ public class ShopGui implements Listener {
ItemStack clicked = e.getCurrentItem(); ItemStack clicked = e.getCurrentItem();
if (clicked == null || clicked.getType() == Material.AIR) return; 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(); int amount = clicked.getAmount();
Material mat = clicked.getType(); Material mat = clicked.getType();
String itemKey = mat.name().toLowerCase(); String itemKey = mat.name().toLowerCase();
if (e.isLeftClick()) { if (e.isLeftClick()) {
// --- KAUFEN --- // --- 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)) { if (!shopManager.buyItem(itemKey, amount)) {
player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop."); player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop.");
return; return;
} }
double totalPrice = shopManager.getCurrentPrice(itemKey) * amount; // 3. Geld abziehen
EconomyResponse response = economy.withdrawPlayer(player, totalPrice);
// TODO: Economy Abzug hier einfügen! if (!response.transactionSuccess()) {
// Beispiel: if (!economy.withdrawPlayer(player, totalPrice)) { ... } player.sendMessage(ChatColor.RED + "Fehler bei der Zahlung: " + response.errorMessage);
return;
}
// 4. Items geben
player.getInventory().addItem(new ItemStack(mat, amount)); 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()) { } else if (e.isRightClick()) {
// --- VERKAUFEN --- // --- VERKAUFEN ---
// Prüfen ob Spieler genug Items hat // 1. Prüfen ob Spieler genug Items hat
if (!player.getInventory().containsAtLeast(new ItemStack(mat), amount)) { if (!player.getInventory().containsAtLeast(new ItemStack(mat), amount)) {
player.sendMessage(ChatColor.RED + "Du hast nicht genug Items zum Verkaufen."); player.sendMessage(ChatColor.RED + "Du hast nicht genug Items zum Verkaufen.");
return; return;
} }
// Verkaufslogik // 2. Preis VOR dem Preisverfall festhalten
double sellPrice = shopManager.getCurrentPrice(itemKey) * amount;
// 3. Items entfernen
player.getInventory().removeItem(new ItemStack(mat, amount));
// 4. Preis im Shop anpassen (Lager erhöhen, Preis senken)
shopManager.sellItem(itemKey, amount); shopManager.sellItem(itemKey, amount);
double sellPrice = shopManager.getCurrentPrice(itemKey) * amount; // Neuer Preis nach dem Verkauf wird genutzt // 5. Geld gutschreiben
// Hinweis: In echten Systemen bekommt man oft den Preis *vor* dem Preisverfall, hier nehmen wir den neuen für Einfachheit oder den gespeicherten: EconomyResponse response = economy.depositPlayer(player, sellPrice);
// Besser wäre: double oldPrice = price * amount; ... player.giveMoney(oldPrice); if (!response.transactionSuccess()) {
// Rollback: Items zurückgeben wenn Zahlung fehlschlägt
// TODO: Economy Gutschrift hier einfügen! player.getInventory().addItem(new ItemStack(mat, amount));
player.sendMessage(ChatColor.RED + "Fehler bei der Auszahlung: " + response.errorMessage);
player.getInventory().removeItem(new ItemStack(mat, amount)); return;
player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", sellPrice) + " verkauft.");
} }
// Inventory schließen, um Preise neu zu laden und Bugs zu vermeiden player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name()
+ " für " + String.format("%.2f", sellPrice) + " Coins verkauft.");
}
player.updateInventory();
player.closeInventory(); player.closeInventory();
// Optional: direkt neu öffnen mit createInventory() wenn es flüssig sein soll
// createInventory();
} }
@EventHandler @EventHandler
public void onInventoryClose(InventoryCloseEvent e) { public void onInventoryClose(InventoryCloseEvent e) {
if (e.getView().getTitle().equals("Shop") && e.getPlayer().equals(player)) { if (e.getView().getTitle().equals("Shop") && e.getPlayer().equals(player)) {
// Listener kann entladen werden, wenn nicht mehr gebraucht // Listener-Cleanup hier möglich, wenn ShopGui nicht als Singleton genutzt wird
// HandlerList.unregisterAll(this); // Vorsicht: falls Singleton, sonst fliegt allen der GUI weg
} }
} }

View File

@@ -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<String> jobIds = new ArrayList<>(jobManager.getJobIds());
int[] slots = centeredSlots(jobIds.size(), 10, 16); // Reihe 2, Slots 1016
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<String> 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<String> 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<String> 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);
}
}

View File

@@ -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();
}
}

View File

@@ -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<String> 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<String> 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<String> 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<String> 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; }
}

View File

@@ -14,29 +14,27 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.RegisteredServiceProvider;
public class SignShopListener implements Listener { public class SignShopListener implements Listener {
private final SurvivalPlus plugin; private final SurvivalPlus plugin;
private Economy economy; // Economy wird zentral aus dem Plugin geholt keine eigene Initialisierung nötig
public SignShopListener(SurvivalPlus plugin) { public SignShopListener(SurvivalPlus plugin) {
this.plugin = plugin; this.plugin = plugin;
try { // Kein eigener Vault-Setup hier! Economy kommt über plugin.getEconomy()
if (plugin.getServer().getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Economy> 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."); /**
} * Hilfsmethode: Economy aus Plugin holen mit Null-Check und Fehlermeldung.
} catch (Exception e) { * Gibt null zurück wenn Economy nicht verfügbar ist.
plugin.getLogger().warning("Fehler beim Laden der Vault API."); */
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 --- // --- Schilderstellung ---
@@ -50,13 +48,14 @@ public class SignShopListener implements Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission("survivalplus.shop.create")) { if (!player.hasPermission("survivalplus.shop.create")) {
player.sendMessage(ChatColor.RED + "Keine Berechtigung, Shops zu erstellen."); player.sendMessage(ChatColor.RED + "Keine Berechtigung, Shops zu erstellen.");
event.setCancelled(true);
return; return;
} }
String line2 = event.getLine(1); String line1 = event.getLine(1); // "64 Diamond"
String line3 = event.getLine(2); String line2 = event.getLine(2); // "500"
String[] parts = line2.split(" "); String[] parts = line1.split(" ");
if (parts.length < 2) { if (parts.length < 2) {
player.sendMessage(ChatColor.RED + "Format in Zeile 2 falsch! Beispiel: 64 Diamond"); player.sendMessage(ChatColor.RED + "Format in Zeile 2 falsch! Beispiel: 64 Diamond");
event.setCancelled(true); event.setCancelled(true);
@@ -70,22 +69,22 @@ public class SignShopListener implements Listener {
event.setCancelled(true); event.setCancelled(true);
return; return;
} }
Double.parseDouble(line3); Double.parseDouble(line2);
} catch (Exception e) { } 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); event.setCancelled(true);
return; return;
} }
// --- NEU: Schild farbig machen --- // Schild farbig formatieren
if (line0Raw.equalsIgnoreCase("[buy]")) { if (line0Raw.equalsIgnoreCase("[buy]")) {
event.setLine(0, ChatColor.GREEN + "[BUY]"); event.setLine(0, ChatColor.GREEN + "[BUY]");
event.setLine(1, ChatColor.WHITE + parts[0] + " " + ChatColor.AQUA + parts[1]); 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");
} else if (line0Raw.equalsIgnoreCase("[sell]")) { } else {
event.setLine(0, ChatColor.RED + "[SELL]"); event.setLine(0, ChatColor.RED + "[SELL]");
event.setLine(1, ChatColor.WHITE + parts[0] + " " + ChatColor.AQUA + parts[1]); 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()) { if (event.getLine(3) != null && !event.getLine(3).isEmpty()) {
@@ -97,23 +96,14 @@ public class SignShopListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
Block block = event.getClickedBlock(); Block block = event.getClickedBlock();
if (block == null || !(block.getState() instanceof Sign)) { if (block == null || !(block.getState() instanceof Sign)) return;
return;
}
Sign sign = (Sign) block.getState(); Sign sign = (Sign) block.getState();
String line0 = ChatColor.stripColor(sign.getLine(0)); String line0 = ChatColor.stripColor(sign.getLine(0));
if (!line0.equalsIgnoreCase("[buy]") && !line0.equalsIgnoreCase("[sell]")) { if (!line0.equalsIgnoreCase("[buy]") && !line0.equalsIgnoreCase("[sell]")) return;
return;
}
if (economy == null) { // SHIFT+KLICK: Shop entfernen
event.getPlayer().sendMessage(ChatColor.RED + "Shop System offline (Vault fehlt).");
return;
}
// --- FEATURE: SHIFT+KLICK LOGIK (LÖSCHEN) ---
if (event.getPlayer().isSneaking()) { if (event.getPlayer().isSneaking()) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission("survivalplus.shop.create")) { if (!player.hasPermission("survivalplus.shop.create")) {
@@ -127,23 +117,25 @@ public class SignShopListener implements Listener {
return; return;
} }
// --- ROUTING ---
if (line0.equalsIgnoreCase("[buy]")) { if (line0.equalsIgnoreCase("[buy]")) {
handleBuy(event, sign); handleBuy(event, sign);
} else if (line0.equalsIgnoreCase("[sell]")) { } else {
handleSell(event, sign); handleSell(event, sign);
} }
} }
// --- KAUFS LOGIK (ROBUST) --- // --- Kaufs-Logik ---
private void handleBuy(PlayerInteractEvent event, Sign sign) { private void handleBuy(PlayerInteractEvent event, Sign sign) {
event.setCancelled(true); // Interaktion stoppen event.setCancelled(true);
Player player = event.getPlayer(); 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()) { if (line1Raw == null || line1Raw.isEmpty()) {
player.sendMessage(ChatColor.RED + "Zeile 1 des Schildes ist leer! (Format: 64 Diamond)"); player.sendMessage(ChatColor.RED + "Zeile 1 des Schildes ist leer! (Format: 64 Diamond)");
return; return;
@@ -153,92 +145,68 @@ public class SignShopListener implements Listener {
return; return;
} }
// 2. Parsing: Zeile 1 "64 Diamond" String[] parts = line1Raw.split(" ");
String[] partsRaw = line1Raw.split(" "); if (parts.length < 2) {
if (partsRaw.length < 2) { player.sendMessage(ChatColor.RED + "Falsches Format! (Format: 64 Diamond)");
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!");
return; return;
} }
int amount; int amount;
Material material; Material material;
try { try {
amount = Integer.parseInt(amountStr); amount = Integer.parseInt(parts[0].trim());
material = Material.matchMaterial(itemNameStr); material = Material.matchMaterial(parts[1].trim());
} catch (NumberFormatException e) { } 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; return;
} }
if (material == null) { if (material == null) {
player.sendMessage(ChatColor.RED + "Item '" + itemNameStr + "' existiert nicht!"); player.sendMessage(ChatColor.RED + "Item '" + parts[1] + "' 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)");
return; return;
} }
// Preis parsen ("500 Coins" → "500")
double price; double price;
try { try {
price = Double.parseDouble(priceStr); price = Double.parseDouble(line2Raw.split(" ")[0].trim());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
player.sendMessage(ChatColor.RED + "Preis '" + priceStr + "' ist keine Zahl! (Beispiel: 500)"); player.sendMessage(ChatColor.RED + "Preis '" + line2Raw + "' ist keine gültige Zahl!");
return;
} catch (ArrayIndexOutOfBoundsException e) {
player.sendMessage(ChatColor.RED + "Fehlerhafter Preis! (Beispiel: 500)");
return; return;
} }
if (amount <= 0 || price < 0) { 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; return;
} }
// 4. Transaktion // Genug Geld?
try { if (economy.getBalance(player) < price) {
ItemStack item = new ItemStack(material, amount); player.sendMessage(ChatColor.RED + "Du hast nicht genug Geld! Du brauchst "
+ String.format("%.2f", price) + " Coins.");
return;
}
if (economy.getBalance(player) >= price) { // Transaktion
EconomyResponse response = economy.withdrawPlayer(player, price); EconomyResponse response = economy.withdrawPlayer(player, price);
if (response.transactionSuccess()) { if (response.transactionSuccess()) {
player.getInventory().addItem(item); player.getInventory().addItem(new ItemStack(material, amount));
player.sendMessage(ChatColor.GREEN + "Du hast " + amount + " " + material.name().toLowerCase() + " für " + price + " gekauft!"); player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x "
+ material.name().toLowerCase() + " für " + String.format("%.2f", price) + " Coins gekauft!");
} else { } else {
player.sendMessage(ChatColor.RED + "Fehler bei der Transaktion."); player.sendMessage(ChatColor.RED + "Fehler bei der Transaktion: " + response.errorMessage);
}
} 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();
} }
} }
// --- VERKAUFS LOGIK --- // --- Verkaufs-Logik ---
private void handleSell(PlayerInteractEvent event, Sign sign) { private void handleSell(PlayerInteractEvent event, Sign sign) {
event.setCancelled(true); event.setCancelled(true);
Player player = event.getPlayer(); 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 line1Raw = ChatColor.stripColor(sign.getLine(1));
String line2Raw = ChatColor.stripColor(sign.getLine(2)); String line2Raw = ChatColor.stripColor(sign.getLine(2));
@@ -247,24 +215,21 @@ public class SignShopListener implements Listener {
return; return;
} }
if (line2Raw == null || line2Raw.isEmpty()) { if (line2Raw == null || line2Raw.isEmpty()) {
player.sendMessage(ChatColor.RED + "Zeile 2 ist leer!"); player.sendMessage(ChatColor.RED + "Preiszeile ist leer!");
return; return;
} }
String[] partsRaw = line1Raw.split(" "); String[] parts = line1Raw.split(" ");
if (partsRaw.length < 2) { if (parts.length < 2) {
player.sendMessage(ChatColor.RED + "Format falsch! Benutze: [Sell] \n 64 Diamond"); player.sendMessage(ChatColor.RED + "Format falsch! (Format: 64 Diamond)");
return; return;
} }
String amountStr = partsRaw[0].trim();
String itemNameStr = partsRaw[1].trim();
int amount; int amount;
Material material; Material material;
try { try {
amount = Integer.parseInt(amountStr); amount = Integer.parseInt(parts[0].trim());
material = Material.matchMaterial(itemNameStr); material = Material.matchMaterial(parts[1].trim());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
player.sendMessage(ChatColor.RED + "Menge ist keine Zahl!"); player.sendMessage(ChatColor.RED + "Menge ist keine Zahl!");
return; return;
@@ -275,50 +240,46 @@ public class SignShopListener implements Listener {
return; 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; double price;
try { try {
price = Double.parseDouble(priceStr); price = Double.parseDouble(line2Raw.split(" ")[0].trim());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
player.sendMessage(ChatColor.RED + "Preis '" + priceStr + "' ist keine Zahl!"); player.sendMessage(ChatColor.RED + "Preis '" + line2Raw + "' ist keine gültige Zahl!");
return; return;
} }
if (amount <= 0 || price < 0) { if (amount <= 0 || price < 0) {
player.sendMessage(ChatColor.RED + "Negativer Wert!"); player.sendMessage(ChatColor.RED + "Ungültige Werte auf dem Schild.");
return; return;
} }
ItemStack tempItem = new ItemStack(material, amount); // Hat der Spieler genug Items?
int hasAmount = 0; int hasAmount = 0;
for (ItemStack item : player.getInventory().getStorageContents()) { for (ItemStack item : player.getInventory().getStorageContents()) {
if (item != null && item.isSimilar(tempItem)) { if (item != null && item.isSimilar(new ItemStack(material))) {
hasAmount += item.getAmount(); hasAmount += item.getAmount();
} }
} }
if (hasAmount < amount) { 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; return;
} }
// Verkauf durchführen // Items entfernen
player.getInventory().removeItem(new ItemStack(material, amount)); 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);
}
} }
} }

View File

@@ -27,6 +27,11 @@ blocks:
# Sollen alle Spieler beim Joinen automatisch in Survival gesetzt werden? # Sollen alle Spieler beim Joinen automatisch in Survival gesetzt werden?
force-survival: true force-survival: true
economy:
start-balance: 500.0
currency-singular: "$"
currency-plural: "$"
# Warp Default Item # Warp Default Item
defaultWarpItem: OAK_SIGN defaultWarpItem: OAK_SIGN
# Anzahl der erlaubten Warps für Member # Anzahl der erlaubten Warps für Member

575
src/main/resources/jobs.yml Normal file
View File

@@ -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 }

View File

@@ -1,10 +1,11 @@
name: SurvivalPlus name: SurvivalPlus
version: 1.1.3 version: 1.1.4
main: de.viper.survivalplus.SurvivalPlus main: de.viper.survivalplus.SurvivalPlus
api-version: 1.21 api-version: 1.21
softdepend: [LuckPerms, PlaceholderAPI, ProtocolLib, Vault] softdepend: [LuckPerms, PlaceholderAPI, ProtocolLib, Vault]
author: Viper loadbefore: [IngameShopSpigot]
author: M_Viper
description: A plugin for enhancing survival gameplay in Minecraft. description: A plugin for enhancing survival gameplay in Minecraft.
commands: commands:
@@ -292,6 +293,22 @@ commands:
permission: survivalplus.head permission: survivalplus.head
permission-message: "§cDu hast keine Berechtigung für diesen Befehl." permission-message: "§cDu hast keine Berechtigung für diesen Befehl."
# --- NEU: MONEY COMMAND ---
money:
description: Verwaltet das Guthaben von Spielern.
usage: /<command> [pay <Spieler> <Betrag>|set <Spieler> <Betrag>|add <Spieler> <Betrag>|remove <Spieler> <Betrag>|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: /<command> [gui|info|join|leave|myjobs] [job]
aliases: [jobs]
permission: survivalplus.job
permission-message: "§cDu hast keine Berechtigung für diesen Befehl."
permissions: permissions:
survivalplus.*: survivalplus.*:
description: Gibt Zugriff auf alle SurvivalPlus-Befehle description: Gibt Zugriff auf alle SurvivalPlus-Befehle
@@ -369,6 +386,11 @@ permissions:
survivalplus.claim.kick: true survivalplus.claim.kick: true
survivalplus.claim.ban: true survivalplus.claim.ban: true
survivalplus.head: 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.claim.admin: true
survivalplus.vanish.silent: true survivalplus.vanish.silent: true
survivalplus.vanish.no-pickup: true survivalplus.vanish.no-pickup: true
@@ -597,3 +619,18 @@ permissions:
survivalplus.head: survivalplus.head:
description: Erlaubt das Holen von Spielerköpfen description: Erlaubt das Holen von Spielerköpfen
default: true 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