/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.animal; import net.minecraft.core.BlockPos; 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.EntityReference; 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.PathfinderMob; 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.animal.AbstractGolem; 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.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; public class IronGolem extends AbstractGolem implements NeutralMob { protected static final EntityDataAccessor 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 long persistentAngerEndTime; private @Nullable EntityReference persistentAngerTarget; public IronGolem(EntityType type, Level level) { super((EntityType)type, level); } @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((PathfinderMob)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, new Class[0])); 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, (target, level) -> target instanceof Enemy && !(target instanceof Creeper))); this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal(this, false)); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.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 currentSupply) { return currentSupply; } @Override protected void doPush(Entity entity) { if (entity instanceof Enemy && !(entity instanceof Creeper) && this.getRandom().nextInt(20) == 0) { this.setTarget((LivingEntity)entity); } super.doPush(entity); } @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.500000277905201E-7 && this.random.nextInt(5) == 0; } @Override public boolean canAttackType(EntityType targetType) { if (this.isPlayerCreated() && targetType == EntityType.PLAYER) { return false; } if (targetType == EntityType.CREEPER) { return false; } return super.canAttackType(targetType); } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.putBoolean("PlayerCreated", this.isPlayerCreated()); this.addPersistentAngerSaveData(output); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); this.setPlayerCreated(input.getBooleanOr("PlayerCreated", false)); this.readPersistentAngerSaveData(this.level(), input); } @Override public void startPersistentAngerTimer() { this.setTimeToRemainAngry(PERSISTENT_ANGER_TIME.sample(this.random)); } @Override public void setPersistentAngerEndTime(long endTime) { this.persistentAngerEndTime = endTime; } @Override public long getPersistentAngerEndTime() { return this.persistentAngerEndTime; } @Override public void setPersistentAngerTarget(@Nullable EntityReference persistentAngerTarget) { this.persistentAngerTarget = persistentAngerTarget; } @Override public @Nullable EntityReference getPersistentAngerTarget() { return this.persistentAngerTarget; } private float getAttackDamage() { return (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE); } @Override public boolean doHurtTarget(ServerLevel level, Entity target) { this.attackAnimationTick = 10; level.broadcastEntityEvent(this, (byte)4); float attackDamage = this.getAttackDamage(); float damage = (int)attackDamage > 0 ? attackDamage / 2.0f + (float)this.random.nextInt((int)attackDamage) : attackDamage; DamageSource damageSource = this.damageSources().mobAttack(this); boolean hurt = target.hurtServer(level, damageSource, damage); if (hurt) { double d; if (target instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity)target; d = livingEntity.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); } else { d = 0.0; } double knockbackResistance = d; double scale = Math.max(0.0, 1.0 - knockbackResistance); target.setDeltaMovement(target.getDeltaMovement().add(0.0, (double)0.4f * scale, 0.0)); EnchantmentHelper.doPostAttackEffects(level, target, damageSource); } this.playSound(SoundEvents.IRON_GOLEM_ATTACK, 1.0f, 1.0f); return hurt; } @Override public boolean hurtServer(ServerLevel level, DamageSource source, float damage) { Crackiness.Level previousCrackiness = this.getCrackiness(); boolean wasHurt = super.hurtServer(level, source, damage); if (wasHurt && this.getCrackiness() != previousCrackiness) { this.playSound(SoundEvents.IRON_GOLEM_DAMAGE, 1.0f, 1.0f); } return wasHurt; } public Crackiness.Level getCrackiness() { return Crackiness.GOLEM.byFraction(this.getHealth() / this.getMaxHealth()); } @Override public void handleEntityEvent(byte id) { if (id == 4) { this.attackAnimationTick = 10; this.playSound(SoundEvents.IRON_GOLEM_ATTACK, 1.0f, 1.0f); } else if (id == 11) { this.offerFlowerTick = 400; } else if (id == 34) { this.offerFlowerTick = 0; } else { super.handleEntityEvent(id); } } public int getAttackAnimationTick() { return this.attackAnimationTick; } public void offerFlower(boolean offer) { if (offer) { this.offerFlowerTick = 400; this.level().broadcastEntityEvent(this, (byte)11); } else { this.offerFlowerTick = 0; this.level().broadcastEntityEvent(this, (byte)34); } } @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.IRON_GOLEM_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.IRON_GOLEM_DEATH; } @Override protected InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemStack = player.getItemInHand(hand); if (!itemStack.is(Items.IRON_INGOT)) { return InteractionResult.PASS; } float healthBefore = this.getHealth(); this.heal(25.0f); if (this.getHealth() == healthBefore) { return InteractionResult.PASS; } float pitch = 1.0f + (this.random.nextFloat() - this.random.nextFloat()) * 0.2f; this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0f, pitch); itemStack.consume(1, player); return InteractionResult.SUCCESS; } @Override protected void playStepSound(BlockPos pos, BlockState blockState) { 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 value) { byte current = this.entityData.get(DATA_FLAGS_ID); if (value) { this.entityData.set(DATA_FLAGS_ID, (byte)(current | 1)); } else { this.entityData.set(DATA_FLAGS_ID, (byte)(current & 0xFFFFFFFE)); } } @Override public void die(DamageSource source) { super.die(source); } @Override public boolean checkSpawnObstruction(LevelReader level) { BlockPos pos = this.blockPosition(); BlockPos belowPos = pos.below(); BlockState below = level.getBlockState(belowPos); if (below.entityCanStandOn(level, belowPos, this)) { for (int i = 1; i < 3; ++i) { BlockState above; BlockPos abovePos = pos.above(i); if (NaturalSpawner.isValidEmptySpawnBlock(level, abovePos, above = level.getBlockState(abovePos), above.getFluidState(), EntityType.IRON_GOLEM)) continue; return false; } return NaturalSpawner.isValidEmptySpawnBlock(level, pos, level.getBlockState(pos), Fluids.EMPTY.defaultFluidState(), EntityType.IRON_GOLEM) && level.isUnobstructed(this); } return false; } @Override public Vec3 getLeashOffset() { return new Vec3(0.0, 0.875f * this.getEyeHeight(), this.getBbWidth() * 0.4f); } }