Skip to content

Commit

Permalink
Rewrite block entities in Java
Browse files Browse the repository at this point in the history
Fixed bugs / code adjusted to match vanilla:
  BaseContainerBlockEntity.readNbt:
    recreate item list before reading
  • Loading branch information
Juuxel committed Jul 27, 2024
1 parent 935e94c commit 4fd7fa7
Show file tree
Hide file tree
Showing 37 changed files with 908 additions and 690 deletions.
46 changes: 46 additions & 0 deletions common/src/main/java/juuxel/adorn/block/AdornBlockEntities.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package juuxel.adorn.block;

import juuxel.adorn.block.entity.AdornBlockEntityType;
import juuxel.adorn.block.entity.BrewerBlockEntity;
import juuxel.adorn.block.entity.DrawerBlockEntity;
import juuxel.adorn.block.entity.KitchenCupboardBlockEntity;
import juuxel.adorn.block.entity.KitchenSinkBlockEntity;
import juuxel.adorn.block.entity.ShelfBlockEntity;
import juuxel.adorn.block.entity.TradingStationBlockEntity;
import juuxel.adorn.lib.registry.Registered;
import juuxel.adorn.lib.registry.Registrar;
import juuxel.adorn.lib.registry.RegistrarFactory;
import juuxel.adorn.platform.PlatformBridges;
import net.minecraft.block.Block;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.entity.BlockEntityType.BlockEntityFactory;
import net.minecraft.registry.RegistryKeys;

import java.util.function.Supplier;

public final class AdornBlockEntities {
public static final Registrar<BlockEntityType<?>> BLOCK_ENTITIES = RegistrarFactory.get().create(RegistryKeys.BLOCK_ENTITY_TYPE);

public static final Registered<BlockEntityType<ShelfBlockEntity>> SHELF = register("shelf", ShelfBlockEntity::new, ShelfBlock.class);
public static final Registered<BlockEntityType<DrawerBlockEntity>> DRAWER = register("drawer", DrawerBlockEntity::new, DrawerBlock.class);
public static final Registered<BlockEntityType<KitchenCupboardBlockEntity>> KITCHEN_CUPBOARD =
register("kitchen_cupboard", KitchenCupboardBlockEntity::new, KitchenCupboardBlock.class);
public static final Registered<BlockEntityType<KitchenSinkBlockEntity>> KITCHEN_SINK =
register("kitchen_sink", PlatformBridges.Companion.getBlockEntities()::createKitchenSink, KitchenSinkBlock.class);
public static final Registered<BlockEntityType<TradingStationBlockEntity>> TRADING_STATION =
register("trading_station", TradingStationBlockEntity::new, AdornBlocks.INSTANCE::getTRADING_STATION);
public static final Registered<BlockEntityType<BrewerBlockEntity>> BREWER =
register("brewer", PlatformBridges.Companion.getBlockEntities()::createBrewer, AdornBlocks.INSTANCE::getBREWER);

private static <E extends BlockEntity> Registered<BlockEntityType<E>> register(String name, BlockEntityFactory<E> factory, Supplier<? extends Block> block) {
return BLOCK_ENTITIES.register(name, () -> BlockEntityType.Builder.create(factory, block.get()).build(null));
}

private static <E extends BlockEntity> Registered<BlockEntityType<E>> register(String name, BlockEntityFactory<E> factory, Class<? extends Block> blockClass) {
return BLOCK_ENTITIES.register(name, () -> new AdornBlockEntityType<>(factory, blockClass::isInstance));
}

public static void init() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package juuxel.adorn.block.entity;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;

import java.util.Set;
import java.util.function.Predicate;

public final class AdornBlockEntityType<E extends BlockEntity> extends BlockEntityType<E> {
private final Predicate<Block> blockPredicate;

public AdornBlockEntityType(BlockEntityFactory<? extends E> factory, Predicate<Block> blockPredicate) {
super(factory, Set.of(), null);
this.blockPredicate = blockPredicate;
}

@Override
public boolean supports(BlockState state) {
return blockPredicate.test(state.getBlock());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package juuxel.adorn.block.entity;

import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.entity.LootableContainerBlockEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.text.Text;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;

/**
* A container block entity that might not have a menu.
* This class handles the serialisation and the container logic.
*/
public abstract class BaseContainerBlockEntity extends LootableContainerBlockEntity {
private final int size;
private DefaultedList<ItemStack> items;

public BaseContainerBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state, int size) {
super(type, pos, state);
items = DefaultedList.ofSize(size, ItemStack.EMPTY);
this.size = size;
}

@Override
protected void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
if (!writeLootTable(nbt)) {
Inventories.writeNbt(nbt, items);
}
}

@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
items = DefaultedList.ofSize(size, ItemStack.EMPTY);
if (!readLootTable(nbt)) {
Inventories.readNbt(nbt, items);
}
}

@Override
protected DefaultedList<ItemStack> method_11282() {
return items;
}

@Override
protected void setInvStackList(DefaultedList<ItemStack> list) {
items = list;
}

@Override
public int size() {
return size;
}

@Override
protected Text getContainerName() {
return getCachedState().getBlock().getName();
}
}
193 changes: 193 additions & 0 deletions common/src/main/java/juuxel/adorn/block/entity/BrewerBlockEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package juuxel.adorn.block.entity;

import juuxel.adorn.block.AdornBlockEntities;
import juuxel.adorn.block.BrewerBlock;
import juuxel.adorn.item.AdornItems;
import juuxel.adorn.menu.BrewerMenu;
import juuxel.adorn.recipe.AdornRecipes;
import juuxel.adorn.recipe.BrewerInventory;
import juuxel.adorn.recipe.FluidBrewingRecipe;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.SidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.menu.Menu;
import net.minecraft.menu.property.PropertyDelegate;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.recipe.RecipeEntry;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;

public abstract class BrewerBlockEntity extends BaseContainerBlockEntity implements SidedInventory, BrewerInventory {
private static final String NBT_PROGRESS = "Progress";
public static final int CONTAINER_SIZE = 4;
public static final int INPUT_SLOT = 0;
public static final int LEFT_INGREDIENT_SLOT = 1;
public static final int RIGHT_INGREDIENT_SLOT = 2;
public static final int FLUID_CONTAINER_SLOT = 3;
public static final int MAX_PROGRESS = 200;
public static final int FLUID_CAPACITY_IN_BUCKETS = 2;

private int progress = 0;
private final PropertyDelegate propertyDelegate = new PropertyDelegate() {
@Override
public int get(int index) {
return switch (index) {
case 0 -> progress;
default -> throw new IllegalArgumentException("Unknown property: " + index);
};
}

@Override
public void set(int index, int value) {
switch (index) {
case 0 -> progress = value;
default -> throw new IllegalArgumentException("Unknown property: " + index);
}
}

@Override
public int size() {
return 1;
}
};

public BrewerBlockEntity(BlockPos pos, BlockState state) {
super(AdornBlockEntities.BREWER.get(), pos, state, CONTAINER_SIZE);
}

@Override
protected Menu createMenu(int syncId, PlayerInventory inv) {
return new BrewerMenu(syncId, inv, this, propertyDelegate, getFluidReference());
}

@Override
protected void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
nbt.putInt(NBT_PROGRESS, progress);
}

@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
progress = nbt.getInt(NBT_PROGRESS);
}

@Override
public int[] getAvailableSlots(Direction side) {
var facing = getCachedState().get(BrewerBlock.Companion.getFACING());

if (side == facing.rotateYClockwise()) {
return new int[] { LEFT_INGREDIENT_SLOT };
} else if (side == facing.rotateYCounterclockwise()) {
return new int[] { RIGHT_INGREDIENT_SLOT };
} else if (side == facing.getOpposite()) {
return new int[] { FLUID_CONTAINER_SLOT };
} else if (side == Direction.UP) {
return new int[] { INPUT_SLOT };
} else if (side == Direction.DOWN) {
return new int[] { INPUT_SLOT, FLUID_CONTAINER_SLOT };
} else {
return new int[0];
}
}

@Override
public boolean isValid(int slot, ItemStack stack) {
if (slot == INPUT_SLOT && !(stack.isOf(AdornItems.INSTANCE.getMUG()) && getStack(slot).isEmpty())) return false;
if (slot == FLUID_CONTAINER_SLOT && !getStack(slot).isEmpty()) return false;
return true;
}

@Override
public boolean canInsert(int slot, ItemStack stack, @Nullable Direction dir) {
return dir != Direction.DOWN && isValid(slot, stack);
}

@Override
public boolean canExtract(int slot, ItemStack stack, Direction dir) {
return dir == Direction.DOWN && (slot != FLUID_CONTAINER_SLOT || canExtractFluidContainer());
}

public int calculateComparatorOutput() {
// If brewing has finished
var mugStack = getStack(INPUT_SLOT);
if (!mugStack.isEmpty() && !mugStack.isOf(AdornItems.INSTANCE.getMUG())) {
return 15;
}

var progressFraction = (float) progress / MAX_PROGRESS;
var level = progressFraction * 14;
return MathHelper.ceil(level);
}

protected abstract boolean canExtractFluidContainer();
protected abstract void tryExtractFluidContainer();

private boolean isActive() {
return progress != 0;
}

private static void decrementIngredient(BrewerBlockEntity brewer, int slot) {
var stack = brewer.getStack(slot);
// TODO: Use stack-aware version on Fabric (and Neo if available)
var remainder = stack.getItem().getRecipeRemainder();
stack.decrement(1);

if (remainder != null) {
if (stack.isEmpty()) {
brewer.setStack(slot, new ItemStack(remainder));
} else {
ItemScatterer.spawn(brewer.world, brewer.pos.getX() + 0.5, brewer.pos.getY() + 0.5, brewer.pos.getZ() + 0.5, new ItemStack(remainder));
}
}
}

public static void tick(World world, BlockPos pos, BlockState state, BrewerBlockEntity brewer) {
var originallyActive = brewer.isActive();
brewer.tryExtractFluidContainer();

var dirty = false;
var hasMug = !brewer.getStack(INPUT_SLOT).isEmpty();

if (hasMug != state.get(BrewerBlock.Companion.getHAS_MUG())) {
world.setBlockState(pos, state.with(BrewerBlock.Companion.getHAS_MUG(), hasMug));
}

var recipe = world.getRecipeManager().getFirstMatch(AdornRecipes.BREWING_TYPE.get(), brewer, world).map(RecipeEntry::value).orElse(null);

if (recipe != null && brewer.getStack(INPUT_SLOT).isOf(AdornItems.INSTANCE.getMUG())) {
if (brewer.progress++ >= MAX_PROGRESS) {
decrementIngredient(brewer, LEFT_INGREDIENT_SLOT);
decrementIngredient(brewer, RIGHT_INGREDIENT_SLOT);
brewer.setStack(INPUT_SLOT, recipe.craft(brewer, world.getRegistryManager()));

if (recipe instanceof FluidBrewingRecipe fluidRecipe) {
brewer.getFluidReference().decrement(fluidRecipe.fluid().amount(), fluidRecipe.fluid().unit());
}
}

dirty = true;
} else {
if (brewer.progress != 0) {
brewer.progress = 0;
dirty = true;
}
}

var activeNow = brewer.isActive();
if (originallyActive != activeNow) {
dirty = true;
var newState = state.with(BrewerBlock.Companion.getACTIVE(), activeNow);
world.setBlockState(pos, newState);
}

if (dirty) {
markDirty(world, pos, state);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package juuxel.adorn.block.entity;

import juuxel.adorn.block.AdornBlockEntities;
import juuxel.adorn.menu.DrawerMenu;
import juuxel.adorn.util.ExtensionsKt;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.menu.Menu;
import net.minecraft.util.math.BlockPos;

public final class DrawerBlockEntity extends SimpleContainerBlockEntity {
public DrawerBlockEntity(BlockPos pos, BlockState state) {
super(AdornBlockEntities.DRAWER.get(), pos, state, 15);
}

@Override
protected Menu createMenu(int syncId, PlayerInventory inv) {
return new DrawerMenu(syncId, inv, this, ExtensionsKt.menuContextOf(this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package juuxel.adorn.block.entity;

import juuxel.adorn.block.AdornBlockEntities;
import juuxel.adorn.menu.KitchenCupboardMenu;
import juuxel.adorn.util.ExtensionsKt;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.menu.Menu;
import net.minecraft.util.math.BlockPos;

public final class KitchenCupboardBlockEntity extends SimpleContainerBlockEntity {
public KitchenCupboardBlockEntity(BlockPos pos, BlockState state) {
super(AdornBlockEntities.KITCHEN_CUPBOARD.get(), pos, state, 15);
}

@Override
protected Menu createMenu(int syncId, PlayerInventory inv) {
return new KitchenCupboardMenu(syncId, inv, this, ExtensionsKt.menuContextOf(this));
}
}
Loading

0 comments on commit 4fd7fa7

Please sign in to comment.