Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8bbe78a08 |
No files matched your search
@@ -15,11 +15,13 @@ import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.damage.DamageSource;
|
||||
import org.bukkit.damage.DamageType;
|
||||
import org.bukkit.entity.AbstractArrow;
|
||||
import org.bukkit.entity.Arrow;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Snowball;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.ProjectileHitEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
@@ -45,12 +47,12 @@ public final class FrostShard implements AbilityEffect, Listener, AutoCloseable
|
||||
}
|
||||
public static Settings defaults() {
|
||||
return new Settings(BigInteger.valueOf(4), Duration.ofSeconds(5), 32, Duration.ofSeconds(5),
|
||||
1, 0, Duration.ofSeconds(2));
|
||||
3, 0, Duration.ofSeconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
private final class State implements AutoCloseable {
|
||||
final Snowball projectile;
|
||||
final Arrow projectile;
|
||||
final AbilityExecution execution;
|
||||
final Location origin;
|
||||
final BigInteger intelligence;
|
||||
@@ -58,7 +60,7 @@ public final class FrostShard implements AbilityEffect, Listener, AutoCloseable
|
||||
LivingEntity target;
|
||||
boolean accepted;
|
||||
boolean finished;
|
||||
State(Snowball projectile, AbilityExecution execution, Location origin, BigInteger intelligence, Settings settings) {
|
||||
State(Arrow projectile, AbilityExecution execution, Location origin, BigInteger intelligence, Settings settings) {
|
||||
this.projectile = projectile; this.execution = execution; this.origin = origin;
|
||||
this.intelligence = intelligence; this.settings = settings;
|
||||
}
|
||||
@@ -144,7 +146,9 @@ public final class FrostShard implements AbilityEffect, Listener, AutoCloseable
|
||||
Vector direction = origin.getDirection();
|
||||
if (!Double.isFinite(direction.getX()) || !Double.isFinite(direction.getY()) || !Double.isFinite(direction.getZ())
|
||||
|| direction.lengthSquared() == 0) throw new IllegalStateException("Frost Shard direction is unavailable");
|
||||
var projectile = player.launchProjectile(Snowball.class, direction.normalize().multiply(current.speedPerTick()));
|
||||
var projectile = player.launchProjectile(Arrow.class, direction.normalize().multiply(current.speedPerTick()));
|
||||
projectile.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED);
|
||||
projectile.setDamage(0); projectile.setKnockbackStrength(0); projectile.setPierceLevel(0); projectile.setCritical(false);
|
||||
var state = new State(projectile, execution, origin, execution.character().stats().get(Stat.INT), current);
|
||||
if (projectiles.putIfAbsent(projectile.getUniqueId(), state) != null) {
|
||||
projectile.remove(); throw new IllegalStateException("Duplicate Frost Shard projectile identity");
|
||||
@@ -160,6 +164,13 @@ public final class FrostShard implements AbilityEffect, Listener, AutoCloseable
|
||||
player.getWorld().spawnParticle(Particle.SNOWFLAKE, origin, 8, 0.15, 0.15, 0.15, 0.01);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void containNativeDamage(EntityDamageByEntityEvent event) {
|
||||
if (!(event.getDamager() instanceof Arrow arrow)) return;
|
||||
var state = projectiles.get(arrow.getUniqueId());
|
||||
if (state != null && damaging != state) event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void hit(ProjectileHitEvent event) {
|
||||
var state = projectiles.remove(event.getEntity().getUniqueId());
|
||||
|
||||
@@ -36,7 +36,7 @@ public final class FrostShardSettings implements AutoCloseable {
|
||||
public static final String SLOWNESS = "frost-shard-slowness-duration-seconds";
|
||||
private static final List<String> KEYS = List.of(BASE_DAMAGE, COOLDOWN, LIFETIME, SPEED, RANGE, AMPLIFIER, SLOWNESS);
|
||||
private static final Map<String, String> DEFAULTS = Map.of(BASE_DAMAGE, "4", COOLDOWN, "5", RANGE, "32",
|
||||
LIFETIME, "5", SPEED, "1", AMPLIFIER, "0", SLOWNESS, "2");
|
||||
LIFETIME, "5", SPEED, "3", AMPLIFIER, "0", SLOWNESS, "2");
|
||||
|
||||
@FunctionalInterface interface Replacement { void move(Path from, Path to) throws IOException; }
|
||||
public record Update(String key, String value, FrostShard.Settings settings,
|
||||
|
||||
@@ -77,7 +77,7 @@ frost-shard-base-damage: 4
|
||||
frost-shard-cooldown-seconds: 5
|
||||
frost-shard-range-blocks: 32
|
||||
frost-shard-lifetime-seconds: 5
|
||||
frost-shard-projectile-speed-blocks-per-tick: 1
|
||||
frost-shard-projectile-speed-blocks-per-tick: 3
|
||||
frost-shard-slowness-amplifier: 0
|
||||
frost-shard-slowness-duration-seconds: 2
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ class FrostShardNativeTest {
|
||||
String book = PartyBookNativeTest.text(a).replaceAll("\\s+", "");
|
||||
assertTrue(book.contains("FrostShard"), book); assertTrue(book.contains("INT12"), book);
|
||||
assertTrue(book.contains("WIS10"), book); assertTrue(book.contains("SlownessI"), book);
|
||||
assertTrue(book.contains("projectile-speed=3"), book);
|
||||
var learn = abilityButton(a, "[Learn]", "frost_shard");
|
||||
PartyBookNativeTest.click(f, a, learn); PartyBookNativeTest.click(f, a, learn);
|
||||
PartyBookNativeTest.drain(f); PartyBookNativeTest.drain(f);
|
||||
@@ -41,17 +42,23 @@ class FrostShardNativeTest {
|
||||
eye.setDirection(new org.bukkit.util.Vector(1, 0, 0));
|
||||
doReturn(eye).when(a.player).getEyeLocation();
|
||||
var projectile = projectile(a, new org.bukkit.Location(a.world, 10, 65.6, 0));
|
||||
var velocity = new java.util.concurrent.atomic.AtomicReference<org.bukkit.util.Vector>();
|
||||
doAnswer(call -> {
|
||||
velocity.set(call.getArgument(1));
|
||||
f.dispatcher.accept(new org.bukkit.event.entity.ProjectileLaunchEvent(projectile));
|
||||
return projectile;
|
||||
}).when(a.player).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
}).when(a.player).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
a.core.inventory.getInventory().setSelectedSlot(2);
|
||||
var cast = new org.bukkit.event.player.PlayerInteractEvent(a.player,
|
||||
org.bukkit.event.block.Action.RIGHT_CLICK_AIR, a.core.inventory.getItem(2), null,
|
||||
org.bukkit.block.BlockFace.SELF, org.bukkit.inventory.EquipmentSlot.HAND);
|
||||
f.dispatcher.accept(cast); PartyBookNativeTest.drain(f); PartyBookNativeTest.drain(f);
|
||||
assertEquals(org.bukkit.event.Event.Result.DENY, cast.useItemInHand());
|
||||
verify(a.player).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
verify(a.player).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
assertEquals(new org.bukkit.util.Vector(3, 0, 0), velocity.get());
|
||||
verify(projectile).setPickupStatus(org.bukkit.entity.AbstractArrow.PickupStatus.DISALLOWED);
|
||||
verify(projectile).setDamage(0); verify(projectile).setCritical(false);
|
||||
verify(projectile).setPierceLevel(0); verify(projectile).setKnockbackStrength(0);
|
||||
var frost = FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "frostShard");
|
||||
assertEquals(1, ((java.util.Map<?, ?>) FantasyStickLifecycleNativeTest.Fixture.field(frost, "projectiles")).size());
|
||||
|
||||
@@ -60,6 +67,9 @@ class FrostShardNativeTest {
|
||||
when(target.getType()).thenReturn(org.bukkit.entity.EntityType.ZOMBIE);
|
||||
when(target.getLocation()).thenReturn(new org.bukkit.Location(a.world, 10, 65.6, 0));
|
||||
when(target.addPotionEffect(any())).thenReturn(true);
|
||||
var vanilla = new org.bukkit.event.entity.EntityDamageByEntityEvent(projectile, target,
|
||||
org.bukkit.event.entity.EntityDamageEvent.DamageCause.PROJECTILE, mock(org.bukkit.damage.DamageSource.class), 9);
|
||||
f.dispatcher.accept(vanilla); assertTrue(vanilla.isCancelled(), "Owned arrow native damage is contained");
|
||||
var damages = new java.util.ArrayList<org.bukkit.event.entity.EntityDamageByEntityEvent>();
|
||||
doAnswer(call -> {
|
||||
var source = (org.bukkit.damage.DamageSource) call.getArgument(1);
|
||||
@@ -184,13 +194,13 @@ class FrostShardNativeTest {
|
||||
verify(projectile, atLeastOnce()).remove();
|
||||
|
||||
learnAndAssign(f, a); cast(f, a);
|
||||
verify(a.player, times(1)).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
verify(a.player, times(1)).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
assertTrue(a.messages.stream().anyMatch(message -> message.contains("ready in")));
|
||||
var sheet = a.core.inventory.getItemInOffHand().clone();
|
||||
a.core.inventory.setItemInOffHand(null); f.changed(a); PartyBookNativeTest.drain(f); f.awaitCharacterAndReceipt(a);
|
||||
var b = new FantasyStickLifecycleNativeTest.Actor(data); b.core.inventory.setItemInOffHand(sheet);
|
||||
f.players.put(b.core.id, b.player); f.activate(b); cast(f, b);
|
||||
verify(b.player, never()).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
verify(b.player, never()).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
assertTrue(b.messages.stream().anyMatch(message -> message.contains("ready in")));
|
||||
assertEquals(deadline, storage.find(a.core.record.id()).get().orElseThrow().cooldowns().get("frost_shard"));
|
||||
}
|
||||
@@ -225,10 +235,22 @@ class FrostShardNativeTest {
|
||||
engine.accept(new games.dmg.fantasystick.session.ControlInput(player, active, 2, "frost_shard",
|
||||
games.dmg.fantasystick.session.ControlInput.Kind.ABILITY,
|
||||
games.dmg.fantasystick.session.ControlInput.Gesture.USE, null, 0, 0, () -> true));
|
||||
verify(player, never()).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
verify(player, never()).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
assertEquals(1, failures.size()); engine.close(); frost.close(); parties.close();
|
||||
}
|
||||
|
||||
@Test void retainedProjectileSpeedIsHonoredWithoutRewritingConfiguration() throws Exception {
|
||||
String retained = "frost-shard-projectile-speed-blocks-per-tick: 1\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("frost_shard").orElseThrow();
|
||||
assertEquals(0, new java.math.BigDecimal("1").compareTo(definition.parameters().get("projectile-speed")));
|
||||
assertEquals(retained, java.nio.file.Files.readString(data.resolve("config.yml")));
|
||||
}
|
||||
}
|
||||
|
||||
@Test void configuredDefinitionAndFormulaPreserveReviewedFiniteUncappedScaling() throws Exception {
|
||||
java.nio.file.Files.writeString(data.resolve("config.yml"),
|
||||
"frost-shard-base-damage: 9\nfrost-shard-cooldown-seconds: 2.5\n"
|
||||
@@ -286,7 +308,7 @@ class FrostShardNativeTest {
|
||||
doReturn(eye).when(a.player).getEyeLocation();
|
||||
var projectile = projectile(a, new org.bukkit.Location(a.world, 2, 65.6, 0));
|
||||
doAnswer(call -> { f.dispatcher.accept(new org.bukkit.event.entity.ProjectileLaunchEvent(projectile)); return projectile; })
|
||||
.when(a.player).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
.when(a.player).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
var target = mock(org.bukkit.entity.LivingEntity.class);
|
||||
when(target.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
|
||||
when(target.getLocation()).thenReturn(new org.bukkit.Location(a.world, 2, 65.6, 0));
|
||||
@@ -458,14 +480,14 @@ class FrostShardNativeTest {
|
||||
|
||||
private static void prepareLaunches(FantasyStickLifecycleNativeTest.Fixture fixture,
|
||||
FantasyStickLifecycleNativeTest.Actor actor,
|
||||
org.bukkit.entity.Snowball... projectiles) {
|
||||
org.bukkit.entity.Arrow... projectiles) {
|
||||
var eye = new org.bukkit.Location(actor.world, 0, 65.6, 0);
|
||||
eye.setDirection(new org.bukkit.util.Vector(1, 0, 0)); doReturn(eye).when(actor.player).getEyeLocation();
|
||||
var queue = new java.util.ArrayDeque<>(java.util.List.of(projectiles));
|
||||
doAnswer(call -> {
|
||||
var projectile = queue.removeFirst(); fixture.dispatcher.accept(new org.bukkit.event.entity.ProjectileLaunchEvent(projectile));
|
||||
return projectile;
|
||||
}).when(actor.player).launchProjectile(eq(org.bukkit.entity.Snowball.class), any(org.bukkit.util.Vector.class));
|
||||
}).when(actor.player).launchProjectile(eq(org.bukkit.entity.Arrow.class), any(org.bukkit.util.Vector.class));
|
||||
}
|
||||
|
||||
private static net.minecraft.network.chat.ClickEvent.Custom abilityButton(
|
||||
@@ -518,9 +540,9 @@ class FrostShardNativeTest {
|
||||
old.learned(), old.loadout(), old.cooldowns(), old.restrictedKills(), false, null, null), old.revision());
|
||||
}
|
||||
|
||||
private static org.bukkit.entity.Snowball projectile(FantasyStickLifecycleNativeTest.Actor actor,
|
||||
org.bukkit.Location location) {
|
||||
var projectile = mock(org.bukkit.entity.Snowball.class);
|
||||
private static org.bukkit.entity.Arrow projectile(FantasyStickLifecycleNativeTest.Actor actor,
|
||||
org.bukkit.Location location) {
|
||||
var projectile = mock(org.bukkit.entity.Arrow.class);
|
||||
when(projectile.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
|
||||
when(projectile.getShooter()).thenReturn(actor.player);
|
||||
when(projectile.getLocation()).thenAnswer(ignored -> location.clone());
|
||||
|
||||
@@ -52,7 +52,7 @@ class DistributionTest {
|
||||
assertTrue(defaults.contains("great-sword-recovery-seconds: 1.2"));
|
||||
for (String setting : java.util.List.of("frost-shard-base-damage: 4",
|
||||
"frost-shard-cooldown-seconds: 5", "frost-shard-range-blocks: 32",
|
||||
"frost-shard-lifetime-seconds: 5", "frost-shard-projectile-speed-blocks-per-tick: 1",
|
||||
"frost-shard-lifetime-seconds: 5", "frost-shard-projectile-speed-blocks-per-tick: 3",
|
||||
"frost-shard-slowness-amplifier: 0", "frost-shard-slowness-duration-seconds: 2")) {
|
||||
assertTrue(defaults.contains(setting), "Missing packaged Frost Shard default: " + setting);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ class FrostShardSettingsTest {
|
||||
}
|
||||
|
||||
@Test void rejectsMalformedAndOutOfRangeValuesBeforeQueueingOrChangingEffectiveState() {
|
||||
assertEquals("3", FrostShardSettings.defaultValue(FrostShardSettings.SPEED));
|
||||
try (var settings = new FrostShardSettings(data, defaults())) {
|
||||
assertEquals(3, settings.settings().speedPerTick());
|
||||
for (var entry : Map.of(
|
||||
FrostShardSettings.BASE_DAMAGE, "-1",
|
||||
FrostShardSettings.COOLDOWN, "31536001",
|
||||
|
||||
Reference in new issue
Block a user