Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-13 14:12:41 +02:00
parent 87f80d30c5
commit ad267b6290
9 changed files with 884 additions and 428 deletions

View File

@@ -1,9 +1,10 @@
package dev.viper.eventengine.command;
import dev.viper.eventengine.EventEngine;
import dev.viper.eventengine.command.RegionSetupManager;
import dev.viper.eventengine.events.builtin.ElytraRaceHandler;
import dev.viper.eventengine.model.ActiveEvent;
import dev.viper.eventengine.model.EventDefinition;
import dev.viper.eventengine.model.EventRegion;
import dev.viper.eventengine.model.ScheduleEntry;
import dev.viper.eventengine.manager.EventManager;
import org.bukkit.Bukkit;
@@ -44,14 +45,14 @@ public class EventCommand implements CommandExecutor, TabCompleter {
requirePlayer(sender, () -> plugin.getEventGUI().openMain((Player) sender));
}
// ─── ITEM-REGEN GUI ──────────────────────────────────────────
// ─── ITEM-REGEN GUI ──────────────────────────────────────────
case "itemrain" -> {
requirePerm(sender, () -> {
requirePlayer(sender, () -> plugin.getItemRainGUI().open((Player) sender));
});
}
// ─── START ────────────────────────────────────────────────────
// ─── START ────────────────────────────────────────────────────
case "start" -> {
requirePerm(sender, () -> {
if (args.length < 2) { sender.sendMessage(p + "§cUsage: /event start <id|random>"); return; }
@@ -68,7 +69,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── STOP ─────────────────────────────────────────────────────
// ─── STOP ─────────────────────────────────────────────────────
case "stop" -> {
requirePerm(sender, () -> {
String reason = args.length > 1 ? String.join(" ", Arrays.copyOfRange(args, 1, args.length)) : "Admin";
@@ -78,14 +79,13 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── JOIN/LEAVE ───────────────────────────────────────────────
// ─── JOIN/LEAVE ───────────────────────────────────────────────
case "join" -> requirePlayer(sender, () -> plugin.getEventManager().join((Player) sender));
case "leave" -> requirePlayer(sender, () -> plugin.getEventManager().leave((Player) sender));
// ─── INFO ─────────────────────────────────────────────────────
// ─── INFO ─────────────────────────────────────────────────────
case "info" -> {
if (args.length >= 2) {
// Event-Info
plugin.getEventRegistry().get(args[1]).ifPresentOrElse(def -> {
sender.sendMessage("§8§m══════════════════════════════");
sender.sendMessage("§6 " + def.getDisplayName() + " §8[" + def.getId() + "]");
@@ -94,15 +94,19 @@ public class EventCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§7 Dauer: §f" + EventManager.formatTime(def.getDurationSeconds()));
sender.sendMessage("§7 Spieler: §f" + def.getMinPlayers() + "" + def.getMaxPlayers());
sender.sendMessage("§7 Custom: §f" + def.isCustom());
sender.sendMessage("§7 Renn-Modus: §f" + def.getRaceMode().name()
+ (def.isCircuit() ? " §8(§e" + def.getLaps() + " Runden§8)" : ""));
sender.sendMessage("§7 Start-Befehle: §f" + def.getStartCommands().size());
sender.sendMessage("§7 Belohnungen: §f" + def.getRewards().size());
sender.sendMessage("§7 Winner-Belohnungen: §f" + def.getWinnerRewards().size());
sender.sendMessage("§7 Startbereich: §f" + (def.hasStartRegion() ? "gesetzt" : "-"));
sender.sendMessage("§7 Zielbereich: §f" + (def.hasGoalRegion() ? "gesetzt" : "-"));
Object cpCount = def.getCustomSettings().get("checkpoint_count");
if (cpCount != null)
sender.sendMessage("§7 Elytra-Checkpoints: §f" + cpCount);
sender.sendMessage("§8§m══════════════════════════════");
}, () -> sender.sendMessage(p + "§cEvent nicht gefunden."));
} else {
// Aktuelles Event
ActiveEvent current = plugin.getEventManager().getCurrentEvent();
if (current == null) {
sender.sendMessage(p + "§cKein Event aktiv.");
@@ -117,7 +121,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
}
}
// ─── LIST ─────────────────────────────────────────────────────
// ─── LIST ─────────────────────────────────────────────────────
case "list" -> {
int page = args.length >= 2 ? parseIntSafe(args[1], 1) - 1 : 0;
List<EventDefinition> all = new ArrayList<>(plugin.getEventRegistry().getAll());
@@ -134,7 +138,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
if (totalPages > 1) sender.sendMessage("§7Nächste Seite: §f/event list " + (page + 2));
}
// ─── SCHEDULE ─────────────────────────────────────────────────
// ─── SCHEDULE ─────────────────────────────────────────────────
case "schedule" -> {
List<ScheduleEntry> schedule = plugin.getConfigManager().getSchedule();
sender.sendMessage("§8§m══════════════════════════════");
@@ -148,7 +152,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§8§m══════════════════════════════");
}
// ─── CREATE ───────────────────────────────────────────────────
// ─── CREATE ───────────────────────────────────────────────────
case "create" -> {
requirePerm(sender, () -> {
requirePlayer(sender, () -> {
@@ -167,7 +171,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── DELETE ───────────────────────────────────────────────────
// ─── DELETE ───────────────────────────────────────────────────
case "delete" -> {
requirePerm(sender, () -> {
if (args.length < 2) { sender.sendMessage(p + "§cUsage: /event delete <id>"); return; }
@@ -180,7 +184,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── SCORE ────────────────────────────────────────────────────
// ─── SCORE ────────────────────────────────────────────────────
case "score" -> {
requirePerm(sender, () -> {
if (args.length < 3) { sender.sendMessage(p + "§cUsage: /event score <spieler> <punkte>"); return; }
@@ -194,7 +198,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── ROTATION ─────────────────────────────────────────────────
// ─── ROTATION ─────────────────────────────────────────────────
case "rotation" -> {
requirePerm(sender, () -> {
if (args.length < 3) { sender.sendMessage(p + "§cUsage: /event rotation <add|remove> <id>"); return; }
@@ -209,7 +213,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── RELOAD ───────────────────────────────────────────────────
// ─── RELOAD ───────────────────────────────────────────────────
case "reload" -> {
requirePerm(sender, () -> {
plugin.getConfigManager().load();
@@ -219,7 +223,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── REGION ────────────────────────────────────────────────
// ─── REGION ───────────────────────────────────────────────────
case "region" -> {
requirePerm(sender, () -> {
requirePlayer(sender, () -> {
@@ -241,7 +245,7 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── RACE START/GOAL ───────────────────────────────────────
// ─── RACE START/GOAL ──────────────────────────────────────────
case "race" -> {
requirePerm(sender, () -> {
requirePlayer(sender, () -> {
@@ -278,23 +282,94 @@ public class EventCommand implements CommandExecutor, TabCompleter {
});
}
// ─── ELYTRA CHECKPOINT ────────────────────────────────────────
case "elytra" -> {
requirePerm(sender, () -> {
requirePlayer(sender, () -> {
if (args.length < 4 || !args[1].equalsIgnoreCase("checkpoint")) {
sender.sendMessage(p + "§cUsage: /event elytra checkpoint <add|confirm|clear|list> <id>");
return;
}
String sub = args[2].toLowerCase();
String id = args[3].toLowerCase();
EventDefinition def = plugin.getEventRegistry().get(id).orElse(null);
if (def == null) { sender.sendMessage(p + "§cEvent nicht gefunden: §f" + id); return; }
Player pl = (Player) sender;
RegionSetupManager rsm = plugin.getRegionSetupManager();
switch (sub) {
case "add" -> rsm.handleCheckpointPos1(pl, id);
case "confirm" -> rsm.handleCheckpointPos2(pl, id);
case "clear" -> {
ElytraRaceHandler.clearCheckpoints(def);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
sender.sendMessage(p + "§7Alle Checkpoints für §e" + def.getDisplayName() + " §7gelöscht.");
}
case "list" -> {
Object countObj = def.getCustomSettings().get("checkpoint_count");
int count = countObj instanceof Number n ? n.intValue() : 0;
sender.sendMessage("§8§m══════════════════════════════");
sender.sendMessage("§6 Elytra Checkpoints: §e" + def.getDisplayName() + " §8(" + count + ")");
for (int i = 0; i < count; i++) {
String pf = "checkpoint_" + i + "_";
Map<String, Object> s = def.getCustomSettings();
sender.sendMessage(" §7" + (i + 1) + ". §f" + s.get(pf + "world")
+ " §8[§f" + s.get(pf + "min-x") + "," + s.get(pf + "min-y") + "," + s.get(pf + "min-z")
+ "§8] → [§f" + s.get(pf + "max-x") + "," + s.get(pf + "max-y") + "," + s.get(pf + "max-z") + "§8]");
}
sender.sendMessage("§8§m══════════════════════════════");
}
default -> sender.sendMessage(p + "§cUnbekannte Option. add | confirm | clear | list");
}
});
});
}
// ─── RACEMODE ─────────────────────────────────────────────────
case "racemode" -> {
requirePerm(sender, () -> {
if (args.length < 3) {
sender.sendMessage(p + "§cUsage: /event racemode <id> <sprint|circuit> [laps]");
return;
}
String id = args[1].toLowerCase();
String mode = args[2].toLowerCase();
int laps = args.length >= 4 ? parseIntSafe(args[3], 1) : 1;
EventDefinition def = plugin.getEventRegistry().get(id).orElse(null);
if (def == null) { sender.sendMessage(p + "§cEvent nicht gefunden: §f" + id); return; }
if (mode.equals("circuit")) {
def.setRaceMode(EventDefinition.RaceMode.CIRCUIT);
def.setLaps(Math.max(1, laps));
sender.sendMessage(p + "§a✔ §e" + def.getDisplayName()
+ " §7→ Modus: §6Circuit §8(§e" + def.getLaps() + " Runden§8)");
} else {
def.setRaceMode(EventDefinition.RaceMode.SPRINT);
sender.sendMessage(p + "§a✔ §e" + def.getDisplayName()
+ " §7→ Modus: §6Sprint §8(A→B)");
}
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
});
}
default -> sendHelp(sender);
}
return true;
}
// ─── Tab-Completer ────────────────────────────────────────────────────
// ─── Tab-Completer ────────────────────────────────────────────────────
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
return filter(args[0], "gui", "start", "stop", "join", "leave", "info", "list",
"schedule", "create", "delete", "score", "rotation", "region", "race", "itemrain", "reload");
"schedule", "create", "delete", "score", "rotation", "region", "race",
"elytra", "racemode", "itemrain", "reload");
}
if (args.length == 2) {
return switch (args[0].toLowerCase()) {
case "start", "info", "delete" -> {
case "start", "info", "delete", "racemode" -> {
List<String> ids = plugin.getEventRegistry().getAll().stream()
.map(EventDefinition::getId)
.collect(Collectors.toList());
@@ -302,31 +377,36 @@ public class EventCommand implements CommandExecutor, TabCompleter {
yield filter(args[1], ids.toArray(new String[0]));
}
case "rotation" -> filter(args[1], "add", "remove");
case "score" -> null; // Spielernamen
case "region" -> filter(args[1], "pos1", "pos2", "clear", "info");
case "race" -> filter(args[1], "start", "goal");
case "region" -> filter(args[1], "pos1", "pos2", "clear", "info");
case "race" -> filter(args[1], "start", "goal");
case "elytra" -> filter(args[1], "checkpoint");
default -> null;
};
}
if (args.length == 3 && args[0].equalsIgnoreCase("race")) {
return filter(args[2], "pos1", "pos2", "clear", "info");
if (args.length == 3) {
return switch (args[0].toLowerCase()) {
case "race" -> filter(args[2], "pos1", "pos2", "clear", "info");
case "elytra" -> filter(args[2], "add", "confirm", "clear", "list");
case "racemode" -> filter(args[2], "sprint", "circuit");
case "rotation" -> plugin.getEventRegistry().getAll().stream()
.map(EventDefinition::getId)
.filter(id -> id.startsWith(args[2].toLowerCase()))
.collect(Collectors.toList());
default -> null;
};
}
if (args.length == 3 && args[0].equalsIgnoreCase("rotation")) {
return plugin.getEventRegistry().getAll().stream()
.map(EventDefinition::getId)
.filter(id -> id.startsWith(args[2].toLowerCase()))
.collect(Collectors.toList());
}
if (args.length == 4 && args[0].equalsIgnoreCase("race")) {
return plugin.getEventRegistry().getAll().stream()
.map(EventDefinition::getId)
.filter(id -> id.startsWith(args[3].toLowerCase()))
.collect(Collectors.toList());
if (args.length == 4) {
if (args[0].equalsIgnoreCase("race") || args[0].equalsIgnoreCase("elytra")) {
return plugin.getEventRegistry().getAll().stream()
.map(EventDefinition::getId)
.filter(id -> id.startsWith(args[3].toLowerCase()))
.collect(Collectors.toList());
}
}
return null;
}
// ─── Hilfsmethoden ─────────────────────────────────────────────────────
// ─── Hilfsmethoden ───────────────────────────────────────────────────
private void sendHelp(CommandSender sender) {
sender.sendMessage("§8§m══════════════════════════════");
@@ -345,6 +425,8 @@ public class EventCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§e /event reload §7- Config neu laden");
sender.sendMessage("§e /event region <pos1|pos2|clear|info> <id> §7- Event-Arena setzen");
sender.sendMessage("§e /event race <start|goal> <pos1|pos2|clear|info> <id> §7- Start/Ziel setzen");
sender.sendMessage("§e /event racemode <id> <sprint|circuit> [laps] §7- Renn-Modus setzen");
sender.sendMessage("§e /event elytra checkpoint <add|confirm|clear|list> <id> §7- Elytra-Checkpoints");
sender.sendMessage("§e /event itemrain §7- Item-Regen GUI");
sender.sendMessage("§8§m══════════════════════════════");
}

View File

@@ -1,6 +1,7 @@
package dev.viper.eventengine.command;
import dev.viper.eventengine.EventEngine;
import dev.viper.eventengine.events.builtin.ElytraRaceHandler;
import dev.viper.eventengine.model.EventDefinition;
import dev.viper.eventengine.model.EventRegion;
import org.bukkit.entity.Player;
@@ -11,125 +12,113 @@ import java.util.UUID;
/**
* Stellt Pos1/Pos2-Auswahl für Event-Regionen bereit.
* Eingebaut in EventCommand via sub-commands:
*
* /event region pos1 <id> → Pos1 auf Spieler-Position setzen
* /event region pos2 <id> → Pos2 setzen + Region speichern
* /event region clear <id> → Region entfernen
* /event region info <id> → Region anzeigen
* Befehle:
* /event region pos1/pos2/clear/info <id>
* /event race start pos1/pos2/clear/info <id>
* /event race goal pos1/pos2/clear/info <id>
* /event elytra checkpoint add/confirm/clear/list <id>
*/
public class RegionSetupManager {
private final EventEngine plugin;
// Temporäre Pos1-Speicher pro Spieler: UUID → [eventId, Location]
private final Map<UUID, Object[]> pos1Selection = new HashMap<>();
private final Map<UUID, Object[]> startPos1Selection = new HashMap<>();
private final Map<UUID, Object[]> goalPos1Selection = new HashMap<>();
private final Map<UUID, Object[]> pos1Selection = new HashMap<>();
private final Map<UUID, Object[]> startPos1Selection = new HashMap<>();
private final Map<UUID, Object[]> goalPos1Selection = new HashMap<>();
private final Map<UUID, Object[]> checkpointPos1Selection = new HashMap<>();
public RegionSetupManager(EventEngine plugin) {
this.plugin = plugin;
}
public void handlePos1(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
// ─── Haupt-Region ────────────────────────────────────────────────────
public void handlePos1(Player player, String eventId) {
EventDefinition def = resolve(player, eventId); if (def == null) return;
pos1Selection.put(player.getUniqueId(), new Object[]{eventId, player.getLocation().clone()});
player.sendMessage(plugin.prefix() + "§aPos1 gesetzt: §f" + formatLoc(player));
player.sendMessage(plugin.prefix() + "§7Jetzt: §f/event region pos2 " + eventId);
}
public void handlePos2(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
Object[] sel = pos1Selection.get(player.getUniqueId());
if (sel == null || !sel[0].equals(eventId)) {
player.sendMessage(plugin.prefix() + "§cZuerst Pos1 setzen: §f/event region pos1 " + eventId);
return;
player.sendMessage(plugin.prefix() + "§cZuerst Pos1 setzen: §f/event region pos1 " + eventId); return;
}
org.bukkit.Location pos1 = (org.bukkit.Location) sel[1];
org.bukkit.Location pos2 = player.getLocation().clone();
if (!pos1.getWorld().equals(pos2.getWorld())) {
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!");
return;
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!"); return;
}
EventRegion region = new EventRegion(pos1, pos2);
def.setRegion(region);
// Custom Events direkt speichern; builtin: in memory (gilt bis Reload)
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
pos1Selection.remove(player.getUniqueId());
player.sendMessage(plugin.prefix() + "§a✔ Region gesetzt für §e" + def.getDisplayName() + "§a:");
player.sendMessage("§7 Welt: §f" + region.getWorldName());
player.sendMessage("§7 Von: §f" + region.getMinX() + ", " + region.getMinY() + ", " + region.getMinZ());
player.sendMessage("§7 Bis: §f" + region.getMaxX() + ", " + region.getMaxY() + ", " + region.getMaxZ());
player.sendMessage("§7 Größe: §f" + region.getSizeX() + "x" + region.getSizeY() + "x" + region.getSizeZ());
sendRegionInfo(player, region);
}
public void handleClear(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
def.setRegion(null);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
player.sendMessage(plugin.prefix() + "§7Region für §e" + def.getDisplayName() + " §7entfernt (gesamte Welt).");
}
public void handleStartPos1(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
public void handleInfo(Player player, String eventId) {
EventDefinition def = resolve(player, eventId); if (def == null) return;
if (!def.hasRegion()) {
player.sendMessage(plugin.prefix() + "§e" + def.getDisplayName() + " §7hat keine Region (gesamte Welt)."); return;
}
EventRegion r = def.getRegion();
player.sendMessage("§8§m══════════════════════════════");
player.sendMessage("§6 Region: §e" + def.getDisplayName());
sendRegionInfo(player, r);
player.sendMessage("§7 Größe: §f" + r.getSizeX() + "×" + r.getSizeY() + "×" + r.getSizeZ() + " Blöcke");
player.sendMessage("§8§m══════════════════════════════");
}
// ─── Start-Region ────────────────────────────────────────────────────
public void handleStartPos1(Player player, String eventId) {
EventDefinition def = resolve(player, eventId); if (def == null) return;
startPos1Selection.put(player.getUniqueId(), new Object[]{eventId, player.getLocation().clone()});
player.sendMessage(plugin.prefix() + "§aStart-Pos1 gesetzt: §f" + formatLoc(player));
player.sendMessage(plugin.prefix() + "§7Jetzt: §f/event race start pos2 " + eventId);
}
public void handleStartPos2(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
Object[] sel = startPos1Selection.get(player.getUniqueId());
if (sel == null || !sel[0].equals(eventId)) {
player.sendMessage(plugin.prefix() + "§cZuerst Start-Pos1 setzen: §f/event race start pos1 " + eventId);
return;
player.sendMessage(plugin.prefix() + "§cZuerst Start-Pos1 setzen: §f/event race start pos1 " + eventId); return;
}
org.bukkit.Location pos1 = (org.bukkit.Location) sel[1];
org.bukkit.Location pos2 = player.getLocation().clone();
if (!pos1.getWorld().equals(pos2.getWorld())) {
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!");
return;
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!"); return;
}
EventRegion region = new EventRegion(pos1, pos2);
def.setStartRegion(region);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
startPos1Selection.remove(player.getUniqueId());
player.sendMessage(plugin.prefix() + "§a✔ Startbereich gesetzt für §e" + def.getDisplayName() + "§a:");
sendRegionInfo(player, region);
}
public void handleStartClear(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
def.setStartRegion(null);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
player.sendMessage(plugin.prefix() + "§7Startbereich für §e" + def.getDisplayName() + " §7entfernt.");
}
public void handleStartInfo(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
if (!def.hasStartRegion()) {
player.sendMessage(plugin.prefix() + "§e" + def.getDisplayName() + " §7hat keinen Startbereich.");
return;
player.sendMessage(plugin.prefix() + "§e" + def.getDisplayName() + " §7hat keinen Startbereich."); return;
}
player.sendMessage("§8§m══════════════════════════════");
player.sendMessage("§6 Startbereich: §e" + def.getDisplayName());
@@ -137,56 +126,45 @@ public class RegionSetupManager {
player.sendMessage("§8§m══════════════════════════════");
}
public void handleGoalPos1(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
// ─── Ziel-Region ─────────────────────────────────────────────────────
public void handleGoalPos1(Player player, String eventId) {
EventDefinition def = resolve(player, eventId); if (def == null) return;
goalPos1Selection.put(player.getUniqueId(), new Object[]{eventId, player.getLocation().clone()});
player.sendMessage(plugin.prefix() + "§aZiel-Pos1 gesetzt: §f" + formatLoc(player));
player.sendMessage(plugin.prefix() + "§7Jetzt: §f/event race goal pos2 " + eventId);
}
public void handleGoalPos2(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
Object[] sel = goalPos1Selection.get(player.getUniqueId());
if (sel == null || !sel[0].equals(eventId)) {
player.sendMessage(plugin.prefix() + "§cZuerst Ziel-Pos1 setzen: §f/event race goal pos1 " + eventId);
return;
player.sendMessage(plugin.prefix() + "§cZuerst Ziel-Pos1 setzen: §f/event race goal pos1 " + eventId); return;
}
org.bukkit.Location pos1 = (org.bukkit.Location) sel[1];
org.bukkit.Location pos2 = player.getLocation().clone();
if (!pos1.getWorld().equals(pos2.getWorld())) {
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!");
return;
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!"); return;
}
EventRegion region = new EventRegion(pos1, pos2);
def.setGoalRegion(region);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
goalPos1Selection.remove(player.getUniqueId());
player.sendMessage(plugin.prefix() + "§a✔ Zielbereich gesetzt für §e" + def.getDisplayName() + "§a:");
sendRegionInfo(player, region);
}
public void handleGoalClear(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
def.setGoalRegion(null);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
player.sendMessage(plugin.prefix() + "§7Zielbereich für §e" + def.getDisplayName() + " §7entfernt.");
}
public void handleGoalInfo(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
EventDefinition def = resolve(player, eventId); if (def == null) return;
if (!def.hasGoalRegion()) {
player.sendMessage(plugin.prefix() + "§e" + def.getDisplayName() + " §7hat keinen Zielbereich.");
return;
player.sendMessage(plugin.prefix() + "§e" + def.getDisplayName() + " §7hat keinen Zielbereich."); return;
}
player.sendMessage("§8§m══════════════════════════════");
player.sendMessage("§6 Zielbereich: §e" + def.getDisplayName());
@@ -194,21 +172,46 @@ public class RegionSetupManager {
player.sendMessage("§8§m══════════════════════════════");
}
public void handleInfo(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) { player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId); return; }
if (!def.hasRegion()) {
player.sendMessage(plugin.prefix() + "§e" + def.getDisplayName() + " §7hat keine Region (gesamte Welt).");
return;
// ─── Elytra Checkpoints ───────────────────────────────────────────────
public void handleCheckpointPos1(Player player, String eventId) {
EventDefinition def = resolve(player, eventId); if (def == null) return;
checkpointPos1Selection.put(player.getUniqueId(), new Object[]{eventId, player.getLocation().clone()});
Object countObj = def.getCustomSettings().get("checkpoint_count");
int nextIdx = countObj instanceof Number n ? n.intValue() : 0;
player.sendMessage(plugin.prefix() + "§aCheckpoint-Pos1 §8(#" + (nextIdx + 1) + ")§a gesetzt: §f" + formatLoc(player));
player.sendMessage(plugin.prefix() + "§7Jetzt Pos2: §f/event elytra checkpoint confirm " + eventId);
}
public void handleCheckpointPos2(Player player, String eventId) {
EventDefinition def = resolve(player, eventId); if (def == null) return;
Object[] sel = checkpointPos1Selection.get(player.getUniqueId());
if (sel == null || !sel[0].equals(eventId)) {
player.sendMessage(plugin.prefix() + "§cZuerst Pos1 setzen: §f/event elytra checkpoint add " + eventId); return;
}
EventRegion r = def.getRegion();
player.sendMessage("§8§m══════════════════════════════");
player.sendMessage("§6 Region: §e" + def.getDisplayName());
player.sendMessage("§7 Welt: §f" + r.getWorldName());
player.sendMessage("§7 Von: §f" + r.getMinX() + ", " + r.getMinY() + ", " + r.getMinZ());
player.sendMessage("§7 Bis: §f" + r.getMaxX() + ", " + r.getMaxY() + ", " + r.getMaxZ());
player.sendMessage("§7 Größe: §f" + r.getSizeX() + "×" + r.getSizeY() + "×" + r.getSizeZ() + " Blöcke");
player.sendMessage("§8§m══════════════════════════════");
org.bukkit.Location pos1 = (org.bukkit.Location) sel[1];
org.bukkit.Location pos2 = player.getLocation().clone();
if (!pos1.getWorld().equals(pos2.getWorld())) {
player.sendMessage(plugin.prefix() + "§cBeide Positionen müssen in der gleichen Welt sein!"); return;
}
EventRegion region = new EventRegion(pos1, pos2);
ElytraRaceHandler.addCheckpoint(def, region);
if (def.isCustom()) plugin.getConfigManager().saveCustomEvent(def);
checkpointPos1Selection.remove(player.getUniqueId());
Object countObj = def.getCustomSettings().get("checkpoint_count");
int total = countObj instanceof Number n ? n.intValue() : 0;
player.sendMessage(plugin.prefix() + "§a✔ Checkpoint §e" + total + " §agespeichert für §f" + def.getDisplayName());
sendRegionInfo(player, region);
player.sendMessage(plugin.prefix() + "§7Weiterer Checkpoint: §f/event elytra checkpoint add " + eventId);
}
// ─── Hilfsmethoden ───────────────────────────────────────────────────
private EventDefinition resolve(Player player, String eventId) {
EventDefinition def = plugin.getEventRegistry().get(eventId).orElse(null);
if (def == null) player.sendMessage(plugin.prefix() + "§cEvent nicht gefunden: §f" + eventId);
return def;
}
private String formatLoc(Player p) {

View File

@@ -38,27 +38,23 @@ public class ConfigManager {
private File customEventsFile;
private FileConfiguration customEventsConfig;
// Geladene Zeitplan-Einträge
private final List<ScheduleEntry> schedule = new ArrayList<>();
// Intervall-Fallback (Minuten), wenn kein Zeitplan aktiv
private int intervalMinutes;
private boolean useInterval;
private boolean randomOnInterval;
private String defaultEventId;
// Ankündigungs-Vorlauf in Sekunden
private int announceBefore;
// Ob Events im Chat-Log erscheinen
private boolean logEvents;
// Prefix für alle Nachrichten
private String prefix;
// Schutz-Einstellungen
private boolean enforceRegionBoundary;
private boolean noExplosionBlockDamage;
private boolean restrictBlockInteraction;
private List<Material> itemRainItems;
/** Sekunden für den 3-2-1-Go Countdown vor jedem Event-Start (0 = deaktiviert) */
private int countdownSeconds;
public ConfigManager(EventEngine plugin) {
this.plugin = plugin;
this.log = plugin.getLogger();
@@ -83,6 +79,7 @@ public class ConfigManager {
defaultEventId = mainConfig.getString("settings.default-event", "RANDOM");
announceBefore = mainConfig.getInt("settings.announce-before-seconds", 30);
logEvents = mainConfig.getBoolean("settings.log-events", true);
countdownSeconds = mainConfig.getInt("settings.countdown-seconds", 3);
enforceRegionBoundary = mainConfig.getBoolean("protection.enforce-region-boundary", true);
noExplosionBlockDamage = mainConfig.getBoolean("protection.no-explosion-block-damage", true);
restrictBlockInteraction = mainConfig.getBoolean("protection.restrict-block-interaction", true);
@@ -192,6 +189,9 @@ public class ConfigManager {
public FileConfiguration getMainConfig() { return mainConfig; }
public List<Material> getItemRainItems() { return Collections.unmodifiableList(itemRainItems); }
/** Countdown-Sekunden vor dem eigentlichen Event-Start (0 = kein Countdown) */
public int getCountdownSeconds() { return countdownSeconds; }
public void setItemRainItems(List<Material> items) {
itemRainItems = new ArrayList<>(items);
if (itemRainItems.isEmpty()) itemRainItems.addAll(DEFAULT_ITEM_RAIN_ITEMS);

View File

@@ -0,0 +1,357 @@
package dev.viper.eventengine.events.builtin;
import dev.viper.eventengine.EventEngine;
import dev.viper.eventengine.model.ActiveEvent;
import dev.viper.eventengine.model.EventDefinition;
import dev.viper.eventengine.model.EventRegion;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.configuration.ConfigurationSection;
import java.util.*;
/**
* Elytra-Rennen Handler mit Checkpoint-System.
*
* Setup:
* - Start-Region: /event race start pos1/pos2 <id>
* - Ziel-Region: /event race goal pos1/pos2 <id>
* - Checkpoints: in config.yml / custom_events.yml unter settings.checkpoints:
* settings:
* checkpoints:
* - world: "world"
* min-x: 100 min-y: 60 min-z: 100
* max-x: 110 max-y: 70 max-z: 110
* - world: "world"
* ...
*
* Ablauf:
* 1. Spieler erhalten Elytra + Raketen.
* 2. Checkpoints müssen der Reihe nach durchflogen werden.
* 3. Erst nach dem letzten Checkpoint gilt das Ziel.
* 4. Verpasste Checkpoints → Spieler wird mit Nachricht erinnert.
* 5. Im CIRCUIT-Modus: Runden werden gezählt (alle Checkpoints + Ziel = 1 Runde).
*/
public class ElytraRaceHandler implements IEventHandler, Listener {
private final EventEngine plugin;
private ActiveEvent currentEvent;
/** Checkpoint-Regionen in der Reihenfolge die geflogen werden muss */
private final List<EventRegion> checkpoints = new ArrayList<>();
/**
* Aktueller Checkpoint-Index pro Spieler.
* 0 = muss Checkpoint 0 als nächstes passieren.
* checkpoints.size() = alle Checkpoints durch → Ziel gilt.
*/
private final Map<UUID, Integer> playerCheckpoint = new HashMap<>();
/** Runden-Zähler (CIRCUIT-Modus) */
private final Map<UUID, Integer> lapCount = new HashMap<>();
/** Abschlussposition (CIRCUIT) */
private int finishPosition = 0;
/** Cooldown gegen Mehrfach-Trigger (ms) */
private final Map<UUID, Long> lastTrigger = new HashMap<>();
private static final long TRIGGER_COOLDOWN_MS = 1500;
private final Random rng = new Random();
public ElytraRaceHandler(EventEngine plugin) {
this.plugin = plugin;
}
@Override
public void onStart(ActiveEvent event) {
this.currentEvent = event;
this.finishPosition = 0;
playerCheckpoint.clear();
lapCount.clear();
lastTrigger.clear();
checkpoints.clear();
loadCheckpoints(event.getDefinition());
Bukkit.getPluginManager().registerEvents(this, plugin);
// Elytra + Raketen verteilen
for (UUID uuid : event.getParticipants()) {
Player p = Bukkit.getPlayer(uuid);
if (p == null) continue;
equipPlayer(p, event.getDefinition().getDurationSeconds());
playerCheckpoint.put(uuid, 0);
lapCount.put(uuid, 0);
}
// Start-Teleport
teleportToStart(event);
// Ankündigung mit Checkpoint-Info
String cpInfo = checkpoints.isEmpty()
? "§c⚠ Keine Checkpoints konfiguriert — nur Start/Ziel aktiv."
: "§7" + checkpoints.size() + " Checkpoint" + (checkpoints.size() == 1 ? "" : "s") + " auf der Strecke.";
boolean isCircuit = event.getDefinition().isCircuit();
String modeInfo = isCircuit
? "§e" + event.getDefinition().getLaps() + " Runden §7im Rundkurs-Modus."
: "§7Sprint von Start zu Ziel.";
Bukkit.broadcastMessage(plugin.prefix() + "§b🪂 §eElytra-Rennen! " + modeInfo);
Bukkit.broadcastMessage(plugin.prefix() + cpInfo);
if (!event.getDefinition().hasStartRegion())
Bukkit.broadcastMessage(plugin.prefix() + "§c⚠ Kein Startbereich gesetzt. /event race start pos1/pos2 " + event.getDefinition().getId());
if (!event.getDefinition().hasGoalRegion())
Bukkit.broadcastMessage(plugin.prefix() + "§c⚠ Kein Zielbereich gesetzt. /event race goal pos1/pos2 " + event.getDefinition().getId());
}
@Override
public void onEnd(ActiveEvent event) {
HandlerList.unregisterAll(this);
playerCheckpoint.clear();
lapCount.clear();
lastTrigger.clear();
this.currentEvent = null;
}
@Override
public void onPlayerJoin(ActiveEvent event, Player player) {
equipPlayer(player, event.getDefinition().getDurationSeconds());
playerCheckpoint.put(player.getUniqueId(), 0);
lapCount.put(player.getUniqueId(), 0);
}
@Override
public void onPlayerLeave(ActiveEvent event, Player player) {
playerCheckpoint.remove(player.getUniqueId());
lapCount.remove(player.getUniqueId());
lastTrigger.remove(player.getUniqueId());
}
// ─── Bewegungs-Listener ───────────────────────────────────────────────
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onMove(PlayerMoveEvent e) {
if (currentEvent == null || currentEvent.getState() != ActiveEvent.State.RUNNING) return;
Player player = e.getPlayer();
if (!currentEvent.isParticipant(player)) return;
Location to = e.getTo();
if (to == null) return;
// Nur bei echtem Positions-Wechsel
if (e.getFrom().getBlockX() == to.getBlockX()
&& e.getFrom().getBlockY() == to.getBlockY()
&& e.getFrom().getBlockZ() == to.getBlockZ()) return;
UUID uuid = player.getUniqueId();
long now = System.currentTimeMillis();
Long last = lastTrigger.get(uuid);
if (last != null && now - last < TRIGGER_COOLDOWN_MS) return;
EventDefinition def = currentEvent.getDefinition();
int cpIndex = playerCheckpoint.getOrDefault(uuid, 0);
// Checkpoint-Check (in Reihenfolge)
if (cpIndex < checkpoints.size()) {
EventRegion cp = checkpoints.get(cpIndex);
if (cp.contains(to)) {
lastTrigger.put(uuid, now);
playerCheckpoint.put(uuid, cpIndex + 1);
int nextCp = cpIndex + 1;
String msg = nextCp < checkpoints.size()
? "§a✔ Checkpoint §f" + nextCp + "§a/§f" + checkpoints.size() + " §apAssiert!"
: "§a✔ Letzter Checkpoint! §eFliege zum Ziel!";
player.sendActionBar(msg);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1.8f);
}
return; // Ziel gilt erst nach allen Checkpoints
}
// Ziel-Check (alle Checkpoints durch)
EventRegion goal = def.getGoalRegion();
if (goal == null || !goal.contains(to)) return;
lastTrigger.put(uuid, now);
if (def.isCircuit()) {
handleCircuitGoal(player, def, now);
} else {
handleSprintGoal(player);
}
}
// ─── Sprint: erster gewinnt ────────────────────────────────────────────
private void handleSprintGoal(Player player) {
if (currentEvent.hasWinner()) return;
plugin.getEventManager().endEventWithWinner(player, "Ziel erreicht");
}
// ─── Circuit: Runden zählen ───────────────────────────────────────────
private void handleCircuitGoal(Player player, EventDefinition def, long now) {
UUID uuid = player.getUniqueId();
int done = lapCount.merge(uuid, 1, Integer::sum);
int total = def.getLaps();
long elapsed = currentEvent.getElapsedSeconds();
// Checkpoint-Zähler für nächste Runde zurücksetzen
playerCheckpoint.put(uuid, 0);
if (done < total) {
String msg = "§e🏁 Runde §f" + done + "§e/§f" + total
+ " §8(§f" + formatTime((int) elapsed) + "§8)";
player.sendActionBar(msg);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1.5f);
Bukkit.broadcastMessage(plugin.prefix() + "§f" + player.getName()
+ " §7— Runde §e" + done + "§7/§e" + total
+ " §8(§f" + formatTime((int) elapsed) + "§8)");
} else {
finishPosition++;
int pos = finishPosition;
currentEvent.addScore(uuid, Math.max(0, 100 - (pos - 1) * 20));
String posStr = switch (pos) {
case 1 -> "§6§l1. Platz 🥇";
case 2 -> "§7§l2. Platz 🥈";
case 3 -> "§c§l3. Platz 🥉";
default -> "§7" + pos + ". Platz";
};
player.sendTitle(posStr, "§fZeit: §e" + formatTime((int) elapsed), 0, 60, 20);
player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, pos == 1 ? 1.2f : 0.8f);
Bukkit.broadcastMessage(plugin.prefix() + "§b🪂 §f" + player.getName()
+ " §ehat das Rennen beendet! §8[" + posStr + "§8] §8(§f"
+ formatTime((int) elapsed) + "§8)");
currentEvent.removeParticipant(player);
if (pos == 1) {
currentEvent.setWinner(uuid);
plugin.getEventManager().distributeWinnerRewards(def, player);
}
if (currentEvent.getParticipantCount() == 0) {
plugin.getEventManager().endEvent("Rennen beendet — alle Spieler im Ziel");
}
}
}
// ─── Hilfsmethoden ────────────────────────────────────────────────────
private void equipPlayer(Player p, int durationSec) {
p.getEquipment().setChestplate(new ItemStack(Material.ELYTRA));
// Raketen für die gesamte Event-Dauer (mind. 64)
int rockets = Math.max(64, (durationSec / 5));
p.getInventory().addItem(new ItemStack(Material.FIREWORK_ROCKET, Math.min(rockets, 64)));
}
private void teleportToStart(ActiveEvent event) {
EventRegion start = event.getDefinition().getStartRegion();
if (start == null) return;
for (UUID uuid : event.getParticipants()) {
Player p = Bukkit.getPlayer(uuid);
if (p == null) continue;
Location loc = start.randomPoint(rng);
if (loc != null) p.teleport(loc);
}
}
/**
* Lädt Checkpoint-Regionen aus den Custom-Settings der EventDefinition.
*
* Erwartet in custom_events.yml oder event-overrides unter settings:
* settings:
* checkpoint_count: 2
* checkpoint_0_world: "world"
* checkpoint_0_min-x: 100 ... checkpoint_0_max-z: 110
* checkpoint_1_world: "world"
* ...
*
* Alternativ Direktformat mit Liste wenn durch den Builder gesetzt.
*/
private void loadCheckpoints(EventDefinition def) {
Map<String, Object> settings = def.getCustomSettings();
if (settings == null || settings.isEmpty()) return;
Object countObj = settings.get("checkpoint_count");
if (!(countObj instanceof Number)) return;
int count = ((Number) countObj).intValue();
for (int i = 0; i < count; i++) {
String prefix = "checkpoint_" + i + "_";
try {
String world = (String) settings.get(prefix + "world");
int minX = toInt(settings.get(prefix + "min-x"));
int minY = toInt(settings.get(prefix + "min-y"));
int minZ = toInt(settings.get(prefix + "min-z"));
int maxX = toInt(settings.get(prefix + "max-x"));
int maxY = toInt(settings.get(prefix + "max-y"));
int maxZ = toInt(settings.get(prefix + "max-z"));
checkpoints.add(new EventRegion(world, minX, minY, minZ, maxX, maxY, maxZ));
plugin.getLogger().info("ElytraRace: Checkpoint " + i + " geladen (" + world + ")");
} catch (Exception ex) {
plugin.getLogger().warning("ElytraRace: Checkpoint " + i + " fehlerhaft — " + ex.getMessage());
}
}
plugin.getLogger().info("ElytraRace: " + checkpoints.size() + " Checkpoint(s) geladen.");
}
private int toInt(Object o) {
if (o instanceof Number n) return n.intValue();
return Integer.parseInt(String.valueOf(o));
}
private String formatTime(int seconds) {
int m = seconds / 60, s = seconds % 60;
if (m == 0) return s + "s";
return m + "m " + s + "s";
}
// ─── Checkpoint-Setup per Command-Hilfsklasse ──────────────────────────
/**
* Speichert einen neuen Checkpoint in den Custom-Settings der EventDefinition.
* Aufruf z.B. aus EventCommand: /event elytra checkpoint add <id> pos1/pos2
*
* @param def Die EventDefinition des Elytra-Rennens
* @param region Die fertige Checkpoint-Region
*/
public static void addCheckpoint(EventDefinition def, EventRegion region) {
Map<String, Object> s = def.getCustomSettings();
Object countObj = s.get("checkpoint_count");
int idx = countObj instanceof Number n ? n.intValue() : 0;
String pf = "checkpoint_" + idx + "_";
s.put(pf + "world", region.getWorldName());
s.put(pf + "min-x", region.getMinX()); s.put(pf + "min-y", region.getMinY()); s.put(pf + "min-z", region.getMinZ());
s.put(pf + "max-x", region.getMaxX()); s.put(pf + "max-y", region.getMaxY()); s.put(pf + "max-z", region.getMaxZ());
s.put("checkpoint_count", idx + 1);
}
/**
* Entfernt alle Checkpoints aus den Custom-Settings.
*/
public static void clearCheckpoints(EventDefinition def) {
Map<String, Object> s = def.getCustomSettings();
Object countObj = s.get("checkpoint_count");
int count = countObj instanceof Number n ? n.intValue() : 0;
for (int i = 0; i < count; i++) {
String pf = "checkpoint_" + i + "_";
s.remove(pf + "world");
s.remove(pf + "min-x"); s.remove(pf + "min-y"); s.remove(pf + "min-z");
s.remove(pf + "max-x"); s.remove(pf + "max-y"); s.remove(pf + "max-z");
}
s.remove("checkpoint_count");
}
}

View File

@@ -17,13 +17,16 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.GameMode;
import java.util.*;
// ══════════════════════════════════════════════════════════
// RENNEN — alle 10
// RENNEN — Basis + alle außer ElytraRaceHandler
// ElytraRaceHandler ist in ElytraRaceHandler.java (eigene Datei)
// ══════════════════════════════════════════════════════════
/** Basis für alle Renn-Events: Punkte pro Sekunde, wer am Ende mehr hat gewinnt */
/** Basis für alle Renn-Events */
class BaseRaceHandler implements IEventHandler {
protected final EventEngine plugin;
protected final String icon;
@@ -67,14 +70,7 @@ class ParkourRaceHandler extends BaseRaceHandler {
ParkourRaceHandler(EventEngine p){super(p,"🏃","Parkour Race");}
@Override public void onStart(ActiveEvent e){ super.onStart(e); Bukkit.broadcastMessage(plugin.prefix()+"§a🏃 §eParkour Race! §7Lauft durch den Kurs — wer zuerst am Ziel ist, gewinnt!"); }
}
class ElytraRaceHandler extends BaseRaceHandler {
ElytraRaceHandler(EventEngine p){super(p,"🪂","Elytra-Rennen");}
@Override public void onStart(ActiveEvent e){
super.onStart(e);
for(UUID u:e.getParticipants()){Player p=Bukkit.getPlayer(u);if(p==null)continue;p.getEquipment().setChestplate(new ItemStack(Material.ELYTRA));p.getInventory().addItem(new ItemStack(Material.FIREWORK_ROCKET,64));}
Bukkit.broadcastMessage(plugin.prefix()+"§b🪂 §eElytra-Rennen! §7Fliegt mit Elytren zum Ziel!");
}
}
// ElytraRaceHandler → eigene Datei: ElytraRaceHandler.java
class BoatRaceHandler extends BaseRaceHandler {
BoatRaceHandler(EventEngine p){super(p,"","Boot-Rennen");}
@Override public void onStart(ActiveEvent e){ super.onStart(e); for(UUID u:e.getParticipants()){Player p=Bukkit.getPlayer(u);if(p==null)continue;p.getInventory().addItem(new ItemStack(Material.OAK_BOAT));} Bukkit.broadcastMessage(plugin.prefix()+"§b⛵ §eBoot-Rennen! §7Klettert in euer Boot und rast zum Ziel!"); }
@@ -333,7 +329,7 @@ class CapturePointsHandler implements IEventHandler {
CapturePointsHandler(EventEngine p){this.plugin=p;}
@Override public void onStart(ActiveEvent e){
Bukkit.broadcastMessage(plugin.prefix()+"§e🚩 §ePunkte-Capture! §7Steht auf den markierten Punkten um Punkte zu sammeln. Mehr Spieler auf einem Punkt = schnellere Einnahme!");
ticker=new BukkitRunnable(){ @Override public void run(){ if(e.getState()==ActiveEvent.State.ENDED){cancel();return;} /* Punkt-Check hier — benötigt vorbereitete Punkte-Positionen in den Settings */ for(UUID u:e.getParticipants()){Player p=Bukkit.getPlayer(u);if(p==null)continue;/* Placeholder: jeder Spieler auf einem Kontrollpunkt bekommt Punkte */} }};
ticker=new BukkitRunnable(){ @Override public void run(){ if(e.getState()==ActiveEvent.State.ENDED){cancel();return;} }};
ticker.runTaskTimer(plugin,20L,20L);
}
@Override public void onEnd(ActiveEvent e){ if(ticker!=null)ticker.cancel(); }
@@ -374,15 +370,12 @@ class ColorWarHandler implements IEventHandler, Listener {
}
@Override public void onEnd(ActiveEvent e){
HandlerList.unregisterAll(this);
// Zähle Blöcke pro Farbe in der Region
if(e.getDefinition().hasRegion()){
org.bukkit.World w=e.getDefinition().getRegion().getWorld();
if(w!=null){Map<Material,Integer>counts=new EnumMap<>(Material.class);dev.viper.eventengine.model.EventRegion r=e.getDefinition().getRegion();for(int x=r.getMinX();x<=r.getMaxX();x++)for(int z=r.getMinZ();z<=r.getMaxZ();z++){Material t=w.getBlockAt(x,r.getMinY(),z).getType();if(Arrays.asList(COLORS).contains(t))counts.merge(t,1,Integer::sum);}Bukkit.broadcastMessage(plugin.prefix()+"§e🎨 §7Farben-Ergebnis:");counts.forEach((m,cnt)->Bukkit.broadcastMessage(" §7"+m.name()+": §e"+cnt+" §7Blöcke"));}
}
current=null;teamColors.clear();
}
// Damit ist der Import nötig
private static final Material[] COLORS_REF = COLORS;
}
// ══════════════════════════════════════════════════════════
@@ -395,11 +388,8 @@ class TinyPlayersHandler implements IEventHandler {
TinyPlayersHandler(EventEngine pp){this.p=pp;}
@Override public void onStart(ActiveEvent e){
List<Player> players = new ArrayList<>();
if (e.getParticipants().isEmpty()) {
players.addAll(Bukkit.getOnlinePlayers());
} else {
for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl!=null)players.add(pl);}
}
if (e.getParticipants().isEmpty()) { players.addAll(Bukkit.getOnlinePlayers()); }
else { for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl!=null)players.add(pl);} }
for (Player pl : players) {
applyScale(pl, 0.6);
pl.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS,e.getDefinition().getDurationSeconds()*20,0,false,false));
@@ -408,138 +398,53 @@ class TinyPlayersHandler implements IEventHandler {
Bukkit.broadcastMessage(p.prefix()+"§e🐭 §eMini-Spieler! §7Alle sind jetzt winzig klein!");
}
@Override public void onEnd(ActiveEvent e){
if (e.getParticipants().isEmpty()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
restoreScale(pl);
pl.removePotionEffect(PotionEffectType.SLOWNESS);
pl.removePotionEffect(PotionEffectType.JUMP_BOOST);
}
} else {
for(UUID u:e.getParticipants()){
Player pl=Bukkit.getPlayer(u);
if(pl!=null){
restoreScale(pl);
pl.removePotionEffect(PotionEffectType.SLOWNESS);
pl.removePotionEffect(PotionEffectType.JUMP_BOOST);
}
}
}
Iterable<UUID> targets = e.getParticipants().isEmpty() ? uuidsOf(Bukkit.getOnlinePlayers()) : e.getParticipants();
for(UUID u:targets){Player pl=Bukkit.getPlayer(u);if(pl!=null){restoreScale(pl);pl.removePotionEffect(PotionEffectType.SLOWNESS);pl.removePotionEffect(PotionEffectType.JUMP_BOOST);}}
prevScale.clear();
}
private void applyScale(Player pl, double scale) {
org.bukkit.attribute.Attribute attrType = getScaleAttribute();
if (attrType == null) return;
org.bukkit.attribute.AttributeInstance attr = pl.getAttribute(attrType);
if (attr == null) return;
prevScale.putIfAbsent(pl.getUniqueId(), attr.getBaseValue());
attr.setBaseValue(scale);
}
private void restoreScale(Player pl) {
org.bukkit.attribute.Attribute attrType = getScaleAttribute();
if (attrType == null) return;
org.bukkit.attribute.AttributeInstance attr = pl.getAttribute(attrType);
if (attr == null) return;
Double prev = prevScale.remove(pl.getUniqueId());
if (prev != null) attr.setBaseValue(prev);
}
private org.bukkit.attribute.Attribute getScaleAttribute() {
try {
return org.bukkit.attribute.Attribute.valueOf("GENERIC_SCALE");
} catch (IllegalArgumentException ex) {
return null;
}
}
private void applyScale(Player pl, double scale){org.bukkit.attribute.Attribute a=scaleAttr();if(a==null)return;org.bukkit.attribute.AttributeInstance ai=pl.getAttribute(a);if(ai==null)return;prevScale.putIfAbsent(pl.getUniqueId(),ai.getBaseValue());ai.setBaseValue(scale);}
private void restoreScale(Player pl){org.bukkit.attribute.Attribute a=scaleAttr();if(a==null)return;org.bukkit.attribute.AttributeInstance ai=pl.getAttribute(a);if(ai==null)return;Double prev=prevScale.remove(pl.getUniqueId());if(prev!=null)ai.setBaseValue(prev);}
private org.bukkit.attribute.Attribute scaleAttr(){try{return org.bukkit.attribute.Attribute.valueOf("GENERIC_SCALE");}catch(IllegalArgumentException ex){return null;}}
private List<UUID> uuidsOf(Collection<? extends Player> ps){List<UUID> r=new ArrayList<>();ps.forEach(p->r.add(p.getUniqueId()));return r;}
}
class GiantPlayersHandler implements IEventHandler {
private final EventEngine p;
private final Map<UUID, Double> prevScale = new HashMap<>();
GiantPlayersHandler(EventEngine pp){this.p=pp;}
@Override public void onStart(ActiveEvent e){
List<Player> players = new ArrayList<>();
if (e.getParticipants().isEmpty()) {
players.addAll(Bukkit.getOnlinePlayers());
} else {
for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl!=null)players.add(pl);}
}
for(Player pl:players){
applyScale(pl, 1.6);
pl.addPotionEffect(new PotionEffect(PotionEffectType.SPEED,e.getDefinition().getDurationSeconds()*20,1,false,false));
pl.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,e.getDefinition().getDurationSeconds()*20,1,false,false));
}
if (e.getParticipants().isEmpty()) { players.addAll(Bukkit.getOnlinePlayers()); }
else { for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl!=null)players.add(pl);} }
for(Player pl:players){applyScale(pl,1.6);pl.addPotionEffect(new PotionEffect(PotionEffectType.SPEED,e.getDefinition().getDurationSeconds()*20,1,false,false));pl.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,e.getDefinition().getDurationSeconds()*20,1,false,false));}
Bukkit.broadcastMessage(p.prefix()+"§6🦣 §eRiesen-Spieler! §7Alle sind jetzt riesig! Stärke und Geschwindigkeit erhöht!");
}
@Override public void onEnd(ActiveEvent e){
if (e.getParticipants().isEmpty()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
restoreScale(pl);
pl.removePotionEffect(PotionEffectType.SPEED);
pl.removePotionEffect(PotionEffectType.STRENGTH);
}
} else {
for(UUID u:e.getParticipants()){
Player pl=Bukkit.getPlayer(u);
if(pl!=null){
restoreScale(pl);
pl.removePotionEffect(PotionEffectType.SPEED);
pl.removePotionEffect(PotionEffectType.STRENGTH);
}
}
}
Iterable<UUID> targets = e.getParticipants().isEmpty() ? uuidsOf(Bukkit.getOnlinePlayers()) : e.getParticipants();
for(UUID u:targets){Player pl=Bukkit.getPlayer(u);if(pl!=null){restoreScale(pl);pl.removePotionEffect(PotionEffectType.SPEED);pl.removePotionEffect(PotionEffectType.STRENGTH);}}
prevScale.clear();
}
private void applyScale(Player pl, double scale) {
org.bukkit.attribute.Attribute attrType = getScaleAttribute();
if (attrType == null) return;
org.bukkit.attribute.AttributeInstance attr = pl.getAttribute(attrType);
if (attr == null) return;
prevScale.putIfAbsent(pl.getUniqueId(), attr.getBaseValue());
attr.setBaseValue(scale);
}
private void restoreScale(Player pl) {
org.bukkit.attribute.Attribute attrType = getScaleAttribute();
if (attrType == null) return;
org.bukkit.attribute.AttributeInstance attr = pl.getAttribute(attrType);
if (attr == null) return;
Double prev = prevScale.remove(pl.getUniqueId());
if (prev != null) attr.setBaseValue(prev);
}
private org.bukkit.attribute.Attribute getScaleAttribute() {
try {
return org.bukkit.attribute.Attribute.valueOf("GENERIC_SCALE");
} catch (IllegalArgumentException ex) {
return null;
}
}
private void applyScale(Player pl, double scale){org.bukkit.attribute.Attribute a=scaleAttr();if(a==null)return;org.bukkit.attribute.AttributeInstance ai=pl.getAttribute(a);if(ai==null)return;prevScale.putIfAbsent(pl.getUniqueId(),ai.getBaseValue());ai.setBaseValue(scale);}
private void restoreScale(Player pl){org.bukkit.attribute.Attribute a=scaleAttr();if(a==null)return;org.bukkit.attribute.AttributeInstance ai=pl.getAttribute(a);if(ai==null)return;Double prev=prevScale.remove(pl.getUniqueId());if(prev!=null)ai.setBaseValue(prev);}
private org.bukkit.attribute.Attribute scaleAttr(){try{return org.bukkit.attribute.Attribute.valueOf("GENERIC_SCALE");}catch(IllegalArgumentException ex){return null;}}
private List<UUID> uuidsOf(Collection<? extends Player> ps){List<UUID> r=new ArrayList<>();ps.forEach(p->r.add(p.getUniqueId()));return r;}
}
class InvisiblePlayersHandler implements IEventHandler {
private final EventEngine p;
InvisiblePlayersHandler(EventEngine pp){this.p=pp;}
@Override public void onStart(ActiveEvent e){
if (e.getParticipants().isEmpty()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
pl.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,e.getDefinition().getDurationSeconds()*20,0,false,false));
}
} else {
for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl==null)continue;pl.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,e.getDefinition().getDurationSeconds()*20,0,false,false));}
}
Iterable<UUID> targets = e.getParticipants().isEmpty() ? uuidsOf(Bukkit.getOnlinePlayers()) : e.getParticipants();
for(UUID u:targets){Player pl=Bukkit.getPlayer(u);if(pl==null)continue;pl.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY,e.getDefinition().getDurationSeconds()*20,0,false,false));}
Bukkit.broadcastMessage(p.prefix()+"§7👻 §eUnsichtbarkeit! §7Alle Spieler sind unsichtbar!");
}
@Override public void onEnd(ActiveEvent e){
if (e.getParticipants().isEmpty()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
pl.removePotionEffect(PotionEffectType.INVISIBILITY);
}
} else {
for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl!=null)pl.removePotionEffect(PotionEffectType.INVISIBILITY);}
}
Iterable<UUID> targets = e.getParticipants().isEmpty() ? uuidsOf(Bukkit.getOnlinePlayers()) : e.getParticipants();
for(UUID u:targets){Player pl=Bukkit.getPlayer(u);if(pl!=null)pl.removePotionEffect(PotionEffectType.INVISIBILITY);}
}
private List<UUID> uuidsOf(Collection<? extends Player> ps){List<UUID> r=new ArrayList<>();ps.forEach(p->r.add(p.getUniqueId()));return r;}
}
class RandomTeleportHandler implements IEventHandler {
private final EventEngine plugin; private BukkitRunnable tpRunner;
RandomTeleportHandler(EventEngine p){this.plugin=p;}
@@ -548,40 +453,22 @@ class RandomTeleportHandler implements IEventHandler {
final Random rng=new Random();
tpRunner=new BukkitRunnable(){@Override public void run(){
if(e.getState()==ActiveEvent.State.ENDED){cancel();return;}
if (e.getParticipants().isEmpty()) {
for (Player p : Bukkit.getOnlinePlayers()) {
if (rng.nextInt(3) != 0) continue;
Location loc=e.getDefinition().hasRegion()?e.getDefinition().getRegion().randomLocation(rng):p.getLocation().add((rng.nextDouble()-0.5)*30,0,(rng.nextDouble()-0.5)*30);
if(loc!=null)p.teleport(loc);
p.sendActionBar("§d⚡ ZUFALLS-TP!");
}
return;
}
for(UUID u:e.getParticipants()){
Player p=Bukkit.getPlayer(u);
if(p==null||rng.nextInt(3)!=0)continue;
Location loc=e.getDefinition().hasRegion()?e.getDefinition().getRegion().randomLocation(rng):p.getLocation().add((rng.nextDouble()-0.5)*30,0,(rng.nextDouble()-0.5)*30);
if(loc!=null)p.teleport(loc);
p.sendActionBar("§d⚡ ZUFALLS-TP!");
}
Iterable<UUID> targets = e.getParticipants().isEmpty() ? uuidsOf(Bukkit.getOnlinePlayers()) : e.getParticipants();
for(UUID u:targets){Player p=Bukkit.getPlayer(u);if(p==null||rng.nextInt(3)!=0)continue;Location loc=e.getDefinition().hasRegion()?e.getDefinition().getRegion().randomLocation(rng):p.getLocation().add((rng.nextDouble()-0.5)*30,0,(rng.nextDouble()-0.5)*30);if(loc!=null)p.teleport(loc);p.sendActionBar("§d⚡ ZUFALLS-TP!");}
}};
tpRunner.runTaskTimer(plugin,20L,100L);
}
@Override public void onEnd(ActiveEvent e){ if(tpRunner!=null)tpRunner.cancel(); }
private List<UUID> uuidsOf(Collection<? extends Player> ps){List<UUID> r=new ArrayList<>();ps.forEach(p->r.add(p.getUniqueId()));return r;}
}
class SwapInventoriesHandler implements IEventHandler {
private final EventEngine p;
SwapInventoriesHandler(EventEngine pp){this.p=pp;}
@Override public void onStart(ActiveEvent e){
List<Player> players = new ArrayList<>();
if (e.getParticipants().isEmpty()) {
players.addAll(Bukkit.getOnlinePlayers());
} else {
for (UUID u : e.getParticipants()) {
Player pl = Bukkit.getPlayer(u);
if (pl != null) players.add(pl);
}
}
List<Player> players=new ArrayList<>();
if(e.getParticipants().isEmpty()){players.addAll(Bukkit.getOnlinePlayers());}
else{for(UUID u:e.getParticipants()){Player pl=Bukkit.getPlayer(u);if(pl!=null)players.add(pl);}}
if(players.size()<2){Bukkit.broadcastMessage(p.prefix()+"§cMindestens 2 Spieler benötigt!");return;}
Collections.shuffle(players);
List<ItemStack[]>inventories=new ArrayList<>();
@@ -591,6 +478,7 @@ class SwapInventoriesHandler implements IEventHandler {
}
@Override public void onEnd(ActiveEvent e){}
}
class ReverseGravityHandler implements IEventHandler {
private final EventEngine plugin; private BukkitRunnable ticker;
ReverseGravityHandler(EventEngine p){this.plugin=p;}
@@ -599,22 +487,15 @@ class ReverseGravityHandler implements IEventHandler {
final Random rng=new Random();
ticker=new BukkitRunnable(){@Override public void run(){
if(e.getState()==ActiveEvent.State.ENDED){cancel();return;}
if (e.getParticipants().isEmpty()) {
for (Player p : Bukkit.getOnlinePlayers()) {
p.setVelocity(p.getVelocity().setY(0.5 + rng.nextDouble() * 0.5));
}
return;
}
for(UUID u:e.getParticipants()){
Player p=Bukkit.getPlayer(u);
if(p==null)continue;
p.setVelocity(p.getVelocity().setY(0.5+rng.nextDouble()*0.5));
}
Iterable<UUID> targets = e.getParticipants().isEmpty() ? uuidsOf(Bukkit.getOnlinePlayers()) : e.getParticipants();
for(UUID u:targets){Player p=Bukkit.getPlayer(u);if(p==null)continue;p.setVelocity(p.getVelocity().setY(0.5+rng.nextDouble()*0.5));}
}};
ticker.runTaskTimer(plugin,20L,15L);
}
@Override public void onEnd(ActiveEvent e){ if(ticker!=null)ticker.cancel(); }
private List<UUID> uuidsOf(Collection<? extends Player> ps){List<UUID> r=new ArrayList<>();ps.forEach(p->r.add(p.getUniqueId()));return r;}
}
class BouncyBlocksHandler implements IEventHandler, Listener {
private final EventEngine plugin; private ActiveEvent current;
BouncyBlocksHandler(EventEngine p){this.plugin=p;}

View File

@@ -6,17 +6,53 @@ import dev.viper.eventengine.model.EventCategory;
import dev.viper.eventengine.model.EventDefinition;
import dev.viper.eventengine.model.EventRegion;
import dev.viper.eventengine.model.EventType;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Verarbeitet Ziel-Einläufe für Renn-Events.
*
* SPRINT-Modus (Standard):
* Wer als erstes die Zielregion betritt, gewinnt → endEventWithWinner().
*
* CIRCUIT-Modus (Rundkurs):
* Jedes Mal wenn ein Spieler die Zielregion passiert, zählt eine Runde.
* Nach def.getLaps() Runden → Spieler gewinnt.
* Zwischenzeiten werden per ActionBar gemeldet.
* Alle anderen können weiterlaufen — Platz 2/3 werden ebenfalls ausgewertet.
*/
public class RaceGoalListener implements Listener {
private final EventEngine plugin;
/**
* Rundenstand pro Spieler im CIRCUIT-Modus.
* Key = UUID, Value = absolvierte Runden
*/
private final Map<UUID, Integer> lapCount = new HashMap<>();
/**
* Verhindert Mehrfach-Trigger wenn der Spieler mehrere Ticks in der Zielregion bleibt.
* Key = UUID, Value = System.currentTimeMillis() des letzten Triggers.
*/
private final Map<UUID, Long> lastGoalTime = new HashMap<>();
/** Mindestabstand in ms zwischen zwei aufeinanderfolgenden Zieltriggern (3s) */
private static final long GOAL_COOLDOWN_MS = 3000;
/** Abschlusspositionen für das Leaderboard im CIRCUIT-Modus */
private int finishPosition = 0;
public RaceGoalListener(EventEngine plugin) {
this.plugin = plugin;
}
@@ -38,18 +74,94 @@ public class RaceGoalListener implements Listener {
Location to = e.getTo();
if (to == null) return;
// Nur bei echtem Block-Wechsel prüfen (Performance)
if (e.getFrom().getBlockX() == to.getBlockX()
&& e.getFrom().getBlockY() == to.getBlockY()
&& e.getFrom().getBlockZ() == to.getBlockZ()) return;
if (!goal.contains(to)) return;
if (active.hasWinner()) return;
// Cooldown verhindern
long now = System.currentTimeMillis();
Long last = lastGoalTime.get(player.getUniqueId());
if (last != null && now - last < GOAL_COOLDOWN_MS) return;
lastGoalTime.put(player.getUniqueId(), now);
if (def.isCircuit()) {
handleCircuitGoal(active, def, player);
} else {
handleSprintGoal(active, player);
}
}
// ─── Sprint: erster gewinnt sofort ────────────────────────────────────
private void handleSprintGoal(ActiveEvent active, Player player) {
if (active.hasWinner()) return; // bereits entschieden
plugin.getEventManager().endEventWithWinner(player, "Ziel erreicht");
}
// ─── Circuit: Runden zählen ───────────────────────────────────────────
private void handleCircuitGoal(ActiveEvent active, EventDefinition def, Player player) {
UUID uuid = player.getUniqueId();
int done = lapCount.merge(uuid, 1, Integer::sum);
int total = def.getLaps();
long elapsed = active.getElapsedSeconds();
if (done < total) {
// Zwischenrunde
String msg = "§e🏁 Runde §f" + done + "§e/§f" + total
+ " §8(Zeit: §f" + formatTime((int) elapsed) + "§8)";
player.sendActionBar(msg);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1.5f);
Bukkit.broadcastMessage(plugin.prefix() + "§f" + player.getName()
+ " §7— Runde §e" + done + "§7/§e" + total
+ " §8(§f" + formatTime((int) elapsed) + "§8)");
} else {
// Ziel erreicht (alle Runden voll)
finishPosition++;
int pos = finishPosition;
active.addScore(uuid, Math.max(0, 100 - (pos - 1) * 20)); // Punkte: 1. = 100, 2. = 80, …
String posStr = switch (pos) {
case 1 -> "§6§l1. Platz 🥇";
case 2 -> "§7§l2. Platz 🥈";
case 3 -> "§c§l3. Platz 🥉";
default -> "§7" + pos + ". Platz";
};
player.sendTitle(posStr, "§fZeit: §e" + formatTime((int) elapsed), 0, 60, 20);
player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, pos == 1 ? 1.2f : 0.8f);
Bukkit.broadcastMessage(plugin.prefix() + "§6🏁 §f" + player.getName()
+ " §ehat das Rennen beendet! §8[" + posStr + "§8] §8(Zeit: §f"
+ formatTime((int) elapsed) + "§8)");
// Spieler aus aktivem Rennen entfernen (kann zuschauen)
active.removeParticipant(player);
// Wenn alle Spieler fertig (oder nur noch einer übrig) → Event beenden
int remaining = active.getParticipantCount();
if (pos == 1) {
// Gewinner setzen
active.setWinner(uuid);
plugin.getEventManager().distributeWinnerRewards(def, player);
}
if (remaining == 0 || (remaining == 1 && active.getParticipants().size() == 1)) {
plugin.getEventManager().endEvent("Rennen beendet — alle Spieler im Ziel");
}
}
}
private boolean isRaceEvent(EventDefinition def) {
if (def.getCategory() == EventCategory.RACING) return true;
return def.getType() == EventType.TEAM_RELAY_RACE;
}
private String formatTime(int seconds) {
int m = seconds / 60, s = seconds % 60;
if (m == 0) return s + "s";
return m + "m " + s + "s";
}
}

View File

@@ -6,6 +6,7 @@ import dev.viper.eventengine.events.builtin.IEventHandler;
import dev.viper.eventengine.model.ActiveEvent;
import dev.viper.eventengine.model.EventDefinition;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
@@ -14,7 +15,7 @@ import java.util.logging.Logger;
/**
* Steuert den Lebenszyklus von Events:
* start → announce → run → end → reward
* start → announce → countdown → run → end → reward
*/
public class EventManager {
@@ -39,9 +40,7 @@ public class EventManager {
return false;
}
currentEvent = new ActiveEvent(def);
currentEvent.setState(ActiveEvent.State.RUNNING);
// Ankündigung
broadcast(def.getAnnouncement());
if (def.getDurationSeconds() > 0)
broadcast(plugin.prefix() + "§7Dauer: §e" + formatTime(def.getDurationSeconds()));
@@ -51,12 +50,65 @@ public class EventManager {
log.info("Event gestartet von " + by + ": " + def.getDisplayName());
}
// 3..2..1..Go Countdown, danach eigentlicher Start
int countdown = plugin.getConfigManager().getCountdownSeconds();
if (countdown > 0) {
currentEvent = new ActiveEvent(def);
currentEvent.setState(ActiveEvent.State.WAITING);
runCountdown(def, initiator, countdown);
} else {
launchEvent(def, initiator);
}
return true;
}
/** Countdown-Loop: 3..2..1..Go */
private void runCountdown(EventDefinition def, Player initiator, int seconds) {
final ActiveEvent snap = currentEvent;
new BukkitRunnable() {
int remaining = seconds;
@Override
public void run() {
if (snap != currentEvent) { cancel(); return; } // abgebrochen
if (remaining > 0) {
// Titel + Sound für alle Online-Spieler
String color = remaining <= 3 ? "§c" : "§e";
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendTitle(color + remaining, "§7" + def.getDisplayName() + " §7startet gleich!", 0, 25, 5);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_HAT, 1f, 1f);
}
broadcast(plugin.prefix() + "§e⏳ " + def.getDisplayName() + " §7startet in §c" + remaining + " §7Sekunde" + (remaining == 1 ? "" : "n") + "...");
remaining--;
} else {
cancel();
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendTitle("§a§lGO!", "§7" + def.getDisplayName(), 0, 30, 10);
p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_LARGE_BLAST, 1f, 1.2f);
}
broadcast(plugin.prefix() + "§a§l🚀 GO! §r§e" + def.getDisplayName() + " §7hat begonnen!");
launchEvent(def, initiator);
}
}
}.runTaskTimer(plugin, 0L, 20L);
}
/** Eigentlicher Event-Start nach dem Countdown */
private void launchEvent(EventDefinition def, Player initiator) {
if (currentEvent == null || currentEvent.getState() == ActiveEvent.State.ENDED) {
currentEvent = new ActiveEvent(def);
}
currentEvent.setState(ActiveEvent.State.RUNNING);
runCommands(def.getStartCommands(), null);
handlerRegistry.get(def.getType()).ifPresent(h -> h.onStart(currentEvent));
plugin.getScoreboardManager().start(currentEvent);
if (def.getDurationSeconds() > 0) {
final ActiveEvent snap = currentEvent;
if (def.getDurationSeconds() > 70)
new BukkitRunnable() { @Override public void run() {
if (currentEvent == snap && currentEvent.getState() == ActiveEvent.State.RUNNING)
@@ -75,7 +127,6 @@ public class EventManager {
}}.runTaskLater(plugin, def.getDurationSeconds() * 20L).getTaskId();
currentEvent.setTaskId(taskId);
}
return true;
}
public boolean endEvent(String reason) {
@@ -112,7 +163,24 @@ public class EventManager {
if (currentEvent == null || currentEvent.getState() == ActiveEvent.State.ENDED) return false;
if (winner != null) {
currentEvent.setWinner(winner.getUniqueId());
broadcast(plugin.prefix() + "§6🏁 " + winner.getName() + " §ehat das Ziel erreicht und gewinnt!");
// Ziel-Nachricht mit Zeit
long elapsed = currentEvent.getElapsedSeconds();
broadcast(plugin.prefix() + "§6🏁 §f" + winner.getName()
+ " §ehat das Ziel erreicht und gewinnt! §8(Zeit: §f" + formatTime((int) elapsed) + "§8)");
// Title für Gewinner
winner.sendTitle("§6§l🏁 ZIEL!", "§e" + currentEvent.getDefinition().getDisplayName(), 0, 60, 20);
winner.playSound(winner.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f);
// Title für alle anderen
for (UUID uuid : currentEvent.getParticipants()) {
Player p = Bukkit.getPlayer(uuid);
if (p != null && !p.equals(winner)) {
p.sendTitle("§c§lZU SPÄT!", "§7" + winner.getName() + " §7war schneller.", 0, 40, 15);
}
}
distributeWinnerRewards(currentEvent.getDefinition(), winner);
}
return endEvent(reason);
@@ -175,7 +243,7 @@ public class EventManager {
}
}
private void distributeWinnerRewards(EventDefinition def, Player winner) {
public void distributeWinnerRewards(EventDefinition def, Player winner) {
if (def.getWinnerRewards().isEmpty() || winner == null) return;
runCommands(def.getWinnerRewards(), winner);
}

View File

@@ -1,52 +1,47 @@
package dev.viper.eventengine.model;
import dev.viper.eventengine.util.ColorUtil;
import dev.viper.eventengine.model.EventRegion;
import org.bukkit.configuration.ConfigurationSection;
import java.util.*;
/**
* Repräsentiert ein einzelnes Event — sowohl eingebaute als auch Custom-Events.
* Custom-Events werden in custom_events.yml gespeichert.
*/
public class EventDefinition {
/** Sprint = einmaliger Punkt-zu-Punkt Lauf; CIRCUIT = Rundkurs (laps Runden) */
public enum RaceMode { SPRINT, CIRCUIT }
private final String id;
private String displayName;
private String description;
private EventType type;
private EventCategory category;
// Zeitlimit in Sekunden (0 = unbegrenzt)
private int durationSeconds;
// Mindest-/Höchst-Spielerzahl
private int minPlayers;
private int maxPlayers;
// Befehle die beim Start/Ende ausgeführt werden
private List<String> startCommands;
private List<String> endCommands;
// Belohnungen
private List<String> rewards;
// Ankündigungs-Text
private String announcement;
// Ob das Event in der Rotation ist
private boolean inRotation;
// Gewichtung in der Rotation
private int weight;
// Optionaler Start-/Zielbereich (Rennen)
private EventRegion startRegion;
private EventRegion goalRegion;
// Custom-Einstellungen (Key-Value)
private Map<String, Object> customSettings;
// Ist es ein benutzerdefiniertes Event?
private boolean isCustom;
// Optionale Event-Region (null = gesamte Welt)
private EventRegion region;
// Winner-Belohnungen (nur Sieger)
private List<String> winnerRewards;
// ─── Konstruktor für builtin Events ────────────────────────────────────
// ── Renn-Einstellungen ────────────────────────────────────────────────
/** Sprint (A→B) oder Circuit (Rundkurs) */
private RaceMode raceMode = RaceMode.SPRINT;
/** Anzahl der Runden bei CIRCUIT-Modus */
private int laps = 1;
// ─── Konstruktor für builtin Events ───────────────────────────────────
public EventDefinition(EventType type) {
this.id = type.name().toLowerCase();
this.type = type;
@@ -62,14 +57,12 @@ public class EventDefinition {
this.announcement = ColorUtil.color("&6✦ &e" + displayName + " &6startet jetzt!");
this.inRotation = true;
this.weight = 1;
this.startRegion = null;
this.goalRegion = null;
this.customSettings = new HashMap<>();
this.isCustom = false;
this.winnerRewards = new ArrayList<>();
}
// ─── Konstruktor für Custom Events ────────────────────────────────────
// ─── Konstruktor für Custom Events ────────────────────────────────────
public EventDefinition(String id) {
this.id = id;
this.type = EventType.CUSTOM;
@@ -85,14 +78,12 @@ public class EventDefinition {
this.announcement = ColorUtil.color("&6✦ &e" + id + " &6startet jetzt!");
this.inRotation = true;
this.weight = 1;
this.startRegion = null;
this.goalRegion = null;
this.customSettings = new HashMap<>();
this.isCustom = true;
this.winnerRewards = new ArrayList<>();
}
// ─── Laden aus ConfigurationSection ───────────────────────────────────
// ─── Laden aus ConfigurationSection ───────────────────────────────────
public static EventDefinition fromConfig(String id, ConfigurationSection sec) {
EventDefinition def = new EventDefinition(id);
def.displayName = sec.getString("display-name", id);
@@ -108,6 +99,13 @@ public class EventDefinition {
def.inRotation = sec.getBoolean("in-rotation", true);
def.weight = sec.getInt("weight", 1);
def.winnerRewards = sec.getStringList("winner-rewards");
// Renn-Einstellungen
String raceModeStr = sec.getString("race-mode", "SPRINT").toUpperCase();
try { def.raceMode = RaceMode.valueOf(raceModeStr); }
catch (Exception e) { def.raceMode = RaceMode.SPRINT; }
def.laps = Math.max(1, sec.getInt("laps", 1));
String catStr = sec.getString("category", "CUSTOM");
try { def.category = EventCategory.valueOf(catStr); }
catch (Exception e) { def.category = EventCategory.CUSTOM; }
@@ -119,49 +117,37 @@ public class EventDefinition {
}
}
// Region laden
// Region
if (sec.contains("region.world")) {
try {
String w = sec.getString("region.world");
int minX = sec.getInt("region.min-x");
int minY = sec.getInt("region.min-y");
int minZ = sec.getInt("region.min-z");
int maxX = sec.getInt("region.max-x");
int maxY = sec.getInt("region.max-y");
int maxZ = sec.getInt("region.max-z");
def.region = new EventRegion(w, minX, minY, minZ, maxX, maxY, maxZ);
def.region = new EventRegion(
sec.getString("region.world"),
sec.getInt("region.min-x"), sec.getInt("region.min-y"), sec.getInt("region.min-z"),
sec.getInt("region.max-x"), sec.getInt("region.max-y"), sec.getInt("region.max-z"));
} catch (Exception ignored) {}
}
// Start-/Zielregion laden
// Start-Region
if (sec.contains("start-region.world")) {
try {
String w = sec.getString("start-region.world");
int minX = sec.getInt("start-region.min-x");
int minY = sec.getInt("start-region.min-y");
int minZ = sec.getInt("start-region.min-z");
int maxX = sec.getInt("start-region.max-x");
int maxY = sec.getInt("start-region.max-y");
int maxZ = sec.getInt("start-region.max-z");
def.startRegion = new EventRegion(w, minX, minY, minZ, maxX, maxY, maxZ);
def.startRegion = new EventRegion(
sec.getString("start-region.world"),
sec.getInt("start-region.min-x"), sec.getInt("start-region.min-y"), sec.getInt("start-region.min-z"),
sec.getInt("start-region.max-x"), sec.getInt("start-region.max-y"), sec.getInt("start-region.max-z"));
} catch (Exception ignored) {}
}
// Ziel-Region
if (sec.contains("goal-region.world")) {
try {
String w = sec.getString("goal-region.world");
int minX = sec.getInt("goal-region.min-x");
int minY = sec.getInt("goal-region.min-y");
int minZ = sec.getInt("goal-region.min-z");
int maxX = sec.getInt("goal-region.max-x");
int maxY = sec.getInt("goal-region.max-y");
int maxZ = sec.getInt("goal-region.max-z");
def.goalRegion = new EventRegion(w, minX, minY, minZ, maxX, maxY, maxZ);
def.goalRegion = new EventRegion(
sec.getString("goal-region.world"),
sec.getInt("goal-region.min-x"), sec.getInt("goal-region.min-y"), sec.getInt("goal-region.min-z"),
sec.getInt("goal-region.max-x"), sec.getInt("goal-region.max-y"), sec.getInt("goal-region.max-z"));
} catch (Exception ignored) {}
}
return def;
}
// ─── Speichern in ConfigurationSection ────────────────────────────────
// ─── Speichern in ConfigurationSection ────────────────────────────────
public void saveToConfig(ConfigurationSection sec) {
sec.set("display-name", displayName);
sec.set("description", description);
@@ -176,32 +162,23 @@ public class EventDefinition {
sec.set("weight", weight);
sec.set("winner-rewards", winnerRewards);
sec.set("category", category.name());
sec.set("race-mode", raceMode.name());
sec.set("laps", laps);
if (region != null) {
sec.set("region.world", region.getWorldName());
sec.set("region.min-x", region.getMinX());
sec.set("region.min-y", region.getMinY());
sec.set("region.min-z", region.getMinZ());
sec.set("region.max-x", region.getMaxX());
sec.set("region.max-y", region.getMaxY());
sec.set("region.max-z", region.getMaxZ());
sec.set("region.min-x", region.getMinX()); sec.set("region.min-y", region.getMinY()); sec.set("region.min-z", region.getMinZ());
sec.set("region.max-x", region.getMaxX()); sec.set("region.max-y", region.getMaxY()); sec.set("region.max-z", region.getMaxZ());
}
if (startRegion != null) {
sec.set("start-region.world", startRegion.getWorldName());
sec.set("start-region.min-x", startRegion.getMinX());
sec.set("start-region.min-y", startRegion.getMinY());
sec.set("start-region.min-z", startRegion.getMinZ());
sec.set("start-region.max-x", startRegion.getMaxX());
sec.set("start-region.max-y", startRegion.getMaxY());
sec.set("start-region.max-z", startRegion.getMaxZ());
sec.set("start-region.min-x", startRegion.getMinX()); sec.set("start-region.min-y", startRegion.getMinY()); sec.set("start-region.min-z", startRegion.getMinZ());
sec.set("start-region.max-x", startRegion.getMaxX()); sec.set("start-region.max-y", startRegion.getMaxY()); sec.set("start-region.max-z", startRegion.getMaxZ());
}
if (goalRegion != null) {
sec.set("goal-region.world", goalRegion.getWorldName());
sec.set("goal-region.min-x", goalRegion.getMinX());
sec.set("goal-region.min-y", goalRegion.getMinY());
sec.set("goal-region.min-z", goalRegion.getMinZ());
sec.set("goal-region.max-x", goalRegion.getMaxX());
sec.set("goal-region.max-y", goalRegion.getMaxY());
sec.set("goal-region.max-z", goalRegion.getMaxZ());
sec.set("goal-region.min-x", goalRegion.getMinX()); sec.set("goal-region.min-y", goalRegion.getMinY()); sec.set("goal-region.min-z", goalRegion.getMinZ());
sec.set("goal-region.max-x", goalRegion.getMaxX()); sec.set("goal-region.max-y", goalRegion.getMaxY()); sec.set("goal-region.max-z", goalRegion.getMaxZ());
}
if (!customSettings.isEmpty()) {
for (Map.Entry<String, Object> entry : customSettings.entrySet()) {
@@ -210,7 +187,7 @@ public class EventDefinition {
}
}
// ─── Getter/Setter ─────────────────────────────────────────────────────
// ─── Getter/Setter ─────────────────────────────────────────────────────
public String getId() { return id; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String n) { this.displayName = n; }
@@ -252,5 +229,11 @@ public class EventDefinition {
public boolean hasGoalRegion() { return goalRegion != null; }
public Object getSetting(String key) { return customSettings.get(key); }
public void setSetting(String key, Object val) { customSettings.put(key, val); }
// Renn-Getter/Setter
public RaceMode getRaceMode() { return raceMode; }
public void setRaceMode(RaceMode m) { this.raceMode = m; }
public int getLaps() { return laps; }
public void setLaps(int l) { this.laps = Math.max(1, l); }
public boolean isCircuit() { return raceMode == RaceMode.CIRCUIT; }
}
// Wird unten in der Klasse ergänzt — separate Patch-Datei

View File

@@ -14,8 +14,10 @@ settings:
# Sekunden VOR dem Event-Start, wann die Ankündigung erfolgt
announce-before-seconds: 30
# 3..2..1..Go Countdown vor jedem Event-Start (Sekunden; 0 = deaktiviert)
countdown-seconds: 3
# Intervall-Modus: Startet automatisch alle X Minuten ein Event
# Nützlich wenn kein fester Zeitplan gewünscht ist
use-interval: false
interval-minutes: 60
@@ -31,98 +33,66 @@ settings:
# Leer lassen oder "DAILY" für jeden Tag
# time: Uhrzeit im Format HH:MM (24h)
# event: Event-ID (aus /event list) oder "RANDOM"
#
# ─── Beispiel-Zeitplan ───────────────────────────────────────────
schedule:
# Täglich 12 Uhr: Zufälliges Event
- days: []
time: "12:00"
event: "RANDOM"
# Täglich 18 Uhr: PvP Deathmatch
- days: []
time: "18:00"
event: "pvp_deathmatch"
# Freitag & Samstag Abend: Hunger Games
- days: [FRIDAY, SATURDAY]
time: "20:00"
event: "pvp_hunger_games"
# Samstag Nachmittag: Build Battle
- days: [SATURDAY]
time: "15:00"
event: "build_battle"
# Sonntag Mittag: Drop Party
- days: [SUNDAY]
time: "14:00"
event: "fun_drop_party"
# Montag: Lotterie
- days: [MONDAY]
time: "19:00"
event: "economy_lottery"
# Mittwoch: Team Survival
- days: [WEDNESDAY]
time: "18:00"
event: "team_survival"
# ── Event-Overrides ───────────────────────────────────────────────
# Hier können einzelne eingebaute Events angepasst werden.
# OHNE diese Sektion gelten die Plugin-Defaults.
# Custom Events werden in custom_events.yml gespeichert.
# Einzelne eingebaute Events anpassen.
# Renn-Modus Beispiel:
#
# Beispiel:
# event-overrides:
# pvp_deathmatch:
# race_elytra:
# race-mode: CIRCUIT # SPRINT (A→B) oder CIRCUIT (Rundkurs)
# laps: 3 # Runden im Circuit-Modus
# duration-seconds: 600
# min-players: 4
# rewards:
# - "eco give %player% 500"
# - "give %player% diamond 3"
# fun_drop_party:
# duration-seconds: 120
# announcement: "&6✦ &eDROP PARTY &7startet! Kommt alle auf den Spawn!"
# - "eco give %player% 1000"
#
# race_parkour:
# race-mode: SPRINT
#
# Elytra Checkpoint-Setup (in-game einfacher per Command):
# /event elytra checkpoint add <id> → Pos1
# /event elytra checkpoint confirm <id> → Pos2 → Checkpoint wird gespeichert
# /event elytra checkpoint list <id> → Übersicht
# /event elytra checkpoint clear <id> → Alle löschen
# /event racemode <id> <sprint|circuit> [laps]
event-overrides: {}
# ── Event-Regionen ────────────────────────────────────────────────
# Regionen können direkt hier oder ingame per Command gesetzt werden:
# /event region pos1 <event-id> → Erste Ecke (stehe an der Position)
# /event region pos2 <event-id> → Zweite Ecke → Region wird gespeichert
# /event region clear <event-id> → Region entfernen (gesamte Welt)
# /event region info <event-id> → Region anzeigen
#
# Für Custom Events wird die Region in custom_events.yml gespeichert.
# Für builtin Events hier in event-overrides eintragen:
#
# event-overrides:
# pvp_deathmatch:
# region:
# world: world
# min-x: -50
# min-y: 60
# min-z: -50
# max-x: 50
# max-y: 120
# max-z: 50
#
# ── Block-Schutz während Events ──────────────────────────────────
# TNT und Explosionen zerstören KEINE Blöcke solange ein Event läuft.
# Das gilt immer — unabhängig ob eine Region definiert ist oder nicht.
# Spieler (PvP-Schaden durch Explosionen) bleibt aktiv.
protection:
# Spieler können die Region während eines Events nicht verlassen
enforce-region-boundary: true
# TNT/Explosionen zerstören keine Blöcke während Events
no-explosion-block-damage: true
# Teilnehmer können nur innerhalb der Region Blöcke abbauen/setzen
restrict-block-interaction: true
# ── Item-Regen ──────────────────────────────────────────────────
item-rain:
# Items, die vom Himmel fallen (Material-Namen)
items:
- DIAMOND
- GOLD_INGOT