Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c9dc5b361 | ||
|
|
00aa7566ad | ||
|
|
84e013ac11 |
@@ -22,13 +22,13 @@ The plugin JAR is written to `build/libs/`.
|
||||
## Player commands
|
||||
|
||||
```text
|
||||
/setbase
|
||||
/base
|
||||
/base upgrade
|
||||
/basenavigation
|
||||
/baseflight
|
||||
/basevisitors
|
||||
/gotobase <player>
|
||||
/setbase (alias: /sethome)
|
||||
/base (alias: /home)
|
||||
/base upgrade (alias: /home upgrade)
|
||||
/basenavigation [on|off] (alias: /homenavigation)
|
||||
/baseflight [on|off] (alias: /homeflight)
|
||||
/basevisitors [on|off] (alias: /homevisitors)
|
||||
/gotobase <player> (alias: /visit <player>)
|
||||
/baseprogress
|
||||
/baseprogress bossbar
|
||||
```
|
||||
|
||||
@@ -28,6 +28,11 @@ description: Chronological record of material decisions affecting the Spigot Bas
|
||||
- Verified the implementation with `./gradlew clean check jar`: 34 tests passed and the plugin JAR was produced successfully.
|
||||
- User stories remain in progress pending live-server integration verification and completion of runtime administrative configuration editing.
|
||||
|
||||
## 2026-08-09 — Navigation preference fix
|
||||
|
||||
- Added explicit, idempotent `/basenavigation on` and `/basenavigation off` modes with autocomplete while retaining no-argument toggling.
|
||||
- Preserved a disabled navigation preference during later grass-or-dirt progression instead of forcing navigation back on.
|
||||
|
||||
## 2026-08-09 — Initial release scope completed
|
||||
|
||||
- Added live, validated, persisted numeric configuration updates through `/baseadmin config`.
|
||||
@@ -35,3 +40,16 @@ description: Chronological record of material decisions affecting the Spigot Bas
|
||||
- Made progression materials, visitor currency, boss-bar duration, particle count, and title timing configurable.
|
||||
- Preserved unknown forward-compatible YAML fields for retained player records while continuing to reject invalid progression state.
|
||||
- Completed and verified all approved user stories with the automated Gradle check lifecycle.
|
||||
|
||||
## 2026-08-10 — Player command aliases
|
||||
|
||||
- Added `/sethome` for `/setbase`, `/home` for `/base`, and `/visit` for `/gotobase`.
|
||||
- Visitor autocomplete remains available for eligible offline bases through the `/visit` alias.
|
||||
- Verified the aliases and plugin build with `./gradlew clean check jar`.
|
||||
|
||||
## 2026-08-10 — Toggle command autocomplete
|
||||
|
||||
- Added explicit, idempotent `on` and `off` modes and autocomplete to base flight and visitor access while retaining no-argument toggling.
|
||||
- Added `/homenavigation`, `/homeflight`, and `/homevisitors` aliases with the same autocomplete as their canonical commands.
|
||||
- Generalized toggle preference resolution for navigation, flight, and visitor access.
|
||||
- Verified the implementation with `./gradlew clean check jar`.
|
||||
|
||||
@@ -17,6 +17,7 @@ As a **player**, I want to earn and set a personal base so that later quality-of
|
||||
- [x] Player-placed blocks may contribute when broken; natural-generation detection is not required.
|
||||
- [x] Base I unlocks when the player reaches the configured threshold, which defaults to 250 qualifying blocks.
|
||||
- [x] `/setbase` is unavailable before Base I and explains the unmet requirement.
|
||||
- [x] `/sethome` is an alias for `/setbase` with identical behavior.
|
||||
- [x] After Base I unlocks, `/setbase` records the player's current world and block location as the center of a cylindrical base.
|
||||
- [x] The initial cylinder has a configurable 10-block horizontal radius and extends a configurable 25 blocks above and 25 blocks below the set Y coordinate.
|
||||
- [x] The first successful `/setbase` is immediately available.
|
||||
|
||||
@@ -18,6 +18,10 @@ As a **player with Base I**, I want visual guidance toward my base so that I can
|
||||
- [x] Particle generation is bounded to avoid excessive server or client load.
|
||||
- [x] A player in another world receives a clear message instead of a misleading particle direction.
|
||||
- [x] `/basenavigation` toggles guidance on and off after Base II is unlocked.
|
||||
- [x] `/basenavigation on` enables guidance idempotently and `/basenavigation off` disables it idempotently.
|
||||
- [x] Invalid navigation arguments show command usage, and `on` and `off` are offered through autocomplete.
|
||||
- [x] `/homenavigation` aliases `/basenavigation` with identical behavior and autocomplete.
|
||||
- [x] Grass-or-dirt progress after Base II preserves the player's selected navigation preference.
|
||||
- [x] The navigation preference persists across reconnects and restarts.
|
||||
|
||||
## Related
|
||||
|
||||
@@ -25,6 +25,8 @@ As a **player with Base I**, I want to unlock controlled flight around my base s
|
||||
- [x] Passing beyond the warning buffer removes only flight granted by this plugin.
|
||||
- [x] Flight is not granted outside the unlocked vertical range.
|
||||
- [x] `/baseflight` toggles the player's unlocked base flight on and off.
|
||||
- [x] `/baseflight on` enables flight idempotently and `/baseflight off` disables it idempotently, with `on` and `off` offered through autocomplete.
|
||||
- [x] `/homeflight` aliases `/baseflight` with identical behavior and autocomplete.
|
||||
- [x] The flight toggle persists across reconnects and restarts.
|
||||
- [x] The plugin handles teleportation, world changes, game-mode changes, death, logout, and plugin shutdown without leaving unintended flight enabled.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ As a **player with Base II**, I want to earn `/base` so that I can return safely
|
||||
- [x] Only placements made in Survival mode and within the base's current horizontal and vertical bounds count.
|
||||
- [x] Player-placed blocks and replacement of previously broken blocks may contribute repeatedly.
|
||||
- [x] Base III unlocks `/base` with a configurable 30-second warm-up and three-hour cooldown by default.
|
||||
- [x] `/home` is an alias for `/base`, including `/home upgrade`.
|
||||
- [x] Looking around without changing block coordinates does not cancel the warm-up.
|
||||
- [x] Changing block X, Y, or Z, taking damage, teleporting, changing worlds, dying, disconnecting, or starting a conflicting teleport cancels the warm-up.
|
||||
- [x] Cancellation clearly informs the player and does not consume the cooldown.
|
||||
|
||||
@@ -16,8 +16,11 @@ As a **player with Base III**, I want to open my base to visitors so that other
|
||||
- [x] A successful purchase removes the complete price atomically from the player's direct inventory.
|
||||
- [x] Insufficient funds, an invalid state, or a failed persistence operation does not consume any diamonds or grant Base IV.
|
||||
- [x] `/basevisitors` lets a Base IV owner toggle visitor access on and off.
|
||||
- [x] `/basevisitors on` enables access idempotently and `/basevisitors off` disables it idempotently, with `on` and `off` offered through autocomplete.
|
||||
- [x] `/homevisitors` aliases `/basevisitors` with identical behavior and autocomplete.
|
||||
- [x] The visitor-access preference persists across reconnects and restarts.
|
||||
- [x] `/gotobase <owner>` autocompletes bases that the requesting player is currently eligible to visit.
|
||||
- [x] `/visit <owner>` aliases `/gotobase <owner>` with identical autocomplete, including eligible bases whose owners are offline.
|
||||
- [x] Enabled bases remain visitable while their owners are offline.
|
||||
- [x] A visitor teleport uses the destination owner's current warm-up tier.
|
||||
- [x] Looking around is permitted, while movement between block coordinates, damage, teleportation, world change, death, logout, or a conflicting teleport cancels the visitor warm-up.
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.bukkit.ChatColor;
|
||||
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 BaseFlightCommand implements CommandExecutor {
|
||||
final class BaseFlightCommand implements CommandExecutor, TabCompleter {
|
||||
private static final List<String> MODES = List.of("on", "off");
|
||||
private final BaseStateManager stateManager;
|
||||
private final BaseFlightController controller;
|
||||
|
||||
@@ -26,10 +30,17 @@ final class BaseFlightCommand implements CommandExecutor {
|
||||
player.sendMessage(ChatColor.RED + "Base flight is still locked.");
|
||||
return true;
|
||||
}
|
||||
final boolean enabled;
|
||||
try {
|
||||
enabled = TogglePreference.resolve(state.flightEnabled(), arguments);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
player.sendMessage(ChatColor.RED + "Usage: /baseflight [on|off]");
|
||||
return true;
|
||||
}
|
||||
state = stateManager.update(
|
||||
player.getUniqueId(),
|
||||
player.getName(),
|
||||
current -> current.withFlightEnabled(!current.flightEnabled())
|
||||
current -> current.withFlightEnabled(enabled)
|
||||
);
|
||||
if (!state.flightEnabled()) {
|
||||
controller.removeGrantedFlight(player);
|
||||
@@ -40,4 +51,18 @@ final class BaseFlightCommand implements CommandExecutor {
|
||||
+ ChatColor.YELLOW + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
CommandSender sender,
|
||||
Command command,
|
||||
String alias,
|
||||
String[] arguments
|
||||
) {
|
||||
if (arguments.length != 1) {
|
||||
return List.of();
|
||||
}
|
||||
String prefix = arguments[0].toLowerCase(Locale.ROOT);
|
||||
return MODES.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.bukkit.ChatColor;
|
||||
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 BaseNavigationCommand implements CommandExecutor {
|
||||
final class BaseNavigationCommand implements CommandExecutor, TabCompleter {
|
||||
private static final List<String> MODES = List.of("on", "off");
|
||||
|
||||
private final BaseStateManager stateManager;
|
||||
|
||||
BaseNavigationCommand(BaseStateManager stateManager) {
|
||||
@@ -28,10 +33,17 @@ final class BaseNavigationCommand implements CommandExecutor {
|
||||
player.sendMessage(ChatColor.RED + "Set your base before enabling navigation.");
|
||||
return true;
|
||||
}
|
||||
final boolean enabled;
|
||||
try {
|
||||
enabled = TogglePreference.resolve(state.navigationEnabled(), arguments);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
player.sendMessage(ChatColor.RED + "Usage: /basenavigation [on|off]");
|
||||
return true;
|
||||
}
|
||||
state = stateManager.update(
|
||||
player.getUniqueId(),
|
||||
player.getName(),
|
||||
current -> current.withNavigationEnabled(!current.navigationEnabled())
|
||||
current -> current.withNavigationEnabled(enabled)
|
||||
);
|
||||
stateManager.saveIfDirty();
|
||||
player.sendMessage(ChatColor.YELLOW + "Base navigation is now "
|
||||
@@ -44,4 +56,18 @@ final class BaseNavigationCommand implements CommandExecutor {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
CommandSender sender,
|
||||
Command command,
|
||||
String alias,
|
||||
String[] arguments
|
||||
) {
|
||||
if (arguments.length != 1) {
|
||||
return List.of();
|
||||
}
|
||||
String prefix = arguments[0].toLowerCase(Locale.ROOT);
|
||||
return MODES.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class BaseProgressionService {
|
||||
}
|
||||
boolean unlocked = baseLevel != previousLevel;
|
||||
PlayerState updated = player.withGrassAndDirtProgress(count, baseLevel);
|
||||
if (baseLevel >= 2 && !updated.navigationEnabled()) {
|
||||
if (unlocked && baseLevel == 2) {
|
||||
updated = updated.withNavigationEnabled(true);
|
||||
}
|
||||
return new ProgressionUpdate(updated, unlocked, false, false, false, false);
|
||||
|
||||
@@ -189,7 +189,7 @@ final class BaseTeleportManager implements Listener {
|
||||
public void onCommand(PlayerCommandPreprocessEvent event) {
|
||||
String command = event.getMessage().toLowerCase(Locale.ROOT).split("\\s+", 2)[0];
|
||||
if (command.equals("/base") || command.equals("/gotobase")
|
||||
|| command.equals("/spawn") || command.equals("/home")
|
||||
|| command.equals("/visit") || command.equals("/spawn") || command.equals("/home")
|
||||
|| command.equals("/tp") || command.equals("/teleport")) {
|
||||
cancel(event.getPlayer(), "Base teleport cancelled by another teleport command.");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.bukkit.ChatColor;
|
||||
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 BaseVisitorsCommand implements CommandExecutor {
|
||||
final class BaseVisitorsCommand implements CommandExecutor, TabCompleter {
|
||||
private static final List<String> MODES = List.of("on", "off");
|
||||
private final BaseStateManager stateManager;
|
||||
|
||||
BaseVisitorsCommand(BaseStateManager stateManager) {
|
||||
@@ -24,10 +28,17 @@ final class BaseVisitorsCommand implements CommandExecutor {
|
||||
player.sendMessage(ChatColor.RED + "Base IV visitor access is still locked.");
|
||||
return true;
|
||||
}
|
||||
final boolean enabled;
|
||||
try {
|
||||
enabled = TogglePreference.resolve(state.visitorsEnabled(), arguments);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
player.sendMessage(ChatColor.RED + "Usage: /basevisitors [on|off]");
|
||||
return true;
|
||||
}
|
||||
state = stateManager.update(
|
||||
player.getUniqueId(),
|
||||
player.getName(),
|
||||
current -> current.withVisitorsEnabled(!current.visitorsEnabled())
|
||||
current -> current.withVisitorsEnabled(enabled)
|
||||
);
|
||||
stateManager.saveIfDirty();
|
||||
player.sendMessage(ChatColor.YELLOW + "Visitor teleports are now "
|
||||
@@ -35,4 +46,18 @@ final class BaseVisitorsCommand implements CommandExecutor {
|
||||
+ ChatColor.YELLOW + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
CommandSender sender,
|
||||
Command command,
|
||||
String alias,
|
||||
String[] arguments
|
||||
) {
|
||||
if (arguments.length != 1) {
|
||||
return List.of();
|
||||
}
|
||||
String prefix = arguments[0].toLowerCase(Locale.ROOT);
|
||||
return MODES.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +71,15 @@ public final class SpigotBasePlugin extends JavaPlugin {
|
||||
command("baseprogress").setExecutor(
|
||||
new BaseProgressCommand(stateManager, settingsProvider)
|
||||
);
|
||||
command("basenavigation").setExecutor(new BaseNavigationCommand(stateManager));
|
||||
command("baseflight").setExecutor(new BaseFlightCommand(stateManager, flightController));
|
||||
command("basevisitors").setExecutor(new BaseVisitorsCommand(stateManager));
|
||||
BaseNavigationCommand navigationCommand = new BaseNavigationCommand(stateManager);
|
||||
command("basenavigation").setExecutor(navigationCommand);
|
||||
command("basenavigation").setTabCompleter(navigationCommand);
|
||||
BaseFlightCommand flightCommand = new BaseFlightCommand(stateManager, flightController);
|
||||
command("baseflight").setExecutor(flightCommand);
|
||||
command("baseflight").setTabCompleter(flightCommand);
|
||||
BaseVisitorsCommand visitorsCommand = new BaseVisitorsCommand(stateManager);
|
||||
command("basevisitors").setExecutor(visitorsCommand);
|
||||
command("basevisitors").setTabCompleter(visitorsCommand);
|
||||
GoToBaseCommand goToBaseCommand = new GoToBaseCommand(stateManager, teleportManager);
|
||||
command("gotobase").setExecutor(goToBaseCommand);
|
||||
command("gotobase").setTabCompleter(goToBaseCommand);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
final class TogglePreference {
|
||||
private TogglePreference() {
|
||||
}
|
||||
|
||||
static boolean resolve(boolean current, String[] arguments) {
|
||||
if (arguments.length == 0) {
|
||||
return !current;
|
||||
}
|
||||
if (arguments.length != 1) {
|
||||
throw new IllegalArgumentException("expected zero or one argument");
|
||||
}
|
||||
return switch (arguments[0].toLowerCase(Locale.ROOT)) {
|
||||
case "on" -> true;
|
||||
case "off" -> false;
|
||||
default -> throw new IllegalArgumentException("expected on or off");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,27 @@ commands:
|
||||
setbase:
|
||||
description: Set your unlocked personal base.
|
||||
usage: /setbase
|
||||
aliases: [sethome]
|
||||
base:
|
||||
description: Teleport to or upgrade your base.
|
||||
usage: /base [upgrade]
|
||||
aliases: [home]
|
||||
basenavigation:
|
||||
description: Toggle particle navigation toward your base.
|
||||
usage: /basenavigation
|
||||
usage: /basenavigation [on|off]
|
||||
aliases: [homenavigation]
|
||||
baseflight:
|
||||
description: Toggle flight within your base.
|
||||
usage: /baseflight
|
||||
usage: /baseflight [on|off]
|
||||
aliases: [homeflight]
|
||||
basevisitors:
|
||||
description: Toggle visitor access to your base.
|
||||
usage: /basevisitors
|
||||
usage: /basevisitors [on|off]
|
||||
aliases: [homevisitors]
|
||||
gotobase:
|
||||
description: Visit an available player base.
|
||||
usage: /gotobase <player>
|
||||
aliases: [visit]
|
||||
baseprogress:
|
||||
description: View progression or toggle progress boss bars.
|
||||
usage: /baseprogress [bossbar]
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.UnaryOperator;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class BaseFlightCommandTest {
|
||||
@Test
|
||||
void autocompletesExplicitModes() {
|
||||
BaseFlightCommand command = new BaseFlightCommand(
|
||||
mock(BaseStateManager.class),
|
||||
mock(BaseFlightController.class)
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
List.of("off"),
|
||||
command.onTabComplete(null, null, "baseflight", new String[] {"of"})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitOnIsIdempotent() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getName()).thenReturn("Builder");
|
||||
|
||||
PlayerState current = PlayerState.newPlayer(playerId, "Builder")
|
||||
.withAdministrativeLevels(1, 0, 1, 0, 0, false, true, false);
|
||||
AtomicReference<PlayerState> updated = new AtomicReference<>();
|
||||
BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||
when(stateManager.player(playerId, "Builder")).thenReturn(current);
|
||||
when(stateManager.update(any(), anyString(), any())).thenAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
UnaryOperator<PlayerState> operation = invocation.getArgument(2);
|
||||
PlayerState result = operation.apply(current);
|
||||
updated.set(result);
|
||||
return result;
|
||||
});
|
||||
|
||||
BaseFlightCommand command = new BaseFlightCommand(
|
||||
stateManager,
|
||||
mock(BaseFlightController.class)
|
||||
);
|
||||
command.onCommand(player, null, "baseflight", new String[] {"on"});
|
||||
|
||||
assertTrue(updated.get().flightEnabled());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -21,4 +22,18 @@ final class BaseNavigationProgressionTest {
|
||||
assertTrue(update.player().navigationEnabled());
|
||||
assertTrue(update.unlockedBaseLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
void laterProgressPreservesDisabledNavigationPreference() {
|
||||
BaseProgressionService service =
|
||||
new BaseProgressionService(PluginSettings.from(Map.of()));
|
||||
PlayerState player = PlayerState.newPlayer(UUID.randomUUID(), "Alex")
|
||||
.withGrassAndDirtProgress(500, 2)
|
||||
.withNavigationEnabled(false);
|
||||
|
||||
ProgressionUpdate update = service.recordGrassOrDirtBreak(player);
|
||||
|
||||
assertFalse(update.player().navigationEnabled());
|
||||
assertFalse(update.unlockedBaseLevel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.UnaryOperator;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class BaseVisitorsCommandTest {
|
||||
@Test
|
||||
void autocompletesExplicitModes() {
|
||||
BaseVisitorsCommand command = new BaseVisitorsCommand(mock(BaseStateManager.class));
|
||||
|
||||
assertEquals(
|
||||
List.of("on"),
|
||||
command.onTabComplete(null, null, "basevisitors", new String[] {"on"})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitOnIsIdempotent() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getName()).thenReturn("Host");
|
||||
|
||||
PlayerState current = PlayerState.newPlayer(playerId, "Host")
|
||||
.withAdministrativeLevels(4, 0, 0, 0, 0, false, false, true);
|
||||
AtomicReference<PlayerState> updated = new AtomicReference<>();
|
||||
BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||
when(stateManager.player(playerId, "Host")).thenReturn(current);
|
||||
when(stateManager.update(any(), anyString(), any())).thenAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
UnaryOperator<PlayerState> operation = invocation.getArgument(2);
|
||||
PlayerState result = operation.apply(current);
|
||||
updated.set(result);
|
||||
return result;
|
||||
});
|
||||
|
||||
BaseVisitorsCommand command = new BaseVisitorsCommand(stateManager);
|
||||
command.onCommand(player, null, "basevisitors", new String[] {"on"});
|
||||
|
||||
assertTrue(updated.get().visitorsEnabled());
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,60 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
final class PluginMetadataTest {
|
||||
@Test
|
||||
void sethomeAliasesSetbase() {
|
||||
Map<?, ?> commands = commands();
|
||||
|
||||
Map<?, ?> setbase = (Map<?, ?>) commands.get("setbase");
|
||||
assertEquals(List.of("sethome"), setbase.get("aliases"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void homeAliasesBase() {
|
||||
Map<?, ?> commands = commands();
|
||||
|
||||
Map<?, ?> base = (Map<?, ?>) commands.get("base");
|
||||
assertEquals(List.of("home"), base.get("aliases"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void homevisitorsAliasesBasevisitors() {
|
||||
Map<?, ?> commands = commands();
|
||||
|
||||
Map<?, ?> visitors = (Map<?, ?>) commands.get("basevisitors");
|
||||
assertEquals(List.of("homevisitors"), visitors.get("aliases"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void homeflightAliasesBaseflight() {
|
||||
Map<?, ?> commands = commands();
|
||||
|
||||
Map<?, ?> flight = (Map<?, ?>) commands.get("baseflight");
|
||||
assertEquals(List.of("homeflight"), flight.get("aliases"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void homenavigationAliasesBasenavigation() {
|
||||
Map<?, ?> commands = commands();
|
||||
|
||||
Map<?, ?> navigation = (Map<?, ?>) commands.get("basenavigation");
|
||||
assertEquals(List.of("homenavigation"), navigation.get("aliases"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitAliasesGotobase() {
|
||||
Map<?, ?> commands = commands();
|
||||
|
||||
Map<?, ?> gotobase = (Map<?, ?>) commands.get("gotobase");
|
||||
assertEquals(List.of("visit"), gotobase.get("aliases"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void declaresPluginEntrypointCommandsAndPermissions() {
|
||||
InputStream stream = getClass().getClassLoader().getResourceAsStream("plugin.yml");
|
||||
@@ -30,4 +79,11 @@ final class PluginMetadataTest {
|
||||
Map<?, ?> permissions = (Map<?, ?>) plugin.get("permissions");
|
||||
assertNotNull(permissions.get("spigotbase.admin"));
|
||||
}
|
||||
|
||||
private Map<?, ?> commands() {
|
||||
InputStream stream = getClass().getClassLoader().getResourceAsStream("plugin.yml");
|
||||
assertNotNull(stream);
|
||||
Map<?, ?> plugin = new Yaml().load(stream);
|
||||
return (Map<?, ?>) plugin.get("commands");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class TogglePreferenceTest {
|
||||
@Test
|
||||
void noArgumentTogglesCurrentPreference() {
|
||||
assertFalse(TogglePreference.resolve(true, new String[0]));
|
||||
assertTrue(TogglePreference.resolve(false, new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitModesAreIdempotent() {
|
||||
assertTrue(TogglePreference.resolve(true, new String[] {"on"}));
|
||||
assertTrue(TogglePreference.resolve(false, new String[] {"on"}));
|
||||
assertFalse(TogglePreference.resolve(true, new String[] {"off"}));
|
||||
assertFalse(TogglePreference.resolve(false, new String[] {"off"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnknownOrExtraArguments() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> TogglePreference.resolve(false, new String[] {"maybe"})
|
||||
);
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> TogglePreference.resolve(false, new String[] {"on", "off"})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user