fix(heights): restore login stature silently
This commit is contained in:
1 parent
3c6237946e
commit
359b723897
4 files changed
+144
-15
No files matched your search
@@ -82,6 +82,10 @@ public final class BukkitStatureService implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void resume(Player player) {
|
public void resume(Player player) {
|
||||||
|
resume(player, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume(Player player, boolean notify) {
|
||||||
UUID id = player.getUniqueId();
|
UUID id = player.getUniqueId();
|
||||||
queue.submit(() -> {
|
queue.submit(() -> {
|
||||||
requireOnline(id);
|
requireOnline(id);
|
||||||
@@ -89,7 +93,7 @@ public final class BukkitStatureService implements AutoCloseable {
|
|||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
return () -> stature.resume(id, limits, now);
|
return () -> stature.resume(id, limits, now);
|
||||||
}, (result, error) -> {
|
}, (result, error) -> {
|
||||||
if (error == null) { applyRestoration(id, result); } else { report(id, error); }
|
if (error == null) { applyRestoration(id, result, notify); } else { report(id, error); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +175,12 @@ public final class BukkitStatureService implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyRestoration(UUID id, double value) {
|
private void applyRestoration(UUID id, double value) {
|
||||||
|
applyRestoration(id, value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyRestoration(UUID id, double value, boolean notify) {
|
||||||
HeightStore.Sequence pending = store.sequence(id);
|
HeightStore.Sequence pending = store.sequence(id);
|
||||||
if (applyOnline(id, value) && pending != null && !Instant.now().isBefore(pending.expiresAt())) {
|
if (applyOnline(id, value, notify) && pending != null && !Instant.now().isBefore(pending.expiresAt())) {
|
||||||
worker.execute(() -> {
|
worker.execute(() -> {
|
||||||
try {
|
try {
|
||||||
stature.acknowledgeRestoration(id, pending);
|
stature.acknowledgeRestoration(id, pending);
|
||||||
@@ -184,16 +192,30 @@ public final class BukkitStatureService implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean applyOnline(UUID id, double value) {
|
private boolean applyOnline(UUID id, double value) {
|
||||||
|
return applyOnline(id, value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean applyOnline(UUID id, double value, boolean notify) {
|
||||||
Player player = plugin.getServer().getPlayer(id);
|
Player player = plugin.getServer().getPlayer(id);
|
||||||
if (plugin.isEnabled() && player != null && player.isOnline() && !player.isDead()) {
|
return plugin.isEnabled() && applyToPlayer(player, value, notify);
|
||||||
AttributeInstance attribute = player.getAttribute(Attribute.SCALE);
|
}
|
||||||
if (attribute != null) {
|
|
||||||
attribute.setBaseValue(value);
|
static boolean applyToPlayer(Player player, double value, boolean notify) {
|
||||||
player.sendMessage("Your scale is now " + value + ".");
|
if (player == null || !player.isOnline() || player.isDead()) {
|
||||||
return true;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return applyScale(player, player.getAttribute(Attribute.SCALE), value, notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean applyScale(Player player, AttributeInstance attribute, double value, boolean notify) {
|
||||||
|
if (attribute == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
attribute.setBaseValue(value);
|
||||||
|
if (notify) {
|
||||||
|
player.sendMessage("Your scale is now " + value + ".");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void report(UUID id, Exception error) {
|
private void report(UUID id, Exception error) {
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ import org.bukkit.inventory.Recipe;
|
|||||||
public final class StatureListener implements Listener {
|
public final class StatureListener implements Listener {
|
||||||
private final PotionRecipes potions;
|
private final PotionRecipes potions;
|
||||||
private final BiConsumer<Player, StaturePotion> drink;
|
private final BiConsumer<Player, StaturePotion> drink;
|
||||||
private final Consumer<Player> resume;
|
private final BiConsumer<Player, Boolean> resume;
|
||||||
private final Consumer<Runnable> nextTick;
|
private final Consumer<Runnable> nextTick;
|
||||||
|
|
||||||
public StatureListener(PotionRecipes potions, BiConsumer<Player, StaturePotion> drink,
|
public StatureListener(PotionRecipes potions, BiConsumer<Player, StaturePotion> drink,
|
||||||
Consumer<Player> resume, Consumer<Runnable> nextTick) {
|
BiConsumer<Player, Boolean> resume, Consumer<Runnable> nextTick) {
|
||||||
this.potions = potions;
|
this.potions = potions;
|
||||||
this.drink = drink;
|
this.drink = drink;
|
||||||
this.resume = resume;
|
this.resume = resume;
|
||||||
@@ -54,11 +54,11 @@ public final class StatureListener implements Listener {
|
|||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onJoin(PlayerJoinEvent event) {
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
resume.accept(event.getPlayer());
|
resume.accept(event.getPlayer(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onRespawn(PlayerRespawnEvent event) {
|
public void onRespawn(PlayerRespawnEvent event) {
|
||||||
nextTick.accept(() -> resume.accept(event.getPlayer()));
|
nextTick.accept(() -> resume.accept(event.getPlayer(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package games.dmg.spigotheights;
|
||||||
|
|
||||||
|
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.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
class BukkitStatureServiceTest {
|
||||||
|
@TempDir
|
||||||
|
Path directory;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void silentRestorationKeepsStoredClampingAndExpiredBaselineWithoutFeedback() throws Exception {
|
||||||
|
HeightSettings settings = new HeightSettings(0.4, 2.0, 0.1, 0.5, 1.5, 20);
|
||||||
|
HeightStore store = new HeightStore(directory.toFile());
|
||||||
|
TemporaryStature stature = new TemporaryStature(store);
|
||||||
|
PlayerFixture player = new PlayerFixture();
|
||||||
|
|
||||||
|
UUID saved = UUID.randomUUID();
|
||||||
|
store.save(saved, 3.0);
|
||||||
|
assertTrue(BukkitStatureService.applyScale(
|
||||||
|
player.player, player.attribute, stature.resume(saved, settings, Instant.now()), false));
|
||||||
|
assertEquals(2.0, player.scale);
|
||||||
|
assertEquals(List.of(), player.messages);
|
||||||
|
|
||||||
|
UUID expired = UUID.randomUUID();
|
||||||
|
Instant start = Instant.parse("2026-01-01T00:00:00Z");
|
||||||
|
stature.apply(expired, 1.4, StaturePotion.DIMINUTION, settings, start, bound -> 0);
|
||||||
|
assertTrue(BukkitStatureService.applyScale(player.player, player.attribute,
|
||||||
|
stature.resume(expired, settings, start.plusSeconds(301)), false));
|
||||||
|
assertEquals(1.4, player.scale);
|
||||||
|
assertEquals(List.of(), player.messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void notifyingApplicationPreservesPotionAdministratorAndRespawnFeedback() {
|
||||||
|
PlayerFixture player = new PlayerFixture();
|
||||||
|
|
||||||
|
assertTrue(BukkitStatureService.applyScale(player.player, player.attribute, 1.2, true));
|
||||||
|
assertEquals(1.2, player.scale);
|
||||||
|
assertEquals(List.of("Your scale is now 1.2."), player.messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unavailablePlayerStillRefusesRestorationWithoutFeedback() {
|
||||||
|
Player offline = BukkitDoubles.proxy(Player.class, (proxy, method, args) -> {
|
||||||
|
if (method.getName().equals("isOnline")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new AssertionError("Offline player should not be changed: " + method.getName());
|
||||||
|
});
|
||||||
|
|
||||||
|
assertFalse(BukkitStatureService.applyToPlayer(offline, 0.8, false));
|
||||||
|
assertFalse(BukkitStatureService.applyToPlayer(null, 0.8, true));
|
||||||
|
assertFalse(BukkitStatureService.applyScale(offline, null, 0.8, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PlayerFixture {
|
||||||
|
private double scale = 1.0;
|
||||||
|
private final List<String> messages = new ArrayList<>();
|
||||||
|
private final AttributeInstance attribute = BukkitDoubles.proxy(
|
||||||
|
AttributeInstance.class, (proxy, method, args) -> switch (method.getName()) {
|
||||||
|
case "setBaseValue" -> { scale = (double) args[0]; yield null; }
|
||||||
|
case "getBaseValue" -> scale;
|
||||||
|
default -> throw new UnsupportedOperationException(method.getName());
|
||||||
|
});
|
||||||
|
private final Player player = BukkitDoubles.proxy(Player.class, (proxy, method, args) -> {
|
||||||
|
if (method.getName().equals("sendMessage")) {
|
||||||
|
messages.add((String) args[0]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException(method.getName());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,13 @@ package games.dmg.spigotheights;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.player.PlayerItemConsumeEvent;
|
import org.bukkit.event.player.PlayerItemConsumeEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
import org.bukkit.inventory.EquipmentSlot;
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -16,7 +19,8 @@ class StatureListenerTest {
|
|||||||
PotionRecipes potions = new PotionRecipes(null, new NamespacedKey("spigotheights", "stature_potion"),
|
PotionRecipes potions = new PotionRecipes(null, new NamespacedKey("spigotheights", "stature_potion"),
|
||||||
BukkitDoubles.Item::new);
|
BukkitDoubles.Item::new);
|
||||||
List<StaturePotion> drinks = new ArrayList<>();
|
List<StaturePotion> drinks = new ArrayList<>();
|
||||||
StatureListener listener = new StatureListener(potions, (player, kind) -> drinks.add(kind), player -> {}, Runnable::run);
|
StatureListener listener = new StatureListener(potions, (player, kind) -> drinks.add(kind),
|
||||||
|
(player, notify) -> {}, Runnable::run);
|
||||||
Player player = ThrowableStatureListenerTest.entity(Player.class, null);
|
Player player = ThrowableStatureListenerTest.entity(Player.class, null);
|
||||||
var cancelled = new PlayerItemConsumeEvent(player, potions.create(StaturePotion.GROWTH), EquipmentSlot.HAND);
|
var cancelled = new PlayerItemConsumeEvent(player, potions.create(StaturePotion.GROWTH), EquipmentSlot.HAND);
|
||||||
cancelled.setCancelled(true);
|
cancelled.setCancelled(true);
|
||||||
@@ -29,4 +33,23 @@ class StatureListenerTest {
|
|||||||
listener.onConsume(cancelled);
|
listener.onConsume(cancelled);
|
||||||
assertEquals(List.of(StaturePotion.GROWTH), drinks);
|
assertEquals(List.of(StaturePotion.GROWTH), drinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loginRestorationIsSilentWhileRespawnRestorationStillNotifies() {
|
||||||
|
PotionRecipes potions = new PotionRecipes(null, new NamespacedKey("spigotheights", "stature_potion"),
|
||||||
|
BukkitDoubles.Item::new);
|
||||||
|
List<Boolean> notificationModes = new ArrayList<>();
|
||||||
|
List<Runnable> nextTick = new ArrayList<>();
|
||||||
|
StatureListener listener = new StatureListener(potions, (player, kind) -> {},
|
||||||
|
(player, notify) -> notificationModes.add(notify), nextTick::add);
|
||||||
|
Player player = ThrowableStatureListenerTest.entity(Player.class, null);
|
||||||
|
|
||||||
|
listener.onJoin(new PlayerJoinEvent(player, "joined"));
|
||||||
|
assertEquals(List.of(false), notificationModes);
|
||||||
|
|
||||||
|
listener.onRespawn(new PlayerRespawnEvent(player, new Location(null, 0, 0, 0), false));
|
||||||
|
assertEquals(1, nextTick.size());
|
||||||
|
nextTick.getFirst().run();
|
||||||
|
assertEquals(List.of(false, true), notificationModes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user