653 lines
18 KiB
Java
653 lines
18 KiB
Java
package net.minecraft.world.entity.animal;
|
|
|
|
import com.mojang.serialization.Dynamic;
|
|
import java.util.function.Predicate;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
|
|
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.ItemTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
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.Entity;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.Leashable;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
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.FlyingMoveControl;
|
|
import net.minecraft.world.entity.ai.control.LookControl;
|
|
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
|
import net.minecraft.world.entity.ai.goal.TemptGoal.ForNonPathfinders;
|
|
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
|
|
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
|
import net.minecraft.world.entity.monster.Ghast;
|
|
import net.minecraft.world.entity.monster.Ghast.GhastMoveControl;
|
|
import net.minecraft.world.entity.monster.Ghast.RandomFloatAroundGoal;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.LevelReader;
|
|
import net.minecraft.world.level.biome.Biome.Precipitation;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.storage.ValueInput;
|
|
import net.minecraft.world.level.storage.ValueOutput;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.Vec2;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class HappyGhast extends Animal {
|
|
public static final float BABY_SCALE = 0.2375F;
|
|
public static final int WANDER_GROUND_DISTANCE = 16;
|
|
public static final int SMALL_RESTRICTION_RADIUS = 32;
|
|
public static final int LARGE_RESTRICTION_RADIUS = 64;
|
|
public static final int RESTRICTION_RADIUS_BUFFER = 16;
|
|
public static final int FAST_HEALING_TICKS = 20;
|
|
public static final int SLOW_HEALING_TICKS = 600;
|
|
public static final int MAX_PASSANGERS = 4;
|
|
private static final int STILL_TIMEOUT_ON_LOAD_GRACE_PERIOD = 60;
|
|
private static final int MAX_STILL_TIMEOUT = 10;
|
|
public static final float SPEED_MULTIPLIER_WHEN_PANICKING = 2.0F;
|
|
public static final Predicate<ItemStack> IS_FOOD = itemStack -> itemStack.is(ItemTags.HAPPY_GHAST_FOOD);
|
|
private int leashHolderTime = 0;
|
|
private int serverStillTimeout;
|
|
private static final EntityDataAccessor<Boolean> IS_LEASH_HOLDER = SynchedEntityData.defineId(HappyGhast.class, EntityDataSerializers.BOOLEAN);
|
|
private static final EntityDataAccessor<Boolean> STAYS_STILL = SynchedEntityData.defineId(HappyGhast.class, EntityDataSerializers.BOOLEAN);
|
|
private static final float MAX_SCALE = 1.0F;
|
|
|
|
public HappyGhast(EntityType<? extends HappyGhast> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.moveControl = new GhastMoveControl(this, true, this::isOnStillTimeout);
|
|
this.lookControl = new HappyGhast.HappyGhastLookControl();
|
|
}
|
|
|
|
private void setServerStillTimeout(int serverStillTimeout) {
|
|
if (this.serverStillTimeout <= 0 && serverStillTimeout > 0 && this.level() instanceof ServerLevel serverLevel) {
|
|
this.syncPacketPositionCodec(this.getX(), this.getY(), this.getZ());
|
|
serverLevel.getChunkSource().chunkMap.broadcast(this, ClientboundEntityPositionSyncPacket.of(this));
|
|
}
|
|
|
|
this.serverStillTimeout = serverStillTimeout;
|
|
this.syncStayStillFlag();
|
|
}
|
|
|
|
private PathNavigation createBabyNavigation(Level level) {
|
|
return new HappyGhast.BabyFlyingPathNavigation(this, level);
|
|
}
|
|
|
|
@Override
|
|
protected void registerGoals() {
|
|
this.goalSelector.addGoal(3, new HappyGhast.HappyGhastFloatGoal());
|
|
this.goalSelector
|
|
.addGoal(
|
|
4,
|
|
new ForNonPathfinders(
|
|
this,
|
|
1.0,
|
|
itemStack -> !this.isWearingBodyArmor() && !this.isBaby() ? itemStack.is(ItemTags.HAPPY_GHAST_TEMPT_ITEMS) : IS_FOOD.test(itemStack),
|
|
false,
|
|
7.0
|
|
)
|
|
);
|
|
this.goalSelector.addGoal(5, new RandomFloatAroundGoal(this, 16));
|
|
}
|
|
|
|
private void adultGhastSetup() {
|
|
this.moveControl = new GhastMoveControl(this, true, this::isOnStillTimeout);
|
|
this.lookControl = new HappyGhast.HappyGhastLookControl();
|
|
this.navigation = this.createNavigation(this.level());
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
this.removeAllGoals(goal -> true);
|
|
this.registerGoals();
|
|
((Brain<HappyGhast>)this.brain).stopAll(serverLevel, this);
|
|
this.brain.clearMemories();
|
|
}
|
|
}
|
|
|
|
private void babyGhastSetup() {
|
|
this.moveControl = new FlyingMoveControl(this, 180, true);
|
|
this.lookControl = new LookControl(this);
|
|
this.navigation = this.createBabyNavigation(this.level());
|
|
this.setServerStillTimeout(0);
|
|
this.removeAllGoals(goal -> true);
|
|
}
|
|
|
|
@Override
|
|
protected void ageBoundaryReached() {
|
|
if (this.isBaby()) {
|
|
this.babyGhastSetup();
|
|
} else {
|
|
this.adultGhastSetup();
|
|
}
|
|
|
|
super.ageBoundaryReached();
|
|
}
|
|
|
|
public static Builder createAttributes() {
|
|
return Animal.createAnimalAttributes()
|
|
.add(Attributes.MAX_HEALTH, 20.0)
|
|
.add(Attributes.TEMPT_RANGE, 16.0)
|
|
.add(Attributes.FLYING_SPEED, 0.05)
|
|
.add(Attributes.MOVEMENT_SPEED, 0.05)
|
|
.add(Attributes.FOLLOW_RANGE, 16.0)
|
|
.add(Attributes.CAMERA_DISTANCE, 8.0);
|
|
}
|
|
|
|
@Override
|
|
protected float sanitizeScale(float scale) {
|
|
return Math.min(scale, 1.0F);
|
|
}
|
|
|
|
@Override
|
|
protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
|
|
}
|
|
|
|
@Override
|
|
public boolean onClimbable() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void travel(Vec3 travelVector) {
|
|
float f = (float)this.getAttributeValue(Attributes.FLYING_SPEED) * 5.0F / 3.0F;
|
|
this.travelFlying(travelVector, f, f, f);
|
|
}
|
|
|
|
@Override
|
|
public float getWalkTargetValue(BlockPos pos, LevelReader level) {
|
|
if (!level.isEmptyBlock(pos)) {
|
|
return 0.0F;
|
|
} else {
|
|
return level.isEmptyBlock(pos.below()) && !level.isEmptyBlock(pos.below(2)) ? 10.0F : 5.0F;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canBreatheUnderwater() {
|
|
return this.isBaby() ? true : super.canBreatheUnderwater();
|
|
}
|
|
|
|
@Override
|
|
protected boolean shouldStayCloseToLeashHolder() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected void playStepSound(BlockPos pos, BlockState state) {
|
|
}
|
|
|
|
@Override
|
|
public float getVoicePitch() {
|
|
return 1.0F;
|
|
}
|
|
|
|
@Override
|
|
public SoundSource getSoundSource() {
|
|
return SoundSource.NEUTRAL;
|
|
}
|
|
|
|
@Override
|
|
public int getAmbientSoundInterval() {
|
|
int i = super.getAmbientSoundInterval();
|
|
return this.isVehicle() ? i * 6 : i;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getAmbientSound() {
|
|
return this.isBaby() ? SoundEvents.GHASTLING_AMBIENT : SoundEvents.HAPPY_GHAST_AMBIENT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
|
return this.isBaby() ? SoundEvents.GHASTLING_HURT : SoundEvents.HAPPY_GHAST_HURT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getDeathSound() {
|
|
return this.isBaby() ? SoundEvents.GHASTLING_DEATH : SoundEvents.HAPPY_GHAST_DEATH;
|
|
}
|
|
|
|
@Override
|
|
public int getMaxSpawnClusterSize() {
|
|
return 1;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
|
|
return EntityType.HAPPY_GHAST.create(level, EntitySpawnReason.BREEDING);
|
|
}
|
|
|
|
@Override
|
|
public boolean canFallInLove() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public float getAgeScale() {
|
|
return this.isBaby() ? 0.2375F : 1.0F;
|
|
}
|
|
|
|
@Override
|
|
public boolean isFood(ItemStack stack) {
|
|
return IS_FOOD.test(stack);
|
|
}
|
|
|
|
@Override
|
|
public boolean canUseSlot(EquipmentSlot slot) {
|
|
return slot != EquipmentSlot.BODY ? super.canUseSlot(slot) : this.isAlive() && !this.isBaby();
|
|
}
|
|
|
|
@Override
|
|
protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
|
|
return slot == EquipmentSlot.BODY;
|
|
}
|
|
|
|
@Override
|
|
public InteractionResult mobInteract(Player player, InteractionHand hand) {
|
|
if (this.isBaby()) {
|
|
return super.mobInteract(player, hand);
|
|
} else {
|
|
ItemStack itemStack = player.getItemInHand(hand);
|
|
if (!itemStack.isEmpty()) {
|
|
InteractionResult interactionResult = itemStack.interactLivingEntity(player, this, hand);
|
|
if (interactionResult.consumesAction()) {
|
|
return interactionResult;
|
|
}
|
|
}
|
|
|
|
if (this.isWearingBodyArmor() && !player.isSecondaryUseActive()) {
|
|
this.doPlayerRide(player);
|
|
return InteractionResult.SUCCESS;
|
|
} else {
|
|
return super.mobInteract(player, hand);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void doPlayerRide(Player player) {
|
|
if (!this.level().isClientSide) {
|
|
player.startRiding(this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void addPassenger(Entity passenger) {
|
|
if (!this.isVehicle()) {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_DOWN, this.getSoundSource(), 1.0F, 1.0F);
|
|
}
|
|
|
|
super.addPassenger(passenger);
|
|
if (!this.level().isClientSide) {
|
|
if (!this.scanPlayerAboveGhast()) {
|
|
this.setServerStillTimeout(0);
|
|
} else if (this.serverStillTimeout > 10) {
|
|
this.setServerStillTimeout(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void removePassenger(Entity passenger) {
|
|
super.removePassenger(passenger);
|
|
if (!this.level().isClientSide) {
|
|
this.setServerStillTimeout(10);
|
|
}
|
|
|
|
if (!this.isVehicle()) {
|
|
this.clearHome();
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_UP, this.getSoundSource(), 1.0F, 1.0F);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean canAddPassenger(Entity passenger) {
|
|
return this.getPassengers().size() < 4;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public LivingEntity getControllingPassenger() {
|
|
return (LivingEntity)(this.isWearingBodyArmor() && !this.isOnStillTimeout() && this.getFirstPassenger() instanceof Player player
|
|
? player
|
|
: super.getControllingPassenger());
|
|
}
|
|
|
|
@Override
|
|
protected Vec3 getRiddenInput(Player player, Vec3 travelVector) {
|
|
float f = player.xxa;
|
|
float g = 0.0F;
|
|
float h = 0.0F;
|
|
if (player.zza != 0.0F) {
|
|
float i = Mth.cos(player.getXRot() * (float) (Math.PI / 180.0));
|
|
float j = -Mth.sin(player.getXRot() * (float) (Math.PI / 180.0));
|
|
if (player.zza < 0.0F) {
|
|
i *= -0.5F;
|
|
j *= -0.5F;
|
|
}
|
|
|
|
h = j;
|
|
g = i;
|
|
}
|
|
|
|
if (player.isJumping()) {
|
|
h += 0.5F;
|
|
}
|
|
|
|
return new Vec3(f, h, g).scale(3.9F * this.getAttributeValue(Attributes.FLYING_SPEED));
|
|
}
|
|
|
|
protected Vec2 getRiddenRotation(LivingEntity entity) {
|
|
return new Vec2(entity.getXRot() * 0.5F, entity.getYRot());
|
|
}
|
|
|
|
@Override
|
|
protected void tickRidden(Player player, Vec3 travelVector) {
|
|
super.tickRidden(player, travelVector);
|
|
Vec2 vec2 = this.getRiddenRotation(player);
|
|
float f = this.getYRot();
|
|
float g = Mth.wrapDegrees(vec2.y - f);
|
|
float h = 0.08F;
|
|
f += g * 0.08F;
|
|
this.setRot(f, vec2.x);
|
|
this.yRotO = this.yBodyRot = this.yHeadRot = f;
|
|
}
|
|
|
|
@Override
|
|
protected Provider<HappyGhast> brainProvider() {
|
|
return HappyGhastAi.brainProvider();
|
|
}
|
|
|
|
@Override
|
|
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
|
|
return HappyGhastAi.makeBrain(this.brainProvider().makeBrain(dynamic));
|
|
}
|
|
|
|
@Override
|
|
protected void customServerAiStep(ServerLevel level) {
|
|
if (this.isBaby()) {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("happyGhastBrain");
|
|
((Brain<HappyGhast>)this.brain).tick(level, this);
|
|
profilerFiller.pop();
|
|
profilerFiller.push("happyGhastActivityUpdate");
|
|
HappyGhastAi.updateActivity(this);
|
|
profilerFiller.pop();
|
|
}
|
|
|
|
this.checkRestriction();
|
|
super.customServerAiStep(level);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
if (!this.level().isClientSide()) {
|
|
if (this.leashHolderTime > 0) {
|
|
this.leashHolderTime--;
|
|
}
|
|
|
|
this.setLeashHolder(this.leashHolderTime > 0);
|
|
if (this.serverStillTimeout > 0) {
|
|
if (this.tickCount > 60) {
|
|
this.serverStillTimeout--;
|
|
}
|
|
|
|
this.setServerStillTimeout(this.serverStillTimeout);
|
|
}
|
|
|
|
if (this.scanPlayerAboveGhast()) {
|
|
this.setServerStillTimeout(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void aiStep() {
|
|
if (!this.level().isClientSide) {
|
|
this.setRequiresPrecisePosition(this.isOnStillTimeout());
|
|
}
|
|
|
|
super.aiStep();
|
|
this.continuousHeal();
|
|
}
|
|
|
|
private int getHappyGhastRestrictionRadius() {
|
|
return !this.isBaby() && this.getItemBySlot(EquipmentSlot.BODY).isEmpty() ? 64 : 32;
|
|
}
|
|
|
|
private void checkRestriction() {
|
|
if (!this.isLeashed() && !this.isVehicle()) {
|
|
int i = this.getHappyGhastRestrictionRadius();
|
|
if (!this.hasHome() || !this.getHomePosition().closerThan(this.blockPosition(), i + 16) || i != this.getHomeRadius()) {
|
|
this.setHomeTo(this.blockPosition(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void continuousHeal() {
|
|
if (this.level() instanceof ServerLevel serverLevel && this.isAlive() && this.deathTime == 0 && this.getMaxHealth() != this.getHealth()) {
|
|
boolean bl = serverLevel.dimensionType().natural() && (this.isInClouds() || serverLevel.precipitationAt(this.blockPosition()) != Precipitation.NONE);
|
|
if (this.tickCount % (bl ? 20 : 600) == 0) {
|
|
this.heal(1.0F);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void sendDebugPackets() {
|
|
super.sendDebugPackets();
|
|
DebugPackets.sendEntityBrain(this);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(IS_LEASH_HOLDER, false);
|
|
builder.define(STAYS_STILL, false);
|
|
}
|
|
|
|
private void setLeashHolder(boolean leashHolder) {
|
|
this.entityData.set(IS_LEASH_HOLDER, leashHolder);
|
|
}
|
|
|
|
public boolean isLeashHolder() {
|
|
return this.entityData.get(IS_LEASH_HOLDER);
|
|
}
|
|
|
|
private void syncStayStillFlag() {
|
|
this.entityData.set(STAYS_STILL, this.serverStillTimeout > 0);
|
|
}
|
|
|
|
public boolean staysStill() {
|
|
return this.entityData.get(STAYS_STILL);
|
|
}
|
|
|
|
@Override
|
|
public boolean supportQuadLeashAsHolder() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public Vec3[] getQuadLeashHolderOffsets() {
|
|
return Leashable.createQuadLeashOffsets(this, -0.03125, 0.4375, 0.46875, 0.03125);
|
|
}
|
|
|
|
@Override
|
|
public Vec3 getLeashOffset() {
|
|
return Vec3.ZERO;
|
|
}
|
|
|
|
@Override
|
|
public double leashElasticDistance() {
|
|
return 10.0;
|
|
}
|
|
|
|
@Override
|
|
public double leashSnapDistance() {
|
|
return 16.0;
|
|
}
|
|
|
|
@Override
|
|
public void onElasticLeashPull() {
|
|
super.onElasticLeashPull();
|
|
this.getMoveControl().setWait();
|
|
}
|
|
|
|
@Override
|
|
public void notifyLeashHolder(Leashable leashHolder) {
|
|
if (leashHolder.supportQuadLeash()) {
|
|
this.leashHolderTime = 5;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(ValueOutput output) {
|
|
super.addAdditionalSaveData(output);
|
|
output.putInt("still_timeout", this.serverStillTimeout);
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(ValueInput input) {
|
|
super.readAdditionalSaveData(input);
|
|
this.setServerStillTimeout(input.getIntOr("still_timeout", 0));
|
|
}
|
|
|
|
public boolean isOnStillTimeout() {
|
|
return this.staysStill() || this.serverStillTimeout > 0;
|
|
}
|
|
|
|
private boolean scanPlayerAboveGhast() {
|
|
AABB aABB = this.getBoundingBox();
|
|
AABB aABB2 = new AABB(aABB.minX - 1.0, aABB.maxY - 1.0E-5F, aABB.minZ - 1.0, aABB.maxX + 1.0, aABB.maxY + aABB.getYsize() / 2.0, aABB.maxZ + 1.0);
|
|
|
|
for (Player player : this.level().players()) {
|
|
if (!player.isSpectator()) {
|
|
Entity entity = player.getRootVehicle();
|
|
if (!(entity instanceof HappyGhast) && aABB2.contains(entity.position())) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected BodyRotationControl createBodyControl() {
|
|
return new HappyGhast.HappyGhastBodyRotationControl();
|
|
}
|
|
|
|
@Override
|
|
public boolean canBeCollidedWith(@Nullable Entity entity) {
|
|
if (!this.isBaby() && this.isAlive()) {
|
|
if (this.level().isClientSide() && entity instanceof Player && entity.position().y >= this.getBoundingBox().maxY) {
|
|
return true;
|
|
} else {
|
|
return this.isVehicle() && entity instanceof HappyGhast ? true : this.isOnStillTimeout();
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isFlyingVehicle() {
|
|
return !this.isBaby();
|
|
}
|
|
|
|
static class BabyFlyingPathNavigation extends FlyingPathNavigation {
|
|
public BabyFlyingPathNavigation(HappyGhast ghast, Level level) {
|
|
super(ghast, level);
|
|
this.setCanOpenDoors(false);
|
|
this.setCanFloat(true);
|
|
this.setRequiredPathLength(48.0F);
|
|
}
|
|
|
|
@Override
|
|
protected boolean canMoveDirectly(Vec3 posVec31, Vec3 posVec32) {
|
|
return isClearForMovementBetween(this.mob, posVec31, posVec32, false);
|
|
}
|
|
}
|
|
|
|
class HappyGhastBodyRotationControl extends BodyRotationControl {
|
|
public HappyGhastBodyRotationControl() {
|
|
super(HappyGhast.this);
|
|
}
|
|
|
|
@Override
|
|
public void clientTick() {
|
|
if (HappyGhast.this.isVehicle()) {
|
|
HappyGhast.this.yHeadRot = HappyGhast.this.getYRot();
|
|
HappyGhast.this.yBodyRot = HappyGhast.this.yHeadRot;
|
|
}
|
|
|
|
super.clientTick();
|
|
}
|
|
}
|
|
|
|
class HappyGhastFloatGoal extends FloatGoal {
|
|
public HappyGhastFloatGoal() {
|
|
super(HappyGhast.this);
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return !HappyGhast.this.isOnStillTimeout() && super.canUse();
|
|
}
|
|
}
|
|
|
|
class HappyGhastLookControl extends LookControl {
|
|
HappyGhastLookControl() {
|
|
super(HappyGhast.this);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (HappyGhast.this.isOnStillTimeout()) {
|
|
float f = wrapDegrees90(HappyGhast.this.getYRot());
|
|
HappyGhast.this.setYRot(HappyGhast.this.getYRot() - f);
|
|
HappyGhast.this.setYHeadRot(HappyGhast.this.getYRot());
|
|
} else if (this.lookAtCooldown > 0) {
|
|
this.lookAtCooldown--;
|
|
double d = this.wantedX - HappyGhast.this.getX();
|
|
double e = this.wantedZ - HappyGhast.this.getZ();
|
|
HappyGhast.this.setYRot(-((float)Mth.atan2(d, e)) * (180.0F / (float)Math.PI));
|
|
HappyGhast.this.yBodyRot = HappyGhast.this.getYRot();
|
|
HappyGhast.this.yHeadRot = HappyGhast.this.yBodyRot;
|
|
} else {
|
|
Ghast.faceMovementDirection(this.mob);
|
|
}
|
|
}
|
|
|
|
public static float wrapDegrees90(float degrees) {
|
|
float f = degrees % 90.0F;
|
|
if (f >= 45.0F) {
|
|
f -= 90.0F;
|
|
}
|
|
|
|
if (f < -45.0F) {
|
|
f += 90.0F;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
}
|
|
}
|