Update from Git Manager GUI

This commit is contained in:
2026-02-01 22:19:40 +01:00
parent ce43cac14f
commit 6a892c45db
9 changed files with 570 additions and 42 deletions

View File

@@ -0,0 +1,312 @@
package de.nexuslobby.modules.ball;
import de.nexuslobby.NexusLobby;
import de.nexuslobby.api.Module;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures;
import org.bukkit.util.Vector;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import java.util.Objects;
public class SoccerModule implements Module, Listener, CommandExecutor {
private ArmorStand ball;
private Location spawnLocation;
private long lastMoveTime;
private final String TEXTURE_URL = "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e";
private final String BALL_TAG = "nexusball_entity"; // Eindeutiges Tag zur Identifizierung
private final String BALL_NAME = "§x§N§e§x§u§s§B§a§l§l"; // Zusätzliche Erkennung
@Override
public String getName() { return "Soccer"; }
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance());
if (NexusLobby.getInstance().getCommand("nexuslobby") != null) {
Objects.requireNonNull(NexusLobby.getInstance().getCommand("nexuslobby")).setExecutor(this);
}
loadConfigLocation();
// AGGRESSIVES MEHRFACHES CLEANUP-SYSTEM
// 1. Sofort beim Enable
removeAllOldBallsGlobal();
// 2. Nach 0.5 Sekunden
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 10L);
// 3. Nach 1 Sekunde
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 20L);
// 4. Nach 2 Sekunden - cleanup und dann spawnen
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> {
removeAllOldBallsGlobal();
spawnBall();
}, 40L);
// 5. Nach 3 Sekunden - finales Cleanup für hartnäckige Duplikate
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 60L);
// 6. Nach 5 Sekunden - letzter Check
Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 100L);
// Haupt-Physik & Anti-Klon Tick
Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> {
// ANTI-KLON-SYSTEM: Prüfe die Umgebung des Spawns auf illegale Kopien
if (spawnLocation != null && spawnLocation.getWorld() != null) {
for (Entity entity : spawnLocation.getWorld().getNearbyEntities(spawnLocation, 50, 50, 50)) {
if (entity instanceof ArmorStand stand) {
// Wenn der ArmorStand unser Tag hat, aber nicht unsere aktive Instanz ist -> Löschen
if (stand.getScoreboardTags().contains(BALL_TAG)) {
if (ball == null || !stand.getUniqueId().equals(ball.getUniqueId())) {
stand.remove();
}
}
}
}
}
if (ball == null || !ball.isValid()) return;
Vector vel = ball.getVelocity();
double speed = vel.length();
handleWallBounce(vel);
handleParticles(speed);
// Dribbel-Logik
for (Entity nearby : ball.getNearbyEntities(0.7, 0.5, 0.7)) {
if (nearby instanceof Player p) {
Vector direction = ball.getLocation().toVector().subtract(p.getLocation().toVector());
if (direction.lengthSquared() > 0) {
direction.normalize();
direction.setY(0.12);
ball.setVelocity(direction.multiply(0.35));
}
lastMoveTime = System.currentTimeMillis();
}
}
// Automatischer Respawn bei Inaktivität oder Void
long delay = NexusLobby.getInstance().getConfig().getLong("ball.respawn_delay", 60) * 1000;
if (System.currentTimeMillis() - lastMoveTime > delay || ball.getLocation().getY() < -5) {
respawnBall();
}
}, 1L, 1L);
}
/**
* Scannt ALLE Welten nach Entities mit dem BALL_TAG und entfernt sie.
* Nutzt mehrere Erkennungsmethoden für maximale Sicherheit.
*/
private void removeAllOldBallsGlobal() {
int removed = 0;
// Alle Welten durchsuchen
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof ArmorStand stand) {
boolean shouldRemove = false;
// Methode 1: Tag-basiert
if (stand.getScoreboardTags().contains(BALL_TAG)) {
shouldRemove = true;
}
// Methode 2: Name-basiert (falls Tags verloren gehen)
if (stand.getCustomName() != null && stand.getCustomName().equals(BALL_NAME)) {
shouldRemove = true;
}
// Methode 3: Kopf-Textur-basiert (prüfe ob es ein Soccer-Ball Kopf ist)
if (stand.getEquipment() != null && stand.getEquipment().getHelmet() != null) {
ItemStack helmet = stand.getEquipment().getHelmet();
if (helmet.getType() == Material.PLAYER_HEAD && helmet.hasItemMeta()) {
SkullMeta meta = (SkullMeta) helmet.getItemMeta();
if (meta.hasOwner() && meta.getOwnerProfile() != null) {
PlayerProfile profile = meta.getOwnerProfile();
if (profile.getName() != null && profile.getName().equals("SoccerBall")) {
shouldRemove = true;
}
}
}
}
// Methode 4: Position-basiert - Entferne alle unsichtbaren, kleinen ArmorStands in der Nähe des Spawns
if (spawnLocation != null && spawnLocation.getWorld() != null &&
stand.getWorld().equals(spawnLocation.getWorld()) &&
stand.isSmall() && stand.isInvisible() && !stand.hasBasePlate()) {
double distance = stand.getLocation().distance(spawnLocation);
// Wenn innerhalb von 5 Blöcken vom Spawn und hat einen Kopf
if (distance < 5.0 && stand.getEquipment() != null &&
stand.getEquipment().getHelmet() != null &&
stand.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD) {
shouldRemove = true;
}
}
// Nur entfernen wenn es NICHT unsere aktuelle Ball-Instanz ist
if (shouldRemove && (ball == null || !stand.getUniqueId().equals(ball.getUniqueId()))) {
stand.remove();
removed++;
}
}
}
}
if (removed > 0) {
Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt.");
}
}
private void handleWallBounce(Vector vel) {
if (vel.lengthSquared() < 0.001) return;
Location loc = ball.getLocation();
Block nextX = loc.clone().add(vel.getX() * 1.3, 0.5, 0).getBlock();
Block nextZ = loc.clone().add(0, 0.5, vel.getZ() * 1.3).getBlock();
boolean bounced = false;
if (nextX.getType().isSolid()) { vel.setX(-vel.getX() * 0.75); bounced = true; }
if (nextZ.getType().isSolid()) { vel.setZ(-vel.getZ() * 0.75); bounced = true; }
if (bounced) {
ball.setVelocity(vel);
ball.getWorld().playSound(ball.getLocation(), Sound.BLOCK_WOOD_BREAK, 0.6f, 1.3f);
}
}
private void handleParticles(double speed) {
if (speed < 0.05) return;
Location loc = ball.getLocation().add(0, 0.2, 0);
World world = loc.getWorld();
if (world == null) return;
if (speed > 0.85) world.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0);
else if (speed > 0.45) world.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08);
else world.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02);
}
private void spawnBall() {
if (spawnLocation == null || (ball != null && ball.isValid())) return;
// Ball direkt auf dem Boden spawnen (keine Y-Offset Erhöhung)
Location spawnLoc = spawnLocation.clone();
ball = (ArmorStand) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.ARMOR_STAND);
ball.setInvisible(true);
ball.setGravity(true);
ball.setBasePlate(false);
ball.setSmall(true);
ball.setInvulnerable(false);
ball.setArms(false);
ball.setCustomNameVisible(false);
// Setze Custom Name für zusätzliche Erkennung (unsichtbar für Spieler)
ball.setCustomName(BALL_NAME);
// Deaktiviert das Speichern in der Welt-Datei
ball.setPersistent(false);
// Markiert den Ball für das Cleanup-System
ball.addScoreboardTag(BALL_TAG);
ItemStack ballHead = getSoccerHead();
if (ball.getEquipment() != null) ball.getEquipment().setHelmet(ballHead);
lastMoveTime = System.currentTimeMillis();
}
private ItemStack getSoccerHead() {
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) head.getItemMeta();
if (meta == null) return head;
PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), "SoccerBall");
try {
profile.getTextures().setSkin(new URL(TEXTURE_URL));
} catch (MalformedURLException ignored) {}
meta.setOwnerProfile(profile);
head.setItemMeta(meta);
return head;
}
public void respawnBall() {
if (ball != null) {
ball.remove();
ball = null;
}
removeAllOldBallsGlobal();
spawnBall();
}
@EventHandler
public void onBallPunch(EntityDamageByEntityEvent event) {
if (event.getEntity().equals(ball)) {
event.setCancelled(true);
if (event.getDamager() instanceof Player p) {
Vector shootDir = p.getLocation().getDirection();
if (shootDir.lengthSquared() > 0) {
shootDir.normalize().multiply(1.35).setY(0.38);
ball.setVelocity(shootDir);
}
ball.getWorld().playSound(ball.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.6f, 1.5f);
lastMoveTime = System.currentTimeMillis();
}
}
}
@EventHandler
public void onBallInteract(PlayerInteractAtEntityEvent event) {
if (event.getRightClicked().equals(ball)) event.setCancelled(true);
}
private void loadConfigLocation() {
FileConfiguration config = NexusLobby.getInstance().getConfig();
if (config.contains("ball.spawn")) spawnLocation = config.getLocation("ball.spawn");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player p) || !p.hasPermission("nexuslobby.admin")) return true;
if (args.length >= 2 && args[0].equalsIgnoreCase("ball")) {
if (args[1].equalsIgnoreCase("setspawn")) {
spawnLocation = p.getLocation();
NexusLobby.getInstance().getConfig().set("ball.spawn", spawnLocation);
NexusLobby.getInstance().saveConfig();
respawnBall();
p.sendMessage("§8[§6Nexus§8] §aBall-Spawn gesetzt. Cleanup ist aktiv!");
return true;
} else if (args[1].equalsIgnoreCase("respawn")) {
respawnBall();
p.sendMessage("§8[§6Nexus§8] §eBall manuell respawnt.");
return true;
}
}
return false;
}
@Override
public void onDisable() {
if (ball != null) ball.remove();
}
}