366 lines
11 KiB
Java
366 lines
11 KiB
Java
package net.minecraft.world.entity.monster;
|
|
|
|
import java.util.EnumSet;
|
|
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.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.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.Attributes;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
|
|
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.Goal.Flag;
|
|
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.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.phys.Vec3;
|
|
import org.jetbrains.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((float) (Math.PI * 5.0 / 4.0));
|
|
protected static final EntityDataAccessor<Byte> DATA_FLAGS_ID = SynchedEntityData.defineId(Vex.class, EntityDataSerializers.BYTE);
|
|
private static final int FLAG_IS_CHARGING = 1;
|
|
@Nullable
|
|
Mob owner;
|
|
@Nullable
|
|
private BlockPos boundOrigin;
|
|
private boolean hasLimitedLife;
|
|
private int limitedLifeTicks;
|
|
|
|
public Vex(EntityType<? extends Vex> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.moveControl = new Vex.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 Vex.VexChargeAttackGoal());
|
|
this.goalSelector.addGoal(8, new Vex.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());
|
|
this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this));
|
|
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, Player.class, true));
|
|
}
|
|
|
|
public static Builder createAttributes() {
|
|
return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0).add(Attributes.ATTACK_DAMAGE, 4.0);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(DATA_FLAGS_ID, (byte)0);
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag tag) {
|
|
super.readAdditionalSaveData(tag);
|
|
if (tag.contains("BoundX")) {
|
|
this.boundOrigin = new BlockPos(tag.getInt("BoundX"), tag.getInt("BoundY"), tag.getInt("BoundZ"));
|
|
}
|
|
|
|
if (tag.contains("LifeTicks")) {
|
|
this.setLimitedLife(tag.getInt("LifeTicks"));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void restoreFrom(Entity entity) {
|
|
super.restoreFrom(entity);
|
|
if (entity instanceof Vex vex) {
|
|
this.owner = vex.getOwner();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag tag) {
|
|
super.addAdditionalSaveData(tag);
|
|
if (this.boundOrigin != null) {
|
|
tag.putInt("BoundX", this.boundOrigin.getX());
|
|
tag.putInt("BoundY", this.boundOrigin.getY());
|
|
tag.putInt("BoundZ", this.boundOrigin.getZ());
|
|
}
|
|
|
|
if (this.hasLimitedLife) {
|
|
tag.putInt("LifeTicks", this.limitedLifeTicks);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public Mob getOwner() {
|
|
return this.owner;
|
|
}
|
|
|
|
@Nullable
|
|
public BlockPos getBoundOrigin() {
|
|
return this.boundOrigin;
|
|
}
|
|
|
|
public void setBoundOrigin(@Nullable BlockPos boundOrigin) {
|
|
this.boundOrigin = boundOrigin;
|
|
}
|
|
|
|
private boolean getVexFlag(int mask) {
|
|
int i = this.entityData.get(DATA_FLAGS_ID);
|
|
return (i & mask) != 0;
|
|
}
|
|
|
|
private void setVexFlag(int mask, boolean value) {
|
|
int i = this.entityData.get(DATA_FLAGS_ID);
|
|
if (value) {
|
|
i |= mask;
|
|
} else {
|
|
i &= ~mask;
|
|
}
|
|
|
|
this.entityData.set(DATA_FLAGS_ID, (byte)(i & 0xFF));
|
|
}
|
|
|
|
public boolean isCharging() {
|
|
return this.getVexFlag(1);
|
|
}
|
|
|
|
public void setIsCharging(boolean charging) {
|
|
this.setVexFlag(1, charging);
|
|
}
|
|
|
|
public void setOwner(Mob owner) {
|
|
this.owner = owner;
|
|
}
|
|
|
|
public void setLimitedLife(int limitedLifeTicks) {
|
|
this.hasLimitedLife = true;
|
|
this.limitedLifeTicks = limitedLifeTicks;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getAmbientSound() {
|
|
return SoundEvents.VEX_AMBIENT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getDeathSound() {
|
|
return SoundEvents.VEX_DEATH;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
|
return SoundEvents.VEX_HURT;
|
|
}
|
|
|
|
@Override
|
|
public float getLightLevelDependentMagicValue() {
|
|
return 1.0F;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public SpawnGroupData finalizeSpawn(
|
|
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
|
|
) {
|
|
RandomSource randomSource = level.getRandom();
|
|
this.populateDefaultEquipmentSlots(randomSource, difficulty);
|
|
this.populateDefaultEquipmentEnchantments(level, randomSource, difficulty);
|
|
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
|
|
}
|
|
|
|
@Override
|
|
protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) {
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD));
|
|
this.setDropChance(EquipmentSlot.MAINHAND, 0.0F);
|
|
}
|
|
|
|
class VexChargeAttackGoal extends Goal {
|
|
public VexChargeAttackGoal() {
|
|
this.setFlags(EnumSet.of(Flag.MOVE));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
LivingEntity livingEntity = Vex.this.getTarget();
|
|
return livingEntity != null && livingEntity.isAlive() && !Vex.this.getMoveControl().hasWanted() && Vex.this.random.nextInt(reducedTickDelay(7)) == 0
|
|
? Vex.this.distanceToSqr(livingEntity) > 4.0
|
|
: 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 livingEntity = Vex.this.getTarget();
|
|
if (livingEntity != null) {
|
|
Vec3 vec3 = livingEntity.getEyePosition();
|
|
Vex.this.moveControl.setWantedPosition(vec3.x, vec3.y, vec3.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 livingEntity = Vex.this.getTarget();
|
|
if (livingEntity != null) {
|
|
if (Vex.this.getBoundingBox().intersects(livingEntity.getBoundingBox())) {
|
|
Vex.this.doHurtTarget(getServerLevel(Vex.this.level()), livingEntity);
|
|
Vex.this.setIsCharging(false);
|
|
} else {
|
|
double d = Vex.this.distanceToSqr(livingEntity);
|
|
if (d < 9.0) {
|
|
Vec3 vec3 = livingEntity.getEyePosition();
|
|
Vex.this.moveControl.setWantedPosition(vec3.x, vec3.y, vec3.z, 1.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class VexCopyOwnerTargetGoal extends TargetGoal {
|
|
private final TargetingConditions copyOwnerTargeting = TargetingConditions.forNonCombat().ignoreLineOfSight().ignoreInvisibilityTesting();
|
|
|
|
public VexCopyOwnerTargetGoal(final PathfinderMob mob) {
|
|
super(mob, false);
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return Vex.this.owner != null && Vex.this.owner.getTarget() != null && this.canAttack(Vex.this.owner.getTarget(), this.copyOwnerTargeting);
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Vex.this.setTarget(Vex.this.owner.getTarget());
|
|
super.start();
|
|
}
|
|
}
|
|
|
|
class VexMoveControl extends MoveControl {
|
|
public VexMoveControl(final Vex vex) {
|
|
super(vex);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (this.operation == MoveControl.Operation.MOVE_TO) {
|
|
Vec3 vec3 = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ());
|
|
double d = vec3.length();
|
|
if (d < 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(vec3.scale(this.speedModifier * 0.05 / d)));
|
|
if (Vex.this.getTarget() == null) {
|
|
Vec3 vec32 = Vex.this.getDeltaMovement();
|
|
Vex.this.setYRot(-((float)Mth.atan2(vec32.x, vec32.z)) * (180.0F / (float)Math.PI));
|
|
Vex.this.yBodyRot = Vex.this.getYRot();
|
|
} else {
|
|
double e = Vex.this.getTarget().getX() - Vex.this.getX();
|
|
double f = Vex.this.getTarget().getZ() - Vex.this.getZ();
|
|
Vex.this.setYRot(-((float)Mth.atan2(e, f)) * (180.0F / (float)Math.PI));
|
|
Vex.this.yBodyRot = Vex.this.getYRot();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class VexRandomMoveGoal extends Goal {
|
|
public VexRandomMoveGoal() {
|
|
this.setFlags(EnumSet.of(Flag.MOVE));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return !Vex.this.getMoveControl().hasWanted() && Vex.this.random.nextInt(reducedTickDelay(7)) == 0;
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
BlockPos blockPos = Vex.this.getBoundOrigin();
|
|
if (blockPos == null) {
|
|
blockPos = Vex.this.blockPosition();
|
|
}
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
BlockPos blockPos2 = blockPos.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7);
|
|
if (Vex.this.level().isEmptyBlock(blockPos2)) {
|
|
Vex.this.moveControl.setWantedPosition(blockPos2.getX() + 0.5, blockPos2.getY() + 0.5, blockPos2.getZ() + 0.5, 0.25);
|
|
if (Vex.this.getTarget() == null) {
|
|
Vex.this.getLookControl().setLookAt(blockPos2.getX() + 0.5, blockPos2.getY() + 0.5, blockPos2.getZ() + 0.5, 180.0F, 20.0F);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|