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> 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(); 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; } }