package net.minecraft.world.level.block.entity; import com.google.common.annotations.VisibleForTesting; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.resources.RegistryOps; import net.minecraft.world.Container; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.JukeboxSong; import net.minecraft.world.item.JukeboxSongPlayer; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.JukeboxBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent.Context; import net.minecraft.world.phys.Vec3; import net.minecraft.world.ticks.ContainerSingleItem; public class JukeboxBlockEntity extends BlockEntity implements ContainerSingleItem.BlockContainerSingleItem { public static final String SONG_ITEM_TAG_ID = "RecordItem"; public static final String TICKS_SINCE_SONG_STARTED_TAG_ID = "ticks_since_song_started"; private ItemStack item = ItemStack.EMPTY; private final JukeboxSongPlayer jukeboxSongPlayer = new JukeboxSongPlayer(this::onSongChanged, this.getBlockPos()); public JukeboxBlockEntity(BlockPos pos, BlockState blockState) { super(BlockEntityType.JUKEBOX, pos, blockState); } public JukeboxSongPlayer getSongPlayer() { return this.jukeboxSongPlayer; } public void onSongChanged() { this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock()); this.setChanged(); } private void notifyItemChangedInJukebox(boolean hasRecord) { if (this.level != null && this.level.getBlockState(this.getBlockPos()) == this.getBlockState()) { this.level.setBlock(this.getBlockPos(), this.getBlockState().setValue(JukeboxBlock.HAS_RECORD, hasRecord), 2); this.level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), Context.of(this.getBlockState())); } } public void popOutTheItem() { if (this.level != null && !this.level.isClientSide) { BlockPos blockPos = this.getBlockPos(); ItemStack itemStack = this.getTheItem(); if (!itemStack.isEmpty()) { this.removeTheItem(); Vec3 vec3 = Vec3.atLowerCornerWithOffset(blockPos, 0.5, 1.01, 0.5).offsetRandom(this.level.random, 0.7F); ItemStack itemStack2 = itemStack.copy(); ItemEntity itemEntity = new ItemEntity(this.level, vec3.x(), vec3.y(), vec3.z(), itemStack2); itemEntity.setDefaultPickUpDelay(); this.level.addFreshEntity(itemEntity); } } } public static void tick(Level level, BlockPos pos, BlockState state, JukeboxBlockEntity jukebox) { jukebox.jukeboxSongPlayer.tick(level, state); } public int getComparatorOutput() { return (Integer)JukeboxSong.fromStack(this.level.registryAccess(), this.item).map(Holder::value).map(JukeboxSong::comparatorOutput).orElse(0); } @Override protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.loadAdditional(tag, registries); RegistryOps registryOps = registries.createSerializationContext(NbtOps.INSTANCE); ItemStack itemStack = (ItemStack)tag.read("RecordItem", ItemStack.CODEC, registryOps).orElse(ItemStack.EMPTY); if (!this.item.isEmpty() && !ItemStack.isSameItemSameComponents(itemStack, this.item)) { this.jukeboxSongPlayer.stop(this.level, this.getBlockState()); } this.item = itemStack; tag.getLong("ticks_since_song_started") .ifPresent(long_ -> JukeboxSong.fromStack(registries, this.item).ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying(holder, long_))); } @Override protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.saveAdditional(tag, registries); if (!this.getTheItem().isEmpty()) { RegistryOps registryOps = registries.createSerializationContext(NbtOps.INSTANCE); tag.store("RecordItem", ItemStack.CODEC, registryOps, this.getTheItem()); } if (this.jukeboxSongPlayer.getSong() != null) { tag.putLong("ticks_since_song_started", this.jukeboxSongPlayer.getTicksSinceSongStarted()); } } @Override public ItemStack getTheItem() { return this.item; } @Override public ItemStack splitTheItem(int amount) { ItemStack itemStack = this.item; this.setTheItem(ItemStack.EMPTY); return itemStack; } @Override public void setTheItem(ItemStack item) { this.item = item; boolean bl = !this.item.isEmpty(); Optional> optional = JukeboxSong.fromStack(this.level.registryAccess(), this.item); this.notifyItemChangedInJukebox(bl); if (bl && optional.isPresent()) { this.jukeboxSongPlayer.play(this.level, (Holder)optional.get()); } else { this.jukeboxSongPlayer.stop(this.level, this.getBlockState()); } } @Override public int getMaxStackSize() { return 1; } @Override public BlockEntity getContainerBlockEntity() { return this; } @Override public boolean canPlaceItem(int slot, ItemStack stack) { return stack.has(DataComponents.JUKEBOX_PLAYABLE) && this.getItem(slot).isEmpty(); } @Override public boolean canTakeItem(Container target, int slot, ItemStack stack) { return target.hasAnyMatching(ItemStack::isEmpty); } @Override public void preRemoveSideEffects(BlockPos pos, BlockState state) { this.popOutTheItem(); } @VisibleForTesting public void setSongItemWithoutPlaying(ItemStack stack) { this.item = stack; JukeboxSong.fromStack(this.level.registryAccess(), stack).ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying(holder, 0L)); this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock()); this.setChanged(); } @VisibleForTesting public void tryForcePlaySong() { JukeboxSong.fromStack(this.level.registryAccess(), this.getTheItem()).ifPresent(holder -> this.jukeboxSongPlayer.play(this.level, holder)); } }