|
|
|
@@ -40,7 +40,7 @@ class OutOfCombatRecoveryNativeTest {
|
|
|
|
|
@Test void exactDelayRepeatedIntervalsAndLargeClockJumpsPersistOnePulseBeforeNativePublication() {
|
|
|
|
|
var f = new Direct();
|
|
|
|
|
f.recovery.sweep();
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(29)); f.recovery.sweep();
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(9)); f.recovery.sweep();
|
|
|
|
|
assertTrue(f.events.isEmpty());
|
|
|
|
|
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(1)); f.recovery.sweep();
|
|
|
|
@@ -72,12 +72,12 @@ class OutOfCombatRecoveryNativeTest {
|
|
|
|
|
|
|
|
|
|
@Test void acceptedEnvironmentalAndDealtDamageResetWhileCancelledAndZeroAttemptsDoNot() {
|
|
|
|
|
var f = new Direct(); f.recovery.sweep();
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(29));
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(9));
|
|
|
|
|
f.recovery.acceptedDamage(damage(f.player, 2));
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(29)); f.recovery.sweep(); assertTrue(f.events.isEmpty());
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(9)); f.recovery.sweep(); assertTrue(f.events.isEmpty());
|
|
|
|
|
f.clock.advance(Duration.ofSeconds(1)); f.recovery.sweep(); f.runNext(); assertEquals(5, f.health.get());
|
|
|
|
|
|
|
|
|
|
var noReset = new Direct(); noReset.recovery.sweep(); noReset.clock.advance(Duration.ofSeconds(29));
|
|
|
|
|
var noReset = new Direct(); noReset.recovery.sweep(); noReset.clock.advance(Duration.ofSeconds(9));
|
|
|
|
|
var cancelled = damage(noReset.player, 2); cancelled.setCancelled(true); noReset.recovery.acceptedDamage(cancelled);
|
|
|
|
|
noReset.recovery.acceptedDamage(damage(noReset.player, 0));
|
|
|
|
|
noReset.clock.advance(Duration.ofSeconds(1)); noReset.recovery.sweep(); noReset.runNext();
|
|
|
|
@@ -85,58 +85,58 @@ class OutOfCombatRecoveryNativeTest {
|
|
|
|
|
|
|
|
|
|
var dealt = new Direct();
|
|
|
|
|
var target = mock(Player.class); when(target.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
|
|
|
|
|
dealt.recovery.sweep(); dealt.clock.advance(Duration.ofSeconds(29));
|
|
|
|
|
dealt.recovery.sweep(); dealt.clock.advance(Duration.ofSeconds(9));
|
|
|
|
|
var source = DamageSource.builder(org.bukkit.damage.DamageType.PLAYER_ATTACK)
|
|
|
|
|
.withCausingEntity(dealt.player).withDirectEntity(dealt.player).build();
|
|
|
|
|
dealt.recovery.acceptedDamage(new EntityDamageByEntityEvent(dealt.player, target,
|
|
|
|
|
EntityDamageEvent.DamageCause.ENTITY_ATTACK, source, 2));
|
|
|
|
|
dealt.clock.advance(Duration.ofSeconds(29)); dealt.recovery.sweep(); assertTrue(dealt.events.isEmpty());
|
|
|
|
|
dealt.clock.advance(Duration.ofSeconds(9)); dealt.recovery.sweep(); assertTrue(dealt.events.isEmpty());
|
|
|
|
|
dealt.clock.advance(Duration.ofSeconds(1)); dealt.recovery.sweep(); dealt.runNext();
|
|
|
|
|
assertEquals(5, dealt.health.get(), "Accepted outgoing damage starts a fresh delay for its source character");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void cancellationFailureAndDamageDuringPersistenceNeverPublishOrCatchUp() {
|
|
|
|
|
var cancelled = new Direct(); cancelled.veto = true; cancelled.recovery.sweep();
|
|
|
|
|
cancelled.clock.advance(Duration.ofSeconds(30)); cancelled.recovery.sweep();
|
|
|
|
|
cancelled.clock.advance(Duration.ofSeconds(10)); cancelled.recovery.sweep();
|
|
|
|
|
assertEquals(1, cancelled.events.size()); assertTrue(cancelled.callbacks.isEmpty()); assertEquals(4, cancelled.health.get());
|
|
|
|
|
cancelled.clock.advance(Duration.ofSeconds(9)); cancelled.recovery.sweep(); assertEquals(1, cancelled.events.size());
|
|
|
|
|
|
|
|
|
|
var failed = new Direct(); failed.automatic = false; failed.recovery.sweep(); failed.clock.advance(Duration.ofSeconds(30));
|
|
|
|
|
var failed = new Direct(); failed.automatic = false; failed.recovery.sweep(); failed.clock.advance(Duration.ofSeconds(10));
|
|
|
|
|
failed.recovery.sweep(); assertNotNull(failed.pending);
|
|
|
|
|
failed.pending.completeExceptionally(new java.io.IOException("simulated")); failed.runNext();
|
|
|
|
|
assertEquals(4, failed.health.get());
|
|
|
|
|
failed.clock.advance(Duration.ofSeconds(9)); failed.recovery.sweep();
|
|
|
|
|
assertEquals(1, failed.events.size(), "Failed persistence does not immediately replay a missed pulse");
|
|
|
|
|
|
|
|
|
|
var stale = new Direct(); stale.automatic = false; stale.recovery.sweep(); stale.clock.advance(Duration.ofSeconds(30));
|
|
|
|
|
var stale = new Direct(); stale.automatic = false; stale.recovery.sweep(); stale.clock.advance(Duration.ofSeconds(10));
|
|
|
|
|
stale.recovery.sweep();
|
|
|
|
|
stale.recovery.acceptedDamage(damage(stale.player, 1));
|
|
|
|
|
stale.pending.complete(Optional.of(stale.saved(BigDecimal.valueOf(5)))); stale.runNext();
|
|
|
|
|
assertEquals(4, stale.health.get(), "Damage during the save invalidates native publication");
|
|
|
|
|
stale.clock.advance(Duration.ofSeconds(29)); stale.recovery.sweep(); assertEquals(1, stale.events.size());
|
|
|
|
|
stale.clock.advance(Duration.ofSeconds(9)); stale.recovery.sweep(); assertEquals(1, stale.events.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void passiveBonusZeroCancellationAndFullHealthStayInTheBasePulseTransaction() {
|
|
|
|
|
var zero = new Direct(); zero.recovery.attachBonusHealing(ignored -> BigInteger.ZERO);
|
|
|
|
|
zero.recovery.sweep(); zero.clock.advance(Duration.ofSeconds(30)); zero.recovery.sweep(); zero.runNext();
|
|
|
|
|
zero.recovery.sweep(); zero.clock.advance(Duration.ofSeconds(10)); zero.recovery.sweep(); zero.runNext();
|
|
|
|
|
assertEquals(5, zero.health.get(), "A zero passive bonus leaves the base pulse unchanged");
|
|
|
|
|
|
|
|
|
|
var cancelled = new Direct(); cancelled.recovery.attachBonusHealing(ignored -> BigInteger.valueOf(3)); cancelled.veto = true;
|
|
|
|
|
var cancelledRevision = cancelled.record.get().revision();
|
|
|
|
|
cancelled.recovery.sweep(); cancelled.clock.advance(Duration.ofSeconds(30)); cancelled.recovery.sweep();
|
|
|
|
|
cancelled.recovery.sweep(); cancelled.clock.advance(Duration.ofSeconds(10)); cancelled.recovery.sweep();
|
|
|
|
|
assertEquals(4, cancelled.events.getFirst().getAmount());
|
|
|
|
|
assertEquals(4, cancelled.health.get()); assertEquals(cancelledRevision, cancelled.record.get().revision());
|
|
|
|
|
|
|
|
|
|
var full = new Direct(); full.health.set(10d); full.record.set(full.saved(BigDecimal.TEN));
|
|
|
|
|
full.recovery.attachBonusHealing(ignored -> BigInteger.valueOf(3)); var fullRevision = full.record.get().revision();
|
|
|
|
|
full.recovery.sweep(); full.clock.advance(Duration.ofSeconds(30)); full.recovery.sweep();
|
|
|
|
|
full.recovery.sweep(); full.clock.advance(Duration.ofSeconds(10)); full.recovery.sweep();
|
|
|
|
|
assertTrue(full.events.isEmpty()); assertEquals(fullRevision, full.record.get().revision());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void stackedRegenerationBonusProducesOneMaximumCappedNativeAndPersistedPulse() {
|
|
|
|
|
var f = new Direct(); f.health.set(9d); f.record.set(f.saved(BigDecimal.valueOf(9)));
|
|
|
|
|
f.recovery.attachBonusHealing(ignored -> BigInteger.valueOf(3));
|
|
|
|
|
f.recovery.sweep(); f.clock.advance(Duration.ofSeconds(30)); f.recovery.sweep();
|
|
|
|
|
f.recovery.sweep(); f.clock.advance(Duration.ofSeconds(10)); f.recovery.sweep();
|
|
|
|
|
assertEquals(1, f.events.size()); assertEquals(1, f.events.getFirst().getAmount());
|
|
|
|
|
f.runNext(); assertEquals(10, f.health.get()); assertEquals(0, BigDecimal.TEN.compareTo(f.record.get().health()));
|
|
|
|
|
}
|
|
|
|
@@ -151,6 +151,21 @@ class OutOfCombatRecoveryNativeTest {
|
|
|
|
|
assertEquals(5, f.health.get(), "The new delay evaluates from the retained activity timestamp");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void retainedThirtySecondDelayRemainsEffectiveWithoutRewritingConfiguration() throws Exception {
|
|
|
|
|
String retained = "out-of-combat-healing-delay-seconds: 30\n";
|
|
|
|
|
java.nio.file.Files.writeString(data.resolve("config.yml"), retained);
|
|
|
|
|
try (var f = new FantasyStickLifecycleNativeTest.Fixture(data)) {
|
|
|
|
|
f.plugin.setEnabled(true);
|
|
|
|
|
var settings = (games.dmg.fantasystick.persistence.OutOfCombatRecoverySettings)
|
|
|
|
|
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "outOfCombatRecoverySettings");
|
|
|
|
|
assertEquals("30", settings.value(games.dmg.fantasystick.persistence.OutOfCombatRecoverySettings.DELAY));
|
|
|
|
|
assertEquals(retained, java.nio.file.Files.readString(data.resolve("config.yml")));
|
|
|
|
|
var sender = new EquipmentAdminCommandNativeTest.Sender();
|
|
|
|
|
assertEquals(List.of("10", "30"), f.command.tabComplete(sender.port, "fantasystick",
|
|
|
|
|
new String[]{"config", "out-of-combat-healing-delay-seconds", ""}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void packagedPluginConnectsZeroDelayRecoveryConfigurationAndRestart() throws Exception {
|
|
|
|
|
java.nio.file.Files.writeString(data.resolve("config.yml"),
|
|
|
|
|
"out-of-combat-healing-delay-seconds: 0\nout-of-combat-healing-interval-seconds: 10\nout-of-combat-healing-amount: 1\n");
|
|
|
|
|