mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 14:34:59 +01:00
Emulate client side vehicle movement (#4648)
* 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
This commit is contained in:
parent
4f7e9fca9c
commit
34bab14860
34 changed files with 1903 additions and 67 deletions
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
|
|||
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;
|
||||
|
@ -294,6 +295,36 @@ public class LivingEntity extends Entity {
|
|||
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.
|
||||
*/
|
||||
|
@ -407,9 +438,25 @@ public class LivingEntity extends Entity {
|
|||
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));
|
||||
|
|
|
@ -27,20 +27,30 @@ package org.geysermc.geyser.entity.type.living.animal;
|
|||
|
||||
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);
|
||||
|
@ -84,4 +94,55 @@ public class PigEntity extends AnimalEntity {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,23 +27,33 @@ package org.geysermc.geyser.entity.type.living.animal;
|
|||
|
||||
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) {
|
||||
|
@ -131,4 +141,60 @@ public class StriderEntity extends AnimalEntity {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -111,5 +121,58 @@ public class CamelEntity extends AbstractHorseEntity {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
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.AttributeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
|
@ -42,6 +43,7 @@ import org.geysermc.geyser.level.BedrockDimension;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.AttributeUtils;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
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.GlobalPos;
|
||||
|
@ -74,6 +76,16 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
*/
|
||||
@Getter
|
||||
private boolean isRidingInFront;
|
||||
/**
|
||||
* Used when emulating client-side vehicles
|
||||
*/
|
||||
@Getter
|
||||
private Vector2f vehicleInput = Vector2f.ZERO;
|
||||
/**
|
||||
* Used when emulating client-side vehicles
|
||||
*/
|
||||
@Getter
|
||||
private int vehicleJumpStrength;
|
||||
|
||||
private int lastAirSupply = getMaxAir();
|
||||
|
||||
|
@ -315,6 +327,17 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
this.setAirSupply(getMaxAir());
|
||||
}
|
||||
|
||||
public void setVehicleInput(Vector2f vehicleInput) {
|
||||
this.vehicleInput = Vector2f.from(
|
||||
MathUtils.clamp(vehicleInput.getX(), -1.0f, 1.0f),
|
||||
MathUtils.clamp(vehicleInput.getY(), -1.0f, 1.0f)
|
||||
);
|
||||
}
|
||||
|
||||
public void setVehicleJumpStrength(int vehicleJumpStrength) {
|
||||
this.vehicleJumpStrength = MathUtils.constrain(vehicleJumpStrength, 0, 100);
|
||||
}
|
||||
|
||||
private boolean isBelowVoidFloor() {
|
||||
return position.getY() < voidFloorPosition();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.vehicle;
|
||||
|
||||
import org.cloudburstmc.math.TrigMath;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
|
||||
public class BoostableVehicleComponent<T extends LivingEntity & ClientVehicle> extends VehicleComponent<T> {
|
||||
private int boostLength;
|
||||
private int boostTicks = 1;
|
||||
|
||||
public BoostableVehicleComponent(T vehicle, float stepHeight) {
|
||||
super(vehicle, stepHeight);
|
||||
}
|
||||
|
||||
public void startBoost(int boostLength) {
|
||||
this.boostLength = boostLength;
|
||||
this.boostTicks = 1;
|
||||
}
|
||||
|
||||
public float getBoostMultiplier() {
|
||||
if (isBoosting()) {
|
||||
return 1.0f + 1.15f * TrigMath.sin((float) boostTicks / (float) boostLength * TrigMath.PI);
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public boolean isBoosting() {
|
||||
return boostTicks <= boostLength;
|
||||
}
|
||||
|
||||
public void tickBoost() {
|
||||
if (isBoosting()) {
|
||||
boostTicks++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.vehicle;
|
||||
|
||||
import lombok.Setter;
|
||||
import org.cloudburstmc.math.vector.Vector2f;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||
|
||||
public class CamelVehicleComponent extends VehicleComponent<CamelEntity> {
|
||||
private static final int STANDING_TICKS = 52;
|
||||
private static final int DASH_TICKS = 55;
|
||||
|
||||
@Setter
|
||||
private float horseJumpStrength = 0.42f; // Not sent by vanilla Java server when spawned
|
||||
|
||||
@Setter
|
||||
private long lastPoseTick;
|
||||
|
||||
private int dashTick;
|
||||
private int effectJumpBoost;
|
||||
|
||||
public CamelVehicleComponent(CamelEntity vehicle) {
|
||||
super(vehicle, 1.5f);
|
||||
}
|
||||
|
||||
public void startDashCooldown() {
|
||||
// tickVehicle is only called while the vehicle is mounted. Use session ticks to keep
|
||||
// track of time instead of counting down
|
||||
this.dashTick = vehicle.getSession().getTicks() + DASH_TICKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickVehicle() {
|
||||
if (this.dashTick != 0) {
|
||||
if (vehicle.getSession().getTicks() > this.dashTick) {
|
||||
vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
|
||||
this.dashTick = 0;
|
||||
} else {
|
||||
vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, true);
|
||||
}
|
||||
}
|
||||
|
||||
vehicle.setFlag(EntityFlag.CAN_DASH, vehicle.getFlag(EntityFlag.SADDLED) && !isStationary());
|
||||
vehicle.updateBedrockMetadata();
|
||||
super.tickVehicle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismount() {
|
||||
// Prevent camel from getting stuck in dash animation
|
||||
vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
|
||||
vehicle.updateBedrockMetadata();
|
||||
super.onDismount();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean travel(VehicleContext ctx, float speed) {
|
||||
if (vehicle.isOnGround() && isStationary()) {
|
||||
vehicle.setMotion(vehicle.getMotion().mul(0, 1, 0));
|
||||
}
|
||||
|
||||
return super.travel(ctx, speed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vector3f getInputVelocity(VehicleContext ctx, float speed) {
|
||||
if (isStationary()) {
|
||||
return Vector3f.ZERO;
|
||||
}
|
||||
|
||||
SessionPlayerEntity player = vehicle.getSession().getPlayerEntity();
|
||||
Vector3f inputVelocity = super.getInputVelocity(ctx, speed);
|
||||
float jumpStrength = player.getVehicleJumpStrength();
|
||||
|
||||
if (jumpStrength > 0) {
|
||||
player.setVehicleJumpStrength(0);
|
||||
|
||||
if (jumpStrength >= 90) {
|
||||
jumpStrength = 1.0f;
|
||||
} else {
|
||||
jumpStrength = 0.4f + 0.4f * jumpStrength / 90.0f;
|
||||
}
|
||||
|
||||
return inputVelocity.add(Vector3f.createDirectionDeg(0, -player.getYaw())
|
||||
.mul(22.2222f * jumpStrength * this.moveSpeed * getVelocityMultiplier(ctx))
|
||||
.up(1.4285f * jumpStrength * (this.horseJumpStrength * getJumpVelocityMultiplier(ctx) + (this.effectJumpBoost * 0.1f))));
|
||||
}
|
||||
|
||||
return inputVelocity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vector2f getVehicleRotation() {
|
||||
if (isStationary()) {
|
||||
return Vector2f.from(vehicle.getYaw(), vehicle.getPitch());
|
||||
}
|
||||
return super.getVehicleRotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the camel is sitting
|
||||
* or transitioning to standing pose.
|
||||
*/
|
||||
private boolean isStationary() {
|
||||
// Java checks if sitting using lastPoseTick
|
||||
return this.lastPoseTick < 0 || vehicle.getSession().getWorldTicks() < this.lastPoseTick + STANDING_TICKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEffect(Effect effect, int effectAmplifier) {
|
||||
if (effect == Effect.JUMP_BOOST) {
|
||||
effectJumpBoost = effectAmplifier + 1;
|
||||
} else {
|
||||
super.setEffect(effect, effectAmplifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEffect(Effect effect) {
|
||||
if (effect == Effect.JUMP_BOOST) {
|
||||
effectJumpBoost = 0;
|
||||
} else {
|
||||
super.removeEffect(effect);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.vehicle;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector2f;
|
||||
|
||||
public interface ClientVehicle {
|
||||
VehicleComponent<?> getVehicleComponent();
|
||||
|
||||
Vector2f getAdjustedInput(Vector2f input);
|
||||
|
||||
float getVehicleSpeed();
|
||||
|
||||
boolean isClientControlled();
|
||||
|
||||
default boolean canWalkOnLava() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean canClimb() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,964 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.vehicle;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.TrigMath;
|
||||
import org.cloudburstmc.math.vector.Vector2f;
|
||||
import org.cloudburstmc.math.vector.Vector3d;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket;
|
||||
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.level.block.Fluid;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BedBlock;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.level.block.type.TrapDoorBlock;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.session.cache.tags.BlockTag;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.translator.collision.SolidCollision;
|
||||
import org.geysermc.geyser.util.BlockUtils;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket;
|
||||
|
||||
public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
|
||||
private static final ObjectDoublePair<Fluid> EMPTY_FLUID_PAIR = ObjectDoublePair.of(Fluid.EMPTY, 0.0);
|
||||
private static final float MAX_LOGICAL_FLUID_HEIGHT = 8.0f / BlockStateValues.NUM_FLUID_LEVELS;
|
||||
private static final float BASE_SLIPPERINESS_CUBED = 0.6f * 0.6f * 0.6f;
|
||||
private static final float MIN_VELOCITY = 0.003f;
|
||||
|
||||
protected final T vehicle;
|
||||
protected final BoundingBox boundingBox;
|
||||
|
||||
protected float stepHeight;
|
||||
protected float moveSpeed;
|
||||
protected double gravity;
|
||||
protected int effectLevitation;
|
||||
protected boolean effectSlowFalling;
|
||||
protected boolean effectWeaving;
|
||||
|
||||
public VehicleComponent(T vehicle, float stepHeight) {
|
||||
this.vehicle = vehicle;
|
||||
this.stepHeight = stepHeight;
|
||||
this.moveSpeed = (float) AttributeType.Builtin.GENERIC_MOVEMENT_SPEED.getDef();
|
||||
this.gravity = AttributeType.Builtin.GENERIC_GRAVITY.getDef();
|
||||
|
||||
double width = vehicle.getBoundingBoxWidth();
|
||||
double height = vehicle.getBoundingBoxHeight();
|
||||
this.boundingBox = new BoundingBox(
|
||||
vehicle.getPosition().getX(),
|
||||
vehicle.getPosition().getY() + height / 2,
|
||||
vehicle.getPosition().getZ(),
|
||||
width, height, width
|
||||
);
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
boundingBox.setSizeX(width);
|
||||
boundingBox.setSizeZ(width);
|
||||
}
|
||||
|
||||
public void setHeight(float height) {
|
||||
boundingBox.translate(0, (height - boundingBox.getSizeY()) / 2, 0);
|
||||
boundingBox.setSizeY(height);
|
||||
}
|
||||
|
||||
public void moveAbsolute(double x, double y, double z) {
|
||||
boundingBox.setMiddleX(x);
|
||||
boundingBox.setMiddleY(y + boundingBox.getSizeY() / 2);
|
||||
boundingBox.setMiddleZ(z);
|
||||
}
|
||||
|
||||
public void moveRelative(double x, double y, double z) {
|
||||
boundingBox.translate(x, y, z);
|
||||
}
|
||||
|
||||
public void moveRelative(Vector3d vec) {
|
||||
boundingBox.translate(vec);
|
||||
}
|
||||
|
||||
public BoundingBox getBoundingBox() {
|
||||
return this.boundingBox;
|
||||
}
|
||||
|
||||
public void setEffect(Effect effect, int effectAmplifier) {
|
||||
switch (effect) {
|
||||
case LEVITATION -> effectLevitation = effectAmplifier + 1;
|
||||
case SLOW_FALLING -> effectSlowFalling = true;
|
||||
case WEAVING -> effectWeaving = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeEffect(Effect effect) {
|
||||
switch (effect) {
|
||||
case LEVITATION -> effectLevitation = 0;
|
||||
case SLOW_FALLING -> effectSlowFalling = false;
|
||||
case WEAVING -> effectWeaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMoveSpeed(float moveSpeed) {
|
||||
this.moveSpeed = moveSpeed;
|
||||
}
|
||||
|
||||
public float getMoveSpeed() {
|
||||
return moveSpeed;
|
||||
}
|
||||
|
||||
public void setStepHeight(float stepHeight) {
|
||||
this.stepHeight = MathUtils.clamp(stepHeight, 1.0f, 10.0f);
|
||||
}
|
||||
|
||||
public void setGravity(double gravity) {
|
||||
this.gravity = MathUtils.constrain(gravity, -1.0, 1.0);
|
||||
}
|
||||
|
||||
public Vector3d correctMovement(Vector3d movement) {
|
||||
return vehicle.getSession().getCollisionManager().correctMovement(
|
||||
movement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava()
|
||||
);
|
||||
}
|
||||
|
||||
public void onMount() {
|
||||
vehicle.getSession().getPlayerEntity().setVehicleInput(Vector2f.ZERO);
|
||||
vehicle.getSession().getPlayerEntity().setVehicleJumpStrength(0);
|
||||
}
|
||||
|
||||
public void onDismount() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every session tick while the player is mounted on the vehicle.
|
||||
*/
|
||||
public void tickVehicle() {
|
||||
if (!vehicle.isClientControlled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VehicleContext ctx = new VehicleContext();
|
||||
ctx.loadSurroundingBlocks();
|
||||
|
||||
ObjectDoublePair<Fluid> fluidHeight = updateFluidMovement(ctx);
|
||||
switch (fluidHeight.left()) {
|
||||
case WATER -> waterMovement(ctx);
|
||||
case LAVA -> {
|
||||
if (vehicle.canWalkOnLava() && ctx.centerBlock().is(Blocks.LAVA)) {
|
||||
landMovement(ctx);
|
||||
} else {
|
||||
lavaMovement(ctx, fluidHeight.rightDouble());
|
||||
}
|
||||
}
|
||||
case EMPTY -> landMovement(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds velocity of all colliding fluids to the vehicle, and returns the height of the fluid to use for movement.
|
||||
*
|
||||
* @param ctx context
|
||||
* @return type and height of fluid to use for movement
|
||||
*/
|
||||
protected ObjectDoublePair<Fluid> updateFluidMovement(VehicleContext ctx) {
|
||||
BoundingBox box = boundingBox.clone();
|
||||
box.expand(-0.001);
|
||||
|
||||
Vector3d min = box.getMin();
|
||||
Vector3d max = box.getMax();
|
||||
|
||||
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getFloorX(), min.getFloorY(), min.getFloorZ(), max.getFloorX(), max.getFloorY(), max.getFloorZ());
|
||||
|
||||
double waterHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.WATER, 0.014, min.getY());
|
||||
double lavaHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.LAVA, vehicle.getSession().getDimensionType().ultrawarm() ? 0.007 : 0.007 / 3, min.getY());
|
||||
|
||||
// Apply upward motion if the vehicle is a Strider, and it is submerged in lava
|
||||
if (lavaHeight > 0 && vehicle.getDefinition().entityType() == EntityType.STRIDER) {
|
||||
Vector3i blockPos = ctx.centerPos().toInt();
|
||||
if (!CollisionManager.FLUID_COLLISION.isBelow(blockPos.getY(), boundingBox) || ctx.getBlock(blockPos.up()).is(Blocks.LAVA)) {
|
||||
vehicle.setMotion(vehicle.getMotion().mul(0.5f).add(0, 0.05f, 0));
|
||||
} else {
|
||||
vehicle.setOnGround(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Water movement has priority over lava movement
|
||||
if (waterHeight > 0) {
|
||||
return ObjectDoublePair.of(Fluid.WATER, waterHeight);
|
||||
}
|
||||
|
||||
if (lavaHeight > 0) {
|
||||
return ObjectDoublePair.of(Fluid.LAVA, lavaHeight);
|
||||
}
|
||||
|
||||
return EMPTY_FLUID_PAIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates how deep the vehicle is in a fluid, and applies its velocity.
|
||||
*
|
||||
* @param ctx context
|
||||
* @param iter iterator of colliding blocks
|
||||
* @param fluid type of fluid
|
||||
* @param speed multiplier for fluid motion
|
||||
* @param minY minY of the bounding box used to check for fluid collision; not exactly the same as the vehicle's bounding box
|
||||
* @return height of fluid compared to minY
|
||||
*/
|
||||
protected double getFluidHeightAndApplyMovement(VehicleContext ctx, BlockPositionIterator iter, Fluid fluid, double speed, double minY) {
|
||||
Vector3d totalVelocity = Vector3d.ZERO;
|
||||
double maxFluidHeight = 0;
|
||||
int fluidBlocks = 0;
|
||||
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
int blockId = ctx.getBlockId(iter);
|
||||
if (BlockStateValues.getFluid(blockId) != fluid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ());
|
||||
float worldFluidHeight = getWorldFluidHeight(fluid, blockId);
|
||||
|
||||
double vehicleFluidHeight = blockPos.getY() + worldFluidHeight - minY;
|
||||
if (vehicleFluidHeight < 0) {
|
||||
// Vehicle is not submerged in this fluid block
|
||||
continue;
|
||||
}
|
||||
|
||||
// flowBlocked is only used when determining if a falling fluid should drag the vehicle downwards.
|
||||
// If this block is not a falling fluid, set to true to avoid unnecessary checks.
|
||||
boolean flowBlocked = worldFluidHeight != 1;
|
||||
|
||||
Vector3d velocity = Vector3d.ZERO;
|
||||
for (Direction direction : Direction.HORIZONTAL) {
|
||||
Vector3i adjacentBlockPos = blockPos.add(direction.getUnitVector());
|
||||
int adjacentBlockId = ctx.getBlockId(adjacentBlockPos);
|
||||
Fluid adjacentFluid = BlockStateValues.getFluid(adjacentBlockId);
|
||||
|
||||
float fluidHeightDiff = 0;
|
||||
if (adjacentFluid == fluid) {
|
||||
fluidHeightDiff = getLogicalFluidHeight(fluid, blockId) - getLogicalFluidHeight(fluid, adjacentBlockId);
|
||||
} else if (adjacentFluid == Fluid.EMPTY) {
|
||||
// If the adjacent block is not a fluid and does not have collision,
|
||||
// check if there is a fluid under it
|
||||
BlockCollision adjacentBlockCollision = BlockUtils.getCollision(adjacentBlockId);
|
||||
if (adjacentBlockCollision == null) {
|
||||
float adjacentFluidHeight = getLogicalFluidHeight(fluid, ctx.getBlockId(adjacentBlockPos.add(Direction.DOWN.getUnitVector())));
|
||||
if (adjacentFluidHeight != -1) { // Only care about same type of fluid
|
||||
fluidHeightDiff = getLogicalFluidHeight(fluid, blockId) - (adjacentFluidHeight - MAX_LOGICAL_FLUID_HEIGHT);
|
||||
}
|
||||
} else if (!flowBlocked) {
|
||||
// No need to check if flow is already blocked from another direction, or if this isn't a falling fluid.
|
||||
flowBlocked = isFlowBlocked(fluid, adjacentBlockId);
|
||||
}
|
||||
}
|
||||
|
||||
if (fluidHeightDiff != 0) {
|
||||
velocity = velocity.add(direction.getUnitVector().toDouble().mul(fluidHeightDiff));
|
||||
}
|
||||
}
|
||||
|
||||
if (worldFluidHeight == 1) { // If falling fluid
|
||||
// If flow is not blocked, check if it is blocked for the fluid above
|
||||
if (!flowBlocked) {
|
||||
Vector3i blockPosUp = blockPos.up();
|
||||
for (Direction direction : Direction.HORIZONTAL) {
|
||||
flowBlocked = isFlowBlocked(fluid, ctx.getBlockId(blockPosUp.add(direction.getUnitVector())));
|
||||
if (flowBlocked) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flowBlocked) {
|
||||
velocity = javaNormalize(velocity).add(0.0, -6.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
velocity = javaNormalize(velocity);
|
||||
|
||||
maxFluidHeight = Math.max(vehicleFluidHeight, maxFluidHeight);
|
||||
if (maxFluidHeight < 0.4) {
|
||||
velocity = velocity.mul(maxFluidHeight);
|
||||
}
|
||||
|
||||
totalVelocity = totalVelocity.add(velocity);
|
||||
fluidBlocks++;
|
||||
}
|
||||
|
||||
if (!totalVelocity.equals(Vector3d.ZERO)) {
|
||||
Vector3f motion = vehicle.getMotion();
|
||||
|
||||
totalVelocity = javaNormalize(totalVelocity.mul(1.0 / fluidBlocks));
|
||||
totalVelocity = totalVelocity.mul(speed);
|
||||
|
||||
if (totalVelocity.length() < 0.0045 && Math.abs(motion.getX()) < MIN_VELOCITY && Math.abs(motion.getZ()) < MIN_VELOCITY) {
|
||||
totalVelocity = javaNormalize(totalVelocity).mul(0.0045);
|
||||
}
|
||||
|
||||
vehicle.setMotion(motion.add(totalVelocity.toFloat()));
|
||||
}
|
||||
|
||||
return maxFluidHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Java edition returns the zero vector if the length of the input vector is less than 0.0001
|
||||
*/
|
||||
protected Vector3d javaNormalize(Vector3d vec) {
|
||||
double len = vec.length();
|
||||
return len < 1.0E-4 ? Vector3d.ZERO : Vector3d.from(vec.getX() / len, vec.getY() / len, vec.getZ() / len);
|
||||
}
|
||||
|
||||
protected float getWorldFluidHeight(Fluid fluidType, int blockId) {
|
||||
return (float) switch (fluidType) {
|
||||
case WATER -> BlockStateValues.getWaterHeight(blockId);
|
||||
case LAVA -> BlockStateValues.getLavaHeight(blockId);
|
||||
case EMPTY -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
protected float getLogicalFluidHeight(Fluid fluidType, int blockId) {
|
||||
return Math.min(getWorldFluidHeight(fluidType, blockId), MAX_LOGICAL_FLUID_HEIGHT);
|
||||
}
|
||||
|
||||
protected boolean isFlowBlocked(Fluid fluid, int adjacentBlockId) {
|
||||
if (BlockState.of(adjacentBlockId).is(Blocks.ICE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BlockStateValues.getFluid(adjacentBlockId) == fluid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: supposed to check if the opposite face of the block touching the fluid is solid, instead of SolidCollision
|
||||
return BlockUtils.getCollision(adjacentBlockId) instanceof SolidCollision;
|
||||
}
|
||||
|
||||
protected void waterMovement(VehicleContext ctx) {
|
||||
double gravity = getGravity();
|
||||
float drag = vehicle.getFlag(EntityFlag.SPRINTING) ? 0.9f : 0.8f; // 0.8f: getBaseMovementSpeedMultiplier
|
||||
double originalY = ctx.centerPos().getY();
|
||||
boolean falling = vehicle.getMotion().getY() <= 0;
|
||||
|
||||
// NOT IMPLEMENTED: depth strider and dolphins grace
|
||||
|
||||
boolean horizontalCollision = travel(ctx, 0.02f);
|
||||
|
||||
if (horizontalCollision && isClimbing(ctx)) {
|
||||
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.2f, vehicle.getMotion().getZ()));
|
||||
}
|
||||
|
||||
vehicle.setMotion(vehicle.getMotion().mul(drag, 0.8f, drag));
|
||||
vehicle.setMotion(getFluidGravity(gravity, falling));
|
||||
|
||||
if (horizontalCollision && shouldApplyFluidJumpBoost(ctx, originalY)) {
|
||||
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ()));
|
||||
}
|
||||
}
|
||||
|
||||
protected void lavaMovement(VehicleContext ctx, double lavaHeight) {
|
||||
double gravity = getGravity();
|
||||
double originalY = ctx.centerPos().getY();
|
||||
boolean falling = vehicle.getMotion().getY() <= 0;
|
||||
|
||||
boolean horizontalCollision = travel(ctx, 0.02f);
|
||||
|
||||
if (lavaHeight <= (boundingBox.getSizeY() * 0.85 < 0.4 ? 0.0 : 0.4)) { // Swim height
|
||||
vehicle.setMotion(vehicle.getMotion().mul(0.5f, 0.8f, 0.5f));
|
||||
vehicle.setMotion(getFluidGravity(gravity, falling));
|
||||
} else {
|
||||
vehicle.setMotion(vehicle.getMotion().mul(0.5f));
|
||||
}
|
||||
|
||||
vehicle.setMotion(vehicle.getMotion().down((float) (gravity / 4.0)));
|
||||
|
||||
if (horizontalCollision && shouldApplyFluidJumpBoost(ctx, originalY)) {
|
||||
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ()));
|
||||
}
|
||||
}
|
||||
|
||||
protected void landMovement(VehicleContext ctx) {
|
||||
double gravity = getGravity();
|
||||
float slipperiness = BlockStateValues.getSlipperiness(getVelocityBlock(ctx));
|
||||
float drag = vehicle.isOnGround() ? 0.91f * slipperiness : 0.91f;
|
||||
float speed = vehicle.getVehicleSpeed() * (vehicle.isOnGround() ? BASE_SLIPPERINESS_CUBED / (slipperiness * slipperiness * slipperiness) : 0.1f);
|
||||
|
||||
boolean horizontalCollision = travel(ctx, speed);
|
||||
|
||||
if (isClimbing(ctx)) {
|
||||
Vector3f motion = vehicle.getMotion();
|
||||
vehicle.setMotion(
|
||||
Vector3f.from(
|
||||
MathUtils.clamp(motion.getX(), -0.15f, 0.15f),
|
||||
horizontalCollision ? 0.2f : Math.max(motion.getY(), -0.15f),
|
||||
MathUtils.clamp(motion.getZ(), -0.15f, 0.15f)
|
||||
)
|
||||
);
|
||||
// NOT IMPLEMENTED: climbing in powdered snow
|
||||
}
|
||||
|
||||
if (effectLevitation > 0) {
|
||||
vehicle.setMotion(vehicle.getMotion().up((0.05f * effectLevitation - vehicle.getMotion().getY()) * 0.2f));
|
||||
} else {
|
||||
vehicle.setMotion(vehicle.getMotion().down((float) gravity));
|
||||
// NOT IMPLEMENTED: slow fall when in unloaded chunk
|
||||
}
|
||||
|
||||
vehicle.setMotion(vehicle.getMotion().mul(drag, 0.98f, drag));
|
||||
}
|
||||
|
||||
protected boolean shouldApplyFluidJumpBoost(VehicleContext ctx, double originalY) {
|
||||
BoundingBox box = boundingBox.clone();
|
||||
box.translate(vehicle.getMotion().toDouble().up(0.6f - ctx.centerPos().getY() + originalY));
|
||||
box.expand(-1.0E-7);
|
||||
|
||||
BlockPositionIterator iter = vehicle.getSession().getCollisionManager().collidableBlocksIterator(box);
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
int blockId = ctx.getBlockId(iter);
|
||||
|
||||
// Also check for fluids
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(blockId);
|
||||
if (blockCollision == null && BlockStateValues.getFluid(blockId) != Fluid.EMPTY) {
|
||||
blockCollision = CollisionManager.SOLID_COLLISION;
|
||||
}
|
||||
|
||||
if (blockCollision != null && blockCollision.checkIntersection(iter.getX(), iter.getY(), iter.getZ(), box)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Vector3f getFluidGravity(double gravity, boolean falling) {
|
||||
Vector3f motion = vehicle.getMotion();
|
||||
if (gravity != 0 && !vehicle.getFlag(EntityFlag.SPRINTING)) {
|
||||
float newY = (float) (motion.getY() - gravity / 16);
|
||||
if (falling && Math.abs(motion.getY() - 0.005f) >= MIN_VELOCITY && Math.abs(newY) < MIN_VELOCITY) {
|
||||
newY = -MIN_VELOCITY;
|
||||
}
|
||||
return Vector3f.from(motion.getX(), newY, motion.getZ());
|
||||
}
|
||||
return motion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any blocks the vehicle is colliding with should multiply movement. (Cobweb, powder snow, berry bush)
|
||||
* <p>
|
||||
* This is different from the speed factor of a block the vehicle is standing on, such as soul sand.
|
||||
*
|
||||
* @param ctx context
|
||||
* @return the multiplier
|
||||
*/
|
||||
protected @Nullable Vector3f getBlockMovementMultiplier(VehicleContext ctx) {
|
||||
BoundingBox box = boundingBox.clone();
|
||||
box.expand(-1.0E-7);
|
||||
|
||||
Vector3i min = box.getMin().toInt();
|
||||
Vector3i max = box.getMax().toInt();
|
||||
|
||||
// Iterate xyz backwards
|
||||
// Minecraft iterates forwards but only the last multiplier affects movement
|
||||
for (int x = max.getX(); x >= min.getX(); x--) {
|
||||
for (int y = max.getY(); y >= min.getY(); y--) {
|
||||
for (int z = max.getZ(); z >= min.getZ(); z--) {
|
||||
Block block = ctx.getBlock(x, y, z).block();
|
||||
Vector3f multiplier = null;
|
||||
|
||||
if (block == Blocks.COBWEB) {
|
||||
if (effectWeaving) {
|
||||
multiplier = Vector3f.from(0.5, 0.25, 0.5);
|
||||
} else {
|
||||
multiplier = Vector3f.from(0.25, 0.05f, 0.25);
|
||||
}
|
||||
} else if (block == Blocks.POWDER_SNOW) {
|
||||
multiplier = Vector3f.from(0.9f, 1.5, 0.9f);
|
||||
} else if (block == Blocks.SWEET_BERRY_BUSH) {
|
||||
multiplier = Vector3f.from(0.8f, 0.75, 0.8f);
|
||||
}
|
||||
|
||||
if (multiplier != null) {
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void applyBlockCollisionEffects(VehicleContext ctx) {
|
||||
BoundingBox box = boundingBox.clone();
|
||||
box.expand(-1.0E-7);
|
||||
|
||||
Vector3i min = box.getMin().toInt();
|
||||
Vector3i max = box.getMax().toInt();
|
||||
|
||||
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ());
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
BlockState blockState = ctx.getBlock(iter);
|
||||
|
||||
if (blockState.is(Blocks.HONEY_BLOCK)) {
|
||||
onHoneyBlockCollision();
|
||||
} else if (blockState.is(Blocks.BUBBLE_COLUMN)) {
|
||||
onBubbleColumnCollision(blockState.getValue(Properties.DRAG));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onHoneyBlockCollision() {
|
||||
if (vehicle.isOnGround() || vehicle.getMotion().getY() >= -0.08f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOT IMPLEMENTED: don't slide if inside the honey block
|
||||
Vector3f motion = vehicle.getMotion();
|
||||
float mul = motion.getY() < -0.13f ? -0.05f / motion.getY() : 1;
|
||||
vehicle.setMotion(Vector3f.from(motion.getX() * mul, -0.05f, motion.getZ() * mul));
|
||||
}
|
||||
|
||||
protected void onBubbleColumnCollision(boolean drag) {
|
||||
Vector3f motion = vehicle.getMotion();
|
||||
vehicle.setMotion(Vector3f.from(
|
||||
motion.getX(),
|
||||
drag ? Math.max(-0.3f, motion.getY() - 0.03f) : Math.min(0.7f, motion.getY() + 0.06f),
|
||||
motion.getZ()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next position of the vehicle while checking for collision and adjusting velocity.
|
||||
*
|
||||
* @return true if there was a horizontal collision
|
||||
*/
|
||||
protected boolean travel(VehicleContext ctx, float speed) {
|
||||
Vector3f motion = vehicle.getMotion();
|
||||
|
||||
// Java only does this client side
|
||||
motion = motion.mul(0.98f);
|
||||
|
||||
motion = Vector3f.from(
|
||||
Math.abs(motion.getX()) < MIN_VELOCITY ? 0 : motion.getX(),
|
||||
Math.abs(motion.getY()) < MIN_VELOCITY ? 0 : motion.getY(),
|
||||
Math.abs(motion.getZ()) < MIN_VELOCITY ? 0 : motion.getZ()
|
||||
);
|
||||
|
||||
// !isImmobile
|
||||
if (vehicle.isAlive()) {
|
||||
motion = motion.add(getInputVelocity(ctx, speed));
|
||||
}
|
||||
|
||||
Vector3f movementMultiplier = getBlockMovementMultiplier(ctx);
|
||||
if (movementMultiplier != null) {
|
||||
motion = motion.mul(movementMultiplier);
|
||||
}
|
||||
|
||||
// Check world border before blocks
|
||||
Vector3d correctedMovement = vehicle.getSession().getWorldBorder().correctMovement(boundingBox, motion.toDouble());
|
||||
correctedMovement = vehicle.getSession().getCollisionManager().correctMovement(
|
||||
correctedMovement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava()
|
||||
);
|
||||
|
||||
boundingBox.translate(correctedMovement);
|
||||
ctx.loadSurroundingBlocks(); // Context must be reloaded after vehicle is moved
|
||||
|
||||
// Non-zero values indicate a collision on that axis
|
||||
Vector3d moveDiff = motion.toDouble().sub(correctedMovement);
|
||||
|
||||
vehicle.setOnGround(moveDiff.getY() != 0 && motion.getY() < 0);
|
||||
boolean horizontalCollision = moveDiff.getX() != 0 || moveDiff.getZ() != 0;
|
||||
|
||||
boolean bounced = false;
|
||||
if (vehicle.isOnGround()) {
|
||||
Block landingBlock = getLandingBlock(ctx).block();
|
||||
|
||||
if (landingBlock == Blocks.SLIME_BLOCK) {
|
||||
motion = Vector3f.from(motion.getX(), -motion.getY(), motion.getZ());
|
||||
bounced = true;
|
||||
|
||||
// Slow horizontal movement
|
||||
float absY = Math.abs(motion.getY());
|
||||
if (absY < 0.1f) {
|
||||
float mul = 0.4f + absY * 0.2f;
|
||||
motion = motion.mul(mul, 1.0f, mul);
|
||||
}
|
||||
} else if (landingBlock instanceof BedBlock) {
|
||||
motion = Vector3f.from(motion.getX(), -motion.getY() * 0.66f, motion.getZ());
|
||||
bounced = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set motion to 0 if a movement multiplier was used, else set to 0 on each axis with a collision
|
||||
if (movementMultiplier != null) {
|
||||
motion = Vector3f.ZERO;
|
||||
} else {
|
||||
motion = motion.mul(
|
||||
moveDiff.getX() == 0 ? 1 : 0,
|
||||
moveDiff.getY() == 0 || bounced ? 1 : 0,
|
||||
moveDiff.getZ() == 0 ? 1 : 0
|
||||
);
|
||||
}
|
||||
|
||||
// Send the new position to the bedrock client and java server
|
||||
moveVehicle(ctx.centerPos());
|
||||
vehicle.setMotion(motion);
|
||||
|
||||
applyBlockCollisionEffects(ctx);
|
||||
|
||||
float velocityMultiplier = getVelocityMultiplier(ctx);
|
||||
vehicle.setMotion(vehicle.getMotion().mul(velocityMultiplier, 1.0f, velocityMultiplier));
|
||||
|
||||
return horizontalCollision;
|
||||
}
|
||||
|
||||
protected boolean isClimbing(VehicleContext ctx) {
|
||||
if (!vehicle.canClimb()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockState blockState = ctx.centerBlock();
|
||||
if (vehicle.getSession().getTagCache().is(BlockTag.CLIMBABLE, blockState.block())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the vehicle is in an open trapdoor with a ladder of the same direction under it
|
||||
if (blockState.block() instanceof TrapDoorBlock && blockState.getValue(Properties.OPEN)) {
|
||||
BlockState ladderState = ctx.getBlock(ctx.centerPos().toInt().down());
|
||||
return ladderState.is(Blocks.LADDER) &&
|
||||
ladderState.getValue(Properties.HORIZONTAL_FACING) == blockState.getValue(Properties.HORIZONTAL_FACING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the player's input into velocity.
|
||||
*
|
||||
* @param ctx context
|
||||
* @param speed multiplier for input
|
||||
* @return velocity
|
||||
*/
|
||||
protected Vector3f getInputVelocity(VehicleContext ctx, float speed) {
|
||||
Vector2f input = vehicle.getSession().getPlayerEntity().getVehicleInput();
|
||||
input = input.mul(0.98f);
|
||||
input = vehicle.getAdjustedInput(input);
|
||||
input = normalizeInput(input);
|
||||
input = input.mul(speed);
|
||||
|
||||
// Match player rotation
|
||||
float yaw = vehicle.getSession().getPlayerEntity().getYaw();
|
||||
float sin = TrigMath.sin(yaw * TrigMath.DEG_TO_RAD);
|
||||
float cos = TrigMath.cos(yaw * TrigMath.DEG_TO_RAD);
|
||||
return Vector3f.from(input.getX() * cos - input.getY() * sin, 0, input.getY() * cos + input.getX() * sin);
|
||||
}
|
||||
|
||||
protected Vector2f normalizeInput(Vector2f input) {
|
||||
float lenSquared = input.lengthSquared();
|
||||
if (lenSquared < 1.0E-7) {
|
||||
return Vector2f.ZERO;
|
||||
} else if (lenSquared > 1.0) {
|
||||
return input.normalize();
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation to use for the vehicle. This is based on the player's head rotation.
|
||||
*/
|
||||
protected Vector2f getVehicleRotation() {
|
||||
LivingEntity player = vehicle.getSession().getPlayerEntity();
|
||||
return Vector2f.from(player.getYaw(), player.getPitch() * 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new position for the vehicle and sends packets to both the java server and bedrock client.
|
||||
* <p>
|
||||
* This also updates the session's last vehicle move timestamp.
|
||||
* @param javaPos the new java position of the vehicle
|
||||
*/
|
||||
protected void moveVehicle(Vector3d javaPos) {
|
||||
Vector3f bedrockPos = javaPos.toFloat();
|
||||
Vector2f rotation = getVehicleRotation();
|
||||
|
||||
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
|
||||
moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId());
|
||||
|
||||
if (vehicle.isOnGround()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
|
||||
}
|
||||
|
||||
if (vehicle.getPosition().getX() != bedrockPos.getX()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
|
||||
moveEntityDeltaPacket.setX(bedrockPos.getX());
|
||||
}
|
||||
if (vehicle.getPosition().getY() != bedrockPos.getY()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
|
||||
moveEntityDeltaPacket.setY(bedrockPos.getY());
|
||||
}
|
||||
if (vehicle.getPosition().getZ() != bedrockPos.getZ()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
|
||||
moveEntityDeltaPacket.setZ(bedrockPos.getZ());
|
||||
}
|
||||
vehicle.setPosition(bedrockPos);
|
||||
|
||||
if (vehicle.getYaw() != rotation.getX()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
|
||||
moveEntityDeltaPacket.setYaw(rotation.getX());
|
||||
vehicle.setYaw(rotation.getX());
|
||||
}
|
||||
if (vehicle.getPitch() != rotation.getY()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
|
||||
moveEntityDeltaPacket.setPitch(rotation.getY());
|
||||
vehicle.setPitch(rotation.getY());
|
||||
}
|
||||
if (vehicle.getHeadYaw() != rotation.getX()) { // Same as yaw
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
|
||||
moveEntityDeltaPacket.setHeadYaw(rotation.getX());
|
||||
vehicle.setHeadYaw(rotation.getX());
|
||||
}
|
||||
|
||||
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
|
||||
vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket);
|
||||
}
|
||||
|
||||
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos.getX(), javaPos.getY(), javaPos.getZ(), rotation.getX(), rotation.getY());
|
||||
vehicle.getSession().sendDownstreamPacket(moveVehiclePacket);
|
||||
vehicle.getSession().setLastVehicleMoveTimestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
protected double getGravity() {
|
||||
if (!vehicle.getFlag(EntityFlag.HAS_GRAVITY)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vehicle.getMotion().getY() <= 0 && effectSlowFalling) {
|
||||
return Math.min(0.01, this.gravity);
|
||||
}
|
||||
|
||||
return this.gravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the position of the main block supporting the vehicle.
|
||||
* Used when determining slipperiness, speed, etc.
|
||||
* <p>
|
||||
* Should use {@link VehicleContext#supportingBlockPos()}, instead of calling this directly.
|
||||
*
|
||||
* @param ctx context
|
||||
* @return position of the main block supporting this entity
|
||||
*/
|
||||
private @Nullable Vector3i getSupportingBlockPos(VehicleContext ctx) {
|
||||
Vector3i result = null;
|
||||
|
||||
if (vehicle.isOnGround()) {
|
||||
BoundingBox box = boundingBox.clone();
|
||||
box.extend(0, -1.0E-6, 0); // Extend slightly down
|
||||
|
||||
Vector3i min = box.getMin().toInt();
|
||||
Vector3i max = box.getMax().toInt();
|
||||
|
||||
// Use minY as maxY
|
||||
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), min.getY(), max.getZ());
|
||||
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ());
|
||||
int blockId = ctx.getBlockId(iter);
|
||||
|
||||
BlockCollision blockCollision;
|
||||
if (vehicle.canWalkOnLava()) {
|
||||
blockCollision = vehicle.getSession().getCollisionManager().getCollisionLavaWalking(blockId, blockPos.getY(), boundingBox);
|
||||
} else {
|
||||
blockCollision = BlockUtils.getCollision(blockId);
|
||||
}
|
||||
|
||||
if (blockCollision != null && blockCollision.checkIntersection(blockPos, box)) {
|
||||
double distance = ctx.centerPos().distanceSquared(blockPos.toDouble().add(0.5f, 0.5f, 0.5f));
|
||||
if (distance <= minDistance) {
|
||||
minDistance = distance;
|
||||
result = blockPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block that is x amount of blocks under the main supporting block.
|
||||
*/
|
||||
protected BlockState getBlockUnderSupport(VehicleContext ctx, float dist) {
|
||||
Vector3i supportingBlockPos = ctx.supportingBlockPos();
|
||||
|
||||
Vector3i blockPos;
|
||||
if (supportingBlockPos != null) {
|
||||
blockPos = Vector3i.from(supportingBlockPos.getX(), Math.floor(ctx.centerPos().getY() - dist), supportingBlockPos.getZ());
|
||||
} else {
|
||||
blockPos = ctx.centerPos().sub(0, dist, 0).toInt();
|
||||
}
|
||||
|
||||
return ctx.getBlock(blockPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* The block to use when determining if the vehicle should bounce after landing. Currently just slime and bed blocks.
|
||||
*/
|
||||
protected BlockState getLandingBlock(VehicleContext ctx) {
|
||||
return getBlockUnderSupport(ctx, 0.2f);
|
||||
}
|
||||
|
||||
/**
|
||||
* The block to use when calculating slipperiness and speed. If on a slab, this will be the block under the slab.
|
||||
*/
|
||||
protected BlockState getVelocityBlock(VehicleContext ctx) {
|
||||
return getBlockUnderSupport(ctx, 0.500001f);
|
||||
}
|
||||
|
||||
protected float getVelocityMultiplier(VehicleContext ctx) {
|
||||
Block block = ctx.centerBlock().block();
|
||||
if (block == Blocks.WATER || block == Blocks.BUBBLE_COLUMN) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) {
|
||||
return 0.4f;
|
||||
}
|
||||
|
||||
block = getVelocityBlock(ctx).block();
|
||||
if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) {
|
||||
return 0.4f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
protected float getJumpVelocityMultiplier(VehicleContext ctx) {
|
||||
Block block = ctx.centerBlock().block();
|
||||
if (block == Blocks.HONEY_BLOCK) {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
block = getVelocityBlock(ctx).block();
|
||||
if (block == Blocks.HONEY_BLOCK) {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
protected class VehicleContext {
|
||||
private Vector3d centerPos;
|
||||
private Vector3d cachePos;
|
||||
private BlockState centerBlock;
|
||||
private Vector3i supportingBlockPos;
|
||||
private BlockPositionIterator blockIter;
|
||||
private int[] blocks;
|
||||
|
||||
/**
|
||||
* Cache frequently used data and blocks used in movement calculations.
|
||||
* <p>
|
||||
* Can be called multiple times, and must be called at least once before using the VehicleContext.
|
||||
*/
|
||||
protected void loadSurroundingBlocks() {
|
||||
this.centerPos = boundingBox.getBottomCenter();
|
||||
|
||||
// Reuse block cache if vehicle moved less than 1 block
|
||||
if (this.cachePos == null || this.cachePos.distanceSquared(this.centerPos) > 1) {
|
||||
BoundingBox box = boundingBox.clone();
|
||||
box.expand(2);
|
||||
|
||||
Vector3i min = box.getMin().toInt();
|
||||
Vector3i max = box.getMax().toInt();
|
||||
this.blockIter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ());
|
||||
this.blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), this.blockIter);
|
||||
|
||||
this.cachePos = this.centerPos;
|
||||
}
|
||||
|
||||
this.centerBlock = getBlock(this.centerPos.toInt());
|
||||
this.supportingBlockPos = null;
|
||||
}
|
||||
|
||||
protected Vector3d centerPos() {
|
||||
return this.centerPos;
|
||||
}
|
||||
|
||||
protected BlockState centerBlock() {
|
||||
return this.centerBlock;
|
||||
}
|
||||
|
||||
protected Vector3i supportingBlockPos() {
|
||||
if (this.supportingBlockPos == null) {
|
||||
this.supportingBlockPos = getSupportingBlockPos(this);
|
||||
}
|
||||
|
||||
return this.supportingBlockPos;
|
||||
}
|
||||
|
||||
protected int getBlockId(int x, int y, int z) {
|
||||
int index = this.blockIter.getIndex(x, y, z);
|
||||
if (index == -1) {
|
||||
vehicle.getSession().getGeyser().getLogger().debug("[client-vehicle] Block cache miss");
|
||||
return vehicle.getSession().getGeyser().getWorldManager().getBlockAt(vehicle.getSession(), x, y, z);
|
||||
}
|
||||
|
||||
return blocks[index];
|
||||
}
|
||||
|
||||
protected int getBlockId(Vector3i pos) {
|
||||
return getBlockId(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
protected int getBlockId(BlockPositionIterator iter) {
|
||||
return getBlockId(iter.getX(), iter.getY(), iter.getZ());
|
||||
}
|
||||
|
||||
protected BlockState getBlock(int x, int y, int z) {
|
||||
return BlockState.of(getBlockId(x, y, z));
|
||||
}
|
||||
|
||||
protected BlockState getBlock(Vector3i pos) {
|
||||
return BlockState.of(getBlockId(pos.getX(), pos.getY(), pos.getZ()));
|
||||
}
|
||||
|
||||
protected BlockState getBlock(BlockPositionIterator iter) {
|
||||
return BlockState.of(getBlockId(iter.getX(), iter.getY(), iter.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,6 +62,16 @@ public class PlayerInventory extends Inventory {
|
|||
cursor = newCursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player is holding the specified item in either hand
|
||||
*
|
||||
* @param item The item to look for
|
||||
* @return If the player is holding the item in either hand
|
||||
*/
|
||||
public boolean isHolding(@NonNull Item item) {
|
||||
return getItemInHand().asItem() == item || getOffhand().asItem() == item;
|
||||
}
|
||||
|
||||
public GeyserItemStack getItemInHand(@NonNull Hand hand) {
|
||||
return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ public class StoredItemMappings {
|
|||
private final ItemMapping banner;
|
||||
private final ItemMapping barrier;
|
||||
private final ItemMapping bow;
|
||||
private final ItemMapping carrotOnAStick;
|
||||
private final ItemMapping compass;
|
||||
private final ItemMapping crossbow;
|
||||
private final ItemMapping egg;
|
||||
|
@ -52,6 +53,7 @@ public class StoredItemMappings {
|
|||
private final ItemMapping shield;
|
||||
private final ItemMapping totem;
|
||||
private final ItemMapping upgradeTemplate;
|
||||
private final ItemMapping warpedFungusOnAStick;
|
||||
private final ItemMapping wheat;
|
||||
private final ItemMapping writableBook;
|
||||
private final ItemMapping writtenBook;
|
||||
|
@ -60,6 +62,7 @@ public class StoredItemMappings {
|
|||
this.banner = load(itemMappings, Items.WHITE_BANNER); // As of 1.17.10, all banners have the same Bedrock ID
|
||||
this.barrier = load(itemMappings, Items.BARRIER);
|
||||
this.bow = load(itemMappings, Items.BOW);
|
||||
this.carrotOnAStick = load(itemMappings, Items.CARROT_ON_A_STICK);
|
||||
this.compass = load(itemMappings, Items.COMPASS);
|
||||
this.crossbow = load(itemMappings, Items.CROSSBOW);
|
||||
this.egg = load(itemMappings, Items.EGG);
|
||||
|
@ -69,6 +72,7 @@ public class StoredItemMappings {
|
|||
this.shield = load(itemMappings, Items.SHIELD);
|
||||
this.totem = load(itemMappings, Items.TOTEM_OF_UNDYING);
|
||||
this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
|
||||
this.warpedFungusOnAStick = load(itemMappings, Items.WARPED_FUNGUS_ON_A_STICK);
|
||||
this.wheat = load(itemMappings, Items.WHEAT);
|
||||
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
|
||||
this.writtenBook = load(itemMappings, Items.WRITTEN_BOOK);
|
||||
|
|
|
@ -34,11 +34,13 @@ import org.geysermc.geyser.util.DimensionUtils;
|
|||
* Represents the information we store from the current Java dimension
|
||||
* @param piglinSafe Whether piglins and hoglins are safe from conversion in this dimension.
|
||||
* This controls if they have the shaking effect applied in the dimension.
|
||||
* @param ultrawarm If this dimension is ultrawarm.
|
||||
* Used when calculating movement in lava for client-side vehicles.
|
||||
* @param bedrockId the Bedrock dimension ID of this dimension.
|
||||
* As a Java dimension can be null in some login cases (e.g. GeyserConnect), make sure the player
|
||||
* is logged in before utilizing this field.
|
||||
*/
|
||||
public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale, int bedrockId, boolean isNetherLike) {
|
||||
public record JavaDimension(int minY, int maxY, boolean piglinSafe, boolean ultrawarm, double worldCoordinateScale, int bedrockId, boolean isNetherLike) {
|
||||
|
||||
public static JavaDimension read(RegistryEntryContext entry) {
|
||||
NbtMap dimension = entry.data();
|
||||
|
@ -48,6 +50,8 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world
|
|||
|
||||
// Set if piglins/hoglins should shake
|
||||
boolean piglinSafe = dimension.getBoolean("piglin_safe");
|
||||
// Entities in lava move faster in ultrawarm dimensions
|
||||
boolean ultrawarm = dimension.getBoolean("ultrawarm");
|
||||
// Load world coordinate scale for the world border
|
||||
double coordinateScale = dimension.getNumber("coordinate_scale").doubleValue(); // FIXME see if we can change this in the NBT library itself.
|
||||
|
||||
|
@ -67,6 +71,6 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world
|
|||
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(effects);
|
||||
}
|
||||
|
||||
return new JavaDimension(minY, maxY, piglinSafe, coordinateScale, bedrockId, isNetherLike);
|
||||
return new JavaDimension(minY, maxY, piglinSafe, ultrawarm, coordinateScale, bedrockId, isNetherLike);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.geysermc.geyser.registry.BlockRegistries;
|
|||
* Used for block entities if the Java block state contains Bedrock block information.
|
||||
*/
|
||||
public final class BlockStateValues {
|
||||
public static final int NUM_WATER_LEVELS = 9;
|
||||
public static final int NUM_FLUID_LEVELS = 9;
|
||||
|
||||
/**
|
||||
* Checks if a block sticks to other blocks
|
||||
|
@ -99,6 +99,25 @@ public final class BlockStateValues {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of fluid from the block state, including waterlogged blocks.
|
||||
*
|
||||
* @param state BlockState of the block
|
||||
* @return The type of fluid
|
||||
*/
|
||||
public static Fluid getFluid(int state) {
|
||||
BlockState blockState = BlockState.of(state);
|
||||
if (blockState.is(Blocks.WATER) || BlockRegistries.WATERLOGGED.get().get(state)) {
|
||||
return Fluid.WATER;
|
||||
}
|
||||
|
||||
if (blockState.is(Blocks.LAVA)) {
|
||||
return Fluid.LAVA;
|
||||
}
|
||||
|
||||
return Fluid.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level of water from the block state.
|
||||
*
|
||||
|
@ -127,7 +146,7 @@ public final class BlockStateValues {
|
|||
waterLevel = 0;
|
||||
}
|
||||
if (waterLevel >= 0) {
|
||||
double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_WATER_LEVELS);
|
||||
double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_FLUID_LEVELS);
|
||||
// Falling water is a full block
|
||||
if (waterLevel >= 8) {
|
||||
waterHeight = 1;
|
||||
|
@ -137,6 +156,39 @@ public final class BlockStateValues {
|
|||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level of lava from the block state.
|
||||
*
|
||||
* @param state BlockState of the block
|
||||
* @return The lava level or -1 if the block isn't lava
|
||||
*/
|
||||
public static int getLavaLevel(int state) {
|
||||
BlockState blockState = BlockState.of(state);
|
||||
if (!blockState.is(Blocks.LAVA)) {
|
||||
return -1;
|
||||
}
|
||||
return blockState.getValue(Properties.LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of lava from the block state
|
||||
*
|
||||
* @param state BlockState of the block
|
||||
* @return The lava height or -1 if the block does not contain lava
|
||||
*/
|
||||
public static double getLavaHeight(int state) {
|
||||
int lavaLevel = BlockStateValues.getLavaLevel(state);
|
||||
if (lavaLevel >= 0) {
|
||||
double lavaHeight = 1 - (lavaLevel + 1) / ((double) NUM_FLUID_LEVELS);
|
||||
// Falling lava is a full block
|
||||
if (lavaLevel >= 8) {
|
||||
lavaHeight = 1;
|
||||
}
|
||||
return lavaHeight;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the slipperiness of a block.
|
||||
* This is used in ItemEntity to calculate the friction on an item as it slides across the ground
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.level.block;
|
||||
|
||||
public enum Fluid {
|
||||
WATER,
|
||||
LAVA,
|
||||
EMPTY
|
||||
}
|
|
@ -33,6 +33,8 @@ import org.cloudburstmc.math.vector.Vector3d;
|
|||
@Data
|
||||
@AllArgsConstructor
|
||||
public class BoundingBox implements Cloneable {
|
||||
private static final double EPSILON = 1.0E-7;
|
||||
|
||||
private double middleX;
|
||||
private double middleY;
|
||||
private double middleZ;
|
||||
|
@ -57,10 +59,24 @@ public class BoundingBox implements Cloneable {
|
|||
sizeZ += Math.abs(z);
|
||||
}
|
||||
|
||||
public void expand(double x, double y, double z) {
|
||||
sizeX += x;
|
||||
sizeY += y;
|
||||
sizeZ += z;
|
||||
}
|
||||
|
||||
public void translate(Vector3d translate) {
|
||||
translate(translate.getX(), translate.getY(), translate.getZ());
|
||||
}
|
||||
|
||||
public void extend(Vector3d extend) {
|
||||
extend(extend.getX(), extend.getY(), extend.getZ());
|
||||
}
|
||||
|
||||
public void expand(double expand) {
|
||||
expand(expand, expand, expand);
|
||||
}
|
||||
|
||||
public boolean checkIntersection(double offsetX, double offsetY, double offsetZ, BoundingBox otherBox) {
|
||||
return (Math.abs((middleX + offsetX) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX())) &&
|
||||
(Math.abs((middleY + offsetY) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY())) &&
|
||||
|
@ -78,6 +94,14 @@ public class BoundingBox implements Cloneable {
|
|||
return Vector3d.from(x, y, z);
|
||||
}
|
||||
|
||||
public double getMin(Axis axis) {
|
||||
return switch (axis) {
|
||||
case X -> middleX - sizeX / 2;
|
||||
case Y -> middleY - sizeY / 2;
|
||||
case Z -> middleZ - sizeZ / 2;
|
||||
};
|
||||
}
|
||||
|
||||
public Vector3d getMax() {
|
||||
double x = middleX + sizeX / 2;
|
||||
double y = middleY + sizeY / 2;
|
||||
|
@ -85,15 +109,23 @@ public class BoundingBox implements Cloneable {
|
|||
return Vector3d.from(x, y, z);
|
||||
}
|
||||
|
||||
public double getMax(Axis axis) {
|
||||
return switch (axis) {
|
||||
case X -> middleX + sizeX / 2;
|
||||
case Y -> middleY + sizeY / 2;
|
||||
case Z -> middleZ + sizeZ / 2;
|
||||
};
|
||||
}
|
||||
|
||||
public Vector3d getBottomCenter() {
|
||||
return Vector3d.from(middleX, middleY - sizeY / 2, middleZ);
|
||||
}
|
||||
|
||||
private boolean checkOverlapInAxis(double xOffset, double yOffset, double zOffset, BoundingBox otherBox, Axis axis) {
|
||||
return switch (axis) {
|
||||
case X -> Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX());
|
||||
case Y -> Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY());
|
||||
case Z -> Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ());
|
||||
case X -> (sizeX + otherBox.getSizeX()) - Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 > EPSILON;
|
||||
case Y -> (sizeY + otherBox.getSizeY()) - Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 > EPSILON;
|
||||
case Z -> (sizeZ + otherBox.getSizeZ()) - Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 > EPSILON;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.geysermc.erosion.util.BlockPositionIterator;
|
|||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
|
@ -45,7 +46,9 @@ import org.geysermc.geyser.level.block.type.BlockState;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.translator.collision.OtherCollision;
|
||||
import org.geysermc.geyser.translator.collision.ScaffoldingCollision;
|
||||
import org.geysermc.geyser.translator.collision.SolidCollision;
|
||||
import org.geysermc.geyser.util.BlockUtils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
@ -53,6 +56,8 @@ import java.text.DecimalFormatSymbols;
|
|||
import java.util.Locale;
|
||||
|
||||
public class CollisionManager {
|
||||
public static final BlockCollision SOLID_COLLISION = new SolidCollision(null);
|
||||
public static final BlockCollision FLUID_COLLISION = new OtherCollision(new BoundingBox[]{new BoundingBox(0.5, 0.25, 0.5, 1, 0.5, 1)});
|
||||
|
||||
private final GeyserSession session;
|
||||
|
||||
|
@ -128,6 +133,21 @@ public class CollisionManager {
|
|||
playerBoundingBox.setSizeY(playerHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounding box to use for player movement.
|
||||
* <p>
|
||||
* This will return either the bounding box of a {@link ClientVehicle}, or the player's own bounding box.
|
||||
*
|
||||
* @return the bounding box to use for movement calculations
|
||||
*/
|
||||
public BoundingBox getActiveBoundingBox() {
|
||||
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||
return clientVehicle.getVehicleComponent().getBoundingBox();
|
||||
}
|
||||
|
||||
return playerBoundingBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
|
||||
* the two versions. Will also send corrected movement packets back to Bedrock if they collide with pistons.
|
||||
|
@ -150,6 +170,15 @@ public class CollisionManager {
|
|||
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
|
||||
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
|
||||
|
||||
// Don't correct position if controlling a vehicle
|
||||
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||
playerBoundingBox.setMiddleX(position.getX());
|
||||
playerBoundingBox.setMiddleY(position.getY() + playerBoundingBox.getSizeY() / 2);
|
||||
playerBoundingBox.setMiddleZ(position.getZ());
|
||||
|
||||
return playerBoundingBox.getBottomCenter();
|
||||
}
|
||||
|
||||
Vector3d startingPos = playerBoundingBox.getBottomCenter();
|
||||
Vector3d movement = position.sub(startingPos);
|
||||
Vector3d adjustedMovement = correctPlayerMovement(movement, false, teleported);
|
||||
|
@ -173,7 +202,8 @@ public class CollisionManager {
|
|||
// Send corrected position to Bedrock if they differ by too much to prevent de-syncs
|
||||
if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) {
|
||||
PlayerEntity playerEntity = session.getPlayerEntity();
|
||||
if (pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
|
||||
// Client will dismount if on a vehicle
|
||||
if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
|
||||
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), newOnGround, true);
|
||||
}
|
||||
}
|
||||
|
@ -268,13 +298,13 @@ public class CollisionManager {
|
|||
if (teleported || (!checkWorld && session.getPistonCache().getPistons().isEmpty())) { // There is nothing to check
|
||||
return movement;
|
||||
}
|
||||
return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld);
|
||||
return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld, false);
|
||||
}
|
||||
|
||||
public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld) {
|
||||
public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld, boolean walkOnLava) {
|
||||
Vector3d adjustedMovement = movement;
|
||||
if (!movement.equals(Vector3d.ZERO)) {
|
||||
adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld);
|
||||
adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld, walkOnLava);
|
||||
}
|
||||
|
||||
boolean verticalCollision = adjustedMovement.getY() != movement.getY();
|
||||
|
@ -283,26 +313,27 @@ public class CollisionManager {
|
|||
onGround = onGround || (verticalCollision && falling);
|
||||
if (onGround && horizontalCollision) {
|
||||
Vector3d horizontalMovement = Vector3d.from(movement.getX(), 0, movement.getZ());
|
||||
Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld);
|
||||
Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld, walkOnLava);
|
||||
|
||||
BoundingBox stretchedBoundingBox = boundingBox.clone();
|
||||
stretchedBoundingBox.extend(horizontalMovement);
|
||||
double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld).getY();
|
||||
double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld, walkOnLava).getY();
|
||||
if (maxStepUp < stepUp) { // The player collided with a block above them
|
||||
boundingBox.translate(0, maxStepUp, 0);
|
||||
Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, boundingBox, checkWorld);
|
||||
boundingBox.translate(0, -maxStepUp, 0);
|
||||
BoundingBox stepUpBoundingBox = boundingBox.clone();
|
||||
stepUpBoundingBox.translate(0, maxStepUp, 0);
|
||||
|
||||
Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, stepUpBoundingBox, checkWorld, walkOnLava);
|
||||
if (squaredHorizontalLength(adjustedStepUpMovement) > squaredHorizontalLength(stepUpMovement)) {
|
||||
stepUpMovement = adjustedStepUpMovement.up(maxStepUp);
|
||||
}
|
||||
}
|
||||
|
||||
if (squaredHorizontalLength(stepUpMovement) > squaredHorizontalLength(adjustedMovement)) {
|
||||
boundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ());
|
||||
BoundingBox stepUpBoundingBox = boundingBox.clone();
|
||||
stepUpBoundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ());
|
||||
|
||||
// Apply the player's remaining vertical movement
|
||||
double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), boundingBox, checkWorld).getY();
|
||||
boundingBox.translate(-stepUpMovement.getX(), -stepUpMovement.getY(), -stepUpMovement.getZ());
|
||||
double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), stepUpBoundingBox, checkWorld, walkOnLava).getY();
|
||||
|
||||
stepUpMovement = stepUpMovement.up(verticalMovement);
|
||||
adjustedMovement = stepUpMovement;
|
||||
|
@ -315,43 +346,53 @@ public class CollisionManager {
|
|||
return vector.getX() * vector.getX() + vector.getZ() * vector.getZ();
|
||||
}
|
||||
|
||||
private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld) {
|
||||
private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld, boolean walkOnLava) {
|
||||
double movementX = movement.getX();
|
||||
double movementY = movement.getY();
|
||||
double movementZ = movement.getZ();
|
||||
|
||||
// Position might change slightly due to floating point error
|
||||
double originalX = boundingBox.getMiddleX();
|
||||
double originalY = boundingBox.getMiddleY();
|
||||
double originalZ = boundingBox.getMiddleZ();
|
||||
|
||||
BoundingBox movementBoundingBox = boundingBox.clone();
|
||||
movementBoundingBox.extend(movement);
|
||||
BlockPositionIterator iter = collidableBlocksIterator(movementBoundingBox);
|
||||
if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) {
|
||||
movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld);
|
||||
movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld, walkOnLava);
|
||||
boundingBox.translate(0, movementY, 0);
|
||||
}
|
||||
boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX);
|
||||
if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
|
||||
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld);
|
||||
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld, walkOnLava);
|
||||
boundingBox.translate(0, 0, movementZ);
|
||||
}
|
||||
if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) {
|
||||
movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld);
|
||||
movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld, walkOnLava);
|
||||
boundingBox.translate(movementX, 0, 0);
|
||||
}
|
||||
if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
|
||||
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld);
|
||||
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld, walkOnLava);
|
||||
boundingBox.translate(0, 0, movementZ);
|
||||
}
|
||||
|
||||
boundingBox.translate(-movementX, -movementY, -movementZ);
|
||||
boundingBox.setMiddleX(originalX);
|
||||
boundingBox.setMiddleY(originalY);
|
||||
boundingBox.setMiddleZ(originalZ);
|
||||
|
||||
return Vector3d.from(movementX, movementY, movementZ);
|
||||
}
|
||||
|
||||
private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld) {
|
||||
private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld, boolean walkOnLava) {
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
int x = iter.getX();
|
||||
int y = iter.getY();
|
||||
int z = iter.getZ();
|
||||
if (checkWorld) {
|
||||
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, x, y, z);
|
||||
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, x, y, z);
|
||||
|
||||
BlockCollision blockCollision = walkOnLava ? getCollisionLavaWalking(blockId, y, boundingBox) : BlockUtils.getCollision(blockId);
|
||||
if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) {
|
||||
offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset);
|
||||
}
|
||||
|
@ -364,6 +405,16 @@ public class CollisionManager {
|
|||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the block collision appropriate for entities that can walk on lava (Strider)
|
||||
*/
|
||||
public BlockCollision getCollisionLavaWalking(int blockId, int blockY, BoundingBox boundingBox) {
|
||||
if (BlockStateValues.getLavaLevel(blockId) == 0 && FLUID_COLLISION.isBelow(blockY, boundingBox)) {
|
||||
return FLUID_COLLISION;
|
||||
}
|
||||
return BlockUtils.getCollision(blockId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the block located at the player's floor position plus 1 would intersect with the player,
|
||||
* were they not sneaking
|
||||
|
@ -417,7 +468,7 @@ public class CollisionManager {
|
|||
double eyeY = playerBoundingBox.getMiddleY() - playerBoundingBox.getSizeY() / 2d + session.getEyeHeight();
|
||||
double eyeZ = playerBoundingBox.getMiddleZ();
|
||||
|
||||
eyeY -= 1 / ((double) BlockStateValues.NUM_WATER_LEVELS); // Subtract the height of one water layer
|
||||
eyeY -= 1 / ((double) BlockStateValues.NUM_FLUID_LEVELS); // Subtract the height of one water layer
|
||||
int blockID = session.getGeyser().getWorldManager().getBlockAt(session, GenericMath.floor(eyeX), GenericMath.floor(eyeY), GenericMath.floor(eyeZ));
|
||||
double waterHeight = BlockStateValues.getWaterHeight(blockID);
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ public enum Direction {
|
|||
EAST(4, Vector3i.UNIT_X, Axis.X, org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction.EAST);
|
||||
|
||||
public static final Direction[] VALUES = values();
|
||||
public static final Direction[] HORIZONTAL = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
|
||||
|
||||
private final int reversedId;
|
||||
@Getter
|
||||
|
|
|
@ -128,6 +128,7 @@ import org.geysermc.geyser.entity.type.Entity;
|
|||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.entity.type.Tickable;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler;
|
||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||
import org.geysermc.geyser.impl.camera.CameraDefinitions;
|
||||
|
@ -602,6 +603,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
*/
|
||||
private ScheduledFuture<?> tickThread = null;
|
||||
|
||||
/**
|
||||
* The number of ticks that have elapsed since the start of this session
|
||||
*/
|
||||
private int ticks;
|
||||
|
||||
/**
|
||||
* The world time in ticks according to the server
|
||||
* <p>
|
||||
* Note: The TickingStatePacket is currently ignored.
|
||||
*/
|
||||
@Setter
|
||||
private long worldTicks;
|
||||
|
||||
/**
|
||||
* Used to return the player to their original rotation after using an item in BedrockInventoryTransactionTranslator
|
||||
*/
|
||||
|
@ -1261,6 +1275,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
isInWorldBorderWarningArea = false;
|
||||
}
|
||||
|
||||
Entity vehicle = playerEntity.getVehicle();
|
||||
if (vehicle instanceof ClientVehicle clientVehicle && vehicle.isValid()) {
|
||||
clientVehicle.getVehicleComponent().tickVehicle();
|
||||
}
|
||||
|
||||
for (Tickable entity : entityCache.getTickableEntities()) {
|
||||
entity.tick();
|
||||
|
@ -1296,6 +1314,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
|
||||
ticks++;
|
||||
worldTicks++;
|
||||
}
|
||||
|
||||
public void setAuthenticationData(AuthData authData) {
|
||||
|
|
|
@ -33,7 +33,9 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.level.physics.Axis;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -119,6 +121,12 @@ public class PistonCache {
|
|||
private void sendPlayerMovement() {
|
||||
if (!playerDisplacement.equals(Vector3d.ZERO) && playerMotion.equals(Vector3f.ZERO)) {
|
||||
SessionPlayerEntity playerEntity = session.getPlayerEntity();
|
||||
|
||||
Entity vehicle = playerEntity.getVehicle();
|
||||
if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isOnGround = playerDisplacement.getY() > 0 || playerEntity.isOnGround();
|
||||
Vector3d position = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
|
||||
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), isOnGround, true);
|
||||
|
@ -128,6 +136,13 @@ public class PistonCache {
|
|||
private void sendPlayerMotion() {
|
||||
if (!playerMotion.equals(Vector3f.ZERO)) {
|
||||
SessionPlayerEntity playerEntity = session.getPlayerEntity();
|
||||
|
||||
Entity vehicle = playerEntity.getVehicle();
|
||||
if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||
vehicle.setMotion(playerMotion);
|
||||
return;
|
||||
}
|
||||
|
||||
playerEntity.setMotion(playerMotion);
|
||||
|
||||
SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket();
|
||||
|
@ -149,10 +164,15 @@ public class PistonCache {
|
|||
totalDisplacement = totalDisplacement.max(-0.51d, -0.51d, -0.51d).min(0.51d, 0.51d, 0.51d);
|
||||
|
||||
Vector3d delta = totalDisplacement.sub(playerDisplacement);
|
||||
// Check if the piston is pushing a player into collision
|
||||
delta = session.getCollisionManager().correctPlayerMovement(delta, true, false);
|
||||
|
||||
session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ());
|
||||
// Check if the piston is pushing a player into collision
|
||||
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||
delta = clientVehicle.getVehicleComponent().correctMovement(delta);
|
||||
clientVehicle.getVehicleComponent().moveRelative(delta);
|
||||
} else {
|
||||
delta = session.getCollisionManager().correctPlayerMovement(delta, true, false);
|
||||
session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ());
|
||||
}
|
||||
|
||||
playerDisplacement = totalDisplacement;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.session.cache;
|
|||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.GenericMath;
|
||||
import org.cloudburstmc.math.vector.Vector2d;
|
||||
import org.cloudburstmc.math.vector.Vector3d;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
|
||||
|
@ -36,8 +37,12 @@ import lombok.Getter;
|
|||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.level.physics.Axis;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import static org.geysermc.geyser.level.physics.CollisionManager.COLLISION_TOLERANCE;
|
||||
|
||||
public class WorldBorder {
|
||||
private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D;
|
||||
|
||||
|
@ -190,6 +195,53 @@ public class WorldBorder {
|
|||
return entityPosition.getX() > warningMinX && entityPosition.getX() < warningMaxX && entityPosition.getZ() > warningMinZ && entityPosition.getZ() < warningMaxZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the movement of an entity so that it does not cross the world border.
|
||||
*
|
||||
* @param boundingBox bounding box of the entity
|
||||
* @param movement movement of the entity
|
||||
* @return the corrected movement
|
||||
*/
|
||||
public Vector3d correctMovement(BoundingBox boundingBox, Vector3d movement) {
|
||||
double correctedX;
|
||||
if (movement.getX() < 0) {
|
||||
correctedX = -limitMovement(-movement.getX(), boundingBox.getMin(Axis.X) - GenericMath.floor(minX));
|
||||
} else {
|
||||
correctedX = limitMovement(movement.getX(), GenericMath.ceil(maxX) - boundingBox.getMax(Axis.X));
|
||||
}
|
||||
|
||||
// Outside of border, don't adjust movement
|
||||
if (Double.isNaN(correctedX)) {
|
||||
return movement;
|
||||
}
|
||||
|
||||
double correctedZ;
|
||||
if (movement.getZ() < 0) {
|
||||
correctedZ = -limitMovement(-movement.getZ(), boundingBox.getMin(Axis.Z) - GenericMath.floor(minZ));
|
||||
} else {
|
||||
correctedZ = limitMovement(movement.getZ(), GenericMath.ceil(maxZ) - boundingBox.getMax(Axis.Z));
|
||||
}
|
||||
|
||||
if (Double.isNaN(correctedZ)) {
|
||||
return movement;
|
||||
}
|
||||
|
||||
return Vector3d.from(correctedX, movement.getY(), correctedZ);
|
||||
}
|
||||
|
||||
private double limitMovement(double movement, double limit) {
|
||||
if (limit < 0) {
|
||||
// Return NaN to indicate outside of border
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
if (limit < COLLISION_TOLERANCE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.min(movement, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the world border's minimum and maximum properties
|
||||
*/
|
||||
|
|
|
@ -166,4 +166,22 @@ public class BlockCollision {
|
|||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this block collision is below the given bounding box.
|
||||
*
|
||||
* @param blockY the y position of the block in the world
|
||||
* @param boundingBox the bounding box to compare
|
||||
* @return true if this block collision is below the bounding box
|
||||
*/
|
||||
public boolean isBelow(int blockY, BoundingBox boundingBox) {
|
||||
double minY = boundingBox.getMiddleY() - boundingBox.getSizeY() / 2;
|
||||
for (BoundingBox b : boundingBoxes) {
|
||||
double offset = blockY + b.getMiddleY() + b.getSizeY() / 2 - minY;
|
||||
if (offset > CollisionManager.COLLISION_TOLERANCE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import org.cloudburstmc.math.vector.Vector3i;
|
|||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
|
@ -347,18 +348,31 @@ public class PistonBlockEntity {
|
|||
blockMovement = 1f - lastProgress;
|
||||
}
|
||||
|
||||
BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox();
|
||||
boolean onGround;
|
||||
BoundingBox playerBoundingBox;
|
||||
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||
onGround = session.getPlayerEntity().getVehicle().isOnGround();
|
||||
playerBoundingBox = clientVehicle.getVehicleComponent().getBoundingBox();
|
||||
} else {
|
||||
onGround = session.getPlayerEntity().isOnGround();
|
||||
playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox();
|
||||
}
|
||||
|
||||
// Shrink the collision in the other axes slightly, to avoid false positives when pressed up against the side of blocks
|
||||
Vector3d shrink = Vector3i.ONE.sub(direction.abs()).toDouble().mul(CollisionManager.COLLISION_TOLERANCE * 2);
|
||||
playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() - shrink.getX());
|
||||
playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() - shrink.getY());
|
||||
playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() - shrink.getZ());
|
||||
double sizeX = playerBoundingBox.getSizeX();
|
||||
double sizeY = playerBoundingBox.getSizeY();
|
||||
double sizeZ = playerBoundingBox.getSizeZ();
|
||||
|
||||
playerBoundingBox.setSizeX(sizeX - shrink.getX());
|
||||
playerBoundingBox.setSizeY(sizeY - shrink.getY());
|
||||
playerBoundingBox.setSizeZ(sizeZ - shrink.getZ());
|
||||
|
||||
// Resolve collision with the piston head
|
||||
BlockState pistonHeadId = Blocks.PISTON_HEAD.defaultBlockState()
|
||||
.withValue(Properties.SHORT, false)
|
||||
.withValue(Properties.FACING, orientation);
|
||||
pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox);
|
||||
pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox, onGround);
|
||||
|
||||
// Resolve collision with any attached moving blocks, but skip slime blocks
|
||||
// This prevents players from being launched by slime blocks covered by other blocks
|
||||
|
@ -366,7 +380,7 @@ public class PistonBlockEntity {
|
|||
BlockState state = entry.getValue();
|
||||
if (!state.is(Blocks.SLIME_BLOCK)) {
|
||||
Vector3d blockPos = entry.getKey().toDouble();
|
||||
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox);
|
||||
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround);
|
||||
}
|
||||
}
|
||||
// Resolve collision with slime blocks
|
||||
|
@ -374,14 +388,14 @@ public class PistonBlockEntity {
|
|||
BlockState state = entry.getValue();
|
||||
if (state.is(Blocks.SLIME_BLOCK)) {
|
||||
Vector3d blockPos = entry.getKey().toDouble();
|
||||
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox);
|
||||
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround);
|
||||
}
|
||||
}
|
||||
|
||||
// Undo shrink
|
||||
playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + shrink.getX());
|
||||
playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() + shrink.getY());
|
||||
playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + shrink.getZ());
|
||||
playerBoundingBox.setSizeX(sizeX);
|
||||
playerBoundingBox.setSizeY(sizeY);
|
||||
playerBoundingBox.setSizeZ(sizeZ);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -391,20 +405,22 @@ public class PistonBlockEntity {
|
|||
* @param playerBoundingBox The player's bounding box
|
||||
* @return True if the player attached, otherwise false
|
||||
*/
|
||||
private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox) {
|
||||
private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox, boolean onGround) {
|
||||
if (orientation.isVertical()) {
|
||||
return false;
|
||||
}
|
||||
return session.getPlayerEntity().isOnGround() && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox);
|
||||
return onGround && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a player if the player is on the pushing side of the slime block
|
||||
*
|
||||
* @param blockPos The position of the slime block
|
||||
* @param playerPos The player's position
|
||||
* @param playerBoundingBox The player's bounding box
|
||||
*/
|
||||
private void applySlimeBlockMotion(Vector3d blockPos, Vector3d playerPos) {
|
||||
private void applySlimeBlockMotion(Vector3d blockPos, BoundingBox playerBoundingBox) {
|
||||
Vector3d playerPos = Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ());
|
||||
|
||||
Direction movementDirection = orientation;
|
||||
// Invert direction when pulling
|
||||
if (action == PistonValueType.PULLING) {
|
||||
|
@ -470,7 +486,7 @@ public class PistonBlockEntity {
|
|||
return maxIntersection;
|
||||
}
|
||||
|
||||
private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) {
|
||||
private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox, boolean onGround) {
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
Vector3d movement = getMovement().toDouble();
|
||||
// Check if the player collides with the movingBlock block entity
|
||||
|
@ -480,12 +496,12 @@ public class PistonBlockEntity {
|
|||
|
||||
if (state.is(Blocks.SLIME_BLOCK)) {
|
||||
pistonCache.setPlayerSlimeCollision(true);
|
||||
applySlimeBlockMotion(finalBlockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()));
|
||||
applySlimeBlockMotion(finalBlockPos, playerBoundingBox);
|
||||
}
|
||||
}
|
||||
|
||||
Vector3d blockPos = startingPos.add(movement.mul(blockMovement));
|
||||
if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox)) {
|
||||
if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox, onGround)) {
|
||||
pistonCache.setPlayerCollided(true);
|
||||
pistonCache.setPlayerAttachedToHoney(true);
|
||||
|
||||
|
@ -508,7 +524,7 @@ public class PistonBlockEntity {
|
|||
|
||||
if (state.is(Blocks.SLIME_BLOCK)) {
|
||||
pistonCache.setPlayerSlimeCollision(true);
|
||||
applySlimeBlockMotion(blockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()));
|
||||
applySlimeBlockMotion(blockPos, playerBoundingBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -584,7 +600,7 @@ public class PistonBlockEntity {
|
|||
movingBlockMap.put(getPistonHeadPos(), this);
|
||||
|
||||
Vector3i movement = getMovement();
|
||||
BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone();
|
||||
BoundingBox playerBoundingBox = session.getCollisionManager().getActiveBoundingBox().clone();
|
||||
if (orientation == Direction.UP) {
|
||||
// Extend the bounding box down, to catch collisions when the player is falling down
|
||||
playerBoundingBox.extend(0, -256, 0);
|
||||
|
@ -628,17 +644,19 @@ public class PistonBlockEntity {
|
|||
return;
|
||||
}
|
||||
placedFinalBlocks = true;
|
||||
|
||||
Vector3i movement = getMovement();
|
||||
BoundingBox playerBoundingBox = session.getCollisionManager().getActiveBoundingBox().clone();
|
||||
attachedBlocks.forEach((blockPos, state) -> {
|
||||
blockPos = blockPos.add(movement);
|
||||
// Don't place blocks that collide with the player
|
||||
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
|
||||
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), playerBoundingBox)) {
|
||||
ChunkUtils.updateBlock(session, state, blockPos);
|
||||
}
|
||||
});
|
||||
if (action == PistonValueType.PUSHING) {
|
||||
Vector3i pistonHeadPos = getPistonHeadPos().add(movement);
|
||||
if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
|
||||
if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), playerBoundingBox)) {
|
||||
ChunkUtils.updateBlock(session, Blocks.PISTON_HEAD.defaultBlockState()
|
||||
.withValue(Properties.SHORT, false)
|
||||
.withValue(Properties.FACING, orientation), pistonHeadPos);
|
||||
|
|
|
@ -52,6 +52,8 @@ public class BedrockPlayerInputTranslator extends PacketTranslator<PlayerInputPa
|
|||
|
||||
session.sendDownstreamGamePacket(playerInputPacket);
|
||||
|
||||
session.getPlayerEntity().setVehicleInput(packet.getInputMotion());
|
||||
|
||||
// Bedrock only sends movement vehicle packets while moving
|
||||
// This allows horses to take damage while standing on magma
|
||||
Entity vehicle = session.getPlayerEntity().getVehicle();
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.cloudburstmc.math.vector.Vector3f;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
|
@ -84,7 +85,9 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
|
||||
session.sendDownstreamGamePacket(playerRotationPacket);
|
||||
} else {
|
||||
if (session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
||||
// World border collision will be handled by client vehicle
|
||||
if (!(entity.getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled())
|
||||
&& session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ import org.geysermc.geyser.translator.protocol.Translator;
|
|||
public class BedrockRiderJumpTranslator extends PacketTranslator<RiderJumpPacket> {
|
||||
@Override
|
||||
public void translate(GeyserSession session, RiderJumpPacket packet) {
|
||||
session.getPlayerEntity().setVehicleJumpStrength(packet.getJumpStrength());
|
||||
|
||||
Entity vehicle = session.getPlayerEntity().getVehicle();
|
||||
if (vehicle instanceof AbstractHorseEntity) {
|
||||
ServerboundPlayerCommandPacket playerCommandPacket = new ServerboundPlayerCommandPacket(vehicle.getEntityId(), PlayerState.START_HORSE_JUMP, packet.getJumpStrength());
|
||||
|
|
|
@ -101,6 +101,7 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
|||
DimensionUtils.fastSwitchDimension(session, fakeDim);
|
||||
}
|
||||
session.setWorldName(spawnInfo.getWorldName());
|
||||
session.setWorldTicks(0);
|
||||
DimensionUtils.switchDimension(session, newDimension);
|
||||
|
||||
ChunkUtils.loadDimension(session);
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
|||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundMoveVehiclePacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -40,6 +41,10 @@ public class JavaMoveVehicleTranslator extends PacketTranslator<ClientboundMoveV
|
|||
Entity entity = session.getPlayerEntity().getVehicle();
|
||||
if (entity == null) return;
|
||||
|
||||
if (entity instanceof ClientVehicle clientVehicle) {
|
||||
clientVehicle.getVehicleComponent().moveAbsolute(packet.getX(), packet.getY(), packet.getZ());
|
||||
}
|
||||
|
||||
entity.moveAbsolute(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
|||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundRemoveMobEffectPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -39,11 +40,15 @@ public class JavaRemoveMobEffectTranslator extends PacketTranslator<ClientboundR
|
|||
@Override
|
||||
public void translate(GeyserSession session, ClientboundRemoveMobEffectPacket packet) {
|
||||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity == session.getPlayerEntity()) {
|
||||
session.getEffectCache().removeEffect(packet.getEffect());
|
||||
} else if (entity instanceof ClientVehicle clientVehicle) {
|
||||
clientVehicle.getVehicleComponent().removeEffect(packet.getEffect());
|
||||
}
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
MobEffectPacket mobEffectPacket = new MobEffectPacket();
|
||||
mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE);
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -55,6 +56,10 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
|
|||
session.getPlayerEntity().setVehicle(entity);
|
||||
// We need to confirm teleports before entering a vehicle, or else we will likely exit right out
|
||||
session.confirmTeleport(passenger.getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0).toDouble());
|
||||
|
||||
if (entity instanceof ClientVehicle clientVehicle) {
|
||||
clientVehicle.getVehicleComponent().onMount();
|
||||
}
|
||||
}
|
||||
if (passenger == null) {
|
||||
// Can occur if the passenger is outside the client's tracking range
|
||||
|
@ -100,6 +105,10 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
|
|||
// as of Java 1.19.3, but the scheduled future checks for the vehicle being null anyway.
|
||||
session.getMountVehicleScheduledFuture().cancel(false);
|
||||
}
|
||||
|
||||
if (entity instanceof ClientVehicle clientVehicle) {
|
||||
clientVehicle.getVehicleComponent().onDismount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
|||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundTeleportEntityPacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -40,6 +41,10 @@ public class JavaTeleportEntityTranslator extends PacketTranslator<ClientboundTe
|
|||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||
if (entity == null) return;
|
||||
|
||||
if (entity instanceof ClientVehicle clientVehicle) {
|
||||
clientVehicle.getVehicleComponent().moveAbsolute(packet.getX(), packet.getY(), packet.getZ());
|
||||
}
|
||||
|
||||
entity.teleport(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
|||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundUpdateMobEffectPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -39,13 +40,16 @@ public class JavaUpdateMobEffectTranslator extends PacketTranslator<ClientboundU
|
|||
@Override
|
||||
public void translate(GeyserSession session, ClientboundUpdateMobEffectPacket packet) {
|
||||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||
if (entity == session.getPlayerEntity()) {
|
||||
session.getEffectCache().setEffect(packet.getEffect(), packet.getAmplifier());
|
||||
}
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity == session.getPlayerEntity()) {
|
||||
session.getEffectCache().setEffect(packet.getEffect(), packet.getAmplifier());
|
||||
} else if (entity instanceof ClientVehicle clientVehicle) {
|
||||
clientVehicle.getVehicleComponent().setEffect(packet.getEffect(), packet.getAmplifier());
|
||||
}
|
||||
|
||||
int duration = packet.getDuration();
|
||||
if (duration < 0) {
|
||||
// java edition uses -1 for infinite, but bedrock doesn't have infinite
|
||||
|
|
|
@ -36,6 +36,8 @@ public class JavaSetTimeTranslator extends PacketTranslator<ClientboundSetTimePa
|
|||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundSetTimePacket packet) {
|
||||
session.setWorldTicks(packet.getWorldAge());
|
||||
|
||||
// Bedrock sends a GameRulesChangedPacket if there is no daylight cycle
|
||||
// Java just sends a negative long if there is no daylight cycle
|
||||
long time = packet.getTime();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[versions]
|
||||
base-api = "1.0.1"
|
||||
cumulus = "1.1.2"
|
||||
erosion = "1.1-20240515.191456-1"
|
||||
erosion = "1.1-20240521.000109-3"
|
||||
events = "1.1-SNAPSHOT"
|
||||
jackson = "2.17.0"
|
||||
fastutil = "8.5.2"
|
||||
|
|
Loading…
Reference in a new issue