Skip to content

Commit

Permalink
Improve retexture block entity impl
Browse files Browse the repository at this point in the history
Want to discourage use of forge data for the texture as its difficult to sync
Added syncing for the forge data approach, as you can see its difficult
  • Loading branch information
KnightMiner committed Nov 1, 2022
1 parent a79684a commit 0bdd148
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package slimeknights.mantle.block.entity;

import lombok.Getter;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.common.util.Lazy;
import slimeknights.mantle.block.RetexturedBlock;
import slimeknights.mantle.util.RetexturedHelper;

import javax.annotation.Nonnull;

import static slimeknights.mantle.util.RetexturedHelper.TAG_TEXTURE;

/**
* Standard implementation for {@link IRetexturedBlockEntity}, use alongside {@link RetexturedBlock} and {@link slimeknights.mantle.item.RetexturedBlockItem}
*/
public class DefaultRetexturedBlockEntity extends MantleBlockEntity implements IRetexturedBlockEntity {
private final Lazy<IModelData> data = Lazy.of(this::getRetexturedModelData);
@Nonnull
@Getter
private Block texture = Blocks.AIR;
public DefaultRetexturedBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}

@Nonnull
@Override
public IModelData getModelData() {
return this.data.get();
}

@Override
public String getTextureName() {
return RetexturedHelper.getTextureName(texture);
}

@Override
public void updateTexture(String name) {
Block oldTexture = texture;
texture = RetexturedHelper.getBlock(name);
if (oldTexture != texture) {
setChangedFast();
RetexturedHelper.onTextureUpdated(this);
}
}

@Override
protected boolean shouldSyncOnUpdate() {
return true;
}

@Override
protected void saveSynced(CompoundTag tags) {
super.saveSynced(tags);
if (texture != Blocks.AIR) {
tags.putString(TAG_TEXTURE, getTextureName());
}
}

@Override
public void load(CompoundTag tags) {
super.load(tags);
if (tags.contains(TAG_TEXTURE, Tag.TAG_STRING)) {
texture = RetexturedHelper.getBlock(tags.getString(TAG_TEXTURE));
RetexturedHelper.onTextureUpdated(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

/**
* Standard interface that should be used by retexturable tile entities, allows control over where the texture is saved.
* Note that in the future, more of these methods will be made abstract, discouraging the use of {@link #getTileData()} to store the texture (as we can sync our own tag easier)
*
* Use alongside {@link RetexturedBlock} and {@link slimeknights.mantle.item.RetexturedBlockItem}. See {@link RetexturedBlockEntity} for implementation.
* Use alongside {@link RetexturedBlock} and {@link slimeknights.mantle.item.RetexturedBlockItem}. See {@link DefaultRetexturedBlockEntity} for implementation.
*/
public interface IRetexturedBlockEntity {
/* Gets the Forge tile data for the tile entity */
Expand All @@ -21,10 +22,10 @@ public interface IRetexturedBlockEntity {
* Gets the current texture block name. Encouraged to override this to not use {@link #getTileData()}
* @return Texture block name
*/

default String getTextureName() {
return RetexturedHelper.getTextureName(getTileData());
}

/**
* Gets the current texture block
* @return Texture block
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.common.util.Lazy;
import slimeknights.mantle.block.RetexturedBlock;
import slimeknights.mantle.util.RetexturedHelper;

import javax.annotation.Nonnull;

import static slimeknights.mantle.util.RetexturedHelper.TAG_TEXTURE;

/**
* Minimal implementation for {@link IRetexturedBlockEntity}, use alongside {@link RetexturedBlock} and {@link slimeknights.mantle.item.RetexturedBlockItem}
* Minimal implementation of retextured blocks by storing data in the block entity. Does not handle syncing the best
* @deprecated use {@link DefaultRetexturedBlockEntity}
*/
@Deprecated
public class RetexturedBlockEntity extends MantleBlockEntity implements IRetexturedBlockEntity {

/** Lazy value of model data as it will not change after first fetch */
private final Lazy<IModelData> data = Lazy.of(this::getRetexturedModelData);
public RetexturedBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
Expand All @@ -33,6 +35,17 @@ protected boolean shouldSyncOnUpdate() {
return true;
}

@Override
protected void saveSynced(CompoundTag nbt) {
super.saveSynced(nbt);
// ensure the texture syncs, by default forge data does not
if (!nbt.contains("ForgeData")) {
CompoundTag forgeData = new CompoundTag();
forgeData.putString(TAG_TEXTURE, getTextureName());
nbt.put("ForgeData", forgeData);
}
}

@Override
public void load(CompoundTag tags) {
String oldName = getTextureName();
Expand Down
37 changes: 36 additions & 1 deletion src/main/java/slimeknights/mantle/util/RetexturedHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@
import lombok.NoArgsConstructor;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.data.ModelProperty;
import net.minecraftforge.registries.ForgeRegistries;
import slimeknights.mantle.block.entity.IRetexturedBlockEntity;

import javax.annotation.Nullable;
import java.util.Objects;

/**
* This utility contains helpers to handle the NBT for retexturable blocks
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RetexturedHelper {
/** Tag name for texture blocks. Should not be used directly, use the utils to interact */
private static final String TAG_TEXTURE = "texture";
public static final String TAG_TEXTURE = "texture";
/** Property for tile entities containing a texture block */
public static final ModelProperty<Block> BLOCK_PROPERTY = new ModelProperty<>(block -> block != Blocks.AIR);

Expand Down Expand Up @@ -51,6 +57,18 @@ public static String getTextureName(@Nullable CompoundTag nbt) {
return nbt.getString(TAG_TEXTURE);
}

/**
* Gets the name of the texture from the block
* @param block Block
* @return Name of the texture, or empty if the block is air
*/
public static String getTextureName(Block block) {
if (block == Blocks.AIR) {
return "";
}
return Objects.requireNonNull(block.getRegistryName()).toString();
}


/* Setting */

Expand All @@ -68,4 +86,21 @@ public static void setTexture(@Nullable CompoundTag nbt, String texture) {
}
}
}

/** Helper to call client side when the texture changes to refresh model data */
public static <T extends BlockEntity & IRetexturedBlockEntity> void onTextureUpdated(T self) {
// update the texture in BE data
Level level = self.getLevel();
if (level != null && level.isClientSide) {
Block texture = self.getTexture();
texture = texture == Blocks.AIR ? null : texture;
IModelData data = self.getModelData();
if (data.getData(BLOCK_PROPERTY) != texture) {
data.setData(BLOCK_PROPERTY, texture);
self.requestModelDataUpdate();
BlockState state = self.getBlockState();
level.sendBlockUpdated(self.getBlockPos(), state, state, 0);
}
}
}
}

0 comments on commit 0bdd148

Please sign in to comment.