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

import dev.architectury.registry.menu.ExtendedMenuProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
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.nbt.NbtElement;
import net.minecraft.nbt.NbtList;
import net.minecraft.nbt.NbtString;
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.state.property.Properties;
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.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.SimpleEnergyStorage;
import rearth.oritech.api.item.ItemApi;
import rearth.oritech.api.item.containers.SimpleInventoryStorage;
import rearth.oritech.block.base.block.MultiblockMachine;
import rearth.oritech.block.base.entity.MachineBlockEntity;
import rearth.oritech.block.blocks.augmenter.AugmentResearchStationBlock;
import rearth.oritech.client.init.ModScreens;
import rearth.oritech.client.ui.BasicMachineScreenHandler;
import rearth.oritech.client.ui.PlayerModifierScreenHandler;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.init.recipes.AugmentRecipe;
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.util.GeckoLibUtil;

import java.util.*;

public class AugmentApplicationEntity extends BlockEntity implements BlockEntityTicker<AugmentApplicationEntity>, MultiblockMachineController, GeoBlockEntity,
                                                                       ExtendedMenuProvider, ItemApi.BlockProvider, EnergyApi.BlockProvider, ScreenProvider {
    
    public final Set<Identifier> researchedAugments = new HashSet<>();
    
    // config
    public static long maxEnergyTransfer = Oritech.CONFIG.augmenterMaxEnergy() / 10;
    public static long maxEnergyStored = Oritech.CONFIG.augmenterMaxEnergy();
    
    // multiblock
    private final ArrayList<BlockPos> coreBlocksConnected = new ArrayList<>();
    private float coreQuality = 1f;
    
    // animation
    protected final AnimatableInstanceCache animatableInstanceCache = GeckoLibUtil.createInstanceCache(this);
    
    // working state
    private boolean networkDirty = true;
    public final HashMap<Integer, ResearchState> availableStations = new HashMap<>();
    public boolean screenInvOverride = false;
    
    public final SimpleInventoryStorage inventory = new SimpleInventoryStorage(5, this::markDirty);
    
    private final EnergyApi.EnergyStorage energyStorage = new SimpleEnergyStorage(maxEnergyTransfer, maxEnergyStored, maxEnergyStored, this::markDirty);
    private AnimationController<AugmentApplicationEntity> animationController;
    
    
    public AugmentApplicationEntity(BlockPos pos, BlockState state) {
        super(BlockEntitiesContent.PLAYER_MODIFIER_BLOCK_ENTITY, pos, state);
    }
    
    @Override
    public void tick(World world, BlockPos pos, BlockState state, AugmentApplicationEntity blockEntity) {
        
        if (world.isClient) return;
        
        screenInvOverride = false;
        
        // update research stations
        for (int i = 0; i < 3; i++) {
            var station = availableStations.getOrDefault(i, null);
            if (station == null) continue;
            if (station.working) {
                var isDone = world.getTime() > station.researchStartedAt + station.workTime;
                if (!isDone) continue;
                
                researchedAugments.add(station.selectedResearch);
                station.working = false;
                this.markDirty();
            }
        }
        
        if (networkDirty) {
            updateNetwork();
        }
    }
    
    // persist researched augments, inventory, energy
    @Override
    protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        Inventories.writeNbt(nbt, inventory.heldStacks, false, registryLookup);
        nbt.putLong("rf", energyStorage.getAmount());
        addMultiblockToNbt(nbt);
        
        
        var list = new NbtList();
        for (var augment : researchedAugments) {
            list.add(NbtString.of(augment.getPath()));
        }
        
        // also put in pending researches to avoid having to separately store them
        for (var station : availableStations.values()) {
            if (station == null) continue;
            if (station.working) {
                list.add(NbtString.of(station.selectedResearch.getPath()));
            }
        }
        
        nbt.put("researched", list);
        
    }
    
    @Override
    protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        Inventories.readNbt(nbt, inventory.heldStacks, registryLookup);
        energyStorage.setAmount(nbt.getLong("rf"));
        loadMultiblockNbtData(nbt);
        
        var parsedList = nbt.getList("researched", NbtElement.STRING_TYPE);
        for (var element : parsedList) {
            var id = Oritech.id(element.asString());
            researchedAugments.add(id);
        }
        
    }
    
    public void researchAugment(Identifier augment, boolean creative, PlayerEntity player) {
        
        if (!PlayerAugments.allAugments.containsKey(augment)) {
            Oritech.LOGGER.error("Player augment with id" + augment + " not found. This should never happen");
            return;
        }
        
        if (researchedAugments.contains(augment)) {
            Oritech.LOGGER.warn("Player tried to research already researched augment " + augment);
            return;
        }
        
        var recipe = (AugmentRecipe) world.getRecipeManager().get(augment).get().value();
        
        var extracted = energyStorage.extract(recipe.getRfCost(), false);
        
        // remove available resources
        for (var wantedInput : recipe.getResearchCost()) {
            var type = wantedInput.ingredient();
            var missingCount = wantedInput.count();
            
            for (var stack : this.inventory.heldStacks) {
                if (type.test(stack)) {
                    var takeAmount = Math.min(stack.getCount(), missingCount);
                    missingCount -= takeAmount;
                    stack.decrement(takeAmount);
                    
                    if (missingCount <= 0) break;
                }
            }
            for (var stack : player.getInventory().main) {
                if (type.test(stack)) {
                    var takeAmount = Math.min(stack.getCount(), missingCount);
                    missingCount -= takeAmount;
                    stack.decrement(takeAmount);
                    
                    if (missingCount <= 0) break;
                }
            }
        }
        
        // assign first idle station
        for (int i = 0; i < 3; i++) {
            var station = availableStations.getOrDefault(i, null);
            if (station == null) continue;
            if (station.working) continue;
            
            
            if (!Registries.BLOCK.getId(station.type).equals(recipe.getRequiredStation())) continue;
            
            station.selectedResearch = augment;
            station.working = true;
            station.researchStartedAt = world.getTime();
            station.workTime = creative ? 5 : recipe.getTime();
            
            break;
            
        }
        this.markDirty();
    }
    
    public void installAugmentToPlayer(Identifier augment, PlayerEntity player) {
        
        if (!PlayerAugments.allAugments.containsKey(augment)) {
            Oritech.LOGGER.error("Player augment with id" + augment + " not found. This should never happen");
            return;
        }
        
        if (!researchedAugments.contains(augment)) {
            Oritech.LOGGER.warn("Player tried to install augment with id" + augment + " without researching it.");
            return;
        }
        
        var recipe = (AugmentRecipe) world.getRecipeManager().get(augment).get().value();
        
        // remove available resources
        for (var wantedInput : recipe.getApplyCost()) {
            var type = wantedInput.ingredient();
            var missingCount = wantedInput.count();
            
            for (var stack : this.inventory.heldStacks) {
                if (type.test(stack)) {
                    var takeAmount = Math.min(stack.getCount(), missingCount);
                    missingCount -= takeAmount;
                    stack.decrement(takeAmount);
                    
                    if (missingCount <= 0) break;
                }
            }
            
            for (var stack : player.getInventory().main) {
                if (type.test(stack)) {
                    var takeAmount = Math.min(stack.getCount(), missingCount);
                    missingCount -= takeAmount;
                    stack.decrement(takeAmount);
                    
                    if (missingCount <= 0) break;
                }
            }
        }
        
        var augmentInstance = PlayerAugments.allAugments.get(augment);
        augmentInstance.installToPlayer(player);
        this.markNetDirty();
    }
    
    public void removeAugmentFromPlayer(Identifier augment, PlayerEntity player) {
        
        if (!PlayerAugments.allAugments.containsKey(augment)) {
            Oritech.LOGGER.error("Player augment with id" + augment + " not found. This should never happen");
            return;
        }
        
        var augmentInstance = PlayerAugments.allAugments.get(augment);
        augmentInstance.removeFromPlayer(player);
        this.markNetDirty();
    }
    
    public static void toggleAugmentForPlayer(Identifier augment, PlayerEntity player) {
        
        if (!PlayerAugments.allAugments.containsKey(augment)) {
            Oritech.LOGGER.error("Player augment with id" + augment + " not found. This should never happen");
            return;
        }
        
        var augmentInstance = PlayerAugments.allAugments.get(augment);
        
        if (!augmentInstance.isInstalled(player)) {
            Oritech.LOGGER.error("Tried toggling not-installed augment id: " + augment + ". This should never happen");
            return;
        }
        
        augmentInstance.toggle(player);
    }
    
    public boolean hasPlayerAugment(Identifier augment, PlayerEntity player) {
        
        if (!PlayerAugments.allAugments.containsKey(augment)) {
            Oritech.LOGGER.error("Player augment with id" + augment + " not found. This should never happen");
            return false;
        }
        
        var augmentInstance = PlayerAugments.allAugments.get(augment);
        return augmentInstance.isInstalled(player);
        
    }
    
    public void loadResearchesFromPlayer(PlayerEntity player) {
        
        for (var augmentId : PlayerAugments.allAugments.keySet()) {
            var augment = PlayerAugments.allAugments.get(augmentId);
            var isInstalled = augment.isInstalled(player);
            var isResearched = researchedAugments.contains(augmentId);
            
            if (isInstalled && !isResearched) {
                researchedAugments.add(augmentId);
            }
        }
    }
    
    public void loadAvailableStations(PlayerEntity player) {
        var facing = this.getCachedState().get(Properties.HORIZONTAL_FACING);
        
        var targetPositions = List.of(
          new BlockPos(0, 0, -2),
          new BlockPos(1, 0, 2),
          new BlockPos(2, 0, -1)
        );
        
        for (int i = 0; i < targetPositions.size(); i++) {
            var candidatePosOffset = targetPositions.get(i);
            var candidatePos = new BlockPos(Geometry.offsetToWorldPosition(facing, candidatePosOffset, pos));
            
            var candidateState = world.getBlockState(candidatePos);
            if (!(candidateState.getBlock() instanceof AugmentResearchStationBlock) || !candidateState.get(MultiblockMachine.ASSEMBLED)) {
                availableStations.put(i, null);
                continue;
            }
            
            if (availableStations.containsKey(i) && availableStations.get(i) != null && availableStations.get(i).type.equals(candidateState.getBlock()))
                continue;
            
            var newState = new ResearchState(candidateState.getBlock(), false, Identifier.of(""), -1, -1);
            
            availableStations.put(i, newState);
        }
        
    }
    
    private void markNetDirty() {
        this.networkDirty = true;
    }
    
    private void updateNetwork() {
        this.networkDirty = false;
        
        var stations = new ArrayList<Identifier>();
        var states = new ArrayList<Boolean>();
        var targets = new ArrayList<Identifier>();
        var startTimes = new ArrayList<Long>();
        var durations = new ArrayList<Integer>();
        
        availableStations.values().forEach(station -> {
            if (station == null) return;
            stations.add(Registries.BLOCK.getId(station.type));
            states.add(station.working);
            targets.add(station.selectedResearch);
            startTimes.add(station.researchStartedAt);
            durations.add(station.workTime);
        });
        
        // collect researched augments, send them to client
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.AugmentDataPacket(pos, researchedAugments.stream().toList(), stations, states, targets, startTimes, durations));
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.GenericEnergySyncPacket(pos, energyStorage.getAmount(), energyStorage.getCapacity()));
    }
    
    public void handleAugmentUpdatePacket(NetworkContent.AugmentDataPacket packet) {
        this.researchedAugments.clear();
        this.researchedAugments.addAll(packet.allResearched());
        
        this.availableStations.clear();
        
        for (int i = 0; i < packet.researchBlocks().size(); i++) {
            var station = Registries.BLOCK.get(packet.researchBlocks().get(i));
            var state = packet.researchStates().get(i);
            var target = packet.activeResearches().get(i);
            var researchTime = packet.researchTimes().get(i);
            var startedTime = packet.startedTimes().get(i);
            
            var res = new ResearchState(station, state, target, researchTime, startedTime);
            availableStations.put(i, res);
        }
        
    }
    
    @Override
    public List<Vec3i> getCorePositions() {
        return List.of(
          new Vec3i(0, 0, 1),
          new Vec3i(0, 0, -1),
          new Vec3i(-1, 0, 0),
          new Vec3i(-1, 0, 1),
          new Vec3i(-1, 0, -1),
          new Vec3i(0, 1, 1),
          new Vec3i(0, 1, -1),
          new Vec3i(-1, 1, 0),
          new Vec3i(-1, 1, 1),
          new Vec3i(-1, 1, -1)
        );
    }
    
    @Override
    public Direction getFacingForMultiblock() {
        var state = getCachedState();
        return state.get(Properties.HORIZONTAL_FACING).getOpposite();
    }
    
    @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) {
        this.coreQuality = quality;
    }
    
    @Override
    public float getCoreQuality() {
        return coreQuality;
    }
    
    @Override
    public ItemApi.InventoryStorage getInventoryForMultiblock() {
        return inventory;
    }
    
    @Override
    public EnergyApi.EnergyStorage getEnergyStorageForMultiblock(Direction direction) {
        return energyStorage;
    }
    
    @Override
    public void markDirty() {
        super.markDirty();
        this.markNetDirty();
    }
    
    @Override
    public void playSetupAnimation() {
        animationController.setAnimation(MachineBlockEntity.SETUP);
        animationController.forceAnimationReset();
    }
    
    @Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
        animationController = getController();
        controllers.add(animationController.setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
    }
    
    private AnimationController<AugmentApplicationEntity> getController() {
        return new AnimationController<>(this, "machine", 0, state -> {
            
            if (state.isCurrentAnimation(MachineBlockEntity.SETUP)) {
                if (state.getController().hasAnimationFinished()) {
                    return state.setAndContinue(MachineBlockEntity.IDLE);
                } else {
                    return state.setAndContinue(MachineBlockEntity.SETUP);
                }
            }
            
            if (this.getCachedState().get(MultiblockMachine.ASSEMBLED)) {
                return state.setAndContinue(MachineBlockEntity.IDLE);
            } else {
                return state.setAndContinue(MachineBlockEntity.PACKAGED);
            }
        });
    }
    
    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return animatableInstanceCache;
    }
    
    @Override
    public void saveExtraData(PacketByteBuf buf) {
        buf.writeBlockPos(pos);
    }
    
    @Override
    public Text getDisplayName() {
        return Text.empty();
    }
    
    @Nullable
    @Override
    public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
        updateNetwork();
        
        var dist = player.squaredDistanceTo(this.pos.toBottomCenterPos());
        if (dist > 1 || screenInvOverride)
            return new BasicMachineScreenHandler(syncId, playerInventory, this);
        
        return new PlayerModifierScreenHandler(syncId, playerInventory, this);
    }
    
    @Override
    public EnergyApi.EnergyStorage getEnergyStorage(Direction direction) {
        return energyStorage;
    }
    
    @Override
    public ItemApi.InventoryStorage getInventoryStorage(Direction direction) {
        return inventory;
    }
    
    @Override
    public List<GuiSlot> getGuiSlots() {
        return List.of(
          new GuiSlot(0, 30, 30),
          new GuiSlot(1, 50, 30),
          new GuiSlot(2, 70, 30),
          new GuiSlot(3, 90, 30),
          new GuiSlot(4, 110, 30)
        );
    }
    
    @Override
    public float getDisplayedEnergyUsage() {
        return 0;
    }
    
    @Override
    public float getProgress() {
        return 0;
    }
    
    @Override
    public boolean showProgress() {
        return false;
    }
    
    @Override
    public boolean showExpansionPanel() {
        return false;
    }
    
    @Override
    public InventoryInputMode getInventoryInputMode() {
        return InventoryInputMode.FILL_LEFT_TO_RIGHT;
    }
    
    @Override
    public Inventory getDisplayedInventory() {
        return inventory;
    }
    
    @Override
    public ScreenHandlerType<?> getScreenHandlerType() {
        return ModScreens.AUGMENTER_INV_SCREEN;
    }
    
    public static class ResearchState {
        
        public Block type;
        public boolean working;
        public Identifier selectedResearch;
        public int workTime;
        public long researchStartedAt;
        
        public ResearchState(Block type, boolean working, Identifier selectedResearch, int workTime, long researchStartedAt) {
            this.type = type;
            this.working = working;
            this.selectedResearch = selectedResearch;
            this.workTime = workTime;
            this.researchStartedAt = researchStartedAt;
        }
        
        @Override
        public String toString() {
            return "ResearchState{" +
                     "type=" + type +
                     ", working=" + working +
                     ", selectedResearch=" + selectedResearch +
                     ", workTime=" + workTime +
                     ", researchStartedAt=" + researchStartedAt +
                     '}';
        }
    }
    
}