Skip to content

Commit

Permalink
Emulate client side vehicle movement (#4648)
Browse files Browse the repository at this point in the history
* WIP client side vehicles

* Address reviews and remove use of Optional

* Only tick active vehicle

* Track world ticks

* Fixes for Camel dash and pose transition

* Remove vehicle parameter

* Start using blocks refactor

* Update BlockRegistryPopulator

* Update blocks

* Support step height attribute

* Use climbable block tag and TrapDoorBlock

* Lock camel rotation if stationary

* Fix boost ticking

* Keep cache of surrounding blocks

* Fix bug causing BoundingBox position to change in CollisionManager

* Clamp user input

* Support weaving status effect

* Support gravity attribute

* Piston support

* Tick boost for Pig and Strider if any player is controlling

* Submodule

* Address some reviews

* Support world border

* Optimize world border check

* Small optimizations

* Add comments
  • Loading branch information
AJ-Ferguson authored Aug 15, 2024
1 parent 4f7e9fc commit 34bab14
Show file tree
Hide file tree
Showing 34 changed files with 1,903 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ public final class EntityDefinitions {
.type(EntityType.PIG)
.heightAndWidth(0.9f)
.addTranslator(MetadataType.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(null) // Boost time
.addTranslator(MetadataType.INT, PigEntity::setBoost)
.build();
POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase)
.type(EntityType.POLAR_BEAR)
Expand All @@ -914,7 +914,7 @@ public final class EntityDefinitions {
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
.type(EntityType.STRIDER)
.height(1.7f).width(0.9f)
.addTranslator(null) // Boost time
.addTranslator(MetadataType.INT, StriderEntity::setBoost)
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setCold)
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setSaddled)
.build();
Expand Down Expand Up @@ -955,7 +955,7 @@ public final class EntityDefinitions {
.type(EntityType.CAMEL)
.height(2.375f).width(1.7f)
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
.addTranslator(null) // Last pose change tick
.addTranslator(MetadataType.LONG, CamelEntity::setLastPoseTick)
.build();
HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase)
.type(EntityType.HORSE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.registry.type.ItemMapping;
Expand Down Expand Up @@ -294,6 +295,36 @@ public InteractionResult interact(Hand hand) {
return super.interact(hand);
}

@Override
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
if (this instanceof ClientVehicle clientVehicle) {
if (clientVehicle.isClientControlled()) {
return;
}
clientVehicle.getVehicleComponent().moveRelative(relX, relY, relZ);
}

super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
}

@Override
public boolean setBoundingBoxHeight(float height) {
if (valid && this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setHeight(height);
}

return super.setBoundingBoxHeight(height);
}

@Override
public void setBoundingBoxWidth(float width) {
if (valid && this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setWidth(width);
}

super.setBoundingBoxWidth(width);
}

/**
* Checks to see if a nametag interaction would go through.
*/
Expand Down Expand Up @@ -407,9 +438,25 @@ protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newA
this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f);
newAttributes.add(createHealthAttribute());
}
case GENERIC_MOVEMENT_SPEED -> {
AttributeData attributeData = calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED);
newAttributes.add(attributeData);
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setMoveSpeed(attributeData.getValue());
}
}
case GENERIC_STEP_HEIGHT -> {
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setStepHeight((float) AttributeUtils.calculateValue(javaAttribute));
}
}
case GENERIC_GRAVITY -> {
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setGravity(AttributeUtils.calculateValue(javaAttribute));
}
}
case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));
case GENERIC_FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED));
case GENERIC_MOVEMENT_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED));
case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
case GENERIC_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,30 @@

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;

import java.util.UUID;

public class PigEntity extends AnimalEntity {
public class PigEntity extends AnimalEntity implements Tickable, ClientVehicle {
private final BoostableVehicleComponent<PigEntity> vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f);

public PigEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
Expand Down Expand Up @@ -84,4 +94,55 @@ protected InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemS
}
}
}

public void setBoost(IntEntityMetadata entityMetadata) {
vehicleComponent.startBoost(entityMetadata.getPrimitiveValue());
}

@Override
public void tick() {
PlayerEntity player = getPlayerPassenger();
if (player == null) {
return;
}

if (player == session.getPlayerEntity()) {
if (session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK)) {
vehicleComponent.tickBoost();
}
} else { // getHand() for session player seems to always return air
ItemDefinition itemDefinition = session.getItemMappings().getStoredItems().carrotOnAStick().getBedrockDefinition();
if (player.getHand().getDefinition() == itemDefinition || player.getOffhand().getDefinition() == itemDefinition) {
vehicleComponent.tickBoost();
}
}
}

@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}

@Override
public Vector2f getAdjustedInput(Vector2f input) {
return Vector2f.UNIT_Y;
}

@Override
public float getVehicleSpeed() {
return vehicleComponent.getMoveSpeed() * 0.225f * vehicleComponent.getBoostMultiplier();
}

private @Nullable PlayerEntity getPlayerPassenger() {
if (getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) instanceof PlayerEntity playerEntity) {
return playerEntity;
}

return null;
}

@Override
public boolean isClientControlled() {
return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,33 @@

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;

import java.util.UUID;

public class StriderEntity extends AnimalEntity {
public class StriderEntity extends AnimalEntity implements Tickable, ClientVehicle {

private final BoostableVehicleComponent<StriderEntity> vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f);
private boolean isCold = false;

public StriderEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
Expand Down Expand Up @@ -131,4 +141,60 @@ protected InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemS
}
}
}

public void setBoost(IntEntityMetadata entityMetadata) {
vehicleComponent.startBoost(entityMetadata.getPrimitiveValue());
}

@Override
public void tick() {
PlayerEntity player = getPlayerPassenger();
if (player == null) {
return;
}

if (player == session.getPlayerEntity()) {
if (session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK)) {
vehicleComponent.tickBoost();
}
} else { // getHand() for session player seems to always return air
ItemDefinition itemDefinition = session.getItemMappings().getStoredItems().warpedFungusOnAStick().getBedrockDefinition();
if (player.getHand().getDefinition() == itemDefinition || player.getOffhand().getDefinition() == itemDefinition) {
vehicleComponent.tickBoost();
}
}
}

@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}

@Override
public Vector2f getAdjustedInput(Vector2f input) {
return Vector2f.UNIT_Y;
}

@Override
public float getVehicleSpeed() {
return vehicleComponent.getMoveSpeed() * (isCold ? 0.35f : 0.55f) * vehicleComponent.getBoostMultiplier();
}

private @Nullable PlayerEntity getPlayerPassenger() {
if (getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) instanceof PlayerEntity playerEntity) {
return playerEntity;
}

return null;
}

@Override
public boolean isClientControlled() {
return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK);
}

@Override
public boolean canWalkOnLava() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,36 @@

package org.geysermc.geyser.entity.type.living.animal.horse;

import org.cloudburstmc.math.vector.Vector2f;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.vehicle.CamelVehicleComponent;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.LongEntityMetadata;

import java.util.UUID;

public class CamelEntity extends AbstractHorseEntity {

public class CamelEntity extends AbstractHorseEntity implements ClientVehicle {
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;

private final CamelVehicleComponent vehicleComponent = new CamelVehicleComponent(this);

public CamelEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);

Expand Down Expand Up @@ -111,5 +121,58 @@ protected void setDimensions(Pose pose) {
}

public void setDashing(BooleanEntityMetadata entityMetadata) {
// Java sends true to show dash animation and start the dash cooldown,
// false ends the dash animation, not the cooldown.
// Bedrock shows dash animation if HAS_DASH_COOLDOWN is set and the camel is above ground
if (entityMetadata.getPrimitiveValue()) {
setFlag(EntityFlag.HAS_DASH_COOLDOWN, true);
vehicleComponent.startDashCooldown();
} else if (!isClientControlled()) { // Don't remove dash cooldown prematurely if client is controlling
setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
}
}

public void setLastPoseTick(LongEntityMetadata entityMetadata) {
// Tick is based on world time. If negative, the camel is sitting.
// Must be compared to world time to know if the camel is fully standing/sitting or transitioning.
vehicleComponent.setLastPoseTick(entityMetadata.getPrimitiveValue());
}

@Override
protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) {
AttributeData attributeData = super.calculateAttribute(javaAttribute, type);
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_JUMP_STRENGTH) {
vehicleComponent.setHorseJumpStrength(attributeData.getValue());
}
return attributeData;
}

@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}

@Override
public Vector2f getAdjustedInput(Vector2f input) {
return input.mul(0.5f, input.getY() < 0 ? 0.25f : 1.0f);
}

@Override
public boolean isClientControlled() {
return getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) == session.getPlayerEntity();
}

@Override
public float getVehicleSpeed() {
float moveSpeed = vehicleComponent.getMoveSpeed();
if (!getFlag(EntityFlag.HAS_DASH_COOLDOWN) && session.getPlayerEntity().getFlag(EntityFlag.SPRINTING)) {
return moveSpeed + 0.1f;
}
return moveSpeed;
}

@Override
public boolean canClimb() {
return false;
}
}
Loading

0 comments on commit 34bab14

Please sign in to comment.