|
|
|
@@ -26,7 +26,7 @@ class GreatSwordNativeTest {
|
|
|
|
|
String book = PartyBookNativeTest.text(a);
|
|
|
|
|
assertTrue(book.contains("Great Sword"));
|
|
|
|
|
assertTrue(book.contains("STR 12"));
|
|
|
|
|
assertTrue(book.contains("1.2"));
|
|
|
|
|
assertTrue(book.contains("0.75"));
|
|
|
|
|
var learn = abilityButton(a, "[Learn]", "great_sword");
|
|
|
|
|
PartyBookNativeTest.click(f, a, learn); PartyBookNativeTest.click(f, a, learn);
|
|
|
|
|
PartyBookNativeTest.drain(f); PartyBookNativeTest.drain(f);
|
|
|
|
@@ -68,8 +68,8 @@ class GreatSwordNativeTest {
|
|
|
|
|
var saved = storage.find(a.core.record.id()).get().orElseThrow();
|
|
|
|
|
var deadline = saved.cooldowns().get("great_sword");
|
|
|
|
|
assertNotNull(deadline);
|
|
|
|
|
assertFalse(deadline.isBefore(before.plusMillis(1200)));
|
|
|
|
|
assertFalse(deadline.isAfter(java.time.Instant.now().plusMillis(1200)));
|
|
|
|
|
assertFalse(deadline.isBefore(before.plusMillis(750)));
|
|
|
|
|
assertFalse(deadline.isAfter(java.time.Instant.now().plusMillis(750)));
|
|
|
|
|
f.dispatcher.accept(new org.bukkit.event.entity.EntityDeathEvent(target, damages.getFirst().getDamageSource(),
|
|
|
|
|
new java.util.ArrayList<>(), 0));
|
|
|
|
|
PartyBookNativeTest.drain(f); PartyBookNativeTest.drain(f);
|
|
|
|
@@ -259,6 +259,54 @@ class GreatSwordNativeTest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void fractionalRecoveryRefusesImmediatelyAndOneNanosecondBeforeButAcceptsExactBoundary() throws Exception {
|
|
|
|
|
var now = new java.util.concurrent.atomic.AtomicReference<>(java.time.Instant.parse("2026-09-21T20:00:00Z"));
|
|
|
|
|
java.time.Clock clock = new java.time.Clock() {
|
|
|
|
|
@Override public java.time.ZoneId getZone() { return java.time.ZoneOffset.UTC; }
|
|
|
|
|
@Override public java.time.Clock withZone(java.time.ZoneId zone) { return this; }
|
|
|
|
|
@Override public java.time.Instant instant() { return now.get(); }
|
|
|
|
|
};
|
|
|
|
|
var repository = new games.dmg.fantasystick.persistence.YamlCharacterRepository(data);
|
|
|
|
|
var created = new games.dmg.fantasystick.crafting.CharacterFactory().create(
|
|
|
|
|
games.dmg.fantasystick.progression.ProgressionRules.defaults());
|
|
|
|
|
var stats = new java.util.EnumMap<>(created.stats()); stats.put(games.dmg.fantasystick.progression.Stat.STR, BigInteger.valueOf(12));
|
|
|
|
|
var initial = new games.dmg.fantasystick.persistence.CharacterRecord(created.id(), created.name(), created.revision(), stats,
|
|
|
|
|
created.level(), created.experience(), created.unspentPoints(), created.health(), java.util.Set.of("great_sword"),
|
|
|
|
|
java.util.List.of("great_sword", "", "", "", "", "", ""), created.cooldowns(), created.restrictedKills(), false, null, null);
|
|
|
|
|
try (var storage = new games.dmg.fantasystick.persistence.CharacterStorage(repository, clock)) {
|
|
|
|
|
storage.create(initial).get(5, java.util.concurrent.TimeUnit.SECONDS);
|
|
|
|
|
var current = new java.util.concurrent.atomic.AtomicReference<>(initial);
|
|
|
|
|
var sessions = mock(games.dmg.fantasystick.session.EquipmentSessions.class);
|
|
|
|
|
var active = new games.dmg.fantasystick.session.EquipmentSessions.Active(java.util.UUID.randomUUID(),
|
|
|
|
|
java.util.UUID.randomUUID(), initial.id());
|
|
|
|
|
when(sessions.current(active)).thenReturn(true);
|
|
|
|
|
when(sessions.currentRecord(active)).thenAnswer(ignored -> java.util.Optional.of(current.get()));
|
|
|
|
|
when(sessions.acknowledgeProgress(eq(active), any(), any())).thenAnswer(call -> {
|
|
|
|
|
current.set(call.getArgument(2)); return true;
|
|
|
|
|
});
|
|
|
|
|
var player = mock(org.bukkit.entity.Player.class); var target = mock(org.bukkit.entity.LivingEntity.class);
|
|
|
|
|
when(target.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
|
|
|
|
|
var hits = new java.util.concurrent.atomic.AtomicInteger();
|
|
|
|
|
doAnswer(call -> { hits.incrementAndGet(); return null; }).when(target)
|
|
|
|
|
.damage(anyDouble(), any(org.bukkit.damage.DamageSource.class));
|
|
|
|
|
var sword = new games.dmg.fantasystick.ability.GreatSword(
|
|
|
|
|
games.dmg.fantasystick.ability.GreatSword.Settings.defaults());
|
|
|
|
|
var registry = new games.dmg.fantasystick.ability.AbilityRegistry(); registry.register(sword.definition());
|
|
|
|
|
var parties = new games.dmg.fantasystick.party.PartyRoster(clock, ignored -> true);
|
|
|
|
|
var engine = new games.dmg.fantasystick.ability.AbilityEngine(registry, storage, sessions, parties, clock,
|
|
|
|
|
Runnable::run, (delay, action) -> () -> {}, ignored -> {}, failure -> fail(failure.toString()));
|
|
|
|
|
Runnable cast = () -> engine.accept(new games.dmg.fantasystick.session.ControlInput(player, active, 2, "great_sword",
|
|
|
|
|
games.dmg.fantasystick.session.ControlInput.Kind.ABILITY,
|
|
|
|
|
games.dmg.fantasystick.session.ControlInput.Gesture.ATTACK, target, 0, 1, () -> true));
|
|
|
|
|
cast.run(); await(hits, 1);
|
|
|
|
|
var deadline = now.get().plusMillis(750); assertEquals(deadline, current.get().cooldowns().get("great_sword"));
|
|
|
|
|
cast.run(); assertEquals(1, hits.get());
|
|
|
|
|
now.set(deadline.minusNanos(1)); cast.run(); assertEquals(1, hits.get());
|
|
|
|
|
now.set(deadline); cast.run(); await(hits, 2);
|
|
|
|
|
engine.close(); sword.close(); parties.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void failedCooldownPersistenceDealsNoDamageOrFreeEffect() {
|
|
|
|
|
var storage = mock(games.dmg.fantasystick.persistence.CharacterStorage.class);
|
|
|
|
|
var sessions = mock(games.dmg.fantasystick.session.EquipmentSessions.class);
|
|
|
|
@@ -312,6 +360,18 @@ class GreatSwordNativeTest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void retainedRecoveryIsHonoredWithoutRewritingConfiguration() throws Exception {
|
|
|
|
|
String retained = "great-sword-recovery-seconds: 1.2\n";
|
|
|
|
|
java.nio.file.Files.writeString(data.resolve("config.yml"), retained);
|
|
|
|
|
try (var f = new FantasyStickLifecycleNativeTest.Fixture(data)) {
|
|
|
|
|
f.plugin.setEnabled(true);
|
|
|
|
|
var definition = ((games.dmg.fantasystick.ability.AbilityRegistry)
|
|
|
|
|
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "abilityRegistry")).find("great_sword").orElseThrow();
|
|
|
|
|
assertEquals(java.time.Duration.ofMillis(1200), definition.cooldown());
|
|
|
|
|
assertEquals(retained, java.nio.file.Files.readString(data.resolve("config.yml")));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void configuredDefinitionAndChargeCurvePreserveExactUncappedScaling() throws Exception {
|
|
|
|
|
java.nio.file.Files.writeString(data.resolve("config.yml"),
|
|
|
|
|
"great-sword-base-damage: 9\ngreat-sword-recovery-seconds: 2.5\n");
|
|
|
|
@@ -389,6 +449,11 @@ class GreatSwordNativeTest {
|
|
|
|
|
old.cooldowns(), old.restrictedKills(), false, null, null), old.revision());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void await(java.util.concurrent.atomic.AtomicInteger value, int expected) throws Exception {
|
|
|
|
|
for (int attempt = 0; attempt < 500 && value.get() < expected; attempt++) Thread.sleep(10);
|
|
|
|
|
assertEquals(expected, value.get());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void withStrength(Path data, FantasyStickLifecycleNativeTest.Actor actor, int strength) throws Exception {
|
|
|
|
|
var repository = new games.dmg.fantasystick.persistence.YamlCharacterRepository(data);
|
|
|
|
|
var old = repository.load(actor.core.record.id()).orElseThrow();
|
|
|
|
|