450 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.effect;
 | |
| 
 | |
| import com.google.common.collect.ComparisonChain;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import com.mojang.serialization.Codec;
 | |
| import com.mojang.serialization.MapCodec;
 | |
| import com.mojang.serialization.codecs.RecordCodecBuilder;
 | |
| import io.netty.buffer.ByteBuf;
 | |
| import it.unimi.dsi.fastutil.ints.Int2IntFunction;
 | |
| import java.util.Optional;
 | |
| import net.minecraft.core.Holder;
 | |
| import net.minecraft.core.particles.ParticleOptions;
 | |
| import net.minecraft.network.RegistryFriendlyByteBuf;
 | |
| import net.minecraft.network.codec.ByteBufCodecs;
 | |
| import net.minecraft.network.codec.StreamCodec;
 | |
| import net.minecraft.server.level.ServerLevel;
 | |
| import net.minecraft.util.ExtraCodecs;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.world.damagesource.DamageSource;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| import net.minecraft.world.entity.LivingEntity;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| public class MobEffectInstance implements Comparable<MobEffectInstance> {
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	public static final int INFINITE_DURATION = -1;
 | |
| 	public static final int MIN_AMPLIFIER = 0;
 | |
| 	public static final int MAX_AMPLIFIER = 255;
 | |
| 	public static final Codec<MobEffectInstance> CODEC = RecordCodecBuilder.create(
 | |
| 		instance -> instance.group(
 | |
| 				MobEffect.CODEC.fieldOf("id").forGetter(MobEffectInstance::getEffect), MobEffectInstance.Details.MAP_CODEC.forGetter(MobEffectInstance::asDetails)
 | |
| 			)
 | |
| 			.apply(instance, MobEffectInstance::new)
 | |
| 	);
 | |
| 	public static final StreamCodec<RegistryFriendlyByteBuf, MobEffectInstance> STREAM_CODEC = StreamCodec.composite(
 | |
| 		MobEffect.STREAM_CODEC, MobEffectInstance::getEffect, MobEffectInstance.Details.STREAM_CODEC, MobEffectInstance::asDetails, MobEffectInstance::new
 | |
| 	);
 | |
| 	private final Holder<MobEffect> effect;
 | |
| 	private int duration;
 | |
| 	private int amplifier;
 | |
| 	private boolean ambient;
 | |
| 	private boolean visible;
 | |
| 	private boolean showIcon;
 | |
| 	/**
 | |
| 	 * A hidden effect which is not shown to the player.
 | |
| 	 */
 | |
| 	@Nullable
 | |
| 	private MobEffectInstance hiddenEffect;
 | |
| 	private final MobEffectInstance.BlendState blendState = new MobEffectInstance.BlendState();
 | |
| 
 | |
| 	public MobEffectInstance(Holder<MobEffect> effect) {
 | |
| 		this(effect, 0, 0);
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance(Holder<MobEffect> effect, int duration) {
 | |
| 		this(effect, duration, 0);
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance(Holder<MobEffect> effect, int duration, int amplifier) {
 | |
| 		this(effect, duration, amplifier, false, true);
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance(Holder<MobEffect> effect, int duration, int amplifier, boolean ambient, boolean visible) {
 | |
| 		this(effect, duration, amplifier, ambient, visible, visible);
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance(Holder<MobEffect> effect, int duration, int amplifier, boolean ambient, boolean visible, boolean showIcon) {
 | |
| 		this(effect, duration, amplifier, ambient, visible, showIcon, null);
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance(
 | |
| 		Holder<MobEffect> effect, int duration, int amplifier, boolean ambient, boolean visible, boolean showIcon, @Nullable MobEffectInstance hiddenEffect
 | |
| 	) {
 | |
| 		this.effect = effect;
 | |
| 		this.duration = duration;
 | |
| 		this.amplifier = Mth.clamp(amplifier, 0, 255);
 | |
| 		this.ambient = ambient;
 | |
| 		this.visible = visible;
 | |
| 		this.showIcon = showIcon;
 | |
| 		this.hiddenEffect = hiddenEffect;
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance(MobEffectInstance other) {
 | |
| 		this.effect = other.effect;
 | |
| 		this.setDetailsFrom(other);
 | |
| 	}
 | |
| 
 | |
| 	private MobEffectInstance(Holder<MobEffect> effect, MobEffectInstance.Details details) {
 | |
| 		this(
 | |
| 			effect,
 | |
| 			details.duration(),
 | |
| 			details.amplifier(),
 | |
| 			details.ambient(),
 | |
| 			details.showParticles(),
 | |
| 			details.showIcon(),
 | |
| 			(MobEffectInstance)details.hiddenEffect().map(detailsx -> new MobEffectInstance(effect, detailsx)).orElse(null)
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	private MobEffectInstance.Details asDetails() {
 | |
| 		return new MobEffectInstance.Details(
 | |
| 			this.getAmplifier(),
 | |
| 			this.getDuration(),
 | |
| 			this.isAmbient(),
 | |
| 			this.isVisible(),
 | |
| 			this.showIcon(),
 | |
| 			Optional.ofNullable(this.hiddenEffect).map(MobEffectInstance::asDetails)
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	public float getBlendFactor(LivingEntity entity, float delta) {
 | |
| 		return this.blendState.getFactor(entity, delta);
 | |
| 	}
 | |
| 
 | |
| 	public ParticleOptions getParticleOptions() {
 | |
| 		return this.effect.value().createParticleOptions(this);
 | |
| 	}
 | |
| 
 | |
| 	void setDetailsFrom(MobEffectInstance effectInstance) {
 | |
| 		this.duration = effectInstance.duration;
 | |
| 		this.amplifier = effectInstance.amplifier;
 | |
| 		this.ambient = effectInstance.ambient;
 | |
| 		this.visible = effectInstance.visible;
 | |
| 		this.showIcon = effectInstance.showIcon;
 | |
| 	}
 | |
| 
 | |
| 	public boolean update(MobEffectInstance other) {
 | |
| 		if (!this.effect.equals(other.effect)) {
 | |
| 			LOGGER.warn("This method should only be called for matching effects!");
 | |
| 		}
 | |
| 
 | |
| 		boolean bl = false;
 | |
| 		if (other.amplifier > this.amplifier) {
 | |
| 			if (other.isShorterDurationThan(this)) {
 | |
| 				MobEffectInstance mobEffectInstance = this.hiddenEffect;
 | |
| 				this.hiddenEffect = new MobEffectInstance(this);
 | |
| 				this.hiddenEffect.hiddenEffect = mobEffectInstance;
 | |
| 			}
 | |
| 
 | |
| 			this.amplifier = other.amplifier;
 | |
| 			this.duration = other.duration;
 | |
| 			bl = true;
 | |
| 		} else if (this.isShorterDurationThan(other)) {
 | |
| 			if (other.amplifier == this.amplifier) {
 | |
| 				this.duration = other.duration;
 | |
| 				bl = true;
 | |
| 			} else if (this.hiddenEffect == null) {
 | |
| 				this.hiddenEffect = new MobEffectInstance(other);
 | |
| 			} else {
 | |
| 				this.hiddenEffect.update(other);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (!other.ambient && this.ambient || bl) {
 | |
| 			this.ambient = other.ambient;
 | |
| 			bl = true;
 | |
| 		}
 | |
| 
 | |
| 		if (other.visible != this.visible) {
 | |
| 			this.visible = other.visible;
 | |
| 			bl = true;
 | |
| 		}
 | |
| 
 | |
| 		if (other.showIcon != this.showIcon) {
 | |
| 			this.showIcon = other.showIcon;
 | |
| 			bl = true;
 | |
| 		}
 | |
| 
 | |
| 		return bl;
 | |
| 	}
 | |
| 
 | |
| 	private boolean isShorterDurationThan(MobEffectInstance other) {
 | |
| 		return !this.isInfiniteDuration() && (this.duration < other.duration || other.isInfiniteDuration());
 | |
| 	}
 | |
| 
 | |
| 	public boolean isInfiniteDuration() {
 | |
| 		return this.duration == -1;
 | |
| 	}
 | |
| 
 | |
| 	public boolean endsWithin(int duration) {
 | |
| 		return !this.isInfiniteDuration() && this.duration <= duration;
 | |
| 	}
 | |
| 
 | |
| 	public MobEffectInstance withScaledDuration(float scale) {
 | |
| 		MobEffectInstance mobEffectInstance = new MobEffectInstance(this);
 | |
| 		mobEffectInstance.duration = mobEffectInstance.mapDuration(i -> Math.max(Mth.floor(i * scale), 1));
 | |
| 		return mobEffectInstance;
 | |
| 	}
 | |
| 
 | |
| 	public int mapDuration(Int2IntFunction mapper) {
 | |
| 		return !this.isInfiniteDuration() && this.duration != 0 ? mapper.applyAsInt(this.duration) : this.duration;
 | |
| 	}
 | |
| 
 | |
| 	public Holder<MobEffect> getEffect() {
 | |
| 		return this.effect;
 | |
| 	}
 | |
| 
 | |
| 	public int getDuration() {
 | |
| 		return this.duration;
 | |
| 	}
 | |
| 
 | |
| 	public int getAmplifier() {
 | |
| 		return this.amplifier;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets whether this potion effect originated from a beacon
 | |
| 	 */
 | |
| 	public boolean isAmbient() {
 | |
| 		return this.ambient;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets whether this potion effect will show ambient particles or not.
 | |
| 	 */
 | |
| 	public boolean isVisible() {
 | |
| 		return this.visible;
 | |
| 	}
 | |
| 
 | |
| 	public boolean showIcon() {
 | |
| 		return this.showIcon;
 | |
| 	}
 | |
| 
 | |
| 	public boolean tickServer(ServerLevel level, LivingEntity entity, Runnable onEffectUpdated) {
 | |
| 		if (!this.hasRemainingDuration()) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			int i = this.isInfiniteDuration() ? entity.tickCount : this.duration;
 | |
| 			if (this.effect.value().shouldApplyEffectTickThisTick(i, this.amplifier) && !this.effect.value().applyEffectTick(level, entity, this.amplifier)) {
 | |
| 				return false;
 | |
| 			} else {
 | |
| 				this.tickDownDuration();
 | |
| 				if (this.downgradeToHiddenEffect()) {
 | |
| 					onEffectUpdated.run();
 | |
| 				}
 | |
| 
 | |
| 				return this.hasRemainingDuration();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void tickClient() {
 | |
| 		if (this.hasRemainingDuration()) {
 | |
| 			this.tickDownDuration();
 | |
| 			this.downgradeToHiddenEffect();
 | |
| 		}
 | |
| 
 | |
| 		this.blendState.tick(this);
 | |
| 	}
 | |
| 
 | |
| 	private boolean hasRemainingDuration() {
 | |
| 		return this.isInfiniteDuration() || this.duration > 0;
 | |
| 	}
 | |
| 
 | |
| 	private void tickDownDuration() {
 | |
| 		if (this.hiddenEffect != null) {
 | |
| 			this.hiddenEffect.tickDownDuration();
 | |
| 		}
 | |
| 
 | |
| 		this.duration = this.mapDuration(i -> i - 1);
 | |
| 	}
 | |
| 
 | |
| 	private boolean downgradeToHiddenEffect() {
 | |
| 		if (this.duration == 0 && this.hiddenEffect != null) {
 | |
| 			this.setDetailsFrom(this.hiddenEffect);
 | |
| 			this.hiddenEffect = this.hiddenEffect.hiddenEffect;
 | |
| 			return true;
 | |
| 		} else {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void onEffectStarted(LivingEntity entity) {
 | |
| 		this.effect.value().onEffectStarted(entity, this.amplifier);
 | |
| 	}
 | |
| 
 | |
| 	public void onMobRemoved(ServerLevel level, LivingEntity entity, Entity.RemovalReason reason) {
 | |
| 		this.effect.value().onMobRemoved(level, entity, this.amplifier, reason);
 | |
| 	}
 | |
| 
 | |
| 	public void onMobHurt(ServerLevel level, LivingEntity entity, DamageSource damageSource, float amount) {
 | |
| 		this.effect.value().onMobHurt(level, entity, this.amplifier, damageSource, amount);
 | |
| 	}
 | |
| 
 | |
| 	public String getDescriptionId() {
 | |
| 		return this.effect.value().getDescriptionId();
 | |
| 	}
 | |
| 
 | |
| 	public String toString() {
 | |
| 		String string;
 | |
| 		if (this.amplifier > 0) {
 | |
| 			string = this.getDescriptionId() + " x " + (this.amplifier + 1) + ", Duration: " + this.describeDuration();
 | |
| 		} else {
 | |
| 			string = this.getDescriptionId() + ", Duration: " + this.describeDuration();
 | |
| 		}
 | |
| 
 | |
| 		if (!this.visible) {
 | |
| 			string = string + ", Particles: false";
 | |
| 		}
 | |
| 
 | |
| 		if (!this.showIcon) {
 | |
| 			string = string + ", Show Icon: false";
 | |
| 		}
 | |
| 
 | |
| 		return string;
 | |
| 	}
 | |
| 
 | |
| 	private String describeDuration() {
 | |
| 		return this.isInfiniteDuration() ? "infinite" : Integer.toString(this.duration);
 | |
| 	}
 | |
| 
 | |
| 	public boolean equals(Object object) {
 | |
| 		if (this == object) {
 | |
| 			return true;
 | |
| 		} else {
 | |
| 			return !(object instanceof MobEffectInstance mobEffectInstance)
 | |
| 				? false
 | |
| 				: this.duration == mobEffectInstance.duration
 | |
| 					&& this.amplifier == mobEffectInstance.amplifier
 | |
| 					&& this.ambient == mobEffectInstance.ambient
 | |
| 					&& this.visible == mobEffectInstance.visible
 | |
| 					&& this.showIcon == mobEffectInstance.showIcon
 | |
| 					&& this.effect.equals(mobEffectInstance.effect);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public int hashCode() {
 | |
| 		int i = this.effect.hashCode();
 | |
| 		i = 31 * i + this.duration;
 | |
| 		i = 31 * i + this.amplifier;
 | |
| 		i = 31 * i + (this.ambient ? 1 : 0);
 | |
| 		i = 31 * i + (this.visible ? 1 : 0);
 | |
| 		return 31 * i + (this.showIcon ? 1 : 0);
 | |
| 	}
 | |
| 
 | |
| 	public int compareTo(MobEffectInstance other) {
 | |
| 		int i = 32147;
 | |
| 		return (this.getDuration() <= 32147 || other.getDuration() <= 32147) && (!this.isAmbient() || !other.isAmbient())
 | |
| 			? ComparisonChain.start()
 | |
| 				.compareFalseFirst(this.isAmbient(), other.isAmbient())
 | |
| 				.compareFalseFirst(this.isInfiniteDuration(), other.isInfiniteDuration())
 | |
| 				.compare(this.getDuration(), other.getDuration())
 | |
| 				.compare(this.getEffect().value().getColor(), other.getEffect().value().getColor())
 | |
| 				.result()
 | |
| 			: ComparisonChain.start()
 | |
| 				.compare(this.isAmbient(), other.isAmbient())
 | |
| 				.compare(this.getEffect().value().getColor(), other.getEffect().value().getColor())
 | |
| 				.result();
 | |
| 	}
 | |
| 
 | |
| 	public void onEffectAdded(LivingEntity livingEntity) {
 | |
| 		this.effect.value().onEffectAdded(livingEntity, this.amplifier);
 | |
| 	}
 | |
| 
 | |
| 	public boolean is(Holder<MobEffect> effect) {
 | |
| 		return this.effect.equals(effect);
 | |
| 	}
 | |
| 
 | |
| 	public void copyBlendState(MobEffectInstance effectInstance) {
 | |
| 		this.blendState.copyFrom(effectInstance.blendState);
 | |
| 	}
 | |
| 
 | |
| 	public void skipBlending() {
 | |
| 		this.blendState.setImmediate(this);
 | |
| 	}
 | |
| 
 | |
| 	static class BlendState {
 | |
| 		private float factor;
 | |
| 		private float factorPreviousFrame;
 | |
| 
 | |
| 		public void setImmediate(MobEffectInstance effectInstance) {
 | |
| 			this.factor = hasEffect(effectInstance) ? 1.0F : 0.0F;
 | |
| 			this.factorPreviousFrame = this.factor;
 | |
| 		}
 | |
| 
 | |
| 		public void copyFrom(MobEffectInstance.BlendState blendState) {
 | |
| 			this.factor = blendState.factor;
 | |
| 			this.factorPreviousFrame = blendState.factorPreviousFrame;
 | |
| 		}
 | |
| 
 | |
| 		public void tick(MobEffectInstance effect) {
 | |
| 			this.factorPreviousFrame = this.factor;
 | |
| 			boolean bl = hasEffect(effect);
 | |
| 			float f = bl ? 1.0F : 0.0F;
 | |
| 			if (this.factor != f) {
 | |
| 				MobEffect mobEffect = effect.getEffect().value();
 | |
| 				int i = bl ? mobEffect.getBlendInDurationTicks() : mobEffect.getBlendOutDurationTicks();
 | |
| 				if (i == 0) {
 | |
| 					this.factor = f;
 | |
| 				} else {
 | |
| 					float g = 1.0F / i;
 | |
| 					this.factor = this.factor + Mth.clamp(f - this.factor, -g, g);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private static boolean hasEffect(MobEffectInstance effect) {
 | |
| 			return !effect.endsWithin(effect.getEffect().value().getBlendOutAdvanceTicks());
 | |
| 		}
 | |
| 
 | |
| 		public float getFactor(LivingEntity entity, float delta) {
 | |
| 			if (entity.isRemoved()) {
 | |
| 				this.factorPreviousFrame = this.factor;
 | |
| 			}
 | |
| 
 | |
| 			return Mth.lerp(delta, this.factorPreviousFrame, this.factor);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	record Details(int amplifier, int duration, boolean ambient, boolean showParticles, boolean showIcon, Optional<MobEffectInstance.Details> hiddenEffect) {
 | |
| 		public static final MapCodec<MobEffectInstance.Details> MAP_CODEC = MapCodec.recursive(
 | |
| 			"MobEffectInstance.Details",
 | |
| 			codec -> RecordCodecBuilder.mapCodec(
 | |
| 				instance -> instance.group(
 | |
| 						ExtraCodecs.UNSIGNED_BYTE.optionalFieldOf("amplifier", 0).forGetter(MobEffectInstance.Details::amplifier),
 | |
| 						Codec.INT.optionalFieldOf("duration", 0).forGetter(MobEffectInstance.Details::duration),
 | |
| 						Codec.BOOL.optionalFieldOf("ambient", false).forGetter(MobEffectInstance.Details::ambient),
 | |
| 						Codec.BOOL.optionalFieldOf("show_particles", true).forGetter(MobEffectInstance.Details::showParticles),
 | |
| 						Codec.BOOL.optionalFieldOf("show_icon").forGetter(details -> Optional.of(details.showIcon())),
 | |
| 						codec.optionalFieldOf("hidden_effect").forGetter(MobEffectInstance.Details::hiddenEffect)
 | |
| 					)
 | |
| 					.apply(instance, MobEffectInstance.Details::create)
 | |
| 			)
 | |
| 		);
 | |
| 		public static final StreamCodec<ByteBuf, MobEffectInstance.Details> STREAM_CODEC = StreamCodec.recursive(
 | |
| 			streamCodec -> StreamCodec.composite(
 | |
| 				ByteBufCodecs.VAR_INT,
 | |
| 				MobEffectInstance.Details::amplifier,
 | |
| 				ByteBufCodecs.VAR_INT,
 | |
| 				MobEffectInstance.Details::duration,
 | |
| 				ByteBufCodecs.BOOL,
 | |
| 				MobEffectInstance.Details::ambient,
 | |
| 				ByteBufCodecs.BOOL,
 | |
| 				MobEffectInstance.Details::showParticles,
 | |
| 				ByteBufCodecs.BOOL,
 | |
| 				MobEffectInstance.Details::showIcon,
 | |
| 				streamCodec.apply(ByteBufCodecs::optional),
 | |
| 				MobEffectInstance.Details::hiddenEffect,
 | |
| 				MobEffectInstance.Details::new
 | |
| 			)
 | |
| 		);
 | |
| 
 | |
| 		private static MobEffectInstance.Details create(
 | |
| 			int amplifier, int duration, boolean ambient, boolean showParticles, Optional<Boolean> showIcon, Optional<MobEffectInstance.Details> hiddenEffect
 | |
| 		) {
 | |
| 			return new MobEffectInstance.Details(amplifier, duration, ambient, showParticles, (Boolean)showIcon.orElse(showParticles), hiddenEffect);
 | |
| 		}
 | |
| 	}
 | |
| }
 |