From 33f0d7a31d5dfdfd214c2d1235b66d5c0d40eb40 Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Thu, 13 Feb 2025 10:40:54 +0100 Subject: [PATCH 1/8] Added of fishing event handler. Added jump puzzel generator --- pom.xml | 2 +- .../killquest/JumpPuzzleGenerator.java | 235 ++++++++++++++++++ .../digitalwm/killquest/KillQuestPlugin.java | 89 ++++++- src/main/resources/plugin.yml | 21 +- 4 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java diff --git a/pom.xml b/pom.xml index 66bb344..94911e4 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.digitalwm.killquestplugin KillQuestPlugin - 1.0 + 1.0.1 install diff --git a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java new file mode 100644 index 0000000..2b45505 --- /dev/null +++ b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java @@ -0,0 +1,235 @@ +package com.digitalwm.killquest; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.level.Level; +import cn.nukkit.math.Vector3; + +import java.util.Random; + +public class JumpPuzzleGenerator { + + private final Random random = new Random(); + private final Level level; + private final Vector3 startPos; + private final int length, width, maxHeight; + private final Player player; + private final KillQuestPlugin plugin; + + public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, int length, int width, int maxHeight) { + this.level = player.getLevel(); + this.startPos = player.getPosition().floor(); + this.length = length; + this.width = width; + this.maxHeight = maxHeight; + this.plugin = plugin; + this.player = player; + } + + public void generate() { + clearArea(); + generateBase(); + generateWalls(); + generatePuzzle(); + } + + private void clearArea() { + plugin.getLogger().info("Clearing area for jump puzzle..."); + + // Get adjusted start position + Vector3 adjustedStart = new Vector3( + startPos.x - width / 2, + startPos.y, + startPos.z - length / 2 + ); + + // ✅ Remove all blocks in the defined area + for (int x = 0; x <= width; x++) { + for (int z = 0; z <= length; z++) { + for (int y = 0; y <= maxHeight; y++) { // Go up to maxHeight + level.setBlock(new Vector3(adjustedStart.x + x, adjustedStart.y + y, adjustedStart.z + z), Block.get(Block.AIR)); + } + } + } + + plugin.getLogger().info("Area cleared successfully!"); + } + + + private void generateBase() { + plugin.getLogger().info("Generating puzzle base with light-emitting blocks..."); + + Vector3 baseStart = new Vector3( + startPos.x - width / 2, + startPos.y - 1, + startPos.z - length / 2 + ); + + Vector3 centerBlock = new Vector3( + startPos.x, + startPos.y - 1, + startPos.z + ); + + for (int x = 0; x <= width; x++) { + for (int z = 0; z <= length; z++) { + Vector3 currentPos = new Vector3(baseStart.x + x, baseStart.y, baseStart.z + z); + + // ✅ Place a Red Block in the exact center + if (currentPos.x == centerBlock.x && currentPos.z == centerBlock.z) { + level.setBlock(currentPos, Block.get(Block.REDSTONE_BLOCK)); + } else { + level.setBlock(currentPos, Block.get(Block.SEA_LANTERN)); // Light-emitting base + } + } + } + + plugin.getLogger().info("Puzzle base generated with lighting and center marker!"); + } + + private void generateWalls() { + plugin.getLogger().info("Generating walls around the puzzle..."); + + Vector3 baseStart = new Vector3( + startPos.x - width / 2, + startPos.y - 1, + startPos.z - length / 2 + ); + + for (int y = 0; y <= maxHeight; y++) { + for (int x = 0; x <= width; x++) { + // Left Wall + level.setBlock(new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z), Block.get(Block.GLASS)); + // Right Wall + level.setBlock(new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z + length), Block.get(Block.GLASS)); + } + for (int z = 0; z <= length; z++) { + // Front Wall + level.setBlock(new Vector3(baseStart.x, baseStart.y + y, baseStart.z + z), Block.get(Block.GLASS)); + // Back Wall + level.setBlock(new Vector3(baseStart.x + width, baseStart.y + y, baseStart.z + z), Block.get(Block.GLASS)); + } + } + + plugin.getLogger().info("Walls generated successfully!"); + } + + private Vector3 getForwardDirection(Vector3 position) { + float yaw = (float) this.player.getYaw(); // Get player's yaw (rotation) + + if (yaw >= -45 && yaw <= 45) { // Facing Z+ + return new Vector3(0, 0, 1); + } else if (yaw > 45 && yaw < 135) { // Facing X- + return new Vector3(-1, 0, 0); + } else if (yaw >= 135 || yaw <= -135) { // Facing Z- + return new Vector3(0, 0, -1); + } else { // Facing X+ + return new Vector3(1, 0, 0); + } + } + + public void clearOnly() { + plugin.getLogger().info("Starting area cleanup for jump puzzle..."); + clearArea(); + plugin.getLogger().info("Jump puzzle area cleared successfully!"); + } + + private void generatePuzzle() { + plugin.getLogger().info("Generating jump puzzle..."); + + // ✅ Start at bottom-left corner but inset by 1 block (not directly on the edge) + Vector3 lastBlock = new Vector3( + startPos.x - width / 2 + 1, + startPos.y, + startPos.z - length / 2 + 1 + ); + + level.setBlock(lastBlock, Block.get(Block.GOLD_BLOCK)); // Start block + + int currentHeight = 0; + int blocksAtCurrentHeight = 0; + + while (currentHeight < maxHeight) { + int newX, newZ; + boolean validMove = false; + + do { + int xOffset = random.nextInt(3) - 1; // -1, 0, or 1 + int zOffset = random.nextInt(3) - 1; // -1, 0, or 1 + + // ✅ Ensure spacing of 1 to 2 blocks per move + xOffset *= random.nextBoolean() ? 2 : 1; + zOffset *= random.nextBoolean() ? 2 : 1; + + newX = (int) lastBlock.x + xOffset; + newZ = (int) lastBlock.z + zOffset; + + // ✅ Prevent out-of-bounds movement + if (newX <= startPos.x - width / 2 + 1 || newX >= startPos.x + width / 2 - 1) { + continue; + } + if (newZ <= startPos.z - length / 2 + 1 || newZ >= startPos.z + length / 2 - 1) { + continue; + } + + // ✅ Ensure the block is not directly above the last block + if (newX != (int) lastBlock.x || newZ != (int) lastBlock.z) { + // ✅ Ensure the block is NOT on the edge (except start block) + if (newX > startPos.x - width / 2 + 1 && newX < startPos.x + width / 2 - 1 && + newZ > startPos.z - length / 2 + 1 && newZ < startPos.z + length / 2 - 1) { + + // ✅ Ensure second level and beyond have air below them + if (currentHeight >= 1) { + Vector3 belowBlock = new Vector3(newX, startPos.y + currentHeight - 1, newZ); + if (level.getBlock(belowBlock).getId() != Block.AIR) { + continue; // Skip if block is not air + } + } + + // ✅ Ensure third level and beyond have TWO blocks of air below them + if (currentHeight >= 2) { + Vector3 belowBlock1 = new Vector3(newX, startPos.y + currentHeight - 1, newZ); + Vector3 belowBlock2 = new Vector3(newX, startPos.y + currentHeight - 2, newZ); + if (level.getBlock(belowBlock1).getId() != Block.AIR || level.getBlock(belowBlock2).getId() != Block.AIR) { + continue; // Skip if both blocks are not air + } + } + + // ✅ Ensure fourth level and beyond have THREE blocks of air below them + if (currentHeight >= 3) { + Vector3 belowBlock1 = new Vector3(newX, startPos.y + currentHeight - 1, newZ); + Vector3 belowBlock2 = new Vector3(newX, startPos.y + currentHeight - 2, newZ); + Vector3 belowBlock3 = new Vector3(newX, startPos.y + currentHeight - 3, newZ); + if (level.getBlock(belowBlock1).getId() != Block.AIR || + level.getBlock(belowBlock2).getId() != Block.AIR || + level.getBlock(belowBlock3).getId() != Block.AIR) { + continue; // Skip if any of the three blocks below are not air + } + } + + validMove = true; + } + } + + } while (!validMove); + + Vector3 newBlock = new Vector3(newX, startPos.y + currentHeight, newZ); + level.setBlock(newBlock, Block.get(Block.STONE)); // Place platform + blocksAtCurrentHeight++; + + // ✅ Ensure at least 4 blocks are placed per height level + if (blocksAtCurrentHeight >= 8) { + currentHeight++; // Move upward only after placing 4 blocks + blocksAtCurrentHeight = 0; + } + + lastBlock = newBlock.clone(); + } + + // ✅ Place end block at max height + Vector3 endBlock = new Vector3(lastBlock.x, lastBlock.y, lastBlock.z); + level.setBlock(endBlock, Block.get(Block.DIAMOND_BLOCK)); + + plugin.getLogger().info("Jump puzzle generated successfully!"); + } +} diff --git a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java index 4538a3a..486a809 100644 --- a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java +++ b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java @@ -10,11 +10,16 @@ import cn.nukkit.event.EventHandler; import cn.nukkit.event.player.PlayerJoinEvent; import cn.nukkit.event.player.PlayerQuitEvent; +import cn.nukkit.event.player.PlayerFishEvent; import cn.nukkit.event.entity.EntityDeathEvent; import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.event.inventory.InventoryPickupItemEvent; import cn.nukkit.event.player.PlayerFormRespondedEvent; +import cn.nukkit.block.Block; +import cn.nukkit.level.Level; +import cn.nukkit.math.Vector3; + import cn.nukkit.entity.Entity; import cn.nukkit.Player; import cn.nukkit.item.Item; @@ -266,7 +271,6 @@ public void destroyScoreboard(Player player) { } } - /** * Loads available quests from quests.yml. */ @@ -501,21 +505,75 @@ public boolean onCommand(CommandSender sender, Command command, String label, St sender.sendMessage("This command can only be executed by a player."); return true; } + Player player = (Player) sender; - List quests = getRandomQuestsForPlayer(player.getName()); + + // Handle Quest Selection if (command.getName().equalsIgnoreCase("quests")) { + List quests = getRandomQuestsForPlayer(player.getName()); FormWindowSimple form = new FormWindowSimple("Quest Selector", "Select one quest from the list below. Only one active quest is allowed at a time."); for (Quest quest : quests) { - // Create a new ElementButton instead of passing a string. form.addButton(new ElementButton(quest.getName() + "\n" + quest.getDescription())); } player.showFormWindow(form); return true; } + + // Handle Jump Puzzle Generation + if (command.getName().equalsIgnoreCase("jumpgen")) { + if (args.length < 3) { + sender.sendMessage("§cUsage: /jumpgen "); + return false; + } + + int length, width, maxHeight; + try { + length = Integer.parseInt(args[0]); + width = Integer.parseInt(args[1]); + maxHeight = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + sender.sendMessage("§cInvalid number format. Use: /jumpgen "); + return true; + } + + // ✅ Use the new class to generate the puzzle + JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, length, width, maxHeight); + generator.generate(); + + sender.sendMessage("§aJumping puzzle generated inside a cage!"); + return true; + } + + // ✅ Handle Puzzle Area Clearing + if (command.getName().equalsIgnoreCase("clearpuzzle")) { + if (args.length < 3) { + sender.sendMessage("§cUsage: /clearpuzzle "); + return false; + } + + int length, width, maxHeight; + try { + length = Integer.parseInt(args[0]); + width = Integer.parseInt(args[1]); + maxHeight = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + sender.sendMessage("§cInvalid number format. Use: /clearpuzzle "); + return true; + } + + getLogger().info("Clearing puzzle area for player " + player.getName() + "..."); + JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, length, width, maxHeight); + generator.clearOnly(); + sender.sendMessage("§aJump puzzle area cleared!"); + getLogger().info("Puzzle area successfully cleared."); + return true; + } + return false; } + /** * Handles the player's form response for quest selection. */ @@ -581,6 +639,31 @@ public void run() { } } + @EventHandler + public void onPlayerFish(PlayerFishEvent event) { + Player player = event.getPlayer(); + Item loot = event.getLoot(); // ✅ Get the caught item + + if (loot != null) { + String itemName = normalizeItemName(loot.getName()); + player.sendMessage("§aYou caught a " + itemName + "!"); + + getLogger().info("Player " + player.getName() + " fished up: " + itemName); + + // ✅ Schedule a delayed task to update quest progress + getServer().getScheduler().scheduleDelayedTask(this, new Runnable() { + @Override + public void run() { + updateQuestProgressForPlayer(player); + } + }, 1); // Delay 1 tick to ensure inventory updates + } + } + + private String normalizeItemName(String itemName) { + return itemName.toLowerCase().replace(" ", "_"); + } + /** * Event handler for entity death events. * Tracks kill progress for the player's active quest. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 39d090b..750a040 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,8 +1,25 @@ name: KillQuestPlugin -version: 1.0.0 +version: 1.0.1 main: com.digitalwm.killquest.KillQuestPlugin api: [1.1.0] commands: quests: description: "Opens the quest selection form." - usage: "/quests" \ No newline at end of file + usage: "/quests" + jumpgen: + description: "Generates a random jumping puzzle." + usage: "/jumpgen " + permission: killquest.jumpgen + aliases: [jp, jumppuzzle] + clearpuzzle: + description: "Clear an area ahead of the puzzle" + usage: "/clearpuzzle " + permission: killquest.clear + +permissions: + killquest.jumpgen: + description: "Allows the player to generate a jump puzzle." + default: op + killquest.clear: + description: "Allows the player to clear an area for jump puzzle." + default: op \ No newline at end of file From 6055fac69b4f00acdadf206637b40259f38304da Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Thu, 13 Feb 2025 09:42:55 +0000 Subject: [PATCH 2/8] Create maven.yml Added Maven Workflow sugested by Github --- .github/workflows/maven.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..06b6aa0 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Java CI with Maven + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml + + # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive + - name: Update dependency graph + uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 From 00f9940c0c976abb3a409798b9897d0eefab0205 Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Thu, 13 Feb 2025 20:15:02 +0100 Subject: [PATCH 3/8] Added jump puzzle protection. No block can be added or removed in the puzzel. Adjusted the puzzel generation. Implemented tracking of start and stop points and Awards 100 credits if from start to end is done in 15 minutes --- .../killquest/JumpPuzzleGenerator.java | 148 ++++++++++++------ .../killquest/JumpPuzzleListener.java | 59 +++++++ .../digitalwm/killquest/KillQuestPlugin.java | 21 ++- 3 files changed, 172 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java diff --git a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java index 2b45505..121b5ea 100644 --- a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java +++ b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java @@ -4,8 +4,10 @@ import cn.nukkit.block.Block; import cn.nukkit.level.Level; import cn.nukkit.math.Vector3; +import cn.nukkit.event.player.PlayerMoveEvent; +import me.onebone.economyapi.EconomyAPI; -import java.util.Random; +import java.util.*; public class JumpPuzzleGenerator { @@ -15,6 +17,14 @@ public class JumpPuzzleGenerator { private final int length, width, maxHeight; private final Player player; private final KillQuestPlugin plugin; + + private Vector3 puzzleMin; + private Vector3 puzzleMax; + private Vector3 startBlock; + private Vector3 endBlock; + + private final Set jumpBlocks = new HashSet<>(); + private final Map playerStartTimes = new HashMap<>(); public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, int length, int width, int maxHeight) { this.level = player.getLevel(); @@ -24,6 +34,14 @@ public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, int length, in this.maxHeight = maxHeight; this.plugin = plugin; this.player = player; + this.setPuzzleBoundaries(this.startPos, this.width, this.length, this.maxHeight); + } + + public void setPuzzleBoundaries(Vector3 startPos, int width, int length, int maxHeight) { + maxHeight++; + puzzleMin = new Vector3(startPos.x - width / 2, startPos.y, startPos.z - length / 2); + puzzleMax = new Vector3(startPos.x + width / 2, startPos.y + maxHeight, startPos.z + length / 2); + plugin.getLogger().info("Puzzle boundaries set: " + puzzleMin + " to " + puzzleMax); } public void generate() { @@ -137,34 +155,40 @@ public void clearOnly() { private void generatePuzzle() { plugin.getLogger().info("Generating jump puzzle..."); - // ✅ Start at bottom-left corner but inset by 1 block (not directly on the edge) Vector3 lastBlock = new Vector3( startPos.x - width / 2 + 1, startPos.y, startPos.z - length / 2 + 1 ); - level.setBlock(lastBlock, Block.get(Block.GOLD_BLOCK)); // Start block + startBlock = lastBlock.clone(); + level.setBlock(startBlock, Block.get(Block.GOLD_BLOCK)); int currentHeight = 0; int blocksAtCurrentHeight = 0; while (currentHeight < maxHeight) { - int newX, newZ; boolean validMove = false; + int retryCount = 0; // ✅ Prevent infinite loops + int newX = (int) lastBlock.x, newZ = (int) lastBlock.z; do { - int xOffset = random.nextInt(3) - 1; // -1, 0, or 1 - int zOffset = random.nextInt(3) - 1; // -1, 0, or 1 + retryCount++; + if (retryCount > 50) { // ✅ If we can't find a position after 50 tries, force move + plugin.getLogger().warning("Stuck in loop! Forcing move..."); + break; + } + + int xOffset = random.nextInt(3) - 1; + int zOffset = random.nextInt(3) - 1; - // ✅ Ensure spacing of 1 to 2 blocks per move xOffset *= random.nextBoolean() ? 2 : 1; zOffset *= random.nextBoolean() ? 2 : 1; newX = (int) lastBlock.x + xOffset; newZ = (int) lastBlock.z + zOffset; - // ✅ Prevent out-of-bounds movement + // ✅ Ensure the new block is within bounds if (newX <= startPos.x - width / 2 + 1 || newX >= startPos.x + width / 2 - 1) { continue; } @@ -172,64 +196,88 @@ private void generatePuzzle() { continue; } - // ✅ Ensure the block is not directly above the last block - if (newX != (int) lastBlock.x || newZ != (int) lastBlock.z) { - // ✅ Ensure the block is NOT on the edge (except start block) - if (newX > startPos.x - width / 2 + 1 && newX < startPos.x + width / 2 - 1 && - newZ > startPos.z - length / 2 + 1 && newZ < startPos.z + length / 2 - 1) { - - // ✅ Ensure second level and beyond have air below them - if (currentHeight >= 1) { - Vector3 belowBlock = new Vector3(newX, startPos.y + currentHeight - 1, newZ); - if (level.getBlock(belowBlock).getId() != Block.AIR) { - continue; // Skip if block is not air - } - } - - // ✅ Ensure third level and beyond have TWO blocks of air below them - if (currentHeight >= 2) { - Vector3 belowBlock1 = new Vector3(newX, startPos.y + currentHeight - 1, newZ); - Vector3 belowBlock2 = new Vector3(newX, startPos.y + currentHeight - 2, newZ); - if (level.getBlock(belowBlock1).getId() != Block.AIR || level.getBlock(belowBlock2).getId() != Block.AIR) { - continue; // Skip if both blocks are not air - } - } - - // ✅ Ensure fourth level and beyond have THREE blocks of air below them - if (currentHeight >= 3) { - Vector3 belowBlock1 = new Vector3(newX, startPos.y + currentHeight - 1, newZ); - Vector3 belowBlock2 = new Vector3(newX, startPos.y + currentHeight - 2, newZ); - Vector3 belowBlock3 = new Vector3(newX, startPos.y + currentHeight - 3, newZ); - if (level.getBlock(belowBlock1).getId() != Block.AIR || - level.getBlock(belowBlock2).getId() != Block.AIR || - level.getBlock(belowBlock3).getId() != Block.AIR) { - continue; // Skip if any of the three blocks below are not air - } - } - - validMove = true; + // ✅ Ensure blocks below are air starting from level 3 + if (currentHeight >= 3) { + Vector3 below1 = new Vector3(newX, startPos.y + currentHeight - 1, newZ); + Vector3 below2 = new Vector3(newX, startPos.y + currentHeight - 2, newZ); + + if (level.getBlock(below1).getId() != Block.AIR || level.getBlock(below2).getId() != Block.AIR) { + continue; // Skip this position if blocks are below + } + } + + // ✅ Ensure blocks below are air starting from level 4 + if (currentHeight >= 4) { + Vector3 below1 = new Vector3(newX, startPos.y + currentHeight - 1, newZ); + Vector3 below2 = new Vector3(newX, startPos.y + currentHeight - 2, newZ); + Vector3 below3 = new Vector3(newX, startPos.y + currentHeight - 3, newZ); + + if (level.getBlock(below1).getId() != Block.AIR || level.getBlock(below2).getId() != Block.AIR || level.getBlock(below3).getId() != Block.AIR) { + continue; // Skip this position if blocks are below } } + validMove = true; + } while (!validMove); + // ✅ Place the jump block Vector3 newBlock = new Vector3(newX, startPos.y + currentHeight, newZ); - level.setBlock(newBlock, Block.get(Block.STONE)); // Place platform + level.setBlock(newBlock, Block.get(Block.STONE)); + jumpBlocks.add(newBlock); + + plugin.getLogger().info("Placed block at x: " + newBlock.x + " z: " + newBlock.z); + blocksAtCurrentHeight++; - // ✅ Ensure at least 4 blocks are placed per height level + // ✅ Ensure at least 4 blocks are placed per height level before increasing height if (blocksAtCurrentHeight >= 8) { - currentHeight++; // Move upward only after placing 4 blocks + currentHeight++; blocksAtCurrentHeight = 0; } lastBlock = newBlock.clone(); } - // ✅ Place end block at max height - Vector3 endBlock = new Vector3(lastBlock.x, lastBlock.y, lastBlock.z); + // ✅ Place end block + endBlock = lastBlock.clone(); level.setBlock(endBlock, Block.get(Block.DIAMOND_BLOCK)); - plugin.getLogger().info("Jump puzzle generated successfully!"); } + + private boolean isInsidePuzzle(Vector3 pos) { + return (pos.x >= puzzleMin.x && pos.x <= puzzleMax.x) && + (pos.y >= puzzleMin.y && pos.y <= puzzleMax.y) && + (pos.z >= puzzleMin.z && pos.z <= puzzleMax.z); + } + + public boolean isPlayerInside(Player player) { + return isInsidePuzzle(player.getPosition()); // ✅ Reuse `isInsidePuzzle` + } + + public void handlePlayerMovement(PlayerMoveEvent event) { + Player player = event.getPlayer(); + Vector3 pos = player.getPosition().floor(); + pos.y = pos.y - 1; + + if (pos.equals(startBlock) && !playerStartTimes.containsKey(player)) { + player.sendMessage("§eYou started the jump puzzle! Reach the end within 15 minutes!"); + playerStartTimes.put(player, System.currentTimeMillis()); + } + + if (playerStartTimes.containsKey(player)) { + long startTime = playerStartTimes.get(player); + if (System.currentTimeMillis() - startTime > 15 * 60 * 1000) { + player.sendMessage("§cYou ran out of time for the jump puzzle!"); + playerStartTimes.remove(player); + return; + } + + if (pos.equals(endBlock)) { + player.sendMessage("§aCongratulations! You completed the jump puzzle!"); + EconomyAPI.getInstance().addMoney(player, 100); + playerStartTimes.remove(player); + } + } + } } diff --git a/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java b/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java new file mode 100644 index 0000000..977963f --- /dev/null +++ b/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java @@ -0,0 +1,59 @@ +package com.digitalwm.killquest; + +import cn.nukkit.Player; +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.Listener; +import cn.nukkit.math.Vector3; +import cn.nukkit.event.block.BlockBreakEvent; +import cn.nukkit.event.block.BlockPlaceEvent; +import cn.nukkit.event.player.PlayerMoveEvent; + +public class JumpPuzzleListener implements Listener { + + private final KillQuestPlugin plugin; + + public JumpPuzzleListener(KillQuestPlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + + // ✅ Cycle through all active puzzles and trigger their event handling + for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles()) { + if (puzzle.isPlayerInside(player)) { + puzzle.handlePlayerMovement(event); + break; // Stop checking after finding one matching puzzle + } + } + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + Player player = event.getPlayer(); + Vector3 pos = event.getBlock().getLocation(); + + for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles()) { + if (puzzle.isPlayerInside(player)) { + event.setCancelled(true); + player.sendMessage("§cYou cannot place blocks inside the jumping puzzle!"); + plugin.getLogger().info("Blocked " + player.getName() + " from placing a block in the puzzle area."); + } + } + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + Vector3 pos = event.getBlock().getLocation(); + + for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles()) { + if (puzzle.isPlayerInside(player)) { + event.setCancelled(true); + player.sendMessage("§cYou cannot break blocks inside the jumping puzzle!"); + plugin.getLogger().info("Blocked " + player.getName() + " from breaking a block in the puzzle area."); + } + } + } +} diff --git a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java index 486a809..d5dc470 100644 --- a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java +++ b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java @@ -70,6 +70,9 @@ public class KillQuestPlugin extends PluginBase implements Listener, CommandExec // Reference to the EconomyAPI instance. private EconomyAPI economy = null; + // Active Jump Puzzles + private final List activeJumpPuzzles = new ArrayList<>(); + public Map getActiveQuests() { return activeQuests; } @@ -111,6 +114,11 @@ public void run() { getServer().getScheduler().scheduleRepeatingTask(this, new QuestScoreboardUpdater(this), 40); getServer().getPluginManager().registerEvents(new ScoreboardListeners(this), this); + getServer().getPluginManager().registerEvents(new JumpPuzzleListener(this), this); + } + + public List getActiveJumpPuzzles() { + return activeJumpPuzzles; } @Override @@ -528,9 +536,9 @@ public boolean onCommand(CommandSender sender, Command command, String label, St int length, width, maxHeight; try { - length = Integer.parseInt(args[0]); - width = Integer.parseInt(args[1]); - maxHeight = Integer.parseInt(args[2]); + length = Math.max(20, Integer.parseInt(args[0])); // Ensure minimum 20 + width = Math.max(20, Integer.parseInt(args[1])); // Ensure minimum 20 + maxHeight = Math.max(20, Integer.parseInt(args[2])); // Ensure minimum 20 } catch (NumberFormatException e) { sender.sendMessage("§cInvalid number format. Use: /jumpgen "); return true; @@ -539,6 +547,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St // ✅ Use the new class to generate the puzzle JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, length, width, maxHeight); generator.generate(); + activeJumpPuzzles.add(generator); sender.sendMessage("§aJumping puzzle generated inside a cage!"); return true; @@ -553,9 +562,9 @@ public boolean onCommand(CommandSender sender, Command command, String label, St int length, width, maxHeight; try { - length = Integer.parseInt(args[0]); - width = Integer.parseInt(args[1]); - maxHeight = Integer.parseInt(args[2]); + length = Math.max(20, Integer.parseInt(args[0])); // Ensure minimum 20 + width = Math.max(20, Integer.parseInt(args[1])); // Ensure minimum 20 + maxHeight = Math.max(20, Integer.parseInt(args[2])); // Ensure minimum 20 } catch (NumberFormatException e) { sender.sendMessage("§cInvalid number format. Use: /clearpuzzle "); return true; From 37fcdc16137d6adb3b14b9c0a173ac57fdf1478a Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Fri, 14 Feb 2025 09:21:22 +0100 Subject: [PATCH 4/8] Added cleapuzzle and listpuzzle logic and commands. Also the puzzle creation takes a name in order to be able to track it --- .../killquest/JumpPuzzleGenerator.java | 45 ++++++++++++++----- .../killquest/JumpPuzzleListener.java | 6 +-- .../digitalwm/killquest/KillQuestPlugin.java | 27 +++++++---- src/main/resources/plugin.yml | 22 +++++++-- 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java index 121b5ea..69c3326 100644 --- a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java +++ b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java @@ -26,7 +26,11 @@ public class JumpPuzzleGenerator { private final Set jumpBlocks = new HashSet<>(); private final Map playerStartTimes = new HashMap<>(); - public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, int length, int width, int maxHeight) { + // List to track all blocks generated in the puzzle + private final List puzzleBlocks = new ArrayList<>(); + private final String puzzleName; // ✅ Declare puzzle name + + public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, String puzzleName, int length, int width, int maxHeight) { this.level = player.getLevel(); this.startPos = player.getPosition().floor(); this.length = length; @@ -34,6 +38,7 @@ public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, int length, in this.maxHeight = maxHeight; this.plugin = plugin; this.player = player; + this.puzzleName = puzzleName; // ✅ Store name this.setPuzzleBoundaries(this.startPos, this.width, this.length, this.maxHeight); } @@ -73,7 +78,6 @@ private void clearArea() { plugin.getLogger().info("Area cleared successfully!"); } - private void generateBase() { plugin.getLogger().info("Generating puzzle base with light-emitting blocks..."); @@ -92,7 +96,7 @@ private void generateBase() { for (int x = 0; x <= width; x++) { for (int z = 0; z <= length; z++) { Vector3 currentPos = new Vector3(baseStart.x + x, baseStart.y, baseStart.z + z); - + puzzleBlocks.add(currentPos); // ✅ Track base blocks // ✅ Place a Red Block in the exact center if (currentPos.x == centerBlock.x && currentPos.z == centerBlock.z) { level.setBlock(currentPos, Block.get(Block.REDSTONE_BLOCK)); @@ -116,16 +120,20 @@ private void generateWalls() { for (int y = 0; y <= maxHeight; y++) { for (int x = 0; x <= width; x++) { - // Left Wall - level.setBlock(new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z), Block.get(Block.GLASS)); - // Right Wall - level.setBlock(new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z + length), Block.get(Block.GLASS)); + Vector3 leftWall = new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z); + Vector3 rightWall = new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z + length); + level.setBlock(leftWall, Block.get(Block.GLASS)); + level.setBlock(rightWall, Block.get(Block.GLASS)); + puzzleBlocks.add(leftWall); + puzzleBlocks.add(rightWall); } for (int z = 0; z <= length; z++) { - // Front Wall - level.setBlock(new Vector3(baseStart.x, baseStart.y + y, baseStart.z + z), Block.get(Block.GLASS)); - // Back Wall - level.setBlock(new Vector3(baseStart.x + width, baseStart.y + y, baseStart.z + z), Block.get(Block.GLASS)); + Vector3 frontWall = new Vector3(baseStart.x, baseStart.y + y, baseStart.z + z); + Vector3 backWall = new Vector3(baseStart.x + width, baseStart.y + y, baseStart.z + z); + level.setBlock(frontWall, Block.get(Block.GLASS)); + level.setBlock(backWall, Block.get(Block.GLASS)); + puzzleBlocks.add(frontWall); + puzzleBlocks.add(backWall); } } @@ -163,6 +171,7 @@ private void generatePuzzle() { startBlock = lastBlock.clone(); level.setBlock(startBlock, Block.get(Block.GOLD_BLOCK)); + puzzleBlocks.add(startBlock); int currentHeight = 0; int blocksAtCurrentHeight = 0; @@ -223,8 +232,8 @@ private void generatePuzzle() { // ✅ Place the jump block Vector3 newBlock = new Vector3(newX, startPos.y + currentHeight, newZ); + puzzleBlocks.add(newBlock); level.setBlock(newBlock, Block.get(Block.STONE)); - jumpBlocks.add(newBlock); plugin.getLogger().info("Placed block at x: " + newBlock.x + " z: " + newBlock.z); @@ -242,6 +251,7 @@ private void generatePuzzle() { // ✅ Place end block endBlock = lastBlock.clone(); level.setBlock(endBlock, Block.get(Block.DIAMOND_BLOCK)); + puzzleBlocks.add(endBlock); plugin.getLogger().info("Jump puzzle generated successfully!"); } @@ -280,4 +290,15 @@ public void handlePlayerMovement(PlayerMoveEvent event) { } } } + + public void removePuzzle() { + plugin.getLogger().info("Removing jump puzzle..."); + + for (Vector3 blockPos : puzzleBlocks) { + level.setBlock(blockPos, Block.get(Block.AIR)); + } + + puzzleBlocks.clear(); // Clear the list after removal + plugin.getLogger().info("Jump puzzle removed successfully!"); + } } diff --git a/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java b/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java index 977963f..4fb47fe 100644 --- a/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java +++ b/src/main/java/com/digitalwm/killquest/JumpPuzzleListener.java @@ -21,7 +21,7 @@ public void onPlayerMove(PlayerMoveEvent event) { Player player = event.getPlayer(); // ✅ Cycle through all active puzzles and trigger their event handling - for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles()) { + for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles().values()) { // ✅ Fix applied if (puzzle.isPlayerInside(player)) { puzzle.handlePlayerMovement(event); break; // Stop checking after finding one matching puzzle @@ -34,7 +34,7 @@ public void onBlockPlace(BlockPlaceEvent event) { Player player = event.getPlayer(); Vector3 pos = event.getBlock().getLocation(); - for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles()) { + for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles().values()) { // ✅ Fix applied if (puzzle.isPlayerInside(player)) { event.setCancelled(true); player.sendMessage("§cYou cannot place blocks inside the jumping puzzle!"); @@ -48,7 +48,7 @@ public void onBlockBreak(BlockBreakEvent event) { Player player = event.getPlayer(); Vector3 pos = event.getBlock().getLocation(); - for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles()) { + for (JumpPuzzleGenerator puzzle : plugin.getActiveJumpPuzzles().values()) { // ✅ Fix applied if (puzzle.isPlayerInside(player)) { event.setCancelled(true); player.sendMessage("§cYou cannot break blocks inside the jumping puzzle!"); diff --git a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java index d5dc470..f81fef5 100644 --- a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java +++ b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java @@ -71,7 +71,7 @@ public class KillQuestPlugin extends PluginBase implements Listener, CommandExec private EconomyAPI economy = null; // Active Jump Puzzles - private final List activeJumpPuzzles = new ArrayList<>(); + private final Map activeJumpPuzzles = new HashMap<>(); public Map getActiveQuests() { return activeQuests; @@ -117,7 +117,7 @@ public void run() { getServer().getPluginManager().registerEvents(new JumpPuzzleListener(this), this); } - public List getActiveJumpPuzzles() { + public Map getActiveJumpPuzzles() { return activeJumpPuzzles; } @@ -529,27 +529,36 @@ public boolean onCommand(CommandSender sender, Command command, String label, St // Handle Jump Puzzle Generation if (command.getName().equalsIgnoreCase("jumpgen")) { - if (args.length < 3) { - sender.sendMessage("§cUsage: /jumpgen "); + if (args.length < 4) { + sender.sendMessage("§cUsage: /jumpgen "); return false; } int length, width, maxHeight; + + String puzzleName = args[0]; + + // ✅ Check for duplicate names + if (activeJumpPuzzles.containsKey(puzzleName)) { + sender.sendMessage("§cA jump puzzle with this name already exists!"); + return true; + } + try { length = Math.max(20, Integer.parseInt(args[0])); // Ensure minimum 20 width = Math.max(20, Integer.parseInt(args[1])); // Ensure minimum 20 maxHeight = Math.max(20, Integer.parseInt(args[2])); // Ensure minimum 20 } catch (NumberFormatException e) { - sender.sendMessage("§cInvalid number format. Use: /jumpgen "); + sender.sendMessage("§cInvalid number format. Use: /jumpgen "); return true; } // ✅ Use the new class to generate the puzzle - JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, length, width, maxHeight); + JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, puzzleName, length, width, maxHeight); generator.generate(); - activeJumpPuzzles.add(generator); + activeJumpPuzzles.put(puzzleName, generator); - sender.sendMessage("§aJumping puzzle generated inside a cage!"); + sender.sendMessage("§aJump puzzle '" + puzzleName + "' generated!"); return true; } @@ -571,7 +580,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } getLogger().info("Clearing puzzle area for player " + player.getName() + "..."); - JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, length, width, maxHeight); + JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, "clear", length, width, maxHeight); generator.clearOnly(); sender.sendMessage("§aJump puzzle area cleared!"); getLogger().info("Puzzle area successfully cleared."); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 750a040..049c6b5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -8,13 +8,23 @@ commands: usage: "/quests" jumpgen: description: "Generates a random jumping puzzle." - usage: "/jumpgen " + usage: "/jumpgen " permission: killquest.jumpgen aliases: [jp, jumppuzzle] - clearpuzzle: + cleararea: description: "Clear an area ahead of the puzzle" - usage: "/clearpuzzle " + usage: "/cleararea " permission: killquest.clear + aliases: [ca] + clearpuzzle: + description: "Clear an create puzzle" + usage: "/clearpuzzle " + permission: killquest.clearpuzzle + aliases: [cp] + listpuzzle: + description: "List all generated puzzles" + usage: "/listpuzzle" + permission: killquest:listpuzzle permissions: killquest.jumpgen: @@ -22,4 +32,10 @@ permissions: default: op killquest.clear: description: "Allows the player to clear an area for jump puzzle." + default: op + killquest.clearpuzzle: + description: "Allows the player to clear a create jump puzzle." + default: op + killquest.listpuzzle: + description: "List all generated puzzles." default: op \ No newline at end of file From 7f2ea7ccd56fdfa9df37c3e4edff6fd1372129b7 Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Sat, 15 Feb 2025 17:42:12 +0100 Subject: [PATCH 5/8] Updated build script to fix some warnings and handle UTF8. Also change the build command to mvn clean package --- pom.xml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94911e4..7e69fe2 100644 --- a/pom.xml +++ b/pom.xml @@ -11,18 +11,33 @@ 1.0.1 - install + package ${basedir}/src/main/java org.apache.maven.plugins maven-compiler-plugin + 3.8.1 1.8 1.8 + UTF-8 + + -Xlint:unchecked + -Xlint:deprecation + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + UTF-8 + + From d0305e1c3accc67f6c54f9efe262fdbfec5f46bb Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Sat, 15 Feb 2025 17:42:53 +0100 Subject: [PATCH 6/8] update the handled commands in context of jumpingpuzzles --- src/main/resources/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 049c6b5..94961f8 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -8,7 +8,7 @@ commands: usage: "/quests" jumpgen: description: "Generates a random jumping puzzle." - usage: "/jumpgen " + usage: "/jumpgen " permission: killquest.jumpgen aliases: [jp, jumppuzzle] cleararea: From 2bcedec976075ccbb39e729108d4340d78443b0b Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Sat, 15 Feb 2025 17:43:23 +0100 Subject: [PATCH 7/8] implemented saving and loading of puzzles, and update the logic of creation, clearing and listing existing puzzles --- .../killquest/JumpPuzzleGenerator.java | 161 +++++++++++++++--- .../digitalwm/killquest/KillQuestPlugin.java | 112 +++++++++++- 2 files changed, 240 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java index 69c3326..434c001 100644 --- a/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java +++ b/src/main/java/com/digitalwm/killquest/JumpPuzzleGenerator.java @@ -6,6 +6,8 @@ import cn.nukkit.math.Vector3; import cn.nukkit.event.player.PlayerMoveEvent; import me.onebone.economyapi.EconomyAPI; +import java.util.HashMap; +import java.util.Map; import java.util.*; @@ -15,7 +17,7 @@ public class JumpPuzzleGenerator { private final Level level; private final Vector3 startPos; private final int length, width, maxHeight; - private final Player player; +// private final Player player; private final KillQuestPlugin plugin; private Vector3 puzzleMin; @@ -23,21 +25,21 @@ public class JumpPuzzleGenerator { private Vector3 startBlock; private Vector3 endBlock; - private final Set jumpBlocks = new HashSet<>(); private final Map playerStartTimes = new HashMap<>(); // List to track all blocks generated in the puzzle - private final List puzzleBlocks = new ArrayList<>(); + + private final Map puzzleBlocks = new HashMap<>(); // ✅ Stores block type + private final String puzzleName; // ✅ Declare puzzle name - public JumpPuzzleGenerator(KillQuestPlugin plugin, Player player, String puzzleName, int length, int width, int maxHeight) { - this.level = player.getLevel(); - this.startPos = player.getPosition().floor(); + public JumpPuzzleGenerator(KillQuestPlugin plugin, Level level, Vector3 startPos, String puzzleName, int length, int width, int maxHeight) { + this.level = level; + this.startPos = startPos; this.length = length; this.width = width; this.maxHeight = maxHeight; this.plugin = plugin; - this.player = player; this.puzzleName = puzzleName; // ✅ Store name this.setPuzzleBoundaries(this.startPos, this.width, this.length, this.maxHeight); } @@ -49,6 +51,10 @@ public void setPuzzleBoundaries(Vector3 startPos, int width, int length, int max plugin.getLogger().info("Puzzle boundaries set: " + puzzleMin + " to " + puzzleMax); } + public String getPuzzleName() { + return puzzleName; + } + public void generate() { clearArea(); generateBase(); @@ -96,7 +102,7 @@ private void generateBase() { for (int x = 0; x <= width; x++) { for (int z = 0; z <= length; z++) { Vector3 currentPos = new Vector3(baseStart.x + x, baseStart.y, baseStart.z + z); - puzzleBlocks.add(currentPos); // ✅ Track base blocks + trackBlock(currentPos, "BASE"); // ✅ Place a Red Block in the exact center if (currentPos.x == centerBlock.x && currentPos.z == centerBlock.z) { level.setBlock(currentPos, Block.get(Block.REDSTONE_BLOCK)); @@ -124,23 +130,23 @@ private void generateWalls() { Vector3 rightWall = new Vector3(baseStart.x + x, baseStart.y + y, baseStart.z + length); level.setBlock(leftWall, Block.get(Block.GLASS)); level.setBlock(rightWall, Block.get(Block.GLASS)); - puzzleBlocks.add(leftWall); - puzzleBlocks.add(rightWall); + trackBlock(leftWall, "WALL"); + trackBlock(rightWall, "WALL"); } for (int z = 0; z <= length; z++) { Vector3 frontWall = new Vector3(baseStart.x, baseStart.y + y, baseStart.z + z); Vector3 backWall = new Vector3(baseStart.x + width, baseStart.y + y, baseStart.z + z); level.setBlock(frontWall, Block.get(Block.GLASS)); level.setBlock(backWall, Block.get(Block.GLASS)); - puzzleBlocks.add(frontWall); - puzzleBlocks.add(backWall); + trackBlock(frontWall, "WALL"); + trackBlock(backWall, "WALL"); } } plugin.getLogger().info("Walls generated successfully!"); } - private Vector3 getForwardDirection(Vector3 position) { +/* private Vector3 getForwardDirection(Vector3 position) { float yaw = (float) this.player.getYaw(); // Get player's yaw (rotation) if (yaw >= -45 && yaw <= 45) { // Facing Z+ @@ -152,12 +158,10 @@ private Vector3 getForwardDirection(Vector3 position) { } else { // Facing X+ return new Vector3(1, 0, 0); } - } + }*/ public void clearOnly() { - plugin.getLogger().info("Starting area cleanup for jump puzzle..."); clearArea(); - plugin.getLogger().info("Jump puzzle area cleared successfully!"); } private void generatePuzzle() { @@ -171,7 +175,7 @@ private void generatePuzzle() { startBlock = lastBlock.clone(); level.setBlock(startBlock, Block.get(Block.GOLD_BLOCK)); - puzzleBlocks.add(startBlock); + trackBlock(startBlock, "START"); // ✅ Track start block int currentHeight = 0; int blocksAtCurrentHeight = 0; @@ -232,10 +236,10 @@ private void generatePuzzle() { // ✅ Place the jump block Vector3 newBlock = new Vector3(newX, startPos.y + currentHeight, newZ); - puzzleBlocks.add(newBlock); + trackBlock(newBlock, "JUMP"); // ✅ Track jump blocks level.setBlock(newBlock, Block.get(Block.STONE)); - plugin.getLogger().info("Placed block at x: " + newBlock.x + " z: " + newBlock.z); + plugin.getLogger().debug("Placed block at x: " + newBlock.x + " z: " + newBlock.z); blocksAtCurrentHeight++; @@ -251,7 +255,7 @@ private void generatePuzzle() { // ✅ Place end block endBlock = lastBlock.clone(); level.setBlock(endBlock, Block.get(Block.DIAMOND_BLOCK)); - puzzleBlocks.add(endBlock); + trackBlock(endBlock, "END"); // ✅ Track end block plugin.getLogger().info("Jump puzzle generated successfully!"); } @@ -292,13 +296,120 @@ public void handlePlayerMovement(PlayerMoveEvent event) { } public void removePuzzle() { - plugin.getLogger().info("Removing jump puzzle..."); + plugin.getLogger().info("Removing puzzle '" + puzzleName + "'..."); + + // ✅ Iterate over the saved block positions and remove them + for (Map.Entry entry : puzzleBlocks.entrySet()) { + Vector3 pos = entry.getKey(); + level.setBlock(pos, Block.get(Block.AIR)); + } + + // ✅ Clear the block tracking map + puzzleBlocks.clear(); + + plugin.getLogger().info("Puzzle '" + puzzleName + "' removed successfully!"); + } + + public Map toMap() { + Map data = new HashMap<>(); + data.put("name", puzzleName); + data.put("startX", startPos.x); + data.put("startY", startPos.y); + data.put("startZ", startPos.z); + data.put("length", length); + data.put("width", width); + data.put("maxHeight", maxHeight); + + List> blockList = new ArrayList<>(); + for (Map.Entry entry : puzzleBlocks.entrySet()) { + Map blockData = new HashMap<>(); + blockData.put("x", entry.getKey().x); + blockData.put("y", entry.getKey().y); + blockData.put("z", entry.getKey().z); + blockData.put("type", entry.getValue()); // ✅ Save block type + blockList.add(blockData); + } + data.put("puzzleBlocks", blockList); + + return data; + } + + public static JumpPuzzleGenerator fromMap(KillQuestPlugin plugin, Map data) { + if (!data.containsKey("name") || !data.containsKey("length") || !data.containsKey("width") || + !data.containsKey("maxHeight") || !data.containsKey("startX") || + !data.containsKey("startY") || !data.containsKey("startZ")) { + plugin.getLogger().warning("Skipping puzzle load: Missing required fields."); + return null; // ✅ Skip invalid puzzles + } + + String name = (String) data.get("name"); + int length = ((Number) data.get("length")).intValue(); + int width = ((Number) data.get("width")).intValue(); + int maxHeight = ((Number) data.get("maxHeight")).intValue(); + + int startX = ((Number) data.get("startX")).intValue(); + int startY = ((Number) data.get("startY")).intValue(); + int startZ = ((Number) data.get("startZ")).intValue(); + Vector3 startPos = new Vector3(startX, startY, startZ); // ✅ Load correct position + + // ✅ Get the default level + Level defaultLevel = plugin.getServer().getDefaultLevel(); + if (defaultLevel == null) { + plugin.getLogger().warning("No default level found. Cannot load puzzle: " + name); + return null; + } - for (Vector3 blockPos : puzzleBlocks) { - level.setBlock(blockPos, Block.get(Block.AIR)); + JumpPuzzleGenerator puzzle = new JumpPuzzleGenerator(plugin, defaultLevel, startPos, name, length, width, maxHeight); + + // ✅ Ensure saved blocks exist before accessing them + if (data.containsKey("puzzleBlocks")) { + @SuppressWarnings("unchecked") + List> blocksData = (List>) data.get("puzzleBlocks"); + Map puzzleBlocks = new HashMap<>(); // Create a modifiable map + for (Map blockData : blocksData) { + if (blockData.containsKey("x") && blockData.containsKey("y") && blockData.containsKey("z") && blockData.containsKey("type")) { + int x = ((Number) blockData.get("x")).intValue(); + int y = ((Number) blockData.get("y")).intValue(); + int z = ((Number) blockData.get("z")).intValue(); + String type = (String) blockData.get("type"); + Vector3 blockPos = new Vector3(x, y, z); + puzzleBlocks.put(blockPos, type); // Use the modifiable map + + // ✅ Check if the block is the start or end block + if ("START".equals(type)) { + puzzle.startBlock = blockPos; + } else if ("END".equals(type)) { + puzzle.endBlock = blockPos; + } + } else { + plugin.getLogger().warning("Skipping invalid block entry in puzzle: " + name); + } + } + // Update the puzzle with the modified blocks + puzzle.setPuzzleBlocks(puzzleBlocks); + } else { + plugin.getLogger().warning("Puzzle " + name + " has no saved blocks."); } - puzzleBlocks.clear(); // Clear the list after removal - plugin.getLogger().info("Jump puzzle removed successfully!"); + return puzzle; + } + + private void trackBlock(Vector3 pos, String type) { + puzzleBlocks.put(pos, type); + } + + public Map getPuzzleBlocks() { + return Collections.unmodifiableMap(this.puzzleBlocks); + } + + // Method to update the internal map with new entries + public void updatePuzzleBlocks(Map newBlocks) { + this.puzzleBlocks.putAll(newBlocks); + } + + // Method to clear and update the internal map with new entries + public void setPuzzleBlocks(Map newBlocks) { + this.puzzleBlocks.clear(); + this.puzzleBlocks.putAll(newBlocks); } } diff --git a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java index f81fef5..04ad006 100644 --- a/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java +++ b/src/main/java/com/digitalwm/killquest/KillQuestPlugin.java @@ -25,6 +25,8 @@ import cn.nukkit.item.Item; import cn.nukkit.utils.Config; +import java.io.IOException; + // Import form API classes using the proper packages: import cn.nukkit.form.window.FormWindowSimple; import cn.nukkit.form.response.FormResponseSimple; @@ -73,6 +75,8 @@ public class KillQuestPlugin extends PluginBase implements Listener, CommandExec // Active Jump Puzzles private final Map activeJumpPuzzles = new HashMap<>(); + private File puzzlesFile; + public Map getActiveQuests() { return activeQuests; } @@ -115,6 +119,26 @@ public void run() { getServer().getScheduler().scheduleRepeatingTask(this, new QuestScoreboardUpdater(this), 40); getServer().getPluginManager().registerEvents(new ScoreboardListeners(this), this); getServer().getPluginManager().registerEvents(new JumpPuzzleListener(this), this); + + getLogger().info("Loading saved puzzles..."); + + // Ensure data folder exists + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + + // Initialize puzzle file if missing (but do NOT overwrite existing ones) + puzzlesFile = new File(getDataFolder(), "puzzles.yml"); + if (!puzzlesFile.exists()) { + try { + puzzlesFile.createNewFile(); + getLogger().info("Created new puzzles.yml file."); + } catch (IOException e) { + getLogger().warning("Failed to create puzzles.yml: " + e.getMessage()); + } + } + + loadJumpPuzzles(); // ✅ Load puzzles AFTER ensuring file exists } public Map getActiveJumpPuzzles() { @@ -127,6 +151,8 @@ public void onDisable() { saveActiveQuestProgress(playerName); } getLogger().info("KillQuestPlugin disabled and active quest progress saved."); + saveJumpPuzzles(); + getLogger().info("Puzzles saved."); } private void logBanner() { @@ -545,27 +571,28 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } try { - length = Math.max(20, Integer.parseInt(args[0])); // Ensure minimum 20 - width = Math.max(20, Integer.parseInt(args[1])); // Ensure minimum 20 - maxHeight = Math.max(20, Integer.parseInt(args[2])); // Ensure minimum 20 + length = Math.max(20, Integer.parseInt(args[1])); // Ensure minimum 20 + width = Math.max(20, Integer.parseInt(args[2])); // Ensure minimum 20 + maxHeight = Math.max(20, Integer.parseInt(args[3])); // Ensure minimum 20 } catch (NumberFormatException e) { sender.sendMessage("§cInvalid number format. Use: /jumpgen "); return true; } // ✅ Use the new class to generate the puzzle - JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, puzzleName, length, width, maxHeight); + JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player.getLevel(), player.getPosition().floor(), puzzleName, length, width, maxHeight); generator.generate(); activeJumpPuzzles.put(puzzleName, generator); + saveJumpPuzzles(); // ✅ Save immediately after generation sender.sendMessage("§aJump puzzle '" + puzzleName + "' generated!"); return true; } // ✅ Handle Puzzle Area Clearing - if (command.getName().equalsIgnoreCase("clearpuzzle")) { + if (command.getName().equalsIgnoreCase("cleararea")) { if (args.length < 3) { - sender.sendMessage("§cUsage: /clearpuzzle "); + sender.sendMessage("§cUsage: /cleararea "); return false; } @@ -575,18 +602,54 @@ public boolean onCommand(CommandSender sender, Command command, String label, St width = Math.max(20, Integer.parseInt(args[1])); // Ensure minimum 20 maxHeight = Math.max(20, Integer.parseInt(args[2])); // Ensure minimum 20 } catch (NumberFormatException e) { - sender.sendMessage("§cInvalid number format. Use: /clearpuzzle "); + sender.sendMessage("§cInvalid number format. Use: /cleararea "); return true; } getLogger().info("Clearing puzzle area for player " + player.getName() + "..."); - JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player, "clear", length, width, maxHeight); + JumpPuzzleGenerator generator = new JumpPuzzleGenerator(this, player.getLevel(), player.getPosition().floor(), "clear", length, width, maxHeight); generator.clearOnly(); sender.sendMessage("§aJump puzzle area cleared!"); getLogger().info("Puzzle area successfully cleared."); return true; } + // Handle Puzzle clear + if (command.getName().equalsIgnoreCase("clearpuzzle")) { + if (args.length < 1) { + sender.sendMessage("§cUsage: /clearpuzzle "); + return false; + } + + String puzzleName = args[0]; + JumpPuzzleGenerator puzzle = activeJumpPuzzles.get(puzzleName); + + if (puzzle == null) { + sender.sendMessage("§cNo puzzle found with that name."); + return true; + } + + puzzle.removePuzzle(); // ✅ Call new remove function + activeJumpPuzzles.remove(puzzleName); + saveJumpPuzzles(); // ✅ Save changes + + sender.sendMessage("§aJump puzzle '" + puzzleName + "' cleared!"); + return true; + } + + // Handle List puzzle + if (command.getName().equalsIgnoreCase("listpuzzle")) { + if (activeJumpPuzzles.isEmpty()) { + sender.sendMessage("§cNo puzzles available."); + } else { + sender.sendMessage("§aActive Jump Puzzles:"); + for (String puzzleName : activeJumpPuzzles.keySet()) { + sender.sendMessage("§6- " + puzzleName); + } + } + return true; + } + return false; } @@ -737,4 +800,37 @@ public void onEntityDeath(EntityDeathEvent event) { String getPlayerLanguage(Player player) { return player.getLoginChainData().getLanguageCode(); // ✅ This works! } + + public void saveJumpPuzzles() { + List> puzzleData = new ArrayList<>(); + for (JumpPuzzleGenerator puzzle : activeJumpPuzzles.values()) { + puzzleData.add(puzzle.toMap()); + } + Config config = new Config(puzzlesFile, Config.YAML); + config.set("puzzles", puzzleData); + config.save(); + } + + public void loadJumpPuzzles() { + if (!puzzlesFile.exists()) { + getLogger().info("No saved puzzles found."); + return; + } + + Config config = new Config(puzzlesFile, Config.YAML); + + // ✅ Explicitly cast to List> to ensure type safety + @SuppressWarnings("unchecked") + List> puzzleData = (List>) (List) config.getMapList("puzzles"); + + for (Map data : puzzleData) { + JumpPuzzleGenerator puzzle = JumpPuzzleGenerator.fromMap(this, data); + if (puzzle != null) { // ✅ Ensure puzzle is valid before adding + activeJumpPuzzles.put(puzzle.getPuzzleName(), puzzle); + getLogger().info("Loaded puzzle: " + puzzle.getPuzzleName()); + } else { + getLogger().warning("Skipping invalid puzzle entry."); + } + } + } } \ No newline at end of file From 31cd83210efc8e41d98967d82de777179ae15d41 Mon Sep 17 00:00:00 2001 From: Dan Harabagiu Date: Sat, 15 Feb 2025 17:51:18 +0100 Subject: [PATCH 8/8] Updated README to reflect the new features --- README.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6cb7f95..cab2294 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ -## **KillQuest - A Nukkit Quest System** -**KillQuest** is a **feature-rich quest system** for **Nukkit** servers, allowing players to complete **kill and gather quests** for rewards. It includes **multilingual support**, **scoreboard tracking**, and **EconomyAPI integration**. +## **KillQuest - A Nukkit Quest System** v1.0.1 +**KillQuest** is a **feature-rich quest system** for **Nukkit** servers, allowing players to complete **kill and gather quests** for rewards, and challenge themselves with procedurally generated jumping puzzles. It includes **multilingual support**, **scoreboard tracking**, and **EconomyAPI integration**. + +--- + +## Changes to 1.0.1 + +- Added Handling of onPlayerFish event so fishing quests can be created +- Added complete new loging of generating jumping puzzles --- @@ -25,7 +32,11 @@ ✔ **Auto-Saving:** Quest progress is saved to files per player. ✔ **EconomyAPI Integration:** Players earn credits upon completion. ✔ **Multilingual Support:** Uses Minecraft’s translations for entity/item names. - +✔ **Jumping Puzzles**: Procedurally generate jumping puzzles with varying heights and difficulties. +✔ **Puzzle Persistence**: Puzzles are saved and can be reloaded after server restarts. +✔ **Player Movement Tracking**: Detect when players start and complete puzzles. +✔ **Block Restrictions**: Prevent players from modifying puzzle areas. +✔ **Puzzle Management Commands**: Create, list, and remove puzzles dynamically. --- ## **🎮 How to Use** @@ -47,12 +58,22 @@ - **Quest progress resets** - **Scoreboard updates** +### How the Jump Puzzle Works +1. A player generates a puzzle using `/jumpgen`. +2. The puzzle is enclosed with walls and a light-emitting base. +3. Players must jump across blocks that are generated to be challenging but solvable. +4. A tracking system monitors when a player starts and completes the puzzle. +5. Upon completion, the player receives 100 credits via EconomyAPI. + --- ## **📝 Configuration** ### **`quests.yml`** Quests are defined in `plugins/KillQuestPlugin/quests.yml`: +### `puzzles.yml` +Stores all active puzzles, including blocks, start, and end positions, ensuring persistence after restarts. + ```yaml quests: - name: "Zombie Slayer" @@ -95,6 +116,9 @@ translations: |---------|------------| | `/quests` | Opens the quest selection UI | | `/killquest reload` | Reloads quests and translations | +| `/jumpgen ` | Generates a jumping puzzle with a unique name. | +| `/clearpuzzle ` | Clears a specific puzzle. | +| `/listpuzzles` | Lists all active puzzles. | --- @@ -123,7 +147,7 @@ translations: --- ## **💡 Future Improvements** -- ✅ **More Quest Types:** Fishing, Crafting, Mining +- ✅ **More Quest Types:** Crafting - ✅ **Permissions Support** - ✅ **More Customization Options** - ✅ **SQL Database support**