minecraft-src/net/minecraft/world/entity/vehicle/Boat.java
2025-07-04 01:41:11 +03:00

950 lines
28 KiB
Java

package net.minecraft.world.entity.vehicle;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.function.IntFunction;
import net.minecraft.BlockUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ServerboundPaddleBoatPacket;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.tags.EntityTypeTags;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.ByIdMap;
import net.minecraft.util.Mth;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.VariantHolder;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.WaterAnimal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.WaterlilyBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
public class Boat extends VehicleEntity implements Leashable, VariantHolder<Boat.Type> {
private static final EntityDataAccessor<Integer> DATA_ID_TYPE = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> DATA_ID_PADDLE_LEFT = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> DATA_ID_PADDLE_RIGHT = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> DATA_ID_BUBBLE_TIME = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.INT);
public static final int PADDLE_LEFT = 0;
public static final int PADDLE_RIGHT = 1;
private static final int TIME_TO_EJECT = 60;
private static final float PADDLE_SPEED = (float) (Math.PI / 8);
public static final double PADDLE_SOUND_TIME = (float) (Math.PI / 4);
public static final int BUBBLE_TIME = 60;
private final float[] paddlePositions = new float[2];
private float invFriction;
private float outOfControlTicks;
private float deltaRotation;
private int lerpSteps;
private double lerpX;
private double lerpY;
private double lerpZ;
private double lerpYRot;
private double lerpXRot;
private boolean inputLeft;
private boolean inputRight;
private boolean inputUp;
private boolean inputDown;
private double waterLevel;
private float landFriction;
private Boat.Status status;
private Boat.Status oldStatus;
private double lastYd;
private boolean isAboveBubbleColumn;
private boolean bubbleColumnDirectionIsDown;
private float bubbleMultiplier;
private float bubbleAngle;
private float bubbleAngleO;
@Nullable
private Leashable.LeashData leashData;
public Boat(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.blocksBuilding = true;
}
public Boat(Level level, double x, double y, double z) {
this(EntityType.BOAT, level);
this.setPos(x, y, z);
this.xo = x;
this.yo = y;
this.zo = z;
}
@Override
protected Entity.MovementEmission getMovementEmission() {
return Entity.MovementEmission.EVENTS;
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(DATA_ID_TYPE, Boat.Type.OAK.ordinal());
builder.define(DATA_ID_PADDLE_LEFT, false);
builder.define(DATA_ID_PADDLE_RIGHT, false);
builder.define(DATA_ID_BUBBLE_TIME, 0);
}
@Override
public boolean canCollideWith(Entity entity) {
return canVehicleCollide(this, entity);
}
public static boolean canVehicleCollide(Entity vehicle, Entity entity) {
return (entity.canBeCollidedWith() || entity.isPushable()) && !vehicle.isPassengerOfSameVehicle(entity);
}
@Override
public boolean canBeCollidedWith() {
return true;
}
@Override
public boolean isPushable() {
return true;
}
@Override
public Vec3 getRelativePortalPosition(Direction.Axis axis, BlockUtil.FoundRectangle portal) {
return LivingEntity.resetForwardDirectionOfRelativePortalPosition(super.getRelativePortalPosition(axis, portal));
}
@Override
protected Vec3 getPassengerAttachmentPoint(Entity entity, EntityDimensions dimensions, float partialTick) {
float f = this.getSinglePassengerXOffset();
if (this.getPassengers().size() > 1) {
int i = this.getPassengers().indexOf(entity);
if (i == 0) {
f = 0.2F;
} else {
f = -0.6F;
}
if (entity instanceof Animal) {
f += 0.2F;
}
}
return new Vec3(0.0, this.getVariant() == Boat.Type.BAMBOO ? dimensions.height() * 0.8888889F : dimensions.height() / 3.0F, f)
.yRot(-this.getYRot() * (float) (Math.PI / 180.0));
}
@Override
public void onAboveBubbleCol(boolean downwards) {
if (!this.level().isClientSide) {
this.isAboveBubbleColumn = true;
this.bubbleColumnDirectionIsDown = downwards;
if (this.getBubbleTime() == 0) {
this.setBubbleTime(60);
}
}
this.level()
.addParticle(ParticleTypes.SPLASH, this.getX() + this.random.nextFloat(), this.getY() + 0.7, this.getZ() + this.random.nextFloat(), 0.0, 0.0, 0.0);
if (this.random.nextInt(20) == 0) {
this.level()
.playLocalSound(this.getX(), this.getY(), this.getZ(), this.getSwimSplashSound(), this.getSoundSource(), 1.0F, 0.8F + 0.4F * this.random.nextFloat(), false);
this.gameEvent(GameEvent.SPLASH, this.getControllingPassenger());
}
}
@Override
public void push(Entity entity) {
if (entity instanceof Boat) {
if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) {
super.push(entity);
}
} else if (entity.getBoundingBox().minY <= this.getBoundingBox().minY) {
super.push(entity);
}
}
@Override
public Item getDropItem() {
return switch (this.getVariant()) {
case SPRUCE -> Items.SPRUCE_BOAT;
case BIRCH -> Items.BIRCH_BOAT;
case JUNGLE -> Items.JUNGLE_BOAT;
case ACACIA -> Items.ACACIA_BOAT;
case CHERRY -> Items.CHERRY_BOAT;
case DARK_OAK -> Items.DARK_OAK_BOAT;
case MANGROVE -> Items.MANGROVE_BOAT;
case BAMBOO -> Items.BAMBOO_RAFT;
default -> Items.OAK_BOAT;
};
}
@Override
public void animateHurt(float yaw) {
this.setHurtDir(-this.getHurtDir());
this.setHurtTime(10);
this.setDamage(this.getDamage() * 11.0F);
}
@Override
public boolean isPickable() {
return !this.isRemoved();
}
@Override
public void lerpTo(double x, double y, double z, float yRot, float xRot, int steps) {
this.lerpX = x;
this.lerpY = y;
this.lerpZ = z;
this.lerpYRot = yRot;
this.lerpXRot = xRot;
this.lerpSteps = 10;
}
@Override
public double lerpTargetX() {
return this.lerpSteps > 0 ? this.lerpX : this.getX();
}
@Override
public double lerpTargetY() {
return this.lerpSteps > 0 ? this.lerpY : this.getY();
}
@Override
public double lerpTargetZ() {
return this.lerpSteps > 0 ? this.lerpZ : this.getZ();
}
@Override
public float lerpTargetXRot() {
return this.lerpSteps > 0 ? (float)this.lerpXRot : this.getXRot();
}
@Override
public float lerpTargetYRot() {
return this.lerpSteps > 0 ? (float)this.lerpYRot : this.getYRot();
}
@Override
public Direction getMotionDirection() {
return this.getDirection().getClockWise();
}
@Override
public void tick() {
this.oldStatus = this.status;
this.status = this.getStatus();
if (this.status != Boat.Status.UNDER_WATER && this.status != Boat.Status.UNDER_FLOWING_WATER) {
this.outOfControlTicks = 0.0F;
} else {
this.outOfControlTicks++;
}
if (!this.level().isClientSide && this.outOfControlTicks >= 60.0F) {
this.ejectPassengers();
}
if (this.getHurtTime() > 0) {
this.setHurtTime(this.getHurtTime() - 1);
}
if (this.getDamage() > 0.0F) {
this.setDamage(this.getDamage() - 1.0F);
}
super.tick();
this.tickLerp();
if (this.isControlledByLocalInstance()) {
if (!(this.getFirstPassenger() instanceof Player)) {
this.setPaddleState(false, false);
}
this.floatBoat();
if (this.level().isClientSide) {
this.controlBoat();
this.level().sendPacketToServer(new ServerboundPaddleBoatPacket(this.getPaddleState(0), this.getPaddleState(1)));
}
this.move(MoverType.SELF, this.getDeltaMovement());
} else {
this.setDeltaMovement(Vec3.ZERO);
}
this.tickBubbleColumn();
for (int i = 0; i <= 1; i++) {
if (this.getPaddleState(i)) {
if (!this.isSilent()
&& this.paddlePositions[i] % (float) (Math.PI * 2) <= (float) (Math.PI / 4)
&& (this.paddlePositions[i] + (float) (Math.PI / 8)) % (float) (Math.PI * 2) >= (float) (Math.PI / 4)) {
SoundEvent soundEvent = this.getPaddleSound();
if (soundEvent != null) {
Vec3 vec3 = this.getViewVector(1.0F);
double d = i == 1 ? -vec3.z : vec3.z;
double e = i == 1 ? vec3.x : -vec3.x;
this.level()
.playSound(null, this.getX() + d, this.getY(), this.getZ() + e, soundEvent, this.getSoundSource(), 1.0F, 0.8F + 0.4F * this.random.nextFloat());
}
}
this.paddlePositions[i] = this.paddlePositions[i] + (float) (Math.PI / 8);
} else {
this.paddlePositions[i] = 0.0F;
}
}
this.checkInsideBlocks();
List<Entity> list = this.level().getEntities(this, this.getBoundingBox().inflate(0.2F, -0.01F, 0.2F), EntitySelector.pushableBy(this));
if (!list.isEmpty()) {
boolean bl = !this.level().isClientSide && !(this.getControllingPassenger() instanceof Player);
for (Entity entity : list) {
if (!entity.hasPassenger(this)) {
if (bl
&& this.getPassengers().size() < this.getMaxPassengers()
&& !entity.isPassenger()
&& this.hasEnoughSpaceFor(entity)
&& entity instanceof LivingEntity
&& !(entity instanceof WaterAnimal)
&& !(entity instanceof Player)) {
entity.startRiding(this);
} else {
this.push(entity);
}
}
}
}
}
private void tickBubbleColumn() {
if (this.level().isClientSide) {
int i = this.getBubbleTime();
if (i > 0) {
this.bubbleMultiplier += 0.05F;
} else {
this.bubbleMultiplier -= 0.1F;
}
this.bubbleMultiplier = Mth.clamp(this.bubbleMultiplier, 0.0F, 1.0F);
this.bubbleAngleO = this.bubbleAngle;
this.bubbleAngle = 10.0F * (float)Math.sin(0.5F * (float)this.level().getGameTime()) * this.bubbleMultiplier;
} else {
if (!this.isAboveBubbleColumn) {
this.setBubbleTime(0);
}
int i = this.getBubbleTime();
if (i > 0) {
this.setBubbleTime(--i);
int j = 60 - i - 1;
if (j > 0 && i == 0) {
this.setBubbleTime(0);
Vec3 vec3 = this.getDeltaMovement();
if (this.bubbleColumnDirectionIsDown) {
this.setDeltaMovement(vec3.add(0.0, -0.7, 0.0));
this.ejectPassengers();
} else {
this.setDeltaMovement(vec3.x, this.hasPassenger(entity -> entity instanceof Player) ? 2.7 : 0.6, vec3.z);
}
}
this.isAboveBubbleColumn = false;
}
}
}
@Nullable
protected SoundEvent getPaddleSound() {
switch (this.getStatus()) {
case IN_WATER:
case UNDER_WATER:
case UNDER_FLOWING_WATER:
return SoundEvents.BOAT_PADDLE_WATER;
case ON_LAND:
return SoundEvents.BOAT_PADDLE_LAND;
case IN_AIR:
default:
return null;
}
}
private void tickLerp() {
if (this.isControlledByLocalInstance()) {
this.lerpSteps = 0;
this.syncPacketPositionCodec(this.getX(), this.getY(), this.getZ());
}
if (this.lerpSteps > 0) {
this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot);
this.lerpSteps--;
}
}
public void setPaddleState(boolean left, boolean right) {
this.entityData.set(DATA_ID_PADDLE_LEFT, left);
this.entityData.set(DATA_ID_PADDLE_RIGHT, right);
}
public float getRowingTime(int side, float limbSwing) {
return this.getPaddleState(side) ? Mth.clampedLerp(this.paddlePositions[side] - (float) (Math.PI / 8), this.paddlePositions[side], limbSwing) : 0.0F;
}
@Nullable
@Override
public Leashable.LeashData getLeashData() {
return this.leashData;
}
@Override
public void setLeashData(@Nullable Leashable.LeashData leashData) {
this.leashData = leashData;
}
@Override
public Vec3 getLeashOffset() {
return new Vec3(0.0, 0.88F * this.getEyeHeight(), this.getBbWidth() * 0.64F);
}
@Override
public void elasticRangeLeashBehaviour(Entity leashHolder, float distance) {
Vec3 vec3 = leashHolder.position().subtract(this.position()).normalize().scale(distance - 6.0);
Vec3 vec32 = this.getDeltaMovement();
boolean bl = vec32.dot(vec3) > 0.0;
this.setDeltaMovement(vec32.add(vec3.scale(bl ? 0.15F : 0.2F)));
}
/**
* Determines whether the boat is in water, gliding on land, or in air
*/
private Boat.Status getStatus() {
Boat.Status status = this.isUnderwater();
if (status != null) {
this.waterLevel = this.getBoundingBox().maxY;
return status;
} else if (this.checkInWater()) {
return Boat.Status.IN_WATER;
} else {
float f = this.getGroundFriction();
if (f > 0.0F) {
this.landFriction = f;
return Boat.Status.ON_LAND;
} else {
return Boat.Status.IN_AIR;
}
}
}
public float getWaterLevelAbove() {
AABB aABB = this.getBoundingBox();
int i = Mth.floor(aABB.minX);
int j = Mth.ceil(aABB.maxX);
int k = Mth.floor(aABB.maxY);
int l = Mth.ceil(aABB.maxY - this.lastYd);
int m = Mth.floor(aABB.minZ);
int n = Mth.ceil(aABB.maxZ);
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
label39:
for (int o = k; o < l; o++) {
float f = 0.0F;
for (int p = i; p < j; p++) {
for (int q = m; q < n; q++) {
mutableBlockPos.set(p, o, q);
FluidState fluidState = this.level().getFluidState(mutableBlockPos);
if (fluidState.is(FluidTags.WATER)) {
f = Math.max(f, fluidState.getHeight(this.level(), mutableBlockPos));
}
if (f >= 1.0F) {
continue label39;
}
}
}
if (f < 1.0F) {
return mutableBlockPos.getY() + f;
}
}
return l + 1;
}
/**
* Decides how much the boat should be gliding on the land (based on any slippery blocks)
*/
public float getGroundFriction() {
AABB aABB = this.getBoundingBox();
AABB aABB2 = new AABB(aABB.minX, aABB.minY - 0.001, aABB.minZ, aABB.maxX, aABB.minY, aABB.maxZ);
int i = Mth.floor(aABB2.minX) - 1;
int j = Mth.ceil(aABB2.maxX) + 1;
int k = Mth.floor(aABB2.minY) - 1;
int l = Mth.ceil(aABB2.maxY) + 1;
int m = Mth.floor(aABB2.minZ) - 1;
int n = Mth.ceil(aABB2.maxZ) + 1;
VoxelShape voxelShape = Shapes.create(aABB2);
float f = 0.0F;
int o = 0;
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
for (int p = i; p < j; p++) {
for (int q = m; q < n; q++) {
int r = (p != i && p != j - 1 ? 0 : 1) + (q != m && q != n - 1 ? 0 : 1);
if (r != 2) {
for (int s = k; s < l; s++) {
if (r <= 0 || s != k && s != l - 1) {
mutableBlockPos.set(p, s, q);
BlockState blockState = this.level().getBlockState(mutableBlockPos);
if (!(blockState.getBlock() instanceof WaterlilyBlock)
&& Shapes.joinIsNotEmpty(blockState.getCollisionShape(this.level(), mutableBlockPos).move(p, s, q), voxelShape, BooleanOp.AND)) {
f += blockState.getBlock().getFriction();
o++;
}
}
}
}
}
}
return f / o;
}
private boolean checkInWater() {
AABB aABB = this.getBoundingBox();
int i = Mth.floor(aABB.minX);
int j = Mth.ceil(aABB.maxX);
int k = Mth.floor(aABB.minY);
int l = Mth.ceil(aABB.minY + 0.001);
int m = Mth.floor(aABB.minZ);
int n = Mth.ceil(aABB.maxZ);
boolean bl = false;
this.waterLevel = -Double.MAX_VALUE;
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
for (int o = i; o < j; o++) {
for (int p = k; p < l; p++) {
for (int q = m; q < n; q++) {
mutableBlockPos.set(o, p, q);
FluidState fluidState = this.level().getFluidState(mutableBlockPos);
if (fluidState.is(FluidTags.WATER)) {
float f = p + fluidState.getHeight(this.level(), mutableBlockPos);
this.waterLevel = Math.max(f, this.waterLevel);
bl |= aABB.minY < f;
}
}
}
}
return bl;
}
/**
* Decides whether the boat is currently underwater.
*/
@Nullable
private Boat.Status isUnderwater() {
AABB aABB = this.getBoundingBox();
double d = aABB.maxY + 0.001;
int i = Mth.floor(aABB.minX);
int j = Mth.ceil(aABB.maxX);
int k = Mth.floor(aABB.maxY);
int l = Mth.ceil(d);
int m = Mth.floor(aABB.minZ);
int n = Mth.ceil(aABB.maxZ);
boolean bl = false;
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
for (int o = i; o < j; o++) {
for (int p = k; p < l; p++) {
for (int q = m; q < n; q++) {
mutableBlockPos.set(o, p, q);
FluidState fluidState = this.level().getFluidState(mutableBlockPos);
if (fluidState.is(FluidTags.WATER) && d < mutableBlockPos.getY() + fluidState.getHeight(this.level(), mutableBlockPos)) {
if (!fluidState.isSource()) {
return Boat.Status.UNDER_FLOWING_WATER;
}
bl = true;
}
}
}
}
return bl ? Boat.Status.UNDER_WATER : null;
}
@Override
protected double getDefaultGravity() {
return 0.04;
}
/**
* Update the boat's speed, based on momentum.
*/
private void floatBoat() {
double d = -this.getGravity();
double e = 0.0;
this.invFriction = 0.05F;
if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) {
this.waterLevel = this.getY(1.0);
double f = this.getWaterLevelAbove() - this.getBbHeight() + 0.101;
if (this.level().noCollision(this, this.getBoundingBox().move(0.0, f - this.getY(), 0.0))) {
this.setPos(this.getX(), f, this.getZ());
this.setDeltaMovement(this.getDeltaMovement().multiply(1.0, 0.0, 1.0));
this.lastYd = 0.0;
}
this.status = Boat.Status.IN_WATER;
} else {
if (this.status == Boat.Status.IN_WATER) {
e = (this.waterLevel - this.getY()) / this.getBbHeight();
this.invFriction = 0.9F;
} else if (this.status == Boat.Status.UNDER_FLOWING_WATER) {
d = -7.0E-4;
this.invFriction = 0.9F;
} else if (this.status == Boat.Status.UNDER_WATER) {
e = 0.01F;
this.invFriction = 0.45F;
} else if (this.status == Boat.Status.IN_AIR) {
this.invFriction = 0.9F;
} else if (this.status == Boat.Status.ON_LAND) {
this.invFriction = this.landFriction;
if (this.getControllingPassenger() instanceof Player) {
this.landFriction /= 2.0F;
}
}
Vec3 vec3 = this.getDeltaMovement();
this.setDeltaMovement(vec3.x * this.invFriction, vec3.y + d, vec3.z * this.invFriction);
this.deltaRotation = this.deltaRotation * this.invFriction;
if (e > 0.0) {
Vec3 vec32 = this.getDeltaMovement();
this.setDeltaMovement(vec32.x, (vec32.y + e * (this.getDefaultGravity() / 0.65)) * 0.75, vec32.z);
}
}
}
private void controlBoat() {
if (this.isVehicle()) {
float f = 0.0F;
if (this.inputLeft) {
this.deltaRotation--;
}
if (this.inputRight) {
this.deltaRotation++;
}
if (this.inputRight != this.inputLeft && !this.inputUp && !this.inputDown) {
f += 0.005F;
}
this.setYRot(this.getYRot() + this.deltaRotation);
if (this.inputUp) {
f += 0.04F;
}
if (this.inputDown) {
f -= 0.005F;
}
this.setDeltaMovement(
this.getDeltaMovement().add(Mth.sin(-this.getYRot() * (float) (Math.PI / 180.0)) * f, 0.0, Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) * f)
);
this.setPaddleState(this.inputRight && !this.inputLeft || this.inputUp, this.inputLeft && !this.inputRight || this.inputUp);
}
}
protected float getSinglePassengerXOffset() {
return 0.0F;
}
public boolean hasEnoughSpaceFor(Entity entity) {
return entity.getBbWidth() < this.getBbWidth();
}
@Override
protected void positionRider(Entity passenger, Entity.MoveFunction callback) {
super.positionRider(passenger, callback);
if (!passenger.getType().is(EntityTypeTags.CAN_TURN_IN_BOATS)) {
passenger.setYRot(passenger.getYRot() + this.deltaRotation);
passenger.setYHeadRot(passenger.getYHeadRot() + this.deltaRotation);
this.clampRotation(passenger);
if (passenger instanceof Animal && this.getPassengers().size() == this.getMaxPassengers()) {
int i = passenger.getId() % 2 == 0 ? 90 : 270;
passenger.setYBodyRot(((Animal)passenger).yBodyRot + i);
passenger.setYHeadRot(passenger.getYHeadRot() + i);
}
}
}
@Override
public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
Vec3 vec3 = getCollisionHorizontalEscapeVector(this.getBbWidth() * Mth.SQRT_OF_TWO, passenger.getBbWidth(), passenger.getYRot());
double d = this.getX() + vec3.x;
double e = this.getZ() + vec3.z;
BlockPos blockPos = BlockPos.containing(d, this.getBoundingBox().maxY, e);
BlockPos blockPos2 = blockPos.below();
if (!this.level().isWaterAt(blockPos2)) {
List<Vec3> list = Lists.<Vec3>newArrayList();
double f = this.level().getBlockFloorHeight(blockPos);
if (DismountHelper.isBlockFloorValid(f)) {
list.add(new Vec3(d, blockPos.getY() + f, e));
}
double g = this.level().getBlockFloorHeight(blockPos2);
if (DismountHelper.isBlockFloorValid(g)) {
list.add(new Vec3(d, blockPos2.getY() + g, e));
}
for (Pose pose : passenger.getDismountPoses()) {
for (Vec3 vec32 : list) {
if (DismountHelper.canDismountTo(this.level(), vec32, passenger, pose)) {
passenger.setPose(pose);
return vec32;
}
}
}
}
return super.getDismountLocationForPassenger(passenger);
}
/**
* Applies this boat's yaw to the given entity. Used to update the orientation of its passenger.
*/
protected void clampRotation(Entity entityToUpdate) {
entityToUpdate.setYBodyRot(this.getYRot());
float f = Mth.wrapDegrees(entityToUpdate.getYRot() - this.getYRot());
float g = Mth.clamp(f, -105.0F, 105.0F);
entityToUpdate.yRotO += g - f;
entityToUpdate.setYRot(entityToUpdate.getYRot() + g - f);
entityToUpdate.setYHeadRot(entityToUpdate.getYRot());
}
@Override
public void onPassengerTurned(Entity entityToUpdate) {
this.clampRotation(entityToUpdate);
}
@Override
protected void addAdditionalSaveData(CompoundTag compound) {
this.writeLeashData(compound, this.leashData);
compound.putString("Type", this.getVariant().getSerializedName());
}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {
this.leashData = this.readLeashData(compound);
if (compound.contains("Type", 8)) {
this.setVariant(Boat.Type.byName(compound.getString("Type")));
}
}
@Override
public InteractionResult interact(Player player, InteractionHand hand) {
InteractionResult interactionResult = super.interact(player, hand);
if (interactionResult != InteractionResult.PASS) {
return interactionResult;
} else if (player.isSecondaryUseActive()) {
return InteractionResult.PASS;
} else if (this.outOfControlTicks < 60.0F) {
if (!this.level().isClientSide) {
return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS;
} else {
return InteractionResult.SUCCESS;
}
} else {
return InteractionResult.PASS;
}
}
@Override
public void remove(Entity.RemovalReason reason) {
if (!this.level().isClientSide && reason.shouldDestroy() && this.isLeashed()) {
this.dropLeash(true, true);
}
super.remove(reason);
}
@Override
protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
this.lastYd = this.getDeltaMovement().y;
if (!this.isPassenger()) {
if (onGround) {
if (this.fallDistance > 3.0F) {
if (this.status != Boat.Status.ON_LAND) {
this.resetFallDistance();
return;
}
this.causeFallDamage(this.fallDistance, 1.0F, this.damageSources().fall());
if (!this.level().isClientSide && !this.isRemoved()) {
this.kill();
if (this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
for (int i = 0; i < 3; i++) {
this.spawnAtLocation(this.getVariant().getPlanks());
}
for (int i = 0; i < 2; i++) {
this.spawnAtLocation(Items.STICK);
}
}
}
}
this.resetFallDistance();
} else if (!this.level().getFluidState(this.blockPosition().below()).is(FluidTags.WATER) && y < 0.0) {
this.fallDistance -= (float)y;
}
}
}
public boolean getPaddleState(int side) {
return this.entityData.get(side == 0 ? DATA_ID_PADDLE_LEFT : DATA_ID_PADDLE_RIGHT) && this.getControllingPassenger() != null;
}
private void setBubbleTime(int bubbleTime) {
this.entityData.set(DATA_ID_BUBBLE_TIME, bubbleTime);
}
private int getBubbleTime() {
return this.entityData.get(DATA_ID_BUBBLE_TIME);
}
public float getBubbleAngle(float partialTicks) {
return Mth.lerp(partialTicks, this.bubbleAngleO, this.bubbleAngle);
}
public void setVariant(Boat.Type variant) {
this.entityData.set(DATA_ID_TYPE, variant.ordinal());
}
public Boat.Type getVariant() {
return Boat.Type.byId(this.entityData.get(DATA_ID_TYPE));
}
@Override
protected boolean canAddPassenger(Entity passenger) {
return this.getPassengers().size() < this.getMaxPassengers() && !this.isEyeInFluid(FluidTags.WATER);
}
protected int getMaxPassengers() {
return 2;
}
@Nullable
@Override
public LivingEntity getControllingPassenger() {
return this.getFirstPassenger() instanceof LivingEntity livingEntity ? livingEntity : super.getControllingPassenger();
}
public void setInput(boolean inputLeft, boolean inputRight, boolean inputUp, boolean inputDown) {
this.inputLeft = inputLeft;
this.inputRight = inputRight;
this.inputUp = inputUp;
this.inputDown = inputDown;
}
@Override
protected Component getTypeName() {
return Component.translatable(this.getDropItem().getDescriptionId());
}
@Override
public boolean isUnderWater() {
return this.status == Boat.Status.UNDER_WATER || this.status == Boat.Status.UNDER_FLOWING_WATER;
}
@Override
public ItemStack getPickResult() {
return new ItemStack(this.getDropItem());
}
public static enum Status {
IN_WATER,
UNDER_WATER,
UNDER_FLOWING_WATER,
ON_LAND,
IN_AIR;
}
public static enum Type implements StringRepresentable {
OAK(Blocks.OAK_PLANKS, "oak"),
SPRUCE(Blocks.SPRUCE_PLANKS, "spruce"),
BIRCH(Blocks.BIRCH_PLANKS, "birch"),
JUNGLE(Blocks.JUNGLE_PLANKS, "jungle"),
ACACIA(Blocks.ACACIA_PLANKS, "acacia"),
CHERRY(Blocks.CHERRY_PLANKS, "cherry"),
DARK_OAK(Blocks.DARK_OAK_PLANKS, "dark_oak"),
MANGROVE(Blocks.MANGROVE_PLANKS, "mangrove"),
BAMBOO(Blocks.BAMBOO_PLANKS, "bamboo");
private final String name;
private final Block planks;
public static final StringRepresentable.EnumCodec<Boat.Type> CODEC = StringRepresentable.fromEnum(Boat.Type::values);
private static final IntFunction<Boat.Type> BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
private Type(final Block planks, final String name) {
this.name = name;
this.planks = planks;
}
@Override
public String getSerializedName() {
return this.name;
}
public String getName() {
return this.name;
}
public Block getPlanks() {
return this.planks;
}
public String toString() {
return this.name;
}
/**
* Get a boat type by its enum ordinal
*/
public static Boat.Type byId(int id) {
return (Boat.Type)BY_ID.apply(id);
}
public static Boat.Type byName(String name) {
return (Boat.Type)CODEC.byName(name, OAK);
}
}
}