3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-20 06:50:09 +01:00

WIP client side vehicles

Dieser Commit ist enthalten in:
AJ Ferguson 2023-06-14 20:42:05 -04:00
Ursprung bf5e08403c
Commit ff0fe96f4b
26 geänderte Dateien mit 1441 neuen und 33 gelöschten Zeilen

Datei anzeigen

@ -826,7 +826,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)
@ -852,7 +852,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();

Datei anzeigen

@ -50,6 +50,7 @@ import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.EntityUtils;
@ -225,6 +226,13 @@ public class Entity implements GeyserEntity {
}
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
if (this instanceof ClientVehicle clientVehicle) {
if (clientVehicle.isLogicalSideForUpdatingMovement()) {
return;
}
clientVehicle.getVehicleComponent().moveRelative(relX, relY, relZ);
}
position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ);
MoveEntityDeltaPacket moveEntityPacket = new MoveEntityDeltaPacket();
@ -443,6 +451,10 @@ public class Entity implements GeyserEntity {
dirtyMetadata.put(EntityDataTypes.HEIGHT, boundingBoxHeight);
updatePassengerOffsets();
if (valid && this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setHeight(boundingBoxHeight);
}
return true;
}
return false;
@ -452,6 +464,10 @@ public class Entity implements GeyserEntity {
if (width != boundingBoxWidth) {
boundingBoxWidth = width;
dirtyMetadata.put(EntityDataTypes.WIDTH, boundingBoxWidth);
if (valid && this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setWidth(boundingBoxWidth);
}
}
}

Datei anzeigen

@ -51,6 +51,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,9 +295,15 @@ 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_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 HORSE_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));

Datei anzeigen

@ -25,10 +25,16 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
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.EntityDefinition;
import org.geysermc.geyser.entity.type.Tickable;
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.item.type.Item;
@ -40,7 +46,8 @@ import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
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);
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);
@ -83,4 +90,38 @@ public class PigEntity extends AnimalEntity {
}
}
}
public void setBoost(IntEntityMetadata entityMetadata) {
vehicleComponent.startBoost(entityMetadata.getPrimitiveValue());
}
@Override
public void tick() {
vehicleComponent.tickVehicle(this);
}
@Nonnull
@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}
@Override
public boolean isLogicalSideForUpdatingMovement() {
return getFlag(EntityFlag.SADDLED)
&& !passengers.isEmpty()
&& passengers.get(0) == session.getPlayerEntity()
&& session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK);
}
@Override
public float getSaddledSpeed() {
return vehicleComponent.getMoveSpeed() * 0.225f * vehicleComponent.getBoostMultiplier();
}
@Nonnull
@Override
public Vector2f getAdjustedInput(Vector2f input) {
return Vector2f.from(0, 1.0f);
}
}

Datei anzeigen

@ -26,11 +26,17 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
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.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.Tickable;
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.item.type.Item;
@ -42,8 +48,9 @@ import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
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);
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) {
@ -130,4 +137,43 @@ public class StriderEntity extends AnimalEntity {
}
}
}
public void setBoost(IntEntityMetadata entityMetadata) {
vehicleComponent.startBoost(entityMetadata.getPrimitiveValue());
}
@Override
public void tick() {
vehicleComponent.tickVehicle(this);
}
@Nonnull
@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}
@Nonnull
@Override
public Vector2f getAdjustedInput(Vector2f input) {
return Vector2f.from(0, 1.0f);
}
@Override
public boolean isLogicalSideForUpdatingMovement() {
// Does not require saddle
return !passengers.isEmpty()
&& passengers.get(0) == session.getPlayerEntity()
&& session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK);
}
@Override
public float getSaddledSpeed() {
return vehicleComponent.getMoveSpeed() * (isCold ? 0.35f : 0.55f) * vehicleComponent.getBoostMultiplier();
}
@Override
public boolean canWalkOnLava() {
return true;
}
}

Datei anzeigen

@ -25,26 +25,38 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
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;
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.type.Tickable;
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.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CamelEntity extends AbstractHorseEntity {
public class CamelEntity extends AbstractHorseEntity implements Tickable, ClientVehicle {
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
private final CamelVehicleComponent vehicleComponent = new CamelVehicleComponent(this);
private int dashTicks;
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,6 +123,64 @@ public class CamelEntity extends AbstractHorseEntity {
}
public void setDashing(BooleanEntityMetadata entityMetadata) {
if (entityMetadata.getPrimitiveValue()) {
setFlag(EntityFlag.HAS_DASH_COOLDOWN, true);
dashTicks = 55;
}
}
@Override
protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) {
AttributeData attributeData = super.calculateAttribute(javaAttribute, type);
if (javaAttribute.getType() == AttributeType.Builtin.HORSE_JUMP_STRENGTH) {
vehicleComponent.setHorseJumpStrength(attributeData.getValue());
}
return attributeData;
}
@Override
public void tick() {
vehicleComponent.tickVehicle(this);
if (dashTicks > 0 && --dashTicks == 0) {
setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
updateBedrockMetadata();
}
}
@Nonnull
@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}
@Nonnull
@Override
public Vector2f getAdjustedInput(Vector2f input) {
return input.mul(0.5f, input.getY() < 0 ? 0.25f : 1.0f);
}
@Override
public boolean isLogicalSideForUpdatingMovement() {
return getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) == session.getPlayerEntity();
}
@Override
public float getSaddledSpeed() {
float moveSpeed = vehicleComponent.getMoveSpeed();
if (dashTicks == 0 && session.getPlayerEntity().getFlag(EntityFlag.SPRINTING)) {
return moveSpeed + 0.1f;
}
return moveSpeed;
}
@Override
public float getStepHeight() {
return 1.5f;
}
@Override
public boolean canClimb() {
return false;
}
}

Datei anzeigen

@ -32,6 +32,8 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
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;
@ -67,6 +69,16 @@ public class SessionPlayerEntity extends PlayerEntity {
*/
@Getter
private boolean isRidingInFront;
/**
* Used when emulating client-side vehicles
*/
@Getter @Setter
private Vector2f vehicleInput = Vector2f.ZERO;
/**
* Used when emulating client-side vehicles
*/
@Getter @Setter
private int vehicleJumpStrength;
/**
* Used for villager inventory emulation.
*/

Datei anzeigen

@ -0,0 +1,63 @@
/*
* 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) {
super(vehicle);
}
public void startBoost(int boostLength) {
this.boostLength = boostLength;
this.boostTicks = 0;
}
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;
}
@Override
public void tickVehicle(T vehicle) {
super.tickVehicle(vehicle);
if (isBoosting()) {
boostTicks++;
}
}
}

Datei anzeigen

@ -0,0 +1,79 @@
/*
* 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 com.github.steveice10.mc.protocol.data.game.entity.Effect;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
public class CamelVehicleComponent extends VehicleComponent<CamelEntity> {
private float horseJumpStrength = 0.42f; // This is the default for Camels. Not sent by vanilla Java server when spawned
private int jumpBoost;
public CamelVehicleComponent(CamelEntity vehicle) {
super(vehicle);
}
public void setHorseJumpStrength(float horseJumpStrength) {
this.horseJumpStrength = horseJumpStrength;
}
@Override
public void setEffect(Effect effect, int effectAmplifier) {
if (effect == Effect.JUMP_BOOST) {
jumpBoost = effectAmplifier + 1;
} else {
super.setEffect(effect, effectAmplifier);
}
}
@Override
public void removeEffect(Effect effect) {
if (effect == Effect.JUMP_BOOST) {
jumpBoost = 0;
} else {
super.removeEffect(effect);
}
}
@Override
protected Vector3f getJumpVelocity(CamelEntity vehicle) {
SessionPlayerEntity player = vehicle.getSession().getPlayerEntity();
float jumpStrength = player.getVehicleJumpStrength();
if (jumpStrength > 0) {
player.setVehicleJumpStrength(0);
jumpStrength = jumpStrength >= 90 ? 1.0f : 0.4f + 0.4f * jumpStrength / 90.0f;
return Vector3f.createDirectionDeg(0, -player.getYaw())
.mul(22.2222f * jumpStrength * moveSpeed * getVelocityMultiplier(vehicle))
.up(1.4285f * jumpStrength * (horseJumpStrength * getJumpVelocityMultiplier(vehicle) + (jumpBoost * 0.1f)));
}
return Vector3f.ZERO;
}
}

Datei anzeigen

@ -0,0 +1,54 @@
/*
* 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;
import javax.annotation.Nonnull;
public interface ClientVehicle {
@Nonnull
VehicleComponent<?> getVehicleComponent();
@Nonnull
Vector2f getAdjustedInput(Vector2f input);
boolean isLogicalSideForUpdatingMovement();
float getSaddledSpeed();
default boolean canWalkOnLava() {
return false;
}
default float getStepHeight() {
return 1.0f;
}
default boolean canClimb() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,766 @@
/*
* 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 com.github.steveice10.mc.protocol.data.game.entity.Effect;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket;
import it.unimi.dsi.fastutil.Pair;
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.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.Fluid;
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.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
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 java.util.Optional;
public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
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;
private static final float CLIMB_SPEED = 0.15f;
protected final BoundingBox boundingBox;
protected float moveSpeed;
protected int levitation;
protected boolean slowFalling;
public VehicleComponent(T vehicle) {
double width = Double.parseDouble(Float.toString(vehicle.getBoundingBoxWidth()));
double height = Double.parseDouble(Float.toString(vehicle.getBoundingBoxHeight()));
boundingBox = new BoundingBox(
vehicle.getPosition().getX(),
vehicle.getPosition().getY() + height / 2,
vehicle.getPosition().getZ(),
width, height, width
);
moveSpeed = GeyserAttributeType.MOVEMENT_SPEED.getDefaultValue();
}
public void setWidth(float width) {
double doubleWidth = Double.parseDouble(Float.toString(width));
boundingBox.setSizeX(doubleWidth);
boundingBox.setSizeZ(doubleWidth);
}
public void setHeight(float height) {
boundingBox.setSizeY(Double.parseDouble(Float.toString(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 setEffect(Effect effect, int effectAmplifier) {
switch (effect) {
case LEVITATION -> levitation = effectAmplifier + 1;
case SLOW_FALLING -> slowFalling = true;
}
}
public void removeEffect(Effect effect) {
switch (effect) {
case LEVITATION -> levitation = 0;
case SLOW_FALLING -> slowFalling = false;
}
}
public void setMoveSpeed(float moveSpeed) {
this.moveSpeed = moveSpeed;
}
public float getMoveSpeed() {
return moveSpeed;
}
public void tickVehicle(T vehicle) {
if (!vehicle.isLogicalSideForUpdatingMovement()) {
return;
}
Pair<Fluid, Double> fluidHeight = updateFluidMovement(vehicle);
switch (fluidHeight.left()) {
case WATER -> waterMovement(vehicle);
case LAVA -> {
if (vehicle.canWalkOnLava() && BlockStateValues.getFluid(getBlockAt(vehicle, boundingBox.getBottomCenter().toInt())) == Fluid.LAVA) {
landMovement(vehicle);
} else {
lavaMovement(vehicle, fluidHeight.right());
}
}
case EMPTY -> landMovement(vehicle);
}
}
protected Pair<Fluid, Double> updateFluidMovement(T vehicle) {
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());
int[] blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), iter);
double waterHeight = getFluidHeightAndApplyMovement(Fluid.WATER, 0.014, min.getY(), vehicle, iter, blocks);
double lavaHeight = getFluidHeightAndApplyMovement(Fluid.LAVA, vehicle.getSession().getDimensionType().ultraWarm() ? 0.007 : 0.007 / 3, min.getY(), vehicle, iter, blocks);
if (lavaHeight > 0 && vehicle.getDefinition().entityType() == EntityType.STRIDER) {
Vector3i blockPos = boundingBox.getBottomCenter().toInt();
if (!CollisionManager.FLUID_COLLISION.isBelow(blockPos.getY(), boundingBox) || BlockStateValues.getFluid(getBlockAt(vehicle, blockPos.up())) == Fluid.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 Pair.of(Fluid.WATER, waterHeight);
}
if (lavaHeight > 0) {
return Pair.of(Fluid.LAVA, lavaHeight);
}
return Pair.of(Fluid.EMPTY, 0.0);
}
protected double getFluidHeightAndApplyMovement(Fluid fluid, double speed, double minY, T vehicle, BlockPositionIterator iter, int[] blocks) {
Vector3d totalVelocity = Vector3d.ZERO;
double maxFluidHeight = 0;
int fluidBlocks = 0;
for (iter.reset(); iter.hasNext(); iter.next()) {
int blockId = blocks[iter.getIteration()];
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) {
continue;
}
boolean flowBlocked = worldFluidHeight != 1; // This is only used for determining if a falling fluid should drag the vehicle downwards
Vector3d velocity = Vector3d.ZERO;
for (Direction direction : Direction.HORIZONTAL) {
Vector3i adjacentBlockPos = blockPos.add(direction.getUnitVector());
int adjacentBlockId = getBlockAt(vehicle, 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, getBlockAt(vehicle, 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) {
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, getBlockAt(vehicle, 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 int getBlockAt(T vehicle, Vector3i blockPos) {
return vehicle.getSession().getGeyser().getWorldManager().getBlockAt(vehicle.getSession(), blockPos);
}
private float getWorldFluidHeight(Fluid fluidType, int blockId) {
return (float) switch (fluidType) {
case WATER -> BlockStateValues.getWaterHeight(blockId);
case LAVA -> BlockStateValues.getLavaHeight(blockId);
case EMPTY -> -1;
};
}
private float getLogicalFluidHeight(Fluid fluidType, int blockId) {
return Math.min(getWorldFluidHeight(fluidType, blockId), MAX_LOGICAL_FLUID_HEIGHT);
}
private boolean isFlowBlocked(Fluid fluid, int adjacentBlockId) {
if (adjacentBlockId == BlockStateValues.JAVA_ICE_ID) {
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(T vehicle) {
float gravity = getGravity(vehicle);
float drag = vehicle.getFlag(EntityFlag.SPRINTING) ? 0.9f : 0.8f; // 0.8f: getBaseMovementSpeedMultiplier
double originalY = boundingBox.getBottomCenter().getY();
boolean falling = vehicle.getMotion().getY() <= 0;
// NOT IMPLEMENTED: depth strider and dolphins grace
boolean horizontalCollision = travel(vehicle,0.02f);
if (horizontalCollision && isClimbing(vehicle)) {
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.2f, vehicle.getMotion().getZ()));
}
vehicle.setMotion(vehicle.getMotion().mul(drag, 0.8f, drag));
vehicle.setMotion(getFluidGravity(vehicle, gravity, falling));
if (horizontalCollision && shouldApplyFluidJumpBoost(vehicle, originalY)) {
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ()));
}
}
protected void lavaMovement(T vehicle, double lavaHeight) {
float gravity = getGravity(vehicle);
double originalY = boundingBox.getBottomCenter().getY();
boolean falling = vehicle.getMotion().getY() <= 0;
boolean horizontalCollision = travel(vehicle, 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(vehicle, gravity, falling));
} else {
vehicle.setMotion(vehicle.getMotion().mul(0.5f));
}
vehicle.setMotion(vehicle.getMotion().down(gravity / 4.0f));
if (horizontalCollision && shouldApplyFluidJumpBoost(vehicle, originalY)) {
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ()));
}
}
protected void landMovement(T vehicle) {
float gravity = getGravity(vehicle);
float slipperiness = BlockStateValues.getSlipperiness(getBlockAt(vehicle, getVelocityAffectingPos(vehicle)));
float drag = vehicle.isOnGround() ? 0.91f * slipperiness : 0.91f;
float speed = vehicle.getSaddledSpeed() * (vehicle.isOnGround() ? BASE_SLIPPERINESS_CUBED / (slipperiness * slipperiness * slipperiness) : 0.1f);
boolean horizontalCollision = travel(vehicle, speed);
if (isClimbing(vehicle)) {
vehicle.setMotion(getClimbingSpeed(vehicle, horizontalCollision));
// NOT IMPLEMENTED: climbing in powdered snow
}
if (levitation > 0) {
vehicle.setMotion(vehicle.getMotion().up((0.05f * levitation - vehicle.getMotion().getY()) * 0.2f));
} else {
vehicle.setMotion(vehicle.getMotion().down(gravity));
// NOT IMPLEMENTED: slow fall when in unloaded chunk
}
vehicle.setMotion(vehicle.getMotion().mul(drag, 0.98f, drag));
}
protected boolean shouldApplyFluidJumpBoost(T vehicle, double originalY) {
BoundingBox box = boundingBox.clone();
box.translate(vehicle.getMotion().toDouble().up(0.6f - boundingBox.getBottomCenter().getY() + originalY));
box.expand(-1.0E-7);
BlockPositionIterator iter = vehicle.getSession().getCollisionManager().collidableBlocksIterator(box);
int[] blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), iter);
for (iter.reset(); iter.hasNext(); iter.next()) {
int blockId = blocks[iter.getIteration()];
// 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 getClimbingSpeed(T vehicle, boolean horizontalCollision) {
Vector3f motion = vehicle.getMotion();
return Vector3f.from(
MathUtils.clamp(motion.getX(), -CLIMB_SPEED, CLIMB_SPEED),
horizontalCollision ? 0.2f : Math.max(motion.getY(), -CLIMB_SPEED),
MathUtils.clamp(motion.getZ(), -CLIMB_SPEED, CLIMB_SPEED)
);
}
protected Vector3f getFluidGravity(T vehicle, float gravity, boolean falling) {
Vector3f motion = vehicle.getMotion();
if (vehicle.getFlag(EntityFlag.HAS_GRAVITY) && !vehicle.getFlag(EntityFlag.SPRINTING)) {
float newY = 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;
}
protected Optional<Vector3f> getBlockMovementMultiplier(T vehicle) {
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());
int[] blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), iter);
// Iterate backwards
for (int i = blocks.length - 1; i >= 0; i--) {
String cleanIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blocks[i], BlockMapping.AIR).getCleanJavaIdentifier();
Vector3f multiplier = switch (cleanIdentifier) {
case "minecraft:cobweb" -> Vector3f.from(0.25f, 0.05f, 0.25f);
case "minecraft:powder_snow" -> Vector3f.from(0.9f, 1.5f, 0.9f);
case "minecraft:sweet_berry_bush" -> Vector3f.from(0.8f, 0.75f, 0.8f);
default -> null;
};
if (multiplier != null) {
return Optional.of(multiplier);
}
}
return Optional.empty();
}
protected void applyBlockCollisionEffects(T vehicle) {
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());
int[] blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), iter);
for (iter.reset(); iter.hasNext(); iter.next()) {
int blockId = blocks[iter.getIteration()];
if (BlockStateValues.JAVA_HONEY_BLOCK_ID == blockId) {
onHoneyBlockCollision(vehicle);
} else if (BlockStateValues.JAVA_BUBBLE_COLUMN_DRAG_ID == blockId) {
onBubbleColumnCollision(vehicle, true);
} else if (BlockStateValues.JAVA_BUBBLE_COLUMN_UPWARD_ID == blockId) {
onBubbleColumnCollision(vehicle, false);
}
}
}
protected void onHoneyBlockCollision(T vehicle) {
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(T vehicle, 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()
));
}
protected Vector3f getJumpVelocity(T vehicle) {
return Vector3f.ZERO;
}
/**
* @return True if there was a horizontal collision
*/
protected boolean travel(T vehicle, 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()
);
// TODO: isImmobile? set input to 0 and jump to false
motion = motion.add(getInputVelocity(vehicle, speed));
motion = motion.add(getJumpVelocity(vehicle));
Optional<Vector3f> movementMultiplier = getBlockMovementMultiplier(vehicle);
if (movementMultiplier.isPresent()) {
motion = motion.mul(movementMultiplier.get());
}
Vector3d correctedMovement = vehicle.getSession().getCollisionManager().correctMovement(
motion.toDouble(), boundingBox, vehicle.isOnGround(), vehicle.getStepHeight(), true, vehicle.canWalkOnLava()
);
boundingBox.translate(correctedMovement);
Vector3d newPos = boundingBox.getBottomCenter();
// Non-zero values indicate a collision on that axis
Vector3d moveDiff = motion.toDouble().sub(correctedMovement);
boolean onGround = moveDiff.getY() != 0 && motion.getY() < 0;
boolean horizontalCollision = moveDiff.getX() != 0 || moveDiff.getZ() != 0;
boolean bounced = false;
if (onGround) {
Vector3i landingPos = newPos.sub(0, 0.2f, 0).toInt();
int landingBlockId = getBlockAt(vehicle, landingPos);
if (landingBlockId == BlockStateValues.JAVA_SLIME_BLOCK_ID) {
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 (BlockStateValues.getBedColor(landingBlockId) != -1) { // If bed
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.isPresent()) {
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(vehicle, newPos, onGround);
vehicle.setMotion(motion);
applyBlockCollisionEffects(vehicle);
float velocityMultiplier = getVelocityMultiplier(vehicle);
vehicle.setMotion(vehicle.getMotion().mul(velocityMultiplier, 1.0f, velocityMultiplier));
return horizontalCollision;
}
protected boolean isClimbing(T vehicle) {
if (!vehicle.canClimb()) {
return false;
}
Vector3i blockPos = boundingBox.getBottomCenter().toInt();
int blockId = getBlockAt(vehicle, blockPos);
if (BlockStateValues.isClimbable(blockId)) {
return true;
}
// Check if the vehicle is in an open trapdoor with a ladder of the same direction under it
Optional<Direction> openTrapdoorDirection = BlockStateValues.getOpenTrapdoorDirection(blockId);
if (openTrapdoorDirection.isPresent()) {
Optional<Direction> ladderDirection = BlockStateValues.getLadderDirection(getBlockAt(vehicle, blockPos.down()));
return ladderDirection.isPresent() && ladderDirection.get() == openTrapdoorDirection.get();
}
return false;
}
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;
}
protected Vector3f getInputVelocity(T vehicle, float speed) {
Vector2f input = vehicle.getSession().getPlayerEntity().getVehicleInput();
input = input.mul(0.98f);
input = vehicle.getAdjustedInput(input);
input = normalizeInput(input);
input = input.mul(speed);
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 void moveVehicle(T vehicle, Vector3d javaPos, boolean isOnGround) {
Vector3f bedrockPos = javaPos.toFloat();
float yaw = vehicle.getSession().getPlayerEntity().getYaw();
float pitch = vehicle.getSession().getPlayerEntity().getPitch() * 0.5f;
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId());
if (isOnGround) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
}
vehicle.setOnGround(isOnGround);
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() != yaw) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityDeltaPacket.setYaw(yaw);
vehicle.setYaw(yaw);
}
if (vehicle.getPitch() != pitch) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
moveEntityDeltaPacket.setPitch(pitch);
vehicle.setPitch(pitch);
}
if (vehicle.getHeadYaw() != yaw) { // Same as yaw
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
moveEntityDeltaPacket.setHeadYaw(yaw);
vehicle.setHeadYaw(yaw);
}
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket);
}
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos.getX(), javaPos.getY(), javaPos.getZ(), yaw, pitch);
vehicle.getSession().sendDownstreamPacket(moveVehiclePacket);
vehicle.getSession().setLastVehicleMoveTimestamp(System.currentTimeMillis());
}
protected float getGravity(T vehicle) {
if (!vehicle.getFlag(EntityFlag.HAS_GRAVITY)) {
return 0;
}
if (vehicle.getMotion().getY() <= 0 && slowFalling) {
return 0.01f;
}
return 0.08f;
}
protected Optional<Vector3i> getSupportingBlockPos(T vehicle) {
Vector3i result = null;
if (vehicle.isOnGround()) {
Vector3d bottomCenter = boundingBox.getBottomCenter();
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());
int[] blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), iter);
double minDistance = Double.MAX_VALUE;
for (iter.reset(); iter.hasNext(); iter.next()) {
Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ());
int blockId = blocks[iter.getIteration()];
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 = bottomCenter.distanceSquared(blockPos.toDouble().add(0.5f, 0.5f, 0.5f));
if (distance <= minDistance) {
minDistance = distance;
result = blockPos;
}
}
}
}
return Optional.ofNullable(result);
}
protected Vector3i getVelocityAffectingPos(T vehicle) {
Optional<Vector3i> supportingBlockPos = getSupportingBlockPos(vehicle);
if (supportingBlockPos.isPresent()) {
Vector3i blockPos = supportingBlockPos.get();
return Vector3i.from(blockPos.getX(), Math.floor(boundingBox.getBottomCenter().getY() - 0.500001f), blockPos.getZ());
} else {
return vehicle.getPosition().sub(0, 0.500001f, 0).toInt();
}
}
protected float getVelocityMultiplier(T vehicle) {
int blockId = getBlockAt(vehicle, boundingBox.getBottomCenter().toInt());
if (BlockStateValues.getWaterLevel(blockId) != -1 // getWaterLevel does not include waterlogged blocks
|| blockId == BlockStateValues.JAVA_BUBBLE_COLUMN_DRAG_ID
|| blockId == BlockStateValues.JAVA_BUBBLE_COLUMN_UPWARD_ID) {
return 1.0f;
}
if (blockId == BlockStateValues.JAVA_SOUL_SAND_ID || blockId == BlockStateValues.JAVA_HONEY_BLOCK_ID) {
return 0.4f;
}
blockId = getBlockAt(vehicle, getVelocityAffectingPos(vehicle));
if (blockId == BlockStateValues.JAVA_SOUL_SAND_ID || blockId == BlockStateValues.JAVA_HONEY_BLOCK_ID) {
return 0.4f;
}
return 1.0f;
}
protected float getJumpVelocityMultiplier(T vehicle) {
int blockId = getBlockAt(vehicle, boundingBox.getBottomCenter().toInt());
if (blockId == BlockStateValues.JAVA_HONEY_BLOCK_ID) {
return 0.5f;
}
blockId = getBlockAt(vehicle, getVelocityAffectingPos(vehicle));
if (blockId == BlockStateValues.JAVA_HONEY_BLOCK_ID) {
return 0.5f;
}
return 1.0f;
}
}

Datei anzeigen

@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.jetbrains.annotations.Range;
@ -62,6 +63,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();
}

Datei anzeigen

@ -35,8 +35,10 @@ import java.util.Map;
* 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.
*/
public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale) {
public record JavaDimension(int minY, int maxY, boolean piglinSafe, boolean ultraWarm, double worldCoordinateScale) {
public static void load(CompoundTag tag, Map<String, JavaDimension> map) {
for (CompoundTag dimension : JavaCodecUtil.iterateAsTag(tag.get("minecraft:dimension_type"))) {
@ -47,10 +49,12 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world
// Set if piglins/hoglins should shake
boolean piglinSafe = ((Number) elements.get("piglin_safe").getValue()).byteValue() != (byte) 0;
// Entities in lava move faster in ultrawarm dimensions
boolean ultraWarm = ((Number) elements.get("ultrawarm").getValue()).byteValue() != (byte) 0;
// Load world coordinate scale for the world border
double coordinateScale = ((Number) elements.get("coordinate_scale").getValue()).doubleValue();
map.put((String) dimension.get("name").getValue(), new JavaDimension(minY, maxY, piglinSafe, coordinateScale));
map.put((String) dimension.get("name").getValue(), new JavaDimension(minY, maxY, piglinSafe, ultraWarm, coordinateScale));
}
}
}

Datei anzeigen

@ -39,6 +39,7 @@ import org.geysermc.geyser.util.collection.FixedInt2IntMap;
import org.geysermc.geyser.util.collection.LecternHasBookMap;
import java.util.Locale;
import java.util.Optional;
/**
* Used for block entities if the Java block state contains Bedrock block information.
@ -66,6 +67,10 @@ public final class BlockStateValues {
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap();
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
private static final Int2IntMap LAVA_LEVEL = new Int2IntOpenHashMap();
private static final IntSet ALL_CLIMBABLE = new IntOpenHashSet();
private static final Int2ObjectMap<Direction> LADDER_DIRECTION = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<Direction> OPEN_TRAPDOOR_DIRECTION = new Int2ObjectOpenHashMap<>();
public static final int JAVA_AIR_ID = 0;
@ -76,8 +81,12 @@ public final class BlockStateValues {
public static int JAVA_SLIME_BLOCK_ID;
public static int JAVA_SPAWNER_ID;
public static int JAVA_WATER_ID;
public static int JAVA_BUBBLE_COLUMN_DRAG_ID;
public static int JAVA_BUBBLE_COLUMN_UPWARD_ID;
public static int JAVA_SOUL_SAND_ID;
public static int JAVA_ICE_ID;
public static final int NUM_WATER_LEVELS = 9;
public static final int NUM_FLUID_LEVELS = 9;
/**
* Determines if the block state contains Bedrock block information
@ -193,6 +202,13 @@ public final class BlockStateValues {
return;
}
if (javaId.startsWith("minecraft:lava") && !javaId.contains("cauldron")) {
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
int level = Integer.parseInt(strLevel);
LAVA_LEVEL.put(javaBlockState, level);
return;
}
if (javaId.startsWith("minecraft:jigsaw[orientation=")) {
String blockStateData = javaId.substring(javaId.indexOf("orientation=") + "orientation=".length(), javaId.lastIndexOf('_'));
Direction direction = Direction.valueOf(blockStateData.toUpperCase(Locale.ROOT));
@ -208,6 +224,18 @@ public final class BlockStateValues {
if (javaId.contains("_cauldron") && !javaId.contains("water_")) {
NON_WATER_CAULDRONS.add(javaBlockState);
}
if (javaId.contains("vine") || javaId.startsWith("minecraft:ladder") || javaId.startsWith("minecraft:scaffolding")) {
ALL_CLIMBABLE.add(javaBlockState);
}
if (javaId.startsWith("minecraft:ladder")) {
LADDER_DIRECTION.put(javaBlockState, getBlockDirection(javaId));
}
if (javaId.contains("_trapdoor[") && javaId.contains("open=true")) {
OPEN_TRAPDOOR_DIRECTION.put(javaBlockState, getBlockDirection(javaId));
}
}
/**
@ -466,6 +494,24 @@ public final class BlockStateValues {
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
}
/**
* Get the type of fluid from the block state.
*
* @param state BlockState of the block
* @return The type of fluid
*/
public static Fluid getFluid(int state) {
if (WATER_LEVEL.containsKey(state) || BlockRegistries.WATERLOGGED.get().get(state)) {
return Fluid.WATER;
}
if (LAVA_LEVEL.containsKey(state)) {
return Fluid.LAVA;
}
return Fluid.EMPTY;
}
/**
* Get the level of water from the block state.
*
@ -490,7 +536,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;
@ -500,6 +546,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) {
return LAVA_LEVEL.getOrDefault(state, -1);
}
/**
* 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;
}
public static boolean isClimbable(int state) {
return ALL_CLIMBABLE.contains(state);
}
/**
* Get the slipperiness of a block.
* This is used in ItemEntity to calculate the friction on an item as it slides across the ground
@ -517,6 +596,28 @@ public final class BlockStateValues {
};
}
/**
* Get the direction of a ladder.
* Used when determining if an entity is climbing
*
* @param state BlockState of the block
* @return The ladder's direction, or empty if not a ladder
*/
public static Optional<Direction> getLadderDirection(int state) {
return Optional.ofNullable(LADDER_DIRECTION.get(state));
}
/**
* Get the direction of an open trapdoor.
* Used when determining if an entity is climbing
*
* @param state BlockState of the block
* @return The open trapdoor's direction, or empty if not an open trapdoor
*/
public static Optional<Direction> getOpenTrapdoorDirection(int state) {
return Optional.ofNullable(OPEN_TRAPDOOR_DIRECTION.get(state));
}
private static Direction getBlockDirection(String javaId) {
if (javaId.contains("down")) {
return Direction.DOWN;

Datei anzeigen

@ -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
}

Datei anzeigen

@ -57,10 +57,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())) &&
@ -91,9 +105,9 @@ public class BoundingBox implements Cloneable {
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 > CollisionManager.COLLISION_TOLERANCE;
case Y -> (sizeY + otherBox.getSizeY()) - Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 > CollisionManager.COLLISION_TOLERANCE;
case Z -> (sizeZ + otherBox.getSizeZ()) - Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 > CollisionManager.COLLISION_TOLERANCE;
};
}

Datei anzeigen

@ -41,7 +41,9 @@ import org.geysermc.geyser.level.block.BlockStateValues;
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;
@ -49,6 +51,8 @@ import java.text.DecimalFormatSymbols;
import java.util.Locale;
public class CollisionManager {
public static final BlockCollision SOLID_COLLISION = new SolidCollision("");
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;
@ -264,13 +268,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();
@ -279,14 +283,14 @@ 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);
Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, boundingBox, checkWorld, walkOnLava);
boundingBox.translate(0, -maxStepUp, 0);
if (squaredHorizontalLength(adjustedStepUpMovement) > squaredHorizontalLength(stepUpMovement)) {
@ -297,7 +301,7 @@ public class CollisionManager {
if (squaredHorizontalLength(stepUpMovement) > squaredHorizontalLength(adjustedMovement)) {
boundingBox.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();
double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), boundingBox, checkWorld, walkOnLava).getY();
boundingBox.translate(-stepUpMovement.getX(), -stepUpMovement.getY(), -stepUpMovement.getZ());
stepUpMovement = stepUpMovement.up(verticalMovement);
@ -311,7 +315,7 @@ 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();
@ -320,20 +324,20 @@ public class CollisionManager {
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);
}
@ -341,13 +345,21 @@ public class CollisionManager {
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;
if (walkOnLava) {
blockCollision = getCollisionLavaWalking(blockId, y, boundingBox);
} else {
blockCollision = BlockUtils.getCollision(blockId);
}
if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) {
offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset);
}
@ -360,6 +372,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
@ -412,7 +434,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);

Datei anzeigen

@ -39,6 +39,7 @@ public enum Direction {
EAST(4, Vector3i.UNIT_X, Axis.X, com.github.steveice10.mc.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

Datei anzeigen

@ -270,6 +270,10 @@ public final class BlockRegistryPopulator {
int spawnerRuntimeId = -1;
int uniqueJavaId = -1;
int waterRuntimeId = -1;
int bubbleColumnUpwardRuntimeId = -1;
int bubbleColumnDragRuntimeId = -1;
int soulSandRuntimeId = -1;
int iceRuntimeId = -1;
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocksJson.fields();
while (blocksIterator.hasNext()) {
javaRuntimeId++;
@ -357,6 +361,16 @@ public final class BlockRegistryPopulator {
honeyBlockRuntimeId = javaRuntimeId;
} else if (javaId.equals("minecraft:slime_block")) {
slimeBlockRuntimeId = javaRuntimeId;
} else if (javaId.startsWith("minecraft:bubble_column")) {
if (javaId.contains("drag=true")) {
bubbleColumnDragRuntimeId = javaRuntimeId;
} else {
bubbleColumnUpwardRuntimeId = javaRuntimeId;
}
} else if (javaId.equals("minecraft:soul_sand")) {
soulSandRuntimeId = javaRuntimeId;
} else if (javaId.equals("minecraft:ice")) {
iceRuntimeId = javaRuntimeId;
}
}
@ -395,6 +409,26 @@ public final class BlockRegistryPopulator {
}
BlockStateValues.JAVA_WATER_ID = waterRuntimeId;
if (bubbleColumnDragRuntimeId == -1) {
throw new AssertionError("Unable to find drag bubble column in palette");
}
BlockStateValues.JAVA_BUBBLE_COLUMN_DRAG_ID = bubbleColumnDragRuntimeId;
if (bubbleColumnUpwardRuntimeId == -1) {
throw new AssertionError("Unable to find upward bubble column in palette");
}
BlockStateValues.JAVA_BUBBLE_COLUMN_UPWARD_ID = bubbleColumnUpwardRuntimeId;
if (soulSandRuntimeId == -1) {
throw new AssertionError("Unable to find soul sand in palette");
}
BlockStateValues.JAVA_SOUL_SAND_ID = soulSandRuntimeId;
if (iceRuntimeId == -1) {
throw new AssertionError("Unable to find ice in palette");
}
BlockStateValues.JAVA_ICE_ID = iceRuntimeId;
BlockRegistries.CLEAN_JAVA_IDENTIFIERS.set(cleanIdentifiers.toArray(new String[0]));
BLOCKS_JSON = blocksJson;

Datei anzeigen

@ -166,4 +166,15 @@ public class BlockCollision {
}
return offset;
}
public boolean isBelow(double y, BoundingBox boundingBox) {
double minY = boundingBox.getMiddleY() - boundingBox.getSizeY() / 2;
for (BoundingBox b : boundingBoxes) {
double offset = y + b.getMiddleY() + b.getSizeY() / 2 - minY;
if (offset > CollisionManager.COLLISION_TOLERANCE) {
return false;
}
}
return true;
}
}

Datei anzeigen

@ -52,6 +52,8 @@ public class BedrockPlayerInputTranslator extends PacketTranslator<PlayerInputPa
session.sendDownstreamPacket(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();

Datei anzeigen

@ -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());

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.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);
}
}

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.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);

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.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());
}
}

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.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,11 +40,15 @@ public class JavaUpdateMobEffectTranslator extends PacketTranslator<ClientboundU
@Override
public void translate(GeyserSession session, ClientboundUpdateMobEffectPacket packet) {
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
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());
}
if (entity == null)
return;
int duration = packet.getDuration();
if (duration < 0) {