fix(protection): restore cleared Leaf effects
This commit is contained in:
@@ -37,6 +37,12 @@ public final class LeafPlugin extends JavaPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
getServer().getScheduler().runTaskTimer(
|
||||
this,
|
||||
new LeafRecoveryTask(runtime),
|
||||
LeafRecoveryTask.INTERVAL_TICKS,
|
||||
LeafRecoveryTask.INTERVAL_TICKS
|
||||
);
|
||||
getServer().getScheduler().runTaskTimer(this, this::saveState, 600L, 600L);
|
||||
getLogger().info("Leaf enabled.");
|
||||
}
|
||||
|
||||
@@ -88,23 +88,38 @@ public final class LeafProtection {
|
||||
ignored -> new HashMap<>()
|
||||
);
|
||||
PotionEffect previous = owned.get(desired.getType());
|
||||
if (previous != null && !previous.equals(desired)) {
|
||||
PotionEffect active = player.getPotionEffect(desired.getType());
|
||||
if (previous != null && previous.equals(active)) {
|
||||
if (previous.equals(desired)) {
|
||||
return true;
|
||||
}
|
||||
removeMatching(player, previous);
|
||||
owned.remove(desired.getType());
|
||||
active = player.getPotionEffect(desired.getType());
|
||||
} else if (previous != null) {
|
||||
owned.remove(desired.getType());
|
||||
}
|
||||
|
||||
PotionEffect active = player.getPotionEffect(desired.getType());
|
||||
if (desired.equals(active) && desired.equals(owned.get(desired.getType()))) {
|
||||
if (active != null) {
|
||||
removeEmptyOwnership(playerId, owned);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean applied = player.addPotionEffect(desired);
|
||||
if (applied) {
|
||||
owned.put(desired.getType(), desired);
|
||||
}
|
||||
removeEmptyOwnership(playerId, owned);
|
||||
return applied;
|
||||
}
|
||||
|
||||
private void removeEmptyOwnership(
|
||||
UUID playerId,
|
||||
Map<PotionEffectType, PotionEffect> owned
|
||||
) {
|
||||
if (owned.isEmpty()) {
|
||||
appliedEffects.remove(playerId);
|
||||
}
|
||||
return applied || desired.equals(active);
|
||||
}
|
||||
|
||||
private static void removeMatching(Player player, PotionEffect expected) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package games.dmg.leaf;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** Periodically restores eligible protection after effects are cleared or expire. */
|
||||
public final class LeafRecoveryTask implements Runnable {
|
||||
static final long INTERVAL_TICKS = 20L;
|
||||
|
||||
private final LeafRuntime runtime;
|
||||
|
||||
public LeafRecoveryTask(LeafRuntime runtime) {
|
||||
this.runtime = Objects.requireNonNull(runtime, "runtime");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runtime.reconcileAllOnline();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package games.dmg.leaf;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class LeafRecoveryTaskTest {
|
||||
@Test
|
||||
void reconcilesAllOnlinePlayersEverySecond() {
|
||||
LeafRuntime runtime = mock(LeafRuntime.class);
|
||||
LeafRecoveryTask recovery = new LeafRecoveryTask(runtime);
|
||||
|
||||
recovery.run();
|
||||
|
||||
verify(runtime).reconcileAllOnline();
|
||||
assertEquals(20L, LeafRecoveryTask.INTERVAL_TICKS);
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,38 @@ final class LeafRuntimeTest {
|
||||
verify(protection).isEffective(player, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void recoveryReconcilesEligiblePlayersAndLeavesOptedOutPlayersUnprotected()
|
||||
throws Exception {
|
||||
UUID protectedId = UUID.randomUUID();
|
||||
UUID optedOutId = UUID.randomUUID();
|
||||
Player protectedPlayer = player(protectedId, "Alex");
|
||||
Player optedOutPlayer = player(optedOutId, "Steve");
|
||||
Server server = mock(Server.class);
|
||||
when(server.getPlayer(protectedId)).thenReturn(protectedPlayer);
|
||||
when(server.getPlayer(optedOutId)).thenReturn(optedOutPlayer);
|
||||
org.mockito.Mockito.doReturn(List.of(protectedPlayer, optedOutPlayer))
|
||||
.when(server).getOnlinePlayers();
|
||||
LeafProtection protection = mock(LeafProtection.class);
|
||||
when(protection.apply(protectedPlayer, 1, 1)).thenReturn(true);
|
||||
LeafRuntime runtime = runtime(
|
||||
server,
|
||||
protection,
|
||||
temporaryDirectory.resolve("recovery.yml")
|
||||
);
|
||||
Instant observedAt = Instant.parse("2026-08-10T00:00:00Z");
|
||||
runtime.observe(protectedPlayer, observedAt);
|
||||
runtime.observe(optedOutPlayer, observedAt);
|
||||
runtime.setOwnChoice(protectedPlayer, true);
|
||||
org.mockito.Mockito.clearInvocations(protection);
|
||||
|
||||
runtime.reconcileAllOnline();
|
||||
|
||||
verify(protection).apply(protectedPlayer, 1, 1);
|
||||
verify(protection, never()).apply(optedOutPlayer, 1, 1);
|
||||
verify(protection).remove(optedOutPlayer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void welcomesPlayersOnEachJoinOnlyDuringTheConfiguredWindow() throws Exception {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
|
||||
Reference in New Issue
Block a user