feat: replace Elytra mode with Invisible Barrier mode
This commit is contained in:
parent
9decc8716f
commit
2773db98cf
19 changed files with 215 additions and 895 deletions
0
gradlew
vendored
Normal file → Executable file
0
gradlew
vendored
Normal file → Executable file
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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<Player, ParkourPlayer> playerResolver;
|
||||
private final FireworkUseHandler handler;
|
||||
|
||||
public ElytraFireworkListener(
|
||||
@NotNull Function<Player, ParkourPlayer> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ElytraRing> rings = new ArrayList<>();
|
||||
private final Map<ParkourPlayer, Integer> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, Long> 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<ElytraRing> 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<ElytraRing> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ElytraRing> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ElytraRing> generateRings(@NotNull Location start, @NotNull Vector direction, int count) {
|
||||
List<ElytraRing> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ElytraRing> rings;
|
||||
private final Map<ParkourPlayer, Integer> playerRingIndex;
|
||||
private final Consumer<ParkourPlayer> 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<ElytraRing> rings,
|
||||
@NotNull Map<ParkourPlayer, Integer> playerRingIndex,
|
||||
@NotNull RingPassHandler onRingPassed,
|
||||
@NotNull Consumer<ParkourPlayer> 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<ParkourPlayer> 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<ElytraRing> rings) {
|
||||
rings.addAll(ringGenerator.generateRings(spawn, new Vector(1, 0, 0), config.getRingLead()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Location> activeBarriers = new HashSet<>();
|
||||
private final Random random = new Random();
|
||||
private final Map<Location, Particle.DustOptions> 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<Map.Entry<Location, Particle.DustOptions>> it = blockColors.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Location, Particle.DustOptions> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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Гонка"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue