Added gravity

This commit is contained in:
TheMode 2019-08-27 20:49:11 +02:00
parent 0a732034c2
commit 8b95e3881d
17 changed files with 264 additions and 52 deletions

View File

@ -36,7 +36,6 @@ public abstract class Entity implements Viewable, DataContainer {
protected Instance instance;
protected Position position;
protected boolean onGround;
protected float lastX, lastY, lastZ;
protected float lastYaw, lastPitch;
private int id;
@ -45,6 +44,9 @@ public abstract class Entity implements Viewable, DataContainer {
// Velocity
// TODO gravity implementation for entity other than players
protected Vector velocity = new Vector(); // Movement in block per second
protected float gravityDragPerTick;
private int gravityTickCounter;
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private Data data;
private Set<Entity> passengers = new CopyOnWriteArraySet<>();
@ -104,6 +106,8 @@ public abstract class Entity implements Viewable, DataContainer {
// Called when entity a new instance is set
public abstract void spawn();
public abstract boolean isOnGround();
public void teleport(Position position, Runnable callback) {
if (instance == null)
throw new IllegalStateException("You need to use Entity#setInstance before teleporting an entity!");
@ -114,7 +118,7 @@ public abstract class Entity implements Viewable, DataContainer {
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = position;
entityTeleportPacket.onGround = onGround;
entityTeleportPacket.onGround = isOnGround();
sendPacketToViewers(entityTeleportPacket);
};
@ -125,7 +129,7 @@ public abstract class Entity implements Viewable, DataContainer {
callback.run();
});
} else {
if (isChunkUnloaded(position.getX(), position.getZ()))
if (ChunkUtils.isChunkUnloaded(instance, position.getX(), position.getZ()))
return;
runnable.run();
if (callback != null)
@ -210,6 +214,36 @@ public abstract class Entity implements Viewable, DataContainer {
}
}
// Gravity
if (!(this instanceof Player) && !noGravity && gravityDragPerTick != 0) { // Players do manage gravity client-side
Position position = getPosition();
if (!isOnGround()) {
float strength = gravityDragPerTick * gravityTickCounter;
int firstBlock = 0;
for (int y = (int) position.getY(); y > 0; y--) {
short blockId = instance.getBlockId((int) position.getX(), y, (int) position.getZ());
if (blockId != 0) {
firstBlock = y;
break;
}
}
float newY = position.getY() - strength;
newY = Math.max(newY, firstBlock);
refreshPosition(position.getX(), newY, position.getZ());
gravityTickCounter++;
if (isOnGround()) { // Round Y axis when gravity movement is done
refreshPosition(position.getX(), Math.round(position.getY()), position.getZ());
gravityTickCounter = 0;
}
if (this instanceof EntityCreature) // Objects are automatically updated client side
teleport(getPosition());
}
}
update();
// Scheduled synchronization
@ -295,6 +329,10 @@ public abstract class Entity implements Viewable, DataContainer {
this.velocityTime = 0;
}
public void setGravity(float gravityDragPerTick) {
this.gravityDragPerTick = gravityDragPerTick;
}
public float getDistance(Entity entity) {
return getPosition().getDistance(entity.getPosition());
}
@ -360,18 +398,26 @@ public abstract class Entity implements Viewable, DataContainer {
sendMetadataIndex(0);
}
public boolean isOnFire() {
return onFire;
}
public void setGlowing(boolean glowing) {
this.glowing = glowing;
sendMetadataIndex(0);
}
public boolean isGlowing() {
return glowing;
}
public void setNoGravity(boolean noGravity) {
this.noGravity = noGravity;
sendMetadataIndex(5);
}
public boolean isChunkUnloaded(float x, float z) {
return getInstance().getChunk((int) Math.floor(x / 16), (int) Math.floor(z / 16)) == null;
public boolean hasNoGravity() {
return noGravity;
}
public void refreshPosition(float x, float y, float z) {
@ -436,6 +482,10 @@ public abstract class Entity implements Viewable, DataContainer {
}
}
public void refreshPosition(Position position) {
refreshPosition(position.getX(), position.getY(), position.getZ());
}
public void refreshView(float yaw, float pitch) {
this.lastYaw = position.getYaw();
this.lastPitch = position.getPitch();
@ -554,7 +604,7 @@ public abstract class Entity implements Viewable, DataContainer {
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = getPosition();
entityTeleportPacket.onGround = onGround;
entityTeleportPacket.onGround = isOnGround();
sendPacketToViewers(entityTeleportPacket);
}

View File

@ -5,13 +5,15 @@ import fr.themode.minestom.net.packet.server.play.EntityPacket;
import fr.themode.minestom.net.packet.server.play.EntityRelativeMovePacket;
import fr.themode.minestom.net.packet.server.play.SpawnMobPacket;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.utils.ChunkUtils;
import fr.themode.minestom.utils.EntityUtils;
import fr.themode.minestom.utils.Position;
// TODO viewers synchronization each X ticks?
public abstract class EntityCreature extends LivingEntity {
public EntityCreature(int entityType) {
super(entityType);
public EntityCreature(EntityType entityType) {
super(entityType.getId());
}
@Override
@ -26,7 +28,7 @@ public abstract class EntityCreature extends LivingEntity {
float newY = position.getY() + y;
float newZ = position.getZ() + z;
if (isChunkUnloaded(newX, newZ))
if (ChunkUtils.isChunkUnloaded(getInstance(), newX, newZ))
return;
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
@ -67,4 +69,8 @@ public abstract class EntityCreature extends LivingEntity {
playerConnection.sendPacket(getMetadataPacket());
}
@Override
public boolean isOnGround() {
return EntityUtils.isOnGround(this);
}
}

View File

@ -0,0 +1,85 @@
package fr.themode.minestom.entity;
import java.util.Arrays;
public enum EntityType {
ARMOR_STAND(1), // Object
BAT(3),
BLAZE(4),
CAT(6),
CAVE_SPIDER(7),
CHICKEN(8),
COD(9),
COW(10),
CREEPER(11),
DONKEY(12),
DOLPHIN(13),
DROWNED(15),
ELDER_GUARDIAN(16),
ENDER_DRAGON(18),
ENDERMAN(19),
ENDERMITE(20),
EVOKER(22),
FOX(27),
GHAST(28),
GIANT(29),
GUARDIAN(30),
HORSE(31),
HUSK(32),
ILLUSIONER(33),
LLAMA(38),
MAGMA_CUBE(40),
MULE(48),
MOOSHROOM(49),
OCELOT(50),
PANDA(52),
PARROT(53),
PIG(54),
PUFFERFISH(55),
ZOMBIE_PIGMAN(56),
POLAR_BEAR(57),
RABBIT(59),
SALMON(60),
SHEEP(61),
SHULKER(62),
SILVERFISH(64),
SKELETON(65),
SKELETON_HORSE(66),
SLIME(67),
SNOW_GOLEM(69),
SPIDER(72),
SQUID(73),
STRAY(74),
TRADER_LLAMA(75),
TROPICAL_FISH(76),
TURTLE(77),
VEX(83),
VILLAGER(84),
IRON_GOLEM(85),
VINDICATOR(86),
PILLAGER(87),
WANGERING_TRADER(88),
WITCH(89),
WITHER(90),
WITHER_SKELETON(91),
WOLF(93),
ZOMBIE(94),
ZOMBIE_HORSE(95),
ZOMBIE_VILLAGER(96),
PHANTOM(97),
RAVAGER(98);
private int id;
EntityType(int id) {
this.id = id;
}
public static EntityType fromId(int id) {
return Arrays.stream(values()).filter(entityType -> entityType.id == id).findFirst().get();
}
public int getId() {
return id;
}
}

View File

@ -12,6 +12,7 @@ public class ItemEntity extends ObjectEntity {
public ItemEntity(ItemStack itemStack) {
super(34);
this.itemStack = itemStack;
setGravity(0.02f);
}
@Override

View File

@ -5,6 +5,7 @@ import fr.themode.minestom.entity.property.Attribute;
import fr.themode.minestom.event.PickupItemEvent;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.packet.server.play.AnimationPacket;
import fr.themode.minestom.net.packet.server.play.CollectItemPacket;
import fr.themode.minestom.net.packet.server.play.EntityPropertiesPacket;
@ -26,6 +27,7 @@ public abstract class LivingEntity extends Entity {
public LivingEntity(int entityType) {
super(entityType);
setupAttributes();
setGravity(0.02f);
}
public abstract void kill();
@ -79,11 +81,21 @@ public abstract class LivingEntity extends Entity {
return buffer;
}
public void damage(float value) {
AnimationPacket animationPacket = new AnimationPacket();
animationPacket.entityId = getEntityId();
animationPacket.animation = AnimationPacket.Animation.TAKE_DAMAGE;
sendPacketToViewersAndSelf(animationPacket);
setHealth(getHealth() - value);
}
public float getHealth() {
return health;
}
public void setHealth(float health) {
health = Math.min(health, getMaxHealth());
this.health = health;
if (this.health <= 0) {
kill();

View File

@ -2,6 +2,7 @@ package fr.themode.minestom.entity;
import fr.themode.minestom.net.packet.server.play.SpawnObjectPacket;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.utils.EntityUtils;
// TODO viewers synchronization each X ticks?
public abstract class ObjectEntity extends Entity {
@ -31,4 +32,10 @@ public abstract class ObjectEntity extends Entity {
public void removeViewer(Player player) {
super.removeViewer(player);
}
@Override
public boolean isOnGround() {
return EntityUtils.isOnGround(this);
}
}

View File

@ -4,6 +4,7 @@ import fr.themode.minestom.Main;
import fr.themode.minestom.bossbar.BossBar;
import fr.themode.minestom.chat.Chat;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.demo.ChickenCreature;
import fr.themode.minestom.entity.property.Attribute;
import fr.themode.minestom.event.*;
import fr.themode.minestom.instance.Chunk;
@ -13,6 +14,7 @@ import fr.themode.minestom.instance.InstanceContainer;
import fr.themode.minestom.instance.demo.ChunkGeneratorDemo;
import fr.themode.minestom.inventory.Inventory;
import fr.themode.minestom.inventory.PlayerInventory;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.packet.client.ClientPlayPacket;
import fr.themode.minestom.net.packet.server.ServerPacket;
import fr.themode.minestom.net.packet.server.play.*;
@ -40,6 +42,8 @@ public class Player extends LivingEntity {
private GameMode gameMode;
private LevelType levelType;
protected boolean onGround;
private static InstanceContainer instanceContainer;
static {
@ -82,7 +86,7 @@ public class Player extends LivingEntity {
protected boolean spawned;
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
super(93);
super(100);
this.uuid = uuid;
this.username = username;
this.playerConnection = playerConnection;
@ -98,8 +102,11 @@ public class Player extends LivingEntity {
setEventCallback(AttackEvent.class, event -> {
Entity entity = event.getTarget();
if (entity instanceof EntityCreature) {
((EntityCreature) entity).kill();
sendMessage("You killed an entity!");
((EntityCreature) entity).damage(-1);
Vector velocity = getPosition().clone().getDirection().multiply(6);
velocity.setY(4f);
entity.setVelocity(velocity, 150);
sendMessage("You attacked an entity!");
} else if (entity instanceof Player) {
Player player = (Player) entity;
Vector velocity = getPosition().clone().getDirection().multiply(6);
@ -145,18 +152,18 @@ public class Player extends LivingEntity {
setGameMode(GameMode.SURVIVAL);
teleport(new Position(0, 66, 0));
/*ChickenCreature chickenCreature = new ChickenCreature();
ChickenCreature chickenCreature = new ChickenCreature();
chickenCreature.refreshPosition(2, 65, 2);
chickenCreature.setInstance(getInstance());
for (int ix = 0; ix < 4; ix++)
for (int iz = 0; iz < 4; iz++) {
ItemEntity itemEntity = new ItemEntity(new ItemStack(1, (byte) 32));
itemEntity.refreshPosition(ix, 66, iz);
itemEntity.setNoGravity(true);
itemEntity.refreshPosition(ix, 68, iz);
//itemEntity.setNoGravity(true);
itemEntity.setInstance(getInstance());
//itemEntity.remove();
}*/
}
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = "TEAMNAME" + new Random().nextInt(100);
@ -270,6 +277,11 @@ public class Player extends LivingEntity {
}
@Override
public boolean isOnGround() {
return onGround;
}
@Override
public void remove() {
clearBossBars();
@ -345,15 +357,12 @@ public class Player extends LivingEntity {
playerConnection.sendPacket(chatMessagePacket);
}
public void damage(float amount) {
@Override
public void damage(float value) {
if (getGameMode() == GameMode.CREATIVE)
return;
AnimationPacket animationPacket = new AnimationPacket();
animationPacket.entityId = getEntityId();
animationPacket.animation = AnimationPacket.Animation.TAKE_DAMAGE;
sendPacketToViewersAndSelf(animationPacket);
setHealth(getHealth() - amount);
super.damage(value);
}
@Override
@ -471,7 +480,7 @@ public class Player extends LivingEntity {
@Override
public void teleport(Position position, Runnable callback) {
super.teleport(position, () -> {
if (!instance.hasEnabledAutoChunkLoad() && isChunkUnloaded(position.getX(), position.getZ()))
if (!instance.hasEnabledAutoChunkLoad() && ChunkUtils.isChunkUnloaded(instance, position.getX(), position.getZ()))
return;
updatePlayerPosition();
if (callback != null)

View File

@ -1,12 +1,12 @@
package fr.themode.minestom.entity.demo;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.utils.Vector;
import fr.themode.minestom.entity.EntityType;
public class ChickenCreature extends EntityCreature {
public ChickenCreature() {
super(8);
super(EntityType.CHICKEN);
}
@Override
@ -43,11 +43,11 @@ public class ChickenCreature extends EntityCreature {
move(x * speed, 0, z * speed);
}
}*/
move(0, 0, speed);
// move(0, 0, speed);
}
@Override
public void spawn() {
setVelocity(new Vector(0, 1, 0), 3000);
// setVelocity(new Vector(0, 1, 0), 3000);
}
}

View File

@ -65,9 +65,12 @@ public class BlockBatch implements BlockModifier {
}
chunk.refreshDataPacket();
instance.sendChunkUpdate(chunk);
if (isLast && callback != null)
if (isLast) {
// data.clear();
if (callback != null)
callback.run();
}
}
});
}
}

View File

@ -54,6 +54,7 @@ public class ChunkBatch implements BlockModifier {
data.apply(chunk);
}
// dataList.clear();
chunk.refreshDataPacket();
instance.sendChunkUpdate(chunk);
if (callback != null)

View File

@ -153,21 +153,23 @@ public abstract class Instance implements BlockModifier {
lastInstance.removeEntity(entity); // If entity is in another instance, remove it from there and add it to this
}
if (entity instanceof Player) {
Player player = (Player) entity;
sendChunks(player);
// Send player all visible entities
long[] visibleChunksEntity = ChunkUtils.getChunksInRange(entity.getPosition(), Main.ENTITY_VIEW_DISTANCE);
boolean isPlayer = entity instanceof Player;
if (isPlayer) {
sendChunks((Player) entity);
}
// Send all visible entities
for (long chunkIndex : visibleChunksEntity) {
getEntitiesInChunk(chunkIndex).forEach(ent -> {
ent.addViewer(player);
if (isPlayer)
ent.addViewer((Player) entity);
if (ent instanceof Player) {
player.addViewer((Player) ent);
entity.addViewer((Player) ent);
}
});
}
}
Chunk chunk = getChunkAt(entity.getPosition());
addEntityToChunk(entity, chunk);

View File

@ -207,15 +207,19 @@ public class InstanceContainer extends Instance {
@Override
public void sendChunk(Player player, Chunk chunk) {
/*Buffer data = chunk.getFullDataPacket();
if(data == null) {
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
buffer.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(buffer);
buffer.getData().resetReaderIndex();
chunk.setFullDataPacket(buffer);
sendChunkUpdate(player, chunk);
});
}else{
sendChunkUpdate(player, chunk);
}*/
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(player, chunk);
});
// TODO use cached chunk data
/*chunkData.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(chunkData);
chunkData.getData().resetReaderIndex();*/
}
@Override

View File

@ -6,6 +6,7 @@ import fr.themode.minestom.net.packet.client.play.ClientPlayerLookPacket;
import fr.themode.minestom.net.packet.client.play.ClientPlayerPacket;
import fr.themode.minestom.net.packet.client.play.ClientPlayerPositionAndLookPacket;
import fr.themode.minestom.net.packet.client.play.ClientPlayerPositionPacket;
import fr.themode.minestom.utils.ChunkUtils;
public class PlayerPositionListener {
@ -42,7 +43,7 @@ public class PlayerPositionListener {
private static void processMovement(Player player, float x, float y, float z, Runnable runnable) {
//System.out.println("MOVEMENT PACKET " + Math.round(x) + ":" + Math.round(y) + ":" + Math.round(z));
boolean chunkTest = player.isChunkUnloaded(x, z);
boolean chunkTest = ChunkUtils.isChunkUnloaded(player.getInstance(), x, z);
if (chunkTest) {
player.teleport(player.getPosition());
return;

View File

@ -10,10 +10,18 @@ public class BlockPosition {
this.z = z;
}
public void add(int x, int y, int z) {
public BlockPosition add(int x, int y, int z) {
this.x += x;
this.y += y;
this.z += z;
return this;
}
public BlockPosition subtract(int x, int y, int z) {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
public int getX() {

View File

@ -1,7 +1,13 @@
package fr.themode.minestom.utils;
import fr.themode.minestom.instance.Instance;
public class ChunkUtils {
public static boolean isChunkUnloaded(Instance instance, float x, float z) {
return instance.getChunk((int) Math.floor(x / 16), (int) Math.floor(z / 16)) == null;
}
public static long getChunkIndex(int chunkX, int chunkZ) {
return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL);
}

View File

@ -3,6 +3,7 @@ package fr.themode.minestom.utils;
import fr.themode.minestom.Main;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.Instance;
public class EntityUtils {
@ -26,4 +27,13 @@ public class EntityUtils {
return false;
}
public static boolean isOnGround(Entity entity) {
Instance instance = entity.getInstance();
if (instance == null)
return false;
Position position = entity.getPosition();
short blockId = instance.getBlockId(position.toBlockPosition().subtract(0, 1, 0));
return blockId != 0;
}
}

View File

@ -28,6 +28,13 @@ public class Position {
return this;
}
public Position subtract(float x, float y, float z) {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
public float getDistance(Position position) {
return (float) Math.sqrt(MathUtils.square(position.getX() - getX()) + MathUtils.square(position.getY() - getY()) + MathUtils.square(position.getZ() - getZ()));
}
@ -133,7 +140,7 @@ public class Position {
}
public BlockPosition toBlockPosition() {
return new BlockPosition((int) getX(), (int) getY(), (int) getZ());
return new BlockPosition((int) Math.ceil(getX()), (int) Math.ceil(getY()), (int) Math.ceil(getZ()));
}
@Override