Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a637e4090c |
No files matched your search
@@ -28,6 +28,7 @@ public final class TrainingDummyController implements Listener, TrainingDamage,
|
||||
private final NamespacedKey idKey;
|
||||
private final NamespacedKey typeKey;
|
||||
private final java.util.Set<Entity> relocating = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>());
|
||||
private final java.util.Set<Zombie> loaded = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>());
|
||||
private Attempt pending;
|
||||
private boolean closed;
|
||||
|
||||
@@ -64,14 +65,18 @@ public final class TrainingDummyController implements Listener, TrainingDamage,
|
||||
for (var entity : chunk.getEntities()) {
|
||||
if (!tagged(entity)) continue;
|
||||
UUID id = id(entity); var target = id == null ? null : desired.get(id);
|
||||
if (!(entity instanceof Zombie zombie) || target == null || !retained.add(id)) { entity.remove(); continue; }
|
||||
if (!(entity instanceof Zombie zombie) || target == null || !retained.add(id)) {
|
||||
entity.remove();
|
||||
if (entity instanceof Zombie zombie) loaded.remove(zombie);
|
||||
continue;
|
||||
}
|
||||
configure(zombie, target);
|
||||
var current = zombie.getLocation(); var expected = location(target);
|
||||
if (current.distanceSquared(expected) > 0.000001 || Math.abs(current.getYaw() - expected.getYaw()) > 0.001
|
||||
|| Math.abs(current.getPitch() - expected.getPitch()) > 0.001) {
|
||||
relocating.add(zombie); boolean moved;
|
||||
try { moved = zombie.teleport(expected); } finally { relocating.remove(zombie); }
|
||||
if (!moved) { zombie.remove(); spawn(target); }
|
||||
if (!moved) { zombie.remove(); loaded.remove(zombie); spawn(target); }
|
||||
}
|
||||
}
|
||||
for (var value : desired.values()) if (!retained.contains(value.id())) spawn(value);
|
||||
@@ -80,7 +85,10 @@ public final class TrainingDummyController implements Listener, TrainingDamage,
|
||||
public void removeLoaded(UUID id) {
|
||||
for (var world : plugin.getServer().getWorlds()) {
|
||||
var entities = world.getEntities(); if (entities != null) for (var entity : entities) {
|
||||
if (tagged(entity) && id.equals(id(entity))) entity.remove();
|
||||
if (tagged(entity) && id.equals(id(entity))) {
|
||||
entity.remove();
|
||||
if (entity instanceof Zombie zombie) loaded.remove(zombie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,8 +119,10 @@ public final class TrainingDummyController implements Listener, TrainingDamage,
|
||||
private void configure(Zombie zombie, TrainingDummy value) {
|
||||
zombie.getPersistentDataContainer().set(typeKey, PersistentDataType.STRING, TYPE);
|
||||
zombie.getPersistentDataContainer().set(idKey, PersistentDataType.STRING, value.id().toString());
|
||||
loaded.add(zombie);
|
||||
zombie.setAI(false); zombie.setAware(false); zombie.setGravity(false); zombie.setSilent(true);
|
||||
zombie.setInvulnerable(false); zombie.setCollidable(false); zombie.setCanPickupItems(false);
|
||||
zombie.setInvulnerable(false); zombie.setCollidable(true); refreshPushExemptions(zombie);
|
||||
zombie.setCanPickupItems(false);
|
||||
zombie.setLeashHolder(null); zombie.setRemoveWhenFarAway(false); zombie.setPersistent(true);
|
||||
zombie.setShouldBurnInDay(false); zombie.setBaby(false); zombie.setCanBreakDoors(false);
|
||||
zombie.setFireTicks(0); zombie.setFreezeTicks(0); zombie.setVelocity(new org.bukkit.util.Vector());
|
||||
@@ -121,6 +131,29 @@ public final class TrainingDummyController implements Listener, TrainingDamage,
|
||||
zombie.setCustomNameVisible(true);
|
||||
}
|
||||
|
||||
private void refreshPushExemptions(Zombie dummy) {
|
||||
var exemptions = dummy.getCollidableExemptions();
|
||||
exemptions.clear();
|
||||
var world = dummy.getLocation().getWorld();
|
||||
if (world == null) return;
|
||||
var entities = world.getEntities();
|
||||
if (entities == null) return;
|
||||
for (var entity : entities) {
|
||||
if (entity != dummy && entity instanceof LivingEntity) exemptions.add(entity.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePushExemption(Entity entity, boolean present) {
|
||||
if (!(entity instanceof LivingEntity)) return;
|
||||
var world = entity.getWorld();
|
||||
if (world == null) return;
|
||||
for (var dummy : java.util.List.copyOf(loaded)) {
|
||||
if (dummy == entity || !world.equals(dummy.getWorld()) || !managed(dummy)) continue;
|
||||
if (present) dummy.getCollidableExemptions().add(entity.getUniqueId());
|
||||
else dummy.getCollidableExemptions().remove(entity.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
private Location location(TrainingDummy value) {
|
||||
return new Location(plugin.getServer().getWorld(value.world()), value.x(), value.y(), value.z(), value.yaw(), value.pitch());
|
||||
}
|
||||
@@ -180,9 +213,18 @@ public final class TrainingDummyController implements Listener, TrainingDamage,
|
||||
}
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void chunk(org.bukkit.event.world.ChunkLoadEvent event) { reconcile(event.getChunk()); }
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void entityAdded(com.destroystokyo.paper.event.entity.EntityAddToWorldEvent event) {
|
||||
updatePushExemption(event.getEntity(), true);
|
||||
}
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void entityRemoved(com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent event) {
|
||||
if (event.getEntity() instanceof Zombie zombie) loaded.remove(zombie);
|
||||
updatePushExemption(event.getEntity(), false);
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
closed = true; pending = null;
|
||||
closed = true; pending = null; loaded.clear();
|
||||
for (var world : plugin.getServer().getWorlds()) {
|
||||
var entities = world.getEntities(); if (entities != null) for (var entity : entities) if (tagged(entity)) entity.remove();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@ class TrainingDummyNativeTest {
|
||||
var chunk = mock(Chunk.class); when(chunk.getWorld()).thenReturn(admin.world);
|
||||
when(chunk.getX()).thenReturn(x); when(chunk.getZ()).thenReturn(z); when(chunk.getEntities()).thenReturn(new Entity[0]);
|
||||
when(admin.world.getChunkAt(x, z)).thenReturn(chunk); when(admin.world.isChunkLoaded(x, z)).thenReturn(true);
|
||||
when(admin.world.getEntities()).thenReturn(List.of());
|
||||
var pusher = zombie(admin.core.location.clone().add(1, 0, 0));
|
||||
var projectile = mock(org.bukkit.entity.Arrow.class);
|
||||
when(projectile.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
when(admin.world.getEntities()).thenReturn(List.of(admin.player, pusher, projectile));
|
||||
var zombie = zombie(admin.core.location);
|
||||
doAnswer(call -> { ((Consumer<Zombie>) call.getArgument(2)).accept(zombie); return zombie; })
|
||||
.when(admin.world).spawn(any(Location.class), eq(Zombie.class), any(Consumer.class));
|
||||
@@ -59,12 +62,22 @@ class TrainingDummyNativeTest {
|
||||
Runnable callback = f.tasks.poll(5, java.util.concurrent.TimeUnit.SECONDS); assertNotNull(callback); callback.run();
|
||||
var directory = (TrainingDummyDirectory) FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "trainingDummies");
|
||||
var saved = directory.all().getFirst(); assertEquals("Trial Target", saved.name());
|
||||
verify(zombie).setAI(false); verify(zombie).setGravity(false); verify(zombie).setCollidable(false);
|
||||
verify(zombie).setAI(false); verify(zombie).setGravity(false); verify(zombie).setCollidable(true);
|
||||
assertTrue(zombie.getCollidableExemptions().contains(admin.core.id));
|
||||
assertTrue(zombie.getCollidableExemptions().contains(pusher.getUniqueId()));
|
||||
assertFalse(zombie.getCollidableExemptions().contains(projectile.getUniqueId()),
|
||||
"Projectile identities must retain collision with the dummy hit surface");
|
||||
verify(zombie).setCanPickupItems(false); verify(zombie).setRemoveWhenFarAway(false); verify(zombie).setPersistent(true);
|
||||
verify(zombie).setShouldBurnInDay(false); verify(zombie).setBaby(false);
|
||||
verify(zombie).customName(Component.text("Trial Target")); verify(zombie).setCustomNameVisible(true);
|
||||
var controller = (TrainingDummyController) FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "trainingDummyController");
|
||||
assertTrue(controller.tagged(zombie));
|
||||
var latePusher = zombie(admin.core.location.clone().add(2, 0, 0));
|
||||
when(admin.world.getEntities()).thenReturn(List.of(zombie, latePusher));
|
||||
controller.entityAdded(new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(latePusher, admin.world));
|
||||
assertTrue(zombie.getCollidableExemptions().contains(latePusher.getUniqueId()));
|
||||
controller.entityRemoved(new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(latePusher, admin.world));
|
||||
assertFalse(zombie.getCollidableExemptions().contains(latePusher.getUniqueId()));
|
||||
assertFalse(((games.dmg.fantasystick.party.PartyRegistrarController)
|
||||
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "partyRegistrarController")).tagged(zombie));
|
||||
assertFalse(((games.dmg.fantasystick.shop.FantasyArmorerController)
|
||||
@@ -222,11 +235,45 @@ class TrainingDummyNativeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test void pinnedNativeProjectileSweepRequiresTheDummyPickableHitSurface() {
|
||||
var level = mock(net.minecraft.world.level.Level.class);
|
||||
var projectile = mock(net.minecraft.world.entity.Entity.class);
|
||||
when(projectile.level()).thenReturn(level); when(projectile.getRootVehicle()).thenReturn(projectile);
|
||||
var dummy = mock(net.minecraft.world.entity.monster.zombie.Zombie.class, CALLS_REAL_METHODS);
|
||||
doReturn(false).when(dummy).isRemoved();
|
||||
doReturn(new net.minecraft.world.phys.AABB(1, 0, 0, 2, 2, 1)).when(dummy).getBoundingBox();
|
||||
doReturn(0f).when(dummy).getPickRadius(); doReturn(dummy).when(dummy).getRootVehicle();
|
||||
when(level.getEntities(same(projectile), any(net.minecraft.world.phys.AABB.class), any())).thenAnswer(call -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
var predicate = (java.util.function.Predicate<net.minecraft.world.entity.Entity>) call.getArgument(2);
|
||||
return predicate.test(dummy) ? List.of(dummy) : List.of();
|
||||
});
|
||||
var start = new net.minecraft.world.phys.Vec3(0, 1, 0.5);
|
||||
var end = new net.minecraft.world.phys.Vec3(3, 1, 0.5);
|
||||
var search = new net.minecraft.world.phys.AABB(0, 0, 0, 3, 2, 1);
|
||||
dummy.collides = false;
|
||||
assertFalse(dummy.isPickable());
|
||||
assertNull(net.minecraft.world.entity.projectile.ProjectileUtil.getEntityHitResult(
|
||||
projectile, start, end, search, net.minecraft.world.entity.Entity::isPickable, 9));
|
||||
dummy.collides = true;
|
||||
assertTrue(dummy.isPickable());
|
||||
assertSame(dummy, net.minecraft.world.entity.projectile.ProjectileUtil.getEntityHitResult(
|
||||
projectile, start, end, search, net.minecraft.world.entity.Entity::isPickable, 9).getEntity());
|
||||
var pusher = mock(net.minecraft.world.entity.Entity.class);
|
||||
var pusherId = UUID.randomUUID(); when(pusher.getUUID()).thenReturn(pusherId);
|
||||
doReturn(true).when(dummy).isPushable(); dummy.collidableExemptions = new java.util.HashSet<>();
|
||||
assertTrue(dummy.canCollideWithBukkit(pusher));
|
||||
dummy.collidableExemptions.add(pusherId);
|
||||
assertFalse(dummy.canCollideWithBukkit(pusher),
|
||||
"A living-entity exemption suppresses body pushing without removing projectile pickability");
|
||||
}
|
||||
|
||||
static Zombie zombie(Location location) {
|
||||
var zombie = mock(Zombie.class); when(zombie.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
when(zombie.getType()).thenReturn(org.bukkit.entity.EntityType.ZOMBIE); when(zombie.getName()).thenReturn("Training Dummy");
|
||||
when(zombie.getLocation()).thenReturn(location.clone()); when(zombie.getHealth()).thenReturn(20d);
|
||||
when(zombie.getAbsorptionAmount()).thenReturn(0d);
|
||||
when(zombie.getLocation()).thenReturn(location.clone()); when(zombie.getWorld()).thenReturn(location.getWorld());
|
||||
when(zombie.getHealth()).thenReturn(20d); when(zombie.getAbsorptionAmount()).thenReturn(0d);
|
||||
when(zombie.getCollidableExemptions()).thenReturn(new java.util.HashSet<>());
|
||||
Map<String, String> values = new HashMap<>(); var pdc = mock(PersistentDataContainer.class);
|
||||
when(zombie.getPersistentDataContainer()).thenReturn(pdc);
|
||||
doAnswer(call -> { values.put(((NamespacedKey) call.getArgument(0)).getKey(), call.getArgument(2)); return null; })
|
||||
|
||||
Reference in new issue
Block a user