Files
spigot-quest-board/src/main/java/games/dmg/spigotquestboard/QuestCommand.java
T
dmg 5ca939be6c
Release / release (push) Successful in 2m22s
CI / build (push) Successful in 1m8s
feat(quests): add issuer quest cancellation
2026-09-05 07:54:23 -04:00

169 lines
6.6 KiB
Java

package games.dmg.spigotquestboard;
import java.io.IOException;
import java.time.Clock;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
final class QuestCommand implements CommandExecutor, TabCompleter {
private static final List<String> QUANTITIES = List.of("1", "16", "32", "64");
private final QuestCreationGateway creator;
private final QuestBrowser browser;
private final QuestCompletionGateway completer;
private final QuestCancellationGateway canceller;
private final Clock clock;
QuestCommand(QuestCreationGateway creator, QuestBrowser browser, Clock clock) {
this(creator, browser, null, null, clock);
}
QuestCommand(
QuestCreationGateway creator,
QuestBrowser browser,
QuestCompletionGateway completer,
Clock clock
) {
this(creator, browser, completer, null, clock);
}
QuestCommand(
QuestCreationGateway creator,
QuestBrowser browser,
QuestCompletionGateway completer,
QuestCancellationGateway canceller,
Clock clock
) {
this.creator = Objects.requireNonNull(creator, "creator");
this.browser = Objects.requireNonNull(browser, "browser");
this.completer = completer;
this.canceller = canceller;
this.clock = Objects.requireNonNull(clock, "clock");
}
@Override
public boolean onCommand(
CommandSender sender, Command command, String label, String[] arguments
) {
if (arguments.length == 0
|| (arguments.length == 1 && "list".equalsIgnoreCase(arguments[0]))) {
Instant now = clock.instant();
sender.sendMessage(QuestListingFormatter.formatAll(browser.activeQuests(now), now));
return true;
}
if (arguments.length == 2 && "complete".equalsIgnoreCase(arguments[0])) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can complete quests with inventory items.");
return true;
}
if (completer == null) {
sender.sendMessage("Quest completion is unavailable.");
return true;
}
try {
completer.complete(player, arguments[1], clock.instant());
} catch (IllegalArgumentException | IllegalStateException exception) {
sender.sendMessage(exception.getMessage());
} catch (IOException exception) {
sender.sendMessage(
"The quest could not be saved. Your delivered blocks were restored."
);
}
return true;
}
if (arguments.length == 2 && "cancel".equalsIgnoreCase(arguments[0])) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can cancel quests.");
return true;
}
if (canceller == null) {
sender.sendMessage("Quest cancellation is unavailable.");
return true;
}
try {
canceller.cancel(player, arguments[1], clock.instant());
} catch (IllegalArgumentException | IllegalStateException exception) {
sender.sendMessage(exception.getMessage());
} catch (IOException exception) {
sender.sendMessage(
"The quest could not be saved. It remains active and its reward remains escrowed."
);
}
return true;
}
if (arguments.length != 3 || !"create".equalsIgnoreCase(arguments[0])) {
usage(sender);
return true;
}
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can create quests with inventory rewards.");
return true;
}
final int quantity;
try {
quantity = Integer.parseInt(arguments[2]);
} catch (NumberFormatException exception) {
sender.sendMessage("Quest quantity must be a positive whole number.");
return true;
}
try {
Quest quest = creator.create(player, arguments[1], quantity, clock.instant());
sender.sendMessage("Quest " + quest.id() + " created. Your exact held stack is now escrowed.");
} catch (IllegalArgumentException exception) {
sender.sendMessage(exception.getMessage());
} catch (IOException exception) {
sender.sendMessage("The quest could not be saved. Your held reward was restored.");
}
return true;
}
@Override
public List<String> onTabComplete(
CommandSender sender, Command command, String alias, String[] arguments
) {
if (!(sender instanceof Player)) {
return List.of();
}
if (arguments.length == 1) {
return startsWith(List.of("create", "list", "complete", "cancel"), arguments[0]);
}
if (arguments.length == 2 && "create".equalsIgnoreCase(arguments[0])) {
return creator.suggestBlockMaterials(arguments[1]);
}
if (arguments.length == 3 && "create".equalsIgnoreCase(arguments[0])) {
return startsWith(QUANTITIES, arguments[2]);
}
if (arguments.length == 2 && "complete".equalsIgnoreCase(arguments[0])) {
return startsWith(browser.completableQuestIds(clock.instant()), arguments[1]);
}
if (arguments.length == 2 && "cancel".equalsIgnoreCase(arguments[0])) {
Player player = (Player) sender;
return startsWith(
browser.cancellableQuestIds(player.getUniqueId(), clock.instant()), arguments[1]
);
}
return List.of();
}
private static List<String> startsWith(List<String> candidates, String prefix) {
String normalized = prefix.toLowerCase(Locale.ROOT);
return candidates.stream()
.filter(candidate -> candidate.toLowerCase(Locale.ROOT).startsWith(normalized))
.toList();
}
private static void usage(CommandSender sender) {
sender.sendMessage(
"Usage: /quests [list] | /quests create <block> <quantity> | "
+ "/quests complete <quest> | /quests cancel <quest>"
);
sender.sendMessage("Hold the entire reward stack in your main hand; its exact metadata will be escrowed.");
}
}