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

import com.mojang.authlib.GameProfile;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.UnbreakableComponent;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.loot.context.LootContextParameterSet;
import net.minecraft.loot.context.LootContextParameters;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.text.Text;
import net.minecraft.util.Pair;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import org.jetbrains.annotations.Nullable;
import rearth.oritech.Oritech;
import rearth.oritech.block.base.entity.MultiblockFrameInteractionEntity;
import rearth.oritech.client.init.ModScreens;
import rearth.oritech.client.init.ParticleContent;
import rearth.oritech.init.BlockContent;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.network.NetworkContent;
import rearth.oritech.util.FakeMachinePlayer;

import java.util.List;
import java.util.Objects;
import java.util.UUID;

public class DestroyerBlockEntity extends MultiblockFrameInteractionEntity {
    
    public boolean hasCropFilterAddon;
    public int range = 1;
    public int yieldAddons = 0;
    
    // non-persistent
    public BlockPos quarryTarget = BlockPos.ORIGIN;
    public float targetHardness = 1f;
    private ServerPlayerEntity destroyerPlayerEntity = null;
    
    public DestroyerBlockEntity(BlockPos pos, BlockState state) {
        super(BlockEntitiesContent.DESTROYER_BLOCK_ENTITY, pos, state);
    }
    
    @Override
    public void gatherAddonStats(List<AddonBlock> addons) {
        range = 1;
        yieldAddons = 0;
        super.gatherAddonStats(addons);
    }
    
    @Override
    public void getAdditionalStatFromAddon(AddonBlock addonBlock) {
        if (addonBlock.state().getBlock().equals(BlockContent.CROP_FILTER_ADDON))
            hasCropFilterAddon = true;
        
        if (addonBlock.state().getBlock().equals(BlockContent.QUARRY_ADDON))
            range *= 8;
        
        if (addonBlock.state().getBlock().equals(BlockContent.MACHINE_YIELD_ADDON))
            yieldAddons++;
        
        
        super.getAdditionalStatFromAddon(addonBlock);
    }
    
    @Override
    public void resetAddons() {
        super.resetAddons();
        hasCropFilterAddon = false;
        range = 1;
        yieldAddons = 0;
    }
    
    @Override
    protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        nbt.putBoolean("cropAddon", hasCropFilterAddon);
        nbt.putInt("range", range);
        nbt.putInt("yield", yieldAddons);
    }
    
    @Override
    protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        hasCropFilterAddon = nbt.getBoolean("cropAddon");
        range = nbt.getInt("range");
        yieldAddons = nbt.getInt("yield");
    }
    
    @Override
    protected boolean hasWorkAvailable(BlockPos toolPosition) {
        
        if (range > 1) {
            return hasQuarryTarget(toolPosition);
        }
        
        var targetPosition = toolPosition.down();
        var targetState = Objects.requireNonNull(world).getBlockState(targetPosition);
        
        // skip not grown crops
        if (hasCropFilterAddon && isImmatureCrop(targetState)) {
            return false;
        }
        
        return !targetState.getBlock().equals(Blocks.AIR);
    }
    
    private PlayerEntity getDestroyerPlayerEntity() {
        if (destroyerPlayerEntity == null && world instanceof ServerWorld serverWorld) {
            destroyerPlayerEntity = FakeMachinePlayer.create(serverWorld, new GameProfile(UUID.randomUUID(), "oritech_destroyer"), inventory);
        }
        
        return destroyerPlayerEntity;
    }
    
    private boolean hasQuarryTarget(BlockPos toolPosition) {
        return getQuarryDownwardState(toolPosition) != null;
    }

    public static boolean isImmatureCrop(BlockState targetState) {
        Block targetBlock = targetState.getBlock();
        return (targetBlock instanceof CropBlock cropBlock && !cropBlock.isMature(targetState))
            || (targetBlock instanceof NetherWartBlock && targetState.get(NetherWartBlock.AGE) < NetherWartBlock.MAX_AGE)
            || (targetBlock instanceof CocoaBlock && targetState.get(CocoaBlock.AGE) < CocoaBlock.MAX_AGE);
    }
    
    private Pair<BlockPos, BlockState> getQuarryDownwardState(BlockPos toolPosition) {
        for (int i = 1; i <= range; i++) {
            var checkPos = toolPosition.down(i);
            var targetState = world.getBlockState(checkPos);
            if (!targetState.isAir() && !targetState.getFluidState().isStill()) {  // pass through both air and liquid
                quarryTarget = checkPos;
                targetHardness = Math.clamp(targetState.getHardness(world, checkPos), 0, 100);
                syncQuarryNetworkData();
                return new Pair<>(checkPos, targetState);
            }
        }
        
        quarryTarget = BlockPos.ORIGIN;
        return null;
    }
    
    @Override
    public void finishBlockWork(BlockPos processed) {
        
        var targetPosition = processed.down();
        var targetState = Objects.requireNonNull(world).getBlockState(targetPosition);
        
        if (range > 1) {
            if (quarryTarget != BlockPos.ORIGIN) {
                targetPosition = quarryTarget;
                targetState = world.getBlockState(targetPosition);
            } else {
                var data = getQuarryDownwardState(processed);
                if (data == null) return;
                targetPosition = data.getLeft();
                targetState = data.getRight();
            }
        }
        
        // remove fluids
        if (targetState.getFluidState().isStill()) {
            world.setBlockState(targetPosition, Blocks.AIR.getDefaultState());
        }
        
        var targetHardness = targetState.getBlock().getHardness();
        if (targetHardness < 0) return;    // skip undestroyable blocks, such as bedrock
        
        // skip not grown crops
        if (range == 1 && hasCropFilterAddon && isImmatureCrop(targetState)) {
            return;
        }
        
        if (!targetState.getBlock().equals(Blocks.AIR)) {
            
            var targetEntity = world.getBlockEntity(targetPosition);
            List<ItemStack> dropped;
            if (yieldAddons > 0) {
                dropped = getLootDrops(targetState, (ServerWorld) world, targetPosition, targetEntity, yieldAddons);
            } else {
                dropped = Block.getDroppedStacks(targetState, (ServerWorld) world, targetPosition, targetEntity);
            }
            if (dropped.isEmpty()) {
                // If the block doesn't drop any loot, try to break it again with shears
                // Good for seagrass, cobwebs, vines, etc.
                dropped = Block.getDroppedStacks(targetState, (ServerWorld) world, targetPosition, targetEntity, null, new ItemStack(Items.SHEARS));
            }
            
            // only proceed if all stacks fit
            for (var stack : dropped) {
                if (this.inventory.insert(stack, true) != stack.getCount()) return;
            }
            
            for (var stack : dropped) {
                this.inventory.insert(stack, false);
            }
            
            targetState.getBlock().onBreak(world, targetPosition, targetState, getDestroyerPlayerEntity());
            world.playSound(null, targetPosition, targetState.getSoundGroup().getBreakSound(), SoundCategory.BLOCKS, 1f, 1f);
            world.breakBlock(targetPosition, false);
            super.finishBlockWork(processed);
        }
    }

    public static List<ItemStack> getLootDrops(BlockState state, ServerWorld world, BlockPos pos, @Nullable BlockEntity blockEntity, int yieldAddons) {
        return getLootDrops(state, world, pos, blockEntity, yieldAddons, null);
    }

    public static List<ItemStack> getLootDrops(BlockState state, ServerWorld world, BlockPos pos, @Nullable BlockEntity blockEntity, int yieldAddons, @Nullable PlayerEntity entity) {

        var sampleTool = new ItemStack(Items.NETHERITE_PICKAXE);
        sampleTool.set(DataComponentTypes.UNBREAKABLE, new UnbreakableComponent(false));
        var fortuneEntry = world.getRegistryManager().get(RegistryKeys.ENCHANTMENT).getEntry(Enchantments.FORTUNE).get();
        sampleTool.addEnchantment(fortuneEntry, Math.min(yieldAddons, 3));

        var builder = new LootContextParameterSet.Builder(world).add(LootContextParameters.ORIGIN, Vec3d.ofCenter(pos))
                .add(LootContextParameters.TOOL, sampleTool)
                .addOptional(LootContextParameters.BLOCK_ENTITY, blockEntity);
        if (entity != null)
            builder.addOptional(LootContextParameters.THIS_ENTITY, entity);
        return state.getDroppedStacks(builder);
    }

        @Override
    protected void doProgress(boolean moving) {
        super.doProgress(moving);
        
        if (moving)
            return;
        
        if (range > 1 && quarryTarget != BlockPos.ORIGIN) {
            ParticleContent.QUARRY_DESTROY_EFFECT.spawn(world, Vec3d.ofCenter(quarryTarget).add(0, 0.5, 0), 3);
        } else if (hasWorkAvailable(getCurrentTarget())) {
            ParticleContent.BLOCK_DESTROY_EFFECT.spawn(world, Vec3d.of(getCurrentTarget().down()), 4);
        }
    }
    
    @Override
    public List<Pair<Text, Text>> getExtraExtensionLabels() {
        if (range == 1 && yieldAddons == 0) return super.getExtraExtensionLabels();
        return List.of(new Pair<>(Text.translatable("title.oritech.machine.addon_range", range), Text.translatable("tooltip.oritech.block_destroyer.addon_range")), new Pair<>(Text.translatable("title.oritech.machine.addon_fortune", yieldAddons), Text.translatable("tooltip.oritech.machine.addon_fortune")));
    }
    
    @Override
    public BlockState getMachineHead() {
        return BlockContent.BLOCK_DESTROYER_HEAD.getDefaultState();
    }
    
    @Override
    public List<GuiSlot> getGuiSlots() {
        return List.of(
          new GuiSlot(0, 117, 20, true),
          new GuiSlot(1, 117, 38, true),
          new GuiSlot(2, 135, 20, true),
          new GuiSlot(3, 135, 38, true));
    }
    
    @Override
    public int getInventorySize() {
        return 4;
    }
    
    @Override
    public List<Vec3i> getAddonSlots() {
        return List.of(
          new Vec3i(0, 0, -2),
          new Vec3i(-1, 0, -1),
          new Vec3i(0, 0, 2),
          new Vec3i(-1, 0, 1)
        );
    }
    
    @Override
    public float getMoveTime() {
        return Oritech.CONFIG.destroyerConfig.moveDuration() * this.getSpeedMultiplier();
    }
    
    @Override
    public float getWorkTime() {
        return (float) (Oritech.CONFIG.destroyerConfig.workDuration() * this.getSpeedMultiplier() * Math.pow(targetHardness,  0.5f));
    }
    
    @Override
    public int getMoveEnergyUsage() {
        return Oritech.CONFIG.destroyerConfig.moveEnergyUsage();
    }
    
    @Override
    public int getOperationEnergyUsage() {
        return Oritech.CONFIG.destroyerConfig.workEnergyUsage();
    }
    
    @Override
    public ScreenHandlerType<?> getScreenHandlerType() {
        return ModScreens.DESTROYER_SCREEN;
    }
    
    @Override
    public List<Vec3i> getCorePositions() {
        return List.of(
          new Vec3i(0, 0, -1),
          new Vec3i(0, 0, 1)
        );
    }
    
    @Override
    public void sendMovementNetworkPacket(BlockPos from) {
        super.sendMovementNetworkPacket(from);
        syncQuarryNetworkData();
    }
    
    private void syncQuarryNetworkData() {
        NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.QuarryTargetPacket(pos, quarryTarget, range, yieldAddons, getBaseAddonData().speed()));
    }
    
    @Override
    public void playSetupAnimation() {
    
    }
}