package net.minecraft.world.entity; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.UUID; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; 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.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class Interaction extends Entity implements Attackable, Targeting { private static final EntityDataAccessor DATA_WIDTH_ID = SynchedEntityData.defineId(Interaction.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor DATA_HEIGHT_ID = SynchedEntityData.defineId(Interaction.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor DATA_RESPONSE_ID = SynchedEntityData.defineId(Interaction.class, EntityDataSerializers.BOOLEAN); private static final String TAG_WIDTH = "width"; private static final String TAG_HEIGHT = "height"; private static final String TAG_ATTACK = "attack"; private static final String TAG_INTERACTION = "interaction"; private static final String TAG_RESPONSE = "response"; private static final float DEFAULT_WIDTH = 1.0F; private static final float DEFAULT_HEIGHT = 1.0F; private static final boolean DEFAULT_RESPONSE = false; @Nullable private Interaction.PlayerAction attack; @Nullable private Interaction.PlayerAction interaction; public Interaction(EntityType entityType, Level level) { super(entityType, level); this.noPhysics = true; } @Override protected void defineSynchedData(Builder builder) { builder.define(DATA_WIDTH_ID, 1.0F); builder.define(DATA_HEIGHT_ID, 1.0F); builder.define(DATA_RESPONSE_ID, false); } @Override protected void readAdditionalSaveData(CompoundTag tag) { this.setWidth(tag.getFloatOr("width", 1.0F)); this.setHeight(tag.getFloatOr("height", 1.0F)); this.attack = (Interaction.PlayerAction)tag.read("attack", Interaction.PlayerAction.CODEC).orElse(null); this.interaction = (Interaction.PlayerAction)tag.read("interaction", Interaction.PlayerAction.CODEC).orElse(null); this.setResponse(tag.getBooleanOr("response", false)); this.setBoundingBox(this.makeBoundingBox()); } @Override protected void addAdditionalSaveData(CompoundTag tag) { tag.putFloat("width", this.getWidth()); tag.putFloat("height", this.getHeight()); tag.storeNullable("attack", Interaction.PlayerAction.CODEC, this.attack); tag.storeNullable("interaction", Interaction.PlayerAction.CODEC, this.interaction); tag.putBoolean("response", this.getResponse()); } @Override public void onSyncedDataUpdated(EntityDataAccessor dataAccessor) { super.onSyncedDataUpdated(dataAccessor); if (DATA_HEIGHT_ID.equals(dataAccessor) || DATA_WIDTH_ID.equals(dataAccessor)) { this.refreshDimensions(); } } @Override public boolean canBeHitByProjectile() { return false; } @Override public boolean isPickable() { return true; } @Override public PushReaction getPistonPushReaction() { return PushReaction.IGNORE; } @Override public boolean isIgnoringBlockTriggers() { return true; } @Override public boolean skipAttackInteraction(Entity entity) { if (entity instanceof Player player) { this.attack = new Interaction.PlayerAction(player.getUUID(), this.level().getGameTime()); if (player instanceof ServerPlayer serverPlayer) { CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(serverPlayer, this, player.damageSources().generic(), 1.0F, 1.0F, false); } return !this.getResponse(); } else { return false; } } @Override public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) { return false; } @Override public InteractionResult interact(Player player, InteractionHand hand) { if (this.level().isClientSide) { return this.getResponse() ? InteractionResult.SUCCESS : InteractionResult.CONSUME; } else { this.interaction = new Interaction.PlayerAction(player.getUUID(), this.level().getGameTime()); return InteractionResult.CONSUME; } } @Override public void tick() { } @Nullable @Override public LivingEntity getLastAttacker() { return this.attack != null ? this.level().getPlayerByUUID(this.attack.player()) : null; } @Nullable @Override public LivingEntity getTarget() { return this.interaction != null ? this.level().getPlayerByUUID(this.interaction.player()) : null; } 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 float getHeight() { return this.entityData.get(DATA_HEIGHT_ID); } private void setResponse(boolean response) { this.entityData.set(DATA_RESPONSE_ID, response); } private boolean getResponse() { return this.entityData.get(DATA_RESPONSE_ID); } private EntityDimensions getDimensions() { return EntityDimensions.scalable(this.getWidth(), this.getHeight()); } @Override public EntityDimensions getDimensions(Pose pose) { return this.getDimensions(); } @Override protected AABB makeBoundingBox(Vec3 position) { return this.getDimensions().makeBoundingBox(position); } record PlayerAction(UUID player, long timestamp) { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( UUIDUtil.CODEC.fieldOf("player").forGetter(Interaction.PlayerAction::player), Codec.LONG.fieldOf("timestamp").forGetter(Interaction.PlayerAction::timestamp) ) .apply(instance, Interaction.PlayerAction::new) ); } }