/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.monster; import java.util.EnumSet; 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.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityReference; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.TraceableEntity; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; 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.TargetGoal; import net.minecraft.world.entity.ai.targeting.TargetingConditions; import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.raid.Raider; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; 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 Vex extends Monster implements TraceableEntity { public static final float FLAP_DEGREES_PER_TICK = 45.836624f; public static final int TICKS_PER_FLAP = Mth.ceil(3.9269907f); protected static final EntityDataAccessor DATA_FLAGS_ID = SynchedEntityData.defineId(Vex.class, EntityDataSerializers.BYTE); private static final int FLAG_IS_CHARGING = 1; private @Nullable EntityReference owner; private @Nullable BlockPos boundOrigin; private boolean hasLimitedLife; private int limitedLifeTicks; public Vex(EntityType type, Level level) { super((EntityType)type, level); this.moveControl = new VexMoveControl(this); this.xpReward = 3; } @Override public boolean isFlapping() { return this.tickCount % TICKS_PER_FLAP == 0; } @Override protected boolean isAffectedByBlocks() { return !this.isRemoved(); } @Override public void tick() { this.noPhysics = true; super.tick(); this.noPhysics = false; this.setNoGravity(true); if (this.hasLimitedLife && --this.limitedLifeTicks <= 0) { this.limitedLifeTicks = 20; this.hurt(this.damageSources().starve(), 1.0f); } } @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(4, new VexChargeAttackGoal()); this.goalSelector.addGoal(8, new VexRandomMoveGoal()); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0f, 1.0f)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0f)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers(new Class[0])); this.targetSelector.addGoal(2, new VexCopyOwnerTargetGoal(this)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal((Mob)this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0).add(Attributes.ATTACK_DAMAGE, 4.0); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(DATA_FLAGS_ID, (byte)0); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); this.boundOrigin = input.read("bound_pos", BlockPos.CODEC).orElse(null); input.getInt("life_ticks").ifPresentOrElse(this::setLimitedLife, () -> { this.hasLimitedLife = false; }); this.owner = EntityReference.read(input, "owner"); } @Override public void restoreFrom(Entity oldEntity) { super.restoreFrom(oldEntity); if (oldEntity instanceof Vex) { Vex vex = (Vex)oldEntity; this.owner = vex.owner; } } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.storeNullable("bound_pos", BlockPos.CODEC, this.boundOrigin); if (this.hasLimitedLife) { output.putInt("life_ticks", this.limitedLifeTicks); } EntityReference.store(this.owner, output, "owner"); } @Override public @Nullable Mob getOwner() { return EntityReference.get(this.owner, this.level(), Mob.class); } public @Nullable BlockPos getBoundOrigin() { return this.boundOrigin; } public void setBoundOrigin(@Nullable BlockPos boundOrigin) { this.boundOrigin = boundOrigin; } private boolean getVexFlag(int flag) { byte flags = this.entityData.get(DATA_FLAGS_ID); return (flags & flag) != 0; } private void setVexFlag(int flag, boolean value) { int flags = this.entityData.get(DATA_FLAGS_ID).byteValue(); flags = value ? (flags |= flag) : (flags &= ~flag); this.entityData.set(DATA_FLAGS_ID, (byte)(flags & 0xFF)); } public boolean isCharging() { return this.getVexFlag(1); } public void setIsCharging(boolean value) { this.setVexFlag(1, value); } public void setOwner(Mob owner) { this.owner = EntityReference.of(owner); } public void setLimitedLife(int lifeTicks) { this.hasLimitedLife = true; this.limitedLifeTicks = lifeTicks; } @Override protected SoundEvent getAmbientSound() { return SoundEvents.VEX_AMBIENT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.VEX_DEATH; } @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.VEX_HURT; } @Override public float getLightLevelDependentMagicValue() { return 1.0f; } @Override public @Nullable SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData) { RandomSource random = level.getRandom(); this.populateDefaultEquipmentSlots(random, difficulty); this.populateDefaultEquipmentEnchantments(level, random, difficulty); return super.finalizeSpawn(level, difficulty, spawnReason, groupData); } @Override protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) { this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); this.setDropChance(EquipmentSlot.MAINHAND, 0.0f); } private class VexMoveControl extends MoveControl { public VexMoveControl(Vex vex2) { super(vex2); } @Override public void tick() { if (this.operation != MoveControl.Operation.MOVE_TO) { return; } Vec3 delta = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); double deltaLength = delta.length(); if (deltaLength < Vex.this.getBoundingBox().getSize()) { this.operation = MoveControl.Operation.WAIT; Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5)); } else { Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(delta.scale(this.speedModifier * 0.05 / deltaLength))); if (Vex.this.getTarget() == null) { Vec3 movement = Vex.this.getDeltaMovement(); Vex.this.setYRot(-((float)Mth.atan2(movement.x, movement.z)) * 57.295776f); Vex.this.yBodyRot = Vex.this.getYRot(); } else { double tx = Vex.this.getTarget().getX() - Vex.this.getX(); double tz = Vex.this.getTarget().getZ() - Vex.this.getZ(); Vex.this.setYRot(-((float)Mth.atan2(tx, tz)) * 57.295776f); Vex.this.yBodyRot = Vex.this.getYRot(); } } } } private class VexChargeAttackGoal extends Goal { public VexChargeAttackGoal() { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canUse() { LivingEntity target = Vex.this.getTarget(); if (target != null && target.isAlive() && !Vex.this.getMoveControl().hasWanted() && Vex.this.random.nextInt(VexChargeAttackGoal.reducedTickDelay(7)) == 0) { return Vex.this.distanceToSqr(target) > 4.0; } return false; } @Override public boolean canContinueToUse() { return Vex.this.getMoveControl().hasWanted() && Vex.this.isCharging() && Vex.this.getTarget() != null && Vex.this.getTarget().isAlive(); } @Override public void start() { LivingEntity attackTarget = Vex.this.getTarget(); if (attackTarget != null) { Vec3 eyePosition = attackTarget.getEyePosition(); Vex.this.moveControl.setWantedPosition(eyePosition.x, eyePosition.y, eyePosition.z, 1.0); } Vex.this.setIsCharging(true); Vex.this.playSound(SoundEvents.VEX_CHARGE, 1.0f, 1.0f); } @Override public void stop() { Vex.this.setIsCharging(false); } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { LivingEntity attackTarget = Vex.this.getTarget(); if (attackTarget == null) { return; } if (Vex.this.getBoundingBox().intersects(attackTarget.getBoundingBox())) { Vex.this.doHurtTarget(VexChargeAttackGoal.getServerLevel(Vex.this.level()), attackTarget); Vex.this.setIsCharging(false); } else { double distance = Vex.this.distanceToSqr(attackTarget); if (distance < 9.0) { Vec3 eyePosition = attackTarget.getEyePosition(); Vex.this.moveControl.setWantedPosition(eyePosition.x, eyePosition.y, eyePosition.z, 1.0); } } } } private class VexRandomMoveGoal extends Goal { public VexRandomMoveGoal() { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canUse() { return !Vex.this.getMoveControl().hasWanted() && Vex.this.random.nextInt(VexRandomMoveGoal.reducedTickDelay(7)) == 0; } @Override public boolean canContinueToUse() { return false; } @Override public void tick() { BlockPos boundOrigin = Vex.this.getBoundOrigin(); if (boundOrigin == null) { boundOrigin = Vex.this.blockPosition(); } for (int attempts = 0; attempts < 3; ++attempts) { BlockPos testPos = boundOrigin.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7); if (!Vex.this.level().isEmptyBlock(testPos)) continue; Vex.this.moveControl.setWantedPosition((double)testPos.getX() + 0.5, (double)testPos.getY() + 0.5, (double)testPos.getZ() + 0.5, 0.25); if (Vex.this.getTarget() != null) break; Vex.this.getLookControl().setLookAt((double)testPos.getX() + 0.5, (double)testPos.getY() + 0.5, (double)testPos.getZ() + 0.5, 180.0f, 20.0f); break; } } } private class VexCopyOwnerTargetGoal extends TargetGoal { private final TargetingConditions copyOwnerTargeting; public VexCopyOwnerTargetGoal(PathfinderMob mob) { super(mob, false); this.copyOwnerTargeting = TargetingConditions.forNonCombat().ignoreLineOfSight().ignoreInvisibilityTesting(); } @Override public boolean canUse() { Mob owner = Vex.this.getOwner(); return owner != null && owner.getTarget() != null && this.canAttack(owner.getTarget(), this.copyOwnerTargeting); } @Override public void start() { Mob owner = Vex.this.getOwner(); Vex.this.setTarget(owner != null ? owner.getTarget() : null); super.start(); } } }