2023-07-06 05:57:19 +02:00
|
|
|
package io.github.cottonmc.templates.block;
|
2019-06-19 22:32:58 +02:00
|
|
|
|
2023-06-16 04:50:41 +02:00
|
|
|
import io.github.cottonmc.templates.Templates;
|
2023-07-31 06:21:20 +02:00
|
|
|
import io.github.cottonmc.templates.api.TemplateInteractionUtil;
|
2023-07-04 02:39:48 +02:00
|
|
|
import io.github.cottonmc.templates.api.ThemeableBlockEntity;
|
2019-06-19 22:32:58 +02:00
|
|
|
import net.minecraft.block.BlockState;
|
|
|
|
import net.minecraft.block.Blocks;
|
|
|
|
import net.minecraft.block.entity.BlockEntity;
|
|
|
|
import net.minecraft.block.entity.BlockEntityType;
|
2023-07-31 05:39:11 +02:00
|
|
|
import net.minecraft.item.BlockItem;
|
|
|
|
import net.minecraft.item.ItemStack;
|
2023-06-15 09:08:20 +02:00
|
|
|
import net.minecraft.nbt.NbtCompound;
|
2023-07-31 05:39:11 +02:00
|
|
|
import net.minecraft.nbt.NbtElement;
|
2023-06-15 07:53:04 +02:00
|
|
|
import net.minecraft.nbt.NbtHelper;
|
2023-06-15 10:24:11 +02:00
|
|
|
import net.minecraft.network.listener.ClientPlayPacketListener;
|
|
|
|
import net.minecraft.network.packet.Packet;
|
|
|
|
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
|
2023-06-15 09:08:20 +02:00
|
|
|
import net.minecraft.registry.Registries;
|
2023-06-16 04:50:41 +02:00
|
|
|
import net.minecraft.server.world.ServerWorld;
|
2023-06-15 09:08:20 +02:00
|
|
|
import net.minecraft.util.math.BlockPos;
|
2023-06-15 10:24:11 +02:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
import javax.annotation.Nonnull;
|
2023-06-15 10:24:11 +02:00
|
|
|
import java.util.Objects;
|
2019-06-19 22:32:58 +02:00
|
|
|
|
2023-07-04 03:55:32 +02:00
|
|
|
public class TemplateEntity extends BlockEntity implements ThemeableBlockEntity {
|
2019-06-19 22:32:58 +02:00
|
|
|
protected BlockState renderedState = Blocks.AIR.getDefaultState();
|
2023-07-31 05:39:11 +02:00
|
|
|
protected byte bitfield = DEFAULT_BITFIELD;
|
2023-07-03 09:35:07 +02:00
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
protected static final int SPENT_GLOWSTONE_DUST_MASK = 0b00000001;
|
|
|
|
protected static final int SPENT_REDSTONE_TORCH_MASK = 0b00000010;
|
|
|
|
protected static final int SPENT_POPPED_CHORUS_MASK = 0b00000100;
|
|
|
|
protected static final int EMITS_REDSTONE_MASK = 0b00001000;
|
|
|
|
protected static final int IS_SOLID_MASK = 0b00010000;
|
|
|
|
protected static final byte DEFAULT_BITFIELD = IS_SOLID_MASK; //brand-new templates shall be solid
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-07-31 06:21:20 +02:00
|
|
|
//Using one-character names is a little brash, like, what if there's a mod that adds crap to the NBT of every
|
|
|
|
//block entity, and uses short names for the same reason I am (because there are lots and lots of block entities)?
|
2023-07-31 05:39:11 +02:00
|
|
|
//Kinda doubt it?
|
|
|
|
protected static final String BLOCKSTATE_KEY = "s";
|
|
|
|
protected static final String BITFIELD_KEY = "b";
|
2023-07-08 08:29:22 +02:00
|
|
|
|
2023-06-16 04:50:41 +02:00
|
|
|
public TemplateEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
|
2023-06-15 09:08:20 +02:00
|
|
|
super(type, pos, state);
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2019-06-19 22:32:58 +02:00
|
|
|
@Override
|
2023-06-15 09:08:20 +02:00
|
|
|
public void readNbt(NbtCompound tag) {
|
|
|
|
super.readNbt(tag);
|
2023-06-16 04:50:41 +02:00
|
|
|
|
|
|
|
BlockState lastRenderedState = renderedState;
|
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
if(tag.contains("BlockState")) { //2.0.4 and earlier
|
|
|
|
renderedState = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
|
|
|
|
|
|
|
|
if(tag.getBoolean("spentglow")) spentGlowstoneDust();
|
|
|
|
if(tag.getBoolean("spentredst")) spentRedstoneTorch();
|
|
|
|
if(tag.getBoolean("spentchor")) spentPoppedChorus();
|
|
|
|
setEmitsRedstone(tag.getBoolean("emitsredst"));
|
|
|
|
setSolidity(!tag.contains("solid") || tag.getBoolean("solid")); //default to "true" if it's nonexistent
|
|
|
|
} else {
|
|
|
|
renderedState = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound(BLOCKSTATE_KEY));
|
|
|
|
bitfield = tag.contains(BITFIELD_KEY) ? tag.getByte(BITFIELD_KEY) : DEFAULT_BITFIELD;
|
|
|
|
}
|
2023-06-16 04:50:41 +02:00
|
|
|
|
2023-07-03 09:35:07 +02:00
|
|
|
//Force a chunk remesh on the client if the displayed blockstate has changed
|
2023-07-21 09:49:49 +02:00
|
|
|
//TODO: doors? (need remeshing when the *other* party changes)
|
2023-06-16 04:50:41 +02:00
|
|
|
if(world != null && world.isClient && !Objects.equals(lastRenderedState, renderedState)) {
|
|
|
|
Templates.chunkRerenderProxy.accept(world, pos);
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2019-06-19 22:32:58 +02:00
|
|
|
@Override
|
2023-06-15 09:08:20 +02:00
|
|
|
public void writeNbt(NbtCompound tag) {
|
|
|
|
super.writeNbt(tag);
|
2023-07-08 08:29:22 +02:00
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
if(renderedState != Blocks.AIR.getDefaultState()) tag.put(BLOCKSTATE_KEY, NbtHelper.fromBlockState(renderedState));
|
|
|
|
if(bitfield != DEFAULT_BITFIELD) tag.putByte(BITFIELD_KEY, bitfield);
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
public static @Nonnull BlockState readStateFromItem(ItemStack stack) {
|
|
|
|
NbtCompound blockEntityTag = BlockItem.getBlockEntityNbt(stack);
|
|
|
|
if(blockEntityTag == null) return Blocks.AIR.getDefaultState();
|
|
|
|
|
|
|
|
//slightly paranoid NBT handling cause you never know what mysteries are afoot with items
|
|
|
|
NbtElement subElement;
|
|
|
|
if(blockEntityTag.contains(BLOCKSTATE_KEY)) subElement = blockEntityTag.get(BLOCKSTATE_KEY); //2.0.5
|
|
|
|
else if(blockEntityTag.contains("BlockState")) subElement = blockEntityTag.get("BlockState"); //old 2.0.4 items
|
|
|
|
else return Blocks.AIR.getDefaultState();
|
|
|
|
|
|
|
|
if(!(subElement instanceof NbtCompound subCompound)) return Blocks.AIR.getDefaultState();
|
|
|
|
|
|
|
|
else return NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), subCompound);
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-07-31 06:21:20 +02:00
|
|
|
//Awkward: usually the BlockState is the source of truth for things like the "emits light" blockstate, but if you
|
|
|
|
//ctrl-pick a glowing block and place it, it should still be glowing. This is some hacky shit that guesses the value of
|
|
|
|
//the LIGHT blockstate based off information in the NBT tag, and also prevents bugginess like "the blockstate is not
|
|
|
|
//glowing but the copied NBT thinks glowstone dust was already added, so it refuses to accept more dust"
|
|
|
|
public static @Nullable BlockState weirdNbtLightLevelStuff(@Nullable BlockState state, ItemStack stack) {
|
|
|
|
if(state == null || stack == null) return state;
|
|
|
|
|
|
|
|
NbtCompound blockEntityTag = BlockItem.getBlockEntityNbt(stack);
|
|
|
|
if(blockEntityTag == null) return state;
|
|
|
|
|
|
|
|
if(state.contains(TemplateInteractionUtil.LIGHT)) {
|
|
|
|
state = state.with(TemplateInteractionUtil.LIGHT,
|
|
|
|
blockEntityTag.getBoolean("spentglow") || //2.0.4
|
|
|
|
((blockEntityTag.contains(BITFIELD_KEY) ? blockEntityTag.getByte(BITFIELD_KEY) : DEFAULT_BITFIELD) & SPENT_GLOWSTONE_DUST_MASK) != 0 || //2.0.5
|
|
|
|
readStateFromItem(stack).getLuminance() != 0 //glowstone dust wasn't manually added, the block just emits light
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
//RenderAttachmentBlockEntity impl. Note that ThemeableBlockEntity depends on this returning a BlockState object.
|
2019-06-19 22:32:58 +02:00
|
|
|
@Override
|
|
|
|
public BlockState getRenderAttachmentData() {
|
|
|
|
return renderedState;
|
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-06-15 10:24:11 +02:00
|
|
|
public void setRenderedState(BlockState newState) {
|
2023-07-03 09:35:07 +02:00
|
|
|
if(!Objects.equals(renderedState, newState)) {
|
|
|
|
renderedState = newState;
|
2023-07-31 05:39:11 +02:00
|
|
|
markDirtyAndDispatch();
|
2023-07-03 09:35:07 +02:00
|
|
|
}
|
2023-06-15 10:24:11 +02:00
|
|
|
}
|
|
|
|
|
2023-07-03 09:35:07 +02:00
|
|
|
public boolean hasSpentGlowstoneDust() {
|
2023-07-31 05:39:11 +02:00
|
|
|
return (bitfield & SPENT_GLOWSTONE_DUST_MASK) != 0;
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-07-03 09:35:07 +02:00
|
|
|
public void spentGlowstoneDust() {
|
2023-07-31 05:39:11 +02:00
|
|
|
bitfield |= SPENT_GLOWSTONE_DUST_MASK;
|
|
|
|
markDirtyAndDispatch();
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-07-03 09:35:07 +02:00
|
|
|
public boolean hasSpentRedstoneTorch() {
|
2023-07-31 05:39:11 +02:00
|
|
|
return (bitfield & SPENT_REDSTONE_TORCH_MASK) != 0;
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-06-15 07:59:48 +02:00
|
|
|
|
2023-07-03 09:35:07 +02:00
|
|
|
public void spentRedstoneTorch() {
|
2023-07-31 05:39:11 +02:00
|
|
|
bitfield |= SPENT_REDSTONE_TORCH_MASK;
|
|
|
|
markDirtyAndDispatch();
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|
2023-07-06 05:40:12 +02:00
|
|
|
|
|
|
|
public boolean hasSpentPoppedChorus() {
|
2023-07-31 05:39:11 +02:00
|
|
|
return (bitfield & SPENT_POPPED_CHORUS_MASK) != 0;
|
2023-07-06 05:40:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void spentPoppedChorus() {
|
2023-07-31 05:39:11 +02:00
|
|
|
bitfield |= SPENT_POPPED_CHORUS_MASK;
|
|
|
|
markDirtyAndDispatch();
|
2023-07-06 05:40:12 +02:00
|
|
|
}
|
2023-07-08 08:29:22 +02:00
|
|
|
|
|
|
|
public boolean emitsRedstone() {
|
2023-07-31 05:39:11 +02:00
|
|
|
return (bitfield & EMITS_REDSTONE_MASK) != 0;
|
2023-07-08 08:29:22 +02:00
|
|
|
}
|
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
public void setEmitsRedstone(boolean nextEmitsRedstone) {
|
|
|
|
boolean currentlyEmitsRedstone = emitsRedstone();
|
|
|
|
|
|
|
|
if(currentlyEmitsRedstone != nextEmitsRedstone) {
|
|
|
|
if(currentlyEmitsRedstone) bitfield &= ~EMITS_REDSTONE_MASK;
|
|
|
|
else bitfield |= EMITS_REDSTONE_MASK;
|
|
|
|
markDirtyAndDispatch();
|
2023-07-08 08:29:22 +02:00
|
|
|
if(world != null) world.updateNeighbors(pos, getCachedState().getBlock());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isSolid() {
|
2023-07-31 05:39:11 +02:00
|
|
|
return (bitfield & IS_SOLID_MASK) != 0;
|
2023-07-08 08:29:22 +02:00
|
|
|
}
|
|
|
|
|
2023-07-31 05:39:11 +02:00
|
|
|
public void setSolidity(boolean nextSolid) {
|
|
|
|
boolean currentlySolid = isSolid();
|
|
|
|
|
|
|
|
if(currentlySolid != nextSolid) {
|
|
|
|
if(currentlySolid) bitfield &= ~IS_SOLID_MASK;
|
|
|
|
else bitfield |= IS_SOLID_MASK;
|
|
|
|
markDirtyAndDispatch();
|
|
|
|
if(world != null) world.setBlockState(pos, getCachedState()); //do i need to invalidate any shape caches or something
|
2023-07-08 08:29:22 +02:00
|
|
|
}
|
|
|
|
}
|
2023-07-31 05:39:11 +02:00
|
|
|
|
|
|
|
//<standard blockentity boilerplate>
|
|
|
|
@Nullable
|
|
|
|
@Override
|
|
|
|
public Packet<ClientPlayPacketListener> toUpdatePacket() {
|
|
|
|
return BlockEntityUpdateS2CPacket.create(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public NbtCompound toInitialChunkDataNbt() {
|
|
|
|
//TERRIBLE yarn name, this is "getUpdateTag", it's the nbt that will be sent to clients
|
|
|
|
//and it just calls "writeNbt"
|
|
|
|
return createNbt();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void dispatch() {
|
|
|
|
if(world instanceof ServerWorld sworld) sworld.getChunkManager().markForUpdate(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void markDirtyAndDispatch() {
|
|
|
|
markDirty();
|
|
|
|
dispatch();
|
|
|
|
}
|
|
|
|
//</standard blockentity boilerplate>
|
2019-06-19 22:32:58 +02:00
|
|
|
}
|