Projectile Movement (#1929)

Fixes

    #1780
    #1778
    The laggy fireballs
    Llama spit
    Shulker bullets
This commit is contained in:
David Choo 2021-02-19 11:12:36 -05:00 committed by GitHub
parent c4573bb73d
commit b7828267a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 332 additions and 78 deletions

View File

@ -35,6 +35,8 @@ public class AbstractArrowEntity extends Entity {
public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
setMotion(motion);
} }
@Override @Override
@ -47,4 +49,20 @@ public class AbstractArrowEntity extends Entity {
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public void setRotation(Vector3f rotation) {
// Ignore the rotation sent by the Java server since the
// Java client calculates the rotation from the motion
}
@Override
public void setMotion(Vector3f motion) {
super.setMotion(motion);
double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ());
float yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()));
float pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed));
rotation = Vector3f.from(yaw, pitch, yaw);
}
} }

View File

@ -26,43 +26,167 @@
package org.geysermc.connector.entity; package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.GeyserConnector; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.collision.CollisionTranslator;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class FishingHookEntity extends Entity { import java.util.List;
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) { import java.util.concurrent.ThreadLocalRandom;
public class FishingHookEntity extends ThrowableEntity {
private boolean hooked = false;
private final BoundingBox boundingBox;
private boolean inWater = false;
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, PlayerEntity owner) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25);
Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId());
if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) {
entity = session.getPlayerEntity();
}
if (entity != null) { // In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change.
this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); // This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock,
return; // so that it can be handled by moveAbsoluteImmediate.
} this.metadata.putFloat(EntityData.BOUNDING_BOX_HEIGHT, 128);
}
this.metadata.put(EntityData.OWNER_EID, owner.getGeyserId());
} }
@Override @Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 7) { if (entityMetadata.getId() == 7) { // Hooked entity
Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1); int hookedEntityId = (int) entityMetadata.getValue() - 1;
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) { Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId);
if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) {
entity = session.getPlayerEntity(); entity = session.getPlayerEntity();
} }
if (entity != null) { if (entity != null) {
metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); metadata.put(EntityData.TARGET_EID, entity.getGeyserId());
hooked = true;
} else {
hooked = false;
} }
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
boundingBox.setMiddleX(position.getX());
boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2);
boundingBox.setMiddleZ(position.getZ());
CollisionManager collisionManager = session.getCollisionManager();
List<Vector3i> collidableBlocks = collisionManager.getCollidableBlocks(boundingBox);
boolean touchingWater = false;
boolean collided = false;
for (Vector3i blockPos : collidableBlocks) {
if (0 <= blockPos.getY() && blockPos.getY() <= 255) {
int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos);
BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ());
if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) {
// TODO Push bounding box out of collision to improve movement
collided = true;
}
int waterLevel = BlockStateValues.getWaterLevel(blockID);
if (BlockTranslator.isWaterlogged(blockID)) {
waterLevel = 0;
}
if (waterLevel >= 0) {
double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0;
// Falling water is a full block
if (waterLevel >= 8) {
waterMaxY = blockPos.getY() + 1;
}
if (position.getY() <= waterMaxY) {
touchingWater = true;
}
}
}
}
if (!inWater && touchingWater) {
sendSplashSound(session);
}
inWater = touchingWater;
if (!collided) {
super.moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
} else {
super.moveAbsoluteImmediate(session, this.position, rotation, true, true);
}
}
private void sendSplashSound(GeyserSession session) {
if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY()));
if (volume > 1) {
volume = 1;
}
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
playSoundPacket.setSound("random.splash");
playSoundPacket.setPosition(position);
playSoundPacket.setVolume(volume);
playSoundPacket.setPitch(1f + ThreadLocalRandom.current().nextFloat() * 0.3f);
session.sendUpstreamPacket(playSoundPacket);
}
}
@Override
public void tick(GeyserSession session) {
if (hooked || !isInAir(session) && !isInWater(session) || isOnGround()) {
motion = Vector3f.ZERO;
return;
}
float gravity = getGravity(session);
motion = motion.down(gravity);
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
float drag = getDrag(session);
motion = motion.mul(drag);
}
@Override
protected float getGravity(GeyserSession session) {
if (!isInWater(session) && !onGround) {
return 0.03f;
}
return 0;
}
/**
* @param session the session of the Bedrock client.
* @return true if this entity is currently in air.
*/
protected boolean isInAir(GeyserSession session) {
if (session.getConnector().getConfig().isCacheChunks()) {
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return block == BlockTranslator.JAVA_AIR_ID;
}
}
return false;
}
@Override
protected float getDrag(GeyserSession session) {
return 0.92f;
}
} }

View File

@ -32,19 +32,44 @@ import org.geysermc.connector.network.session.GeyserSession;
public class ItemedFireballEntity extends ThrowableEntity { public class ItemedFireballEntity extends ThrowableEntity {
private final Vector3f acceleration; private final Vector3f acceleration;
/**
* The number of ticks to advance movement before sending to Bedrock
*/
protected int futureTicks = 3;
public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation); super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation);
acceleration = motion;
float magnitude = motion.length();
if (magnitude != 0) {
acceleration = motion.div(magnitude).mul(0.1f);
} else {
acceleration = Vector3f.ZERO;
}
}
private Vector3f tickMovement(GeyserSession session, Vector3f position) {
position = position.add(motion);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);
return position;
}
@Override
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Advance the position by a few ticks before sending it to Bedrock
Vector3f lastMotion = motion;
Vector3f newPosition = position;
for (int i = 0; i < futureTicks; i++) {
newPosition = tickMovement(session, newPosition);
}
super.moveAbsoluteImmediate(session, newPosition, rotation, isOnGround, teleported);
this.position = position;
this.motion = lastMotion;
} }
@Override @Override
public void tick(GeyserSession session) { public void tick(GeyserSession session) {
position = position.add(motion); moveAbsoluteImmediate(session, tickMovement(session, position), rotation, false, false);
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
// TODO: movement is incredibly stiff.
// TODO: Only use this laggy movement for fireballs that be reflected
moveAbsoluteImmediate(session, position, rotation, false, true);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);
} }
} }

View File

@ -29,20 +29,21 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues;
/** /**
* Used as a class for any object-like entity that moves as a projectile * Used as a class for any object-like entity that moves as a projectile
*/ */
public class ThrowableEntity extends Entity implements Tickable { public class ThrowableEntity extends Entity implements Tickable {
private Vector3f lastPosition; protected Vector3f lastJavaPosition;
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
this.lastPosition = position; this.lastJavaPosition = position;
} }
/** /**
@ -52,22 +53,65 @@ public class ThrowableEntity extends Entity implements Tickable {
*/ */
@Override @Override
public void tick(GeyserSession session) { public void tick(GeyserSession session) {
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
float drag = getDrag(session); float drag = getDrag(session);
float gravity = getGravity(); float gravity = getGravity(session);
motion = motion.mul(drag).down(gravity); motion = motion.mul(drag).down(gravity);
} }
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported); MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
moveEntityDeltaPacket.setRuntimeEntityId(geyserId);
if (isOnGround) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
}
setOnGround(isOnGround);
if (teleported) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING);
}
if (this.position.getX() != position.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
moveEntityDeltaPacket.setX(position.getX());
}
if (this.position.getY() != position.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
moveEntityDeltaPacket.setY(position.getY());
}
if (this.position.getZ() != position.getZ()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
moveEntityDeltaPacket.setZ(position.getZ());
}
setPosition(position);
if (this.rotation.getX() != rotation.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityDeltaPacket.setYaw(rotation.getX());
}
if (this.rotation.getY() != rotation.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
moveEntityDeltaPacket.setPitch(rotation.getY());
}
if (this.rotation.getZ() != rotation.getZ()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
moveEntityDeltaPacket.setHeadYaw(rotation.getZ());
}
setRotation(rotation);
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
session.sendUpstreamPacket(moveEntityDeltaPacket);
}
} }
/** /**
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion. * Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
* *
* @param session the session of the Bedrock client.
* @return the amount of gravity to apply to this entity while in motion. * @return the amount of gravity to apply to this entity while in motion.
*/ */
protected float getGravity() { protected float getGravity(GeyserSession session) {
if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) {
switch (entityType) { switch (entityType) {
case THROWN_POTION: case THROWN_POTION:
@ -76,11 +120,14 @@ public class ThrowableEntity extends Entity implements Tickable {
case THROWN_EXP_BOTTLE: case THROWN_EXP_BOTTLE:
return 0.07f; return 0.07f;
case FIREBALL: case FIREBALL:
case SHULKER_BULLET:
return 0; return 0;
case SNOWBALL: case SNOWBALL:
case THROWN_EGG: case THROWN_EGG:
case THROWN_ENDERPEARL: case THROWN_ENDERPEARL:
return 0.03f; return 0.03f;
case LLAMA_SPIT:
return 0.06f;
} }
} }
return 0; return 0;
@ -101,11 +148,14 @@ public class ThrowableEntity extends Entity implements Tickable {
case SNOWBALL: case SNOWBALL:
case THROWN_EGG: case THROWN_EGG:
case THROWN_ENDERPEARL: case THROWN_ENDERPEARL:
case LLAMA_SPIT:
return 0.99f; return 0.99f;
case FIREBALL: case FIREBALL:
case SMALL_FIREBALL: case SMALL_FIREBALL:
case DRAGON_FIREBALL: case DRAGON_FIREBALL:
return 0.95f; return 0.95f;
case SHULKER_BULLET:
return 1;
} }
} }
return 1; return 1;
@ -117,8 +167,10 @@ public class ThrowableEntity extends Entity implements Tickable {
*/ */
protected boolean isInWater(GeyserSession session) { protected boolean isInWater(GeyserSession session) {
if (session.getConnector().getConfig().isCacheChunks()) { if (session.getConnector().getConfig().isCacheChunks()) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
return block == BlockTranslator.BEDROCK_WATER_ID; int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return BlockStateValues.getWaterLevel(block) != -1;
}
} }
return false; return false;
} }
@ -136,14 +188,13 @@ public class ThrowableEntity extends Entity implements Tickable {
@Override @Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
position = lastPosition; moveAbsoluteImmediate(session, lastJavaPosition.add(relX, relY, relZ), rotation, isOnGround, false);
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); lastJavaPosition = position;
lastPosition = position;
} }
@Override @Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported); moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
lastPosition = position; lastJavaPosition = position;
} }
} }

View File

@ -35,6 +35,8 @@ public class WitherSkullEntity extends ItemedFireballEntity {
public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
this.futureTicks = 1;
} }
@Override @Override

View File

@ -118,7 +118,7 @@ public enum EntityType {
TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"),
TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f), TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f),
CAT(CatEntity.class, 75, 0.35f, 0.3f), CAT(CatEntity.class, 75, 0.35f, 0.3f),
SHULKER_BULLET(Entity.class, 76, 0.3125f), SHULKER_BULLET(ThrowableEntity.class, 76, 0.3125f),
FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"),
CHALKBOARD(Entity.class, 78, 0f), CHALKBOARD(Entity.class, 78, 0f),
DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f), DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f),
@ -145,7 +145,7 @@ public enum EntityType {
MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"),
MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"),
LINGERING_POTION(ThrowableEntity.class, 101, 0f), LINGERING_POTION(ThrowableEntity.class, 101, 0f),
LLAMA_SPIT(Entity.class, 102, 0.25f), LLAMA_SPIT(ThrowableEntity.class, 102, 0.25f),
EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"),
EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"),
VEX(VexEntity.class, 105, 0.8f, 0.4f), VEX(VexEntity.class, 105, 0.8f, 0.4f),

View File

@ -177,24 +177,24 @@ public class CollisionManager {
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
} }
public List<Vector3i> getPlayerCollidableBlocks() { public List<Vector3i> getCollidableBlocks(BoundingBox box) {
List<Vector3i> blocks = new ArrayList<>(); List<Vector3i> blocks = new ArrayList<>();
Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(), Vector3d position = Vector3d.from(box.getMiddleX(),
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), box.getMiddleY() - (box.getSizeY() / 2),
playerBoundingBox.getMiddleZ()); box.getMiddleZ());
// Loop through all blocks that could collide with the player // Loop through all blocks that could collide
int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE)); int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE));
int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE); int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE);
// Y extends 0.5 blocks down because of fence hitboxes // Y extends 0.5 blocks down because of fence hitboxes
int minCollisionY = (int) Math.floor(position.getY() - 0.5); int minCollisionY = (int) Math.floor(position.getY() - 0.5);
int maxCollisionY = (int) Math.floor(position.getY() + playerBoundingBox.getSizeY()); int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY());
int minCollisionZ = (int) Math.floor(position.getZ() - ((playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE)); int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE));
int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE); int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE);
for (int y = minCollisionY; y < maxCollisionY + 1; y++) { for (int y = minCollisionY; y < maxCollisionY + 1; y++) {
for (int x = minCollisionX; x < maxCollisionX + 1; x++) { for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
@ -207,6 +207,10 @@ public class CollisionManager {
return blocks; return blocks;
} }
public List<Vector3i> getPlayerCollidableBlocks() {
return getCollidableBlocks(playerBoundingBox);
}
/** /**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be * Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
* cancelled * cancelled

View File

@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.*; import org.geysermc.connector.entity.*;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
@ -69,8 +70,18 @@ public class JavaSpawnEntityTranslator extends PacketTranslator<ServerSpawnEntit
type, position, motion, rotation, (HangingDirection) packet.getData()); type, position, motion, rotation, (HangingDirection) packet.getData());
} else if (packet.getType() == EntityType.FISHING_BOBBER) { } else if (packet.getType() == EntityType.FISHING_BOBBER) {
// Fishing bobbers need the owner for the line // Fishing bobbers need the owner for the line
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), int ownerEntityId = ((ProjectileData) packet.getData()).getOwnerId();
type, position, motion, rotation, (ProjectileData) packet.getData()); Entity owner = session.getEntityCache().getEntityByJavaId(ownerEntityId);
if (owner == null && session.getPlayerEntity().getEntityId() == ownerEntityId) {
owner = session.getPlayerEntity();
}
// Java clients only spawn fishing hooks with a player as its owner
if (owner instanceof PlayerEntity) {
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
type, position, motion, rotation, (PlayerEntity) owner);
} else {
return;
}
} else if (packet.getType() == EntityType.BOAT) { } else if (packet.getType() == EntityType.BOAT) {
// Initial rotation is incorrect // Initial rotation is incorrect
entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),

View File

@ -49,70 +49,72 @@ public class BlockStateValues {
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap(); private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap(); private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap(); private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap();
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
/** /**
* Determines if the block state contains Bedrock block information * Determines if the block state contains Bedrock block information
* *
* @param entry The String to JsonNode map used in BlockTranslator * @param javaId The Java Identifier of the block
* @param javaBlockState the Java Block State of the block * @param javaBlockState the Java Block State of the block
* @param blockData JsonNode of info about the block from blocks.json
*/ */
public static void storeBlockStateValues(Map.Entry<String, JsonNode> entry, int javaBlockState) { public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
JsonNode bannerColor = entry.getValue().get("banner_color"); JsonNode bannerColor = blockData.get("banner_color");
if (bannerColor != null) { if (bannerColor != null) {
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
return; // There will never be a banner color and a skull variant return; // There will never be a banner color and a skull variant
} }
JsonNode bedColor = entry.getValue().get("bed_color"); JsonNode bedColor = blockData.get("bed_color");
if (bedColor != null) { if (bedColor != null) {
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
return; return;
} }
if (entry.getKey().contains("command_block")) { if (javaId.contains("command_block")) {
COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0); COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
return; return;
} }
if (entry.getValue().get("double_chest_position") != null) { if (blockData.get("double_chest_position") != null) {
boolean isX = (entry.getValue().get("x") != null); boolean isX = (blockData.get("x") != null);
boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) || boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) ||
(entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean())); (blockData.get("z") != null && blockData.get("z").asBoolean()));
boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left")); boolean isLeft = (blockData.get("double_chest_position").asText().contains("left"));
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft)); DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
return; return;
} }
if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) { if (javaId.contains("potted_") || javaId.contains("flower_pot")) {
FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", "")); FLOWER_POT_VALUES.put(javaBlockState, javaId.replace("potted_", ""));
return; return;
} }
JsonNode notePitch = entry.getValue().get("note_pitch"); JsonNode notePitch = blockData.get("note_pitch");
if (notePitch != null) { if (notePitch != null) {
NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue()); NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue());
return; return;
} }
if (entry.getKey().contains("piston")) { if (javaId.contains("piston")) {
// True if extended, false if not // True if extended, false if not
PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true")); PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true"));
IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky")); IS_STICKY_PISTON.put(javaBlockState, javaId.contains("sticky"));
return; return;
} }
JsonNode skullVariation = entry.getValue().get("variation"); JsonNode skullVariation = blockData.get("variation");
if (skullVariation != null) { if (skullVariation != null) {
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
} }
JsonNode skullRotation = entry.getValue().get("skull_rotation"); JsonNode skullRotation = blockData.get("skull_rotation");
if (skullRotation != null) { if (skullRotation != null) {
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
} }
if (entry.getKey().contains("wall_skull") || entry.getKey().contains("wall_head")) { if (javaId.contains("wall_skull") || javaId.contains("wall_head")) {
String direction = entry.getKey().substring(entry.getKey().lastIndexOf("facing=") + 7); String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7);
int rotation = 0; int rotation = 0;
switch (direction.substring(0, direction.length() - 1)) { switch (direction.substring(0, direction.length() - 1)) {
case "north": case "north":
@ -131,10 +133,16 @@ public class BlockStateValues {
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation); SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
} }
JsonNode shulkerDirection = entry.getValue().get("shulker_direction"); JsonNode shulkerDirection = blockData.get("shulker_direction");
if (shulkerDirection != null) { if (shulkerDirection != null) {
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
} }
if (javaId.startsWith("minecraft:water")) {
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
int level = Integer.parseInt(strLevel);
WATER_LEVEL.put(javaBlockState, level);
}
} }
/** /**
@ -263,4 +271,15 @@ public class BlockStateValues {
public static byte getShulkerBoxDirection(int state) { public static byte getShulkerBoxDirection(int state) {
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
} }
/**
* Get the level of water from the block state.
* This is used in FishingHookEntity to create splash sounds when the hook hits the water.
*
* @param state BlockState of the block
* @return The water level or -1 if the block isn't water
*/
public static int getWaterLevel(int state) {
return WATER_LEVEL.getOrDefault(state, -1);
}
} }

View File

@ -185,7 +185,7 @@ public class BlockTranslator {
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId); BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; String cleanJavaIdentifier = entry.getKey().split("\\[")[0];

@ -1 +1 @@
Subproject commit 2e52b01cc541c8925346f93be8940087d9af1661 Subproject commit bf0610450ce94507a18286e94af2965550ff9eaa