From 3cd1ab6290b98eb4fd22d649a71680d768290c33 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Sat, 8 Aug 2026 14:19:10 -0400 Subject: [PATCH] feat(commands): report aura progress --- design/log.md | 1 + .../us-004-check-personal-progress.md | 14 +++--- .../dmg/creeperfear/CreeperFearPlugin.java | 5 ++ .../command/CreeperAuraCommand.java | 49 +++++++++++++++++++ .../creeperfear/command/ProgressMessages.java | 24 +++++++++ src/main/resources/plugin.yml | 9 ++++ .../command/ProgressMessagesTest.java | 31 ++++++++++++ 7 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 src/main/java/games/dmg/creeperfear/command/CreeperAuraCommand.java create mode 100644 src/main/java/games/dmg/creeperfear/command/ProgressMessages.java create mode 100644 src/test/java/games/dmg/creeperfear/command/ProgressMessagesTest.java diff --git a/design/log.md b/design/log.md index cb1c5fd..6cdcb0e 100644 --- a/design/log.md +++ b/design/log.md @@ -17,3 +17,4 @@ description: Chronological record of material changes to the Spigot Creeper Fear - Completed US-002 with per-tier rank advancement, online aura state, creeper block protection, rank damage multipliers, and rank VI cancellation. - Completed US-003 with temporary configurable boss bars, current-tier progress presentation, full-screen rank-up titles, and maximum-rank hiding. - Extended US-001 so a self-destructing creeper awards deduplicated progress and normal feedback to every player its explosion hits. +- Completed US-004 with an asynchronous self-service progress command, ordinary-player permission, completion output, and command metadata. diff --git a/design/user-stories/us-004-check-personal-progress.md b/design/user-stories/us-004-check-personal-progress.md index 3047b33..53c852d 100644 --- a/design/user-stories/us-004-check-personal-progress.md +++ b/design/user-stories/us-004-check-personal-progress.md @@ -2,7 +2,7 @@ type: User Story title: "US-004: Check personal progress" description: Let players request their current Creeper Aura rank and tier progress through a command. -status: backlog +status: done --- # US-004: Check personal progress @@ -11,12 +11,12 @@ As a **player**, I want a command that reports my Creeper Aura progress so that ## Acceptance criteria -- [ ] `/creeperaura progress` reports the player's current locked or ranked state and current-tier progress. -- [ ] Before rank VI, the response reports the next rank requirement and the number of additional kills needed. -- [ ] At rank VI, the response clearly reports that progression is complete. -- [ ] The self-service command is available to ordinary players without administrative permission. -- [ ] Console use, invalid arguments, and unavailable player data produce clear responses. -- [ ] Command usage is included in plugin help and command metadata. +- [x] `/creeperaura progress` reports the player's current locked or ranked state and current-tier progress. +- [x] Before rank VI, the response reports the next rank requirement and the number of additional kills needed. +- [x] At rank VI, the response clearly reports that progression is complete. +- [x] The self-service command is available to ordinary players without administrative permission. +- [x] Console use, invalid arguments, and unavailable player data produce clear responses. +- [x] Command usage is included in plugin help and command metadata. ## Related diff --git a/src/main/java/games/dmg/creeperfear/CreeperFearPlugin.java b/src/main/java/games/dmg/creeperfear/CreeperFearPlugin.java index 1ac3b61..3a1044b 100644 --- a/src/main/java/games/dmg/creeperfear/CreeperFearPlugin.java +++ b/src/main/java/games/dmg/creeperfear/CreeperFearPlugin.java @@ -2,6 +2,8 @@ package games.dmg.creeperfear; import games.dmg.creeperfear.aura.AuraRules; import games.dmg.creeperfear.aura.CreeperAuraListener; +import games.dmg.creeperfear.command.CreeperAuraCommand; +import games.dmg.creeperfear.command.ProgressMessages; import games.dmg.creeperfear.feedback.ProgressDisplay; import games.dmg.creeperfear.feedback.ProgressFeedback; import games.dmg.creeperfear.listener.CreeperDeathListener; @@ -10,6 +12,7 @@ import games.dmg.creeperfear.listener.PlayerSessionListener; import games.dmg.creeperfear.progress.ProgressService; import games.dmg.creeperfear.progress.SqliteProgressRepository; import java.nio.file.Path; +import java.util.Objects; import java.util.logging.Level; import org.bukkit.plugin.java.JavaPlugin; @@ -34,6 +37,8 @@ public final class CreeperFearPlugin extends JavaPlugin { new PlayerSessionListener(progressService, getLogger()), this); getServer().getPluginManager().registerEvents( new CreeperAuraListener(progressService, auraRules), this); + Objects.requireNonNull(getCommand("creeperaura"), "creeperaura command") + .setExecutor(new CreeperAuraCommand(this, progressService, new ProgressMessages(auraRules))); getServer().getOnlinePlayers().forEach(player -> progressService.loadOnline(player.getUniqueId()) .exceptionally(failure -> { getLogger().log(Level.SEVERE, diff --git a/src/main/java/games/dmg/creeperfear/command/CreeperAuraCommand.java b/src/main/java/games/dmg/creeperfear/command/CreeperAuraCommand.java new file mode 100644 index 0000000..57e2ad4 --- /dev/null +++ b/src/main/java/games/dmg/creeperfear/command/CreeperAuraCommand.java @@ -0,0 +1,49 @@ +package games.dmg.creeperfear.command; + +import games.dmg.creeperfear.progress.AuraRank; +import games.dmg.creeperfear.progress.PlayerProgress; +import games.dmg.creeperfear.progress.ProgressService; +import java.util.logging.Level; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +public final class CreeperAuraCommand implements CommandExecutor { + private final JavaPlugin plugin; + private final ProgressService progressService; + private final ProgressMessages messages; + + public CreeperAuraCommand(JavaPlugin plugin, ProgressService progressService, ProgressMessages messages) { + this.plugin = plugin; + this.progressService = progressService; + this.messages = messages; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length != 1 || !args[0].equalsIgnoreCase("progress")) { + return false; + } + if (!(sender instanceof Player player)) { + sender.sendMessage(ChatColor.RED + "This form of the command can only be used by a player."); + return true; + } + + progressService.find(player.getUniqueId()).whenComplete((stored, failure) -> + plugin.getServer().getScheduler().runTask(plugin, () -> { + if (failure != null) { + plugin.getLogger().log(Level.SEVERE, + "Could not read Creeper Aura progress for " + player.getUniqueId(), failure); + sender.sendMessage(ChatColor.RED + "Creeper Aura progress is temporarily unavailable."); + return; + } + PlayerProgress progress = stored.orElseGet(() -> new PlayerProgress( + player.getUniqueId(), player.getName(), AuraRank.LOCKED, 0)); + sender.sendMessage(ChatColor.GREEN + messages.describe(progress)); + })); + return true; + } +} diff --git a/src/main/java/games/dmg/creeperfear/command/ProgressMessages.java b/src/main/java/games/dmg/creeperfear/command/ProgressMessages.java new file mode 100644 index 0000000..993e933 --- /dev/null +++ b/src/main/java/games/dmg/creeperfear/command/ProgressMessages.java @@ -0,0 +1,24 @@ +package games.dmg.creeperfear.command; + +import games.dmg.creeperfear.aura.AuraRules; +import games.dmg.creeperfear.progress.AuraRank; +import games.dmg.creeperfear.progress.PlayerProgress; + +public final class ProgressMessages { + private final AuraRules rules; + + public ProgressMessages(AuraRules rules) { + this.rules = rules; + } + + public String describe(PlayerProgress progress) { + if (progress.rank().isMaximum()) { + return "Creeper Aura VI — maximum rank reached; progression complete."; + } + int requirement = rules.requirementForCurrentRank(progress.rank()); + int remaining = Math.max(0, requirement - progress.tierKills()); + String rank = progress.rank() == AuraRank.LOCKED ? "Locked" : progress.rank().name(); + return "Creeper Aura " + rank + " — " + progress.tierKills() + "/" + requirement + + " current-tier kills; " + remaining + " remaining."; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3ea427a..df2b7dc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,3 +4,12 @@ main: games.dmg.creeperfear.CreeperFearPlugin api-version: '26.2' author: dmg.games description: Unlock Creeper Aura ranks by defeating creepers. +commands: + creeperaura: + description: Check and administer Creeper Aura progression. + usage: / progress + permission: creeperfear.progress +permissions: + creeperfear.progress: + description: Check personal Creeper Aura progression. + default: true diff --git a/src/test/java/games/dmg/creeperfear/command/ProgressMessagesTest.java b/src/test/java/games/dmg/creeperfear/command/ProgressMessagesTest.java new file mode 100644 index 0000000..10fa024 --- /dev/null +++ b/src/test/java/games/dmg/creeperfear/command/ProgressMessagesTest.java @@ -0,0 +1,31 @@ +package games.dmg.creeperfear.command; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import games.dmg.creeperfear.aura.AuraRules; +import games.dmg.creeperfear.progress.AuraRank; +import games.dmg.creeperfear.progress.PlayerProgress; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class ProgressMessagesTest { + private final ProgressMessages messages = new ProgressMessages(AuraRules.defaults()); + + @Test + void reportsCurrentTierAndRemainingKills() { + PlayerProgress progress = new PlayerProgress(UUID.randomUUID(), "Player", AuraRank.II, 40); + + assertEquals( + "Creeper Aura II — 40/100 current-tier kills; 60 remaining.", + messages.describe(progress)); + } + + @Test + void reportsCompletedProgressAtRankSix() { + PlayerProgress progress = new PlayerProgress(UUID.randomUUID(), "Player", AuraRank.VI, 0); + + assertEquals( + "Creeper Aura VI — maximum rank reached; progression complete.", + messages.describe(progress)); + } +}