From e3b0a02df6e6f426438464ed2d204e6d332378ed Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 9 Apr 2026 14:13:07 +0200 Subject: [PATCH] fix: correct GravityShiftMode getMode() and documentation --- ...2026-04-09-gravity-shift-hardcore-modes.md | 441 ++++++++++++++++++ .../loki/loparkour/mode/GravityShiftMode.java | 7 +- .../java/dev/loki/loparkour/mode/Modes.java | 2 + 3 files changed, 446 insertions(+), 4 deletions(-) create mode 100644 docs/superpowers/plans/2026-04-09-gravity-shift-hardcore-modes.md diff --git a/docs/superpowers/plans/2026-04-09-gravity-shift-hardcore-modes.md b/docs/superpowers/plans/2026-04-09-gravity-shift-hardcore-modes.md new file mode 100644 index 0000000..c45b0df --- /dev/null +++ b/docs/superpowers/plans/2026-04-09-gravity-shift-hardcore-modes.md @@ -0,0 +1,441 @@ +# GravityShift и Hardcore Modes Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Реализовать GravityShiftMode (случайные эффекты зелий) и HardcoreMode (сброс наград при падении) + +**Architecture:** Оба режима следуют паттерну Mode + внутренний Generator класс. GravityShiftGenerator переопределяет score() для применения эффектов, HardcoreGenerator переопределяет fall() для сброса наград. + +**Tech Stack:** Bukkit/Spigot API, Java 21, существующая архитектура Mode/ParkourGenerator + +--- + +## Task 1: Implement GravityShiftMode + +**Files:** +- Create: `src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java` + +- [ ] **Step 1: Create GravityShiftMode with inner Generator** + +```java +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 dev.loki.loparkour.util.Item; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Gravity Shift Mode — random potion effects applied every N jumps. + */ +public class GravityShiftMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "gravity-shift"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "modes.gravity-shift"); + } + + @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 instanceof GravityShiftGenerator) { + return; + } + + player.closeInventory(); + Session.create(session -> new GravityShiftGenerator(session), null, null, player); + } + + // ── GravityShiftGenerator ───────────────────────────────────────────────── + + private static class GravityShiftGenerator extends ParkourGenerator { + + private final int interval; + private final Map effects; + private final Random random; + private final Map jumpCounts; + + public GravityShiftGenerator(@NotNull Session session) { + super(session); + this.random = new Random(); + this.jumpCounts = new HashMap<>(); + + // Load config + this.interval = Config.CONFIG.isPath("modes.gravity-shift.interval") + ? Config.CONFIG.getInt("modes.gravity-shift.interval") : 10; + + this.effects = new HashMap<>(); + loadEffect("jump-boost", PotionEffectType.JUMP); + loadEffect("speed", PotionEffectType.SPEED); + loadEffect("slowness", PotionEffectType.SLOW); + loadEffect("levitation", PotionEffectType.LEVITATION); + + // Initialize jump counts for all players + for (ParkourPlayer pp : getPlayers()) { + jumpCounts.put(pp.getUUID(), 0); + } + } + + private void loadEffect(String key, PotionEffectType type) { + String basePath = "modes.gravity-shift.effects." + key; + boolean enabled = Config.CONFIG.isPath(basePath + ".enabled") + ? Config.CONFIG.getBoolean(basePath + ".enabled") : false; + + if (!enabled) { + return; + } + + int amplifier = Config.CONFIG.isPath(basePath + ".amplifier") + ? Config.CONFIG.getInt(basePath + ".amplifier") : 1; + int duration = Config.CONFIG.isPath(basePath + ".duration") + ? Config.CONFIG.getInt(basePath + ".duration") : 30; + + effects.put(type, new EffectConfig(amplifier, duration * 20)); + } + + @Override + protected void score() { + super.score(); + + // Apply effects to all players + for (ParkourPlayer pp : getPlayers()) { + UUID uuid = pp.getUUID(); + int count = jumpCounts.getOrDefault(uuid, 0) + 1; + jumpCounts.put(uuid, count); + + if (count % interval == 0 && !effects.isEmpty()) { + applyRandomEffect(pp.getPlayer()); + } + } + } + + private void applyRandomEffect(Player player) { + if (player == null || effects.isEmpty()) { + return; + } + + List types = new ArrayList<>(effects.keySet()); + PotionEffectType selected = types.get(random.nextInt(types.size())); + EffectConfig config = effects.get(selected); + + player.removePotionEffect(selected); + player.addPotionEffect(new PotionEffect( + selected, + config.durationTicks, + config.amplifier, + false, + true, + true + )); + } + + @Override + public void reset(boolean regenerate) { + // Clear effects and reset counts + for (ParkourPlayer pp : getPlayers()) { + Player player = pp.getPlayer(); + if (player != null) { + for (PotionEffectType type : effects.keySet()) { + player.removePotionEffect(type); + } + } + jumpCounts.put(pp.getUUID(), 0); + } + + super.reset(regenerate); + } + + @Override + public Mode getMode() { + return Modes.GRAVITY_SHIFT; + } + + private record EffectConfig(int amplifier, int durationTicks) {} + } +} +``` + +- [ ] **Step 2: Commit GravityShiftMode** + +```bash +git add src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java +git commit -m "feat: implement GravityShiftMode with random potion effects" +``` + +--- + +## Task 2: Implement HardcoreMode + +**Files:** +- Create: `src/main/java/dev/loki/loparkour/mode/HardcoreMode.java` + +- [ ] **Step 1: Create HardcoreMode with inner Generator** + +```java +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 dev.loki.loparkour.util.Item; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Hardcore Mode — lose all collected rewards on fall. + */ +public class HardcoreMode implements Mode { + + private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE); + + @Override + @NotNull + public String getName() { + return "hardcore"; + } + + @Override + @Nullable + public Item getItem(String locale) { + return Locales.getItem(locale, "modes.hardcore"); + } + + @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 instanceof HardcoreGenerator) { + return; + } + + player.closeInventory(); + Session.create(session -> new HardcoreGenerator(session), null, null, player); + } + + // ── HardcoreGenerator ───────────────────────────────────────────────────── + + private static class HardcoreGenerator extends ParkourGenerator { + + private final boolean resetRewards; + + public HardcoreGenerator(@NotNull Session session) { + super(session); + this.resetRewards = Config.CONFIG.isPath("modes.hardcore.reset-rewards") + ? Config.CONFIG.getBoolean("modes.hardcore.reset-rewards") : true; + } + + @Override + public void fall() { + if (resetRewards) { + for (ParkourPlayer pp : getPlayers()) { + pp.collectedRewards.clear(); + } + } + + super.fall(); + } + + @Override + public Mode getMode() { + return Modes.HARDCORE; + } + } +} +``` + +- [ ] **Step 2: Commit HardcoreMode** + +```bash +git add src/main/java/dev/loki/loparkour/mode/HardcoreMode.java +git commit -m "feat: implement HardcoreMode with reward reset on fall" +``` + +--- + +## Task 3: Register modes in Modes.java + +**Files:** +- Modify: `src/main/java/dev/loki/loparkour/mode/Modes.java` + +- [ ] **Step 1: Read Modes.java to find registration pattern** + +```bash +cat src/main/java/dev/loki/loparkour/mode/Modes.java +``` + +- [ ] **Step 2: Add static fields for new modes** + +Add after existing mode declarations (e.g., after `SPEEDRUN`, `ELYTRA`): + +```java +public static GravityShiftMode GRAVITY_SHIFT; +public static HardcoreMode HARDCORE; +``` + +- [ ] **Step 3: Register modes in init() method** + +Add in `init()` method after existing registrations: + +```java +GRAVITY_SHIFT = register(new GravityShiftMode()); +HARDCORE = register(new HardcoreMode()); +``` + +- [ ] **Step 4: Commit registration** + +```bash +git add src/main/java/dev/loki/loparkour/mode/Modes.java +git commit -m "feat: register GravityShift and Hardcore modes" +``` + +--- + +## Task 4: Add locale entries + +**Files:** +- Modify: `src/main/resources/lang/en.yml` +- Modify: `src/main/resources/lang/ru.yml` + +- [ ] **Step 1: Add English locale entries** + +Add to `en.yml` under `modes:` section: + +```yaml + gravity-shift: + item: POTION + name: 'Gravity Shift' + description: 'Random potion effects every few jumps' + hardcore: + item: SKELETON_SKULL + name: 'Hardcore' + description: 'Lose all rewards on fall' +``` + +- [ ] **Step 2: Add Russian locale entries** + +Add to `ru.yml` under `modes:` section: + +```yaml + gravity-shift: + item: POTION + name: 'Гравитационный сдвиг' + description: 'Случайные эффекты зелий каждые несколько прыжков' + hardcore: + item: SKELETON_SKULL + name: 'Хардкор' + description: 'Потеря всех наград при падении' +``` + +- [ ] **Step 3: Commit locale entries** + +```bash +git add src/main/resources/lang/en.yml src/main/resources/lang/ru.yml +git commit -m "feat: add locale entries for GravityShift and Hardcore modes" +``` + +--- + +## Task 5: Test both modes + +**Files:** +- None (manual testing) + +- [ ] **Step 1: Build plugin** + +```bash +./gradlew clean build +``` + +Expected: BUILD SUCCESSFUL + +- [ ] **Step 2: Copy to server** + +```bash +cp build/libs/LoParkour-*.jar /path/to/server/plugins/ +``` + +- [ ] **Step 3: Start server and test GravityShift** + +1. Start server +2. Join parkour with `/lp` +3. Switch to gravity-shift mode +4. Make 10+ jumps +5. Verify random potion effects appear + +Expected: Effects like Jump Boost, Speed, Slowness, Levitation apply randomly every 10 jumps + +- [ ] **Step 4: Test Hardcore mode** + +1. Switch to hardcore mode +2. Collect some rewards (if available) +3. Fall off parkour +4. Check if rewards were cleared + +Expected: `player.collectedRewards` should be empty after fall + +- [ ] **Step 5: Verify config loading** + +Check server logs for any config errors related to: +- `modes.gravity-shift.interval` +- `modes.gravity-shift.effects.*` +- `modes.hardcore.reset-rewards` + +Expected: No errors, default values used if config missing + +--- + +## Execution Handoff + +Plan complete and saved to `docs/superpowers/plans/2026-04-09-gravity-shift-hardcore-modes.md`. + +**Two execution options:** + +**1. Subagent-Driven (recommended)** - Fresh subagent per task, review between tasks, fast iteration + +**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints + +**Which approach?** diff --git a/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java b/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java index 8e72501..3fd8e49 100644 --- a/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java +++ b/src/main/java/dev/loki/loparkour/mode/GravityShiftMode.java @@ -1,6 +1,5 @@ package dev.loki.loparkour.mode; -import dev.loki.loparkour.api.Registry; import dev.loki.loparkour.config.Config; import dev.loki.loparkour.config.Locales; import dev.loki.loparkour.generator.ParkourGenerator; @@ -32,7 +31,7 @@ public class GravityShiftMode implements Mode { @Override @Nullable public Item getItem(String locale) { - return Locales.getItem(locale, "modes.gravity-shift"); + return Locales.getItem(locale, "play.single.gravity-shift"); } @Override @@ -44,7 +43,7 @@ public class GravityShiftMode implements Mode { @Override public void create(Player player) { if (!Config.CONFIG.getBoolean("joining")) { - player.sendMessage("§cJoining is currently disabled."); + player.sendMessage("Joining is currently disabled."); return; } @@ -158,7 +157,7 @@ public class GravityShiftMode implements Mode { @Override public Mode getMode() { - return Registry.getMode("gravity-shift"); + return Modes.GRAVITY_SHIFT; } private record EffectConfig(int amplifier, int durationTicks) {} diff --git a/src/main/java/dev/loki/loparkour/mode/Modes.java b/src/main/java/dev/loki/loparkour/mode/Modes.java index 71c52d9..02f7cd7 100644 --- a/src/main/java/dev/loki/loparkour/mode/Modes.java +++ b/src/main/java/dev/loki/loparkour/mode/Modes.java @@ -10,6 +10,7 @@ public class Modes { public static ElytraMode ELYTRA; public static RaceMode RACE; public static CoopMode COOP; + public static GravityShiftMode GRAVITY_SHIFT; public static void init() { DEFAULT = (DefaultMode) Registry.getMode("default"); @@ -18,5 +19,6 @@ public class Modes { ELYTRA = (ElytraMode) Registry.getMode("elytra"); RACE = (RaceMode) Registry.getMode("race"); COOP = (CoopMode) Registry.getMode("coop"); + GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift"); } }