minecraft-src/net/minecraft/world/entity/AreaEffectCloud.java
2025-07-04 03:45:38 +03:00

418 lines
14 KiB
Java

package net.minecraft.world.entity;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.UUIDUtil;
import net.minecraft.core.component.DataComponentGetter;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.particles.ColorParticleOption;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.SynchedEntityData.Builder;
import net.minecraft.resources.RegistryOps;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.ARGB;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.material.PushReaction;
import org.jetbrains.annotations.Nullable;
public class AreaEffectCloud extends Entity implements TraceableEntity {
private static final int TIME_BETWEEN_APPLICATIONS = 5;
private static final EntityDataAccessor<Float> DATA_RADIUS = SynchedEntityData.defineId(AreaEffectCloud.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> DATA_WAITING = SynchedEntityData.defineId(AreaEffectCloud.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<ParticleOptions> DATA_PARTICLE = SynchedEntityData.defineId(AreaEffectCloud.class, EntityDataSerializers.PARTICLE);
private static final float MAX_RADIUS = 32.0F;
private static final int DEFAULT_AGE = 0;
private static final int DEFAULT_DURATION_ON_USE = 0;
private static final float DEFAULT_RADIUS_ON_USE = 0.0F;
private static final float DEFAULT_RADIUS_PER_TICK = 0.0F;
private static final float DEFAULT_POTION_DURATION_SCALE = 1.0F;
private static final float MINIMAL_RADIUS = 0.5F;
private static final float DEFAULT_RADIUS = 3.0F;
public static final float DEFAULT_WIDTH = 6.0F;
public static final float HEIGHT = 0.5F;
public static final int INFINITE_DURATION = -1;
public static final int DEFAULT_LINGERING_DURATION = 600;
private static final int DEFAULT_WAIT_TIME = 20;
private static final int DEFAULT_REAPPLICATION_DELAY = 20;
private static final ColorParticleOption DEFAULT_PARTICLE = ColorParticleOption.create(ParticleTypes.ENTITY_EFFECT, -1);
private PotionContents potionContents = PotionContents.EMPTY;
private float potionDurationScale = 1.0F;
private final Map<Entity, Integer> victims = Maps.<Entity, Integer>newHashMap();
private int duration = -1;
private int waitTime = 20;
private int reapplicationDelay = 20;
private int durationOnUse = 0;
private float radiusOnUse = 0.0F;
private float radiusPerTick = 0.0F;
@Nullable
private LivingEntity owner;
@Nullable
private UUID ownerUUID;
public AreaEffectCloud(EntityType<? extends AreaEffectCloud> entityType, Level level) {
super(entityType, level);
this.noPhysics = true;
}
public AreaEffectCloud(Level level, double x, double y, double z) {
this(EntityType.AREA_EFFECT_CLOUD, level);
this.setPos(x, y, z);
}
@Override
protected void defineSynchedData(Builder builder) {
builder.define(DATA_RADIUS, 3.0F);
builder.define(DATA_WAITING, false);
builder.define(DATA_PARTICLE, DEFAULT_PARTICLE);
}
public void setRadius(float radius) {
if (!this.level().isClientSide) {
this.getEntityData().set(DATA_RADIUS, Mth.clamp(radius, 0.0F, 32.0F));
}
}
@Override
public void refreshDimensions() {
double d = this.getX();
double e = this.getY();
double f = this.getZ();
super.refreshDimensions();
this.setPos(d, e, f);
}
public float getRadius() {
return this.getEntityData().get(DATA_RADIUS);
}
public void setPotionContents(PotionContents potionContents) {
this.potionContents = potionContents;
this.updateColor();
}
public void setPotionDurationScale(float potionDurationScale) {
this.potionDurationScale = potionDurationScale;
}
private void updateColor() {
ParticleOptions particleOptions = this.entityData.get(DATA_PARTICLE);
if (particleOptions instanceof ColorParticleOption colorParticleOption) {
int i = this.potionContents.equals(PotionContents.EMPTY) ? 0 : this.potionContents.getColor();
this.entityData.set(DATA_PARTICLE, ColorParticleOption.create(colorParticleOption.getType(), ARGB.opaque(i)));
}
}
public void addEffect(MobEffectInstance effectInstance) {
this.setPotionContents(this.potionContents.withEffectAdded(effectInstance));
}
public ParticleOptions getParticle() {
return this.getEntityData().get(DATA_PARTICLE);
}
public void setParticle(ParticleOptions particleOption) {
this.getEntityData().set(DATA_PARTICLE, particleOption);
}
/**
* Sets if the cloud is waiting. While waiting, the radius is ignored and the cloud shows fewer particles in its area.
*/
protected void setWaiting(boolean waiting) {
this.getEntityData().set(DATA_WAITING, waiting);
}
/**
* Returns {@code true} if the cloud is waiting. While waiting, the radius is ignored and the cloud shows fewer particles in its area.
*/
public boolean isWaiting() {
return this.getEntityData().get(DATA_WAITING);
}
public int getDuration() {
return this.duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
@Override
public void tick() {
super.tick();
if (this.level() instanceof ServerLevel serverLevel) {
this.serverTick(serverLevel);
} else {
this.clientTick();
}
}
private void clientTick() {
boolean bl = this.isWaiting();
float f = this.getRadius();
if (!bl || !this.random.nextBoolean()) {
ParticleOptions particleOptions = this.getParticle();
int i;
float g;
if (bl) {
i = 2;
g = 0.2F;
} else {
i = Mth.ceil((float) Math.PI * f * f);
g = f;
}
for (int j = 0; j < i; j++) {
float h = this.random.nextFloat() * (float) (Math.PI * 2);
float k = Mth.sqrt(this.random.nextFloat()) * g;
double d = this.getX() + Mth.cos(h) * k;
double e = this.getY();
double l = this.getZ() + Mth.sin(h) * k;
if (particleOptions.getType() == ParticleTypes.ENTITY_EFFECT) {
if (bl && this.random.nextBoolean()) {
this.level().addAlwaysVisibleParticle(ColorParticleOption.create(ParticleTypes.ENTITY_EFFECT, -1), d, e, l, 0.0, 0.0, 0.0);
} else {
this.level().addAlwaysVisibleParticle(particleOptions, d, e, l, 0.0, 0.0, 0.0);
}
} else if (bl) {
this.level().addAlwaysVisibleParticle(particleOptions, d, e, l, 0.0, 0.0, 0.0);
} else {
this.level().addAlwaysVisibleParticle(particleOptions, d, e, l, (0.5 - this.random.nextDouble()) * 0.15, 0.01F, (0.5 - this.random.nextDouble()) * 0.15);
}
}
}
}
private void serverTick(ServerLevel level) {
if (this.duration != -1 && this.tickCount >= this.waitTime + this.duration) {
this.discard();
} else {
boolean bl = this.isWaiting();
boolean bl2 = this.tickCount < this.waitTime;
if (bl != bl2) {
this.setWaiting(bl2);
}
if (!bl2) {
float f = this.getRadius();
if (this.radiusPerTick != 0.0F) {
f += this.radiusPerTick;
if (f < 0.5F) {
this.discard();
return;
}
this.setRadius(f);
}
if (this.tickCount % 5 == 0) {
this.victims.entrySet().removeIf(entry -> this.tickCount >= (Integer)entry.getValue());
if (!this.potionContents.hasEffects()) {
this.victims.clear();
} else {
List<MobEffectInstance> list = new ArrayList();
this.potionContents.forEachEffect(list::add, this.potionDurationScale);
List<LivingEntity> list2 = this.level().getEntitiesOfClass(LivingEntity.class, this.getBoundingBox());
if (!list2.isEmpty()) {
for (LivingEntity livingEntity : list2) {
if (!this.victims.containsKey(livingEntity) && livingEntity.isAffectedByPotions() && !list.stream().noneMatch(livingEntity::canBeAffected)) {
double d = livingEntity.getX() - this.getX();
double e = livingEntity.getZ() - this.getZ();
double g = d * d + e * e;
if (g <= f * f) {
this.victims.put(livingEntity, this.tickCount + this.reapplicationDelay);
for (MobEffectInstance mobEffectInstance : list) {
if (mobEffectInstance.getEffect().value().isInstantenous()) {
mobEffectInstance.getEffect().value().applyInstantenousEffect(level, this, this.getOwner(), livingEntity, mobEffectInstance.getAmplifier(), 0.5);
} else {
livingEntity.addEffect(new MobEffectInstance(mobEffectInstance), this);
}
}
if (this.radiusOnUse != 0.0F) {
f += this.radiusOnUse;
if (f < 0.5F) {
this.discard();
return;
}
this.setRadius(f);
}
if (this.durationOnUse != 0 && this.duration != -1) {
this.duration = this.duration + this.durationOnUse;
if (this.duration <= 0) {
this.discard();
return;
}
}
}
}
}
}
}
}
}
}
}
public float getRadiusOnUse() {
return this.radiusOnUse;
}
public void setRadiusOnUse(float radiusOnUse) {
this.radiusOnUse = radiusOnUse;
}
public float getRadiusPerTick() {
return this.radiusPerTick;
}
public void setRadiusPerTick(float radiusPerTick) {
this.radiusPerTick = radiusPerTick;
}
public int getDurationOnUse() {
return this.durationOnUse;
}
public void setDurationOnUse(int durationOnUse) {
this.durationOnUse = durationOnUse;
}
public int getWaitTime() {
return this.waitTime;
}
public void setWaitTime(int waitTime) {
this.waitTime = waitTime;
}
public void setOwner(@Nullable LivingEntity owner) {
this.owner = owner;
this.ownerUUID = owner == null ? null : owner.getUUID();
}
@Nullable
public LivingEntity getOwner() {
if (this.owner != null && !this.owner.isRemoved()) {
return this.owner;
} else {
if (this.ownerUUID != null && this.level() instanceof ServerLevel serverLevel) {
this.owner = serverLevel.getEntity(this.ownerUUID) instanceof LivingEntity livingEntity ? livingEntity : null;
}
return this.owner;
}
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
this.tickCount = tag.getIntOr("Age", 0);
this.duration = tag.getIntOr("Duration", -1);
this.waitTime = tag.getIntOr("WaitTime", 20);
this.reapplicationDelay = tag.getIntOr("ReapplicationDelay", 20);
this.durationOnUse = tag.getIntOr("DurationOnUse", 0);
this.radiusOnUse = tag.getFloatOr("RadiusOnUse", 0.0F);
this.radiusPerTick = tag.getFloatOr("RadiusPerTick", 0.0F);
this.setRadius(tag.getFloatOr("Radius", 3.0F));
this.ownerUUID = (UUID)tag.read("Owner", UUIDUtil.CODEC).orElse(null);
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
this.setParticle((ParticleOptions)tag.read("Particle", ParticleTypes.CODEC, registryOps).orElse(DEFAULT_PARTICLE));
this.setPotionContents((PotionContents)tag.read("potion_contents", PotionContents.CODEC, registryOps).orElse(PotionContents.EMPTY));
this.potionDurationScale = tag.getFloatOr("potion_duration_scale", 1.0F);
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
tag.putInt("Age", this.tickCount);
tag.putInt("Duration", this.duration);
tag.putInt("WaitTime", this.waitTime);
tag.putInt("ReapplicationDelay", this.reapplicationDelay);
tag.putInt("DurationOnUse", this.durationOnUse);
tag.putFloat("RadiusOnUse", this.radiusOnUse);
tag.putFloat("RadiusPerTick", this.radiusPerTick);
tag.putFloat("Radius", this.getRadius());
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
tag.store("Particle", ParticleTypes.CODEC, registryOps, this.getParticle());
tag.storeNullable("Owner", UUIDUtil.CODEC, this.ownerUUID);
if (!this.potionContents.equals(PotionContents.EMPTY)) {
tag.store("potion_contents", PotionContents.CODEC, registryOps, this.potionContents);
}
if (this.potionDurationScale != 1.0F) {
tag.putFloat("potion_duration_scale", this.potionDurationScale);
}
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
if (DATA_RADIUS.equals(dataAccessor)) {
this.refreshDimensions();
}
super.onSyncedDataUpdated(dataAccessor);
}
@Override
public PushReaction getPistonPushReaction() {
return PushReaction.IGNORE;
}
@Override
public EntityDimensions getDimensions(Pose pose) {
return EntityDimensions.scalable(this.getRadius() * 2.0F, 0.5F);
}
@Override
public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
return false;
}
@Nullable
@Override
public <T> T get(DataComponentType<? extends T> component) {
if (component == DataComponents.POTION_CONTENTS) {
return castComponentValue((DataComponentType<T>)component, this.potionContents);
} else {
return component == DataComponents.POTION_DURATION_SCALE
? castComponentValue((DataComponentType<T>)component, this.potionDurationScale)
: super.get(component);
}
}
@Override
protected void applyImplicitComponents(DataComponentGetter componentGetter) {
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.POTION_CONTENTS);
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.POTION_DURATION_SCALE);
super.applyImplicitComponents(componentGetter);
}
@Override
protected <T> boolean applyImplicitComponent(DataComponentType<T> component, T value) {
if (component == DataComponents.POTION_CONTENTS) {
this.setPotionContents(castComponentValue(DataComponents.POTION_CONTENTS, value));
return true;
} else if (component == DataComponents.POTION_DURATION_SCALE) {
this.setPotionDurationScale(castComponentValue(DataComponents.POTION_DURATION_SCALE, value));
return true;
} else {
return super.applyImplicitComponent(component, value);
}
}
}