2023-08-17 20:53:13 +02:00
|
|
|
package com.minelittlepony.unicopia.entity;
|
|
|
|
|
|
|
|
import java.util.Optional;
|
2023-08-25 23:06:08 +02:00
|
|
|
import java.util.function.Predicate;
|
|
|
|
import java.util.stream.Stream;
|
2023-08-17 20:53:13 +02:00
|
|
|
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
import com.minelittlepony.unicopia.USounds;
|
|
|
|
import com.minelittlepony.unicopia.entity.ai.ArenaAttackGoal;
|
2023-08-17 20:53:13 +02:00
|
|
|
import com.minelittlepony.unicopia.item.AmuletItem;
|
|
|
|
import com.minelittlepony.unicopia.item.UItems;
|
2023-08-25 23:06:08 +02:00
|
|
|
import com.minelittlepony.unicopia.particle.ParticleSource;
|
|
|
|
import com.minelittlepony.unicopia.particle.ParticleUtils;
|
2023-08-17 20:53:13 +02:00
|
|
|
import com.minelittlepony.unicopia.util.VecHelper;
|
2023-08-25 23:06:08 +02:00
|
|
|
import com.minelittlepony.unicopia.util.shape.Sphere;
|
2023-08-17 20:53:13 +02:00
|
|
|
|
|
|
|
import net.minecraft.block.BlockState;
|
|
|
|
import net.minecraft.entity.Entity;
|
|
|
|
import net.minecraft.entity.EntityType;
|
2023-08-26 17:16:31 +02:00
|
|
|
import net.minecraft.entity.ExperienceOrbEntity;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.entity.LivingEntity;
|
2023-08-26 17:16:31 +02:00
|
|
|
import net.minecraft.entity.MovementType;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.entity.ai.goal.ActiveTargetGoal;
|
2023-08-25 23:06:08 +02:00
|
|
|
import net.minecraft.entity.ai.goal.FlyGoal;
|
|
|
|
import net.minecraft.entity.ai.goal.LongDoorInteractGoal;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.entity.ai.goal.LookAroundGoal;
|
|
|
|
import net.minecraft.entity.ai.goal.LookAtEntityGoal;
|
|
|
|
import net.minecraft.entity.ai.goal.PounceAtTargetGoal;
|
|
|
|
import net.minecraft.entity.ai.goal.RevengeGoal;
|
|
|
|
import net.minecraft.entity.ai.goal.WanderAroundGoal;
|
2023-08-25 23:06:08 +02:00
|
|
|
import net.minecraft.entity.ai.pathing.BirdPathNodeMaker;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.entity.ai.pathing.EntityNavigation;
|
|
|
|
import net.minecraft.entity.ai.pathing.MobNavigation;
|
2023-08-25 23:06:08 +02:00
|
|
|
import net.minecraft.entity.ai.pathing.PathNodeNavigator;
|
|
|
|
import net.minecraft.entity.ai.pathing.PathNodeType;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.entity.attribute.DefaultAttributeContainer;
|
|
|
|
import net.minecraft.entity.attribute.EntityAttributes;
|
|
|
|
import net.minecraft.entity.boss.BossBar;
|
|
|
|
import net.minecraft.entity.boss.ServerBossBar;
|
2023-08-26 17:16:31 +02:00
|
|
|
import net.minecraft.entity.boss.BossBar.Style;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.entity.damage.DamageSource;
|
|
|
|
import net.minecraft.entity.data.DataTracker;
|
|
|
|
import net.minecraft.entity.data.TrackedData;
|
|
|
|
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
|
|
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
|
|
|
import net.minecraft.entity.effect.StatusEffects;
|
|
|
|
import net.minecraft.entity.mob.HostileEntity;
|
|
|
|
import net.minecraft.entity.passive.IronGolemEntity;
|
|
|
|
import net.minecraft.entity.passive.MerchantEntity;
|
|
|
|
import net.minecraft.entity.player.PlayerEntity;
|
|
|
|
import net.minecraft.item.ItemStack;
|
|
|
|
import net.minecraft.nbt.NbtCompound;
|
|
|
|
import net.minecraft.nbt.NbtElement;
|
|
|
|
import net.minecraft.nbt.NbtHelper;
|
2023-08-25 23:06:08 +02:00
|
|
|
import net.minecraft.particle.ParticleEffect;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.particle.ParticleTypes;
|
|
|
|
import net.minecraft.predicate.entity.EntityPredicates;
|
|
|
|
import net.minecraft.registry.tag.*;
|
|
|
|
import net.minecraft.server.network.ServerPlayerEntity;
|
2023-08-25 23:06:08 +02:00
|
|
|
import net.minecraft.server.world.ServerWorld;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.sound.SoundEvent;
|
|
|
|
import net.minecraft.sound.SoundEvents;
|
|
|
|
import net.minecraft.text.Text;
|
|
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
import net.minecraft.util.math.Direction;
|
2023-08-25 23:06:08 +02:00
|
|
|
import net.minecraft.util.math.MathHelper;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.util.math.Vec3d;
|
2023-08-26 17:16:31 +02:00
|
|
|
import net.minecraft.world.GameRules;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.world.World;
|
2023-08-26 17:16:31 +02:00
|
|
|
import net.minecraft.world.WorldEvents;
|
2023-08-17 20:53:13 +02:00
|
|
|
import net.minecraft.world.event.GameEvent;
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
public class SombraEntity extends HostileEntity implements ArenaCombatant, ParticleSource<SombraEntity> {
|
|
|
|
static final byte BITE = 70;
|
|
|
|
static final int MAX_BITE_TIME = 20;
|
|
|
|
static final Predicate<Entity> EFFECT_TARGET_PREDICATE = EntityPredicates.VALID_LIVING_ENTITY.and(EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR);
|
2023-08-17 20:53:13 +02:00
|
|
|
|
|
|
|
private static final TrackedData<Optional<BlockPos>> HOME_POS = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.OPTIONAL_BLOCK_POS);
|
2023-08-26 17:16:31 +02:00
|
|
|
private static final TrackedData<Float> TARGET_SIZE = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
2023-08-17 20:53:13 +02:00
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
private final ServerBossBar bossBar;
|
2023-08-25 23:06:08 +02:00
|
|
|
final EntityReference<StormCloudEntity> stormCloud = new EntityReference<>();
|
|
|
|
|
|
|
|
private int prevBiteTime;
|
|
|
|
private int biteTime;
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
private float prevSize;
|
|
|
|
private float currentSize;
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
public static void startEncounter(World world, BlockPos pos) {
|
|
|
|
StormCloudEntity cloud = UEntities.STORM_CLOUD.create(world);
|
|
|
|
cloud.setPosition(pos.up(10).toCenterPos());
|
|
|
|
cloud.setSize(1);
|
|
|
|
cloud.cursed = true;
|
|
|
|
world.spawnEntity(cloud);
|
|
|
|
}
|
|
|
|
|
2023-08-17 20:53:13 +02:00
|
|
|
public SombraEntity(EntityType<SombraEntity> type, World world) {
|
2023-08-26 17:16:31 +02:00
|
|
|
this(type, world, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public SombraEntity(EntityType<SombraEntity> type, World world, @Nullable ServerBossBar bossBar) {
|
2023-08-17 20:53:13 +02:00
|
|
|
super(type, world);
|
2023-08-26 17:16:31 +02:00
|
|
|
this.bossBar = bossBar == null ? createBossBar(getDisplayName()) : bossBar;
|
|
|
|
this.bossBar.setName(getDisplayName());
|
|
|
|
this.bossBar.setStyle(Style.NOTCHED_10);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static ServerBossBar createBossBar(Text name) {
|
|
|
|
return new SombraBossBar(name);
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static DefaultAttributeContainer.Builder createMobAttributes() {
|
|
|
|
return HostileEntity.createMobAttributes()
|
|
|
|
.add(EntityAttributes.GENERIC_MAX_HEALTH, 2000)
|
2023-08-25 23:06:08 +02:00
|
|
|
.add(EntityAttributes.GENERIC_ATTACK_KNOCKBACK, 1.5)
|
|
|
|
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 22);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public SombraEntity asEntity() {
|
|
|
|
return this;
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected Entity.MoveEffect getMoveEffect() {
|
|
|
|
return Entity.MoveEffect.NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean canAvoidTraps() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected SoundEvent getHurtSound(DamageSource source) {
|
|
|
|
return SoundEvents.ENTITY_WARDEN_HURT;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected SoundEvent getDeathSound() {
|
|
|
|
return SoundEvents.ENTITY_WARDEN_DEATH;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void initDataTracker() {
|
|
|
|
super.initDataTracker();
|
|
|
|
dataTracker.startTracking(HOME_POS, Optional.empty());
|
2023-08-26 17:16:31 +02:00
|
|
|
dataTracker.startTracking(TARGET_SIZE, 1F);
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void initGoals() {
|
2023-08-25 23:06:08 +02:00
|
|
|
goalSelector.add(2, new LongDoorInteractGoal(this, true));
|
|
|
|
goalSelector.add(5, new FlyGoal(this, 1));
|
|
|
|
goalSelector.add(6, new LookAtEntityGoal(this, PlayerEntity.class, 18F));
|
2023-08-17 20:53:13 +02:00
|
|
|
goalSelector.add(7, new LookAroundGoal(this));
|
2023-08-25 23:06:08 +02:00
|
|
|
goalSelector.add(7, new WanderAroundGoal(this, 1));
|
|
|
|
goalSelector.add(8, new PounceAtTargetGoal(this, 1.3F));
|
|
|
|
goalSelector.add(8, new ArenaAttackGoal<>(this));
|
2023-08-17 20:53:13 +02:00
|
|
|
targetSelector.add(1, new RevengeGoal(this));
|
|
|
|
targetSelector.add(2, new ActiveTargetGoal<>(this, PlayerEntity.class, false));
|
|
|
|
targetSelector.add(3, new ActiveTargetGoal<>(this, MerchantEntity.class, false));
|
|
|
|
targetSelector.add(3, new ActiveTargetGoal<>(this, IronGolemEntity.class, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected EntityNavigation createNavigation(World world) {
|
2023-08-25 23:06:08 +02:00
|
|
|
MobNavigation nav = new MobNavigation(this, world) {
|
|
|
|
@Override
|
|
|
|
protected PathNodeNavigator createPathNodeNavigator(int range) {
|
|
|
|
nodeMaker = new BirdPathNodeMaker();
|
|
|
|
nodeMaker.setCanEnterOpenDoors(true);
|
|
|
|
return new PathNodeNavigator(nodeMaker, range);
|
|
|
|
}
|
|
|
|
};
|
2023-08-17 20:53:13 +02:00
|
|
|
nav.setCanPathThroughDoors(true);
|
|
|
|
nav.setCanSwim(true);
|
|
|
|
nav.setCanEnterOpenDoors(true);
|
2023-08-25 23:06:08 +02:00
|
|
|
nav.canJumpToNext(PathNodeType.UNPASSABLE_RAIL);
|
2023-08-17 20:53:13 +02:00
|
|
|
return nav;
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
@Override
|
2023-08-17 20:53:13 +02:00
|
|
|
public Optional<BlockPos> getHomePos() {
|
|
|
|
return dataTracker.get(HOME_POS);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setHomePos(BlockPos pos) {
|
|
|
|
dataTracker.set(HOME_POS, Optional.of(pos));
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
public float getBiteAmount(float tickDelta) {
|
|
|
|
float progress = (MathHelper.lerp(tickDelta, prevBiteTime, biteTime) / (float)MAX_BITE_TIME);
|
2023-08-26 17:16:31 +02:00
|
|
|
return 1 - Math.abs(MathHelper.sin(progress * MathHelper.PI * 3));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public float getScaleFactor() {
|
|
|
|
return Math.max(1, dataTracker.get(TARGET_SIZE));
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getScaleFactor(float tickDelta) {
|
|
|
|
return MathHelper.lerp(tickDelta, prevSize, currentSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setScaleFactor(float targetSize) {
|
|
|
|
dataTracker.set(TARGET_SIZE, targetSize);
|
|
|
|
calculateDimensions();
|
2023-08-25 23:06:08 +02:00
|
|
|
}
|
|
|
|
|
2023-08-17 20:53:13 +02:00
|
|
|
@Override
|
|
|
|
public void tick() {
|
2023-08-25 23:06:08 +02:00
|
|
|
setPersistent();
|
2023-08-17 20:53:13 +02:00
|
|
|
Optional<BlockPos> homePos = getHomePos();
|
|
|
|
|
|
|
|
if (homePos.isEmpty() && !isRemoved()) {
|
2023-08-26 17:16:31 +02:00
|
|
|
setHomePos(getBlockPos());
|
|
|
|
homePos = getHomePos();
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
if (getBlockPos().getSquaredDistance(homePos.get()) > MathHelper.square(getAreaRadius())) {
|
2023-08-17 20:53:13 +02:00
|
|
|
teleportTo(Vec3d.ofCenter(homePos.get()));
|
2023-08-25 23:06:08 +02:00
|
|
|
getNavigation().stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
prevBiteTime = biteTime;
|
|
|
|
if (biteTime > 0) {
|
|
|
|
biteTime--;
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
float targetSize = getScaleFactor();
|
|
|
|
boolean sizeChanging = prevSize != currentSize;
|
|
|
|
prevSize = currentSize;
|
|
|
|
tickGrowth(targetSize, sizeChanging);
|
|
|
|
|
|
|
|
if (!hasPositionTarget() && homePos.isPresent()) {
|
|
|
|
setPositionTarget(homePos.get(), (int)getAreaRadius());
|
|
|
|
}
|
|
|
|
|
|
|
|
setVelocity(Vec3d.ZERO);
|
2023-08-17 20:53:13 +02:00
|
|
|
super.tick();
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
if (getTarget() == null && getVelocity().y < 0) {
|
|
|
|
setVelocity(getVelocity().multiply(1, 0.4, 1));
|
|
|
|
}
|
2023-08-26 17:16:31 +02:00
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
addVelocity(0, 0.0242F, 0);
|
2023-08-26 17:16:31 +02:00
|
|
|
|
|
|
|
if (isDead()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-08-17 20:53:13 +02:00
|
|
|
if (isSubmergedInWater()) {
|
|
|
|
jump();
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
if (random.nextInt(1200) == 0) {
|
2023-08-26 17:16:31 +02:00
|
|
|
laugh();
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
if (random.nextInt(340) == 0) {
|
2023-08-17 20:53:13 +02:00
|
|
|
playSound(SoundEvents.AMBIENT_CAVE.value(), 1, 0.3F);
|
2023-08-25 23:06:08 +02:00
|
|
|
} else if (random.nextInt(1340) == 0) {
|
|
|
|
playSound(USounds.ENTITY_SOMBRA_AMBIENT, 1, 1);
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (getWorld().isClient) {
|
2023-08-25 23:06:08 +02:00
|
|
|
generateBodyParticles();
|
|
|
|
} else {
|
|
|
|
for (BlockPos p : BlockPos.iterateOutwards(getBlockPos(), 2, 1, 2)) {
|
|
|
|
if (getWorld().getBlockState(p).getLuminance() > 3) {
|
|
|
|
destroyLightSource(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
if (random.nextInt(200) == 0) {
|
|
|
|
for (BlockPos p : BlockPos.iterateRandomly(random, 3, getBlockPos(), 20)) {
|
|
|
|
CrystalShardsEntity.infestBlock((ServerWorld)getWorld(), p);
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
if (getTarget() == null && getNavigation().isIdle()) {
|
|
|
|
getNavigation().startMovingTo(homePos.get().getX(), homePos.get().getY() + 10, homePos.get().getZ(), 2);
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
getHomePos().ifPresent(this::generateArenaEffects);
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
protected void tickGrowth(float targetSize, boolean changing) {
|
|
|
|
if (currentSize != targetSize) {
|
|
|
|
float sizeDifference = (dataTracker.get(TARGET_SIZE) - currentSize);
|
|
|
|
currentSize = Math.abs(sizeDifference) < 0.01F ? targetSize : currentSize + (sizeDifference * 0.2F);
|
|
|
|
calculateDimensions();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentSize == targetSize && changing) {
|
|
|
|
laugh();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentSize == targetSize && isDead()) {
|
|
|
|
setScaleFactor(currentSize + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void laugh() {
|
|
|
|
if (!getWorld().isClient) {
|
|
|
|
playSound(USounds.ENTITY_SOMBRA_LAUGH, 1, 1);
|
|
|
|
getWorld().sendEntityStatus(this, BITE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
protected void applyAreaEffects(Entity target) {
|
|
|
|
if (this.age % 150 == 0) {
|
|
|
|
target.playSound(
|
|
|
|
random.nextInt(30) == 0 ? USounds.ENTITY_SOMBRA_AMBIENT
|
|
|
|
: random.nextInt(10) == 0 ? USounds.Vanilla.ENTITY_GHAST_AMBIENT
|
|
|
|
: USounds.Vanilla.AMBIENT_CAVE.value(),
|
|
|
|
(float)random.nextTriangular(1, 0.2F),
|
|
|
|
(float)random.nextTriangular(0.3F, 0.2F)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
((LivingEntity)target).addStatusEffect(new StatusEffectInstance(StatusEffects.BLINDNESS, 26, 0, true, false));
|
|
|
|
((LivingEntity)target).addStatusEffect(new StatusEffectInstance(StatusEffects.DARKNESS, 26, 0, true, false));
|
|
|
|
|
|
|
|
if (getTarget() == null && target instanceof PlayerEntity player && player.distanceTo(this) < getAreaRadius() / 2F) {
|
|
|
|
setTarget(player);
|
|
|
|
if (teleportTo(target.getPos())) {
|
|
|
|
setPosition(getPos().add(0, 4, 0));
|
|
|
|
}
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
protected void destroyLightSource(BlockPos pos) {
|
|
|
|
getWorld().breakBlock(pos, true);
|
|
|
|
playSound(USounds.ENTITY_SOMBRA_SNICKER, 1, 1);
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
protected void generateBodyParticles() {
|
|
|
|
for (int i = 0; i < 23; i++) {
|
|
|
|
getWorld().addParticle(ParticleTypes.LARGE_SMOKE,
|
|
|
|
random.nextTriangular(getX(), 8),
|
|
|
|
random.nextTriangular(getY(), 1),
|
|
|
|
random.nextTriangular(getZ(), 8),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
private void generateArenaEffects(BlockPos home) {
|
|
|
|
if (getWorld().isClient()) {
|
|
|
|
Stream.concat(
|
|
|
|
new Sphere(false, getAreaRadius()).translate(home).randomPoints(random).filter(this::isSurfaceBlock).limit(80),
|
|
|
|
new Sphere(true, getAreaRadius()).translate(home).randomPoints(random).filter(this::isSurfaceBlock).limit(30))
|
|
|
|
.forEach(pos -> {
|
|
|
|
ParticleEffect type = random.nextInt(3) < 1 ? ParticleTypes.LARGE_SMOKE : ParticleTypes.SOUL_FIRE_FLAME;
|
|
|
|
ParticleUtils.spawnParticle(getWorld(), type, pos, Vec3d.ZERO);
|
|
|
|
ParticleUtils.spawnParticle(getWorld(), type, pos, pos.subtract(getPos()).add(0, 0.1, 0).multiply(-0.013));
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
for (Entity target : VecHelper.findInRange(this, getWorld(), home.toCenterPos(), getAreaRadius() - 0.2F, EFFECT_TARGET_PREDICATE)) {
|
|
|
|
applyAreaEffects(target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
private boolean isSurfaceBlock(Vec3d pos) {
|
|
|
|
BlockPos bPos = BlockPos.ofFloored(pos);
|
|
|
|
return getWorld().isAir(bPos) && !getWorld().isAir(bPos.down());
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2023-08-25 23:06:08 +02:00
|
|
|
protected void mobTick() {
|
|
|
|
super.mobTick();
|
|
|
|
bossBar.setPercent(getHealth() / getMaxHealth());
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
@Override
|
|
|
|
public boolean shouldRender(double distance) {
|
|
|
|
double d = 64 * getRenderDistanceMultiplier();
|
|
|
|
return distance < d * d;
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean handleFallDamage(float distance, float damageMultiplier, DamageSource cause) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean damage(DamageSource source, float amount) {
|
|
|
|
if (source.getAttacker() instanceof PlayerEntity player) {
|
|
|
|
if (AmuletSelectors.ALICORN_AMULET.test(player)) {
|
|
|
|
if (!getWorld().isClient) {
|
2023-08-25 23:06:08 +02:00
|
|
|
playSound(USounds.ENTITY_SOMBRA_SNICKER, 1, 1);
|
2023-08-17 20:53:13 +02:00
|
|
|
player.sendMessage(Text.translatable("entity.unicopia.sombra.taunt"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ItemStack amulet = AmuletItem.getForEntity(player);
|
|
|
|
if (amulet.isOf(UItems.ALICORN_AMULET)) {
|
|
|
|
amulet.decrement(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
boolean damaged = super.damage(source, amount);
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
if (!getWorld().isClient) {
|
|
|
|
if (source.getAttacker() instanceof PlayerEntity player) {
|
|
|
|
teleportRandomly(16);
|
|
|
|
}
|
|
|
|
|
|
|
|
float targetSize = getScaleFactor();
|
|
|
|
if (targetSize > 1) {
|
|
|
|
setScaleFactor(Math.max(1, targetSize * 0.9F));
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return damaged;
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
@Override
|
|
|
|
public void onDeath(DamageSource damageSource) {
|
|
|
|
if (!dead) {
|
|
|
|
stormCloud.ifPresent(getWorld(), cloud -> {
|
|
|
|
cloud.setStormTicks(0);
|
|
|
|
cloud.setDissipating(true);
|
|
|
|
});
|
|
|
|
stormCloud.set(null);
|
|
|
|
getHomePos().ifPresent(home -> {
|
|
|
|
VecHelper.findInRange(this, getWorld(), home.toCenterPos(), getAreaRadius() - 0.2F, e -> e instanceof CrystalShardsEntity).forEach(e -> {
|
|
|
|
((CrystalShardsEntity)e).setDecaying(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
super.onDeath(damageSource);
|
|
|
|
}
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
@Override
|
|
|
|
protected void updatePostDeath() {
|
|
|
|
if (++deathTime >= 180 && deathTime <= 200) {
|
|
|
|
getWorld().addParticle(ParticleTypes.EXPLOSION_EMITTER,
|
|
|
|
random.nextTriangular(getX(), 4F),
|
|
|
|
random.nextTriangular(getY() + 2, 2F),
|
|
|
|
random.nextTriangular(getZ(), 4F), 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
move(MovementType.SELF, new Vec3d(0, 0.3F, 0));
|
|
|
|
|
|
|
|
if (getWorld() instanceof ServerWorld sw) {
|
|
|
|
final boolean dropLoot = this.getWorld().getGameRules().getBoolean(GameRules.DO_MOB_LOOT);
|
|
|
|
final int experience = 500;
|
|
|
|
|
|
|
|
if (deathTime > 150 && deathTime % 5 == 0 && dropLoot) {
|
|
|
|
ExperienceOrbEntity.spawn(sw, getPos(), MathHelper.floor(experience * 0.08f));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deathTime == 1 && !isSilent()) {
|
|
|
|
getWorld().syncGlobalEvent(WorldEvents.ENDER_DRAGON_DIES, this.getBlockPos(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deathTime == 200) {
|
|
|
|
if (dropLoot) {
|
|
|
|
ExperienceOrbEntity.spawn(sw, getPos(), MathHelper.floor(experience * 0.2f));
|
|
|
|
}
|
|
|
|
remove(RemovalReason.KILLED);
|
|
|
|
emitGameEvent(GameEvent.ENTITY_DIE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 20:53:13 +02:00
|
|
|
@Override
|
|
|
|
protected void fall(double y, boolean onGroundIn, BlockState state, BlockPos pos) {
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
@Override
|
|
|
|
public boolean canTarget(LivingEntity target) {
|
|
|
|
return super.canTarget(target) && getHomePos().filter(home -> target.getPos().isInRange(home.toCenterPos(), getAreaRadius())).isPresent();
|
|
|
|
}
|
|
|
|
|
2023-08-26 17:16:31 +02:00
|
|
|
@Override
|
|
|
|
public boolean isInWalkTargetRange(BlockPos pos) {
|
|
|
|
BlockPos center = getHomePos().orElse(getBlockPos());
|
|
|
|
double distance = pos.getSquaredDistanceFromCenter(center.getX() + 0.5, center.getY() + 0.5, center.getZ() + 0.5);
|
|
|
|
return distance < MathHelper.square(getAreaRadius());
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
@Override
|
|
|
|
public boolean tryAttack(Entity target) {
|
2023-08-26 17:16:31 +02:00
|
|
|
laugh();
|
2023-08-25 23:06:08 +02:00
|
|
|
return super.tryAttack(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleStatus(byte status) {
|
|
|
|
if (status == BITE) {
|
|
|
|
biteTime = MAX_BITE_TIME;
|
|
|
|
} else {
|
|
|
|
super.handleStatus(status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 20:53:13 +02:00
|
|
|
protected boolean teleportRandomly(int maxDistance) {
|
|
|
|
if (getWorld().isClient() || !isAlive()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return teleportTo(getPos().add(VecHelper.supply(() -> random.nextTriangular(0, maxDistance))));
|
|
|
|
}
|
|
|
|
|
2023-08-25 23:06:08 +02:00
|
|
|
@Override
|
|
|
|
public boolean teleportTo(Vec3d destination) {
|
2023-08-17 20:53:13 +02:00
|
|
|
Vec3d oldPos = getPos();
|
|
|
|
if (canTeleportTo(destination) && teleport(destination.x, destination.y, destination.z, true)) {
|
|
|
|
getWorld().emitGameEvent(GameEvent.TELEPORT, oldPos, GameEvent.Emitter.of(this));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("deprecation")
|
|
|
|
private boolean canTeleportTo(Vec3d destination) {
|
|
|
|
BlockPos.Mutable mutable = new BlockPos.Mutable(destination.x, destination.y, destination.z);
|
|
|
|
while (mutable.getY() > getWorld().getBottomY() && !getWorld().getBlockState(mutable).blocksMovement()) {
|
|
|
|
mutable.move(Direction.DOWN);
|
|
|
|
}
|
|
|
|
BlockState destinationState = getWorld().getBlockState(mutable);
|
|
|
|
return destinationState.blocksMovement() && !destinationState.getFluidState().isIn(FluidTags.WATER);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Deprecated
|
|
|
|
public float getBrightnessAtEyes() {
|
|
|
|
return super.getBrightnessAtEyes() * 0.2F;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setCustomName(@Nullable Text name) {
|
|
|
|
super.setCustomName(name);
|
|
|
|
bossBar.setName(getDisplayName());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStartedTrackingBy(ServerPlayerEntity player) {
|
|
|
|
super.onStartedTrackingBy(player);
|
|
|
|
bossBar.addPlayer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStoppedTrackingBy(ServerPlayerEntity player) {
|
|
|
|
super.onStoppedTrackingBy(player);
|
|
|
|
bossBar.removePlayer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void writeCustomDataToNbt(NbtCompound nbt) {
|
|
|
|
super.writeCustomDataToNbt(nbt);
|
|
|
|
getHomePos().map(NbtHelper::fromBlockPos).ifPresent(pos -> {
|
|
|
|
nbt.put("homePos", pos);
|
|
|
|
});
|
2023-08-25 23:06:08 +02:00
|
|
|
nbt.put("cloud", stormCloud.toNBT());
|
2023-08-26 17:16:31 +02:00
|
|
|
nbt.putFloat("size", getScaleFactor());
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void readCustomDataFromNbt(NbtCompound nbt) {
|
|
|
|
super.readCustomDataFromNbt(nbt);
|
|
|
|
if (nbt.contains("homePos", NbtElement.COMPOUND_TYPE)) {
|
|
|
|
setHomePos(NbtHelper.toBlockPos(nbt.getCompound("homePos")));
|
|
|
|
}
|
|
|
|
if (hasCustomName()) {
|
|
|
|
bossBar.setName(getDisplayName());
|
|
|
|
}
|
2023-08-26 17:16:31 +02:00
|
|
|
setScaleFactor(nbt.getFloat("size"));
|
2023-08-25 23:06:08 +02:00
|
|
|
stormCloud.fromNBT(nbt.getCompound("cloud"));
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|
2023-08-26 17:16:31 +02:00
|
|
|
|
|
|
|
private static class SombraBossBar extends ServerBossBar {
|
|
|
|
public SombraBossBar(Text displayName) {
|
|
|
|
super(displayName, BossBar.Color.PURPLE, BossBar.Style.PROGRESS);
|
|
|
|
setDarkenSky(true);
|
|
|
|
setThickenFog(true);
|
|
|
|
setDragonMusic(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setPercent(float percent) {
|
|
|
|
super.setPercent(percent);
|
|
|
|
if (percent > 0.6F && getColor() == Color.PURPLE) {
|
|
|
|
setColor(Color.RED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-17 20:53:13 +02:00
|
|
|
}
|