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 DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID = SynchedEntityData.defineId( Display.class, EntityDataSerializers.INT ); private static final EntityDataAccessor DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID = SynchedEntityData.defineId( Display.class, EntityDataSerializers.INT ); private static final EntityDataAccessor DATA_POS_ROT_INTERPOLATION_DURATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_TRANSLATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.VECTOR3); private static final EntityDataAccessor DATA_SCALE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.VECTOR3); private static final EntityDataAccessor DATA_LEFT_ROTATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.QUATERNION); private static final EntityDataAccessor DATA_RIGHT_ROTATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.QUATERNION); private static final EntityDataAccessor DATA_BILLBOARD_RENDER_CONSTRAINTS_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.BYTE); private static final EntityDataAccessor DATA_BRIGHTNESS_OVERRIDE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_VIEW_RANGE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor DATA_SHADOW_RADIUS_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor DATA_SHADOW_STRENGTH_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor DATA_WIDTH_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor DATA_HEIGHT_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor 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 CODEC = StringRepresentable.fromEnum(Display.BillboardConstraints::values); public static final IntFunction 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 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 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 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 { static Display.GenericInterpolator 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 DATA_ITEM_STACK_ID = SynchedEntityData.defineId( Display.ItemDisplay.class, EntityDataSerializers.ITEM_STACK ); private static final EntityDataAccessor 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 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, 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 DATA_TEXT_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.COMPONENT); private static final EntityDataAccessor DATA_LINE_WIDTH_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_BACKGROUND_COLOR_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_TEXT_OPACITY_ID = SynchedEntityData.defineId(Display.TextDisplay.class, EntityDataSerializers.BYTE); private static final EntityDataAccessor 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 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 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 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 { public Transformation get(float f) { return f >= 1.0 ? this.current : this.previous.slerp(this.current, f); } } }