|
|
|
@@ -32,7 +32,7 @@ class ShieldBashNativeTest {
|
|
|
|
|
a.core.inventory.getInventory().setSelectedSlot(0); PartyBookNativeTest.open(f, a);
|
|
|
|
|
String book = PartyBookNativeTest.text(a).replaceAll("\\s+", "");
|
|
|
|
|
assertTrue(book.contains("ShieldBash")); assertTrue(book.contains("STR12")); assertTrue(book.contains("CON12"));
|
|
|
|
|
assertFalse(book.contains("Cost:")); assertTrue(book.contains("3")); assertTrue(book.contains("6s"));
|
|
|
|
|
assertFalse(book.contains("Cost:")); assertTrue(book.contains("3")); assertTrue(book.contains("3s"));
|
|
|
|
|
PartyBookNativeTest.click(f, a, abilityButton(a, "[Learn]", "shield_bash"));
|
|
|
|
|
PartyBookNativeTest.drain(f); PartyBookNativeTest.drain(f);
|
|
|
|
|
var storage = (CharacterStorage) FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "characters");
|
|
|
|
@@ -197,6 +197,71 @@ class ShieldBashNativeTest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void packagedCooldownRefusesImmediatelyAndOneNanosecondBeforeButAcceptsExactBoundary() 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 EnumMap<>(created.stats()); stats.put(Stat.STR, BigInteger.valueOf(12)); stats.put(Stat.CON, BigInteger.valueOf(12));
|
|
|
|
|
var initial = new CharacterRecord(created.id(), created.name(), created.revision(), stats, created.level(), created.experience(),
|
|
|
|
|
created.unspentPoints(), created.health(), java.util.Set.of("shield_bash"),
|
|
|
|
|
List.of("shield_bash", "", "", "", "", "", ""), created.cooldowns(), created.restrictedKills(), false, null, null);
|
|
|
|
|
try (var storage = new 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 world = mock(org.bukkit.World.class); var player = mock(org.bukkit.entity.Player.class);
|
|
|
|
|
when(player.getUniqueId()).thenReturn(active.player()); doReturn(new Location(world, 0, 64, 0)).when(player).getLocation();
|
|
|
|
|
var target = mock(org.bukkit.entity.LivingEntity.class); when(target.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
|
|
|
|
|
when(target.getLocation()).thenReturn(new Location(world, 2, 64, 0)); doReturn(true).when(player).hasLineOfSight(target);
|
|
|
|
|
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 bash = new games.dmg.fantasystick.ability.ShieldBash(
|
|
|
|
|
games.dmg.fantasystick.ability.ShieldBash.Settings.defaults(), ignored -> {});
|
|
|
|
|
var registry = new games.dmg.fantasystick.ability.AbilityRegistry(); registry.register(bash.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, "shield_bash",
|
|
|
|
|
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().plusSeconds(3); assertEquals(deadline, current.get().cooldowns().get("shield_bash"));
|
|
|
|
|
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(); bash.close(); parties.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void retainedSixSecondCooldownIsHonoredWithoutRewritingConfiguration() throws Exception {
|
|
|
|
|
String retained = "shield-bash-cooldown-seconds: 6\n";
|
|
|
|
|
java.nio.file.Files.writeString(data.resolve("config.yml"), retained);
|
|
|
|
|
try (var f = new FantasyStickLifecycleNativeTest.Fixture(data)) {
|
|
|
|
|
f.plugin.setEnabled(true);
|
|
|
|
|
var registry = (games.dmg.fantasystick.ability.AbilityRegistry)
|
|
|
|
|
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "abilityRegistry");
|
|
|
|
|
assertEquals(java.time.Duration.ofSeconds(6), registry.find("shield_bash").orElseThrow().cooldown());
|
|
|
|
|
assertEquals(retained, java.nio.file.Files.readString(data.resolve("config.yml")));
|
|
|
|
|
var sender = new EquipmentAdminCommandNativeTest.Sender();
|
|
|
|
|
assertEquals(List.of("3", "6"), f.command.tabComplete(sender.port, "fantasystick",
|
|
|
|
|
new String[]{"config", "shield-bash-cooldown-seconds", ""}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test void exactDamageFormulaSupportsFiniteUncappedStats() {
|
|
|
|
|
assertEquals(BigInteger.valueOf(5), games.dmg.fantasystick.ability.ShieldBash.damage(BigInteger.valueOf(12)));
|
|
|
|
|
assertEquals(BigInteger.valueOf(3), games.dmg.fantasystick.ability.ShieldBash.damage(BigInteger.valueOf(9)));
|
|
|
|
@@ -291,6 +356,11 @@ class ShieldBashNativeTest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 learnAndAssign(FantasyStickLifecycleNativeTest.Fixture fixture,
|
|
|
|
|
FantasyStickLifecycleNativeTest.Actor actor) throws Exception {
|
|
|
|
|
actor.core.inventory.getInventory().setSelectedSlot(0); PartyBookNativeTest.open(fixture, actor);
|
|
|
|
|