Minestom/src/test/java/net/minestom/server/entity/EntityVelocityIntegrationTe...

214 lines
8.4 KiB
Java

package net.minestom.server.entity;
import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.instance.Instance;
import net.minestom.server.network.packet.server.play.EntityVelocityPacket;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import static org.junit.jupiter.api.Assertions.*;
@EnvTest
public class EntityVelocityIntegrationTest {
@Test
public void gravity(Env env) {
var instance = env.createFlatInstance();
loadChunks(instance);
var entity = new Entity(EntityTypes.ZOMBIE);
entity.setInstance(instance, new Pos(0, 42, 0)).join();
env.tick(); // Ensure velocity downwards is present
testMovement(env, entity, new Vec(0.0, 42.0, 0.0),
new Vec(0.0, 41.92159999847412, 0.0),
new Vec(0.0, 41.76636799395752, 0.0),
new Vec(0.0, 41.53584062504456, 0.0),
new Vec(0.0, 41.231523797587016, 0.0),
new Vec(0.0, 40.85489329934836, 0.0),
new Vec(0.0, 40.40739540236494, 0.0),
new Vec(0.0, 40.0, 0.0));
}
@Test
public void singleKnockback(Env env) {
var instance = env.createFlatInstance();
loadChunks(instance);
var entity = new Entity(EntityTypes.ZOMBIE);
entity.setInstance(instance, new Pos(0, 40, 0)).join();
env.tick();
env.tick(); // Ensures the entity is onGround
entity.takeKnockback(0.4f, 0, -1);
testMovement(env, entity, new Vec(0.0, 40.0, 0.0),
new Vec(0.0, 40.360800005197525, 0.4000000059604645),
new Vec(0.0, 40.63598401564693, 0.6184000345826153),
new Vec(0.0, 40.827264349610196, 0.8171440663565412),
new Vec(0.0, 40.9363190790167, 0.9980011404830835),
new Vec(0.0, 40.96479271438924, 1.1625810826814025),
new Vec(0.0, 40.914296876071546, 1.3123488343981535),
new Vec(0.0, 40.7864109520312, 1.4486374923882126),
new Vec(0.0, 40.58268274250654, 1.5726601747334787),
new Vec(0.0, 40.304629091760695, 1.685520818920295),
new Vec(0.0, 40.0, 1.7882240080901861),
new Vec(0.0, 40.0, 1.8816839129282854),
new Vec(0.0, 40.0, 1.9327130268970532),
new Vec(0.0, 40.0, 1.9605749263602332),
new Vec(0.0, 40.0, 1.9757875252341128),
new Vec(0.0, 40.0, 1.9840936051840241),
new Vec(0.0, 40.0, 1.9886287253634418),
new Vec(0.0, 40.0, 1.9886287253634418));
}
@Test
public void doubleKnockback(Env env) {
var instance = env.createFlatInstance();
loadChunks(instance);
var entity = new Entity(EntityTypes.ZOMBIE);
entity.setInstance(instance, new Pos(0, 40, 0)).join();
env.tick();
env.tick(); // Ensures the entity is onGround
entity.takeKnockback(0.4f, 0, -1);
entity.takeKnockback(0.5f, 0, -1);
assertTrue(entity.hasVelocity());
testMovement(env, entity, new Vec(0.0, 40.0, 0.0),
new Vec(0.0, 40.4, 0.7000000029802322),
new Vec(0.0, 40.71360000610351, 1.0822000490009787),
new Vec(0.0, 40.94252801654052, 1.4300021009034531),
new Vec(0.0, 41.088477469609366, 1.7465019772561767),
new Vec(0.0, 41.153107934874726, 2.0345168730376946),
new Vec(0.0, 41.138045790541625, 2.2966104357523673),
new Vec(0.0, 41.04488488728202, 2.5351155846963964),
new Vec(0.0, 40.87518719878482, 2.7521552764905097),
new Vec(0.0, 40.630483459294965, 2.949661401715245),
new Vec(0.0, 40.312273788401676, 3.1293919808495585),
new Vec(0.0, 40.0, 3.292946812575406),
new Vec(0.0, 40.0, 3.441781713735323),
new Vec(0.0, 40.0, 3.523045579207649),
new Vec(0.0, 40.0, 3.56741565490924),
new Vec(0.0, 40.0, 3.5916417190562298),
new Vec(0.0, 40.0, 3.6048691516168874),
new Vec(0.0, 40.0, 3.6120913306338815),
new Vec(0.0, 40.0, 3.616034640835186));
}
@Test
public void flyingVelocity(Env env) {
var instance = env.createFlatInstance();
loadChunks(instance);
var player = env.createPlayer(instance, new Pos(0, 42, 0));
env.tick();
final double epsilon = 0.00001;
assertEquals(player.getVelocity().y(), -1.568, epsilon);
double previousVelocity = player.getVelocity().y();
player.setFlying(true);
env.tick();
// Every tick, the y velocity is multiplied by 0.6, and after 27 ticks it should be 0
for (int i = 0; i < 27; i++) {
assertEquals(player.getVelocity().y(), previousVelocity * 0.6, epsilon);
previousVelocity = player.getVelocity().y();
env.tick();
}
assertEquals(player.getVelocity().y(), 0);
}
@Test
public void flyingPlayerMovement(Env env) {
// Player movement should not send velocity packets as already client predicted
var instance = env.createFlatInstance();
var player = env.createPlayer(instance, new Pos(0, 42, 0));
player.setFlying(true);
var witness = env.createConnection();
witness.connect(instance, new Pos(0, 42, 0)).join();
var tracker = witness.trackIncoming(EntityVelocityPacket.class);
env.tick(); // Process gravity velocity
tracker.assertEmpty();
}
@Test
public void testHasVelocity(Env env) {
var instance = env.createFlatInstance();
loadChunks(instance);
var entity = new Entity(EntityType.ZOMBIE);
// Should be false because the new entity should have no velocity
assertFalse(entity.hasVelocity());
entity.setInstance(instance, new Pos(0, 41, 0)).join();
env.tick();
// Should be true: The entity is currently falling (in the air), so it does have a velocity.
// Only entities on the ground should ignore the default velocity.
assertTrue(entity.hasVelocity());
env.tick();
// Now that the entity is on the ground, it should no longer have a velocity.
assertFalse(entity.hasVelocity());
}
@Test
public void countVelocityPackets(Env env) {
final int VELOCITY_UPDATE_INTERVAL = 1;
var instance = env.createFlatInstance();
var viewerConnection = env.createConnection();
viewerConnection.connect(instance, new Pos(1, 40, 1)).join();
var entity = new Entity(EntityType.ZOMBIE);
entity.setInstance(instance, new Pos(0,40,0)).join();
AtomicInteger i = new AtomicInteger();
BooleanSupplier tickLoopCondition = () -> i.getAndIncrement() < Math.max(VELOCITY_UPDATE_INTERVAL, 1);
var tracker = viewerConnection.trackIncoming(EntityVelocityPacket.class);
env.tickWhile(tickLoopCondition, null);
tracker.assertEmpty(); // Verify no updates are sent while the entity is not moving
entity.setVelocity(new Vec(0, 5, 0));
tracker = viewerConnection.trackIncoming(EntityVelocityPacket.class);
i.set(0);
env.tickWhile(tickLoopCondition, null);
tracker.assertCount(1); // Verify the update is only sent once
}
private void testMovement(Env env, Entity entity, Vec... sample) {
final double epsilon = 0.003;
for (Vec vec : sample) {
assertEquals(vec.x(), entity.getPosition().x(), epsilon);
assertEquals(vec.y(), entity.getPosition().y(), epsilon);
assertEquals(vec.z(), entity.getPosition().z(), epsilon);
env.tick();
}
}
private void loadChunks(Instance instance) {
ChunkUtils.optionalLoadAll(instance, new long[]{
ChunkUtils.getChunkIndex(-1, -1),
ChunkUtils.getChunkIndex(-1, 0),
ChunkUtils.getChunkIndex(-1, 1),
ChunkUtils.getChunkIndex(0, -1),
ChunkUtils.getChunkIndex(0, 0),
ChunkUtils.getChunkIndex(0, 1),
ChunkUtils.getChunkIndex(1, -1),
ChunkUtils.getChunkIndex(1, 0),
ChunkUtils.getChunkIndex(1, 1),
}, null).join();
}
}