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

import dev.architectury.registry.menu.ExtendedMenuProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.state.property.Property;
import net.minecraft.text.Text;
import net.minecraft.util.Pair;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import rearth.oritech.Oritech;
import rearth.oritech.api.energy.EnergyApi;
import rearth.oritech.api.energy.containers.DelegatingEnergyStorage;
import rearth.oritech.api.energy.containers.DynamicEnergyStorage;
import rearth.oritech.api.energy.containers.DynamicStatisticEnergyStorage;
import rearth.oritech.api.item.ItemApi;
import rearth.oritech.api.item.containers.SimpleInventoryStorage;
import rearth.oritech.block.blocks.storage.SmallStorageBlock;
import rearth.oritech.client.init.ModScreens;
import rearth.oritech.client.ui.BasicMachineScreenHandler;
import rearth.oritech.client.ui.UpgradableMachineScreenHandler;
import rearth.oritech.init.ItemContent;
import rearth.oritech.network.NetworkContent;
import rearth.oritech.util.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public abstract class ExpandableEnergyStorageBlockEntity extends BlockEntity implements EnergyApi.BlockProvider, ItemApi.BlockProvider, MachineAddonController,
                                                                                          ScreenProvider, ExtendedMenuProvider, BlockEntityTicker<ExpandableEnergyStorageBlockEntity> {
    
    private final List<BlockPos> connectedAddons = new ArrayList<>();
    private final List<BlockPos> openSlots = new ArrayList<>();
    private BaseAddonData addonData = MachineAddonController.DEFAULT_ADDON_DATA;
    
    private boolean networkDirty = false;
    private boolean redstonePowered;
    
    // client only
    public DynamicStatisticEnergyStorage.EnergyStatistics currentStats;
    
    public final SimpleInventoryStorage inventory = new SimpleInventoryStorage(1, this::markDirty);
    
    //own storage
    public final DynamicStatisticEnergyStorage energyStorage = new DynamicStatisticEnergyStorage(getDefaultCapacity(), getDefaultInsertRate(), getDefaultExtractionRate(), this::markDirty);
    
    private final EnergyApi.EnergyStorage outputStorage = new DelegatingEnergyStorage(energyStorage, null) {
        @Override
        public boolean supportsInsertion() {
            return false;
        }
    };
    
    private final EnergyApi.EnergyStorage inputStorage = new DelegatingEnergyStorage(energyStorage, null) {
        @Override
        public boolean supportsExtraction() {
            return false;
        }
    };
    
    public ExpandableEnergyStorageBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
        super(type, pos, state);
    }
    
    @Override
    public void tick(World world, BlockPos pos, BlockState state, ExpandableEnergyStorageBlockEntity blockEntity) {
        if (world.isClient) return;
        
        energyStorage.tick((int) world.getTime());
        
        if (!redstonePowered)
            outputEnergy();
        
        inputFromCrystal();
        
        if (networkDirty) sendNetworkEntry();
    }
    
    private void inputFromCrystal() {
        if (energyStorage.amount >= energyStorage.capacity || inventory.isEmpty()) return;
        
        if (!inventory.getStack(0).getItem().equals(ItemContent.OVERCHARGED_CRYSTAL)) return;
        
        energyStorage.amount = Math.min(energyStorage.capacity, energyStorage.amount + Oritech.CONFIG.overchargedCrystalChargeRate());
    }
    
    private void outputEnergy() {
        if (energyStorage.amount <= 0) return;
        
        chargeItems();
        
        // todo caching for targets? Used to be BlockApiCache.create()
        var target = getOutputPosition(pos, getFacing());
        var candidate = EnergyApi.BLOCK.find(world, target.getRight(), target.getLeft());
        if (candidate != null) {
            EnergyApi.transfer(energyStorage, candidate, Long.MAX_VALUE, false);
        }
    }
    
    private void chargeItems() {
        
        var heldStack = inventory.heldStacks.get(0);
        if (heldStack.isEmpty() || heldStack.getCount() > 1) return;
        
        var stackRef = new StackContext(heldStack, updated -> inventory.heldStacks.set(0, updated));
        var slotEnergyContainer = EnergyApi.ITEM.find(stackRef);
        if (slotEnergyContainer != null) {
            EnergyApi.transfer(energyStorage, slotEnergyContainer, Long.MAX_VALUE, false);
        }
    }
    
    public static Pair<Direction, BlockPos> getOutputPosition(BlockPos pos, Direction facing) {
        var blockInFront = (BlockPos) Geometry.offsetToWorldPosition(facing, new Vec3i(-1, 0, 0), pos);
        var worldOffset = blockInFront.subtract(pos);
        var direction = Direction.fromVector(worldOffset.getX(), worldOffset.getY(), worldOffset.getZ());
        
        return new Pair<>(direction, blockInFront);
    }
    
    @Override
    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        writeAddonToNbt(nbt);
        nbt.putLong("energy_stored", energyStorage.amount);
        Inventories.writeNbt(nbt, inventory.heldStacks, false, registryLookup);
        nbt.putBoolean("redstone", redstonePowered);
    }
    
    @Override
    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        loadAddonNbtData(nbt);
        updateEnergyContainer();
        energyStorage.amount = nbt.getLong("energy_stored");
        Inventories.readNbt(nbt, inventory.heldStacks, registryLookup);
        redstonePowered = nbt.getBoolean("redstone");
    }
    
    @Override
    public ItemApi.InventoryStorage getInventoryStorage(Direction direction) {
        return inventory;
    }
    
    public Direction getFacing() {
        return getCachedState().get(SmallStorageBlock.TARGET_DIR);
    }
    
    
    @Override
    public EnergyApi.EnergyStorage getEnergyStorage(Direction direction) {
        
        if (direction == null)
            return energyStorage;
        
        if (direction.equals(getFacing())) {
            return outputStorage;
        } else {
            return inputStorage;
        }
    }
    
    @Override
    public List<BlockPos> getConnectedAddons() {
        return connectedAddons;
    }
    
    @Override
    public List<BlockPos> getOpenAddonSlots() {
        return openSlots;
    }
    
    @Override
    public Direction getFacingForAddon() {
        var facing = Objects.requireNonNull(world).getBlockState(getPos()).get(SmallStorageBlock.TARGET_DIR);
        
        if (facing.equals(Direction.UP) || facing.equals(Direction.DOWN))
            return Direction.NORTH;
        
        return facing;
    }
    
    @Override
    public DynamicEnergyStorage getStorageForAddon() {
        return energyStorage;
    }
    
    @Override
    public ItemApi.InventoryStorage getInventoryForAddon() {
        return inventory;
    }
    
    @Override
    public ScreenProvider getScreenProvider() {
        return this;
    }
    
    @Override
    public BaseAddonData getBaseAddonData() {
        return addonData;
    }
    
    @Override
    public void setBaseAddonData(BaseAddonData data) {
        this.addonData = data;
    }
    
    @Override
    public void updateEnergyContainer() {
        
        MachineAddonController.super.updateEnergyContainer();
        
        energyStorage.maxExtract = getDefaultExtractionRate() + addonData.energyBonusTransfer();
        
    }
    
    @Override
    public float getDisplayedEnergyTransfer() {
        return energyStorage.maxInsert;
    }
    
    public abstract long getDefaultExtractionRate();
    
    @Override
    public void saveExtraData(PacketByteBuf buf) {
        sendNetworkEntry();
        var data = new ModScreens.UpgradableData(pos, getUiData(), getCoreQuality());
        ModScreens.UpgradableData.PACKET_CODEC.encode(buf, data);
    }
    
    @Override
    public void markDirty() {
        super.markDirty();
        networkDirty = true;
    }
    
    private boolean isActivelyViewed() {
        var closestPlayer = Objects.requireNonNull(world).getClosestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5, false);
        return closestPlayer != null && closestPlayer.currentScreenHandler instanceof BasicMachineScreenHandler handler && getPos().equals(handler.getBlockPos());
    }
    
    protected void sendNetworkEntry() {
        
        if (world.getTime() % 15 != 0 && !isActivelyViewed()) return;
        
        var statistics = energyStorage.getCurrentStatistics(world.getTime());
        
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.GenericEnergySyncPacket(pos, energyStorage.amount, energyStorage.capacity));
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.EnergyStatisticsPacket(pos, statistics));
        networkDirty = false;
    }
    
    @Override
    public Text getDisplayName() {
        return Text.literal("");
    }
    
    @Nullable
    @Override
    public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.FullEnergySyncPacket(pos, energyStorage.amount, energyStorage.capacity, energyStorage.maxInsert, energyStorage.maxExtract));
        
        return new UpgradableMachineScreenHandler(syncId, playerInventory, this, getUiData(), getCoreQuality());
    }
    
    @Override
    public List<GuiSlot> getGuiSlots() {
        return List.of(new GuiSlot(0, 30, 42));
    }
    
    @Override
    public float getDisplayedEnergyUsage() {
        return 0;
    }
    
    @Override
    public float getProgress() {
        return 0;
    }
    
    
    @Override
    public BlockPos getPosForAddon() {
        return getPos();
    }
    
    @Override
    public World getWorldForAddon() {
        return getWorld();
    }
    
    @Override
    public InventoryInputMode getInventoryInputMode() {
        return InventoryInputMode.FILL_LEFT_TO_RIGHT;
    }
    
    @Override
    public boolean inputOptionsEnabled() {
        return false;
    }
    
    @Override
    public Inventory getDisplayedInventory() {
        return inventory;
    }
    
    @Override
    public ScreenHandlerType<?> getScreenHandlerType() {
        return ModScreens.STORAGE_SCREEN;
    }
    
    @Override
    public boolean showProgress() {
        return false;
    }
    
    @Override
    public Property<Direction> getBlockFacingProperty() {
        return SmallStorageBlock.TARGET_DIR;
    }
    
    public void setRedstonePowered(boolean isPowered) {
        this.redstonePowered = isPowered;
    }
    
    @Override
    public boolean hasRedstoneControlAvailable() {
        return true;
    }
    
    @Override
    public int receivedRedstoneSignal() {
        if (redstonePowered) return 15;
        return world.getReceivedRedstonePower(pos);
    }
    
    @Override
    public String currentRedstoneEffect() {
        if (receivedRedstoneSignal() > 0) return "tooltip.oritech.redstone_disabled_storage";
        return "tooltip.oritech.redstone_enabled_direct";
    }
}