package net.minecraft.world.entity.vehicle; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.mojang.datafixers.util.Pair; import java.util.Map; import java.util.Optional; import net.minecraft.Util; import net.minecraft.BlockUtil.FoundRectangle; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.network.syncher.SynchedEntityData.Builder; import net.minecraft.resources.RegistryOps; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BlockTags; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.InterpolationHandler; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.entity.npc.WanderingTrader; import net.minecraft.world.entity.player.Player; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.BaseRailBlock; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.PoweredRailBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.RailShape; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public abstract class AbstractMinecart extends VehicleEntity { private static final Vec3 LOWERED_PASSENGER_ATTACHMENT = new Vec3(0.0, 0.0, 0.0); private static final EntityDataAccessor> DATA_ID_CUSTOM_DISPLAY_BLOCK = SynchedEntityData.defineId( AbstractMinecart.class, EntityDataSerializers.OPTIONAL_BLOCK_STATE ); private static final EntityDataAccessor DATA_ID_DISPLAY_OFFSET = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.INT); private static final ImmutableMap> POSE_DISMOUNT_HEIGHTS = ImmutableMap.of( Pose.STANDING, ImmutableList.of(0, 1, -1), Pose.CROUCHING, ImmutableList.of(0, 1, -1), Pose.SWIMMING, ImmutableList.of(0, 1) ); protected static final float WATER_SLOWDOWN_FACTOR = 0.95F; private static final boolean DEFAULT_FLIPPED_ROTATION = false; private boolean onRails; private boolean flipped = false; private final MinecartBehavior behavior; private static final Map> EXITS = Maps.newEnumMap( Util.make( () -> { Vec3i vec3i = Direction.WEST.getUnitVec3i(); Vec3i vec3i2 = Direction.EAST.getUnitVec3i(); Vec3i vec3i3 = Direction.NORTH.getUnitVec3i(); Vec3i vec3i4 = Direction.SOUTH.getUnitVec3i(); Vec3i vec3i5 = vec3i.below(); Vec3i vec3i6 = vec3i2.below(); Vec3i vec3i7 = vec3i3.below(); Vec3i vec3i8 = vec3i4.below(); return ImmutableMap.of( RailShape.NORTH_SOUTH, Pair.of(vec3i3, vec3i4), RailShape.EAST_WEST, Pair.of(vec3i, vec3i2), RailShape.ASCENDING_EAST, Pair.of(vec3i5, vec3i2), RailShape.ASCENDING_WEST, Pair.of(vec3i, vec3i6), RailShape.ASCENDING_NORTH, Pair.of(vec3i3, vec3i8), RailShape.ASCENDING_SOUTH, Pair.of(vec3i7, vec3i4), RailShape.SOUTH_EAST, Pair.of(vec3i4, vec3i2), RailShape.SOUTH_WEST, Pair.of(vec3i4, vec3i), RailShape.NORTH_WEST, Pair.of(vec3i3, vec3i), RailShape.NORTH_EAST, Pair.of(vec3i3, vec3i2) ); } ) ); protected AbstractMinecart(EntityType entityType, Level level) { super(entityType, level); this.blocksBuilding = true; if (useExperimentalMovement(level)) { this.behavior = new NewMinecartBehavior(this); } else { this.behavior = new OldMinecartBehavior(this); } } protected AbstractMinecart(EntityType entityType, Level level, double x, double y, double z) { this(entityType, level); this.setInitialPos(x, y, z); } public void setInitialPos(double x, double y, double z) { this.setPos(x, y, z); this.xo = x; this.yo = y; this.zo = z; } @Nullable public static T createMinecart( Level level, double x, double y, double z, EntityType type, EntitySpawnReason spawnReason, ItemStack spawnedFrom, @Nullable Player player ) { T abstractMinecart = (T)type.create(level, spawnReason); if (abstractMinecart != null) { abstractMinecart.setInitialPos(x, y, z); EntityType.createDefaultStackConfig(level, spawnedFrom, player).accept(abstractMinecart); if (abstractMinecart.getBehavior() instanceof NewMinecartBehavior newMinecartBehavior) { BlockPos blockPos = abstractMinecart.getCurrentBlockPosOrRailBelow(); BlockState blockState = level.getBlockState(blockPos); newMinecartBehavior.adjustToRails(blockPos, blockState, true); } } return abstractMinecart; } public MinecartBehavior getBehavior() { return this.behavior; } @Override protected Entity.MovementEmission getMovementEmission() { return Entity.MovementEmission.EVENTS; } @Override protected void defineSynchedData(Builder builder) { super.defineSynchedData(builder); builder.define(DATA_ID_CUSTOM_DISPLAY_BLOCK, Optional.empty()); builder.define(DATA_ID_DISPLAY_OFFSET, this.getDefaultDisplayOffset()); } @Override public boolean canCollideWith(Entity entity) { return AbstractBoat.canVehicleCollide(this, entity); } @Override public boolean isPushable() { return true; } @Override public Vec3 getRelativePortalPosition(Direction.Axis axis, FoundRectangle portal) { return LivingEntity.resetForwardDirectionOfRelativePortalPosition(super.getRelativePortalPosition(axis, portal)); } @Override protected Vec3 getPassengerAttachmentPoint(Entity entity, EntityDimensions dimensions, float partialTick) { boolean bl = entity instanceof Villager || entity instanceof WanderingTrader; return bl ? LOWERED_PASSENGER_ATTACHMENT : super.getPassengerAttachmentPoint(entity, dimensions, partialTick); } @Override public Vec3 getDismountLocationForPassenger(LivingEntity passenger) { Direction direction = this.getMotionDirection(); if (direction.getAxis() == Direction.Axis.Y) { return super.getDismountLocationForPassenger(passenger); } else { int[][] is = DismountHelper.offsetsForDirection(direction); BlockPos blockPos = this.blockPosition(); BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); ImmutableList immutableList = passenger.getDismountPoses(); for (Pose pose : immutableList) { EntityDimensions entityDimensions = passenger.getDimensions(pose); float f = Math.min(entityDimensions.width(), 1.0F) / 2.0F; for (int i : POSE_DISMOUNT_HEIGHTS.get(pose)) { for (int[] js : is) { mutableBlockPos.set(blockPos.getX() + js[0], blockPos.getY() + i, blockPos.getZ() + js[1]); double d = this.level() .getBlockFloorHeight( DismountHelper.nonClimbableShape(this.level(), mutableBlockPos), () -> DismountHelper.nonClimbableShape(this.level(), mutableBlockPos.below()) ); if (DismountHelper.isBlockFloorValid(d)) { AABB aABB = new AABB(-f, 0.0, -f, f, entityDimensions.height(), f); Vec3 vec3 = Vec3.upFromBottomCenterOf(mutableBlockPos, d); if (DismountHelper.canDismountTo(this.level(), passenger, aABB.move(vec3))) { passenger.setPose(pose); return vec3; } } } } } double e = this.getBoundingBox().maxY; mutableBlockPos.set((double)blockPos.getX(), e, (double)blockPos.getZ()); for (Pose pose2 : immutableList) { double g = passenger.getDimensions(pose2).height(); int j = Mth.ceil(e - mutableBlockPos.getY() + g); double h = DismountHelper.findCeilingFrom(mutableBlockPos, j, blockPosx -> this.level().getBlockState(blockPosx).getCollisionShape(this.level(), blockPosx)); if (e + g <= h) { passenger.setPose(pose2); break; } } return super.getDismountLocationForPassenger(passenger); } } @Override protected float getBlockSpeedFactor() { BlockState blockState = this.level().getBlockState(this.blockPosition()); return blockState.is(BlockTags.RAILS) ? 1.0F : super.getBlockSpeedFactor(); } @Override public void animateHurt(float yaw) { this.setHurtDir(-this.getHurtDir()); this.setHurtTime(10); this.setDamage(this.getDamage() + this.getDamage() * 10.0F); } @Override public boolean isPickable() { return !this.isRemoved(); } public static Pair exits(RailShape shape) { return (Pair)EXITS.get(shape); } @Override public Direction getMotionDirection() { return this.behavior.getMotionDirection(); } @Override protected double getDefaultGravity() { return this.isInWater() ? 0.005 : 0.04; } @Override public void tick() { if (this.getHurtTime() > 0) { this.setHurtTime(this.getHurtTime() - 1); } if (this.getDamage() > 0.0F) { this.setDamage(this.getDamage() - 1.0F); } this.checkBelowWorld(); this.handlePortal(); this.behavior.tick(); this.updateInWaterStateAndDoFluidPushing(); if (this.isInLava()) { this.lavaIgnite(); this.lavaHurt(); this.fallDistance *= 0.5; } this.firstTick = false; } public boolean isFirstTick() { return this.firstTick; } public BlockPos getCurrentBlockPosOrRailBelow() { int i = Mth.floor(this.getX()); int j = Mth.floor(this.getY()); int k = Mth.floor(this.getZ()); if (useExperimentalMovement(this.level())) { double d = this.getY() - 0.1 - 1.0E-5F; if (this.level().getBlockState(BlockPos.containing(i, d, k)).is(BlockTags.RAILS)) { j = Mth.floor(d); } } else if (this.level().getBlockState(new BlockPos(i, j - 1, k)).is(BlockTags.RAILS)) { j--; } return new BlockPos(i, j, k); } protected double getMaxSpeed(ServerLevel level) { return this.behavior.getMaxSpeed(level); } /** * Called every tick the minecart is on an activator rail. */ public void activateMinecart(int x, int y, int z, boolean powered) { } @Override public void lerpPositionAndRotationStep(int steps, double targetX, double targetY, double targetZ, double targetYRot, double targetXRot) { super.lerpPositionAndRotationStep(steps, targetX, targetY, targetZ, targetYRot, targetXRot); } @Override public void applyGravity() { super.applyGravity(); } @Override public void reapplyPosition() { super.reapplyPosition(); } @Override public boolean updateInWaterStateAndDoFluidPushing() { return super.updateInWaterStateAndDoFluidPushing(); } @Override public Vec3 getKnownMovement() { return this.behavior.getKnownMovement(super.getKnownMovement()); } @Override public InterpolationHandler getInterpolation() { return this.behavior.getInterpolation(); } @Override public void lerpMotion(double x, double y, double z) { this.behavior.lerpMotion(x, y, z); } protected void moveAlongTrack(ServerLevel level) { this.behavior.moveAlongTrack(level); } protected void comeOffTrack(ServerLevel level) { double d = this.getMaxSpeed(level); Vec3 vec3 = this.getDeltaMovement(); this.setDeltaMovement(Mth.clamp(vec3.x, -d, d), vec3.y, Mth.clamp(vec3.z, -d, d)); if (this.onGround()) { this.setDeltaMovement(this.getDeltaMovement().scale(0.5)); } this.move(MoverType.SELF, this.getDeltaMovement()); if (!this.onGround()) { this.setDeltaMovement(this.getDeltaMovement().scale(0.95)); } } protected double makeStepAlongTrack(BlockPos pos, RailShape railShape, double speed) { return this.behavior.stepAlongTrack(pos, railShape, speed); } @Override public void move(MoverType type, Vec3 movement) { if (useExperimentalMovement(this.level())) { Vec3 vec3 = this.position().add(movement); super.move(type, movement); boolean bl = this.behavior.pushAndPickupEntities(); if (bl) { super.move(type, vec3.subtract(this.position())); } if (type.equals(MoverType.PISTON)) { this.onRails = false; } } else { super.move(type, movement); this.applyEffectsFromBlocks(); } } @Override public void applyEffectsFromBlocks() { if (!useExperimentalMovement(this.level())) { this.applyEffectsFromBlocks(this.position(), this.position()); } else { super.applyEffectsFromBlocks(); } } @Override public boolean isOnRails() { return this.onRails; } public void setOnRails(boolean onRails) { this.onRails = onRails; } public boolean isFlipped() { return this.flipped; } public void setFlipped(boolean flipped) { this.flipped = flipped; } public Vec3 getRedstoneDirection(BlockPos pos) { BlockState blockState = this.level().getBlockState(pos); if (blockState.is(Blocks.POWERED_RAIL) && (Boolean)blockState.getValue(PoweredRailBlock.POWERED)) { RailShape railShape = blockState.getValue(((BaseRailBlock)blockState.getBlock()).getShapeProperty()); if (railShape == RailShape.EAST_WEST) { if (this.isRedstoneConductor(pos.west())) { return new Vec3(1.0, 0.0, 0.0); } if (this.isRedstoneConductor(pos.east())) { return new Vec3(-1.0, 0.0, 0.0); } } else if (railShape == RailShape.NORTH_SOUTH) { if (this.isRedstoneConductor(pos.north())) { return new Vec3(0.0, 0.0, 1.0); } if (this.isRedstoneConductor(pos.south())) { return new Vec3(0.0, 0.0, -1.0); } } return Vec3.ZERO; } else { return Vec3.ZERO; } } public boolean isRedstoneConductor(BlockPos pos) { return this.level().getBlockState(pos).isRedstoneConductor(this.level(), pos); } protected Vec3 applyNaturalSlowdown(Vec3 speed) { double d = this.behavior.getSlowdownFactor(); Vec3 vec3 = speed.multiply(d, 0.0, d); if (this.isInWater()) { vec3 = vec3.scale(0.95F); } return vec3; } @Override protected void readAdditionalSaveData(CompoundTag tag) { RegistryOps registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE); this.setCustomDisplayBlockState(tag.read("DisplayState", BlockState.CODEC, registryOps)); this.setDisplayOffset(tag.getIntOr("DisplayOffset", this.getDefaultDisplayOffset())); this.flipped = tag.getBooleanOr("FlippedRotation", false); this.firstTick = tag.getBooleanOr("HasTicked", false); } @Override protected void addAdditionalSaveData(CompoundTag tag) { this.getCustomDisplayBlockState().ifPresent(blockState -> { RegistryOps registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE); tag.store("DisplayState", BlockState.CODEC, registryOps, blockState); }); int i = this.getDisplayOffset(); if (i != this.getDefaultDisplayOffset()) { tag.putInt("DisplayOffset", i); } tag.putBoolean("FlippedRotation", this.flipped); tag.putBoolean("HasTicked", this.firstTick); } @Override public void push(Entity entity) { if (!this.level().isClientSide) { if (!entity.noPhysics && !this.noPhysics) { if (!this.hasPassenger(entity)) { double d = entity.getX() - this.getX(); double e = entity.getZ() - this.getZ(); double f = d * d + e * e; if (f >= 1.0E-4F) { f = Math.sqrt(f); d /= f; e /= f; double g = 1.0 / f; if (g > 1.0) { g = 1.0; } d *= g; e *= g; d *= 0.1F; e *= 0.1F; d *= 0.5; e *= 0.5; if (entity instanceof AbstractMinecart abstractMinecart) { this.pushOtherMinecart(abstractMinecart, d, e); } else { this.push(-d, 0.0, -e); entity.push(d / 4.0, 0.0, e / 4.0); } } } } } } private void pushOtherMinecart(AbstractMinecart otherMinecart, double deltaX, double deltaZ) { double d; double e; if (useExperimentalMovement(this.level())) { d = this.getDeltaMovement().x; e = this.getDeltaMovement().z; } else { d = otherMinecart.getX() - this.getX(); e = otherMinecart.getZ() - this.getZ(); } Vec3 vec3 = new Vec3(d, 0.0, e).normalize(); Vec3 vec32 = new Vec3(Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)), 0.0, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0))).normalize(); double f = Math.abs(vec3.dot(vec32)); if (!(f < 0.8F) || useExperimentalMovement(this.level())) { Vec3 vec33 = this.getDeltaMovement(); Vec3 vec34 = otherMinecart.getDeltaMovement(); if (otherMinecart.isFurnace() && !this.isFurnace()) { this.setDeltaMovement(vec33.multiply(0.2, 1.0, 0.2)); this.push(vec34.x - deltaX, 0.0, vec34.z - deltaZ); otherMinecart.setDeltaMovement(vec34.multiply(0.95, 1.0, 0.95)); } else if (!otherMinecart.isFurnace() && this.isFurnace()) { otherMinecart.setDeltaMovement(vec34.multiply(0.2, 1.0, 0.2)); otherMinecart.push(vec33.x + deltaX, 0.0, vec33.z + deltaZ); this.setDeltaMovement(vec33.multiply(0.95, 1.0, 0.95)); } else { double g = (vec34.x + vec33.x) / 2.0; double h = (vec34.z + vec33.z) / 2.0; this.setDeltaMovement(vec33.multiply(0.2, 1.0, 0.2)); this.push(g - deltaX, 0.0, h - deltaZ); otherMinecart.setDeltaMovement(vec34.multiply(0.2, 1.0, 0.2)); otherMinecart.push(g + deltaX, 0.0, h + deltaZ); } } } public BlockState getDisplayBlockState() { return (BlockState)this.getCustomDisplayBlockState().orElseGet(this::getDefaultDisplayBlockState); } private Optional getCustomDisplayBlockState() { return this.getEntityData().get(DATA_ID_CUSTOM_DISPLAY_BLOCK); } public BlockState getDefaultDisplayBlockState() { return Blocks.AIR.defaultBlockState(); } public int getDisplayOffset() { return this.getEntityData().get(DATA_ID_DISPLAY_OFFSET); } public int getDefaultDisplayOffset() { return 6; } public void setCustomDisplayBlockState(Optional customDisplayBlockState) { this.getEntityData().set(DATA_ID_CUSTOM_DISPLAY_BLOCK, customDisplayBlockState); } public void setDisplayOffset(int displayOffset) { this.getEntityData().set(DATA_ID_DISPLAY_OFFSET, displayOffset); } public static boolean useExperimentalMovement(Level level) { return level.enabledFeatures().contains(FeatureFlags.MINECART_IMPROVEMENTS); } @Override public abstract ItemStack getPickResult(); public boolean isRideable() { return false; } public boolean isFurnace() { return false; } }