From 2773db98cfc53c6642d6c23043a3d7d06a4f12d7 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 6 Jul 2026 11:24:09 +0200 Subject: [PATCH] feat: replace Elytra mode with Invisible Barrier mode --- gradlew | 0 .../loparkour/bootstrap/PluginBootstrap.java | 4 +- .../dev/loki/loparkour/mode/base/Modes.java | 4 +- .../loparkour/mode/elytra/ElytraConfig.java | 58 ----- .../mode/elytra/ElytraFireworkListener.java | 49 ----- .../mode/elytra/ElytraGenerator.java | 121 ----------- .../loki/loparkour/mode/elytra/ElytraHud.java | 29 --- .../loparkour/mode/elytra/ElytraPhysics.java | 137 ------------ .../mode/elytra/ElytraPlayerKit.java | 53 ----- .../loparkour/mode/elytra/ElytraRenderer.java | 58 ----- .../loparkour/mode/elytra/ElytraRing.java | 77 ------- .../mode/elytra/ElytraRingGenerator.java | 87 -------- .../loparkour/mode/elytra/ElytraRingLoop.java | 87 -------- .../loparkour/mode/elytra/ElytraScoring.java | 35 --- .../loki/loparkour/mode/impl/ElytraMode.java | 57 ----- .../mode/impl/InvisibleBarrierMode.java | 199 ++++++++++++++++++ src/main/resources/config.yml | 15 +- src/main/resources/locales/en.yml | 20 +- src/main/resources/locales/ru.yml | 20 +- 19 files changed, 215 insertions(+), 895 deletions(-) mode change 100644 => 100755 gradlew delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraConfig.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraFireworkListener.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraGenerator.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraHud.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraPhysics.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraPlayerKit.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraRenderer.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraRing.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingGenerator.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingLoop.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/elytra/ElytraScoring.java delete mode 100644 src/main/java/dev/loki/loparkour/mode/impl/ElytraMode.java create mode 100644 src/main/java/dev/loki/loparkour/mode/impl/InvisibleBarrierMode.java diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java index 310190b..b037a29 100644 --- a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java +++ b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java @@ -15,8 +15,8 @@ import dev.loki.loparkour.listener.player.PlayerConnectionListener; import dev.loki.loparkour.mode.base.Modes; import dev.loki.loparkour.mode.impl.CoopMode; import dev.loki.loparkour.mode.impl.DefaultMode; -import dev.loki.loparkour.mode.impl.ElytraMode; import dev.loki.loparkour.mode.impl.GravityShiftMode; +import dev.loki.loparkour.mode.impl.InvisibleBarrierMode; import dev.loki.loparkour.mode.impl.RaceMode; import dev.loki.loparkour.mode.impl.SpectatorMode; import dev.loki.loparkour.mode.impl.SpeedrunMode; @@ -71,8 +71,8 @@ public final class PluginBootstrap { Registry.register(new DefaultMode()); Registry.register(new SpectatorMode()); Registry.register(new SpeedrunMode()); - Registry.register(new ElytraMode()); Registry.register(new RaceMode()); + Registry.register(new InvisibleBarrierMode()); Registry.register(new CoopMode()); Registry.register(new GravityShiftMode()); Modes.init(); diff --git a/src/main/java/dev/loki/loparkour/mode/base/Modes.java b/src/main/java/dev/loki/loparkour/mode/base/Modes.java index f5494db..8e0330b 100644 --- a/src/main/java/dev/loki/loparkour/mode/base/Modes.java +++ b/src/main/java/dev/loki/loparkour/mode/base/Modes.java @@ -8,7 +8,7 @@ public class Modes { public static DefaultMode DEFAULT; public static SpectatorMode SPECTATOR; public static SpeedrunMode SPEEDRUN; - public static ElytraMode ELYTRA; + public static InvisibleBarrierMode INVISIBLE_BARRIER; public static RaceMode RACE; public static CoopMode COOP; public static GravityShiftMode GRAVITY_SHIFT; @@ -17,7 +17,7 @@ public class Modes { DEFAULT = (DefaultMode) Registry.getMode("default"); SPECTATOR = (SpectatorMode) Registry.getMode("spectator"); SPEEDRUN = (SpeedrunMode) Registry.getMode("speedrun"); - ELYTRA = (ElytraMode) Registry.getMode("elytra"); + INVISIBLE_BARRIER = (InvisibleBarrierMode) Registry.getMode("invisible-barrier"); RACE = (RaceMode) Registry.getMode("race"); COOP = (CoopMode) Registry.getMode("coop"); GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift"); diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraConfig.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraConfig.java deleted file mode 100644 index 7304df4..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.config.core.Config; - -/** - * Configuration for Elytra mode loaded from config.yml - */ -public class ElytraConfig { - - private final int ringDistanceMin; - private final int ringDistanceMax; - private final int ringSize; - private final int ringLead; - private final int maxHeadingChangeHorizontal; - private final int maxHeadingChangeVertical; - private final int maxHeightAboveSpawn; - private final int maxDeviation; - private final double boostPower; - private final long fireworkCooldownMs; - private final int startingFireworks; - - public ElytraConfig() { - this.ringDistanceMin = Config.CONFIG.isPath("modes.elytra.ring-distance-min") - ? Config.CONFIG.getInt("modes.elytra.ring-distance-min") : 20; - this.ringDistanceMax = Config.CONFIG.isPath("modes.elytra.ring-distance-max") - ? Config.CONFIG.getInt("modes.elytra.ring-distance-max") : 40; - this.ringSize = Config.CONFIG.isPath("modes.elytra.ring-size") - ? Config.CONFIG.getInt("modes.elytra.ring-size") : 5; - this.ringLead = Config.CONFIG.isPath("modes.elytra.ring-lead") - ? Config.CONFIG.getInt("modes.elytra.ring-lead") : 8; - this.maxHeadingChangeHorizontal = Config.CONFIG.isPath("modes.elytra.max-heading-change-horizontal") - ? Config.CONFIG.getInt("modes.elytra.max-heading-change-horizontal") : 30; - this.maxHeadingChangeVertical = Config.CONFIG.isPath("modes.elytra.max-heading-change-vertical") - ? Config.CONFIG.getInt("modes.elytra.max-heading-change-vertical") : 15; - this.maxHeightAboveSpawn = Config.CONFIG.isPath("modes.elytra.max-height-above-spawn") - ? Config.CONFIG.getInt("modes.elytra.max-height-above-spawn") : 60; - this.maxDeviation = Config.CONFIG.isPath("modes.elytra.max-deviation") - ? Config.CONFIG.getInt("modes.elytra.max-deviation") : 25; - this.boostPower = Config.CONFIG.isPath("modes.elytra.boost-power") - ? Config.CONFIG.getDouble("modes.elytra.boost-power") : 1.5; - this.fireworkCooldownMs = Config.CONFIG.isPath("modes.elytra.firework-cooldown-ms") - ? Config.CONFIG.getInt("modes.elytra.firework-cooldown-ms") : 3000; - this.startingFireworks = Config.CONFIG.isPath("modes.elytra.starting-fireworks") - ? Config.CONFIG.getInt("modes.elytra.starting-fireworks") : 64; - } - - public int getRingDistanceMin() { return ringDistanceMin; } - public int getRingDistanceMax() { return ringDistanceMax; } - public int getRingSize() { return ringSize; } - public int getRingLead() { return ringLead; } - public int getMaxHeadingChangeHorizontal() { return maxHeadingChangeHorizontal; } - public int getMaxHeadingChangeVertical() { return maxHeadingChangeVertical; } - public int getMaxHeightAboveSpawn() { return maxHeightAboveSpawn; } - public int getMaxDeviation() { return maxDeviation; } - public double getBoostPower() { return boostPower; } - public long getFireworkCooldownMs() { return fireworkCooldownMs; } - public int getStartingFireworks() { return startingFireworks; } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraFireworkListener.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraFireworkListener.java deleted file mode 100644 index 165eda4..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraFireworkListener.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.player.core.ParkourPlayer; -import org.bukkit.Material; -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.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import java.util.function.Function; - -/** - * Handles firework rocket use while gliding in elytra parkour. - */ -public final class ElytraFireworkListener implements Listener { - - @FunctionalInterface - public interface FireworkUseHandler { - /** @return true if the rocket was consumed */ - boolean onFirework(@NotNull ParkourPlayer parkourPlayer, @NotNull Player player); - } - - private final Function playerResolver; - private final FireworkUseHandler handler; - - public ElytraFireworkListener( - @NotNull Function playerResolver, - @NotNull FireworkUseHandler handler) { - this.playerResolver = playerResolver; - this.handler = handler; - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPlayerInteract(@NotNull PlayerInteractEvent event) { - Player player = event.getPlayer(); - ParkourPlayer pp = playerResolver.apply(player); - if (pp == null) { - return; - } - - ItemStack item = event.getItem(); - if (item != null && item.getType() == Material.FIREWORK_ROCKET && handler.onFirework(pp, player)) { - item.setAmount(item.getAmount() - 1); - event.setCancelled(true); - } - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraGenerator.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraGenerator.java deleted file mode 100644 index 6a23ca3..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraGenerator.java +++ /dev/null @@ -1,121 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.LoParkour; -import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; -import dev.loki.loparkour.mode.elytra.*; -import dev.loki.loparkour.player.core.ParkourPlayer; -import dev.loki.loparkour.session.core.Session; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; -import org.bukkit.scheduler.BukkitTask; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Elytra mode generator — coordinates ring loop, rendering, and player kit. - */ -public class ElytraGenerator extends ParkourGenerator { - - private final ElytraConfig config; - private final ElytraRingGenerator ringGenerator; - private final ElytraPhysics physics; - private final ElytraRenderer renderer; - private final ElytraRingLoop ringLoop; - private final ElytraFireworkListener fireworkListener; - - private final List rings = new ArrayList<>(); - private final Map playerRingIndex = new HashMap<>(); - - private BukkitTask renderTask; - - public ElytraGenerator(@NotNull Session session) { - super(session); - - this.config = new ElytraConfig(); - this.ringGenerator = new ElytraRingGenerator(config); - this.physics = new ElytraPhysics(config); - this.renderer = new ElytraRenderer(); - this.ringLoop = new ElytraRingLoop( - config, ringGenerator, physics, rings, playerRingIndex, - (player, ring) -> ElytraScoring.onRingPassed(this, player, ring, physics), - p -> fall()); - this.fireworkListener = new ElytraFireworkListener( - this::findParkourPlayer, - (pp, p) -> { - if (physics.applyFireworkBoost(pp)) { - ElytraPlayerKit.spawnBoostFirework(p); - return true; - } - return false; - }); - - Bukkit.getPluginManager().registerEvents(fireworkListener, LoParkour.getPlugin()); - } - - @Override - public void generateFirst(Location spawn, Location blockSpawn) { - ElytraRingLoop.spawnInitialRings(spawn, config, ringGenerator, rings); - - for (ParkourPlayer player : getPlayers()) { - playerRingIndex.put(player, 0); - ElytraPlayerKit.giveElytraAndFireworks(player, config); - ElytraPlayerKit.launch(player, spawn); - } - - startRenderTask(); - } - - @Override - public void tick() { - super.tick(); - ringLoop.tickPlayers(getPlayers(), this); - ringLoop.ensureRingBuffer(); - } - - @Override - public void reset(boolean regenerate) { - cleanup(); - super.reset(regenerate); - } - - private void startRenderTask() { - renderTask = Bukkit.getScheduler().runTaskTimer(LoParkour.getPlugin(), () -> { - for (ParkourPlayer player : getPlayers()) { - int currentIndex = playerRingIndex.getOrDefault(player, 0); - renderer.renderRings(rings, currentIndex); - } - }, 0L, 2L); - } - - private void cleanup() { - if (renderTask != null) { - renderTask.cancel(); - renderTask = null; - } - - HandlerList.unregisterAll(fireworkListener); - - rings.forEach(ElytraRing::remove); - rings.clear(); - playerRingIndex.clear(); - - for (ParkourPlayer player : getPlayers()) { - physics.cleanup(player.player.getUniqueId()); - } - } - - @Nullable - private ParkourPlayer findParkourPlayer(@NotNull Player player) { - return getPlayers().stream() - .filter(pp -> pp.player.equals(player)) - .findFirst() - .orElse(null); - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraHud.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraHud.java deleted file mode 100644 index 97ab270..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraHud.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.config.locale.Locales; -import dev.loki.loparkour.player.core.ParkourPlayer; -import dev.loki.loparkour.util.text.ColorUtil; -import org.jetbrains.annotations.NotNull; - -/** - * Elytra mode action bar display. - */ -public final class ElytraHud { - - private ElytraHud() { - } - - @SuppressWarnings("deprecation") // Spigot action bar via TextComponent - public static void sendActionBar( - @NotNull ParkourPlayer player, - int ringIndex, - int totalRings, - int score) { - String message = Locales.getString(player.locale, "modes.elytra.action_bar") - .formatted(ringIndex + 1, totalRings, score); - - player.player.spigot().sendMessage( - net.md_5.bungee.api.ChatMessageType.ACTION_BAR, - net.md_5.bungee.api.chat.TextComponent.fromLegacyText(ColorUtil.color(message))); - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraPhysics.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraPhysics.java deleted file mode 100644 index 7529e3a..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraPhysics.java +++ /dev/null @@ -1,137 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.player.core.ParkourPlayer; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * Handles physics checks for Elytra mode (ring crossings, falls, boosts) - */ -public class ElytraPhysics { - - private final ElytraConfig config; - private final Map lastFireworkBoost = new HashMap<>(); - - public ElytraPhysics(@NotNull ElytraConfig config) { - this.config = config; - } - - /** - * Check if player has crossed any new rings - * @return new ring index if crossed, otherwise current index - */ - public int checkRingCrossings(@NotNull ParkourPlayer player, @NotNull List rings, int currentIndex) { - if (currentIndex >= rings.size()) { - return currentIndex; - } - - Location playerLoc = player.player.getLocation(); - - // Check next ring - ElytraRing nextRing = rings.get(currentIndex); - if (nextRing.contains(playerLoc)) { - return currentIndex + 1; - } - - return currentIndex; - } - - /** - * Check if player should fall (too far from course) - */ - @NotNull - public FallCheckResult checkFall(@NotNull ParkourPlayer player, @NotNull List rings, int currentIndex) { - if (rings.isEmpty()) { - return FallCheckResult.noFall(); - } - - Location playerLoc = player.player.getLocation(); - - // Check if player is too far from nearest ring - ElytraRing nearestRing = currentIndex < rings.size() ? rings.get(currentIndex) : rings.get(rings.size() - 1); - double distance = playerLoc.distance(nearestRing.getCenter()); - - if (distance > config.getMaxDeviation()) { - return FallCheckResult.fall("Too far from course"); - } - - // Check if player is gliding - if (!player.player.isGliding()) { - return FallCheckResult.fall("Not gliding"); - } - - return FallCheckResult.noFall(); - } - - /** - * Check if player passed through center of ring (for bonus points) - */ - public boolean isPlayerCentered(@NotNull Location playerLoc, @NotNull ElytraRing ring) { - return ring.isCentered(playerLoc, 0.5); // 50% of radius - } - - /** - * Apply firework boost if cooldown allows - */ - public boolean applyFireworkBoost(@NotNull ParkourPlayer player) { - UUID uuid = player.getUUID(); - long now = System.currentTimeMillis(); - Long lastBoost = lastFireworkBoost.get(uuid); - - if (lastBoost != null && (now - lastBoost) < config.getFireworkCooldownMs()) { - return false; // Still on cooldown - } - - // Apply boost - Player p = player.player; - Vector velocity = p.getVelocity(); - Vector boost = p.getLocation().getDirection().multiply(config.getBoostPower()); - p.setVelocity(velocity.add(boost)); - - lastFireworkBoost.put(uuid, now); - return true; - } - - /** - * Cleanup player data - */ - public void cleanup(@NotNull UUID uuid) { - lastFireworkBoost.remove(uuid); - } - - /** - * Result of fall check - */ - public static class FallCheckResult { - private final boolean shouldFall; - private final String reason; - - private FallCheckResult(boolean shouldFall, String reason) { - this.shouldFall = shouldFall; - this.reason = reason; - } - - public static FallCheckResult fall(String reason) { - return new FallCheckResult(true, reason); - } - - public static FallCheckResult noFall() { - return new FallCheckResult(false, ""); - } - - public boolean shouldFall() { - return shouldFall; - } - - public String getReason() { - return reason; - } - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraPlayerKit.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraPlayerKit.java deleted file mode 100644 index c5f45f4..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraPlayerKit.java +++ /dev/null @@ -1,53 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.player.core.ParkourPlayer; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Firework; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.FireworkMeta; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -/** - * Elytra loadout and launch helpers. - */ -public final class ElytraPlayerKit { - - private ElytraPlayerKit() { - } - - public static void giveElytraAndFireworks(@NotNull ParkourPlayer player, @NotNull ElytraConfig config) { - Player p = player.player; - p.getInventory().setChestplate(new ItemStack(Material.ELYTRA)); - p.getInventory().addItem(makeFirework(config.getStartingFireworks())); - } - - public static void launch(@NotNull ParkourPlayer player, @NotNull Location spawn) { - Player p = player.player; - Location launchPos = spawn.clone().add(0, 10, 0); - p.teleport(launchPos); - p.setGliding(true); - p.setVelocity(new Vector(1, 0, 0).normalize().multiply(1.5)); - } - - public static void spawnBoostFirework(@NotNull Player player) { - Firework firework = player.getWorld().spawn(player.getLocation(), Firework.class); - FireworkMeta meta = firework.getFireworkMeta(); - meta.setPower(1); - firework.setFireworkMeta(meta); - firework.detonate(); - } - - @NotNull - public static ItemStack makeFirework(int amount) { - ItemStack firework = new ItemStack(Material.FIREWORK_ROCKET, amount); - FireworkMeta meta = (FireworkMeta) firework.getItemMeta(); - if (meta != null) { - meta.setPower(1); - firework.setItemMeta(meta); - } - return firework; - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRenderer.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRenderer.java deleted file mode 100644 index c7c4ca8..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRenderer.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import org.bukkit.Location; -import org.bukkit.Particle; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * Renders rings using particles for Elytra mode - */ -public class ElytraRenderer { - - private static final int PARTICLES_PER_RING = 32; - - /** - * Render rings starting from currentIndex - */ - public void renderRings(@NotNull List rings, int currentIndex) { - // Render next few rings ahead of player - int renderCount = Math.min(5, rings.size() - currentIndex); - - for (int i = 0; i < renderCount; i++) { - int index = currentIndex + i; - if (index >= rings.size()) break; - - ElytraRing ring = rings.get(index); - renderRing(ring, i == 0); // Highlight next ring - } - } - - private void renderRing(@NotNull ElytraRing ring, boolean isNext) { - Location center = ring.getCenter(); - int radius = ring.getRadius(); - Vector direction = ring.getDirection(); - - // Calculate perpendicular vectors for ring plane - Vector up = new Vector(0, 1, 0); - Vector right = direction.clone().crossProduct(up).normalize(); - Vector actualUp = right.clone().crossProduct(direction).normalize(); - - // Particle color based on if it's the next ring - Particle particle = isNext ? Particle.VILLAGER_HAPPY : Particle.END_ROD; - - // Draw ring circle - for (int i = 0; i < PARTICLES_PER_RING; i++) { - double angle = 2 * Math.PI * i / PARTICLES_PER_RING; - double x = Math.cos(angle) * radius; - double y = Math.sin(angle) * radius; - - Vector offset = right.clone().multiply(x).add(actualUp.clone().multiply(y)); - Location particleLoc = center.clone().add(offset); - - center.getWorld().spawnParticle(particle, particleLoc, 1, 0, 0, 0, 0); - } - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRing.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRing.java deleted file mode 100644 index 097be16..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRing.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import org.bukkit.Location; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -/** - * Represents a ring that players must fly through in Elytra mode - */ -public class ElytraRing { - - private final Location center; - private final int radius; - private final Vector direction; - private final int index; - - public ElytraRing(@NotNull Location center, int radius, @NotNull Vector direction, int index) { - this.center = center.clone(); - this.radius = radius; - this.direction = direction.clone().normalize(); - this.index = index; - } - - @NotNull - public Location getCenter() { - return center.clone(); - } - - public int getRadius() { - return radius; - } - - @NotNull - public Vector getDirection() { - return direction.clone(); - } - - public int getIndex() { - return index; - } - - /** - * Check if a location is inside this ring - */ - public boolean contains(@NotNull Location location) { - if (!location.getWorld().equals(center.getWorld())) { - return false; - } - - Vector toLocation = location.toVector().subtract(center.toVector()); - double distanceFromCenter = toLocation.length(); - - return distanceFromCenter <= radius; - } - - /** - * Check if a location is centered (within inner radius) in this ring - */ - public boolean isCentered(@NotNull Location location, double innerRadiusPercent) { - if (!location.getWorld().equals(center.getWorld())) { - return false; - } - - Vector toLocation = location.toVector().subtract(center.toVector()); - double distanceFromCenter = toLocation.length(); - double innerRadius = radius * innerRadiusPercent; - - return distanceFromCenter <= innerRadius; - } - - /** - * Remove visual representation (called on cleanup) - */ - public void remove() { - // Particles are temporary, nothing to clean up - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingGenerator.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingGenerator.java deleted file mode 100644 index 7454a63..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingGenerator.java +++ /dev/null @@ -1,87 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import org.bukkit.Location; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * Generates rings along a curved aerial path for Elytra mode - */ -public class ElytraRingGenerator { - - private final ElytraConfig config; - private final Random random = new Random(); - private int ringCounter = 0; - - public ElytraRingGenerator(@NotNull ElytraConfig config) { - this.config = config; - } - - /** - * Generate multiple rings starting from a location in a direction - */ - @NotNull - public List generateRings(@NotNull Location start, @NotNull Vector direction, int count) { - List rings = new ArrayList<>(); - Location current = start.clone(); - Vector currentDir = direction.clone().normalize(); - - for (int i = 0; i < count; i++) { - // Random distance between min and max - int distance = config.getRingDistanceMin() + - random.nextInt(config.getRingDistanceMax() - config.getRingDistanceMin() + 1); - - // Apply random heading changes - currentDir = applyHeadingChange(currentDir); - - // Move to next ring position - current.add(currentDir.clone().multiply(distance)); - - // Clamp height - double spawnY = start.getY(); - double maxY = spawnY + config.getMaxHeightAboveSpawn(); - if (current.getY() > maxY) { - current.setY(maxY); - } - if (current.getY() < spawnY - 20) { - current.setY(spawnY - 20); - } - - ElytraRing ring = new ElytraRing(current, config.getRingSize(), currentDir, ringCounter++); - rings.add(ring); - } - - return rings; - } - - @NotNull - private Vector applyHeadingChange(@NotNull Vector direction) { - // Random horizontal angle change - double horizontalChange = (random.nextDouble() * 2 - 1) * config.getMaxHeadingChangeHorizontal(); - - // Random vertical angle change - double verticalChange = (random.nextDouble() * 2 - 1) * config.getMaxHeadingChangeVertical(); - - // Apply rotation - Vector result = direction.clone(); - - // Horizontal rotation (yaw) - double yaw = Math.toRadians(horizontalChange); - double cos = Math.cos(yaw); - double sin = Math.sin(yaw); - double x = result.getX() * cos - result.getZ() * sin; - double z = result.getX() * sin + result.getZ() * cos; - result.setX(x); - result.setZ(z); - - // Vertical rotation (pitch) - double pitch = Math.toRadians(verticalChange); - result.setY(result.getY() + Math.sin(pitch) * 0.5); - - return result.normalize(); - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingLoop.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingLoop.java deleted file mode 100644 index 6b11e73..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraRingLoop.java +++ /dev/null @@ -1,87 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; -import dev.loki.loparkour.player.core.ParkourPlayer; -import org.bukkit.Location; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -/** - * Per-tick ring crossing checks and ahead-of-player ring generation. - */ -public final class ElytraRingLoop { - - private final ElytraConfig config; - private final ElytraRingGenerator ringGenerator; - private final ElytraPhysics physics; - private final List rings; - private final Map playerRingIndex; - private final Consumer onFall; - private final RingPassHandler onRingPassed; - - @FunctionalInterface - public interface RingPassHandler { - void onPassed(@NotNull ParkourPlayer player, @NotNull ElytraRing ring); - } - - public ElytraRingLoop( - @NotNull ElytraConfig config, - @NotNull ElytraRingGenerator ringGenerator, - @NotNull ElytraPhysics physics, - @NotNull List rings, - @NotNull Map playerRingIndex, - @NotNull RingPassHandler onRingPassed, - @NotNull Consumer onFall) { - this.config = config; - this.ringGenerator = ringGenerator; - this.physics = physics; - this.rings = rings; - this.playerRingIndex = playerRingIndex; - this.onRingPassed = onRingPassed; - this.onFall = onFall; - } - - public void tickPlayers(@NotNull Iterable players, @NotNull ParkourGenerator generator) { - for (ParkourPlayer player : players) { - int currentIndex = playerRingIndex.getOrDefault(player, 0); - - int newIndex = physics.checkRingCrossings(player, rings, currentIndex); - if (newIndex > currentIndex) { - onRingPassed.onPassed(player, rings.get(newIndex - 1)); - playerRingIndex.put(player, newIndex); - } - - ElytraPhysics.FallCheckResult fallResult = physics.checkFall(player, rings, currentIndex); - if (fallResult.shouldFall()) { - ElytraScoring.notifyFall(player, fallResult.getReason()); - onFall.accept(player); - } - - int displayIndex = playerRingIndex.getOrDefault(player, 0); - ElytraHud.sendActionBar(player, displayIndex, rings.size(), generator.state.score); - } - } - - public void ensureRingBuffer() { - if (rings.isEmpty()) { - return; - } - if (rings.size() < config.getRingLead() * 2) { - ElytraRing lastRing = rings.get(rings.size() - 1); - Vector direction = new Vector(1, 0, 0); - rings.addAll(ringGenerator.generateRings(lastRing.getCenter(), direction, config.getRingLead())); - } - } - - public static void spawnInitialRings( - @NotNull Location spawn, - @NotNull ElytraConfig config, - @NotNull ElytraRingGenerator ringGenerator, - @NotNull List rings) { - rings.addAll(ringGenerator.generateRings(spawn, new Vector(1, 0, 0), config.getRingLead())); - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraScoring.java b/src/main/java/dev/loki/loparkour/mode/elytra/ElytraScoring.java deleted file mode 100644 index 1768247..0000000 --- a/src/main/java/dev/loki/loparkour/mode/elytra/ElytraScoring.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.loki.loparkour.mode.elytra; - -import dev.loki.loparkour.config.locale.Locales; -import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; -import dev.loki.loparkour.player.core.ParkourPlayer; -import org.bukkit.Sound; -import org.jetbrains.annotations.NotNull; - -/** - * Ring pass feedback and fall messages for elytra mode. - */ -public final class ElytraScoring { - - private ElytraScoring() { - } - - public static void onRingPassed( - @NotNull ParkourGenerator generator, - @NotNull ParkourPlayer player, - @NotNull ElytraRing ring, - @NotNull ElytraPhysics physics) { - boolean centered = physics.isPlayerCentered(player.player.getLocation(), ring); - int points = centered ? 10 : 5; - - generator.state.score += points; - player.player.playSound(player.player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f); - - String key = centered ? "modes.elytra.ring_perfect" : "modes.elytra.ring_good"; - Locales.send(player.player, key, points); - } - - public static void notifyFall(@NotNull ParkourPlayer player, @NotNull String reason) { - Locales.send(player.player, "modes.elytra.fell", reason); - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/impl/ElytraMode.java b/src/main/java/dev/loki/loparkour/mode/impl/ElytraMode.java deleted file mode 100644 index a84dcea..0000000 --- a/src/main/java/dev/loki/loparkour/mode/impl/ElytraMode.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.loki.loparkour.mode.impl; -import dev.loki.loparkour.mode.elytra.ElytraGenerator; -import dev.loki.loparkour.mode.base.ModeMessages; -import dev.loki.loparkour.mode.base.Mode; - -import dev.loki.loparkour.config.core.Config; -import dev.loki.loparkour.config.locale.Locales; -import dev.loki.loparkour.leaderboard.core.Leaderboard; -import dev.loki.loparkour.player.core.ParkourPlayer; -import dev.loki.loparkour.session.core.Session; -import dev.loki.loparkour.util.item.Item; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Elytra Parkour Mode. - * Players fly through vertical rings generated ahead along a curved aerial path. - * No blocks on the ground — pure flight mechanics. - */ -public class ElytraMode implements Mode { - - private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); - - @Override - @NotNull - public String getName() { - return "elytra"; - } - - @Override - @Nullable - public Item getItem(String locale) { - return Locales.getItem(locale, "modes.elytra"); - } - - @Override - @NotNull - public Leaderboard getLeaderboard() { - return leaderboard; - } - - @Override - public void create(Player player) { - if (!ModeMessages.checkJoiningEnabled(player)) { - return; - } - - ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof ElytraGenerator) { - return; - } - - player.closeInventory(); - Session.create(session -> new ElytraGenerator(session), null, null, player); - } -} diff --git a/src/main/java/dev/loki/loparkour/mode/impl/InvisibleBarrierMode.java b/src/main/java/dev/loki/loparkour/mode/impl/InvisibleBarrierMode.java new file mode 100644 index 0000000..862447b --- /dev/null +++ b/src/main/java/dev/loki/loparkour/mode/impl/InvisibleBarrierMode.java @@ -0,0 +1,199 @@ +package dev.loki.loparkour.mode.impl; + +import dev.loki.loparkour.config.locale.Locales; +import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; +import dev.loki.loparkour.leaderboard.core.Leaderboard; +import dev.loki.loparkour.mode.base.Mode; +import dev.loki.loparkour.mode.base.ModeMessages; +import dev.loki.loparkour.mode.base.Modes; +import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.session.core.Session; +import dev.loki.loparkour.util.item.Item; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +public class InvisibleBarrierMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "invisible-barrier"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "play.single.invisible-barrier"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!ModeMessages.checkJoiningEnabled(player)) { + return; + } + + ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator instanceof BarrierGenerator) { + return; + } + + player.closeInventory(); + Session.create(session -> new BarrierGenerator(session), null, null, player); + } + + static class BarrierGenerator extends ParkourGenerator { + + private static final double RENDER_DISTANCE = 24.0; + private static final double PARTICLE_STEP = 0.1; + + private final Set activeBarriers = new HashSet<>(); + private final Random random = new Random(); + private final Map blockColors = new HashMap<>(); + private int particleTick = 0; + + public BarrierGenerator(@NotNull Session session) { + super(session); + } + + @Override + public void generateFirst(@NotNull Location spawn, @NotNull Location block) { + super.generateFirst(spawn, block); + convertLastBlockToBarrier(); + } + + @Override + public void generate(int amount) { + super.generate(amount); + int start = Math.max(0, state.history.size() - amount); + for (int i = start; i < state.history.size(); i++) { + convertBlockToBarrier(state.history.get(i)); + } + } + + @Override + public void tick() { + super.tick(); + particleTick++; + if (particleTick % 4 == 0) { + renderOutlines(); + } + } + + @Override + public void reset(boolean regenerate) { + activeBarriers.clear(); + blockColors.clear(); + particleTick = 0; + super.reset(regenerate); + } + + @Override + public Mode getMode() { + return Modes.INVISIBLE_BARRIER; + } + + private void convertLastBlockToBarrier() { + if (state.history.isEmpty()) return; + convertBlockToBarrier(state.history.get(state.history.size() - 1)); + } + + private void convertBlockToBarrier(@NotNull Block block) { + Location loc = block.getLocation(); + if (block.getType() == Material.BARRIER) return; + block.setType(Material.BARRIER, false); + activeBarriers.add(loc); + Color color = Color.fromRGB(random.nextInt(256), random.nextInt(256), random.nextInt(256)); + blockColors.put(loc, new Particle.DustOptions(color, 1.0f)); + } + + private void renderOutlines() { + if (activeBarriers.isEmpty()) return; + + // Self-clean invalid entries + Iterator> it = blockColors.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + if (entry.getKey().getBlock().getType() != Material.BARRIER) { + it.remove(); + activeBarriers.remove(entry.getKey()); + } + } + + for (ParkourPlayer pp : getPlayers()) { + Player player = pp.player; + Location playerLoc = player.getLocation(); + + for (Location loc : activeBarriers) { + if (loc.getWorld() != playerLoc.getWorld()) continue; + if (loc.distanceSquared(playerLoc) > RENDER_DISTANCE * RENDER_DISTANCE) continue; + + Particle.DustOptions options = blockColors.get(loc); + if (options == null) continue; + + drawBlockOutline(player, loc, options); + } + } + } + + private void drawBlockOutline(@NotNull Player player, @NotNull Location loc, @NotNull Particle.DustOptions options) { + double x = loc.getX(); + double y = loc.getY(); + double z = loc.getZ(); + + // Bottom edges (y offset = 0) + drawEdge(player, x, y, z, x + 1, y, z, options); + drawEdge(player, x + 1, y, z, x + 1, y, z + 1, options); + drawEdge(player, x + 1, y, z + 1, x, y, z + 1, options); + drawEdge(player, x, y, z + 1, x, y, z, options); + + // Top edges (y offset = 1) + drawEdge(player, x, y + 1, z, x + 1, y + 1, z, options); + drawEdge(player, x + 1, y + 1, z, x + 1, y + 1, z + 1, options); + drawEdge(player, x + 1, y + 1, z + 1, x, y + 1, z + 1, options); + drawEdge(player, x, y + 1, z + 1, x, y + 1, z, options); + + // Vertical edges + drawEdge(player, x, y, z, x, y + 1, z, options); + drawEdge(player, x + 1, y, z, x + 1, y + 1, z, options); + drawEdge(player, x + 1, y, z + 1, x + 1, y + 1, z + 1, options); + drawEdge(player, x, y, z + 1, x, y + 1, z + 1, options); + } + + private void drawEdge(@NotNull Player player, double x1, double y1, double z1, double x2, double y2, double z2, @NotNull Particle.DustOptions options) { + double dx = x2 - x1; + double dy = y2 - y1; + double dz = z2 - z1; + double length = Math.sqrt(dx * dx + dy * dy + dz * dz); + int steps = Math.max(1, (int) (length / PARTICLE_STEP)); + + for (int i = 0; i <= steps; i++) { + double t = (double) i / steps; + double px = x1 + dx * t; + double py = y1 + dy * t; + double pz = z1 + dz * t; + player.spawnParticle(Particle.REDSTONE, px, py, pz, 1, options); + } + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 954f2ac..d138d5e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -208,19 +208,8 @@ modes: amplifier: 1 duration: 5 - elytra: - enabled: false - ring-distance-min: 20 - ring-distance-max: 40 - ring-size: 5 - ring-lead: 8 - max-heading-change-horizontal: 30 - max-heading-change-vertical: 15 - max-height-above-spawn: 60 - max-deviation: 25 - boost-power: 1.5 - firework-cooldown-ms: 3000 - starting-fireworks: 64 + invisible-barrier: + enabled: true race: enabled: true diff --git a/src/main/resources/locales/en.yml b/src/main/resources/locales/en.yml index 53a4372..6a2a716 100644 --- a/src/main/resources/locales/en.yml +++ b/src/main/resources/locales/en.yml @@ -40,6 +40,11 @@ play: name: "�D4FF&lGravity Shift" lore: "񢩈Random potion effects||񢩈every few jumps" + invisible-barrier: + material: "barrier" + name: "&#FF44FF&lInvisible Barrier" + lore: "񢩈Invisible blocks with||񢩈colorful particle outlines" + spectator: name: "&#FFFFFFSpectator" @@ -284,22 +289,7 @@ other: name: "�D4FF&lClose" lore: "" - iep: - material: "elytra" - name: "�D4FF&lFlying" - lore: "&#A0A0A0Parkour with elytra!" - modes: - elytra: - material: "elytra" - name: "�D4FF&lElytra" - lore: "&#A0A0A0Fly through rings!||&#A0A0A0Use fireworks to boost" - start_hint: "&#A0A0A0Fly through the rings! Use fireworks to boost." - ring_perfect: "7FF55Perfect! +%d" - ring_good: "7FF55Good! +%d" - fell: "&#FF4444Fell: %s" - action_bar: "&#FFFF55Ring: %d/%d &#A0A0A0| 7FF55Score: &#FFFFFF%d" - race: material: "golden_boots" name: "&#FFD700&lRace" diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index db4071b..b7222b1 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -39,6 +39,11 @@ play: name: "�D4FF&lГравитационный сдвиг" lore: "񢩈Случайные эффекты зелий||񢩈каждые несколько прыжков" + invisible-barrier: + material: "barrier" + name: "&#FF44FF&lНевидимый барьер" + lore: "񢩈Невидимые блоки с||񢩈цветными контурами из частиц" + spectator: name: "&#FFFFFFНаблюдатель" @@ -283,22 +288,7 @@ other: name: "�D4FF&lЗакрыть" lore: "" - iep: - material: "elytra" - name: "�D4FF&lПолёт" - lore: "&#A0A0A0Паркур с элитрами!" - modes: - elytra: - material: "elytra" - name: "�D4FF&lЭлитры" - lore: "&#A0A0A0Летайте через кольца!||&#A0A0A0Используйте фейерверки для ускорения" - start_hint: "&#A0A0A0Летите через кольца! Используйте фейерверки для ускорения." - ring_perfect: "7FF55Идеально! +%d" - ring_good: "7FF55Хорошо! +%d" - fell: "&#FF4444Падение: %s" - action_bar: "&#FFFF55Кольцо: %d/%d &#A0A0A0| 7FF55Счёт: &#FFFFFF%d" - race: material: "golden_boots" name: "&#FFD700&lГонка"