RyanHub – file viewer
filename: common/src/main/java/rearth/oritech/block/entity/pipes/ItemFilterBlockEntity.java
branch: 1.21
back to repo
package rearth.oritech.block.entity.pipes;

import dev.architectury.registry.menu.ExtendedMenuProvider;
import io.wispforest.endec.Endec;
import io.wispforest.owo.serialization.endec.MinecraftEndecs;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtList;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import rearth.oritech.api.item.ItemApi;
import rearth.oritech.api.item.containers.SimpleInventoryStorage;
import rearth.oritech.block.blocks.pipes.item.ItemFilterBlock;
import rearth.oritech.client.ui.ItemFilterScreenHandler;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.network.NetworkContent;

import java.util.HashMap;
import java.util.Map;

public class ItemFilterBlockEntity extends BlockEntity implements ItemApi.BlockProvider, ExtendedMenuProvider, BlockEntityTicker<ItemFilterBlockEntity> {
    
    public final FilterBlockInventory inventory = new FilterBlockInventory(1, this::markDirty);
    
    protected FilterData filterSettings = new FilterData(false, true, false, new HashMap<>());
    
    @Override
    protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        Inventories.writeNbt(nbt, inventory.heldStacks, false, registryLookup);
        nbt.putBoolean("whitelist", filterSettings.useWhitelist);
        nbt.putBoolean("useNbt", filterSettings.useNbt);
        nbt.putBoolean("useComponents", filterSettings.useComponents);
        
        var filterItems = filterSettings.items.values();
        var itemsNbtList = new NbtList();
        
        for (var item : filterItems) {
            var data = item.encode(registryLookup);
            itemsNbtList.add(data);
        }
        
        nbt.put("filterItems", itemsNbtList);
    }
    
    @Override
    protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        Inventories.readNbt(nbt, inventory.heldStacks, registryLookup);
        
        var whiteList = nbt.getBoolean("whitelist");
        var useNbt = nbt.getBoolean("useNbt");
        var useComponents = nbt.getBoolean("useComponents");
        
        var list = nbt.getList("filterItems", NbtElement.COMPOUND_TYPE);
        var itemsList = new HashMap<Integer, ItemStack>();
        for (int i = 0; i < list.size(); i++) {
            var data = list.get(i);
            var stack = ItemStack.fromNbt(registryLookup, data).orElse(ItemStack.EMPTY);
            
            itemsList.put(i, stack);
        }
        
        var data = new FilterData(useNbt, whiteList, useComponents, itemsList);
        this.setFilterSettings(data);
        
    }
    
    public ItemFilterBlockEntity(BlockPos pos, BlockState state) {
        super(BlockEntitiesContent.ITEM_FILTER_ENTITY, pos, state);
    }
    
    @Override
    public ItemApi.InventoryStorage getInventoryStorage(Direction direction) {
        return inventory;
    }
    
    @Override
    public void saveExtraData(PacketByteBuf buf) {
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.ItemFilterSyncPacket(pos, filterSettings));
        buf.writeBlockPos(pos);
    }
    
    @Override
    public Text getDisplayName() {
        return Text.of("");
    }
    
    @Nullable
    @Override
    public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
        return new ItemFilterScreenHandler(syncId, playerInventory, this);
    }
    
    @Override
    public void tick(World world, BlockPos pos, BlockState state, ItemFilterBlockEntity blockEntity) {
        
        // if non-empty and inventory in target, move it
        if (world.isClient || inventory.isEmpty()) return;
        
        var targetDirection = getCachedState().get(ItemFilterBlock.TARGET_DIR);
        var targetPos = pos.add(targetDirection.getVector());
        
        // todo caching
        var targetInv = ItemApi.BLOCK.find(world, targetPos, targetDirection);
        if (targetInv == null) return;
        
        var firstItem = inventory.heldStacks.getFirst();
        var inserted = targetInv.insert(firstItem, false);
        firstItem.decrement(inserted);
        
    }
    
    public FilterData getFilterSettings() {
        return filterSettings;
    }
    
    public void setFilterSettings(FilterData filterSettings) {
        this.filterSettings = filterSettings;
        this.markDirty();
    }
    
    @Override
    public void markDirty() {
        if (this.world != null)
            world.markDirty(pos);
    }
    
    // items is a map of position index (in the filter GUI) to filtered item stack
    public record FilterData(boolean useNbt, boolean useWhitelist, boolean useComponents,
                             Map<Integer, ItemStack> items) {
    }
    
    public class FilterBlockInventory extends SimpleInventoryStorage {
        
        public FilterBlockInventory(int size, Runnable onUpdate) {
            super(size, onUpdate);
        }
        
        @Override
        public boolean supportsExtraction() {
            return false;
        }
        
        public boolean canInsert(ItemStack stack) {
            
            // check filter settings
            var checkNbt = filterSettings.useNbt;
            var checkComponents = filterSettings.useComponents;
            var matchesFilterItems = false; // true if at least 1 item matches
            
            for (var filterItem : filterSettings.items.values()) {
                var matchesType = stack.getItem().equals(filterItem.getItem());
                if (!matchesType) continue;
                
                if (checkComponents) {
                    var componentsMatch = stack.getComponentChanges().equals(filterItem.getComponentChanges());
                    if (!componentsMatch) {
                        break;
                    }
                }
                
                if (checkNbt) {
                    // check if both have nbt, if so compare them
                    // if not both check if neither has nbt, and type matches
                    if (stack.contains(DataComponentTypes.CUSTOM_DATA) && filterItem.contains(DataComponentTypes.CUSTOM_DATA)) {
                        var match = stack.get(DataComponentTypes.CUSTOM_DATA).equals(filterItem.get(DataComponentTypes.CUSTOM_DATA));
                        if (match) {
                            matchesFilterItems = true;
                            break;
                        }
                    } else if (!stack.contains(DataComponentTypes.CUSTOM_DATA) && !filterItem.contains(DataComponentTypes.CUSTOM_DATA)) {
                        matchesFilterItems = true;
                        break;
                    }
                } else {
                    matchesFilterItems = true;
                    break;
                }
                
            }
            
            // matchesFilterItems is true when at least 1 item matches
            if (filterSettings.useWhitelist) {
                return matchesFilterItems;
            } else {
                // blacklist list, if we have a match we return false
                return !matchesFilterItems;
            }
        }
        
        @Override
        public int insertToSlot(ItemStack addedStack, int slot, boolean simulate) {
            
            if (!canInsert(addedStack))
                return 0;
            
            return super.insertToSlot(addedStack, slot, simulate);
        }
    }
    
    public static Endec<Map<Integer, ItemStack>> FILTER_ITEMS_ENDEC = Endec.map(Object::toString, Integer::valueOf, MinecraftEndecs.ITEM_STACK);
}