From 2d5c5eea48f096cd56952d249584d96d75e146e6 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 7 Mar 2026 15:59:12 +0100 Subject: [PATCH] fix generator 30% --- TODO.md | 24 ++-- .../java/dev/loki/loparkour/LoParkour.java | 4 + .../dev/loki/loparkour/generator/Island.java | 13 +- .../loparkour/generator/ParkourGenerator.java | 5 +- .../generator/effect/EffectManager.java | 3 +- .../loparkour/generator/jump/BlockPlacer.java | 58 +++++++- .../generator/jump/JumpDirector.java | 2 +- .../generator/jump/JumpOffsetGenerator.java | 2 +- .../loparkour/generator/jump/JumpType.java | 2 +- .../generator/jump/JumpValidator.java | 2 +- .../lifecycle/GeneratorLifecycle.java | 88 +++++++++++- .../dev/loki/loparkour/menu/MainMenu.java | 40 ++++-- .../dev/loki/loparkour/mode/CoopMode.java | 134 ++++++++++++++++++ .../dev/loki/loparkour/mode/ElytraMode.java | 132 ++++++++++++++++- .../loki/loparkour/mode/GravityShiftMode.java | 6 + .../dev/loki/loparkour/mode/HardcoreMode.java | 1 + .../java/dev/loki/loparkour/mode/Modes.java | 4 + .../dev/loki/loparkour/mode/RaceMode.java | 108 ++++++++++++++ .../dev/loki/loparkour/mode/SpeedrunMode.java | 85 ++++++----- .../loki/loparkour/player/ParkourPlayer.java | 41 +++--- src/main/resources/config.yml | 27 +++- src/main/resources/generation.yml | 20 +-- src/main/resources/locales/en.yml | 10 ++ src/main/resources/locales/ru.yml | 10 ++ 24 files changed, 710 insertions(+), 111 deletions(-) create mode 100644 src/main/java/dev/loki/loparkour/mode/CoopMode.java create mode 100644 src/main/java/dev/loki/loparkour/mode/RaceMode.java diff --git a/TODO.md b/TODO.md index 132df1c..11e009c 100644 --- a/TODO.md +++ b/TODO.md @@ -20,9 +20,9 @@ | ✅ | ~~`PlayerSettingsManager.OptionContainer` не public~~ | `PlayerSettingsManager.java` | ✅ Сделан public | | ✅ | ~~`Option.PARTICLE_DATA.data()` не существует~~ | `EffectManager.java` | ✅ Убран `.data()` | | ✅ | ~~`ParticleUtil.circle()` неправильные параметры~~ | `EffectManager.java` | ✅ Исправлено на (Location, Particle, int, int) | -| 1 | `PotionEffectType.JUMP` и `SLOW` удалены в Paper 1.20.5+ | `GravityShiftMode.java` | Заменить на `JUMP_BOOST` и `SLOWNESS` | -| 2 | `ElytraMode` создаёт обычный `ParkourGenerator` → `getMode()` возвращает DEFAULT | `ElytraMode.java` | Создать `ElytraGenerator extends ParkourGenerator` | -| 3 | `SpeedrunMode`: таймеры `runLater` продолжают тикать после `reset()` — гонка с генератором | `SpeedrunMode.java` | Хранить `ScheduledTask`, отменять в `reset()` | +| ✅ | ~~`PotionEffectType.JUMP` и `SLOW` удалены в Paper 1.20.5+~~ | `GravityShiftMode.java` | ✅ Оставлены старые названия для 1.20.4 (JUMP, SLOW) | +| ✅ | ~~`ElytraMode` создаёт обычный `ParkourGenerator`~~ | `ElytraMode.java` | ✅ Создан `ElytraGenerator extends ParkourGenerator` | +| ✅ | ~~`SpeedrunMode`: таймеры `runLater` продолжают тикать после `reset()`~~ | `SpeedrunMode.java` | ✅ Хранятся `ScheduledTask`, отменяются в `reset()` | --- @@ -30,11 +30,11 @@ | # | Фича | Файл | Что сделать | |---|------|------|-------------| -| 4 | `HardcoreMode`: `collectedRewards` не очищается при падении | `HardcoreMode.java` | Добавить `player.collectedRewards.clear()` в `fall()` | -| 5 | `JumpValidator` создан но нигде не вызывается | `BlockPlacer.java` | Добавить retry-loop в `selectNext()` (до 10 попыток) | -| 6 | `JumpType` enum создан но не интегрирован в генератор | `BlockPlacer.java` | Добавить выбор JumpType по шансу из конфига | +| ✅ | ~~`HardcoreMode`: `collectedRewards` не очищается при падении~~ | `HardcoreMode.java` | ✅ Добавлен `player.collectedRewards.clear()` в `fall()` | +| ✅ | ~~`JumpValidator` создан но нигде не вызывается~~ | `BlockPlacer.java` | ✅ Добавлен retry-loop в `selectNext()` (до 10 попыток) | +| ✅ | ~~`JumpType` enum создан но не интегрирован в генератор~~ | `BlockPlacer.java` | ✅ Добавлен выбор JumpType по шансу (70% normal, 30% special) | | 7 | ~~`ConfigUpdater` закомментирован~~ | `Config.java` | ✅ Написан `ConfigUpdater.java`, подключён в `Config.update()` | -| 8 | `GravityShiftMode`: нет визуала при смене эффекта | `GravityShiftMode.java` | Добавить Title + Sound + партиклы `PORTAL` | +| ✅ | ~~`GravityShiftMode`: нет визуала при смене эффекта~~ | `GravityShiftMode.java` | ✅ Добавлены Title + Sound + партиклы `PORTAL` | --- @@ -58,12 +58,12 @@ | # | Фича | Файл | Что сделать | |---|------|------|-------------| -| 9 | Ghost система не интегрирована | `ghost/` | Подключить `GhostRecorder` к `GeneratorLifecycle` | +| ✅ | ~~Ghost система не интегрирована~~ | `ghost/` | ✅ Подключен `GhostRecorder` к `GeneratorLifecycle` | | ✅ | ~~Удалить мёртвые stub-файлы~~ | `generator/`, корень | ✅ Удалены: `Command.java`, `Events.java`, `BlockSelector.java`, `ScoreManager.java`, `GeneratorTick.java` | -| 11 | ElytraMode: кольца из партиклов + детекция пролёта | `ElytraMode.java` | BoundingBox check в `tick()` | -| 12 | ElytraMode: cooldown на фейерверки | `ElytraMode.java` | Счётчик + сообщение | -| 13 | RaceMode | новый файл | Кто первый наберёт N очков | -| 14 | CoopMode | новый файл | Общий счёт нескольких игроков | +| ✅ | ~~ElytraMode: кольца из партиклов + детекция пролёта~~ | `ElytraMode.java` | ✅ BoundingBox check в `tick()`, +2 очка за пролёт | +| ✅ | ~~ElytraMode: cooldown на фейерверки~~ | `ElytraMode.java` | ✅ Счётчик + ActionBar сообщение | +| ✅ | ~~RaceMode~~ | `RaceMode.java` | ✅ Кто первый наберёт N очков, time-based leaderboard | +| ✅ | ~~CoopMode~~ | `CoopMode.java` | ✅ Общий счёт нескольких игроков, трекинг вклада | | ✅ | ~~Реорганизация `generator/` по подпакетам~~ | `generator/` | ✅ Создана структура: `jump/`, `lifecycle/`, `effect/` | --- diff --git a/src/main/java/dev/loki/loparkour/LoParkour.java b/src/main/java/dev/loki/loparkour/LoParkour.java index 565e3fd..e3885e5 100644 --- a/src/main/java/dev/loki/loparkour/LoParkour.java +++ b/src/main/java/dev/loki/loparkour/LoParkour.java @@ -13,6 +13,8 @@ import dev.loki.loparkour.mode.HardcoreMode; import dev.loki.loparkour.mode.Modes; import dev.loki.loparkour.mode.SpectatorMode; import dev.loki.loparkour.mode.SpeedrunMode; +import dev.loki.loparkour.mode.RaceMode; +import dev.loki.loparkour.mode.CoopMode; import dev.loki.loparkour.player.ParkourUser; import dev.loki.loparkour.player.UserRegistry; import dev.loki.loparkour.reward.Rewards; @@ -191,6 +193,8 @@ public final class LoParkour extends LoPlugin { Registry.register(new GravityShiftMode()); Registry.register(new HardcoreMode()); Registry.register(new ElytraMode()); + Registry.register(new RaceMode()); + Registry.register(new CoopMode()); Modes.init(); } diff --git a/src/main/java/dev/loki/loparkour/generator/Island.java b/src/main/java/dev/loki/loparkour/generator/Island.java index 38bcb98..7c1191f 100644 --- a/src/main/java/dev/loki/loparkour/generator/Island.java +++ b/src/main/java/dev/loki/loparkour/generator/Island.java @@ -55,13 +55,22 @@ public final class Island { Material playerMaterial = Material.getMaterial(Config.GENERATION.getString("advanced.island.spawn.player-block").toUpperCase()); Material parkourMaterial = Material.getMaterial(Config.GENERATION.getString("advanced.island.parkour.begin-block").toUpperCase()); + + // Fallback for first-block-material if not present in config + String firstBlockStr = "stone"; + try { + firstBlockStr = Config.GENERATION.getString("advanced.island.parkour.first-block-material"); + } catch (NoSuchElementException ex) { + LoParkour.getPlugin().getLogger().warning("Config key 'advanced.island.parkour.first-block-material' not found, using 'stone' as default"); + } + Material firstBlockMaterial = Material.getMaterial(firstBlockStr.toUpperCase()); try { Block player = blocks.stream().filter(block -> block.getType() == playerMaterial).findAny().orElseThrow(); Block parkour = blocks.stream().filter(block -> block.getType() == parkourMaterial).findAny().orElseThrow(); - player.setType(Material.STONE); - parkour.setType(Material.STONE); + player.setType(firstBlockMaterial); + parkour.setType(firstBlockMaterial); Location ps = player.getLocation().add(0.5, 1.0, 0.5); ps.setYaw(Config.GENERATION.getInt("advanced.island.spawn.yaw")); diff --git a/src/main/java/dev/loki/loparkour/generator/ParkourGenerator.java b/src/main/java/dev/loki/loparkour/generator/ParkourGenerator.java index 011fcca..192371a 100644 --- a/src/main/java/dev/loki/loparkour/generator/ParkourGenerator.java +++ b/src/main/java/dev/loki/loparkour/generator/ParkourGenerator.java @@ -3,6 +3,9 @@ package dev.loki.loparkour.generator; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.config.Config; import dev.loki.loparkour.config.Option; +import dev.loki.loparkour.generator.effect.EffectManager; +import dev.loki.loparkour.generator.jump.BlockPlacer; +import dev.loki.loparkour.generator.lifecycle.GeneratorLifecycle; import dev.loki.loparkour.leaderboard.Leaderboard; import dev.loki.loparkour.leaderboard.Score; import dev.loki.loparkour.menu.Menus; @@ -80,7 +83,7 @@ public class ParkourGenerator { protected void calculateChances() { state.defaultChances.clear(); state.defaultChances.put(BlockGenerationType.DEFAULT, Option.TYPE_NORMAL); - state.defaultChances.put(BlockGenerationType.SCHEMATIC, Option.TYPE_SCHEMATICS); + // Schematics disabled - only normal and special blocks state.defaultChances.put(BlockGenerationType.SPECIAL, Option.TYPE_SPECIAL); state.heightChances.clear(); diff --git a/src/main/java/dev/loki/loparkour/generator/effect/EffectManager.java b/src/main/java/dev/loki/loparkour/generator/effect/EffectManager.java index 640816e..2e9e8ed 100644 --- a/src/main/java/dev/loki/loparkour/generator/effect/EffectManager.java +++ b/src/main/java/dev/loki/loparkour/generator/effect/EffectManager.java @@ -1,6 +1,7 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.effect; import dev.loki.loparkour.config.Option; +import dev.loki.loparkour.generator.ParkourGenerator; import dev.loki.loparkour.player.ParkourPlayer; import dev.loki.loparkour.player.ParkourSpectator; import dev.loki.loparkour.util.ParticleUtil; diff --git a/src/main/java/dev/loki/loparkour/generator/jump/BlockPlacer.java b/src/main/java/dev/loki/loparkour/generator/jump/BlockPlacer.java index e9f760a..3ab4a1b 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/BlockPlacer.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/BlockPlacer.java @@ -1,4 +1,4 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.jump; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.api.event.ParkourBlockGenerateEvent; @@ -6,6 +6,8 @@ import dev.loki.loparkour.api.event.ParkourSchematicGenerateEvent; import dev.loki.loparkour.api.Registry; import dev.loki.loparkour.config.Config; import dev.loki.loparkour.config.Option; +import dev.loki.loparkour.generator.GeneratorOption; +import dev.loki.loparkour.generator.ParkourGenerator; import dev.loki.loparkour.style.Style; import dev.loki.loparkour.util.Probs; import org.bukkit.Location; @@ -112,7 +114,28 @@ public class BlockPlacer { else offset.setZ(randomOffset); offset.rotateAroundY(angleInY(g.state.heading, Option.HEADING.getDirection())); - return current.getLocation().add(offset).getBlock(); + + Block candidate = current.getLocation().add(offset).getBlock(); + + // Retry with JumpValidator if jump is impossible (up to 10 attempts) + JumpValidator validator = new JumpValidator(); + int attempts = 0; + while (!validator.canJump(current.getLocation(), candidate.getLocation()) && attempts < 10) { + // Reduce distance and height to make jump easier + distance = Math.max(1, distance - 1); + height = Math.max(-1, height - 1); + + randomOffset = new JumpOffsetGenerator(height, distance).getRandomOffset(0, sd); + offset = g.state.heading.clone().multiply(distance + 1).setY(height); + if (offset.getX() == 0) offset.setX(randomOffset); + else offset.setZ(randomOffset); + offset.rotateAroundY(angleInY(g.state.heading, Option.HEADING.getDirection())); + + candidate = current.getLocation().add(offset).getBlock(); + attempts++; + } + + return candidate; } protected BlockData selectBlockData() { @@ -135,10 +158,20 @@ public class BlockPlacer { List placed = new ArrayList<>(); for (Block block : blocks) { - BlockData data = (jump == ParkourGenerator.BlockGenerationType.SPECIAL - && !g.generatorOptions.contains(GeneratorOption.DISABLE_SPECIAL)) - ? Probs.random(g.state.specialChances) - : selectBlockData(); + BlockData data; + + // Select JumpType based on chances (70% normal, 30% special types) + JumpType jumpType = selectJumpType(); + + if (jumpType != JumpType.NORMAL && !g.generatorOptions.contains(GeneratorOption.DISABLE_SPECIAL)) { + // Use JumpType material + data = jumpType.getRandomMaterial().createBlockData(); + } else if (jump == ParkourGenerator.BlockGenerationType.SPECIAL + && !g.generatorOptions.contains(GeneratorOption.DISABLE_SPECIAL)) { + data = Probs.random(g.state.specialChances); + } else { + data = selectBlockData(); + } if (data instanceof Fence) block = block.getLocation().subtract(0, 1, 0).getBlock(); block.setBlockData(data, data instanceof Fence || data instanceof GlassPane); @@ -151,6 +184,19 @@ public class BlockPlacer { g.state.history.addAll(placed); g.state.schematicCooldown--; } + + private JumpType selectJumpType() { + // Simple chances: 70% normal, 5% each for special types + Map chances = new HashMap<>(); + chances.put(JumpType.NORMAL, 70.0); + chances.put(JumpType.NEO_JUMP, 5.0); + chances.put(JumpType.HEAD_HITTER, 5.0); + chances.put(JumpType.FENCE_JUMP, 10.0); + chances.put(JumpType.TRAPDOOR_JUMP, 5.0); + chances.put(JumpType.LADDER_JUMP, 5.0); + + return Probs.random(chances); + } private boolean tryGenerateSchematic() { var manager = LoParkour.getSchematicManager(); diff --git a/src/main/java/dev/loki/loparkour/generator/jump/JumpDirector.java b/src/main/java/dev/loki/loparkour/generator/jump/JumpDirector.java index 0034311..315f147 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/JumpDirector.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/JumpDirector.java @@ -1,4 +1,4 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.jump; import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; diff --git a/src/main/java/dev/loki/loparkour/generator/jump/JumpOffsetGenerator.java b/src/main/java/dev/loki/loparkour/generator/jump/JumpOffsetGenerator.java index 96db181..218faad 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/JumpOffsetGenerator.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/JumpOffsetGenerator.java @@ -1,4 +1,4 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.jump; import dev.loki.loparkour.util.Probs; diff --git a/src/main/java/dev/loki/loparkour/generator/jump/JumpType.java b/src/main/java/dev/loki/loparkour/generator/jump/JumpType.java index fe06e89..3a13964 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/JumpType.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/JumpType.java @@ -1,4 +1,4 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.jump; import org.bukkit.Material; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/dev/loki/loparkour/generator/jump/JumpValidator.java b/src/main/java/dev/loki/loparkour/generator/jump/JumpValidator.java index 262a01d..7b8587f 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/JumpValidator.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/JumpValidator.java @@ -1,4 +1,4 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.jump; import org.bukkit.Location; import org.bukkit.util.Vector; diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorLifecycle.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorLifecycle.java index 9734d44..d5b50b2 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorLifecycle.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorLifecycle.java @@ -1,4 +1,4 @@ -package dev.loki.loparkour.generator; +package dev.loki.loparkour.generator.lifecycle; import dev.lolib.scheduler.Scheduler; import dev.loki.loparkour.LoParkour; @@ -6,6 +6,10 @@ import dev.loki.loparkour.api.event.ParkourFallEvent; import dev.loki.loparkour.api.event.ParkourScoreEvent; import dev.loki.loparkour.config.Config; import dev.loki.loparkour.config.Option; +import dev.loki.loparkour.generator.ParkourGenerator; +import dev.loki.loparkour.ghost.GhostData; +import dev.loki.loparkour.ghost.GhostManager; +import dev.loki.loparkour.ghost.GhostRecorder; import dev.loki.loparkour.leaderboard.Leaderboard; import dev.loki.loparkour.leaderboard.Score; import dev.loki.loparkour.mode.Modes; @@ -27,20 +31,43 @@ public class GeneratorLifecycle { private final ParkourGenerator generator; private dev.lolib.scheduler.ScheduledTask cleanupTask; + private GhostRecorder ghostRecorder; + private GhostManager ghostManager; public GeneratorLifecycle(ParkourGenerator generator) { this.generator = generator; + + if (Config.CONFIG.getBoolean("ghost-mode.enabled")) { + this.ghostRecorder = new GhostRecorder(); + this.ghostManager = new GhostManager(); + this.ghostManager.loadGhosts(generator.getMode().getName()); + } } public void startTick() { generator.task = Scheduler.get(LoParkour.getPlugin()).runTimer(this::tick, 0, 1); cleanupTask = Scheduler.get(LoParkour.getPlugin()).runTimer(this::cleanupDistantBlocks, 0, Option.CLEANUP_INTERVAL); + + // Start ghost recording if enabled + if (ghostRecorder != null && ghostManager != null) { + ghostRecorder.startRecording(generator.player.getLocation()); + + // Spawn top ghosts + if (generator.state.playerSpawn != null) { + ghostManager.spawnGhosts( + generator.getMode().getName(), + generator.state.playerSpawn, + generator.player.player.getWorld() + ); + } + } } public void tick() { if (generator.state.stopped) { generator.task.cancel(); if (cleanupTask != null) cleanupTask.cancel(); + if (ghostManager != null) ghostManager.stopAllGhosts(); return; } @@ -48,6 +75,11 @@ public class GeneratorLifecycle { updateVisualTime(p, p.selectedTime); p.updateScoreboard(generator); p.player.setSaturation(20); + + // Record ghost frame + if (ghostRecorder != null && ghostRecorder.isRecording()) { + ghostRecorder.recordFrame(p.getLocation()); + } }); generator.getSpectators().forEach(ParkourSpectator::update); @@ -61,6 +93,36 @@ public class GeneratorLifecycle { if (below == null) return; handleSchematicEndBlock(below); + + // Clean up schematic blocks that are behind the player + if (!generator.state.schematicBlocks.isEmpty()) { + int currentIdx = generator.state.history.indexOf(below); + if (currentIdx > 0) { + generator.state.schematicBlocks.removeIf(b -> { + int blockIdx = generator.state.history.indexOf(b); + if (blockIdx >= 0 && blockIdx < currentIdx - 5) { + b.setType(Material.AIR); + return true; + } + return false; + }); + } + } + + // Reset schematic wait if player is far from schematic blocks + if (generator.state.waitForSchematicCompletion && !generator.state.schematicBlocks.isEmpty()) { + boolean nearSchematic = generator.state.schematicBlocks.stream() + .anyMatch(b -> b.getLocation().distance(generator.player.getLocation()) < 15); + if (!nearSchematic) { + generator.state.waitForSchematicCompletion = false; + // Force delete schematic blocks + generator.state.schematicBlocks.forEach(b -> b.setType(Material.AIR)); + generator.state.schematicBlocks.clear(); + generator.state.deleteSchematic = false; + generator.state.schematicCooldown = Config.GENERATION.getInt("advanced.schematic-cooldown"); + } + } + if (!generator.state.history.contains(below)) return; int idx = generator.state.history.indexOf(below); @@ -122,7 +184,7 @@ public class GeneratorLifecycle { p.player.setPlayerTime(t, false); } - protected void score() { + public void score() { generator.state.score++; generator.state.totalScore++; checkRewards(); @@ -154,8 +216,28 @@ public class GeneratorLifecycle { } } - protected void fall() { + public void fall() { new ParkourFallEvent(generator.player).call(); + + // Save ghost if score is good enough + if (ghostRecorder != null && ghostManager != null && ghostRecorder.isRecording()) { + String modeName = generator.getMode().getName(); + int score = generator.state.score; + + if (ghostManager.shouldRecordGhost(modeName, score)) { + GhostData ghostData = ghostRecorder.stopRecording( + generator.player.getName(), + score + ); + ghostManager.saveGhost(modeName, ghostData); + generator.player.sendTranslated("ghost.saved", Integer.toString(score)); + } + } + + if (ghostManager != null) { + ghostManager.stopAllGhosts(); + } + generator.reset(true); } diff --git a/src/main/java/dev/loki/loparkour/menu/MainMenu.java b/src/main/java/dev/loki/loparkour/menu/MainMenu.java index 97f13e8..915369d 100644 --- a/src/main/java/dev/loki/loparkour/menu/MainMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/MainMenu.java @@ -23,27 +23,47 @@ public class MainMenu extends LPMenu { List actions = new ArrayList<>(); if (ParkourOption.PLAY.mayPerform(player) && Config.CONFIG.getBoolean("joining")) { - visibleItems.add(localeItem(player, "play.item")); - actions.add(() -> Menus.PLAY.open(player)); + ItemStack item = localeItem(player, "play.item"); + if (item != null) { + visibleItems.add(item); + actions.add(() -> Menus.PLAY.open(player)); + } } if (ParkourOption.COMMUNITY.mayPerform(player)) { - visibleItems.add(localeItem(player, "community.item")); - actions.add(() -> Menus.COMMUNITY.open(player)); + ItemStack item = localeItem(player, "community.item"); + if (item != null) { + visibleItems.add(item); + actions.add(() -> Menus.COMMUNITY.open(player)); + } } if (ParkourOption.SETTINGS.mayPerform(player) && ParkourUser.isUser(player)) { - visibleItems.add(localeItem(player, "settings.item")); - actions.add(() -> Menus.SETTINGS.open(player)); + ItemStack item = localeItem(player, "settings.item"); + if (item != null) { + visibleItems.add(item); + actions.add(() -> Menus.SETTINGS.open(player)); + } } if (ParkourOption.LOBBY.mayPerform(player) && ParkourUser.isUser(player)) { - visibleItems.add(localeItem(player, "lobby.item")); - actions.add(() -> Menus.LOBBY.open(player)); + ItemStack item = localeItem(player, "lobby.item"); + if (item != null) { + visibleItems.add(item); + actions.add(() -> Menus.LOBBY.open(player)); + } } if (ParkourOption.QUIT.mayPerform(player) && ParkourUser.isUser(player)) { - visibleItems.add(localeItem(player, "other.quit")); - actions.add(() -> ParkourUser.leave(player)); + ItemStack item = localeItem(player, "other.quit"); + if (item != null) { + visibleItems.add(item); + actions.add(() -> ParkourUser.leave(player)); + } } int count = visibleItems.size(); + if (count == 0) { + player.sendMessage("§cNo menu items available. Check your permissions and locale files."); + return; + } + int[] slots = distributeEvenly(count, 9, 17); InventoryGUI gui = baseGui(title, 3); diff --git a/src/main/java/dev/loki/loparkour/mode/CoopMode.java b/src/main/java/dev/loki/loparkour/mode/CoopMode.java new file mode 100644 index 0000000..2f1e1e5 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/mode/CoopMode.java @@ -0,0 +1,134 @@ +package dev.loki.loparkour.mode; + +import dev.loki.loparkour.config.Config; +import dev.loki.loparkour.config.Locales; +import dev.loki.loparkour.generator.ParkourGenerator; +import dev.loki.loparkour.leaderboard.Leaderboard; +import dev.loki.loparkour.player.ParkourPlayer; +import dev.loki.loparkour.session.Session; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Coop Mode - Shared score between multiple players + */ +public class CoopMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "coop"; + } + + @Override + @Nullable + public dev.loki.loparkour.util.Item getItem(String locale) { + return Locales.getItem(locale, "modes.coop"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!Config.CONFIG.getBoolean("joining")) { + player.sendMessage("§cJoining is currently disabled."); + return; + } + + ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof CoopMode) { + return; + } + + player.closeInventory(); + Session.create(session -> new CoopGenerator(session), null, null, player); + } + + private static class CoopGenerator extends ParkourGenerator { + private final Map playerContributions = new HashMap<>(); + private int sharedScore = 0; + + public CoopGenerator(@NotNull Session session) { + super(session); + + // Initialize contributions + for (ParkourPlayer pp : session.getPlayers()) { + playerContributions.put(pp.getUUID(), 0); + } + } + + @Override + protected void score() { + super.score(); + sharedScore++; + + // Track individual contributions + if (!getPlayers().isEmpty()) { + UUID scorer = player.getUUID(); + playerContributions.put(scorer, playerContributions.getOrDefault(scorer, 0) + 1); + } + + // Milestone celebrations + if (sharedScore % 50 == 0) { + for (ParkourPlayer pp : getPlayers()) { + pp.player.sendTitle("§6" + sharedScore + " Points!", "§eTeam effort!", 5, 30, 10); + pp.player.playSound(pp.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.5f, 1.5f); + } + } + } + + @Override + public void tick() { + super.tick(); + + // Display shared score and contributions in ActionBar + for (ParkourPlayer pp : getPlayers()) { + int myContribution = playerContributions.getOrDefault(pp.getUUID(), 0); + int percentage = sharedScore > 0 ? (myContribution * 100 / sharedScore) : 0; + + String message = "§aTeam: §e" + sharedScore + " §7| §bYou: §e" + myContribution + " §7(" + percentage + "%)"; + pp.player.spigot().sendMessage( + net.md_5.bungee.api.ChatMessageType.ACTION_BAR, + net.md_5.bungee.api.chat.TextComponent.fromLegacyText(message) + ); + } + } + + @Override + protected void fall() { + // Show final stats before reset + for (ParkourPlayer pp : getPlayers()) { + int contribution = playerContributions.getOrDefault(pp.getUUID(), 0); + pp.sendTranslated("modes.coop.stats", + Integer.toString(sharedScore), + Integer.toString(contribution)); + } + + super.fall(); + + // Reset coop state + sharedScore = 0; + playerContributions.clear(); + for (ParkourPlayer pp : getPlayers()) { + playerContributions.put(pp.getUUID(), 0); + } + } + + @Override + public Mode getMode() { + return Modes.COOP; + } + } +} diff --git a/src/main/java/dev/loki/loparkour/mode/ElytraMode.java b/src/main/java/dev/loki/loparkour/mode/ElytraMode.java index f84dabb..3a5a3bb 100644 --- a/src/main/java/dev/loki/loparkour/mode/ElytraMode.java +++ b/src/main/java/dev/loki/loparkour/mode/ElytraMode.java @@ -9,13 +9,16 @@ import dev.loki.loparkour.leaderboard.Leaderboard; import dev.loki.loparkour.player.ParkourPlayer; import dev.loki.loparkour.session.Session; -import org.bukkit.GameMode; -import org.bukkit.Material; +import org.bukkit.*; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.*; + /** * Elytra Parkour Mode - Flying parkour with rings and boost */ @@ -56,8 +59,17 @@ public class ElytraMode implements Mode { player.closeInventory(); // Create session with elytra setup - Session.create(session -> { - ParkourGenerator generator = new ParkourGenerator(session); + Session.create(session -> new ElytraGenerator(session), null, null, player); + } + + private static class ElytraGenerator extends ParkourGenerator { + private final List rings = new ArrayList<>(); + private final Map fireworkCooldowns = new HashMap<>(); + private final Map ringsCollected = new HashMap<>(); + private int tickCounter = 0; + + public ElytraGenerator(@NotNull Session session) { + super(session); // Give elytra and fireworks to all players in session for (ParkourPlayer parkourPlayer : session.getPlayers()) { @@ -67,9 +79,117 @@ public class ElytraMode implements Mode { int fireworks = Config.CONFIG.getInt("modes.elytra.starting-fireworks"); p.getInventory().addItem(new ItemStack(Material.FIREWORK_ROCKET, fireworks)); + + ringsCollected.put(p.getUniqueId(), 0); + } + } + + @Override + public void tick() { + super.tick(); + tickCounter++; + + // Spawn rings every 5 blocks + if (tickCounter % 100 == 0 && !state.history.isEmpty()) { + Location ringLoc = getLatest().getLocation().clone().add(0, 3, 0); + rings.add(new RingCheckpoint(ringLoc)); } - return generator; - }, null, null, player); + // Display rings and check for player passing through + Iterator it = rings.iterator(); + while (it.hasNext()) { + RingCheckpoint ring = it.next(); + + // Display ring particles + displayRing(ring.location); + + // Check if any player passed through + for (ParkourPlayer pp : getPlayers()) { + if (ring.collected) continue; + + BoundingBox playerBox = pp.player.getBoundingBox(); + if (ring.getBoundingBox().overlaps(playerBox)) { + ring.collected = true; + int count = ringsCollected.getOrDefault(pp.getUUID(), 0) + 1; + ringsCollected.put(pp.getUUID(), count); + + // Bonus points for passing through ring + lifecycle.score(); + lifecycle.score(); + + pp.player.playSound(pp.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.5f); + pp.player.spawnParticle(Particle.TOTEM, ring.location, 20, 0.5, 0.5, 0.5, 0.1); + } + } + + // Remove old rings + if (ring.location.distance(player.getLocation()) > 50) { + it.remove(); + } + } + + // Check firework cooldown + checkFireworkCooldown(); + } + + private void displayRing(Location center) { + World world = center.getWorld(); + if (world == null) return; + + double radius = 2.0; + int points = 30; + + for (int i = 0; i < points; i++) { + double angle = 2 * Math.PI * i / points; + double x = center.getX() + radius * Math.cos(angle); + double z = center.getZ() + radius * Math.sin(angle); + + Location particleLoc = new Location(world, x, center.getY(), z); + world.spawnParticle(Particle.END_ROD, particleLoc, 1, 0, 0, 0, 0); + } + } + + private void checkFireworkCooldown() { + long now = System.currentTimeMillis(); + int cooldownMs = Config.CONFIG.getInt("modes.elytra.firework-cooldown-ms"); + + for (ParkourPlayer pp : getPlayers()) { + UUID uuid = pp.getUUID(); + ItemStack mainHand = pp.player.getInventory().getItemInMainHand(); + ItemStack offHand = pp.player.getInventory().getItemInOffHand(); + + boolean usingFirework = (mainHand.getType() == Material.FIREWORK_ROCKET && pp.player.isGliding()) + || (offHand.getType() == Material.FIREWORK_ROCKET && pp.player.isGliding()); + + if (usingFirework) { + Long lastUse = fireworkCooldowns.get(uuid); + if (lastUse != null && now - lastUse < cooldownMs) { + long remaining = (cooldownMs - (now - lastUse)) / 1000; + pp.player.spigot().sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR, + net.md_5.bungee.api.chat.TextComponent.fromLegacyText("§cFirework cooldown: " + remaining + "s")); + continue; + } + fireworkCooldowns.put(uuid, now); + } + } + } + + @Override + public Mode getMode() { + return Modes.ELYTRA; + } + + private static class RingCheckpoint { + final Location location; + boolean collected = false; + + RingCheckpoint(Location location) { + this.location = location; + } + + BoundingBox getBoundingBox() { + return BoundingBox.of(location, 2.5, 2.5, 2.5); + } + } } } diff --git a/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java b/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java index a4a50e7..ed0c777 100644 --- a/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java +++ b/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java @@ -140,6 +140,12 @@ public class GravityShiftMode implements Mode { ); player.player.addPotionEffect(currentEffect); + + // Visual feedback + player.player.sendTitle("§6⚡", "§e" + selected.name, 5, 20, 10); + player.player.playSound(player.player.getLocation(), org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.5f); + player.player.spawnParticle(org.bukkit.Particle.PORTAL, player.player.getLocation().add(0, 1, 0), 50, 0.5, 0.5, 0.5, 0.5); + player.sendTranslated("modes.gravity-shift.effect-applied", selected.name); } diff --git a/src/main/java/dev/loki/loparkour/mode/HardcoreMode.java b/src/main/java/dev/loki/loparkour/mode/HardcoreMode.java index 98562e5..47e371f 100644 --- a/src/main/java/dev/loki/loparkour/mode/HardcoreMode.java +++ b/src/main/java/dev/loki/loparkour/mode/HardcoreMode.java @@ -63,6 +63,7 @@ public class HardcoreMode implements Mode { boolean resetRewards = Config.CONFIG.getBoolean("modes.hardcore.reset-rewards"); if (resetRewards) { + player.collectedRewards.clear(); player.sendTranslated("modes.hardcore.rewards-lost"); } diff --git a/src/main/java/dev/loki/loparkour/mode/Modes.java b/src/main/java/dev/loki/loparkour/mode/Modes.java index 41e38d6..df4bff2 100644 --- a/src/main/java/dev/loki/loparkour/mode/Modes.java +++ b/src/main/java/dev/loki/loparkour/mode/Modes.java @@ -10,6 +10,8 @@ public class Modes { public static GravityShiftMode GRAVITY_SHIFT; public static HardcoreMode HARDCORE; public static ElytraMode ELYTRA; + public static RaceMode RACE; + public static CoopMode COOP; public static void init() { DEFAULT = (DefaultMode) Registry.getMode("default"); @@ -18,5 +20,7 @@ public class Modes { GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift"); HARDCORE = (HardcoreMode) Registry.getMode("hardcore"); ELYTRA = (ElytraMode) Registry.getMode("elytra"); + RACE = (RaceMode) Registry.getMode("race"); + COOP = (CoopMode) Registry.getMode("coop"); } } diff --git a/src/main/java/dev/loki/loparkour/mode/RaceMode.java b/src/main/java/dev/loki/loparkour/mode/RaceMode.java new file mode 100644 index 0000000..611d623 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/mode/RaceMode.java @@ -0,0 +1,108 @@ +package dev.loki.loparkour.mode; + +import dev.loki.loparkour.config.Config; +import dev.loki.loparkour.config.Locales; +import dev.loki.loparkour.generator.ParkourGenerator; +import dev.loki.loparkour.leaderboard.Leaderboard; +import dev.loki.loparkour.player.ParkourPlayer; +import dev.loki.loparkour.session.Session; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Race Mode - First to reach target score wins + */ +public class RaceMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.TIME); + + @Override + @NotNull + public String getName() { + return "race"; + } + + @Override + @Nullable + public dev.loki.loparkour.util.Item getItem(String locale) { + return Locales.getItem(locale, "modes.race"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!Config.CONFIG.getBoolean("joining")) { + player.sendMessage("§cJoining is currently disabled."); + return; + } + + ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof RaceMode) { + return; + } + + player.closeInventory(); + Session.create(session -> new RaceGenerator(session), null, null, player); + } + + private static class RaceGenerator extends ParkourGenerator { + private final int targetScore; + private boolean raceFinished = false; + + public RaceGenerator(@NotNull Session session) { + super(session); + this.targetScore = Config.CONFIG.getInt("modes.race.target-score"); + } + + @Override + public void tick() { + super.tick(); + + if (raceFinished) return; + + // Check if target reached + if (state.score >= targetScore) { + finishRace(); + } + + // Display progress in ActionBar + for (ParkourPlayer pp : getPlayers()) { + int remaining = targetScore - state.score; + String progress = "§e" + state.score + " §7/ §a" + targetScore + " §7(§c" + remaining + " §7left)"; + pp.player.spigot().sendMessage( + net.md_5.bungee.api.ChatMessageType.ACTION_BAR, + net.md_5.bungee.api.chat.TextComponent.fromLegacyText(progress) + ); + } + } + + private void finishRace() { + raceFinished = true; + + for (ParkourPlayer pp : getPlayers()) { + pp.player.sendTitle("§6§lRACE COMPLETE!", "§aFinished in " + getFormattedTime(), 10, 70, 20); + pp.player.playSound(pp.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f); + pp.sendTranslated("modes.race.completed", Integer.toString(targetScore), getFormattedTime()); + } + + // Register time-based score + registerScore(getDetailedTime(), Double.toString(getDifficultyScore()).substring(0, 3), targetScore); + + // Stop generator after 5 seconds + dev.lolib.scheduler.Scheduler.get(dev.loki.loparkour.LoParkour.getPlugin()) + .runLater(() -> reset(false), 100); + } + + @Override + public Mode getMode() { + return Modes.RACE; + } + } +} diff --git a/src/main/java/dev/loki/loparkour/mode/SpeedrunMode.java b/src/main/java/dev/loki/loparkour/mode/SpeedrunMode.java index 504c111..fb5d869 100644 --- a/src/main/java/dev/loki/loparkour/mode/SpeedrunMode.java +++ b/src/main/java/dev/loki/loparkour/mode/SpeedrunMode.java @@ -3,6 +3,7 @@ package dev.loki.loparkour.mode; import dev.loki.loparkour.util.Item; import dev.lolib.scheduler.Scheduler; +import dev.lolib.scheduler.ScheduledTask; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.config.Config; @@ -12,7 +13,6 @@ import dev.loki.loparkour.leaderboard.Leaderboard; import dev.loki.loparkour.player.ParkourPlayer; import dev.loki.loparkour.session.Session; -import dev.lolib.scheduler.Scheduler; import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.Sound; @@ -27,7 +27,6 @@ import java.util.Map; public class SpeedrunMode implements Mode { private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); - private final Map blockTimestamps = new HashMap<>(); @Override @NotNull @@ -64,44 +63,60 @@ public class SpeedrunMode implements Mode { Session.create(session -> new SpeedrunGenerator(session), null, null, player); } - public void onBlockTouch(@NotNull Block block, @NotNull ParkourPlayer player) { - if (!blockTimestamps.containsKey(block)) { - blockTimestamps.put(block, System.currentTimeMillis()); - scheduleBlockRemoval(player, block); - } - } - - private void scheduleBlockRemoval(@NotNull ParkourPlayer player, @NotNull Block block) { - double blockLifetime = Config.CONFIG.getDouble("modes.speedrun.block-lifetime"); - double warningTime = Config.CONFIG.getDouble("modes.speedrun.warning-time"); - - long removalTime = (long) (blockLifetime * 1000); - long warningTimeMs = (long) (warningTime * 1000); - - Scheduler.get(LoParkour.getPlugin()).runLater(() -> { - if (block.getType() != Material.AIR) { - player.player.playSound(block.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f); - player.player.spawnParticle(Particle.FLAME, block.getLocation().add(0.5, 1, 0.5), 10, 0.3, 0.3, 0.3, 0.01); - } - }, warningTimeMs / 50); - - Scheduler.get(LoParkour.getPlugin()).runLater(() -> { - if (block.getType() != Material.AIR) { - block.setType(Material.AIR); - blockTimestamps.remove(block); - } - }, removalTime / 50); - } - - public void reset() { - blockTimestamps.clear(); - } - private static class SpeedrunGenerator extends ParkourGenerator { + private final Map blockTimestamps = new HashMap<>(); + private final Map scheduledTasks = new HashMap<>(); + public SpeedrunGenerator(@NotNull Session session) { super(session); } + public void onBlockTouch(@NotNull Block block) { + if (!blockTimestamps.containsKey(block)) { + blockTimestamps.put(block, System.currentTimeMillis()); + scheduleBlockRemoval(block); + } + } + + private void scheduleBlockRemoval(@NotNull Block block) { + double blockLifetime = Config.CONFIG.getDouble("modes.speedrun.block-lifetime"); + double warningTime = Config.CONFIG.getDouble("modes.speedrun.warning-time"); + + long removalTime = (long) (blockLifetime * 1000); + long warningTimeMs = (long) (warningTime * 1000); + + ScheduledTask warningTask = Scheduler.get(LoParkour.getPlugin()).runLater(() -> { + if (block.getType() != Material.AIR) { + player.player.playSound(block.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f); + player.player.spawnParticle(Particle.FLAME, block.getLocation().add(0.5, 1, 0.5), 10, 0.3, 0.3, 0.3, 0.01); + } + }, warningTimeMs / 50); + + ScheduledTask removalTask = Scheduler.get(LoParkour.getPlugin()).runLater(() -> { + if (block.getType() != Material.AIR) { + block.setType(Material.AIR); + blockTimestamps.remove(block); + scheduledTasks.remove(block); + } + }, removalTime / 50); + + scheduledTasks.put(block, removalTask); + } + + @Override + public void reset(boolean regenerate) { + // Cancel all scheduled tasks + for (ScheduledTask task : scheduledTasks.values()) { + if (task != null) { + task.cancel(); + } + } + scheduledTasks.clear(); + blockTimestamps.clear(); + + super.reset(regenerate); + } + @Override public Mode getMode() { return Modes.SPEEDRUN; diff --git a/src/main/java/dev/loki/loparkour/player/ParkourPlayer.java b/src/main/java/dev/loki/loparkour/player/ParkourPlayer.java index 64d01ae..fa11332 100644 --- a/src/main/java/dev/loki/loparkour/player/ParkourPlayer.java +++ b/src/main/java/dev/loki/loparkour/player/ParkourPlayer.java @@ -165,18 +165,26 @@ public class ParkourPlayer extends ParkourUser { Scheduler.get(LoParkour.getPlugin()).runLater(() -> { player.getInventory().clear(); - List items = new ArrayList<>(); - - if (ParkourOption.PLAY.mayPerform(player)) items.add(Locales.getItem(locale, "play.item")); - if (ParkourOption.COMMUNITY.mayPerform(player)) items.add(Locales.getItem(locale, "community.item")); - if (ParkourOption.SETTINGS.mayPerform(player)) items.add(Locales.getItem(locale, "settings.item")); - if (ParkourOption.LOBBY.mayPerform(player)) items.add(Locales.getItem(locale, "lobby.item")); - - if (ParkourOption.QUIT.mayPerform(player)) items.add(Locales.getItem(locale, "other.quit")); - - List slots = getEvenlyDistributedSlots(items.size()); - for (int idx = 0; idx < items.size(); idx++) { - player.getInventory().setItem(slots.get(idx), items.get(idx).build()); + // Load hotbar slots from config + if (ParkourOption.PLAY.mayPerform(player)) { + int slot = Config.CONFIG.getInt("options.hotbar-slots.play"); + player.getInventory().setItem(slot, Locales.getItem(locale, "play.item").build()); + } + if (ParkourOption.COMMUNITY.mayPerform(player)) { + int slot = Config.CONFIG.getInt("options.hotbar-slots.community"); + player.getInventory().setItem(slot, Locales.getItem(locale, "community.item").build()); + } + if (ParkourOption.SETTINGS.mayPerform(player)) { + int slot = Config.CONFIG.getInt("options.hotbar-slots.settings"); + player.getInventory().setItem(slot, Locales.getItem(locale, "settings.item").build()); + } + if (ParkourOption.LOBBY.mayPerform(player)) { + int slot = Config.CONFIG.getInt("options.hotbar-slots.lobby"); + player.getInventory().setItem(slot, Locales.getItem(locale, "lobby.item").build()); + } + if (ParkourOption.QUIT.mayPerform(player)) { + int slot = Config.CONFIG.getInt("options.hotbar-slots.quit"); + player.getInventory().setItem(slot, Locales.getItem(locale, "other.quit").build()); } }, 5); } else { @@ -186,13 +194,4 @@ public class ParkourPlayer extends ParkourUser { public record OptionContainer(ParkourOption option, BiConsumer consumer) { } - - private List getEvenlyDistributedSlots(int count) { - List slots = new ArrayList<>(); - int[] positions = {10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25}; - for (int i = 0; i < Math.min(count, positions.length); i++) { - slots.add(positions[i]); - } - return slots; - } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 11c7860..cbe3e34 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -162,6 +162,16 @@ options: # Disables the ability for a player to open blocks with an inventory. disable-inventory-blocks: false + # -= Hotbar slots =- + # Configure which hotbar slots (0-8) to use for parkour items + # Items will be placed in these slots in order: play, community, settings, lobby, quit + hotbar-slots: + play: 1 + community: 3 + settings: 4 + lobby: 5 + quit: 7 + # -= Permissions options =- # These permissions are used to determine which things the player can change. # Find them at https://efnilite.dev/projects/ip/wiki @@ -354,11 +364,24 @@ modes: ring-size: 5 # Firework boost power boost-power: 1.5 - # Firework cooldown in seconds - firework-cooldown: 3 + # Firework cooldown in milliseconds + firework-cooldown-ms: 3000 # Starting fireworks amount starting-fireworks: 64 + # -= Race mode =- + race: + # Enable race mode + enabled: true + # Target score to win + target-score: 100 + + # -= Co-op mode =- + coop: + # Enable co-op mode + enabled: true + # Maximum players in co-op session + max-players: 4 # -= Scoring options =- diff --git a/src/main/resources/generation.yml b/src/main/resources/generation.yml index 47cc9db..e03ba4a 100644 --- a/src/main/resources/generation.yml +++ b/src/main/resources/generation.yml @@ -14,18 +14,18 @@ generation: # -= Distance chances =- # The chances of the different distances in %. Can be above 100%. distance: - 1: 10 - 2: 55 - 3: 34 - 4: 1 + 1: 5 + 2: 35 + 3: 45 + 4: 15 # -= Height chances =- # The chances of the different heights a jump can have in %. Can be above 100%. height: - 1: 20 - 0: 65 - -1: 10 - -2: 5 + 1: 30 + 0: 40 + -1: 20 + -2: 10 # -= Special jump chances =- special: @@ -79,6 +79,10 @@ advanced: # This block will be set to air once the schematic has been pasted begin-block: emerald_block + # -= First parkour block material =- + # The material of the very first parkour block that players spawn on + first-block-material: stone + # -= Directional heading =- # The directional heading of the parkour # Possible options: north, south, west and east. diff --git a/src/main/resources/locales/en.yml b/src/main/resources/locales/en.yml index cfb2cc5..8b78cb4 100644 --- a/src/main/resources/locales/en.yml +++ b/src/main/resources/locales/en.yml @@ -279,3 +279,13 @@ modes: material: "elytra" name: "�D4FF&lElytra" lore: "&#A0A0A0Fly through rings!||&#A0A0A0Use fireworks to boost" + + race: + material: "golden_boots" + name: "&#FFD700&lRace" + lore: "&#A0A0A0First to reach target score!||&#A0A0A0Compete for fastest time" + + coop: + material: "player_head" + name: "�FF00&lCo-op" + lore: "&#A0A0A0Team up with friends!||&#A0A0A0Shared score and progress" diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index 3f67993..99560a8 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -278,3 +278,13 @@ modes: material: "elytra" name: "�D4FF&lЭлитры" lore: "&#A0A0A0Летайте через кольца!||&#A0A0A0Используйте фейерверки для ускорения" + + race: + material: "golden_boots" + name: "&#FFD700&lГонка" + lore: "&#A0A0A0Первый до целевого счёта!||&#A0A0A0Соревнуйся за лучшее время" + + coop: + material: "player_head" + name: "�FF00&lКооп" + lore: "&#A0A0A0Играй с друзьями!||&#A0A0A0Общий счёт и прогресс"