333 lines
12 KiB
Java
333 lines
12 KiB
Java
|
package net.minecraft.world.entity.animal;
|
||
|
|
||
|
import java.util.UUID;
|
||
|
import javax.annotation.Nullable;
|
||
|
import net.minecraft.core.BlockPos;
|
||
|
import net.minecraft.nbt.CompoundTag;
|
||
|
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.util.TimeUtil;
|
||
|
import net.minecraft.util.valueproviders.UniformInt;
|
||
|
import net.minecraft.world.InteractionHand;
|
||
|
import net.minecraft.world.InteractionResult;
|
||
|
import net.minecraft.world.damagesource.DamageSource;
|
||
|
import net.minecraft.world.entity.Crackiness;
|
||
|
import net.minecraft.world.entity.Entity;
|
||
|
import net.minecraft.world.entity.EntityType;
|
||
|
import net.minecraft.world.entity.LivingEntity;
|
||
|
import net.minecraft.world.entity.Mob;
|
||
|
import net.minecraft.world.entity.NeutralMob;
|
||
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||
|
import net.minecraft.world.entity.ai.goal.GolemRandomStrollInVillageGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.MoveBackToVillageGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.MoveTowardsTargetGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.OfferFlowerGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.target.DefendVillageTargetGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||
|
import net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal;
|
||
|
import net.minecraft.world.entity.monster.Creeper;
|
||
|
import net.minecraft.world.entity.monster.Enemy;
|
||
|
import net.minecraft.world.entity.player.Player;
|
||
|
import net.minecraft.world.item.ItemStack;
|
||
|
import net.minecraft.world.item.Items;
|
||
|
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||
|
import net.minecraft.world.level.Level;
|
||
|
import net.minecraft.world.level.LevelReader;
|
||
|
import net.minecraft.world.level.NaturalSpawner;
|
||
|
import net.minecraft.world.level.block.state.BlockState;
|
||
|
import net.minecraft.world.level.material.Fluids;
|
||
|
import net.minecraft.world.phys.Vec3;
|
||
|
|
||
|
public class IronGolem extends AbstractGolem implements NeutralMob {
|
||
|
protected static final EntityDataAccessor<Byte> DATA_FLAGS_ID = SynchedEntityData.defineId(IronGolem.class, EntityDataSerializers.BYTE);
|
||
|
private static final int IRON_INGOT_HEAL_AMOUNT = 25;
|
||
|
private static final boolean DEFAULT_PLAYER_CREATED = false;
|
||
|
private int attackAnimationTick;
|
||
|
private int offerFlowerTick;
|
||
|
private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39);
|
||
|
private int remainingPersistentAngerTime;
|
||
|
@Nullable
|
||
|
private UUID persistentAngerTarget;
|
||
|
|
||
|
public IronGolem(EntityType<? extends IronGolem> p_28834_, Level p_28835_) {
|
||
|
super(p_28834_, p_28835_);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void registerGoals() {
|
||
|
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0, true));
|
||
|
this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9, 32.0F));
|
||
|
this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6, false));
|
||
|
this.goalSelector.addGoal(4, new GolemRandomStrollInVillageGoal(this, 0.6));
|
||
|
this.goalSelector.addGoal(5, new OfferFlowerGoal(this));
|
||
|
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F));
|
||
|
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
|
||
|
this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this));
|
||
|
this.targetSelector.addGoal(2, new HurtByTargetGoal(this));
|
||
|
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
|
||
|
this.targetSelector
|
||
|
.addGoal(
|
||
|
3,
|
||
|
new NearestAttackableTargetGoal<>(
|
||
|
this, Mob.class, 5, false, false, (p_28879_, p_363579_) -> p_28879_ instanceof Enemy && !(p_28879_ instanceof Creeper)
|
||
|
)
|
||
|
);
|
||
|
this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void defineSynchedData(SynchedEntityData.Builder p_333015_) {
|
||
|
super.defineSynchedData(p_333015_);
|
||
|
p_333015_.define(DATA_FLAGS_ID, (byte)0);
|
||
|
}
|
||
|
|
||
|
public static AttributeSupplier.Builder createAttributes() {
|
||
|
return Mob.createMobAttributes()
|
||
|
.add(Attributes.MAX_HEALTH, 100.0)
|
||
|
.add(Attributes.MOVEMENT_SPEED, 0.25)
|
||
|
.add(Attributes.KNOCKBACK_RESISTANCE, 1.0)
|
||
|
.add(Attributes.ATTACK_DAMAGE, 15.0)
|
||
|
.add(Attributes.STEP_HEIGHT, 1.0);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected int decreaseAirSupply(int p_28882_) {
|
||
|
return p_28882_;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void doPush(Entity p_28839_) {
|
||
|
if (p_28839_ instanceof Enemy && !(p_28839_ instanceof Creeper) && this.getRandom().nextInt(20) == 0) {
|
||
|
this.setTarget((LivingEntity)p_28839_);
|
||
|
}
|
||
|
|
||
|
super.doPush(p_28839_);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void aiStep() {
|
||
|
super.aiStep();
|
||
|
if (this.attackAnimationTick > 0) {
|
||
|
this.attackAnimationTick--;
|
||
|
}
|
||
|
|
||
|
if (this.offerFlowerTick > 0) {
|
||
|
this.offerFlowerTick--;
|
||
|
}
|
||
|
|
||
|
if (!this.level().isClientSide) {
|
||
|
this.updatePersistentAnger((ServerLevel)this.level(), true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean canSpawnSprintParticle() {
|
||
|
return this.getDeltaMovement().horizontalDistanceSqr() > 2.5000003E-7F && this.random.nextInt(5) == 0;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean canAttackType(EntityType<?> p_28851_) {
|
||
|
if (this.isPlayerCreated() && p_28851_ == EntityType.PLAYER) {
|
||
|
return false;
|
||
|
} else {
|
||
|
return p_28851_ == EntityType.CREEPER ? false : super.canAttackType(p_28851_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void addAdditionalSaveData(CompoundTag p_28867_) {
|
||
|
super.addAdditionalSaveData(p_28867_);
|
||
|
p_28867_.putBoolean("PlayerCreated", this.isPlayerCreated());
|
||
|
this.addPersistentAngerSaveData(p_28867_);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void readAdditionalSaveData(CompoundTag p_28857_) {
|
||
|
super.readAdditionalSaveData(p_28857_);
|
||
|
this.setPlayerCreated(p_28857_.getBooleanOr("PlayerCreated", false));
|
||
|
this.readPersistentAngerSaveData(this.level(), p_28857_);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void startPersistentAngerTimer() {
|
||
|
this.setRemainingPersistentAngerTime(PERSISTENT_ANGER_TIME.sample(this.random));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setRemainingPersistentAngerTime(int p_28859_) {
|
||
|
this.remainingPersistentAngerTime = p_28859_;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getRemainingPersistentAngerTime() {
|
||
|
return this.remainingPersistentAngerTime;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setPersistentAngerTarget(@Nullable UUID p_28855_) {
|
||
|
this.persistentAngerTarget = p_28855_;
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
@Override
|
||
|
public UUID getPersistentAngerTarget() {
|
||
|
return this.persistentAngerTarget;
|
||
|
}
|
||
|
|
||
|
private float getAttackDamage() {
|
||
|
return (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean doHurtTarget(ServerLevel p_363631_, Entity p_28837_) {
|
||
|
this.attackAnimationTick = 10;
|
||
|
p_363631_.broadcastEntityEvent(this, (byte)4);
|
||
|
float f = this.getAttackDamage();
|
||
|
float f1 = (int)f > 0 ? f / 2.0F + this.random.nextInt((int)f) : f;
|
||
|
DamageSource damagesource = this.damageSources().mobAttack(this);
|
||
|
boolean flag = p_28837_.hurtServer(p_363631_, damagesource, f1);
|
||
|
if (flag) {
|
||
|
double d0 = p_28837_ instanceof LivingEntity livingentity ? livingentity.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE) : 0.0;
|
||
|
double d1 = Math.max(0.0, 1.0 - d0);
|
||
|
p_28837_.setDeltaMovement(p_28837_.getDeltaMovement().add(0.0, 0.4F * d1, 0.0));
|
||
|
EnchantmentHelper.doPostAttackEffects(p_363631_, p_28837_, damagesource);
|
||
|
}
|
||
|
|
||
|
this.playSound(SoundEvents.IRON_GOLEM_ATTACK, 1.0F, 1.0F);
|
||
|
return flag;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean hurtServer(ServerLevel p_365094_, DamageSource p_365765_, float p_365329_) {
|
||
|
Crackiness.Level crackiness$level = this.getCrackiness();
|
||
|
boolean flag = super.hurtServer(p_365094_, p_365765_, p_365329_);
|
||
|
if (flag && this.getCrackiness() != crackiness$level) {
|
||
|
this.playSound(SoundEvents.IRON_GOLEM_DAMAGE, 1.0F, 1.0F);
|
||
|
}
|
||
|
|
||
|
return flag;
|
||
|
}
|
||
|
|
||
|
public Crackiness.Level getCrackiness() {
|
||
|
return Crackiness.GOLEM.byFraction(this.getHealth() / this.getMaxHealth());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void handleEntityEvent(byte p_28844_) {
|
||
|
if (p_28844_ == 4) {
|
||
|
this.attackAnimationTick = 10;
|
||
|
this.playSound(SoundEvents.IRON_GOLEM_ATTACK, 1.0F, 1.0F);
|
||
|
} else if (p_28844_ == 11) {
|
||
|
this.offerFlowerTick = 400;
|
||
|
} else if (p_28844_ == 34) {
|
||
|
this.offerFlowerTick = 0;
|
||
|
} else {
|
||
|
super.handleEntityEvent(p_28844_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int getAttackAnimationTick() {
|
||
|
return this.attackAnimationTick;
|
||
|
}
|
||
|
|
||
|
public void offerFlower(boolean p_28886_) {
|
||
|
if (p_28886_) {
|
||
|
this.offerFlowerTick = 400;
|
||
|
this.level().broadcastEntityEvent(this, (byte)11);
|
||
|
} else {
|
||
|
this.offerFlowerTick = 0;
|
||
|
this.level().broadcastEntityEvent(this, (byte)34);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected SoundEvent getHurtSound(DamageSource p_28872_) {
|
||
|
return SoundEvents.IRON_GOLEM_HURT;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected SoundEvent getDeathSound() {
|
||
|
return SoundEvents.IRON_GOLEM_DEATH;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected InteractionResult mobInteract(Player p_28861_, InteractionHand p_28862_) {
|
||
|
ItemStack itemstack = p_28861_.getItemInHand(p_28862_);
|
||
|
if (!itemstack.is(Items.IRON_INGOT)) {
|
||
|
return InteractionResult.PASS;
|
||
|
} else {
|
||
|
float f = this.getHealth();
|
||
|
this.heal(25.0F);
|
||
|
if (this.getHealth() == f) {
|
||
|
return InteractionResult.PASS;
|
||
|
} else {
|
||
|
float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
|
||
|
this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f1);
|
||
|
itemstack.consume(1, p_28861_);
|
||
|
return InteractionResult.SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void playStepSound(BlockPos p_28864_, BlockState p_28865_) {
|
||
|
this.playSound(SoundEvents.IRON_GOLEM_STEP, 1.0F, 1.0F);
|
||
|
}
|
||
|
|
||
|
public int getOfferFlowerTick() {
|
||
|
return this.offerFlowerTick;
|
||
|
}
|
||
|
|
||
|
public boolean isPlayerCreated() {
|
||
|
return (this.entityData.get(DATA_FLAGS_ID) & 1) != 0;
|
||
|
}
|
||
|
|
||
|
public void setPlayerCreated(boolean p_28888_) {
|
||
|
byte b0 = this.entityData.get(DATA_FLAGS_ID);
|
||
|
if (p_28888_) {
|
||
|
this.entityData.set(DATA_FLAGS_ID, (byte)(b0 | 1));
|
||
|
} else {
|
||
|
this.entityData.set(DATA_FLAGS_ID, (byte)(b0 & -2));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void die(DamageSource p_28846_) {
|
||
|
super.die(p_28846_);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean checkSpawnObstruction(LevelReader p_28853_) {
|
||
|
BlockPos blockpos = this.blockPosition();
|
||
|
BlockPos blockpos1 = blockpos.below();
|
||
|
BlockState blockstate = p_28853_.getBlockState(blockpos1);
|
||
|
if (!blockstate.entityCanStandOn(p_28853_, blockpos1, this)) {
|
||
|
return false;
|
||
|
} else {
|
||
|
for (int i = 1; i < 3; i++) {
|
||
|
BlockPos blockpos2 = blockpos.above(i);
|
||
|
BlockState blockstate1 = p_28853_.getBlockState(blockpos2);
|
||
|
if (!NaturalSpawner.isValidEmptySpawnBlock(p_28853_, blockpos2, blockstate1, blockstate1.getFluidState(), EntityType.IRON_GOLEM)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NaturalSpawner.isValidEmptySpawnBlock(p_28853_, blockpos, p_28853_.getBlockState(blockpos), Fluids.EMPTY.defaultFluidState(), EntityType.IRON_GOLEM)
|
||
|
&& p_28853_.isUnobstructed(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Vec3 getLeashOffset() {
|
||
|
return new Vec3(0.0, 0.875F * this.getEyeHeight(), this.getBbWidth() * 0.4F);
|
||
|
}
|
||
|
}
|