368 lines
14 KiB
Java
368 lines
14 KiB
Java
/*
|
|
* 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<Byte> DATA_FLAGS_ID = SynchedEntityData.defineId(Vex.class, EntityDataSerializers.BYTE);
|
|
private static final int FLAG_IS_CHARGING = 1;
|
|
private @Nullable EntityReference<Mob> owner;
|
|
private @Nullable BlockPos boundOrigin;
|
|
private boolean hasLimitedLife;
|
|
private int limitedLifeTicks;
|
|
|
|
public Vex(EntityType<? extends Vex> type, Level level) {
|
|
super((EntityType<? extends Monster>)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<Player>((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();
|
|
}
|
|
}
|
|
}
|
|
|