diff --git a/TODO.md b/TODO.md index 9c411b7..8533cc3 100644 --- a/TODO.md +++ b/TODO.md @@ -234,7 +234,7 @@ ### Duels (перенос из `dev.efnilite.ipp`) - **122. Duels — модель «генератор на игрока».** ✅ **СДЕЛАНО**: `duels/` — `DuelsMode implements MultiMode`, `DuelsGenerator extends ParkourGenerator` (владелец: `Map`, allowJoining, goal, countdown), `SingleDuelsGenerator extends ParkourGenerator` (`getPlayers()=List.of(свой игрок)` → независимый state/history/score; при `state.score >= goal` → `owningGenerator.win`), `DuelsCountdown` (10с отсчёт + старт трасс). Результаты НЕ пишутся в лидерборд (`getLeaderboard()=null`). Тик через владельца (`tick()` тикает каждый single). Зарегистрирован (Modes.DUELS, PluginBootstrap, config.yml `modes.duels.{enabled,max-players,goal,island-distance}`, локали `play.multi.duels.*`). -- **123. Duels — изменения ядра.** Частично: арены строятся вручную (плоские stone-платформы на сдвинутом Z), хук `selectBlockData()` в нашем коде отсутствует — цвет игрока реализован упрощённо (дефолтный материал; `setPlayerIndex`/`blockData` сохранены для будущего). `final Island` не трогали. +- **123. Duels — изменения ядра.** ✅ **Добор**: добавлен хук `ParkourGenerator.selectBlockData()` (public, возвращает null по умолчанию) → `BlockSelector.selectBlockData()` первым делом проверяет его; `SingleDuelsGenerator` переопределяет → возвращает цвет игрока (16 concrete-цветов, `setPlayerIndex`) и форсит `defaultChances={DEFAULT:1.0}` + пустые specialChances (курс одноцветный, без спец/схем). Арены — плоские stone-платформы на сдвинутом Z (Island не трогали). - **124. Duels — классы:** `DuelsMode implements MultiMode` (create() реально создаёт сессию, join() через `isAcceptingPlayers`, **leave() только `removePlayer` без `ParkourUser.leave`** — фикс рекурсии Coop), `DuelsGenerator` (owner/allowJoining/playerGenerators/spawnData/goal; `initCountdown()` по клику LIME_BANNER 10с; `win()` с guard `stopped`; `reset(false)` синхронно и идемпотентно, НЕ `runLater`), `SingleDuelsGenerator` (`getPlayers()=List.of(player)`, `setPlayerIndex`→цвет, `score()`→`win` при goal). - **125. Duels — интеграция:** `modes.duels{enabled,max:2,target-score,island-distance}` в config.yml, локали `modes.duels.*`/`play.multi.*`, `MultiplayerMenu` (фильтр `instanceof MultiMode`) + кнопка в PlayMenu, баннер-старт в hotbar, `/lp create|join |invite`. Сложность: **L**. - **126. Duels — не повторить баги:** Race (TIME-лидерборд, ресет в воздухе через `runLater`) — у Duels лидерборда нет, время брать из `state.start` своим форматтером, ресет только синхронно с null-guard; Coop (рекурсия, невозможность создать) — не вызывать `ParkourUser.leave` поверх unregister, сессию создавать в `create()`. @@ -243,7 +243,7 @@ - **127. Elytra — модель.** ~~...~~ ✅ **БАЗОВОЕ СДЕЛАНО**: `elytra/section/` (ElytraSection+KnotDirector+PointType+ClientBlockChanger, сплайн через commons-math3), `elytra/generator/ElytraGenerator extends ParkourGenerator` (свой tick/score/фолл/reset, sustained-буст +0.08 в ascending), `elytra/mode/ElytraMode`. Зарегистрирован (Modes.ELYTRA, PluginBootstrap, локали play.elytra.elytra). Команда commons-math3: `org.apache.commons:commons-math3:3.6.1` + relocate. Осталось по п.127: фейерверк-буст (`v*0.7+dir*0.6`) — сделано упрощённо (только sustained); CloseMode (FLAT-кольца). - **128. Elytra — клиент-сайд рендер.** ✅ `ClientBlockChanger`: очередь `Map>`, `player.sendBlockChanges(states)` по чанкам, forward limit min(vd,8), flush/clear. Секции хранят Vector (без пиннинга чанков). -- **129. Elytra — классы.** ✅ Базовый: ElytraSection/KnotDirector/PointType/ClientBlockChanger + ElytraGenerator + ElytraMode. Осталось (M): подклассы-режимы SpeedDemon/MinSpeed/TimeTrial/Close/Obstacle, фейерверк-буст, раздача элитр через PreviousData (сейчас элитры выдаются в слот нагрудника при generateFirst, глiding=true). +- **129. Elytra — классы.** ✅ Базовый: ElytraSection/KnotDirector/PointType/ClientBlockChanger + ElytraGenerator + ElytraMode. ✅ **M-добор**: подклассы-режимы SpeedDemonGenerator (скор = макс. скорость), MinSpeedGenerator (поддержание мин. скорости, ресет при 6 тиках медленнее), TimeTrialGenerator (скор ограничен SCORE из конфига), CloseGenerator (FLAT-кольца); режимы SpeedDemonMode/MinSpeedMode/TimeTrialMode/CloseMode + регистрация + локали `play.elytra.{speeddemon,minspeed,timetrial,close}` + config `mode-settings.{min-speed,time-trial}`. Добавлен `Config.getDouble(path, default)`. Осталось: фейерверк-буст, раздача элитр через PreviousData, Obstacle-режим. - **130. Elytra — зависимости/мир.** ✅ commons-math3 подключён+relocated; отдельный мир не нужен (клиент-сайд); ASCEND_THRESHOLD_Y=50; скор в double (movementScore), в лидерборд пока не пишется (TODO: int-масштаб). Сложность: **S ✅**, M (режимы+бусты) и L (Obstacle+настройки+стили+награды) — далее. - **131. Тесты под новые режимы:** сплайн/секции (изоляция, Y/Z интерполяция), `ElytraGenerator.tick` (score по velocity.x, shouldReset-причины), `DuelsGenerator.win` (guard `stopped`, идемпотентность `reset(false)`), `SingleDuelsGenerator.score`→win при goal, рекурсия unregister (регрессия Coop). diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index bfd5fc3..1bc7f2b 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -113,7 +113,7 @@ - + diff --git a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java index f1e0ce0..59513d0 100644 --- a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java +++ b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java @@ -13,7 +13,11 @@ import dev.loki.loparkour.hook.papi.PAPIHook; import dev.loki.loparkour.listener.gameplay.ParkourRestrictionListener; import dev.loki.loparkour.listener.schematic.SchematicWandListener; import dev.loki.loparkour.listener.player.PlayerConnectionListener; +import dev.loki.loparkour.elytra.mode.CloseMode; import dev.loki.loparkour.elytra.mode.ElytraMode; +import dev.loki.loparkour.elytra.mode.MinSpeedMode; +import dev.loki.loparkour.elytra.mode.SpeedDemonMode; +import dev.loki.loparkour.elytra.mode.TimeTrialMode; import dev.loki.loparkour.mode.base.Modes; import dev.loki.loparkour.mode.impl.barrier.InvisibleBarrierMode; import dev.loki.loparkour.mode.impl.multi.CoopMode; @@ -79,6 +83,10 @@ public final class PluginBootstrap { Registry.register(new CoopMode()); Registry.register(new GravityShiftMode()); Registry.register(new ElytraMode()); + Registry.register(new SpeedDemonMode()); + Registry.register(new MinSpeedMode()); + Registry.register(new TimeTrialMode()); + Registry.register(new CloseMode()); Registry.register(new DuelsMode()); Modes.init(); } diff --git a/src/main/java/dev/loki/loparkour/config/core/Config.java b/src/main/java/dev/loki/loparkour/config/core/Config.java index a3daeda..62c6e88 100644 --- a/src/main/java/dev/loki/loparkour/config/core/Config.java +++ b/src/main/java/dev/loki/loparkour/config/core/Config.java @@ -99,6 +99,10 @@ public enum Config { return accessor.getDouble(path); } + public double getDouble(@NotNull String path, double defaultValue) { + return accessor != null ? accessor.getDouble(path, defaultValue) : defaultValue; + } + @NotNull public String getString(@NotNull String path) { return accessor.getString(path); diff --git a/src/main/java/dev/loki/loparkour/config/core/ConfigAccessor.java b/src/main/java/dev/loki/loparkour/config/core/ConfigAccessor.java index 491412d..4c44ff9 100644 --- a/src/main/java/dev/loki/loparkour/config/core/ConfigAccessor.java +++ b/src/main/java/dev/loki/loparkour/config/core/ConfigAccessor.java @@ -82,6 +82,14 @@ public class ConfigAccessor { warnIfMissing(path); return config.getDouble(path, 0.0); } + + /** + * Get double value at path with a fallback default. + */ + public double getDouble(@NotNull String path, double defaultValue) { + warnIfMissing(path); + return config.getDouble(path, defaultValue); + } /** * Get string value at path. diff --git a/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java b/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java index 2a9c2c3..589efe7 100644 --- a/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java +++ b/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java @@ -33,6 +33,23 @@ public class SingleDuelsGenerator extends ParkourGenerator { public SingleDuelsGenerator(@NotNull Session session) { super(session); + forceDefaultBlocks(); + } + + @Override + public void reset(boolean regenerate) { + super.reset(regenerate); + forceDefaultBlocks(); + } + + /** + * Duels courses use only plain (non-special, non-schematic) blocks so the + * player's color is the only material on the course. + */ + private void forceDefaultBlocks() { + state.defaultChances.clear(); + state.defaultChances.put(BlockGenerationType.DEFAULT, 1.0); + state.specialChances.clear(); } public void setOwnedPlayer(@NotNull ParkourPlayer pp) { @@ -43,6 +60,11 @@ public class SingleDuelsGenerator extends ParkourGenerator { this.blockData = COLORS.get(index % COLORS.size()).createBlockData(); } + @Override + public @NotNull BlockData selectBlockData() { + return blockData == null ? Material.STONE.createBlockData() : blockData; + } + @Override @NotNull public List getPlayers() { diff --git a/src/main/java/dev/loki/loparkour/elytra/generator/CloseGenerator.java b/src/main/java/dev/loki/loparkour/elytra/generator/CloseGenerator.java new file mode 100644 index 0000000..3c0bd70 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/generator/CloseGenerator.java @@ -0,0 +1,14 @@ +package dev.loki.loparkour.elytra.generator; + +import dev.loki.loparkour.elytra.section.ElytraPointType; +import dev.loki.loparkour.session.core.Session; + +/** + * Elytra mode that generates flat rings placed close together. + */ +public class CloseGenerator extends ElytraGenerator { + + public CloseGenerator(Session session) { + super(session, ElytraPointType.FLAT); + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/generator/ElytraGenerator.java b/src/main/java/dev/loki/loparkour/elytra/generator/ElytraGenerator.java index 28f4c4a..a850165 100644 --- a/src/main/java/dev/loki/loparkour/elytra/generator/ElytraGenerator.java +++ b/src/main/java/dev/loki/loparkour/elytra/generator/ElytraGenerator.java @@ -20,7 +20,6 @@ import java.util.Map; import java.util.Random; public class ElytraGenerator extends ParkourGenerator { - private static final int ASCEND_THRESHOLD_Y = 50; private static final double SUSTAINED_BOOST = 0.08; private static final int DEFAULT_RADIUS = 3; @@ -28,18 +27,23 @@ public class ElytraGenerator extends ParkourGenerator { private static final double EPSILON = 1e-6; public final Map sections = new LinkedHashMap<>(); public final ClientBlockChanger blockChanger = new ClientBlockChanger(); - private double movementScore = 0.0; + protected double movementScore = 0.0; private Long startTime = null; private Vector playerSpawn; private Vector blockSpawn; private Random random; - private final ElytraPointType pointType = ElytraPointType.CIRCLE; + protected final ElytraPointType pointType; private final int radius; private final Material material; private int boostedSectionIdx = Integer.MIN_VALUE; public ElytraGenerator(Session session) { + this(session, ElytraPointType.CIRCLE); + } + + protected ElytraGenerator(Session session, ElytraPointType pointType) { super(session); + this.pointType = pointType; this.radius = Config.GENERATION.getInt("elytra.radius", DEFAULT_RADIUS); this.material = Materials.parseOr(Config.GENERATION.getString("advanced.island.parkour.first-block-material"), Material.WHITE_STAINED_GLASS); } @@ -62,21 +66,19 @@ public class ElytraGenerator extends ParkourGenerator { @Override public void generate() { - final int idx; - final ElytraSection section; if (sections.isEmpty()) { - idx = 0; - section = new ElytraSection(blockSpawn.clone().add(new Vector(0, pointType.heightOffset, 0)), random, - ElytraKnotDirector.DEFAULT_VERTICAL_BIAS); - } else { - final int lastIdx = sections.keySet().stream().max(Integer::compareTo).orElse(0); - final ElytraSection previous = sections.get(lastIdx); - final int bias = previous.end().getY() < ASCEND_THRESHOLD_Y ? ElytraKnotDirector.ASCENDING_VERTICAL_BIAS - : ElytraKnotDirector.DEFAULT_VERTICAL_BIAS; - idx = lastIdx + 1; - section = new ElytraSection(previous.end(), random, bias); + final Vector start = blockSpawn.clone().add(new Vector(0, pointType.heightOffset, 0)); + final ElytraSection section = new ElytraSection(start, random, ElytraKnotDirector.DEFAULT_VERTICAL_BIAS); + sections.put(0, section); + blockChanger.queue(section.generateBlocks(pointType, radius)); + return; } - sections.put(idx, section); + final int lastIdx = sections.keySet().stream().max(Integer::compareTo).orElse(0); + final ElytraSection previous = sections.get(lastIdx); + final int bias = previous.end().getY() < ASCEND_THRESHOLD_Y ? ElytraKnotDirector.ASCENDING_VERTICAL_BIAS + : ElytraKnotDirector.DEFAULT_VERTICAL_BIAS; + final ElytraSection section = new ElytraSection(previous.end(), random, bias); + sections.put(lastIdx + 1, section); blockChanger.queue(section.generateBlocks(pointType, radius)); } @@ -114,8 +116,8 @@ public class ElytraGenerator extends ParkourGenerator { @Override public void reset(boolean regenerate) { final Player player = getPlayer(); - for (final ElytraSection section : sections.values()) { - if (player != null) { + if (player != null) { + for (final ElytraSection section : sections.values()) { section.clear(player); } } @@ -142,16 +144,13 @@ public class ElytraGenerator extends ParkourGenerator { return (int) Math.round(movementScore); } - private Player getPlayer() { + protected Player getPlayer() { return session.getPlayers().isEmpty() ? null : session.getPlayers().get(0).player; } private void removeOldest(Player player) { - if (sections.size() <= 2) { - return; - } - final ElytraSection old = sections.remove(sections.keySet().stream().min(Integer::compareTo).orElse(-1)); - if (old != null) { + if (sections.size() > 2) { + final ElytraSection old = sections.remove(sections.keySet().stream().min(Integer::compareTo).orElse(-1)); old.clear(player); } } diff --git a/src/main/java/dev/loki/loparkour/elytra/generator/MinSpeedGenerator.java b/src/main/java/dev/loki/loparkour/elytra/generator/MinSpeedGenerator.java new file mode 100644 index 0000000..beb0d72 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/generator/MinSpeedGenerator.java @@ -0,0 +1,60 @@ +package dev.loki.loparkour.elytra.generator; + +import dev.loki.loparkour.config.core.Config; +import dev.loki.loparkour.session.core.Session; +import org.bukkit.entity.Player; + +/** + * Elytra mode that requires sustained minimum speed and scores horizontal distance covered. + */ +public class MinSpeedGenerator extends ElytraGenerator { + + private static final double TICKS_PER_SECOND = 20.0; + private static final double MIN_SPEED = Config.GENERATION.getDouble("mode-settings.min-speed.min-speed", 0.9); + private static final int TOO_SLOW_LIMIT = 6; + + private int startX = 0; + private double maxSpeed = 0; + private int ticksTooSlow = 0; + + public MinSpeedGenerator(Session session) { + super(session); + } + + @Override + public void tick() { + super.tick(); + final Player player = getPlayer(); + if (player == null) { + return; + } + final double speed = player.getVelocity().clone().setY(0).length() * TICKS_PER_SECOND; + maxSpeed = Math.max(maxSpeed, speed); + if (speed > MIN_SPEED && startX == 0) { + startX = player.getLocation().getBlockX(); + } + if (MIN_SPEED > speed && maxSpeed > MIN_SPEED) { + ticksTooSlow++; + } + if (ticksTooSlow >= TOO_SLOW_LIMIT) { + reset(true); + } + } + + @Override + public void reset(boolean regenerate) { + super.reset(regenerate); + startX = 0; + maxSpeed = 0; + ticksTooSlow = 0; + } + + @Override + public int getElytraScore() { + final Player player = getPlayer(); + if (startX == 0 || player == null) { + return 0; + } + return Math.max(0, (int) Math.round(player.getLocation().getX() - startX)); + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/generator/SpeedDemonGenerator.java b/src/main/java/dev/loki/loparkour/elytra/generator/SpeedDemonGenerator.java new file mode 100644 index 0000000..e3ef007 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/generator/SpeedDemonGenerator.java @@ -0,0 +1,38 @@ +package dev.loki.loparkour.elytra.generator; + +import dev.loki.loparkour.session.core.Session; +import org.bukkit.entity.Player; + +/** + * Elytra mode that scores the highest gliding speed reached during the run. + */ +public class SpeedDemonGenerator extends ElytraGenerator { + + private static final double TICKS_PER_SECOND = 20.0; + + private double maxSpeed = 0; + + public SpeedDemonGenerator(Session session) { + super(session); + } + + @Override + public void tick() { + super.tick(); + final Player player = getPlayer(); + if (player != null && player.isGliding() && player.getVelocity().getX() > 0) { + maxSpeed = Math.max(maxSpeed, player.getVelocity().clone().setY(0).length() * TICKS_PER_SECOND); + } + } + + @Override + public void reset(boolean regenerate) { + super.reset(regenerate); + maxSpeed = 0; + } + + @Override + public int getElytraScore() { + return (int) Math.round(maxSpeed); + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/generator/TimeTrialGenerator.java b/src/main/java/dev/loki/loparkour/elytra/generator/TimeTrialGenerator.java new file mode 100644 index 0000000..71b9117 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/generator/TimeTrialGenerator.java @@ -0,0 +1,24 @@ +package dev.loki.loparkour.elytra.generator; + +import dev.loki.loparkour.config.core.Config; +import dev.loki.loparkour.session.core.Session; + +/** + * Elytra mode that restarts the course once the target score is reached. + */ +public class TimeTrialGenerator extends ElytraGenerator { + + private static final int SCORE = Config.GENERATION.getInt("mode-settings.time-trial.score", 500); + + public TimeTrialGenerator(Session session) { + super(session); + } + + @Override + public void tick() { + super.tick(); + if (getElytraScore() >= SCORE) { + reset(true); + } + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/mode/CloseMode.java b/src/main/java/dev/loki/loparkour/elytra/mode/CloseMode.java new file mode 100644 index 0000000..6132cdb --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/mode/CloseMode.java @@ -0,0 +1,55 @@ +package dev.loki.loparkour.elytra.mode; + +import dev.loki.loparkour.config.locale.Locales; +import dev.loki.loparkour.elytra.generator.CloseGenerator; +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.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 mode with flat rings placed close together. + */ +public class CloseMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "close"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "play.elytra.close"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!ModeMessages.checkJoiningEnabled(player)) { + return; + } + + final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof CloseMode) { + return; + } + + player.closeInventory(); + + Session.create(CloseGenerator::new, null, null, player); + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/mode/MinSpeedMode.java b/src/main/java/dev/loki/loparkour/elytra/mode/MinSpeedMode.java new file mode 100644 index 0000000..4393367 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/mode/MinSpeedMode.java @@ -0,0 +1,55 @@ +package dev.loki.loparkour.elytra.mode; + +import dev.loki.loparkour.config.locale.Locales; +import dev.loki.loparkour.elytra.generator.MinSpeedGenerator; +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.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 mode scoring distance covered while keeping a minimum speed. + */ +public class MinSpeedMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "minspeed"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "play.elytra.minspeed"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!ModeMessages.checkJoiningEnabled(player)) { + return; + } + + final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof MinSpeedMode) { + return; + } + + player.closeInventory(); + + Session.create(MinSpeedGenerator::new, null, null, player); + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/mode/SpeedDemonMode.java b/src/main/java/dev/loki/loparkour/elytra/mode/SpeedDemonMode.java new file mode 100644 index 0000000..19600a1 --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/mode/SpeedDemonMode.java @@ -0,0 +1,55 @@ +package dev.loki.loparkour.elytra.mode; + +import dev.loki.loparkour.config.locale.Locales; +import dev.loki.loparkour.elytra.generator.SpeedDemonGenerator; +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.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 mode scoring the highest gliding speed reached. + */ +public class SpeedDemonMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "speeddemon"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "play.elytra.speeddemon"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!ModeMessages.checkJoiningEnabled(player)) { + return; + } + + final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof SpeedDemonMode) { + return; + } + + player.closeInventory(); + + Session.create(SpeedDemonGenerator::new, null, null, player); + } +} diff --git a/src/main/java/dev/loki/loparkour/elytra/mode/TimeTrialMode.java b/src/main/java/dev/loki/loparkour/elytra/mode/TimeTrialMode.java new file mode 100644 index 0000000..5ccb3da --- /dev/null +++ b/src/main/java/dev/loki/loparkour/elytra/mode/TimeTrialMode.java @@ -0,0 +1,55 @@ +package dev.loki.loparkour.elytra.mode; + +import dev.loki.loparkour.config.locale.Locales; +import dev.loki.loparkour.elytra.generator.TimeTrialGenerator; +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.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 mode that resets the course after the target score is reached. + */ +public class TimeTrialMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "timetrial"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "play.elytra.timetrial"); + } + + @Override + @NotNull + public Leaderboard getLeaderboard() { + return leaderboard; + } + + @Override + public void create(Player player) { + if (!ModeMessages.checkJoiningEnabled(player)) { + return; + } + + final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof TimeTrialMode) { + return; + } + + player.closeInventory(); + + Session.create(TimeTrialGenerator::new, null, null, player); + } +} diff --git a/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java b/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java index cdafd48..f0b1a83 100644 --- a/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java +++ b/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java @@ -14,6 +14,7 @@ import dev.loki.loparkour.player.spectator.ParkourSpectator; import dev.loki.loparkour.session.core.Session; import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -152,6 +153,14 @@ public class ParkourGenerator { return state.history.get(state.history.size() - 1); } + /** + * Hook for modes to force a fixed block material on the whole course + * (e.g. a per-player color in duels), or {@code null} to use the style. + */ + public @Nullable BlockData selectBlockData() { + return null; + } + // Event listener management /** diff --git a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java index 4f2e1e4..d3c06de 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java @@ -31,6 +31,10 @@ public class BlockSelector { */ @NotNull public BlockData selectBlockData() { + final BlockData forced = generator.selectBlockData(); + if (forced != null) { + return forced; + } final Style style = Registry.getStyle(generator.profile.get("style").value()); if (style == null) { final List