875 lines
38 KiB
Java
875 lines
38 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.mojang.logging.LogUtils
|
|
* com.mojang.serialization.Codec
|
|
* it.unimi.dsi.fastutil.ints.IntSet
|
|
* org.joml.Quaternionf
|
|
* org.joml.Quaternionfc
|
|
* org.joml.Vector3f
|
|
* org.joml.Vector3fc
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
*/
|
|
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.network.chat.Component;
|
|
import net.minecraft.network.chat.ComponentSerialization;
|
|
import net.minecraft.network.chat.ComponentUtils;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.permissions.LevelBasedPermissionSet;
|
|
import net.minecraft.util.ARGB;
|
|
import net.minecraft.util.Brightness;
|
|
import net.minecraft.util.ByIdMap;
|
|
import net.minecraft.util.FormattedCharSequence;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.StringRepresentable;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.InterpolationHandler;
|
|
import net.minecraft.world.entity.SlotAccess;
|
|
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.level.storage.ValueInput;
|
|
import net.minecraft.world.level.storage.ValueOutput;
|
|
import net.minecraft.world.phys.AABB;
|
|
import org.joml.Quaternionf;
|
|
import org.joml.Quaternionfc;
|
|
import org.joml.Vector3f;
|
|
import org.joml.Vector3fc;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public abstract class Display
|
|
extends Entity {
|
|
private 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<Vector3fc> DATA_TRANSLATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.VECTOR3);
|
|
private static final EntityDataAccessor<Vector3fc> DATA_SCALE_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.VECTOR3);
|
|
private static final EntityDataAccessor<Quaternionfc> DATA_LEFT_ROTATION_ID = SynchedEntityData.defineId(Display.class, EntityDataSerializers.QUATERNION);
|
|
private static final EntityDataAccessor<Quaternionfc> 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((int[])new int[]{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 = Integer.MIN_VALUE;
|
|
private int interpolationDuration;
|
|
private float lastProgress;
|
|
private AABB cullingBoundingBox;
|
|
private boolean noCulling = true;
|
|
protected boolean updateRenderState;
|
|
private boolean updateStartTick;
|
|
private boolean updateInterpolationDuration;
|
|
private @Nullable RenderState renderState;
|
|
private final InterpolationHandler interpolation = new InterpolationHandler((Entity)this, 0);
|
|
|
|
public Display(EntityType<?> type, Level level) {
|
|
super(type, level);
|
|
this.noPhysics = true;
|
|
this.cullingBoundingBox = this.getBoundingBox();
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> accessor) {
|
|
super.onSyncedDataUpdated(accessor);
|
|
if (DATA_HEIGHT_ID.equals(accessor) || DATA_WIDTH_ID.equals(accessor)) {
|
|
this.updateCulling();
|
|
}
|
|
if (DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID.equals(accessor)) {
|
|
this.updateStartTick = true;
|
|
}
|
|
if (DATA_POS_ROT_INTERPOLATION_DURATION_ID.equals(accessor)) {
|
|
this.interpolation.setInterpolationLength(this.getPosRotInterpolationDuration());
|
|
}
|
|
if (DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID.equals(accessor)) {
|
|
this.updateInterpolationDuration = true;
|
|
}
|
|
if (RENDER_STATE_IDS.contains(accessor.id())) {
|
|
this.updateRenderState = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final boolean hurtServer(ServerLevel level, DamageSource source, float damage) {
|
|
return false;
|
|
}
|
|
|
|
private static Transformation createTransformation(SynchedEntityData entityData) {
|
|
Vector3fc translation = entityData.get(DATA_TRANSLATION_ID);
|
|
Quaternionfc leftRotation = entityData.get(DATA_LEFT_ROTATION_ID);
|
|
Vector3fc scale = entityData.get(DATA_SCALE_ID);
|
|
Quaternionfc rightRotation = entityData.get(DATA_RIGHT_ROTATION_ID);
|
|
return new Transformation(translation, leftRotation, scale, rightRotation);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
Entity vehicle = this.getVehicle();
|
|
if (vehicle != null && vehicle.isRemoved()) {
|
|
this.stopRiding();
|
|
}
|
|
if (this.level().isClientSide()) {
|
|
if (this.updateStartTick) {
|
|
this.updateStartTick = false;
|
|
int interpolationStartDelta = this.getTransformationInterpolationDelay();
|
|
this.interpolationStartClientTick = this.tickCount + interpolationStartDelta;
|
|
}
|
|
if (this.updateInterpolationDuration) {
|
|
this.updateInterpolationDuration = false;
|
|
this.interpolationDuration = this.getTransformationInterpolationDuration();
|
|
}
|
|
if (this.updateRenderState) {
|
|
this.updateRenderState = false;
|
|
boolean shouldInterpolate = this.interpolationDuration != 0;
|
|
this.renderState = shouldInterpolate && this.renderState != null ? this.createInterpolatedRenderState(this.renderState, this.lastProgress) : this.createFreshRenderState();
|
|
this.updateRenderSubState(shouldInterpolate, this.lastProgress);
|
|
}
|
|
this.interpolation.interpolate();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public InterpolationHandler getInterpolation() {
|
|
return this.interpolation;
|
|
}
|
|
|
|
protected abstract void updateRenderSubState(boolean var1, float var2);
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
|
|
entityData.define(DATA_POS_ROT_INTERPOLATION_DURATION_ID, 0);
|
|
entityData.define(DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID, 0);
|
|
entityData.define(DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID, 0);
|
|
entityData.define(DATA_TRANSLATION_ID, new Vector3f());
|
|
entityData.define(DATA_SCALE_ID, new Vector3f(1.0f, 1.0f, 1.0f));
|
|
entityData.define(DATA_RIGHT_ROTATION_ID, new Quaternionf());
|
|
entityData.define(DATA_LEFT_ROTATION_ID, new Quaternionf());
|
|
entityData.define(DATA_BILLBOARD_RENDER_CONSTRAINTS_ID, BillboardConstraints.FIXED.getId());
|
|
entityData.define(DATA_BRIGHTNESS_OVERRIDE_ID, -1);
|
|
entityData.define(DATA_VIEW_RANGE_ID, Float.valueOf(1.0f));
|
|
entityData.define(DATA_SHADOW_RADIUS_ID, Float.valueOf(0.0f));
|
|
entityData.define(DATA_SHADOW_STRENGTH_ID, Float.valueOf(1.0f));
|
|
entityData.define(DATA_WIDTH_ID, Float.valueOf(0.0f));
|
|
entityData.define(DATA_HEIGHT_ID, Float.valueOf(0.0f));
|
|
entityData.define(DATA_GLOW_COLOR_OVERRIDE_ID, -1);
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(ValueInput input) {
|
|
this.setTransformation(input.read(TAG_TRANSFORMATION, Transformation.EXTENDED_CODEC).orElse(Transformation.identity()));
|
|
this.setTransformationInterpolationDuration(input.getIntOr(TAG_TRANSFORMATION_INTERPOLATION_DURATION, 0));
|
|
this.setTransformationInterpolationDelay(input.getIntOr(TAG_TRANSFORMATION_START_INTERPOLATION, 0));
|
|
int teleportDuration = input.getIntOr(TAG_POS_ROT_INTERPOLATION_DURATION, 0);
|
|
this.setPosRotInterpolationDuration(Mth.clamp(teleportDuration, 0, 59));
|
|
this.setBillboardConstraints(input.read(TAG_BILLBOARD, BillboardConstraints.CODEC).orElse(BillboardConstraints.FIXED));
|
|
this.setViewRange(input.getFloatOr(TAG_VIEW_RANGE, 1.0f));
|
|
this.setShadowRadius(input.getFloatOr(TAG_SHADOW_RADIUS, 0.0f));
|
|
this.setShadowStrength(input.getFloatOr(TAG_SHADOW_STRENGTH, 1.0f));
|
|
this.setWidth(input.getFloatOr(TAG_WIDTH, 0.0f));
|
|
this.setHeight(input.getFloatOr(TAG_HEIGHT, 0.0f));
|
|
this.setGlowColorOverride(input.getIntOr(TAG_GLOW_COLOR_OVERRIDE, -1));
|
|
this.setBrightnessOverride(input.read(TAG_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(ValueOutput output) {
|
|
output.store(TAG_TRANSFORMATION, Transformation.EXTENDED_CODEC, Display.createTransformation(this.entityData));
|
|
output.store(TAG_BILLBOARD, BillboardConstraints.CODEC, this.getBillboardConstraints());
|
|
output.putInt(TAG_TRANSFORMATION_INTERPOLATION_DURATION, this.getTransformationInterpolationDuration());
|
|
output.putInt(TAG_POS_ROT_INTERPOLATION_DURATION, this.getPosRotInterpolationDuration());
|
|
output.putFloat(TAG_VIEW_RANGE, this.getViewRange());
|
|
output.putFloat(TAG_SHADOW_RADIUS, this.getShadowRadius());
|
|
output.putFloat(TAG_SHADOW_STRENGTH, this.getShadowStrength());
|
|
output.putFloat(TAG_WIDTH, this.getWidth());
|
|
output.putFloat(TAG_HEIGHT, this.getHeight());
|
|
output.putInt(TAG_GLOW_COLOR_OVERRIDE, this.getGlowColorOverride());
|
|
output.storeNullable(TAG_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;
|
|
}
|
|
|
|
public @Nullable RenderState renderState() {
|
|
return this.renderState;
|
|
}
|
|
|
|
private void setTransformationInterpolationDuration(int duration) {
|
|
this.entityData.set(DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID, duration);
|
|
}
|
|
|
|
private int getTransformationInterpolationDuration() {
|
|
return this.entityData.get(DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID);
|
|
}
|
|
|
|
private void setTransformationInterpolationDelay(int ticks) {
|
|
this.entityData.set(DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID, ticks, true);
|
|
}
|
|
|
|
private int getTransformationInterpolationDelay() {
|
|
return this.entityData.get(DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID);
|
|
}
|
|
|
|
private void setPosRotInterpolationDuration(int duration) {
|
|
this.entityData.set(DATA_POS_ROT_INTERPOLATION_DURATION_ID, duration);
|
|
}
|
|
|
|
private int getPosRotInterpolationDuration() {
|
|
return this.entityData.get(DATA_POS_ROT_INTERPOLATION_DURATION_ID);
|
|
}
|
|
|
|
private void setBillboardConstraints(BillboardConstraints constraints) {
|
|
this.entityData.set(DATA_BILLBOARD_RENDER_CONSTRAINTS_ID, constraints.getId());
|
|
}
|
|
|
|
private BillboardConstraints getBillboardConstraints() {
|
|
return BillboardConstraints.BY_ID.apply(this.entityData.get(DATA_BILLBOARD_RENDER_CONSTRAINTS_ID).byteValue());
|
|
}
|
|
|
|
private void setBrightnessOverride(@Nullable Brightness brightness) {
|
|
this.entityData.set(DATA_BRIGHTNESS_OVERRIDE_ID, brightness != null ? brightness.pack() : -1);
|
|
}
|
|
|
|
private @Nullable Brightness getBrightnessOverride() {
|
|
int value = this.entityData.get(DATA_BRIGHTNESS_OVERRIDE_ID);
|
|
return value != -1 ? Brightness.unpack(value) : null;
|
|
}
|
|
|
|
private int getPackedBrightnessOverride() {
|
|
return this.entityData.get(DATA_BRIGHTNESS_OVERRIDE_ID);
|
|
}
|
|
|
|
private void setViewRange(float range) {
|
|
this.entityData.set(DATA_VIEW_RANGE_ID, Float.valueOf(range));
|
|
}
|
|
|
|
private float getViewRange() {
|
|
return this.entityData.get(DATA_VIEW_RANGE_ID).floatValue();
|
|
}
|
|
|
|
private void setShadowRadius(float size) {
|
|
this.entityData.set(DATA_SHADOW_RADIUS_ID, Float.valueOf(size));
|
|
}
|
|
|
|
private float getShadowRadius() {
|
|
return this.entityData.get(DATA_SHADOW_RADIUS_ID).floatValue();
|
|
}
|
|
|
|
private void setShadowStrength(float strength) {
|
|
this.entityData.set(DATA_SHADOW_STRENGTH_ID, Float.valueOf(strength));
|
|
}
|
|
|
|
private float getShadowStrength() {
|
|
return this.entityData.get(DATA_SHADOW_STRENGTH_ID).floatValue();
|
|
}
|
|
|
|
private void setWidth(float width) {
|
|
this.entityData.set(DATA_WIDTH_ID, Float.valueOf(width));
|
|
}
|
|
|
|
private float getWidth() {
|
|
return this.entityData.get(DATA_WIDTH_ID).floatValue();
|
|
}
|
|
|
|
private void setHeight(float width) {
|
|
this.entityData.set(DATA_HEIGHT_ID, Float.valueOf(width));
|
|
}
|
|
|
|
private int getGlowColorOverride() {
|
|
return this.entityData.get(DATA_GLOW_COLOR_OVERRIDE_ID);
|
|
}
|
|
|
|
private void setGlowColorOverride(int value) {
|
|
this.entityData.set(DATA_GLOW_COLOR_OVERRIDE_ID, value);
|
|
}
|
|
|
|
public float calculateInterpolationProgress(float partialTickTime) {
|
|
float result;
|
|
int duration = this.interpolationDuration;
|
|
if (duration <= 0) {
|
|
return 1.0f;
|
|
}
|
|
float ticksSinceUpdate = (long)this.tickCount - this.interpolationStartClientTick;
|
|
float partialTicksSinceLastUpdate = ticksSinceUpdate + partialTickTime;
|
|
this.lastProgress = result = Mth.clamp(Mth.inverseLerp(partialTicksSinceLastUpdate, 0.0f, duration), 0.0f, 1.0f);
|
|
return result;
|
|
}
|
|
|
|
private float getHeight() {
|
|
return this.entityData.get(DATA_HEIGHT_ID).floatValue();
|
|
}
|
|
|
|
@Override
|
|
public void setPos(double x, double y, double z) {
|
|
super.setPos(x, y, z);
|
|
this.updateCulling();
|
|
}
|
|
|
|
private void updateCulling() {
|
|
float width = this.getWidth();
|
|
float height = this.getHeight();
|
|
this.noCulling = width == 0.0f || height == 0.0f;
|
|
float w = width / 2.0f;
|
|
double x = this.getX();
|
|
double y = this.getY();
|
|
double z = this.getZ();
|
|
this.cullingBoundingBox = new AABB(x - (double)w, y, z - (double)w, x + (double)w, y + (double)height, z + (double)w);
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldRenderAtSqrDistance(double distanceSqr) {
|
|
return distanceSqr < Mth.square((double)this.getViewRange() * 64.0 * Display.getViewScale());
|
|
}
|
|
|
|
@Override
|
|
public int getTeamColor() {
|
|
int glowColorOverride = this.getGlowColorOverride();
|
|
return glowColorOverride != -1 ? glowColorOverride : super.getTeamColor();
|
|
}
|
|
|
|
private RenderState createFreshRenderState() {
|
|
return new RenderState(GenericInterpolator.constant(Display.createTransformation(this.entityData)), this.getBillboardConstraints(), this.getPackedBrightnessOverride(), FloatInterpolator.constant(this.getShadowRadius()), FloatInterpolator.constant(this.getShadowStrength()), this.getGlowColorOverride());
|
|
}
|
|
|
|
private RenderState createInterpolatedRenderState(RenderState previousState, float progress) {
|
|
Transformation currentTransform = previousState.transformation.get(progress);
|
|
float currentShadowRadius = previousState.shadowRadius.get(progress);
|
|
float currentShadowStrength = previousState.shadowStrength.get(progress);
|
|
return new RenderState(new TransformationInterpolator(currentTransform, Display.createTransformation(this.entityData)), this.getBillboardConstraints(), this.getPackedBrightnessOverride(), new LinearFloatInterpolator(currentShadowRadius, this.getShadowRadius()), new LinearFloatInterpolator(currentShadowStrength, this.getShadowStrength()), this.getGlowColorOverride());
|
|
}
|
|
|
|
public record RenderState(GenericInterpolator<Transformation> transformation, BillboardConstraints billboardConstraints, int brightnessOverride, FloatInterpolator shadowRadius, FloatInterpolator shadowStrength, int glowColorOverride) {
|
|
}
|
|
|
|
public static enum BillboardConstraints implements StringRepresentable
|
|
{
|
|
FIXED(0, "fixed"),
|
|
VERTICAL(1, "vertical"),
|
|
HORIZONTAL(2, "horizontal"),
|
|
CENTER(3, "center");
|
|
|
|
public static final Codec<BillboardConstraints> CODEC;
|
|
public static final IntFunction<BillboardConstraints> BY_ID;
|
|
private final byte id;
|
|
private final String name;
|
|
|
|
private BillboardConstraints(byte id, String name) {
|
|
this.name = name;
|
|
this.id = id;
|
|
}
|
|
|
|
@Override
|
|
public String getSerializedName() {
|
|
return this.name;
|
|
}
|
|
|
|
private byte getId() {
|
|
return this.id;
|
|
}
|
|
|
|
static {
|
|
CODEC = StringRepresentable.fromEnum(BillboardConstraints::values);
|
|
BY_ID = ByIdMap.continuous(BillboardConstraints::getId, BillboardConstraints.values(), ByIdMap.OutOfBoundsStrategy.ZERO);
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public static interface GenericInterpolator<T> {
|
|
public static <T> GenericInterpolator<T> constant(T value) {
|
|
return progress -> value;
|
|
}
|
|
|
|
public T get(float var1);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public static interface FloatInterpolator {
|
|
public static FloatInterpolator constant(float value) {
|
|
return progress -> value;
|
|
}
|
|
|
|
public float get(float var1);
|
|
}
|
|
|
|
private record TransformationInterpolator(Transformation previous, Transformation current) implements GenericInterpolator<Transformation>
|
|
{
|
|
@Override
|
|
public Transformation get(float progress) {
|
|
if ((double)progress >= 1.0) {
|
|
return this.current;
|
|
}
|
|
return this.previous.slerp(this.current, progress);
|
|
}
|
|
}
|
|
|
|
private record LinearFloatInterpolator(float previous, float current) implements FloatInterpolator
|
|
{
|
|
@Override
|
|
public float get(float progress) {
|
|
return Mth.lerp(progress, this.previous, this.current);
|
|
}
|
|
}
|
|
|
|
private record ColorInterpolator(int previous, int current) implements IntInterpolator
|
|
{
|
|
@Override
|
|
public int get(float progress) {
|
|
return ARGB.srgbLerp(progress, this.previous, this.current);
|
|
}
|
|
}
|
|
|
|
private record LinearIntInterpolator(int previous, int current) implements IntInterpolator
|
|
{
|
|
@Override
|
|
public int get(float progress) {
|
|
return Mth.lerpInt(progress, this.previous, this.current);
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public static interface IntInterpolator {
|
|
public static IntInterpolator constant(int value) {
|
|
return progress -> value;
|
|
}
|
|
|
|
public int get(float var1);
|
|
}
|
|
|
|
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 = 0x40000000;
|
|
private static final int INITIAL_LINE_WIDTH = 200;
|
|
private static final EntityDataAccessor<Component> DATA_TEXT_ID = SynchedEntityData.defineId(TextDisplay.class, EntityDataSerializers.COMPONENT);
|
|
private static final EntityDataAccessor<Integer> DATA_LINE_WIDTH_ID = SynchedEntityData.defineId(TextDisplay.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Integer> DATA_BACKGROUND_COLOR_ID = SynchedEntityData.defineId(TextDisplay.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Byte> DATA_TEXT_OPACITY_ID = SynchedEntityData.defineId(TextDisplay.class, EntityDataSerializers.BYTE);
|
|
private static final EntityDataAccessor<Byte> DATA_STYLE_FLAGS_ID = SynchedEntityData.defineId(TextDisplay.class, EntityDataSerializers.BYTE);
|
|
private static final IntSet TEXT_RENDER_STATE_IDS = IntSet.of((int[])new int[]{DATA_TEXT_ID.id(), DATA_LINE_WIDTH_ID.id(), DATA_BACKGROUND_COLOR_ID.id(), DATA_TEXT_OPACITY_ID.id(), DATA_STYLE_FLAGS_ID.id()});
|
|
private @Nullable CachedInfo clientDisplayCache;
|
|
private @Nullable TextRenderState textRenderState;
|
|
|
|
public TextDisplay(EntityType<?> type, Level level) {
|
|
super(type, level);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
|
|
super.defineSynchedData(entityData);
|
|
entityData.define(DATA_TEXT_ID, Component.empty());
|
|
entityData.define(DATA_LINE_WIDTH_ID, 200);
|
|
entityData.define(DATA_BACKGROUND_COLOR_ID, 0x40000000);
|
|
entityData.define(DATA_TEXT_OPACITY_ID, (byte)-1);
|
|
entityData.define(DATA_STYLE_FLAGS_ID, (byte)0);
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> accessor) {
|
|
super.onSyncedDataUpdated(accessor);
|
|
if (TEXT_RENDER_STATE_IDS.contains(accessor.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 width) {
|
|
this.entityData.set(DATA_LINE_WIDTH_ID, width);
|
|
}
|
|
|
|
private byte getTextOpacity() {
|
|
return this.entityData.get(DATA_TEXT_OPACITY_ID);
|
|
}
|
|
|
|
private void setTextOpacity(byte opacity) {
|
|
this.entityData.set(DATA_TEXT_OPACITY_ID, opacity);
|
|
}
|
|
|
|
private int getBackgroundColor() {
|
|
return this.entityData.get(DATA_BACKGROUND_COLOR_ID);
|
|
}
|
|
|
|
private void setBackgroundColor(int color) {
|
|
this.entityData.set(DATA_BACKGROUND_COLOR_ID, color);
|
|
}
|
|
|
|
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 flags, ValueInput input, String id, byte mask) {
|
|
if (input.getBooleanOr(id, false)) {
|
|
return (byte)(flags | mask);
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(ValueInput input) {
|
|
super.readAdditionalSaveData(input);
|
|
this.setLineWidth(input.getIntOr(TAG_LINE_WIDTH, 200));
|
|
this.setTextOpacity(input.getByteOr(TAG_TEXT_OPACITY, (byte)-1));
|
|
this.setBackgroundColor(input.getIntOr(TAG_BACKGROUND_COLOR, 0x40000000));
|
|
byte flags = TextDisplay.loadFlag((byte)0, input, TAG_SHADOW, (byte)1);
|
|
flags = TextDisplay.loadFlag(flags, input, TAG_SEE_THROUGH, (byte)2);
|
|
flags = TextDisplay.loadFlag(flags, input, TAG_USE_DEFAULT_BACKGROUND, (byte)4);
|
|
Optional<Align> alignment = input.read(TAG_ALIGNMENT, Align.CODEC);
|
|
if (alignment.isPresent()) {
|
|
flags = switch (alignment.get().ordinal()) {
|
|
default -> throw new MatchException(null, null);
|
|
case 0 -> flags;
|
|
case 1 -> (byte)(flags | 8);
|
|
case 2 -> (byte)(flags | 0x10);
|
|
};
|
|
}
|
|
this.setFlags(flags);
|
|
Optional<Component> text = input.read(TAG_TEXT, ComponentSerialization.CODEC);
|
|
if (text.isPresent()) {
|
|
try {
|
|
Level level = this.level();
|
|
if (level instanceof ServerLevel) {
|
|
ServerLevel serverLevel = (ServerLevel)level;
|
|
CommandSourceStack context = this.createCommandSourceStackForNameResolution(serverLevel).withPermission(LevelBasedPermissionSet.GAMEMASTER);
|
|
MutableComponent resolvedText = ComponentUtils.updateForEntity(context, text.get(), (Entity)this, 0);
|
|
this.setText(resolvedText);
|
|
} else {
|
|
this.setText(Component.empty());
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
LOGGER.warn("Failed to parse display entity text {}", text, (Object)e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void storeFlag(byte flags, ValueOutput output, String id, byte mask) {
|
|
output.putBoolean(id, (flags & mask) != 0);
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(ValueOutput output) {
|
|
super.addAdditionalSaveData(output);
|
|
output.store(TAG_TEXT, ComponentSerialization.CODEC, this.getText());
|
|
output.putInt(TAG_LINE_WIDTH, this.getLineWidth());
|
|
output.putInt(TAG_BACKGROUND_COLOR, this.getBackgroundColor());
|
|
output.putByte(TAG_TEXT_OPACITY, this.getTextOpacity());
|
|
byte flags = this.getFlags();
|
|
TextDisplay.storeFlag(flags, output, TAG_SHADOW, (byte)1);
|
|
TextDisplay.storeFlag(flags, output, TAG_SEE_THROUGH, (byte)2);
|
|
TextDisplay.storeFlag(flags, output, TAG_USE_DEFAULT_BACKGROUND, (byte)4);
|
|
output.store(TAG_ALIGNMENT, Align.CODEC, TextDisplay.getAlign(flags));
|
|
}
|
|
|
|
@Override
|
|
protected void updateRenderSubState(boolean shouldInterpolate, float progress) {
|
|
this.textRenderState = shouldInterpolate && this.textRenderState != null ? this.createInterpolatedTextRenderState(this.textRenderState, progress) : this.createFreshTextRenderState();
|
|
this.clientDisplayCache = null;
|
|
}
|
|
|
|
public @Nullable TextRenderState textRenderState() {
|
|
return this.textRenderState;
|
|
}
|
|
|
|
private TextRenderState createFreshTextRenderState() {
|
|
return new TextRenderState(this.getText(), this.getLineWidth(), IntInterpolator.constant(this.getTextOpacity()), IntInterpolator.constant(this.getBackgroundColor()), this.getFlags());
|
|
}
|
|
|
|
private TextRenderState createInterpolatedTextRenderState(TextRenderState previous, float progress) {
|
|
int currentBackground = previous.backgroundColor.get(progress);
|
|
int currentOpacity = previous.textOpacity.get(progress);
|
|
return new TextRenderState(this.getText(), this.getLineWidth(), new LinearIntInterpolator(currentOpacity, this.getTextOpacity()), new ColorInterpolator(currentBackground, this.getBackgroundColor()), this.getFlags());
|
|
}
|
|
|
|
public CachedInfo cacheDisplay(LineSplitter splitter) {
|
|
if (this.clientDisplayCache == null) {
|
|
this.clientDisplayCache = this.textRenderState != null ? splitter.split(this.textRenderState.text(), this.textRenderState.lineWidth()) : new CachedInfo(List.of(), 0);
|
|
}
|
|
return this.clientDisplayCache;
|
|
}
|
|
|
|
public static Align getAlign(byte flags) {
|
|
if ((flags & 8) != 0) {
|
|
return Align.LEFT;
|
|
}
|
|
if ((flags & 0x10) != 0) {
|
|
return Align.RIGHT;
|
|
}
|
|
return Align.CENTER;
|
|
}
|
|
|
|
public static enum Align implements StringRepresentable
|
|
{
|
|
CENTER("center"),
|
|
LEFT("left"),
|
|
RIGHT("right");
|
|
|
|
public static final Codec<Align> CODEC;
|
|
private final String name;
|
|
|
|
private Align(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
@Override
|
|
public String getSerializedName() {
|
|
return this.name;
|
|
}
|
|
|
|
static {
|
|
CODEC = StringRepresentable.fromEnum(Align::values);
|
|
}
|
|
}
|
|
|
|
public record TextRenderState(Component text, int lineWidth, IntInterpolator textOpacity, IntInterpolator backgroundColor, byte flags) {
|
|
}
|
|
|
|
public record CachedInfo(List<CachedLine> lines, int width) {
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public static interface LineSplitter {
|
|
public CachedInfo split(Component var1, int var2);
|
|
}
|
|
|
|
public record CachedLine(FormattedCharSequence contents, int width) {
|
|
}
|
|
}
|
|
|
|
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(BlockDisplay.class, EntityDataSerializers.BLOCK_STATE);
|
|
private @Nullable BlockRenderState blockRenderState;
|
|
|
|
public BlockDisplay(EntityType<?> type, Level level) {
|
|
super(type, level);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
|
|
super.defineSynchedData(entityData);
|
|
entityData.define(DATA_BLOCK_STATE_ID, Blocks.AIR.defaultBlockState());
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> accessor) {
|
|
super.onSyncedDataUpdated(accessor);
|
|
if (accessor.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(ValueInput input) {
|
|
super.readAdditionalSaveData(input);
|
|
this.setBlockState(input.read(TAG_BLOCK_STATE, BlockState.CODEC).orElse(Blocks.AIR.defaultBlockState()));
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(ValueOutput output) {
|
|
super.addAdditionalSaveData(output);
|
|
output.store(TAG_BLOCK_STATE, BlockState.CODEC, this.getBlockState());
|
|
}
|
|
|
|
public @Nullable BlockRenderState blockRenderState() {
|
|
return this.blockRenderState;
|
|
}
|
|
|
|
@Override
|
|
protected void updateRenderSubState(boolean shouldInterpolate, float progress) {
|
|
this.blockRenderState = new BlockRenderState(this.getBlockState());
|
|
}
|
|
|
|
public record BlockRenderState(BlockState blockState) {
|
|
}
|
|
}
|
|
|
|
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(ItemDisplay.class, EntityDataSerializers.ITEM_STACK);
|
|
private static final EntityDataAccessor<Byte> DATA_ITEM_DISPLAY_ID = SynchedEntityData.defineId(ItemDisplay.class, EntityDataSerializers.BYTE);
|
|
private final SlotAccess slot = SlotAccess.of(this::getItemStack, this::setItemStack);
|
|
private @Nullable ItemRenderState itemRenderState;
|
|
|
|
public ItemDisplay(EntityType<?> type, Level level) {
|
|
super(type, level);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
|
|
super.defineSynchedData(entityData);
|
|
entityData.define(DATA_ITEM_STACK_ID, ItemStack.EMPTY);
|
|
entityData.define(DATA_ITEM_DISPLAY_ID, ItemDisplayContext.NONE.getId());
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> accessor) {
|
|
super.onSyncedDataUpdated(accessor);
|
|
if (DATA_ITEM_STACK_ID.equals(accessor) || DATA_ITEM_DISPLAY_ID.equals(accessor)) {
|
|
this.updateRenderState = true;
|
|
}
|
|
}
|
|
|
|
private ItemStack getItemStack() {
|
|
return this.entityData.get(DATA_ITEM_STACK_ID);
|
|
}
|
|
|
|
private void setItemStack(ItemStack item) {
|
|
this.entityData.set(DATA_ITEM_STACK_ID, item);
|
|
}
|
|
|
|
private void setItemTransform(ItemDisplayContext transform) {
|
|
this.entityData.set(DATA_ITEM_DISPLAY_ID, transform.getId());
|
|
}
|
|
|
|
private ItemDisplayContext getItemTransform() {
|
|
return ItemDisplayContext.BY_ID.apply(this.entityData.get(DATA_ITEM_DISPLAY_ID).byteValue());
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(ValueInput input) {
|
|
super.readAdditionalSaveData(input);
|
|
this.setItemStack(input.read(TAG_ITEM, ItemStack.CODEC).orElse(ItemStack.EMPTY));
|
|
this.setItemTransform(input.read(TAG_ITEM_DISPLAY, ItemDisplayContext.CODEC).orElse(ItemDisplayContext.NONE));
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(ValueOutput output) {
|
|
super.addAdditionalSaveData(output);
|
|
ItemStack itemStack = this.getItemStack();
|
|
if (!itemStack.isEmpty()) {
|
|
output.store(TAG_ITEM, ItemStack.CODEC, itemStack);
|
|
}
|
|
output.store(TAG_ITEM_DISPLAY, ItemDisplayContext.CODEC, this.getItemTransform());
|
|
}
|
|
|
|
@Override
|
|
public @Nullable SlotAccess getSlot(int slot) {
|
|
if (slot == 0) {
|
|
return this.slot;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public @Nullable ItemRenderState itemRenderState() {
|
|
return this.itemRenderState;
|
|
}
|
|
|
|
@Override
|
|
protected void updateRenderSubState(boolean shouldInterpolate, float progress) {
|
|
ItemStack itemStack = this.getItemStack();
|
|
itemStack.setEntityRepresentation(this);
|
|
this.itemRenderState = new ItemRenderState(itemStack, this.getItemTransform());
|
|
}
|
|
|
|
public record ItemRenderState(ItemStack itemStack, ItemDisplayContext itemTransform) {
|
|
}
|
|
}
|
|
}
|
|
|