diff --git a/src/main/java/com/israelsimulator/entity/boss/ArenaRestoreRules.java b/src/main/java/com/israelsimulator/entity/boss/ArenaRestoreRules.java index 0872b34..754979e 100644 --- a/src/main/java/com/israelsimulator/entity/boss/ArenaRestoreRules.java +++ b/src/main/java/com/israelsimulator/entity/boss/ArenaRestoreRules.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiPredicate; import java.util.function.LongFunction; /** @@ -45,10 +46,20 @@ public static boolean shouldRestore(S current, S placed) { /** Splits entries into the ones to restore and the ones a player has changed since. */ public static Plan plan(List> entries, LongFunction currentState) { + return plan(entries, currentState, ArenaRestoreRules::shouldRestore); + } + + /** + * Same as {@link #plan(List, LongFunction)} with a custom "still what the arena placed" check. + * In game this compares the block type only: leaves recompute their distance, water and + * redstone settle after placement, and those state changes are not player edits. + */ + public static Plan plan(List> entries, LongFunction currentState, + BiPredicate stillPlaced) { List> restore = new ArrayList<>(); List> skipped = new ArrayList<>(); for (Entry e : entries) { - if (shouldRestore(currentState.apply(e.pos()), e.placed())) { + if (stillPlaced.test(currentState.apply(e.pos()), e.placed())) { restore.add(e); } else { skipped.add(e); diff --git a/src/main/java/com/israelsimulator/entity/boss/EpsteinArenaSnapshots.java b/src/main/java/com/israelsimulator/entity/boss/EpsteinArenaSnapshots.java index f88a240..b114fc8 100644 --- a/src/main/java/com/israelsimulator/entity/boss/EpsteinArenaSnapshots.java +++ b/src/main/java/com/israelsimulator/entity/boss/EpsteinArenaSnapshots.java @@ -127,7 +127,8 @@ public static boolean restore(ServerLevel level, UUID boss) { if (!level.hasChunksAt(min, max)) return false; List> entries = arena.entries(); - ArenaRestoreRules.Plan plan = ArenaRestoreRules.plan(entries, p -> level.getBlockState(BlockPos.of(p))); + ArenaRestoreRules.Plan plan = ArenaRestoreRules.plan(entries, p -> level.getBlockState(BlockPos.of(p)), + (current, placed) -> current.is(placed.getBlock())); // 1) empty pavilion containers so nothing spills into the world for (ArenaRestoreRules.Entry e : plan.restore()) { diff --git a/src/test/java/com/israelsimulator/entity/boss/EpsteinArenaRestoreTest.java b/src/test/java/com/israelsimulator/entity/boss/EpsteinArenaRestoreTest.java index 9d2a08c..3c2ad1f 100644 --- a/src/test/java/com/israelsimulator/entity/boss/EpsteinArenaRestoreTest.java +++ b/src/test/java/com/israelsimulator/entity/boss/EpsteinArenaRestoreTest.java @@ -124,4 +124,28 @@ int record = boss.indexOf("EpsteinArenaSnapshots.record("); String events = Files.readString(Path.of("src/main/java/com/israelsimulator/event/ModGameEvents.java")); assertTrue(events.contains("EpsteinArenaSnapshots.tickOrphans(level)")); } -} + + @Test + @DisplayName("Block-state updates after placement (leaf distance) are not player changes") + void planIgnoresStateOnlyChangesWithBlockCheck() { + List> entries = List.of( + new ArenaRestoreRules.Entry<>(1L, "air", "oak_leaves[distance=7]"), + new ArenaRestoreRules.Entry<>(2L, "air", "glass"), + new ArenaRestoreRules.Entry<>(3L, "air", "oak_leaves[distance=7]")); + Map world = new HashMap<>(Map.of( + 1L, "oak_leaves[distance=2]", 2L, "glass", 3L, "cobblestone")); + java.util.function.BiPredicate sameBlock = + (current, placed) -> current.split("\\[")[0].equals(placed.split("\\[")[0]); + + ArenaRestoreRules.Plan plan = ArenaRestoreRules.plan(entries, world::get, sameBlock); + assertEquals(List.of(1L, 2L), plan.restore().stream().map(ArenaRestoreRules.Entry::pos).toList()); + assertEquals(List.of(3L), plan.skipped().stream().map(ArenaRestoreRules.Entry::pos).toList()); + String snap = assertDoesNotThrowRead(); + assertTrue(snap.contains("current.is(placed.getBlock())"), "in game the restore compares block types"); + } + + private static String assertDoesNotThrowRead() { + return assertDoesNotThrow(() -> Files.readString(Path.of( + "src/main/java/com/israelsimulator/entity/boss/EpsteinArenaSnapshots.java"))); + } +} \ No newline at end of file diff --git a/test.md b/test.md index 7f394b8..f25c40f 100644 --- a/test.md +++ b/test.md @@ -953,3 +953,9 @@ Non provato dall'utente in gioco. - Problema riscontrato: quando si evoca Bibi con la Stella di Davide la chat mostra "%s used the Star of David. Bibi is here!". Il nome del giocatore non c'è. - Funzionamento atteso: "Dev used the Star of David. Bibi is here!" (in italiano "Dev ha usato la Stella di Davide..."). - Soluzione: `StarOfDavidItem` passa a `Component.translatable` il nome del giocatore come argomento. Il nuovo test `testTranslatablePlaceholdersHaveArguments` cerca nel codice i messaggi tradotti che contengono `%s` ma vengono creati senza argomenti. + +## Arena di Epstein: foglie del padiglione rimaste dopo il ripristino +- Problema riscontrato: dopo la morte di Epstein il terreno viene ripristinato, ma restano sospese in aria le chiome delle palme del padiglione. Nel log, a ogni combattimento, 82–106 blocchi risultano "left as changed by players" anche se nessun giocatore li ha toccati. +- Causa: le foglie ricalcolano lo stato `distance` subito dopo il piazzamento. Il ripristino confrontava lo stato completo del blocco, quindi trattava quelle foglie come una modifica del giocatore e le lasciava lì. +- Funzionamento atteso: alla fine del combattimento sparisce tutto quello che l'arena ha piazzato. Restano solo i blocchi che un giocatore ha davvero sostituito. +- Soluzione: in gioco il ripristino confronta solo il tipo di blocco (`current.is(placed.getBlock())`). Il nuovo test `planIgnoresStateOnlyChangesWithBlockCheck` lo verifica. La correzione vale anche per il palco del Public Speech, che usa gli stessi snapshot.