feat(settings): add native base control dialog
Release / release (push) Successful in 2m45s
CI / build (push) Successful in 1m18s

This commit is contained in:
dmg
2026-08-24 10:54:26 -04:00
parent b2ba4d5821
commit b6bbf9183a
10 changed files with 589 additions and 12 deletions
@@ -15,7 +15,7 @@ import org.bukkit.inventory.PlayerInventory;
final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
private static final List<String> SETTINGS = List.of(
"status", "upgrade", "pocket", "visitors", "navigation", "flight", "border",
"ui", "status", "upgrade", "pocket", "visitors", "navigation", "flight", "border",
"spawnable", "bossbar"
);
private static final List<String> VISITOR_MODES = List.of("allowed", "blocked");
@@ -58,7 +58,11 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
}
PlayerState state = stateManager.player(player.getUniqueId(), player.getName());
if (arguments.length == 0
|| arguments.length == 1 && arguments[0].equalsIgnoreCase("status")) {
|| arguments.length == 1 && arguments[0].equalsIgnoreCase("ui")) {
showDialog(player, state);
return true;
}
if (arguments.length == 1 && arguments[0].equalsIgnoreCase("status")) {
showStatus(player, state);
return true;
}
@@ -441,6 +445,13 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
return true;
}
private void showDialog(Player player, PlayerState state) {
PocketBaseState pocket = pocketBases == null
? PocketBaseState.locked(state.playerId())
: pocketBases.state(state.playerId());
player.showDialog(new BaseSettingsDialogFactory(settings.current()).create(state, pocket));
}
private void showStatus(Player player, PlayerState state) {
player.sendMessage(ChatColor.GOLD + "=== Base Progress ===");
showBasePath(player, state);
@@ -663,7 +674,7 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
private static void sendUsage(Player player) {
player.sendMessage(ChatColor.RED + "Usage: /basesettings "
+ "[status|upgrade|pocket upgrade|pocket mobs "
+ "[ui|status|upgrade|pocket upgrade|pocket mobs "
+ "<hostile|passive> <enable|disable>"
+ "|pocket type <void|nether|overworld> <subtype>"
+ "|visitors <allowed|blocked>|navigation <enable|disable>"
@@ -0,0 +1,353 @@
package games.dmg.spigotbase;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.dialog.ConfirmationDialog;
import net.md_5.bungee.api.dialog.Dialog;
import net.md_5.bungee.api.dialog.DialogBase;
import net.md_5.bungee.api.dialog.DialogListDialog;
import net.md_5.bungee.api.dialog.MultiActionDialog;
import net.md_5.bungee.api.dialog.NoticeDialog;
import net.md_5.bungee.api.dialog.action.ActionButton;
import net.md_5.bungee.api.dialog.action.RunCommandAction;
import net.md_5.bungee.api.dialog.body.PlainMessageBody;
final class BaseSettingsDialogFactory {
private static final int DIALOG_WIDTH = 420;
private final PluginSettings settings;
BaseSettingsDialogFactory(PluginSettings settings) {
this.settings = settings;
}
Dialog create(PlayerState owner, PocketBaseState pocket) {
return new DialogListDialog(
base(
"Base Settings",
"Base Settings",
"Base " + owner.baseLevel() + "/4 • Size " + owner.sizeLevel() + "/3"
+ " • Flight " + owner.flightLevel() + "/3\n"
+ "Pocket Base " + pocket.level() + ""
+ humanize(pocket.biome().commandName())
),
List.of(
generalSettings(owner),
progressReport(),
baseUpgrade(owner),
pocketSettings(owner, pocket)
),
null,
2,
190
);
}
private Dialog generalSettings(PlayerState owner) {
return new MultiActionDialog(
base(
"Base Controls",
"Base Controls",
"Select a setting to change it. Locked controls remain visible and explain "
+ "their requirement in chat when selected."
),
List.of(
toggle(
"Visitors",
owner.baseLevel() >= 4,
owner.visitorsEnabled(),
"Allowed",
"Blocked",
"basesettings visitors "
+ (owner.visitorsEnabled() ? "blocked" : "allowed")
),
toggle(
"Navigation",
owner.baseLevel() >= 2,
owner.navigationEnabled(),
"Enabled",
"Disabled",
"basesettings navigation "
+ mode(!owner.navigationEnabled())
),
toggle(
"Flight",
owner.flightLevel() >= 1,
owner.flightEnabled(),
"Enabled",
"Disabled",
"basesettings flight " + mode(!owner.flightEnabled())
),
toggle(
"Border",
owner.baseLevel() >= 1 && owner.base().isPresent(),
owner.borderEnabled(),
"Enabled",
"Disabled",
"basesettings border " + mode(!owner.borderEnabled())
),
toggle(
"Spawnable Overlay",
owner.baseLevel() >= 1
&& owner.base().isPresent()
&& owner.totalBlocksPlaced()
>= settings.spawnableOverlayUnlockPlacements(),
owner.spawnableOverlayEnabled(),
"Enabled",
"Disabled",
"basesettings spawnable " + mode(!owner.spawnableOverlayEnabled())
),
toggle(
"Progress Boss Bar",
true,
owner.bossBarEnabled(),
"Enabled",
"Disabled",
"basesettings bossbar " + mode(!owner.bossBarEnabled())
)
),
2,
null
);
}
private Dialog progressReport() {
return new NoticeDialog(base(
"Progress Report",
"Progress Report",
"Display the detailed progression report in chat."
)).action(commandButton("Show in Chat", "basesettings status"));
}
private Dialog baseUpgrade(PlayerState owner) {
if (owner.baseLevel() >= 4) {
return notice("Base IV", "Base IV and visitor access are already unlocked.");
}
if (owner.baseLevel() < 3 || owner.base().isEmpty()) {
return notice(
"Base IV — Locked",
"Unlock and establish Base III before purchasing Base IV visitor access."
);
}
int price = settings.visitorUnlockDiamondCost();
return confirmation(
"Unlock Base IV",
"Purchase visitor access for " + price + " diamonds?",
"basesettings upgrade"
);
}
private Dialog pocketSettings(PlayerState owner, PocketBaseState pocket) {
return new DialogListDialog(
base(
"Pocket Base",
"Pocket Base " + pocket.level(),
pocket.level() == 0
? "Pocket Base is locked."
: "Biome: " + pocket.biome().worldType().commandName() + "/"
+ pocket.biome().commandName() + "\nHostile mobs: "
+ enabled(pocket.hostileMobSpawningEnabled()) + " • Passive mobs: "
+ enabled(pocket.passiveMobSpawningEnabled())
),
List.of(
pocketUpgrade(owner, pocket),
pocketMobSettings(pocket),
pocketBiomeSettings(pocket)
),
null,
1,
260
);
}
private Dialog pocketUpgrade(PlayerState owner, PocketBaseState pocket) {
if (owner.baseLevel() < 4 || owner.base().isEmpty()) {
return notice(
"Pocket Upgrade — Locked",
"Establish Base IV before purchasing a Pocket Base."
);
}
int price = pocket.level() == 0
? settings.pocketBaseUnlockCost()
: settings.pocketBaseUpgradeCost();
String currency = humanize(settings.pocketBaseCurrencyMaterial());
String title = pocket.level() == 0
? "Unlock Pocket Base I"
: "Upgrade to Pocket Base " + (pocket.level() + 1);
return confirmation(
title,
"Purchase this upgrade for " + price + " " + currency + "?",
"basesettings pocket upgrade"
);
}
private Dialog pocketMobSettings(PocketBaseState pocket) {
if (pocket.level() < 1) {
return notice(
"Mob Spawning — Locked",
"Unlock Pocket Base I to control natural mob spawning."
);
}
return new MultiActionDialog(
base(
"Mob Spawning",
"Mob Spawning",
"Control natural spawning independently for each mob category."
),
List.of(
toggle(
"Hostile Mobs",
true,
pocket.hostileMobSpawningEnabled(),
"Enabled",
"Disabled",
"basesettings pocket mobs hostile "
+ mode(!pocket.hostileMobSpawningEnabled())
),
toggle(
"Passive Mobs",
true,
pocket.passiveMobSpawningEnabled(),
"Enabled",
"Disabled",
"basesettings pocket mobs passive "
+ mode(!pocket.passiveMobSpawningEnabled())
)
),
1,
null
);
}
private Dialog pocketBiomeSettings(PocketBaseState pocket) {
if (pocket.level() < 1) {
return notice(
"Change Biome — Locked",
"Unlock Pocket Base I to purchase biome changes."
);
}
List<Dialog> worldTypes = Arrays.stream(PocketBaseWorldType.values())
.map(type -> pocketBiomeType(pocket, type))
.map(Dialog.class::cast)
.toList();
return new DialogListDialog(
base(
"Change Biome",
"Change Biome",
"Current biome: " + pocket.biome().worldType().commandName() + "/"
+ pocket.biome().commandName() + "\nEach change costs "
+ settings.pocketBaseBiomeChangeCost() + " "
+ humanize(settings.pocketBaseBiomeCurrencyMaterial()) + "."
),
worldTypes,
null,
1,
220
);
}
private DialogListDialog pocketBiomeType(
PocketBaseState pocket,
PocketBaseWorldType worldType
) {
List<Dialog> biomes = Arrays.stream(PocketBaseBiome.values())
.filter(biome -> biome.worldType() == worldType)
.map(biome -> biomeConfirmation(pocket, biome))
.map(Dialog.class::cast)
.toList();
return new DialogListDialog(
base(
humanize(worldType.commandName()),
humanize(worldType.commandName()) + " Biomes",
"Choose a biome subtype. The active biome is marked as selected."
),
biomes,
null,
2,
190
);
}
private Dialog biomeConfirmation(PocketBaseState pocket, PocketBaseBiome biome) {
String title = humanize(biome.commandName())
+ (pocket.biome() == biome ? " — Selected" : "");
if (pocket.biome() == biome) {
return notice(title, "This is your Pocket Base's current biome.");
}
return confirmation(
title,
"Change the Pocket Base biome for " + settings.pocketBaseBiomeChangeCost()
+ " " + humanize(settings.pocketBaseBiomeCurrencyMaterial()) + "?",
"basesettings pocket type " + biome.worldType().commandName() + " "
+ biome.commandName()
);
}
private static Dialog confirmation(String title, String message, String command) {
return new ConfirmationDialog(base(title, title, message))
.yes(commandButton("Confirm", command));
}
private static Dialog notice(String title, String message) {
return new NoticeDialog(base(title, title, message));
}
private static ActionButton toggle(
String name,
boolean unlocked,
boolean active,
String activeLabel,
String inactiveLabel,
String command
) {
String state = unlocked ? (active ? activeLabel : inactiveLabel) : "Locked";
String tooltip = unlocked
? "Click to change this setting."
: "This setting has not been unlocked yet.";
return new ActionButton(
text(name + ": " + state),
text(tooltip),
190,
new RunCommandAction("/" + command)
);
}
private static ActionButton commandButton(String label, String command) {
return new ActionButton(text(label), new RunCommandAction("/" + command));
}
private static DialogBase base(String title, String externalTitle, String message) {
return new DialogBase(text(title))
.externalTitle(text(externalTitle))
.body(List.of(new PlainMessageBody(text(message), DIALOG_WIDTH)))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.AfterAction.CLOSE);
}
private static BaseComponent text(String value) {
return new TextComponent(value);
}
private static String mode(boolean active) {
return active ? "enable" : "disable";
}
private static String enabled(boolean active) {
return active ? "enabled" : "disabled";
}
private static String humanize(String value) {
String normalized = value.toLowerCase(Locale.ROOT).replace('_', ' ');
StringBuilder result = new StringBuilder(normalized.length());
boolean capitalize = true;
for (int index = 0; index < normalized.length(); index++) {
char character = normalized.charAt(index);
result.append(capitalize ? Character.toUpperCase(character) : character);
capitalize = character == ' ';
}
return result.toString();
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ commands:
aliases: [visit]
basesettings:
description: View progression, upgrade, and manage base settings.
usage: /basesettings [status|upgrade|pocket|visitors|navigation|flight|border|spawnable|bossbar]
usage: /basesettings [ui|status|upgrade|pocket|visitors|navigation|flight|border|spawnable|bossbar]
aliases: [homesettings]
baseadmin:
description: Administer Spigot Base.