888 lines
33 KiB
Java
888 lines
33 KiB
Java
package net.minecraft.world.entity;
|
|
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.math.Transformation;
|
|
import com.mojang.serialization.Codec;
|
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.function.IntFunction;
|
|
import net.minecraft.commands.CommandSourceStack;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.ComponentSerialization;
|
|
import net.minecraft.network.chat.ComponentUtils;
|
|
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.Brightness;
|
|
import net.minecraft.util.ByIdMap;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.StringRepresentable;
|
|
import net.minecraft.util.ByIdMap.OutOfBoundsStrategy;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.Display.BlockDisplay.BlockRenderState;
|
|
import net.minecraft.world.entity.Display.ItemDisplay.ItemRenderState;
|
|
import net.minecraft.world.entity.Display.TextDisplay.Align;
|
|
import net.minecraft.world.entity.Display.TextDisplay.CachedInfo;
|
|
import net.minecraft.world.entity.Display.TextDisplay.LineSplitter;
|
|
import net.minecraft.world.entity.Display.TextDisplay.TextRenderState;
|
|
import net.minecraft.world.item.ItemDisplayContext;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.material.PushReaction;
|
|
import net.minecraft.world.phys.AABB;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.joml.Quaternionf;
|
|
import org.joml.Vector3f;
|
|
import org.slf4j.Logger;
|
|
|
|
public abstract class Display extends Entity {
|
|
static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final int NO_BRIGHTNESS_OVERRIDE = -1;
|
|
private static final EntityDataAccessor<Integer> DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID = SynchedEntityData.defineId(
|
|
Display.class, EntityDataSerializers.INT
|
|
);
|
|
private static final EntityDataAccessor<Integer> DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID = SynchedEntityData.defineId(
|
|
Display.class, EntityDataSerializers.INT
|
|
);
|
|
private static final EntityDataAccessor<Integer> DATA_POS_ROT_INTERPOLATION_DURATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Vector3f> DATA_TRANSLATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.VECTOR3);
|
|
private static final EntityDataAccessor<Vector3f> DATA_SCALE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.VECTOR3);
|
|
private static final EntityDataAccessor<Quaternionf> DATA_LEFT_ROTATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.QUATERNION);
|
|
private static final EntityDataAccessor<Quaternionf> DATA_RIGHT_ROTATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.QUATERNION);
|
|
private static final EntityDataAccessor<Byte> DATA_BILLBOARD_RENDER_CONSTRAINTS_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.BYTE);
|
|
private static final EntityDataAccessor<Integer> DATA_BRIGHTNESS_OVERRIDE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Float> DATA_VIEW_RANGE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT);
|
|
private static final EntityDataAccessor<Float> DATA_SHADOW_RADIUS_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT);
|
|
private static final EntityDataAccessor<Float> DATA_SHADOW_STRENGTH_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT);
|
|
private static final EntityDataAccessor<Float> DATA_WIDTH_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT);
|
|
private static final EntityDataAccessor<Float> DATA_HEIGHT_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT);
|
|
private static final EntityDataAccessor<Integer> DATA_GLOW_COLOR_OVERRIDE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.INT);
|
|
private static final IntSet RENDER_STATE_IDS = IntSet.of(
|
|
DATA_TRANSLATION_ID.id(),
|
|
DATA_SCALE_ID.id(),
|
|
DATA_LEFT_ROTATION_ID.id(),
|
|
DATA_RIGHT_ROTATION_ID.id(),
|
|
DATA_BILLBOARD_RENDER_CONSTRAINTS_ID.id(),
|
|
DATA_BRIGHTNESS_OVERRIDE_ID.id(),
|
|
DATA_SHADOW_RADIUS_ID.id(),
|
|
DATA_SHADOW_STRENGTH_ID.id()
|
|
);
|
|
private static final int INITIAL_TRANSFORMATION_INTERPOLATION_DURATION = 0;
|
|
private static final int INITIAL_TRANSFORMATION_START_INTERPOLATION = 0;
|
|
private static final int INITIAL_POS_ROT_INTERPOLATION_DURATION = 0;
|
|
private static final float INITIAL_SHADOW_RADIUS = 0.0F;
|
|
private static final float INITIAL_SHADOW_STRENGTH = 1.0F;
|
|
private static final float INITIAL_VIEW_RANGE = 1.0F;
|
|
private static final float INITIAL_WIDTH = 0.0F;
|
|
private static final float INITIAL_HEIGHT = 0.0F;
|
|
private static final int NO_GLOW_COLOR_OVERRIDE = -1;
|
|
public static final String TAG_POS_ROT_INTERPOLATION_DURATION = "teleport_duration";
|
|
public static final String TAG_TRANSFORMATION_INTERPOLATION_DURATION = "interpolation_duration";
|
|
public static final String TAG_TRANSFORMATION_START_INTERPOLATION = "start_interpolation";
|
|
public static final String TAG_TRANSFORMATION = "transformation";
|
|
public static final String TAG_BILLBOARD = "billboard";
|
|
public static final String TAG_BRIGHTNESS = "brightness";
|
|
public static final String TAG_VIEW_RANGE = "view_range";
|
|
public static final String TAG_SHADOW_RADIUS = "shadow_radius";
|
|
public static final String TAG_SHADOW_STRENGTH = "shadow_strength";
|
|
public static final String TAG_WIDTH = "width";
|
|
public static final String TAG_HEIGHT = "height";
|
|
public static final String TAG_GLOW_COLOR_OVERRIDE = "glow_color_override";
|
|
private long interpolationStartClientTick = -2147483648L;
|
|
private int interpolationDuration;
|
|
private float lastProgress;
|
|
private AABB cullingBoundingBox;
|
|
private boolean noCulling = true;
|
|
protected boolean updateRenderState;
|
|
private boolean updateStartTick;
|
|
private boolean updateInterpolationDuration;
|
|
@Nullable
|
|
private Display.RenderState renderState;
|
|
private final InterpolationHandler interpolation = new InterpolationHandler(this, 0);
|
|
|
|
public Display(EntityType<?> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.noPhysics = true;
|
|
this.cullingBoundingBox = this.getBoundingBox();
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
if (DATA_HEIGHT_ID.equals(dataAccessor) || DATA_WIDTH_ID.equals(dataAccessor)) {
|
|
this.updateCulling();
|
|
}
|
|
|
|
if (DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID.equals(dataAccessor)) {
|
|
this.updateStartTick = true;
|
|
}
|
|
|
|
if (DATA_POS_ROT_INTERPOLATION_DURATION_ID.equals(dataAccessor)) {
|
|
this.interpolation.setInterpolationLength(this.getPosRotInterpolationDuration());
|
|
}
|
|
|
|
if (DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID.equals(dataAccessor)) {
|
|
this.updateInterpolationDuration = true;
|
|
}
|
|
|
|
if (RENDER_STATE_IDS.contains(dataAccessor.id())) {
|
|
this.updateRenderState = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
|
|
return false;
|
|
}
|
|
|
|
private static Transformation createTransformation(SynchedEntityData synchedEntityData) {
|
|
Vector3f vector3f = synchedEntityData.get(DATA_TRANSLATION_ID);
|
|
Quaternionf quaternionf = synchedEntityData.get(DATA_LEFT_ROTATION_ID);
|
|
Vector3f vector3f2 = synchedEntityData.get(DATA_SCALE_ID);
|
|
Quaternionf quaternionf2 = synchedEntityData.get(DATA_RIGHT_ROTATION_ID);
|
|
return new Transformation(vector3f, quaternionf, vector3f2, quaternionf2);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
Entity entity = this.getVehicle();
|
|
if (entity != null && entity.isRemoved()) {
|
|
this.stopRiding();
|
|
}
|
|
|
|
if (this.level().isClientSide) {
|
|
if (this.updateStartTick) {
|
|
this.updateStartTick = false;
|
|
int i = this.getTransformationInterpolationDelay();
|
|
this.interpolationStartClientTick = this.tickCount + i;
|
|
}
|
|
|
|
if (this.updateInterpolationDuration) {
|
|
this.updateInterpolationDuration = false;
|
|
this.interpolationDuration = this.getTransformationInterpolationDuration();
|
|
}
|
|
|
|
if (this.updateRenderState) {
|
|
this.updateRenderState = false;
|
|
boolean bl = this.interpolationDuration != 0;
|
|
if (bl && this.renderState != null) {
|
|
this.renderState = this.createInterpolatedRenderState(this.renderState, this.lastProgress);
|
|
} else {
|
|
this.renderState = this.createFreshRenderState();
|
|
}
|
|
|
|
this.updateRenderSubState(bl, this.lastProgress);
|
|
}
|
|
|
|
this.interpolation.interpolate();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public InterpolationHandler getInterpolation() {
|
|
return this.interpolation;
|
|
}
|
|
|
|
protected abstract void updateRenderSubState(boolean interpolate, float partialTick);
|
|
|
|
@Override
|
|
protected void defineSynchedData(Builder builder) {
|
|
builder.define(DATA_POS_ROT_INTERPOLATION_DURATION_ID, 0);
|
|
builder.define(DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID, 0);
|
|
builder.define(DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID, 0);
|
|
builder.define(DATA_TRANSLATION_ID, new Vector3f());
|
|
builder.define(DATA_SCALE_ID, new Vector3f(1.0F, 1.0F, 1.0F));
|
|
builder.define(DATA_RIGHT_ROTATION_ID, new Quaternionf());
|
|
builder.define(DATA_LEFT_ROTATION_ID, new Quaternionf());
|
|
builder.define(DATA_BILLBOARD_RENDER_CONSTRAINTS_ID, Display.BillboardConstraints.FIXED.getId());
|
|
builder.define(DATA_BRIGHTNESS_OVERRIDE_ID, -1);
|
|
builder.define(DATA_VIEW_RANGE_ID, 1.0F);
|
|
builder.define(DATA_SHADOW_RADIUS_ID, 0.0F);
|
|
builder.define(DATA_SHADOW_STRENGTH_ID, 1.0F);
|
|
builder.define(DATA_WIDTH_ID, 0.0F);
|
|
builder.define(DATA_HEIGHT_ID, 0.0F);
|
|
builder.define(DATA_GLOW_COLOR_OVERRIDE_ID, -1);
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(CompoundTag tag) {
|
|
this.setTransformation((Transformation)tag.read("transformation", Transformation.EXTENDED_CODEC).orElse(Transformation.identity()));
|
|
this.setTransformationInterpolationDuration(tag.getIntOr("interpolation_duration", 0));
|
|
this.setTransformationInterpolationDelay(tag.getIntOr("start_interpolation", 0));
|
|
int i = tag.getIntOr("teleport_duration", 0);
|
|
this.setPosRotInterpolationDuration(Mth.clamp(i, 0, 59));
|
|
this.setBillboardConstraints(
|
|
(Display.BillboardConstraints)tag.read("billboard", Display.BillboardConstraints.CODEC).orElse(Display.BillboardConstraints.FIXED)
|
|
);
|
|
this.setViewRange(tag.getFloatOr("view_range", 1.0F));
|
|
this.setShadowRadius(tag.getFloatOr("shadow_radius", 0.0F));
|
|
this.setShadowStrength(tag.getFloatOr("shadow_strength", 1.0F));
|
|
this.setWidth(tag.getFloatOr("width", 0.0F));
|
|
this.setHeight(tag.getFloatOr("height", 0.0F));
|
|
this.setGlowColorOverride(tag.getIntOr("glow_color_override", -1));
|
|
this.setBrightnessOverride((Brightness)tag.read("brightness", Brightness.CODEC).orElse(null));
|
|
}
|
|
|
|
private void setTransformation(Transformation transformation) {
|
|
this.entityData.set(DATA_TRANSLATION_ID, transformation.getTranslation());
|
|
this.entityData.set(DATA_LEFT_ROTATION_ID, transformation.getLeftRotation());
|
|
this.entityData.set(DATA_SCALE_ID, transformation.getScale());
|
|
this.entityData.set(DATA_RIGHT_ROTATION_ID, transformation.getRightRotation());
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(CompoundTag tag) {
|
|
tag.store("transformation", Transformation.EXTENDED_CODEC, createTransformation(this.entityData));
|
|
tag.store("billboard", Display.BillboardConstraints.CODEC, this.getBillboardConstraints());
|
|
tag.putInt("interpolation_duration", this.getTransformationInterpolationDuration());
|
|
tag.putInt("teleport_duration", this.getPosRotInterpolationDuration());
|
|
tag.putFloat("view_range", this.getViewRange());
|
|
tag.putFloat("shadow_radius", this.getShadowRadius());
|
|
tag.putFloat("shadow_strength", this.getShadowStrength());
|
|
tag.putFloat("width", this.getWidth());
|
|
tag.putFloat("height", this.getHeight());
|
|
tag.putInt("glow_color_override", this.getGlowColorOverride());
|
|
tag.storeNullable("brightness", Brightness.CODEC, this.getBrightnessOverride());
|
|
}
|
|
|
|
public AABB getBoundingBoxForCulling() {
|
|
return this.cullingBoundingBox;
|
|
}
|
|
|
|
public boolean affectedByCulling() {
|
|
return !this.noCulling;
|
|
}
|
|
|
|
@Override
|
|
public PushReaction getPistonPushReaction() {
|
|
return PushReaction.IGNORE;
|
|
}
|
|
|
|
@Override
|
|
public boolean isIgnoringBlockTriggers() {
|
|
return true;
|
|
}
|
|
|
|
@Nullable
|
|
public Display.RenderState renderState() {
|
|
return this.renderState;
|
|
}
|
|
|
|
private void setTransformationInterpolationDuration(int transformationInterpolationDuration) {
|
|
this.entityData.set(DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID, transformationInterpolationDuration);
|
|
}
|
|
|
|
private int getTransformationInterpolationDuration() {
|
|
return this.entityData.get(DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID);
|
|
}
|
|
|
|
private void setTransformationInterpolationDelay(int transformationInterpolationDelay) {
|
|
this.entityData.set(DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID, transformationInterpolationDelay, true);
|
|
}
|
|
|
|
private int getTransformationInterpolationDelay() {
|
|
return this.entityData.get(DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID);
|
|
}
|
|
|
|
private void setPosRotInterpolationDuration(int posRotInterpolationDuration) {
|
|
this.entityData.set(DATA_POS_ROT_INTERPOLATION_DURATION_ID, posRotInterpolationDuration);
|
|
}
|
|
|
|
private int getPosRotInterpolationDuration() {
|
|
return this.entityData.get(DATA_POS_ROT_INTERPOLATION_DURATION_ID);
|
|
}
|
|
|
|
private void setBillboardConstraints(Display.BillboardConstraints billboardConstraints) {
|
|
this.entityData.set(DATA_BILLBOARD_RENDER_CONSTRAINTS_ID, billboardConstraints.getId());
|
|
}
|
|
|
|
private Display.BillboardConstraints getBillboardConstraints() {
|
|
return (Display.BillboardConstraints)Display.BillboardConstraints.BY_ID.apply(this.entityData.get(DATA_BILLBOARD_RENDER_CONSTRAINTS_ID));
|
|
}
|
|
|
|
private void setBrightnessOverride(@Nullable Brightness brightnessOverride) {
|
|
this.entityData.set(DATA_BRIGHTNESS_OVERRIDE_ID, brightnessOverride != null ? brightnessOverride.pack() : -1);
|
|
}
|
|
|
|
@Nullable
|
|
private Brightness getBrightnessOverride() {
|
|
int i = this.entityData.get(DATA_BRIGHTNESS_OVERRIDE_ID);
|
|
return i != -1 ? Brightness.unpack(i) : null;
|
|
}
|
|
|
|
private int getPackedBrightnessOverride() {
|
|
return this.entityData.get(DATA_BRIGHTNESS_OVERRIDE_ID);
|
|
}
|
|
|
|
private void setViewRange(float viewRange) {
|
|
this.entityData.set(DATA_VIEW_RANGE_ID, viewRange);
|
|
}
|
|
|
|
private float getViewRange() {
|
|
return this.entityData.get(DATA_VIEW_RANGE_ID);
|
|
}
|
|
|
|
private void setShadowRadius(float shadowRadius) {
|
|
this.entityData.set(DATA_SHADOW_RADIUS_ID, shadowRadius);
|
|
}
|
|
|
|
private float getShadowRadius() {
|
|
return this.entityData.get(DATA_SHADOW_RADIUS_ID);
|
|
}
|
|
|
|
private void setShadowStrength(float shadowStrength) {
|
|
this.entityData.set(DATA_SHADOW_STRENGTH_ID, shadowStrength);
|
|
}
|
|
|
|
private float getShadowStrength() {
|
|
return this.entityData.get(DATA_SHADOW_STRENGTH_ID);
|
|
}
|
|
|
|
private void setWidth(float width) {
|
|
this.entityData.set(DATA_WIDTH_ID, width);
|
|
}
|
|
|
|
private float getWidth() {
|
|
return this.entityData.get(DATA_WIDTH_ID);
|
|
}
|
|
|
|
private void setHeight(float height) {
|
|
this.entityData.set(DATA_HEIGHT_ID, height);
|
|
}
|
|
|
|
private int getGlowColorOverride() {
|
|
return this.entityData.get(DATA_GLOW_COLOR_OVERRIDE_ID);
|
|
}
|
|
|
|
private void setGlowColorOverride(int glowColorOverride) {
|
|
this.entityData.set(DATA_GLOW_COLOR_OVERRIDE_ID, glowColorOverride);
|
|
}
|
|
|
|
public float calculateInterpolationProgress(float partialTick) {
|
|
int i = this.interpolationDuration;
|
|
if (i <= 0) {
|
|
return 1.0F;
|
|
} else {
|
|
float f = (float)(this.tickCount - this.interpolationStartClientTick);
|
|
float g = f + partialTick;
|
|
float h = Mth.clamp(Mth.inverseLerp(g, 0.0F, (float)i), 0.0F, 1.0F);
|
|
this.lastProgress = h;
|
|
return h;
|
|
}
|
|
}
|
|
|
|
private float getHeight() {
|
|
return this.entityData.get(DATA_HEIGHT_ID);
|
|
}
|
|
|
|
@Override
|
|
public void setPos(double x, double y, double z) {
|
|
super.setPos(x, y, z);
|
|
this.updateCulling();
|
|
}
|
|
|
|
private void updateCulling() {
|
|
float f = this.getWidth();
|
|
float g = this.getHeight();
|
|
this.noCulling = f == 0.0F || g == 0.0F;
|
|
float h = f / 2.0F;
|
|
double d = this.getX();
|
|
double e = this.getY();
|
|
double i = this.getZ();
|
|
this.cullingBoundingBox = new AABB(d - h, e, i - h, d + h, e + g, i + h);
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldRenderAtSqrDistance(double distance) {
|
|
return distance < Mth.square(this.getViewRange() * 64.0 * getViewScale());
|
|
}
|
|
|
|
@Override
|
|
public int getTeamColor() {
|
|
int i = this.getGlowColorOverride();
|
|
return i != -1 ? i : super.getTeamColor();
|
|
}
|
|
|
|
private Display.RenderState createFreshRenderState() {
|
|
return new Display.RenderState(
|
|
Display.GenericInterpolator.constant(createTransformation(this.entityData)),
|
|
this.getBillboardConstraints(),
|
|
this.getPackedBrightnessOverride(),
|
|
Display.FloatInterpolator.constant(this.getShadowRadius()),
|
|
Display.FloatInterpolator.constant(this.getShadowStrength()),
|
|
this.getGlowColorOverride()
|
|
);
|
|
}
|
|
|
|
private Display.RenderState createInterpolatedRenderState(Display.RenderState renderState, float partialTick) {
|
|
Transformation transformation = renderState.transformation.get(partialTick);
|
|
float f = renderState.shadowRadius.get(partialTick);
|
|
float g = renderState.shadowStrength.get(partialTick);
|
|
return new Display.RenderState(
|
|
new Display.TransformationInterpolator(transformation, createTransformation(this.entityData)),
|
|
this.getBillboardConstraints(),
|
|
this.getPackedBrightnessOverride(),
|
|
new Display.LinearFloatInterpolator(f, this.getShadowRadius()),
|
|
new Display.LinearFloatInterpolator(g, this.getShadowStrength()),
|
|
this.getGlowColorOverride()
|
|
);
|
|
}
|
|
|
|
public static enum BillboardConstraints implements StringRepresentable {
|
|
FIXED((byte)0, "fixed"),
|
|
VERTICAL((byte)1, "vertical"),
|
|
HORIZONTAL((byte)2, "horizontal"),
|
|
CENTER((byte)3, "center");
|
|
|
|
public static final Codec<Display.BillboardConstraints> CODEC = StringRepresentable.fromEnum(Display.BillboardConstraints::values);
|
|
public static final IntFunction<Display.BillboardConstraints> BY_ID = ByIdMap.continuous(
|
|
Display.BillboardConstraints::getId, values(), OutOfBoundsStrategy.ZERO
|
|
);
|
|
private final byte id;
|
|
private final String name;
|
|
|
|
private BillboardConstraints(final byte id, final String name) {
|
|
this.name = name;
|
|
this.id = id;
|
|
}
|
|
|
|
@Override
|
|
public String getSerializedName() {
|
|
return this.name;
|
|
}
|
|
|
|
byte getId() {
|
|
return this.id;
|
|
}
|
|
}
|
|
|
|
public static class BlockDisplay extends Display {
|
|
public static final String TAG_BLOCK_STATE = "block_state";
|
|
private static final EntityDataAccessor<BlockState> DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(
|
|
Display.BlockDisplay.class, EntityDataSerializers.BLOCK_STATE
|
|
);
|
|
@Nullable
|
|
private BlockRenderState blockRenderState;
|
|
|
|
public BlockDisplay(EntityType<?> entityType, Level level) {
|
|
super(entityType, level);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(DATA_BLOCK_STATE_ID, Blocks.AIR.defaultBlockState());
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
if (dataAccessor.equals(DATA_BLOCK_STATE_ID)) {
|
|
this.updateRenderState = true;
|
|
}
|
|
}
|
|
|
|
private BlockState getBlockState() {
|
|
return this.entityData.get(DATA_BLOCK_STATE_ID);
|
|
}
|
|
|
|
private void setBlockState(BlockState blockState) {
|
|
this.entityData.set(DATA_BLOCK_STATE_ID, blockState);
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(CompoundTag tag) {
|
|
super.readAdditionalSaveData(tag);
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
this.setBlockState((BlockState)tag.read("block_state", BlockState.CODEC, registryOps).orElse(Blocks.AIR.defaultBlockState()));
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(CompoundTag tag) {
|
|
super.addAdditionalSaveData(tag);
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
tag.store("block_state", BlockState.CODEC, registryOps, this.getBlockState());
|
|
}
|
|
|
|
@Nullable
|
|
public BlockRenderState blockRenderState() {
|
|
return this.blockRenderState;
|
|
}
|
|
|
|
@Override
|
|
protected void updateRenderSubState(boolean interpolate, float partialTick) {
|
|
this.blockRenderState = new BlockRenderState(this.getBlockState());
|
|
}
|
|
}
|
|
|
|
record ColorInterpolator(int previous, int current) implements Display.IntInterpolator {
|
|
@Override
|
|
public int get(float f) {
|
|
return ARGB.lerp(f, this.previous, this.current);
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface FloatInterpolator {
|
|
static Display.FloatInterpolator constant(float f) {
|
|
return g -> f;
|
|
}
|
|
|
|
float get(float partialTick);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface GenericInterpolator<T> {
|
|
static <T> Display.GenericInterpolator<T> constant(T object) {
|
|
return f -> object;
|
|
}
|
|
|
|
T get(float partialTick);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface IntInterpolator {
|
|
static Display.IntInterpolator constant(int i) {
|
|
return f -> i;
|
|
}
|
|
|
|
int get(float partialTick);
|
|
}
|
|
|
|
public static class ItemDisplay extends Display {
|
|
private static final String TAG_ITEM = "item";
|
|
private static final String TAG_ITEM_DISPLAY = "item_display";
|
|
private static final EntityDataAccessor<ItemStack> DATA_ITEM_STACK_ID = SynchedEntityData.defineId(
|
|
Display.ItemDisplay.class, EntityDataSerializers.ITEM_STACK
|
|
);
|
|
private static final EntityDataAccessor<Byte> DATA_ITEM_DISPLAY_ID = SynchedEntityData.defineId(Display.ItemDisplay.class, EntityDataSerializers.BYTE);
|
|
private final SlotAccess slot = SlotAccess.of(this::getItemStack, this::setItemStack);
|
|
@Nullable
|
|
private ItemRenderState itemRenderState;
|
|
|
|
public ItemDisplay(EntityType<?> entityType, Level level) {
|
|
super(entityType, level);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(DATA_ITEM_STACK_ID, ItemStack.EMPTY);
|
|
builder.define(DATA_ITEM_DISPLAY_ID, ItemDisplayContext.NONE.getId());
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
if (DATA_ITEM_STACK_ID.equals(dataAccessor) || DATA_ITEM_DISPLAY_ID.equals(dataAccessor)) {
|
|
this.updateRenderState = true;
|
|
}
|
|
}
|
|
|
|
private ItemStack getItemStack() {
|
|
return this.entityData.get(DATA_ITEM_STACK_ID);
|
|
}
|
|
|
|
private void setItemStack(ItemStack itemStack) {
|
|
this.entityData.set(DATA_ITEM_STACK_ID, itemStack);
|
|
}
|
|
|
|
private void setItemTransform(ItemDisplayContext itemTransform) {
|
|
this.entityData.set(DATA_ITEM_DISPLAY_ID, itemTransform.getId());
|
|
}
|
|
|
|
private ItemDisplayContext getItemTransform() {
|
|
return (ItemDisplayContext)ItemDisplayContext.BY_ID.apply(this.entityData.get(DATA_ITEM_DISPLAY_ID));
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(CompoundTag tag) {
|
|
super.readAdditionalSaveData(tag);
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
this.setItemStack((ItemStack)tag.read("item", ItemStack.CODEC, registryOps).orElse(ItemStack.EMPTY));
|
|
this.setItemTransform((ItemDisplayContext)tag.read("item_display", ItemDisplayContext.CODEC).orElse(ItemDisplayContext.NONE));
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(CompoundTag tag) {
|
|
super.addAdditionalSaveData(tag);
|
|
if (!this.getItemStack().isEmpty()) {
|
|
tag.store("item", ItemStack.CODEC, this.registryAccess().createSerializationContext(NbtOps.INSTANCE), this.getItemStack());
|
|
}
|
|
|
|
tag.store("item_display", ItemDisplayContext.CODEC, this.getItemTransform());
|
|
}
|
|
|
|
@Override
|
|
public SlotAccess getSlot(int slot) {
|
|
return slot == 0 ? this.slot : SlotAccess.NULL;
|
|
}
|
|
|
|
@Nullable
|
|
public ItemRenderState itemRenderState() {
|
|
return this.itemRenderState;
|
|
}
|
|
|
|
@Override
|
|
protected void updateRenderSubState(boolean interpolate, float partialTick) {
|
|
ItemStack itemStack = this.getItemStack();
|
|
itemStack.setEntityRepresentation(this);
|
|
this.itemRenderState = new ItemRenderState(itemStack, this.getItemTransform());
|
|
}
|
|
}
|
|
|
|
record LinearFloatInterpolator(float previous, float current) implements Display.FloatInterpolator {
|
|
@Override
|
|
public float get(float f) {
|
|
return Mth.lerp(f, this.previous, this.current);
|
|
}
|
|
}
|
|
|
|
record LinearIntInterpolator(int previous, int current) implements Display.IntInterpolator {
|
|
@Override
|
|
public int get(float f) {
|
|
return Mth.lerpInt(f, this.previous, this.current);
|
|
}
|
|
}
|
|
|
|
public record RenderState(
|
|
Display.GenericInterpolator<Transformation> transformation,
|
|
Display.BillboardConstraints billboardConstraints,
|
|
int brightnessOverride,
|
|
Display.FloatInterpolator shadowRadius,
|
|
Display.FloatInterpolator shadowStrength,
|
|
int glowColorOverride
|
|
) {
|
|
}
|
|
|
|
public static class TextDisplay extends Display {
|
|
public static final String TAG_TEXT = "text";
|
|
private static final String TAG_LINE_WIDTH = "line_width";
|
|
private static final String TAG_TEXT_OPACITY = "text_opacity";
|
|
private static final String TAG_BACKGROUND_COLOR = "background";
|
|
private static final String TAG_SHADOW = "shadow";
|
|
private static final String TAG_SEE_THROUGH = "see_through";
|
|
private static final String TAG_USE_DEFAULT_BACKGROUND = "default_background";
|
|
private static final String TAG_ALIGNMENT = "alignment";
|
|
public static final byte FLAG_SHADOW = 1;
|
|
public static final byte FLAG_SEE_THROUGH = 2;
|
|
public static final byte FLAG_USE_DEFAULT_BACKGROUND = 4;
|
|
public static final byte FLAG_ALIGN_LEFT = 8;
|
|
public static final byte FLAG_ALIGN_RIGHT = 16;
|
|
private static final byte INITIAL_TEXT_OPACITY = -1;
|
|
public static final int INITIAL_BACKGROUND = 1073741824;
|
|
private static final int INITIAL_LINE_WIDTH = 200;
|
|
private static final EntityDataAccessor<Component> DATA_TEXT_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.COMPONENT);
|
|
private static final EntityDataAccessor<Integer> DATA_LINE_WIDTH_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Integer> DATA_BACKGROUND_COLOR_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Byte> DATA_TEXT_OPACITY_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.BYTE);
|
|
private static final EntityDataAccessor<Byte> DATA_STYLE_FLAGS_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.BYTE);
|
|
private static final IntSet TEXT_RENDER_STATE_IDS = IntSet.of(
|
|
DATA_TEXT_ID.id(), DATA_LINE_WIDTH_ID.id(), DATA_BACKGROUND_COLOR_ID.id(), DATA_TEXT_OPACITY_ID.id(), DATA_STYLE_FLAGS_ID.id()
|
|
);
|
|
@Nullable
|
|
private CachedInfo clientDisplayCache;
|
|
@Nullable
|
|
private TextRenderState textRenderState;
|
|
|
|
public TextDisplay(EntityType<?> entityType, Level level) {
|
|
super(entityType, level);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(DATA_TEXT_ID, Component.empty());
|
|
builder.define(DATA_LINE_WIDTH_ID, 200);
|
|
builder.define(DATA_BACKGROUND_COLOR_ID, 1073741824);
|
|
builder.define(DATA_TEXT_OPACITY_ID, (byte)-1);
|
|
builder.define(DATA_STYLE_FLAGS_ID, (byte)0);
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
if (TEXT_RENDER_STATE_IDS.contains(dataAccessor.id())) {
|
|
this.updateRenderState = true;
|
|
}
|
|
}
|
|
|
|
private Component getText() {
|
|
return this.entityData.get(DATA_TEXT_ID);
|
|
}
|
|
|
|
private void setText(Component text) {
|
|
this.entityData.set(DATA_TEXT_ID, text);
|
|
}
|
|
|
|
private int getLineWidth() {
|
|
return this.entityData.get(DATA_LINE_WIDTH_ID);
|
|
}
|
|
|
|
private void setLineWidth(int lineWidth) {
|
|
this.entityData.set(DATA_LINE_WIDTH_ID, lineWidth);
|
|
}
|
|
|
|
private byte getTextOpacity() {
|
|
return this.entityData.get(DATA_TEXT_OPACITY_ID);
|
|
}
|
|
|
|
private void setTextOpacity(byte textOpacity) {
|
|
this.entityData.set(DATA_TEXT_OPACITY_ID, textOpacity);
|
|
}
|
|
|
|
private int getBackgroundColor() {
|
|
return this.entityData.get(DATA_BACKGROUND_COLOR_ID);
|
|
}
|
|
|
|
private void setBackgroundColor(int backgroundColor) {
|
|
this.entityData.set(DATA_BACKGROUND_COLOR_ID, backgroundColor);
|
|
}
|
|
|
|
private byte getFlags() {
|
|
return this.entityData.get(DATA_STYLE_FLAGS_ID);
|
|
}
|
|
|
|
private void setFlags(byte flags) {
|
|
this.entityData.set(DATA_STYLE_FLAGS_ID, flags);
|
|
}
|
|
|
|
private static byte loadFlag(byte currentValue, CompoundTag tag, String flag, byte mask) {
|
|
return tag.getBooleanOr(flag, false) ? (byte)(currentValue | mask) : currentValue;
|
|
}
|
|
|
|
// $VF: Unable to simplify switch-on-enum, as the enum class was not able to be found.
|
|
// Please report this to the Vineflower issue tracker, at https://github.com/Vineflower/vineflower/issues with a copy of the class file (if you have the rights to distribute it!)
|
|
@Override
|
|
protected void readAdditionalSaveData(CompoundTag tag) {
|
|
super.readAdditionalSaveData(tag);
|
|
this.setLineWidth(tag.getIntOr("line_width", 200));
|
|
this.setTextOpacity(tag.getByteOr("text_opacity", (byte)-1));
|
|
this.setBackgroundColor(tag.getIntOr("background", 1073741824));
|
|
byte b = loadFlag((byte)0, tag, "shadow", (byte)1);
|
|
b = loadFlag(b, tag, "see_through", (byte)2);
|
|
b = loadFlag(b, tag, "default_background", (byte)4);
|
|
Optional<Align> optional = tag.read("alignment", Align.CODEC);
|
|
if (optional.isPresent()) {
|
|
b = switch (((Align)optional.get()).ordinal()) {
|
|
case 0 -> b;
|
|
case 1 -> (byte)(b | 8);
|
|
case 2 -> (byte)(b | 16);
|
|
default -> throw new MatchException(null, null);
|
|
};
|
|
}
|
|
|
|
this.setFlags(b);
|
|
Tag tag2 = tag.get("text");
|
|
if (tag2 != null) {
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
|
|
try {
|
|
Component component = ComponentSerialization.CODEC.parse(registryOps, tag2).getOrThrow();
|
|
if (component != null && this.level() instanceof ServerLevel serverLevel) {
|
|
CommandSourceStack commandSourceStack = this.createCommandSourceStackForNameResolution(serverLevel).withPermission(2);
|
|
Component component2 = ComponentUtils.updateForEntity(commandSourceStack, component, this, 0);
|
|
this.setText(component2);
|
|
} else {
|
|
this.setText(Component.empty());
|
|
}
|
|
} catch (Exception var10) {
|
|
Display.LOGGER.warn("Failed to parse display entity text {}", tag2, var10);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void storeFlag(byte currentValue, CompoundTag tag, String flag, byte mask) {
|
|
tag.putBoolean(flag, (currentValue & mask) != 0);
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(CompoundTag tag) {
|
|
super.addAdditionalSaveData(tag);
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
tag.store("text", ComponentSerialization.CODEC, registryOps, this.getText());
|
|
tag.putInt("line_width", this.getLineWidth());
|
|
tag.putInt("background", this.getBackgroundColor());
|
|
tag.putByte("text_opacity", this.getTextOpacity());
|
|
byte b = this.getFlags();
|
|
storeFlag(b, tag, "shadow", (byte)1);
|
|
storeFlag(b, tag, "see_through", (byte)2);
|
|
storeFlag(b, tag, "default_background", (byte)4);
|
|
tag.store("alignment", Align.CODEC, getAlign(b));
|
|
}
|
|
|
|
@Override
|
|
protected void updateRenderSubState(boolean interpolate, float partialTick) {
|
|
if (interpolate && this.textRenderState != null) {
|
|
this.textRenderState = this.createInterpolatedTextRenderState(this.textRenderState, partialTick);
|
|
} else {
|
|
this.textRenderState = this.createFreshTextRenderState();
|
|
}
|
|
|
|
this.clientDisplayCache = null;
|
|
}
|
|
|
|
@Nullable
|
|
public TextRenderState textRenderState() {
|
|
return this.textRenderState;
|
|
}
|
|
|
|
private TextRenderState createFreshTextRenderState() {
|
|
return new TextRenderState(
|
|
this.getText(),
|
|
this.getLineWidth(),
|
|
Display.IntInterpolator.constant(this.getTextOpacity()),
|
|
Display.IntInterpolator.constant(this.getBackgroundColor()),
|
|
this.getFlags()
|
|
);
|
|
}
|
|
|
|
private TextRenderState createInterpolatedTextRenderState(TextRenderState renderState, float partialTick) {
|
|
int i = renderState.backgroundColor.get(partialTick);
|
|
int j = renderState.textOpacity.get(partialTick);
|
|
return new TextRenderState(
|
|
this.getText(),
|
|
this.getLineWidth(),
|
|
new Display.LinearIntInterpolator(j, this.getTextOpacity()),
|
|
new Display.ColorInterpolator(i, this.getBackgroundColor()),
|
|
this.getFlags()
|
|
);
|
|
}
|
|
|
|
public CachedInfo cacheDisplay(LineSplitter splitter) {
|
|
if (this.clientDisplayCache == null) {
|
|
if (this.textRenderState != null) {
|
|
this.clientDisplayCache = splitter.split(this.textRenderState.text(), this.textRenderState.lineWidth());
|
|
} else {
|
|
this.clientDisplayCache = new CachedInfo(List.of(), 0);
|
|
}
|
|
}
|
|
|
|
return this.clientDisplayCache;
|
|
}
|
|
|
|
public static Align getAlign(byte flags) {
|
|
if ((flags & 8) != 0) {
|
|
return Align.LEFT;
|
|
} else {
|
|
return (flags & 16) != 0 ? Align.RIGHT : Align.CENTER;
|
|
}
|
|
}
|
|
}
|
|
|
|
record TransformationInterpolator(Transformation previous, Transformation current) implements Display.GenericInterpolator<Transformation> {
|
|
public Transformation get(float f) {
|
|
return f >= 1.0 ? this.current : this.previous.slerp(this.current, f);
|
|
}
|
|
}
|
|
}
|