Volver al índice

src/main/java/ar/com/companeros/WorldActions.java

package ar.com.companeros;

import com.mojang.authlib.GameProfile;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.CropBlock;
import net.minecraft.world.level.block.SweetBerryBushBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.util.FakePlayerFactory;

/** Acciones físicas con permisos, distancia, herramientas y botines del servidor. */
public final class WorldActions {
    private static final java.util.Map<Integer, java.util.List<BlockPos>> SEARCH_ORDER = new java.util.HashMap<>();
    private WorldActions() { }

    private static FakePlayer actor(Companion bot, ItemStack hand) {
        FakePlayer player = FakePlayerFactory.get(bot.serverLevel(), new GameProfile(bot.getUUID(), "Companero"));
        player.setGameMode(GameType.SURVIVAL);
        player.moveTo(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot());
        player.setOnGround(bot.onGround());
        player.setItemInHand(InteractionHand.MAIN_HAND, hand);
        return player;
    }

    public static boolean allowed(Companion bot, BlockPos pos) {
        return bot.inArea(pos) && bot.serverLevel().hasChunkAt(pos)
            && bot.serverLevel().getWorldBorder().isWithinBounds(pos)
            && !WorkArea.get(bot.serverLevel()).contains(pos)
            && bot.serverLevel().getBlockEntity(pos) == null;
    }

    // Sólo árboles con hojas cercanas, cultivos maduros y piedra bajo tierra.
    public static boolean isResource(Companion bot, BlockPos pos, String wanted) {
        if (!allowed(bot, pos) || bot.blocked.getOrDefault(pos, 0) > bot.tickCount) return false;
        BlockState state = bot.level().getBlockState(pos);
        if (wanted.equals("food")) {
            return state.getBlock() instanceof CropBlock crop && crop.isMaxAge(state)
                || state.is(Blocks.SWEET_BERRY_BUSH) && state.getValue(SweetBerryBushBlock.AGE) >= 2;
        }
        if (wanted.equals("stone")) {
            int surface = bot.level().getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
                pos.getX(), pos.getZ());
            return state.is(Blocks.STONE) && pos.getY() < surface - 2 && pos.distSqr(bot.home) > 64;
        }
        if (!state.is(BlockTags.LOGS) || pos.distSqr(bot.home) < 36) return false;
        for (BlockPos leaf : BlockPos.betweenClosed(pos.offset(-2, 1, -2), pos.offset(2, 6, 2))) {
            if (bot.serverLevel().hasChunkAt(leaf) && bot.level().getBlockState(leaf).is(BlockTags.LEAVES)) return true;
        }
        return false;
    }

    /** Procesa un cupo por búsqueda para no revisar una región entera en un tick. */
    public static BlockPos find(Companion bot, String wanted) {
        var offsets = SEARCH_ORDER.computeIfAbsent(bot.radius, radius -> {
            var list = new java.util.ArrayList<BlockPos>();
            for (int ring = 0; ring <= radius; ring++) for (int dx = -ring; dx <= ring; dx++)
                for (int dz = -ring; dz <= ring; dz++) {
                    if (Math.max(Math.abs(dx), Math.abs(dz)) != ring) continue;
                    for (int dy : new int[]{0, 1, 2, 3, 4, 5, -1, -2, -3}) list.add(new BlockPos(dx, dy, dz));
                }
            return list;
        });
        for (int examined = 0; examined < 768; examined++) {
            int index = Math.floorMod(bot.searchCursor++, offsets.size());
            BlockPos pos = bot.home.offset(offsets.get(index));
            if (isResource(bot, pos, wanted)) return pos.immutable();
        }
        return null;
    }

    /** Acumula tiempo de rotura real. Devuelve true cuando la tarea terminó. */
    public static boolean mine(Companion bot, BlockPos pos, String wanted) {
        if (!isResource(bot, pos, wanted)) { bot.miningProgress = 0; return true; }
        if (bot.position().distanceToSqr(Vec3.atCenterOf(pos)) > 16) return false;
        var sight = bot.level().clip(new net.minecraft.world.level.ClipContext(bot.getEyePosition(), Vec3.atCenterOf(pos),
            net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, bot));
        if (sight.getType() != net.minecraft.world.phys.HitResult.Type.MISS && !sight.getBlockPos().equals(pos)) {
            block(bot, pos); return true;
        }
        BlockState state = bot.level().getBlockState(pos);
        ItemStack tool = bot.toolFor(state);
        FakePlayer player = actor(bot, tool);
        if (state.requiresCorrectToolForDrops() && !tool.isCorrectToolForDrops(state)) { block(bot, pos); return true; }
        bot.getLookControl().setLookAt(pos.getX() + .5, pos.getY() + .5, pos.getZ() + .5);
        bot.swing(InteractionHand.MAIN_HAND);
        bot.miningProgress += state.getDestroyProgress(player, bot.level(), pos);
        if (bot.miningProgress < 1) return false;
        bot.miningProgress = 0;
        int experience = ForgeHooks.onBlockBreakEvent(bot.serverLevel(), GameType.SURVIVAL, player, pos);
        if (experience < 0) { block(bot, pos); return true; }
        var drops = Block.getDrops(state, bot.serverLevel(), pos, null, player, tool);
        if (!bot.serverLevel().destroyBlock(pos, false, bot)) { block(bot, pos); return true; }
        for (ItemStack drop : drops) {
            ItemStack left = bot.bag.addItem(drop.copy());
            if (!left.isEmpty()) bot.spawnAtLocation(left);
        }
        if (!tool.isEmpty()) tool.hurtAndBreak(1, bot, entity -> entity.broadcastBreakEvent(InteractionHand.MAIN_HAND));
        if (experience > 0) net.minecraft.world.entity.ExperienceOrb.award(bot.serverLevel(), Vec3.atCenterOf(pos), experience);
        // Replantado con la semilla o baya obtenida: también consume inventario.
        if (state.getBlock() instanceof CropBlock || state.is(Blocks.SWEET_BERRY_BUSH)) {
            ItemStack seed = findPlant(bot, state);
            if (!seed.isEmpty()) place(bot, pos, seed);
        }
        return true;
    }

    private static ItemStack findPlant(Companion bot, BlockState original) {
        for (int i = 0; i < bot.bag.getContainerSize(); i++) {
            ItemStack stack = bot.bag.getItem(i);
            if (stack.getItem() instanceof BlockItem item && item.getBlock() == original.getBlock()) return stack;
        }
        return ItemStack.EMPTY;
    }

    public static void block(Companion bot, BlockPos pos) {
        if (bot.blocked.size() > 256) bot.blocked.clear();
        bot.blocked.put(pos.immutable(), bot.tickCount + 1200);
        bot.getNavigation().stop();
        bot.miningProgress = 0;
    }

    /** BlockItem aplica reglas de colocación y eventos Forge; la pila es del inventario. */
    public static boolean place(Companion bot, BlockPos pos, ItemStack stack) {
        if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem item) || !allowed(bot, pos)
                || !bot.level().getBlockState(pos).isAir() || bot.position().distanceToSqr(Vec3.atCenterOf(pos)) > 20.25)
            return false;
        FakePlayer player = actor(bot, stack);
        for (Direction face : Direction.values()) {
            BlockPos support = pos.relative(face.getOpposite());
            if (!bot.serverLevel().hasChunkAt(support) || bot.level().getBlockState(support).isAir()) continue;
            Vec3 point = Vec3.atCenterOf(support).add(Vec3.atLowerCornerOf(face.getNormal()).scale(.5));
            UseOnContext use = new UseOnContext(player, InteractionHand.MAIN_HAND,
                new BlockHitResult(point, face, support, false));
            BlockPlaceContext context = new BlockPlaceContext(use);
            if (!context.getClickedPos().equals(pos)) continue;
            if (stack.useOn(use).consumesAction()) { bot.swing(InteractionHand.MAIN_HAND); return true; }
        }
        return false;
    }
}