minecraft-src/net/minecraft/world/entity/animal/camel/Camel.java
2025-07-04 02:00:41 +03:00

661 lines
19 KiB
Java

package net.minecraft.world.entity.animal.camel;
import com.google.common.annotations.VisibleForTesting;
import com.mojang.serialization.Dynamic;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags;
import net.minecraft.util.Mth;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.AnimationState;
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.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.Brain.Provider;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
import net.minecraft.world.entity.ai.control.BodyRotationControl;
import net.minecraft.world.entity.ai.control.LookControl;
import net.minecraft.world.entity.ai.control.MoveControl;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.horse.AbstractHorse;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class Camel extends AbstractHorse {
public static final float BABY_SCALE = 0.45F;
public static final int DASH_COOLDOWN_TICKS = 55;
public static final int MAX_HEAD_Y_ROT = 30;
private static final float RUNNING_SPEED_BONUS = 0.1F;
private static final float DASH_VERTICAL_MOMENTUM = 1.4285F;
private static final float DASH_HORIZONTAL_MOMENTUM = 22.2222F;
private static final int DASH_MINIMUM_DURATION_TICKS = 5;
private static final int SITDOWN_DURATION_TICKS = 40;
private static final int STANDUP_DURATION_TICKS = 52;
private static final int IDLE_MINIMAL_DURATION_TICKS = 80;
private static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
public static final EntityDataAccessor<Boolean> DASH = SynchedEntityData.defineId(Camel.class, EntityDataSerializers.BOOLEAN);
public static final EntityDataAccessor<Long> LAST_POSE_CHANGE_TICK = SynchedEntityData.defineId(Camel.class, EntityDataSerializers.LONG);
public final AnimationState sitAnimationState = new AnimationState();
public final AnimationState sitPoseAnimationState = new AnimationState();
public final AnimationState sitUpAnimationState = new AnimationState();
public final AnimationState idleAnimationState = new AnimationState();
public final AnimationState dashAnimationState = new AnimationState();
private static final EntityDimensions SITTING_DIMENSIONS = EntityDimensions.scalable(EntityType.CAMEL.getWidth(), EntityType.CAMEL.getHeight() - 1.43F)
.withEyeHeight(0.845F);
private int dashCooldown = 0;
private int idleAnimationTimeout = 0;
public Camel(EntityType<? extends Camel> entityType, Level level) {
super(entityType, level);
this.moveControl = new Camel.CamelMoveControl();
this.lookControl = new Camel.CamelLookControl();
GroundPathNavigation groundPathNavigation = (GroundPathNavigation)this.getNavigation();
groundPathNavigation.setCanFloat(true);
groundPathNavigation.setCanWalkOverFences(true);
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putLong("LastPoseTick", this.entityData.get(LAST_POSE_CHANGE_TICK));
}
@Override
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
long l = compound.getLong("LastPoseTick");
if (l < 0L) {
this.setPose(Pose.SITTING);
}
this.resetLastPoseChangeTick(l);
}
public static Builder createAttributes() {
return createBaseHorseAttributes()
.add(Attributes.MAX_HEALTH, 32.0)
.add(Attributes.MOVEMENT_SPEED, 0.09F)
.add(Attributes.JUMP_STRENGTH, 0.42F)
.add(Attributes.STEP_HEIGHT, 1.5);
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(DASH, false);
builder.define(LAST_POSE_CHANGE_TICK, 0L);
}
@Override
public SpawnGroupData finalizeSpawn(
ServerLevelAccessor serverLevelAccessor, DifficultyInstance difficultyInstance, EntitySpawnReason entitySpawnReason, @Nullable SpawnGroupData spawnGroupData
) {
CamelAi.initMemories(this, serverLevelAccessor.getRandom());
this.resetLastPoseChangeTickToFullStand(serverLevelAccessor.getLevel().getGameTime());
return super.finalizeSpawn(serverLevelAccessor, difficultyInstance, entitySpawnReason, spawnGroupData);
}
@Override
protected Provider<Camel> brainProvider() {
return CamelAi.brainProvider();
}
@Override
protected void registerGoals() {
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return CamelAi.makeBrain(this.brainProvider().makeBrain(dynamic));
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
return pose == Pose.SITTING ? SITTING_DIMENSIONS.scale(this.getAgeScale()) : super.getDefaultDimensions(pose);
}
@Override
protected void customServerAiStep(ServerLevel serverLevel) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("camelBrain");
Brain<?> brain = this.getBrain();
((Brain<Camel>)brain).tick(serverLevel, this);
profilerFiller.pop();
profilerFiller.push("camelActivityUpdate");
CamelAi.updateActivity(this);
profilerFiller.pop();
super.customServerAiStep(serverLevel);
}
@Override
public void tick() {
super.tick();
if (this.isDashing() && this.dashCooldown < 50 && (this.onGround() || this.isInLiquid() || this.isPassenger())) {
this.setDashing(false);
}
if (this.dashCooldown > 0) {
this.dashCooldown--;
if (this.dashCooldown == 0) {
this.level().playSound(null, this.blockPosition(), SoundEvents.CAMEL_DASH_READY, SoundSource.NEUTRAL, 1.0F, 1.0F);
}
}
if (this.level().isClientSide()) {
this.setupAnimationStates();
}
if (this.refuseToMove()) {
this.clampHeadRotationToBody();
}
if (this.isCamelSitting() && this.isInWater()) {
this.standUpInstantly();
}
}
private void setupAnimationStates() {
if (this.idleAnimationTimeout <= 0) {
this.idleAnimationTimeout = this.random.nextInt(40) + 80;
this.idleAnimationState.start(this.tickCount);
} else {
this.idleAnimationTimeout--;
}
if (this.isCamelVisuallySitting()) {
this.sitUpAnimationState.stop();
this.dashAnimationState.stop();
if (this.isVisuallySittingDown()) {
this.sitAnimationState.startIfStopped(this.tickCount);
this.sitPoseAnimationState.stop();
} else {
this.sitAnimationState.stop();
this.sitPoseAnimationState.startIfStopped(this.tickCount);
}
} else {
this.sitAnimationState.stop();
this.sitPoseAnimationState.stop();
this.dashAnimationState.animateWhen(this.isDashing(), this.tickCount);
this.sitUpAnimationState.animateWhen(this.isInPoseTransition() && this.getPoseTime() >= 0L, this.tickCount);
}
}
@Override
protected void updateWalkAnimation(float partialTick) {
float f;
if (this.getPose() == Pose.STANDING && !this.dashAnimationState.isStarted()) {
f = Math.min(partialTick * 6.0F, 1.0F);
} else {
f = 0.0F;
}
this.walkAnimation.update(f, 0.2F, this.isBaby() ? 3.0F : 1.0F);
}
@Override
public void travel(Vec3 travelVector) {
if (this.refuseToMove() && this.onGround()) {
this.setDeltaMovement(this.getDeltaMovement().multiply(0.0, 1.0, 0.0));
travelVector = travelVector.multiply(0.0, 1.0, 0.0);
}
super.travel(travelVector);
}
@Override
protected void tickRidden(Player player, Vec3 travelVector) {
super.tickRidden(player, travelVector);
if (player.zza > 0.0F && this.isCamelSitting() && !this.isInPoseTransition()) {
this.standUp();
}
}
public boolean refuseToMove() {
return this.isCamelSitting() || this.isInPoseTransition();
}
@Override
protected float getRiddenSpeed(Player player) {
float f = player.isSprinting() && this.getJumpCooldown() == 0 ? 0.1F : 0.0F;
return (float)this.getAttributeValue(Attributes.MOVEMENT_SPEED) + f;
}
@Override
protected Vec2 getRiddenRotation(LivingEntity entity) {
return this.refuseToMove() ? new Vec2(this.getXRot(), this.getYRot()) : super.getRiddenRotation(entity);
}
@Override
protected Vec3 getRiddenInput(Player player, Vec3 travelVector) {
return this.refuseToMove() ? Vec3.ZERO : super.getRiddenInput(player, travelVector);
}
@Override
public boolean canJump() {
return !this.refuseToMove() && super.canJump();
}
@Override
public void onPlayerJump(int jumpPower) {
if (this.isSaddled() && this.dashCooldown <= 0 && this.onGround()) {
super.onPlayerJump(jumpPower);
}
}
@Override
public boolean canSprint() {
return true;
}
@Override
protected void executeRidersJump(float playerJumpPendingScale, Vec3 travelVector) {
double d = this.getJumpPower();
this.addDeltaMovement(
this.getLookAngle()
.multiply(1.0, 0.0, 1.0)
.normalize()
.scale(22.2222F * playerJumpPendingScale * this.getAttributeValue(Attributes.MOVEMENT_SPEED) * this.getBlockSpeedFactor())
.add(0.0, 1.4285F * playerJumpPendingScale * d, 0.0)
);
this.dashCooldown = 55;
this.setDashing(true);
this.hasImpulse = true;
}
public boolean isDashing() {
return this.entityData.get(DASH);
}
public void setDashing(boolean dashing) {
this.entityData.set(DASH, dashing);
}
@Override
public void handleStartJump(int jumpPower) {
this.makeSound(SoundEvents.CAMEL_DASH);
this.gameEvent(GameEvent.ENTITY_ACTION);
this.setDashing(true);
}
@Override
public void handleStopJump() {
}
@Override
public int getJumpCooldown() {
return this.dashCooldown;
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.CAMEL_AMBIENT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.CAMEL_DEATH;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.CAMEL_HURT;
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
if (state.is(BlockTags.CAMEL_SAND_STEP_SOUND_BLOCKS)) {
this.playSound(SoundEvents.CAMEL_STEP_SAND, 1.0F, 1.0F);
} else {
this.playSound(SoundEvents.CAMEL_STEP, 1.0F, 1.0F);
}
}
@Override
public boolean isFood(ItemStack stack) {
return stack.is(ItemTags.CAMEL_FOOD);
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
if (player.isSecondaryUseActive() && !this.isBaby()) {
this.openCustomInventoryScreen(player);
return InteractionResult.SUCCESS;
} else {
InteractionResult interactionResult = itemStack.interactLivingEntity(player, this, hand);
if (interactionResult.consumesAction()) {
return interactionResult;
} else if (this.isFood(itemStack)) {
return this.fedFood(player, itemStack);
} else {
if (this.getPassengers().size() < 2 && !this.isBaby()) {
this.doPlayerRide(player);
}
return InteractionResult.SUCCESS;
}
}
}
@Override
public boolean handleLeashAtDistance(Entity leashHolder, float distance) {
if (distance > 6.0F && this.isCamelSitting() && !this.isInPoseTransition() && this.canCamelChangePose()) {
this.standUp();
}
return true;
}
public boolean canCamelChangePose() {
return this.wouldNotSuffocateAtTargetPose(this.isCamelSitting() ? Pose.STANDING : Pose.SITTING);
}
@Override
protected boolean handleEating(Player player, ItemStack stack) {
if (!this.isFood(stack)) {
return false;
} else {
boolean bl = this.getHealth() < this.getMaxHealth();
if (bl) {
this.heal(2.0F);
}
boolean bl2 = this.isTamed() && this.getAge() == 0 && this.canFallInLove();
if (bl2) {
this.setInLove(player);
}
boolean bl3 = this.isBaby();
if (bl3) {
this.level().addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), 0.0, 0.0, 0.0);
if (!this.level().isClientSide) {
this.ageUp(10);
}
}
if (!bl && !bl2 && !bl3) {
return false;
} else {
if (!this.isSilent()) {
SoundEvent soundEvent = this.getEatingSound();
if (soundEvent != null) {
this.level()
.playSound(
null, this.getX(), this.getY(), this.getZ(), soundEvent, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F
);
}
}
this.gameEvent(GameEvent.EAT);
return true;
}
}
}
@Override
protected boolean canPerformRearing() {
return false;
}
@Override
public boolean canMate(Animal otherAnimal) {
return otherAnimal != this && otherAnimal instanceof Camel camel && this.canParent() && camel.canParent();
}
@Nullable
public Camel getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return EntityType.CAMEL.create(level, EntitySpawnReason.BREEDING);
}
@Nullable
@Override
protected SoundEvent getEatingSound() {
return SoundEvents.CAMEL_EAT;
}
@Override
protected void actuallyHurt(ServerLevel serverLevel, DamageSource damageSource, float f) {
this.standUpInstantly();
super.actuallyHurt(serverLevel, damageSource, f);
}
@Override
protected Vec3 getPassengerAttachmentPoint(Entity entity, EntityDimensions dimensions, float partialTick) {
int i = Math.max(this.getPassengers().indexOf(entity), 0);
boolean bl = i == 0;
float f = 0.5F;
float g = (float)(this.isRemoved() ? 0.01F : this.getBodyAnchorAnimationYOffset(bl, 0.0F, dimensions, partialTick));
if (this.getPassengers().size() > 1) {
if (!bl) {
f = -0.7F;
}
if (entity instanceof Animal) {
f += 0.2F;
}
}
return new Vec3(0.0, g, f * partialTick).yRot(-this.getYRot() * (float) (Math.PI / 180.0));
}
@Override
public float getAgeScale() {
return this.isBaby() ? 0.45F : 1.0F;
}
private double getBodyAnchorAnimationYOffset(boolean firstPassenger, float partialTick, EntityDimensions dimensions, float scale) {
double d = dimensions.height() - 0.375F * scale;
float f = scale * 1.43F;
float g = f - scale * 0.2F;
float h = f - g;
boolean bl = this.isInPoseTransition();
boolean bl2 = this.isCamelSitting();
if (bl) {
int i = bl2 ? 40 : 52;
int j;
float k;
if (bl2) {
j = 28;
k = firstPassenger ? 0.5F : 0.1F;
} else {
j = firstPassenger ? 24 : 32;
k = firstPassenger ? 0.6F : 0.35F;
}
float l = Mth.clamp((float)this.getPoseTime() + partialTick, 0.0F, (float)i);
boolean bl3 = l < j;
float m = bl3 ? l / j : (l - j) / (i - j);
float n = f - k * g;
d += bl2 ? Mth.lerp(m, bl3 ? f : n, bl3 ? n : h) : Mth.lerp(m, bl3 ? h - f : h - n, bl3 ? h - n : 0.0F);
}
if (bl2 && !bl) {
d += h;
}
return d;
}
@Override
public Vec3 getLeashOffset(float partialTick) {
EntityDimensions entityDimensions = this.getDimensions(this.getPose());
float f = this.getAgeScale();
return new Vec3(0.0, this.getBodyAnchorAnimationYOffset(true, partialTick, entityDimensions, f) - 0.2F * f, entityDimensions.width() * 0.56F);
}
@Override
public int getMaxHeadYRot() {
return 30;
}
@Override
protected boolean canAddPassenger(Entity passenger) {
return this.getPassengers().size() <= 2;
}
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
public boolean isCamelSitting() {
return this.entityData.get(LAST_POSE_CHANGE_TICK) < 0L;
}
public boolean isCamelVisuallySitting() {
return this.getPoseTime() < 0L != this.isCamelSitting();
}
public boolean isInPoseTransition() {
long l = this.getPoseTime();
return l < (this.isCamelSitting() ? 40 : 52);
}
private boolean isVisuallySittingDown() {
return this.isCamelSitting() && this.getPoseTime() < 40L && this.getPoseTime() >= 0L;
}
public void sitDown() {
if (!this.isCamelSitting()) {
this.makeSound(SoundEvents.CAMEL_SIT);
this.setPose(Pose.SITTING);
this.gameEvent(GameEvent.ENTITY_ACTION);
this.resetLastPoseChangeTick(-this.level().getGameTime());
}
}
public void standUp() {
if (this.isCamelSitting()) {
this.makeSound(SoundEvents.CAMEL_STAND);
this.setPose(Pose.STANDING);
this.gameEvent(GameEvent.ENTITY_ACTION);
this.resetLastPoseChangeTick(this.level().getGameTime());
}
}
public void standUpInstantly() {
this.setPose(Pose.STANDING);
this.gameEvent(GameEvent.ENTITY_ACTION);
this.resetLastPoseChangeTickToFullStand(this.level().getGameTime());
}
@VisibleForTesting
public void resetLastPoseChangeTick(long lastPoseChangeTick) {
this.entityData.set(LAST_POSE_CHANGE_TICK, lastPoseChangeTick);
}
private void resetLastPoseChangeTickToFullStand(long lastPoseChangedTick) {
this.resetLastPoseChangeTick(Math.max(0L, lastPoseChangedTick - 52L - 1L));
}
public long getPoseTime() {
return this.level().getGameTime() - Math.abs(this.entityData.get(LAST_POSE_CHANGE_TICK));
}
@Override
public SoundEvent getSaddleSoundEvent() {
return SoundEvents.CAMEL_SADDLE;
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
if (!this.firstTick && DASH.equals(dataAccessor)) {
this.dashCooldown = this.dashCooldown == 0 ? 55 : this.dashCooldown;
}
super.onSyncedDataUpdated(dataAccessor);
}
@Override
public boolean isTamed() {
return true;
}
@Override
public void openCustomInventoryScreen(Player player) {
if (!this.level().isClientSide) {
player.openHorseInventory(this, this.inventory);
}
}
@Override
protected BodyRotationControl createBodyControl() {
return new Camel.CamelBodyRotationControl(this);
}
class CamelBodyRotationControl extends BodyRotationControl {
public CamelBodyRotationControl(final Camel camel) {
super(camel);
}
@Override
public void clientTick() {
if (!Camel.this.refuseToMove()) {
super.clientTick();
}
}
}
class CamelLookControl extends LookControl {
CamelLookControl() {
super(Camel.this);
}
@Override
public void tick() {
if (!Camel.this.hasControllingPassenger()) {
super.tick();
}
}
}
class CamelMoveControl extends MoveControl {
public CamelMoveControl() {
super(Camel.this);
}
@Override
public void tick() {
if (this.operation == MoveControl.Operation.MOVE_TO
&& !Camel.this.isLeashed()
&& Camel.this.isCamelSitting()
&& !Camel.this.isInPoseTransition()
&& Camel.this.canCamelChangePose()) {
Camel.this.standUp();
}
super.tick();
}
}
}