package ar.com.companeros.world; import ar.com.companeros.action.ToolResult; import java.util.*; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.TransientCraftingContainer; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.*; import net.minecraft.world.level.block.Blocks; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.event.ForgeEventFactory; /** Fabricación sobre inventario canónico, con asignación de ingredientes y eventos del jugador. */ public final class PlayerCrafting { private PlayerCrafting() { } // Entrada: solo recetas reales de mesa y matrices 2×2 o 3×3. public static ToolResult craft(ServerPlayer player, UUID actionId, Item wanted) { int gridSize = workbench(player) ? 3 : 2; var inventory = player.getInventory(); var before = counts(player); boolean known = false; boolean full = false; for (CraftingRecipe recipe : player.serverLevel().getRecipeManager().getAllRecipesFor(RecipeType.CRAFTING)) { if (!recipe.getResultItem(player.level().registryAccess()).is(wanted) || !recipe.canCraftInDimensions(gridSize, gridSize)) continue; known = true; if (recipe.getIngredients().isEmpty()) continue; // Recetas especiales necesitan su adaptador. SimpleContainer scratch = new SimpleContainer(36); for (int i = 0; i < 36; i++) scratch.setItem(i, inventory.getItem(i).copy()); var matrix = new TransientCraftingContainer(new AbstractContainerMenu(null, -1) { @Override public ItemStack quickMoveStack(Player p, int slot) { return ItemStack.EMPTY; } @Override public boolean stillValid(Player p) { return false; } }, gridSize, gridSize); int recipeWidth = recipe instanceof ShapedRecipe shaped ? shaped.getWidth() : gridSize; int[] budget = {4096}; if (!assign(recipe, scratch, matrix, recipeWidth, gridSize, 0, budget)) continue; if (!recipe.matches(matrix, player.level())) continue; ItemStack result = recipe.assemble(matrix, player.level().registryAccess()); if (result.isEmpty() || !result.is(wanted)) continue; Player previous = ForgeHooks.getCraftingPlayer(); net.minecraft.core.NonNullList remaining; try { ForgeHooks.setCraftingPlayer(player); remaining = recipe.getRemainingItems(matrix); } finally { ForgeHooks.setCraftingPlayer(previous); } if (!scratch.addItem(result.copy()).isEmpty()) { full = true; continue; } boolean fits = true; for (ItemStack stack : remaining) if (!stack.isEmpty() && !scratch.addItem(stack.copy()).isEmpty()) { fits = false; break; } if (!fits) { full = true; continue; } // Commit único; los remanentes y resultado ya caben sin eliminar recursos. for (int i = 0; i < 36; i++) inventory.setItem(i, scratch.getItem(i).copy()); inventory.setChanged(); result.onCraftedBy(player.level(), player, result.getCount()); ForgeEventFactory.firePlayerCraftingEvent(player, result.copy(), matrix); player.awardRecipes(List.of(recipe)); player.containerMenu.broadcastChanges(); var delta = delta(before, counts(player)); return ToolResult.immediate(actionId, ToolResult.Status.SUCCEEDED, ToolResult.Reason.NONE, GlobalPos.of(player.level().dimension(), player.blockPosition()), delta, List.of("crafted:" + recipe.getId())); } return ToolResult.immediate(actionId, ToolResult.Status.BLOCKED, full ? ToolResult.Reason.INVENTORY_FULL : known ? ToolResult.Reason.RESOURCE_EXHAUSTED : ToolResult.Reason.RECIPE_UNSUPPORTED, GlobalPos.of(player.level().dimension(), player.blockPosition()), Map.of(), List.of()); } // Backtracking acotado evita rechazar recetas por etiquetas de ingredientes solapadas. private static boolean assign(CraftingRecipe recipe, SimpleContainer bag, TransientCraftingContainer matrix, int width, int gridWidth, int ingredientIndex, int[] budget) { if (ingredientIndex == recipe.getIngredients().size()) return true; if (budget[0] <= 0) return false; var ingredient = recipe.getIngredients().get(ingredientIndex); int matrixSlot = ingredientIndex / width * gridWidth + ingredientIndex % width; if (matrixSlot >= matrix.getContainerSize()) return false; if (ingredient.isEmpty()) return assign(recipe, bag, matrix, width, gridWidth, ingredientIndex + 1, budget); List tried = new ArrayList<>(); for (int slot = 0; slot < bag.getContainerSize(); slot++) { ItemStack stack = bag.getItem(slot); if (stack.isEmpty() || !ingredient.test(stack) || tried.stream().anyMatch(t -> ItemStack.isSameItemSameTags(t, stack))) continue; tried.add(stack.copy()); if (--budget[0] < 0) return false; ItemStack one = bag.removeItem(slot, 1); matrix.setItem(matrixSlot, one); if (assign(recipe, bag, matrix, width, gridWidth, ingredientIndex + 1, budget)) return true; matrix.setItem(matrixSlot, ItemStack.EMPTY); ItemStack restored = bag.getItem(slot); if (restored.isEmpty()) bag.setItem(slot, one); else restored.grow(1); } return false; } private static boolean workbench(ServerPlayer p) { for (BlockPos pos : BlockPos.betweenClosed(p.blockPosition().offset(-3, -2, -3), p.blockPosition().offset(3, 2, 3))) { if (!p.serverLevel().hasChunkAt(pos) || !p.level().getBlockState(pos).is(Blocks.CRAFTING_TABLE) || !PlayerOperations.reach(p, pos) || !p.serverLevel().mayInteract(p, pos)) continue; var hit = p.level().clip(new net.minecraft.world.level.ClipContext(p.getEyePosition(), net.minecraft.world.phys.Vec3.atCenterOf(pos), net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, p)); if (hit.getBlockPos().equals(pos)) return true; } return false; } public static Map counts(ServerPlayer p) { Map result = new HashMap<>(); for (ItemStack stack : p.getInventory().items) if (!stack.isEmpty()) result.merge(net.minecraftforge.registries.ForgeRegistries.ITEMS.getKey(stack.getItem()).toString(), stack.getCount(), Integer::sum); return result; } public static Map delta(Map before, Map after) { Map result = new HashMap<>(); Set keys = new HashSet<>(before.keySet()); keys.addAll(after.keySet()); for (String key : keys) { int difference = after.getOrDefault(key, 0) - before.getOrDefault(key, 0); if (difference != 0) result.put(key, difference); } return result; } }