diff --git a/src/main/java/de/nexuslobby/modules/portal/Portal.java b/src/main/java/de/nexuslobby/modules/portal/Portal.java new file mode 100644 index 0000000..4e52fa3 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/portal/Portal.java @@ -0,0 +1,41 @@ +package de.nexuslobby.modules.portal; + +import org.bukkit.Location; +import org.bukkit.Particle; + +public class Portal { + private final String name; + private final String type; // "SERVER" oder "WORLD" + private Location pos1; + private Location pos2; + private String destination; // Server Name oder "World;X;Y;Z;Yaw;Pitch" + private Particle particle; + + // NEU: Return Spawn Location, damit Spieler beim Serverwechsel nicht direkt wieder Portal auslösen + private Location returnSpawn; + + public Portal(String name, String type) { + this.name = name; + this.type = type; + this.particle = Particle.PORTAL; // Standard Partikel + } + + public String getName() { return name; } + public String getType() { return type; } + + public Location getPos1() { return pos1; } + public void setPos1(Location pos1) { this.pos1 = pos1; } + + public Location getPos2() { return pos2; } + public void setPos2(Location pos2) { this.pos2 = pos2; } + + public String getDestination() { return destination; } + public void setDestination(String destination) { this.destination = destination; } + + public Particle getParticle() { return particle; } + public void setParticle(Particle particle) { this.particle = particle; } + + // --- Getter & Setter für ReturnSpawn --- + public Location getReturnSpawn() { return returnSpawn; } + public void setReturnSpawn(Location returnSpawn) { this.returnSpawn = returnSpawn; } +} diff --git a/src/main/java/de/nexuslobby/modules/portal/PortalCommand.java b/src/main/java/de/nexuslobby/modules/portal/PortalCommand.java new file mode 100644 index 0000000..9cbb1a7 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/portal/PortalCommand.java @@ -0,0 +1,154 @@ +package de.nexuslobby.modules.portal; + +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class PortalCommand implements CommandExecutor { + + private final PortalManager portalManager; + + public PortalCommand(PortalManager portalManager) { + this.portalManager = portalManager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Nur Spieler können diesen Befehl nutzen."); + return false; + } + + Player p = (Player) sender; + + // Wenn keine Argumente da sind, Hilfe zeigen + if (args.length == 0) { + sendHelp(p); + return true; + } + + // Switch case für die Unterbefehle + switch (args[0].toLowerCase()) { + case "create": + if (args.length < 3) { + p.sendMessage("§cBenutzung: /portal create "); + return true; + } + String type = args[2].toUpperCase(); + if (!type.equals("SERVER") && !type.equals("WORLD")) { + p.sendMessage("§cTyp muss 'server' oder 'world' sein!"); + return true; + } + portalManager.createPortal(args[1], type, p); + break; + + case "setpos1": + if (args.length < 2) { p.sendMessage("§cBenutzung: /portal setpos1 "); return true; } + if (portalManager.setPortalPos(args[1], 1, p.getLocation())) { + p.sendMessage("§aPosition 1 gesetzt für " + args[1]); + } else { + p.sendMessage("§cPortal nicht gefunden."); + } + break; + + case "setpos2": + if (args.length < 2) { p.sendMessage("§cBenutzung: /portal setpos2 "); return true; } + if (portalManager.setPortalPos(args[1], 2, p.getLocation())) { + p.sendMessage("§aPosition 2 gesetzt für " + args[1]); + } else { + p.sendMessage("§cPortal nicht gefunden."); + } + break; + + case "setdest": + if (args.length < 3) { + p.sendMessage("§cBenutzung: /portal setdest "); + p.sendMessage("§7Server: ServerName"); + p.sendMessage("§7Welt: Weltname;X;Y;Z;Yaw;Pitch"); + return true; + } + String dest = args[2]; + // Falls Koordinaten getrennt mit Leerzeichen gegeben werden (z.B. /portal setdest name world 100 64 100) + if (args.length > 3) dest += ";" + args[3]; + if (args.length > 4) dest += ";" + args[4]; + if (args.length > 5) dest += ";" + args[5]; + if (args.length > 6) dest += ";" + args[6]; + + if (portalManager.setDestination(args[1], dest)) { + p.sendMessage("§aZiel gesetzt für " + args[1] + ": " + dest); + } else { + p.sendMessage("§cPortal nicht gefunden."); + } + break; + + case "setparticle": + if (args.length < 3) { + p.sendMessage("§cBenutzung: /portal setparticle "); + return true; + } + if (portalManager.setParticle(args[1], args[2])) { + p.sendMessage("§aPartikel für " + args[1] + " auf " + args[2].toUpperCase() + " gesetzt."); + } else { + p.sendMessage("§cPortal oder Partikel '" + args[2] + "' nicht gefunden."); + } + break; + + case "setspawn": + // Neuer Befehl: /portal setspawn + if (args.length < 2) { + p.sendMessage("§cBenutzung: /portal setspawn "); + return true; + } + // Optional: Berechtigungscheck (anpassbar) + if (!p.hasPermission("nexuslobby.portal")) { + p.sendMessage("§cKeine Rechte!"); + return true; + } + + // Wir speichern die aktuelle Position leicht versetzt (2 Blöcke), damit Spieler nicht direkt wieder im Portal landen. + Location spawnLoc = p.getLocation().clone(); + spawnLoc.add(0, 0, 2); // einfacher Offset als default + + if (portalManager.setPortalReturnSpawn(args[1], spawnLoc)) { + p.sendMessage("§aPortal-Spawnpunkt für '" + args[1] + "' gesetzt!"); + } else { + p.sendMessage("§cPortal nicht gefunden."); + } + break; + + case "delete": + if (args.length < 2) { p.sendMessage("§cBenutzung: /portal delete "); return true; } + portalManager.deletePortal(args[1]); + p.sendMessage("§aPortal gelöscht."); + break; + + case "list": + p.sendMessage("§eAlle Portale:"); + for (String name : portalManager.getPortalNames()) { + p.sendMessage("§7- " + name); + } + break; + + default: + p.sendMessage("§cUnbekannter Befehl."); + sendHelp(p); + break; + } + + return true; + } + + private void sendHelp(Player p) { + p.sendMessage("§6--- Portal Hilfe ---"); + p.sendMessage("§e/portal create "); + p.sendMessage("§e/portal setpos1 "); + p.sendMessage("§e/portal setpos2 "); + p.sendMessage("§e/portal setdest "); + p.sendMessage("§e/portal setparticle "); + p.sendMessage("§e/portal setspawn "); + p.sendMessage("§e/portal delete "); + p.sendMessage("§e/portal list"); + } +} diff --git a/src/main/java/de/nexuslobby/modules/portal/PortalManager.java b/src/main/java/de/nexuslobby/modules/portal/PortalManager.java new file mode 100644 index 0000000..a32c3ed --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/portal/PortalManager.java @@ -0,0 +1,459 @@ +package de.nexuslobby.modules.portal; + +import de.nexuslobby.api.Module; +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.scheduler.BukkitTask; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.Set; + +/** + * PortalManager - verwaltet Portale, lädt/speichert sie, spawnt Partikel und teleporiert Spieler. + */ +public class PortalManager implements Module, Listener { + + private final NexusLobby plugin; + private final Map portals = new HashMap<>(); + private final Map cooldowns = new HashMap<>(); + private final Map selectionMap = new HashMap<>(); + private final NamespacedKey wandKey; + private BukkitTask particleTask; + + public PortalManager(NexusLobby plugin) { + this.plugin = plugin; + this.wandKey = new NamespacedKey(plugin, "nexuslobby_portal_wand"); + } + + @Override + public String getName() { + return "PortalManager"; + } + + @Override + public void onEnable() { + loadPortals(); + Bukkit.getPluginManager().registerEvents(this, plugin); + startParticleTask(); + plugin.getLogger().info("PortalManager geladen."); + } + + @Override + public void onDisable() { + if (particleTask != null) { + particleTask.cancel(); + particleTask = null; + } + HandlerList.unregisterAll(this); + plugin.getLogger().info("PortalManager deaktiviert."); + } + + /** + * Gibt alle Namen der aktuell geladenen Portale zurück. + * Wird vom LobbyTabCompleter genutzt. + */ + public Set getPortalNames() { + return portals.keySet(); + } + + // --- Wand / Selection --- + @org.bukkit.event.EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getAction() == Action.PHYSICAL) return; + + ItemStack item = event.getItem(); + if (item == null || !item.hasItemMeta()) return; + + ItemMeta meta = item.getItemMeta(); + if (meta == null) return; + + if (!meta.getPersistentDataContainer().has(wandKey, PersistentDataType.BYTE)) return; + + Player p = event.getPlayer(); + if (!p.hasPermission("nexuslobby.portal")) { + p.sendMessage("§cDu hast keine Berechtigung, das Portal-Werkzeug zu benutzen."); + return; + } + + event.setCancelled(true); + + if (!event.hasBlock()) { + p.sendMessage("§cDu musst auf einen Block klicken!"); + return; + } + + Block clicked = event.getClickedBlock(); + if (clicked == null) return; + Location clickedLoc = clicked.getLocation(); + + UUID uuid = p.getUniqueId(); + selectionMap.putIfAbsent(uuid, new Location[2]); + + if (event.getAction() == Action.LEFT_CLICK_BLOCK) { + selectionMap.get(uuid)[0] = clickedLoc; + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f); + p.sendMessage("§aPosition 1 gesetzt: " + clickedLoc.getBlockX() + ", " + clickedLoc.getBlockY() + ", " + clickedLoc.getBlockZ()); + } else if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { + selectionMap.get(uuid)[1] = clickedLoc; + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.0f); + p.sendMessage("§bPosition 2 gesetzt: " + clickedLoc.getBlockX() + ", " + clickedLoc.getBlockY() + ", " + clickedLoc.getBlockZ()); + + if (selectionMap.get(uuid)[0] != null) { + p.sendMessage("§eBenutze jetzt: /portal create "); + } + } + } + + // --- Load / Save --- + private YamlConfiguration loadPortalConfig() { + java.io.File file = new java.io.File(plugin.getDataFolder(), "portals.yml"); + if (!file.exists()) { + try { + plugin.getDataFolder().mkdirs(); + file.createNewFile(); + } catch (IOException e) { + plugin.getLogger().severe("Konnte portals.yml nicht anlegen: " + e.getMessage()); + } + } + return YamlConfiguration.loadConfiguration(file); + } + + /** + * Lädt alle Portale aus der Konfiguration. + * PUBLIC für den Zugriff durch NexusLobby.java beim Reload. + */ + public void loadPortals() { + // Liste leeren, um Duplikate beim Reload zu vermeiden + portals.clear(); + + YamlConfiguration portalConfig = loadPortalConfig(); + ConfigurationSection section = portalConfig.getConfigurationSection("portals"); + if (section == null) return; + + for (String key : section.getKeys(false)) { + String type = portalConfig.getString("portals." + key + ".type", "WORLD"); + Portal portal = new Portal(key, type); + + if (portalConfig.contains("portals." + key + ".pos1")) { + portal.setPos1(portalConfig.getLocation("portals." + key + ".pos1")); + } + if (portalConfig.contains("portals." + key + ".pos2")) { + portal.setPos2(portalConfig.getLocation("portals." + key + ".pos2")); + } + + portal.setDestination(portalConfig.getString("portals." + key + ".destination", "")); + try { + portal.setParticle(Particle.valueOf(portalConfig.getString("portals." + key + ".particle", "PORTAL"))); + } catch (IllegalArgumentException ignored) { + portal.setParticle(Particle.PORTAL); + } + + if (portalConfig.contains("portals." + key + ".returnSpawn")) { + portal.setReturnSpawn(portalConfig.getLocation("portals." + key + ".returnSpawn")); + } + + portals.put(key, portal); + } + } + + public void savePortals() { + YamlConfiguration config = loadPortalConfig(); + config.set("portals", null); + + for (Portal portal : portals.values()) { + String path = "portals." + portal.getName() + "."; + config.set(path + "type", portal.getType()); + config.set(path + "pos1", portal.getPos1()); + config.set(path + "pos2", portal.getPos2()); + config.set(path + "destination", portal.getDestination()); + config.set(path + "particle", portal.getParticle() != null ? portal.getParticle().name() : "PORTAL"); + config.set(path + "returnSpawn", portal.getReturnSpawn()); + } + + try { + config.save(new java.io.File(plugin.getDataFolder(), "portals.yml")); + } catch (IOException e) { + plugin.getLogger().severe("Konnte portals.yml nicht speichern!"); + e.printStackTrace(); + } + } + + // --- CRUD --- + public boolean createPortal(String name, String type, Player creator) { + Location[] sel = selectionMap.getOrDefault(creator.getUniqueId(), new Location[2]); + if (sel[0] != null && sel[1] != null) { + Portal portal = new Portal(name, type); + portal.setPos1(sel[0]); + portal.setPos2(sel[1]); + portals.put(name, portal); + savePortals(); + selectionMap.remove(creator.getUniqueId()); + creator.sendMessage("§aPortal '" + name + "' erstellt!"); + return true; + } + creator.sendMessage("§cBitte markiere erst zwei Punkte mit dem Portal-Werkzeug!"); + return false; + } + + public boolean deletePortal(String name) { + if (portals.remove(name) != null) { + savePortals(); + return true; + } + return false; + } + + public boolean setPortalPos(String name, int pos, Location loc) { + Portal portal = portals.get(name); + if (portal == null) return false; + if (pos == 1) portal.setPos1(loc); + else portal.setPos2(loc); + savePortals(); + return true; + } + + public boolean setDestination(String name, String dest) { + Portal portal = portals.get(name); + if (portal == null) return false; + portal.setDestination(dest); + savePortals(); + return true; + } + + public boolean setParticle(String name, String particleName) { + Portal portal = portals.get(name); + if (portal == null) return false; + try { + portal.setParticle(Particle.valueOf(particleName.toUpperCase())); + savePortals(); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + public boolean setPortalReturnSpawn(String name, Location loc) { + Portal portal = portals.get(name); + if (portal == null) return false; + portal.setReturnSpawn(loc); + savePortals(); + return true; + } + + // --- Particles --- + private void startParticleTask() { + particleTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + for (Portal portal : portals.values()) { + if (portal.getPos1() == null || portal.getPos2() == null) continue; + spawnParticles(portal); + } + }, 0L, 5L); + } + + private void spawnParticles(Portal portal) { + Location min = getMinLocation(portal.getPos1(), portal.getPos2()); + Location max = getMaxLocation(portal.getPos1(), portal.getPos2()); + World world = portal.getPos1().getWorld(); + if (world == null) return; + + for (int i = 0; i < 30; i++) { + double x = min.getX() + 0.5 + (Math.random() * (max.getX() - min.getX())); + double y = min.getY() + 0.5 + (Math.random() * (max.getY() - min.getY())); + double z = min.getZ() + 0.5 + (Math.random() * (max.getZ() - min.getZ())); + world.spawnParticle(portal.getParticle(), new Location(world, x, y, z), 3, 0.1, 0.1, 0.1, 0); + } + } + + // --- Teleport / Movement --- + @org.bukkit.event.EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + if (event.getFrom().getX() == event.getTo().getX() && + event.getFrom().getY() == event.getTo().getY() && + event.getFrom().getZ() == event.getTo().getZ()) { + return; + } + + Player player = event.getPlayer(); + Location loc = player.getLocation(); + + for (Portal portal : portals.values()) { + if (portal.getPos1() == null || portal.getPos2() == null) continue; + if (!isInArea(loc, portal.getPos1(), portal.getPos2())) continue; + + if (cooldowns.containsKey(player.getUniqueId()) && + cooldowns.get(player.getUniqueId()) > System.currentTimeMillis()) { + continue; + } + + executeTeleport(player, portal); + cooldowns.put(player.getUniqueId(), System.currentTimeMillis() + 2000); + break; + } + } + + private void executeTeleport(Player player, Portal portal) { + if ("SERVER".equalsIgnoreCase(portal.getType())) { + String serverName = portal.getDestination(); + player.sendMessage("§eVerbinde zum Server: " + serverName); + plugin.getLogger().info("Verbinde " + player.getName() + " -> " + serverName); + + Location loc = portal.getReturnSpawn(); + if (loc == null) { + Location pos2 = portal.getPos2(); + loc = player.getLocation(); + if (pos2 != null) { + double dx = loc.getX() - pos2.getX(); + double dz = loc.getZ() - pos2.getZ(); + double length = Math.sqrt(dx*dx + dz*dz); + if (length == 0) length = 1; + dx = (dx / length) * 2; + dz = (dz / length) * 2; + loc.add(dx, 0, dz); + } else { + loc.add(0,0,2); + } + } + + player.teleport(loc); + connectToServer(player, serverName); + return; + } + + String dest = portal.getDestination(); + if (dest == null || dest.isEmpty()) { + player.sendMessage("§cDieses Portal hat kein Ziel gesetzt!"); + return; + } + + if ("SPAWN".equalsIgnoreCase(dest)) { + Location spawnLoc = getMainSpawnLocation(); + if (spawnLoc != null) { + player.teleport(spawnLoc); + player.sendMessage("§aDu wurdest zum Spawn teleportiert!"); + } else { + player.sendMessage("§cSpawn konnte nicht gefunden werden."); + } + return; + } + + String[] parts = dest.split(";"); + if (parts.length >= 4) { + World world = Bukkit.getWorld(parts[0]); + if (world == null) { + player.sendMessage("§cZielwelt nicht gefunden: " + parts[0]); + return; + } + try { + double x = Double.parseDouble(parts[1]); + double y = Double.parseDouble(parts[2]); + double z = Double.parseDouble(parts[3]); + float yaw = 0f, pitch = 0f; + if (parts.length >= 6) { + yaw = Float.parseFloat(parts[4]); + pitch = Float.parseFloat(parts[5]); + } + Location target = new Location(world, x, y, z, yaw, pitch); + player.teleport(target); + player.sendMessage("§aDu wurdest teleportiert!"); + } catch (NumberFormatException e) { + player.sendMessage("§cUngültige Koordinaten im Portalziel!"); + } + } else { + player.sendMessage("§cUngültiges Portalzielformat!"); + } + } + + private void connectToServer(Player player, String serverName) { + try { + if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) { + plugin.getLogger().warning("BungeeCord outgoing channel not registered; cannot send plugin message."); + player.sendMessage("§cProxy-Verbindung nicht möglich: BungeeCord-Kanal nicht registriert."); + return; + } + + ByteArrayOutputStream b = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(b); + out.writeUTF("Connect"); + out.writeUTF(serverName); + player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Senden der BungeeCord-Message: " + e.getMessage()); + e.printStackTrace(); + player.sendMessage("§cFehler beim Verbinden zum Proxy."); + } catch (RuntimeException e) { + plugin.getLogger().warning("Konnte Plugin-Message nicht senden: " + e.getMessage()); + player.sendMessage("§cProxy-Verbindung nicht möglich."); + } + } + + // --- Hilfsfunktionen --- + private Location getMainSpawnLocation() { + String worldName = plugin.getConfig().getString("spawn.world", null); + if (worldName != null) { + World w = Bukkit.getWorld(worldName); + if (w != null) { + double x = plugin.getConfig().getDouble("spawn.x", w.getSpawnLocation().getX()); + double y = plugin.getConfig().getDouble("spawn.y", w.getSpawnLocation().getY()); + double z = plugin.getConfig().getDouble("spawn.z", w.getSpawnLocation().getZ()); + float yaw = (float) plugin.getConfig().getDouble("spawn.yaw", w.getSpawnLocation().getYaw()); + float pitch = (float) plugin.getConfig().getDouble("spawn.pitch", w.getSpawnLocation().getPitch()); + return new Location(w, x, y, z, yaw, pitch); + } + } + if (!Bukkit.getWorlds().isEmpty()) { + return Bukkit.getWorlds().get(0).getSpawnLocation(); + } + return null; + } + + private boolean isInArea(Location loc, Location loc1, Location loc2) { + if (loc == null || loc1 == null || loc2 == null) return false; + if (!loc.getWorld().equals(loc1.getWorld())) return false; + + int x = loc.getBlockX(); + int y = loc.getBlockY(); + int z = loc.getBlockZ(); + int headY = y + 1; + + Location min = getMinLocation(loc1, loc2); + Location max = getMaxLocation(loc1, loc2); + + boolean xMatch = (x >= min.getBlockX()) && (x <= max.getBlockX()); + boolean zMatch = (z >= min.getBlockZ()) && (z <= max.getBlockZ()); + boolean yMatch = (y >= min.getBlockY()) && (headY <= max.getBlockY()); + + return xMatch && yMatch && zMatch; + } + + private Location getMinLocation(Location a, Location b) { + return new Location(a.getWorld(), Math.min(a.getX(), b.getX()), + Math.min(a.getY(), b.getY()), Math.min(a.getZ(), b.getZ())); + } + + private Location getMaxLocation(Location a, Location b) { + return new Location(a.getWorld(), Math.max(a.getX(), b.getX()), + Math.max(a.getY(), b.getY()), Math.max(a.getZ(), b.getZ())); + } +} \ No newline at end of file