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