minecraft-src/net/minecraft/world/level/block/entity/SignBlockEntity.java
2025-07-04 01:41:11 +03:00

282 lines
9.3 KiB
Java

package net.minecraft.world.level.block.entity;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.DynamicOps;
import java.util.List;
import java.util.UUID;
import java.util.function.UnaryOperator;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.Style;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.network.FilteredText;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.SignBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public class SignBlockEntity extends BlockEntity {
private static final Logger LOGGER = LogUtils.getLogger();
private static final int MAX_TEXT_LINE_WIDTH = 90;
private static final int TEXT_LINE_HEIGHT = 10;
@Nullable
private UUID playerWhoMayEdit;
private SignText frontText = this.createDefaultSignText();
private SignText backText = this.createDefaultSignText();
private boolean isWaxed;
public SignBlockEntity(BlockPos pos, BlockState blockState) {
this(BlockEntityType.SIGN, pos, blockState);
}
public SignBlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) {
super(type, pos, blockState);
}
protected SignText createDefaultSignText() {
return new SignText();
}
public boolean isFacingFrontText(Player player) {
if (this.getBlockState().getBlock() instanceof SignBlock signBlock) {
Vec3 vec3 = signBlock.getSignHitboxCenterPosition(this.getBlockState());
double d = player.getX() - (this.getBlockPos().getX() + vec3.x);
double e = player.getZ() - (this.getBlockPos().getZ() + vec3.z);
float f = signBlock.getYRotationDegrees(this.getBlockState());
float g = (float)(Mth.atan2(e, d) * 180.0F / (float)Math.PI) - 90.0F;
return Mth.degreesDifferenceAbs(f, g) <= 90.0F;
} else {
return false;
}
}
public SignText getText(boolean isFrontText) {
return isFrontText ? this.frontText : this.backText;
}
public SignText getFrontText() {
return this.frontText;
}
public SignText getBackText() {
return this.backText;
}
public int getTextLineHeight() {
return 10;
}
public int getMaxTextLineWidth() {
return 90;
}
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
DynamicOps<Tag> dynamicOps = registries.createSerializationContext(NbtOps.INSTANCE);
SignText.DIRECT_CODEC.encodeStart(dynamicOps, this.frontText).resultOrPartial(LOGGER::error).ifPresent(tagx -> tag.put("front_text", tagx));
SignText.DIRECT_CODEC.encodeStart(dynamicOps, this.backText).resultOrPartial(LOGGER::error).ifPresent(tagx -> tag.put("back_text", tagx));
tag.putBoolean("is_waxed", this.isWaxed);
}
@Override
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.loadAdditional(tag, registries);
DynamicOps<Tag> dynamicOps = registries.createSerializationContext(NbtOps.INSTANCE);
if (tag.contains("front_text")) {
SignText.DIRECT_CODEC
.parse(dynamicOps, tag.getCompound("front_text"))
.resultOrPartial(LOGGER::error)
.ifPresent(signText -> this.frontText = this.loadLines(signText));
}
if (tag.contains("back_text")) {
SignText.DIRECT_CODEC
.parse(dynamicOps, tag.getCompound("back_text"))
.resultOrPartial(LOGGER::error)
.ifPresent(signText -> this.backText = this.loadLines(signText));
}
this.isWaxed = tag.getBoolean("is_waxed");
}
private SignText loadLines(SignText text) {
for (int i = 0; i < 4; i++) {
Component component = this.loadLine(text.getMessage(i, false));
Component component2 = this.loadLine(text.getMessage(i, true));
text = text.setMessage(i, component, component2);
}
return text;
}
private Component loadLine(Component lineText) {
if (this.level instanceof ServerLevel serverLevel) {
try {
return ComponentUtils.updateForEntity(createCommandSourceStack(null, serverLevel, this.worldPosition), lineText, null, 0);
} catch (CommandSyntaxException var4) {
}
}
return lineText;
}
public void updateSignText(Player player, boolean isFrontText, List<FilteredText> filteredText) {
if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) {
this.updateText(signText -> this.setMessages(player, filteredText, signText), isFrontText);
this.setAllowedPlayerEditor(null);
this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
} else {
LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
}
}
public boolean updateText(UnaryOperator<SignText> updater, boolean isFrontText) {
SignText signText = this.getText(isFrontText);
return this.setText((SignText)updater.apply(signText), isFrontText);
}
private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text) {
for (int i = 0; i < filteredText.size(); i++) {
FilteredText filteredText2 = (FilteredText)filteredText.get(i);
Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
if (player.isTextFilteringEnabled()) {
text = text.setMessage(i, Component.literal(filteredText2.filteredOrEmpty()).setStyle(style));
} else {
text = text.setMessage(i, Component.literal(filteredText2.raw()).setStyle(style), Component.literal(filteredText2.filteredOrEmpty()).setStyle(style));
}
}
return text;
}
public boolean setText(SignText text, boolean isFrontText) {
return isFrontText ? this.setFrontText(text) : this.setBackText(text);
}
private boolean setBackText(SignText text) {
if (text != this.backText) {
this.backText = text;
this.markUpdated();
return true;
} else {
return false;
}
}
private boolean setFrontText(SignText text) {
if (text != this.frontText) {
this.frontText = text;
this.markUpdated();
return true;
} else {
return false;
}
}
public boolean canExecuteClickCommands(boolean isFrontText, Player player) {
return this.isWaxed() && this.getText(isFrontText).hasAnyClickCommands(player);
}
public boolean executeClickCommandsIfPresent(Player player, Level level, BlockPos pos, boolean frontText) {
boolean bl = false;
for (Component component : this.getText(frontText).getMessages(player.isTextFilteringEnabled())) {
Style style = component.getStyle();
ClickEvent clickEvent = style.getClickEvent();
if (clickEvent != null && clickEvent.getAction() == ClickEvent.Action.RUN_COMMAND) {
player.getServer().getCommands().performPrefixedCommand(createCommandSourceStack(player, level, pos), clickEvent.getValue());
bl = true;
}
}
return bl;
}
private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level level, BlockPos pos) {
String string = player == null ? "Sign" : player.getName().getString();
Component component = (Component)(player == null ? Component.literal("Sign") : player.getDisplayName());
return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel)level, 2, string, component, level.getServer(), player);
}
public ClientboundBlockEntityDataPacket getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
return this.saveCustomOnly(registries);
}
@Override
public boolean onlyOpCanSetNbt() {
return true;
}
public void setAllowedPlayerEditor(@Nullable UUID playWhoMayEdit) {
this.playerWhoMayEdit = playWhoMayEdit;
}
@Nullable
public UUID getPlayerWhoMayEdit() {
return this.playerWhoMayEdit;
}
private void markUpdated() {
this.setChanged();
this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
}
public boolean isWaxed() {
return this.isWaxed;
}
public boolean setWaxed(boolean isWaxed) {
if (this.isWaxed != isWaxed) {
this.isWaxed = isWaxed;
this.markUpdated();
return true;
} else {
return false;
}
}
public boolean playerIsTooFarAwayToEdit(UUID uuid) {
Player player = this.level.getPlayerByUUID(uuid);
return player == null || !player.canInteractWithBlock(this.getBlockPos(), 4.0);
}
public static void tick(Level level, BlockPos pos, BlockState state, SignBlockEntity sign) {
UUID uUID = sign.getPlayerWhoMayEdit();
if (uUID != null) {
sign.clearInvalidPlayerWhoMayEdit(sign, level, uUID);
}
}
private void clearInvalidPlayerWhoMayEdit(SignBlockEntity sign, Level level, UUID uuid) {
if (sign.playerIsTooFarAwayToEdit(uuid)) {
sign.setAllowedPlayerEditor(null);
}
}
public SoundEvent getSignInteractionFailedSoundEvent() {
return SoundEvents.WAXED_SIGN_INTERACT_FAIL;
}
}