Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3be3e43072 | ||
|
|
9692df9101 |
@@ -21,7 +21,7 @@ As a **player with Base I**, I want to unlock controlled flight around my base s
|
|||||||
- [x] Flight II expands the vertical range to 100 blocks below and above base Y by default.
|
- [x] Flight II expands the vertical range to 100 blocks below and above base Y by default.
|
||||||
- [x] Flight III expands the vertical range to the world's minimum and maximum build heights.
|
- [x] Flight III expands the vertical range to the world's minimum and maximum build heights.
|
||||||
- [x] A configurable five-block horizontal warning buffer extends beyond the current base radius.
|
- [x] A configurable five-block horizontal warning buffer extends beyond the current base radius.
|
||||||
- [x] Plugin-granted flight remains active in the warning buffer and displays prominent on-screen notice that the player is leaving the base.
|
- [x] Plugin-granted flight remains active in the warning buffer, and a prominent on-screen notice that the player is leaving the base appears only while the player is actively flying.
|
||||||
- [x] Passing beyond the warning buffer removes only flight granted by this plugin.
|
- [x] Passing beyond the warning buffer removes only flight granted by this plugin.
|
||||||
- [x] Flight is not granted outside the unlocked vertical range.
|
- [x] Flight is not granted outside the unlocked vertical range.
|
||||||
- [x] `/basesettings flight enable` enables unlocked flight idempotently, and `/basesettings flight disable` disables it idempotently.
|
- [x] `/basesettings flight enable` enables unlocked flight idempotently, and `/basesettings flight disable` disables it idempotently.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ As a **player with Base III**, I want to open my base to visitors so that other
|
|||||||
- [x] `/gotobase <owner>` autocompletes bases that the requesting player is currently eligible to visit.
|
- [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] `/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] Enabled bases remain visitable while their owners are offline.
|
||||||
- [x] A visitor teleport uses the destination owner's current warm-up tier.
|
- [x] A visitor teleport uses the destination owner's current warm-up tier, but always requires at least a one-second stationary warm-up; `/visit` and `/gotobase` never teleport instantly.
|
||||||
- [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.
|
- [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.
|
||||||
- [x] Cancellation or destination failure does not consume a visitor cooldown.
|
- [x] Cancellation or destination failure does not consume a visitor cooldown.
|
||||||
- [x] A safe destination is resolved at or near the owner's recorded base center.
|
- [x] A safe destination is resolved at or near the owner's recorded base center.
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ final class BaseFlightController implements Runnable {
|
|||||||
player.setAllowFlight(true);
|
player.setAllowFlight(true);
|
||||||
grantedFlight.add(player.getUniqueId());
|
grantedFlight.add(player.getUniqueId());
|
||||||
}
|
}
|
||||||
if (distanceSquared > (double) radius * radius) {
|
if (distanceSquared > (double) radius * radius && player.isFlying()) {
|
||||||
if (warned.add(player.getUniqueId())) {
|
if (warned.add(player.getUniqueId())) {
|
||||||
player.sendTitle(
|
player.sendTitle(
|
||||||
ChatColor.RED + "Leaving Your Base",
|
ChatColor.RED + "Leaving Your Base",
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ final class BaseTeleportManager implements Listener {
|
|||||||
begin(
|
begin(
|
||||||
visitor,
|
visitor,
|
||||||
owner.base().orElseThrow(),
|
owner.base().orElseThrow(),
|
||||||
policy.warmup(owner),
|
policy.visitorWarmup(owner),
|
||||||
owner.playerId(),
|
owner.playerId(),
|
||||||
policy.cooldown(owner),
|
policy.cooldown(owner),
|
||||||
"visit " + owner.latestName() + "'s base"
|
"visit " + owner.latestName() + "'s base"
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ public final class TeleportPolicy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Duration visitorWarmup(PlayerState owner) {
|
||||||
|
Duration warmup = warmup(owner);
|
||||||
|
return warmup.compareTo(Duration.ofSeconds(1)) < 0
|
||||||
|
? Duration.ofSeconds(1)
|
||||||
|
: warmup;
|
||||||
|
}
|
||||||
|
|
||||||
public Duration cooldown(PlayerState player) {
|
public Duration cooldown(PlayerState player) {
|
||||||
return switch (player.cooldownLevel()) {
|
return switch (player.cooldownLevel()) {
|
||||||
case 0 -> Duration.ofSeconds(settings.current().initialTeleportCooldownSeconds());
|
case 0 -> Duration.ofSeconds(settings.current().initialTeleportCooldownSeconds());
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package games.dmg.spigotbase;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
final class BaseFlightControllerTest {
|
||||||
|
private final UUID playerId = UUID.randomUUID();
|
||||||
|
private final UUID worldId = UUID.randomUUID();
|
||||||
|
private final Server server = mock(Server.class);
|
||||||
|
private final BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||||
|
private final Player player = mock(Player.class);
|
||||||
|
private final PlayerState state = PlayerState.newPlayer(playerId, "Alex")
|
||||||
|
.withGrassAndDirtProgress(250, 1)
|
||||||
|
.withBase(new BaseLocation(worldId, "world", 0, 64, 0, 0, 0), Instant.EPOCH)
|
||||||
|
.withFlightLevel(1, true);
|
||||||
|
private final BaseFlightController controller = new BaseFlightController(
|
||||||
|
server,
|
||||||
|
stateManager,
|
||||||
|
new SecondaryProgressionService(PluginSettings.from(Map.of())),
|
||||||
|
new BaseBoundsService(PluginSettings.from(Map.of())),
|
||||||
|
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
||||||
|
);
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
World world = mock(World.class);
|
||||||
|
Location location = mock(Location.class);
|
||||||
|
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||||
|
|
||||||
|
doReturn(List.of(player)).when(server).getOnlinePlayers();
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
when(player.getName()).thenReturn("Alex");
|
||||||
|
when(player.getGameMode()).thenReturn(org.bukkit.GameMode.SURVIVAL);
|
||||||
|
when(player.getInventory()).thenReturn(inventory);
|
||||||
|
when(inventory.getStorageContents()).thenReturn(new ItemStack[0]);
|
||||||
|
when(stateManager.player(playerId, "Alex")).thenReturn(state);
|
||||||
|
when(player.getWorld()).thenReturn(world);
|
||||||
|
when(world.getUID()).thenReturn(worldId);
|
||||||
|
when(player.getLocation()).thenReturn(location);
|
||||||
|
when(location.getX()).thenReturn(12.5);
|
||||||
|
when(location.getZ()).thenReturn(0.5);
|
||||||
|
when(location.getBlockY()).thenReturn(64);
|
||||||
|
when(player.getAllowFlight()).thenReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void walkingInWarningBufferDoesNotShowLeavingBaseWarning() {
|
||||||
|
when(player.isFlying()).thenReturn(false);
|
||||||
|
|
||||||
|
controller.run();
|
||||||
|
|
||||||
|
verify(player, never()).sendTitle(anyString(), anyString(), anyInt(), anyInt(), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void flyingInWarningBufferShowsLeavingBaseWarning() {
|
||||||
|
when(player.isFlying()).thenReturn(true);
|
||||||
|
|
||||||
|
controller.run();
|
||||||
|
|
||||||
|
verify(player).sendTitle(anyString(), anyString(), anyInt(), anyInt(), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void warningCanAppearAgainAfterPlayerStopsAndResumesFlyingInBuffer() {
|
||||||
|
when(player.isFlying()).thenReturn(true, false, true);
|
||||||
|
|
||||||
|
controller.run();
|
||||||
|
controller.run();
|
||||||
|
controller.run();
|
||||||
|
|
||||||
|
verify(player, times(2)).sendTitle(anyString(), anyString(), anyInt(), anyInt(), anyInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,17 @@ final class TeleportPolicyTest {
|
|||||||
assertEquals(Duration.ZERO, policy.warmup(player.withTeleportLevels(3, 0)));
|
assertEquals(Duration.ZERO, policy.warmup(player.withTeleportLevels(3, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void visitorWarmupHasOneSecondMinimum() {
|
||||||
|
PlayerState player = baseThreePlayer();
|
||||||
|
|
||||||
|
assertEquals(Duration.ofSeconds(30), policy.visitorWarmup(player));
|
||||||
|
assertEquals(
|
||||||
|
Duration.ofSeconds(1),
|
||||||
|
policy.visitorWarmup(player.withTeleportLevels(3, 0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void cooldownLevelsUseApprovedDurations() {
|
void cooldownLevelsUseApprovedDurations() {
|
||||||
PlayerState player = baseThreePlayer();
|
PlayerState player = baseThreePlayer();
|
||||||
|
|||||||
Reference in New Issue
Block a user