Update from Git Manager GUI

This commit is contained in:
2026-02-27 00:44:25 +01:00
parent f74701ecbe
commit e718d06473
20 changed files with 3679 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
package de.fussball.plugin.game;
import de.fussball.plugin.Fussball;
import de.fussball.plugin.utils.MessageUtil;
import org.bukkit.*;
import org.bukkit.entity.*;
import org.bukkit.event.entity.CreatureSpawnEvent;
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;
public class Ball {
private static final String BALL_TEXTURE_URL =
"https://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e";
// Reibung: jeder Tick wird horizontale Geschwindigkeit um diesen Faktor reduziert
private static final double FRICTION = 0.82;
// Unter dieser Geschwindigkeit → Ball stoppt komplett
private static final double MIN_VELOCITY = 0.04;
private final Game game;
private final Fussball plugin;
private ArmorStand entity;
private final Location spawnLocation;
private boolean active = false;
// ── Torwart-Halten ───────────────────────────────────────────────────────
private boolean heldByGoalkeeper = false;
private Player holdingPlayer = null;
public Ball(Game game, Fussball plugin, Location spawnLocation) {
this.game = game;
this.plugin = plugin;
this.spawnLocation = spawnLocation.clone();
}
// ── Config-Helfer ────────────────────────────────────────────────────────
private double cfg(String path, double def) {
return plugin.getConfig().getDouble(path, def);
}
// ── Spawnen ──────────────────────────────────────────────────────────────
public void spawn() {
if (spawnLocation == null || spawnLocation.getWorld() == null) {
plugin.getLogger().severe("[Fussball] Ball-Spawn Location ist null!"); return;
}
if (entity != null && !entity.isDead()) entity.remove();
spawnLocation.getWorld().loadChunk(spawnLocation.getBlockX() >> 4, spawnLocation.getBlockZ() >> 4);
entity = spawnLocation.getWorld().spawn(
spawnLocation, ArmorStand.class, CreatureSpawnEvent.SpawnReason.CUSTOM, false,
stand -> {
stand.setVisible(false);
stand.setGravity(true);
stand.setCollidable(false);
stand.setInvulnerable(true);
stand.setPersistent(false);
stand.setSilent(true);
stand.setSmall(true);
stand.setArms(false);
stand.setBasePlate(false);
stand.setCustomName("§e⚽ Fußball");
stand.setCustomNameVisible(true);
stand.getEquipment().setHelmet(createBallItem());
}
);
if (entity == null) {
plugin.getLogger().severe("[Fussball] ArmorStand konnte nicht gespawnt werden!"); return;
}
active = true;
}
private ItemStack createBallItem() {
ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) skull.getItemMeta();
if (meta == null) return skull;
meta.setDisplayName("§e⚽ Fußball");
try {
PlayerProfile profile = Bukkit.createPlayerProfile(
UUID.nameUUIDFromBytes("FussballBall".getBytes()), "FussballBall");
PlayerTextures textures = profile.getTextures();
textures.setSkin(new URL(BALL_TEXTURE_URL));
profile.setTextures(textures);
meta.setOwnerProfile(profile);
} catch (MalformedURLException e) {
plugin.getLogger().warning("[Fussball] Ball-Textur URL ungültig: " + e.getMessage());
}
skull.setItemMeta(meta);
return skull;
}
// ── Schuss ───────────────────────────────────────────────────────────────
/** Normaler Schuss (Rechtsklick) */
public void kick(Player player) {
if (entity == null || entity.isDead() || !active) return;
Location ballLoc = entity.getLocation();
Vector dir = getKickDirection(player);
double kickVertical = cfg("ball.kick-vertical", 0.3);
double kickPower = cfg("ball.kick-power", 1.1);
double sprintKickPower = cfg("ball.sprint-kick-power", 1.8);
dir.setY(kickVertical);
dir.multiply(player.isSprinting() ? sprintKickPower : kickPower);
applyKick(dir, ballLoc, 1.8f);
}
/**
* Aufgeladener Schuss (Shift gedrückt halten → loslassen)
* power = 0.0 (kurz gehalten) bis 1.0 (voll aufgeladen, ~1.5s)
*/
public void chargedKick(Player player, double power) {
if (entity == null || entity.isDead() || !active) return;
Location ballLoc = entity.getLocation();
Vector dir = getKickDirection(player);
double minPower = cfg("ball.charged-min-power", 1.3);
double maxPower = cfg("ball.charged-max-power", 3.8);
double actualMultiplier = minPower + (maxPower - minPower) * power;
dir.setY(0.25 + power * 0.25); // mehr Loft bei vollem Schuss
dir.multiply(actualMultiplier);
float pitch = 1.0f + (float) (power * 0.8f);
applyKick(dir, ballLoc, pitch);
// Feuer-Partikel ab 70% Ladung
if (power > 0.7) {
ballLoc.getWorld().spawnParticle(Particle.FLAME, ballLoc, 12, 0.2, 0.2, 0.2, 0.06);
}
}
private Vector getKickDirection(Player player) {
Vector dir = entity.getLocation().toVector().subtract(player.getLocation().toVector());
if (dir.lengthSquared() < 0.0001) dir = player.getLocation().getDirection();
dir.normalize();
return dir;
}
private void applyKick(Vector dir, Location ballLoc, float soundPitch) {
entity.setVelocity(dir);
ballLoc.getWorld().playSound(ballLoc, Sound.ENTITY_GENERIC_SMALL_FALL, 1.0f, soundPitch);
ballLoc.getWorld().spawnParticle(Particle.POOF, ballLoc, 5, 0.1, 0.1, 0.1, 0.05);
}
// ── Physik ───────────────────────────────────────────────────────────────
public void applyFriction() {
if (heldByGoalkeeper) return; // Kein Reibung wenn der TW den Ball hält
if (entity == null || entity.isDead() || !active) return;
Vector vel = entity.getVelocity();
double hx = vel.getX() * FRICTION;
double hz = vel.getZ() * FRICTION;
if (Math.abs(hx) < MIN_VELOCITY) hx = 0;
if (Math.abs(hz) < MIN_VELOCITY) hz = 0;
entity.setVelocity(new Vector(hx, vel.getY(), hz));
}
// ── Torwart-Mechanik ─────────────────────────────────────────────────────
/**
* Torwart greift den Ball Ball schwebt vor ihm und folgt ihm.
* Prüft vorher, ob er im erlaubten Bereich ist.
*/
public void holdBall(Player goalkeeper) {
if (entity == null || entity.isDead() || !active) return;
// NEUE REGEL: Prüfen, ob Spieler im 2-Block-Radius am Tor ist
if (!game.isAllowedToHoldBall(goalkeeper)) {
goalkeeper.sendMessage(MessageUtil.error("Du kannst den Ball nur innerhalb von 2 Blöcken am Tor halten!"));
return;
}
this.heldByGoalkeeper = true;
this.holdingPlayer = goalkeeper;
entity.setGravity(false);
entity.setVelocity(new Vector(0, 0, 0));
}
/**
* Muss jeden Tick aufgerufen werden damit der Ball dem TW folgt.
*/
public void updateHeldPosition() {
if (!heldByGoalkeeper || holdingPlayer == null || entity == null || entity.isDead()) return;
Location hold = holdingPlayer.getLocation().clone();
hold.add(holdingPlayer.getLocation().getDirection().normalize().multiply(0.8));
hold.setY(hold.getY() + 1.0);
entity.teleport(hold);
entity.setVelocity(new Vector(0, 0, 0));
}
/**
* Torwart lässt den Ball los ohne zu werfen (z.B. fallen lassen).
*/
public void releaseBall() {
this.heldByGoalkeeper = false;
this.holdingPlayer = null;
if (entity != null && !entity.isDead()) {
entity.setGravity(true);
}
}
/**
* Torwart wirft den Ball mit gegebener Stärke (0.5 2.5).
*/
public void throwBall(Player goalkeeper, double power) {
releaseBall();
if (entity == null || entity.isDead()) return;
Vector dir = goalkeeper.getLocation().getDirection();
dir.setY(dir.getY() + 0.25);
dir.multiply(power);
entity.setVelocity(dir);
Location loc = entity.getLocation();
loc.getWorld().playSound(loc, Sound.ENTITY_SNOWBALL_THROW, 1f, 1.1f);
loc.getWorld().spawnParticle(Particle.POOF, loc, 5, 0.1, 0.1, 0.1, 0.04);
}
public boolean isHeld() { return heldByGoalkeeper; }
public Player getHoldingPlayer() { return holdingPlayer; }
// ── Util ─────────────────────────────────────────────────────────────────
public void returnToCenter() {
if (entity != null && !entity.isDead()) {
entity.teleport(spawnLocation);
entity.setVelocity(new Vector(0, 0, 0));
}
}
public void remove() {
if (entity != null && !entity.isDead()) entity.remove();
entity = null;
active = false;
}
public ArmorStand getEntity() { return entity; }
public boolean isActive() { return active; }
public Location getSpawnLocation() { return spawnLocation; }
public double getDistanceTo(Player player) {
if (entity == null || entity.isDead()) return Double.MAX_VALUE;
if (!entity.getWorld().equals(player.getWorld())) return Double.MAX_VALUE;
return entity.getLocation().distance(player.getLocation());
}
}