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

import dev.architectury.registry.menu.ExtendedMenuProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
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.DynamicStatisticEnergyStorage;
import rearth.oritech.api.energy.containers.SimpleEnergyStorage;
import rearth.oritech.api.item.ItemApi;
import rearth.oritech.block.blocks.storage.UnstableContainerBlock;
import rearth.oritech.client.init.ModScreens;
import rearth.oritech.client.init.ParticleContent;
import rearth.oritech.client.ui.BasicMachineScreenHandler;
import rearth.oritech.client.ui.UpgradableMachineScreenHandler;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.init.ItemContent;
import rearth.oritech.network.NetworkContent;
import rearth.oritech.util.*;
import software.bernie.geckolib.animatable.GeoBlockEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;

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

public class UnstableContainerBlockEntity extends BlockEntity implements ScreenProvider, ExtendedMenuProvider, BlockEntityTicker<UnstableContainerBlockEntity>,
                                                                           GeoBlockEntity, MultiblockMachineController, EnergyApi.BlockProvider {
    
    public static final RawAnimation SETUP = RawAnimation.begin().thenPlay("setup").thenPlay("idle");
    public static final RawAnimation IDLE = RawAnimation.begin().thenPlay("idle");
    public static final Long BASE_CAPACITY = Oritech.CONFIG.unstableContainerBaseCapacity();
    
    private final ArrayList<BlockPos> coreBlocksConnected = new ArrayList<>();
    
    public BlockState capturedBlock = Blocks.AIR.getDefaultState();
    private boolean networkDirty = false;
    private long age = 0;
    public float qualityMultiplier = 1f;
    private boolean dropped = false;
    
    public final SimpleEnergyStorage laserInputStorage = new SimpleEnergyStorage(100_000_000, 0, 100_000_000);
    
    //own storage
    protected final DynamicStatisticEnergyStorage energyStorage = new DynamicStatisticEnergyStorage(20_000_000L, 20_000_000L, 20_000_000L, this::markDirty);
    
    private final EnergyApi.EnergyStorage outputStorage = new DelegatingEnergyStorage(energyStorage, null) {
        @Override
        public boolean supportsInsertion() {
            return false;
        }
    };
    
    protected final AnimatableInstanceCache animatableInstanceCache = GeckoLibUtil.createInstanceCache(this);
    
    // client only
    public DynamicStatisticEnergyStorage.EnergyStatistics currentStats;
    
    public UnstableContainerBlockEntity(BlockPos pos, BlockState state) {
        super(BlockEntitiesContent.UNSTABLE_CONTAINER_BLOCK_ENTITY, pos, state);
    }
    
    @Override
    public void tick(World world, BlockPos pos, BlockState state, UnstableContainerBlockEntity blockEntity) {
        if (world.isClient) return;
        
        age++;
        if (age > 10 && !state.get(UnstableContainerBlock.SETUP_DONE)) {
            world.setBlockState(pos, state.with(UnstableContainerBlock.SETUP_DONE, true));
        }
        
        energyStorage.tick((int) world.getTime());
        
        adjustEnergyStorageSize();
        
        if (energyStorage.amount > 0)
            outputEnergy();
        
        updateNetwork();
    }
    
    private void adjustEnergyStorageSize() {
        
        var targetMultiplier = 1 + Math.pow((double) laserInputStorage.getAmount() / Oritech.CONFIG.laserArmConfig.energyPerTick(), 2);
        targetMultiplier = Math.min(targetMultiplier, 5_000);
        laserInputStorage.setAmount(0);
        var targetAmount = BASE_CAPACITY * qualityMultiplier * targetMultiplier;
        var currentAmount = energyStorage.getCapacity();
        energyStorage.capacity = (long) MathHelper.lerp(0.005d, currentAmount, targetAmount);
        energyStorage.setMaxInsert((long) targetAmount);
        energyStorage.setMaxExtract((long) targetAmount);
        
        if (energyStorage.capacity < energyStorage.maxInsert * 0.9999) {
            // growing, spawn particles
            ParticleContent.UNSTABLE_CONTAINER_GROWING.spawn(world, pos.toCenterPos(), 2);
        }
        
        if (energyStorage.amount > energyStorage.capacity) {
            energyStorage.amount = energyStorage.capacity;
        }
        
        if (energyStorage.capacity != BASE_CAPACITY * qualityMultiplier)
            energyStorage.update();
        
    }
    
    private void outputEnergy() {
        var positions = List.of(new Vec3i(0, -3, 0), new Vec3i(0, 2, 0));
        for (var outputPos : positions) {
            var worldPos = pos.add(outputPos);
            var candidate = EnergyApi.BLOCK.find(world, worldPos, null);
            if (candidate != null) {
                EnergyApi.transfer(energyStorage, candidate, energyStorage.maxExtract, false);
            }
        }
    }
    
    @Override
    protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        addMultiblockToNbt(nbt);
        var blockId = Registries.BLOCK.getId(capturedBlock.getBlock());
        nbt.putString("captured", blockId.toString());
        nbt.putLong("energy_stored", energyStorage.amount);
        nbt.putLong("energy_capacity", energyStorage.capacity);
        nbt.putFloat("quality", qualityMultiplier);
    }
    
    @Override
    protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        loadMultiblockNbtData(nbt);
        energyStorage.amount = nbt.getLong("energy_stored");
        energyStorage.capacity = nbt.getLong("energy_capacity");
        energyStorage.capacity = nbt.getLong("energy_capacity");
        qualityMultiplier = nbt.getFloat("quality");
        
        var blockId = nbt.getString("captured");
        if (!blockId.isBlank() && Registries.BLOCK.containsId(Identifier.of(blockId)))
            capturedBlock = Registries.BLOCK.get(Identifier.of(blockId)).getDefaultState();
        
    }
    
    private boolean isActivelyViewed() {
        var closestPlayer = Objects.requireNonNull(world).getClosestPlayer(pos.getX(), pos.getY(), pos.getZ(), 15, false);
        return closestPlayer != null && closestPlayer.currentScreenHandler instanceof BasicMachineScreenHandler handler && getPos().equals(handler.getBlockPos());
    }
    
    private void updateNetwork() {
        
        // sync contained block
        if (world.getTime() % 80 == 0 || (networkDirty && world.getTime() % 15 == 0))
            NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.UnstableContainerContentPacket(pos, Registries.BLOCK.getId(capturedBlock.getBlock()), qualityMultiplier));
        
        if (!networkDirty) return;
        if (world.getTime() % 15 != 0 && !isActivelyViewed()) return;
        
        var statistics = energyStorage.getCurrentStatistics(world.getTime());
        
        networkDirty = false;
        
        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));
    }
    
    private void sendFullNetworkEntry() {
        var statistics = energyStorage.getCurrentStatistics(world.getTime());
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.UnstableContainerContentPacket(pos, Registries.BLOCK.getId(capturedBlock.getBlock()), qualityMultiplier));
        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));
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.FullEnergySyncPacket(pos, energyStorage.amount, energyStorage.capacity, energyStorage.maxInsert, energyStorage.maxExtract));
    }
    
    @Override
    public void markDirty() {
        world.markDirty(pos);
        networkDirty = true;
    }
    
    @Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
        controllers.add(new AnimationController<>(this, "machine", 0, state -> {
            if (state.getController().getAnimationState().equals(AnimationController.State.STOPPED)) {
                if (this.getCachedState().get(UnstableContainerBlock.SETUP_DONE)) {
                    return state.setAndContinue(IDLE);
                } else {
                    return state.setAndContinue(SETUP);
                }
            }
            return PlayState.CONTINUE;
        }).setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
    }
    
    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return animatableInstanceCache;
    }
    
    
    @Override
    public List<Vec3i> getCorePositions() {
        return List.of(
          new Vec3i(-1, -2, -1),
          new Vec3i(0, -2, -1),
          new Vec3i(1, -2, -1),
          new Vec3i(-1, -2, 0),
          new Vec3i(0, -2, 0),
          new Vec3i(1, -2, 0),
          new Vec3i(-1, -2, 1),
          new Vec3i(0, -2, 1),
          new Vec3i(1, -2, 1),
          new Vec3i(-1, -1, -1),
          new Vec3i(0, -1, -1),
          new Vec3i(1, -1, -1),
          new Vec3i(-1, -1, 0),
          new Vec3i(0, -1, 0),
          new Vec3i(1, -1, 0),
          new Vec3i(-1, -1, 1),
          new Vec3i(0, -1, 1),
          new Vec3i(1, -1, 1),
          new Vec3i(-1, 0, -1),
          new Vec3i(0, 0, -1),
          new Vec3i(1, 0, -1),
          new Vec3i(-1, 0, 0),
          new Vec3i(1, 0, 0),
          new Vec3i(-1, 0, 1),
          new Vec3i(0, 0, 1),
          new Vec3i(1, 0, 1),
          new Vec3i(0, 1, -1),
          new Vec3i(-1, 1, 0),
          new Vec3i(0, 1, 0),
          new Vec3i(1, 1, 0),
          new Vec3i(0, 1, 1)
        );
    }
    
    @Override
    public Direction getFacingForMultiblock() {
        return Direction.NORTH;
    }
    
    @Override
    public BlockPos getPosForMultiblock() {
        return pos;
    }
    
    @Override
    public World getWorldForMultiblock() {
        return world;
    }
    
    @Override
    public ArrayList<BlockPos> getConnectedCores() {
        return coreBlocksConnected;
    }
    
    @Override
    public void setCoreQuality(float quality) {
    
    }
    
    @Override
    public float getCoreQuality() {
        return 7;
    }
    
    @Override
    public ItemApi.InventoryStorage getInventoryForMultiblock() {
        return null;
    }
    
    @Override
    public EnergyApi.EnergyStorage getEnergyStorageForMultiblock(Direction direction) {
        return getEnergyStorage(direction);
    }
    
    @Override
    public void playSetupAnimation() {
    
    }
    
    @Override
    public void onCoreBroken(BlockPos corePos) {
        onBroken(corePos);
    }
    
    @Override
    public void onControllerBroken() {
        onBroken(pos);
    }
    
    private void onBroken(BlockPos eventSource) {
        if (dropped) return;
        dropped = true;
        
        for (var corePos : coreBlocksConnected) {
            if (corePos.equals(eventSource)) continue;
            world.setBlockState(corePos, Blocks.AIR.getDefaultState());
        }
        
        world.setBlockState(pos, capturedBlock);
        
        var spawnAt = this.pos.toCenterPos().add(0, 1, 0);
        world.spawnEntity(new ItemEntity(world, spawnAt.x, spawnAt.y, spawnAt.z, new ItemStack(ItemContent.UNSTABLE_CONTAINER)));
        
    }
    
    public void setCapturedBlock(BlockState capturedBlock) {
        this.capturedBlock = capturedBlock;
        markDirty();
    }
    
    @Override
    public EnergyApi.EnergyStorage getEnergyStorage(Direction direction) {
        
        if (direction == null) return energyStorage;
        
        if (direction.equals(Direction.DOWN) || direction.equals(Direction.UP))
            return outputStorage;
        
        return energyStorage;
    }
    
    @Override
    public List<GuiSlot> getGuiSlots() {
        return List.of();
    }
    
    @Override
    public float getDisplayedEnergyUsage() {
        return 0;   // todo
    }
    
    @Override
    public float getDisplayedEnergyTransfer() {
        return energyStorage.maxInsert;
    }
    
    @Override
    public float getProgress() {
        return 0;
    }
    
    @Override
    public InventoryInputMode getInventoryInputMode() {
        return InventoryInputMode.FILL_LEFT_TO_RIGHT;
    }
    
    @Override
    public Inventory getDisplayedInventory() {
        return new SimpleInventory();
    }
    
    @Override
    public ScreenHandlerType<?> getScreenHandlerType() {
        return ModScreens.STORAGE_SCREEN;
    }
    
    @Override
    public boolean inputOptionsEnabled() {
        return false;
    }
    
    @Override
    public boolean showProgress() {
        return false;
    }
    
    @Override
    public boolean showExpansionPanel() {
        return false;
    }
    
    @Override
    public void saveExtraData(PacketByteBuf buf) {
        updateNetwork();
        var data = new ModScreens.UpgradableData(pos, new MachineAddonController.AddonUiData(List.of(), List.of(), 1f, 1f, pos, 0), getCoreQuality());
        ModScreens.UpgradableData.PACKET_CODEC.encode(buf, data);
    }
    
    @Override
    public Text getDisplayName() {
        return Text.literal("");
    }
    
    @Override
    public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
        sendFullNetworkEntry();
        return new UpgradableMachineScreenHandler(syncId, playerInventory, this, new MachineAddonController.AddonUiData(List.of(), List.of(), 1f, 1f, pos, 0), getCoreQuality());
    }
}