feat(progression): unlock stealth from potion invisibility
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package games.dmg.spigotstealth;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class BukkitUnlockNotifierTest {
|
||||
@Test
|
||||
void sendsFullScreenTitleAndChatMessageToOnlinePlayer() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
when(player.isOnline()).thenReturn(true);
|
||||
Consumer<Runnable> immediateMainThread = Runnable::run;
|
||||
BukkitUnlockNotifier notifier = new BukkitUnlockNotifier(
|
||||
ignored -> player, immediateMainThread, "Stealth unlocked!");
|
||||
|
||||
notifier.accept(playerId);
|
||||
|
||||
verify(player).sendTitle("Stealth Unlocked", "Your identity can now be concealed", 10, 100, 20);
|
||||
verify(player).sendMessage("Stealth unlocked!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package games.dmg.spigotstealth;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.entity.EntityPotionEffectEvent;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class InvisibilityEffectListenerTest {
|
||||
@Test
|
||||
void onlyPotionDrinkCauseStartsQualifyingInvisibility() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
AtomicLong nanos = new AtomicLong();
|
||||
try (StealthStateManager manager = manager()) {
|
||||
QualifyingInvisibilityService service = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), nanos::get, ignored -> { });
|
||||
InvisibilityEffectListener listener = new InvisibilityEffectListener(
|
||||
service, Clock.fixed(Instant.EPOCH, ZoneOffset.UTC));
|
||||
|
||||
Player player = player(playerId);
|
||||
listener.handlePotionEffect(player, true, EntityPotionEffectEvent.Cause.POTION_SPLASH, EntityPotionEffectEvent.Action.ADDED);
|
||||
assertFalse(service.isQualifying(playerId));
|
||||
listener.handlePotionEffect(player, true, EntityPotionEffectEvent.Cause.COMMAND, EntityPotionEffectEvent.Action.CHANGED);
|
||||
assertFalse(service.isQualifying(playerId));
|
||||
listener.handlePotionEffect(player, true, EntityPotionEffectEvent.Cause.POTION_DRINK, EntityPotionEffectEvent.Action.ADDED);
|
||||
assertTrue(service.isQualifying(playerId));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyLaterNonDrinkChangeStopsTheQualifyingInterval() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
try (StealthStateManager manager = manager()) {
|
||||
QualifyingInvisibilityService service = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), System::nanoTime, ignored -> { });
|
||||
InvisibilityEffectListener listener = new InvisibilityEffectListener(service, Clock.systemUTC());
|
||||
Player player = player(playerId);
|
||||
listener.handlePotionEffect(player, true, EntityPotionEffectEvent.Cause.POTION_DRINK, EntityPotionEffectEvent.Action.ADDED);
|
||||
listener.handlePotionEffect(player, true, EntityPotionEffectEvent.Cause.PLUGIN, EntityPotionEffectEvent.Action.CHANGED);
|
||||
assertFalse(service.isQualifying(playerId));
|
||||
}
|
||||
}
|
||||
|
||||
private static Player player(UUID playerId) {
|
||||
Player player = mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getName()).thenReturn("Alex");
|
||||
return player;
|
||||
}
|
||||
|
||||
private static StealthStateManager manager() {
|
||||
StealthStateRepository repository = new StealthStateRepository() {
|
||||
@Override public PersistentStealthState load() { return new PersistentStealthState(Map.of(), Map.of()); }
|
||||
@Override public void save(PersistentStealthState state) { }
|
||||
};
|
||||
return new StealthStateManager(repository, new PersistentStealthState(Map.of(), Map.of()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package games.dmg.spigotstealth;
|
||||
|
||||
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.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class QualifyingInvisibilityServiceTest {
|
||||
@Test
|
||||
void accumulatesMonotonicElapsedTimeAcrossQualifyingIntervals() {
|
||||
AtomicLong nanos = new AtomicLong();
|
||||
UUID playerId = UUID.randomUUID();
|
||||
try (StealthStateManager manager = manager()) {
|
||||
QualifyingInvisibilityService service = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), nanos::get, ignored -> { });
|
||||
|
||||
service.begin(playerId, "Alex", Instant.parse("2026-08-14T10:00:00Z"));
|
||||
nanos.addAndGet(Duration.ofMinutes(3).toNanos());
|
||||
service.stop(playerId).join();
|
||||
service.begin(playerId, "Alex", Instant.parse("2026-08-14T11:00:00Z"));
|
||||
nanos.addAndGet(Duration.ofMinutes(2).toNanos());
|
||||
service.stop(playerId).join();
|
||||
|
||||
assertEquals(Duration.ofMinutes(5).toMillis(), manager.snapshot().player(playerId).accumulatedMillis());
|
||||
assertFalse(service.isQualifying(playerId));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshSettlesExistingIntervalWithoutDoubleCounting() {
|
||||
AtomicLong nanos = new AtomicLong();
|
||||
UUID playerId = UUID.randomUUID();
|
||||
try (StealthStateManager manager = manager()) {
|
||||
QualifyingInvisibilityService service = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), nanos::get, ignored -> { });
|
||||
service.begin(playerId, "Alex", Instant.EPOCH);
|
||||
nanos.addAndGet(Duration.ofSeconds(5).toNanos());
|
||||
service.begin(playerId, "Alex", Instant.EPOCH.plusSeconds(5)).join();
|
||||
nanos.addAndGet(Duration.ofSeconds(5).toNanos());
|
||||
service.stop(playerId).join();
|
||||
assertEquals(Duration.ofSeconds(10).toMillis(), manager.snapshot().player(playerId).accumulatedMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unlocksAndNotifiesExactlyOnceWhilePreservingExcessTime() {
|
||||
AtomicLong nanos = new AtomicLong();
|
||||
UUID playerId = UUID.randomUUID();
|
||||
ArrayList<UUID> notifications = new ArrayList<>();
|
||||
try (StealthStateManager manager = manager()) {
|
||||
QualifyingInvisibilityService service = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofSeconds(10), nanos::get, notifications::add);
|
||||
service.begin(playerId, "Alex", Instant.EPOCH);
|
||||
nanos.addAndGet(Duration.ofSeconds(11).toNanos());
|
||||
service.checkpoint(playerId).join();
|
||||
nanos.addAndGet(Duration.ofSeconds(2).toNanos());
|
||||
service.stop(playerId).join();
|
||||
|
||||
PlayerStealthState player = manager.snapshot().player(playerId);
|
||||
assertTrue(player.unlocked());
|
||||
assertEquals(Duration.ofSeconds(13).toMillis(), player.accumulatedMillis());
|
||||
assertEquals(java.util.List.of(playerId), notifications);
|
||||
}
|
||||
}
|
||||
|
||||
private static StealthStateManager manager() {
|
||||
StealthStateRepository repository = new StealthStateRepository() {
|
||||
@Override public PersistentStealthState load() { return new PersistentStealthState(Map.of(), Map.of()); }
|
||||
@Override public void save(PersistentStealthState state) { }
|
||||
};
|
||||
return new StealthStateManager(repository, new PersistentStealthState(Map.of(), Map.of()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user