Mecánicas de agrupación en mega-stacks (MC 26.1.2)
Este documento proporciona un desglose técnico y matemático de la agregación de objetos en el suelo, el cálculo del radio de búsqueda y la fusión de entidades en Item Clumps para Minecraft 26.1.2.
📊 Ficha técnica de características
| Propiedad | Especificación |
|---|---|
| Nombre del sistema | Agregador de mega-stacks de objetos en suelo |
| Clase objetivo | net.minecraft.world.entity.item.ItemEntity |
| Límite de fusión predeterminado | 9,999 objetos (Configurable hasta $2,147,483,647$) |
| Radio de búsqueda ($r$) | Horizontal $1\text{ a }10\text{ bloques}$ (Predeterminado: $1\text{ bloque}$) |
| Inyección de radio | @ModifyArgs en AABB.inflate(DDD) |
| GameRules de control | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Cómo funciona la agregación en mega-stacks
En Minecraft Vanilla, los objetos caídos llaman a mergeWithNeighbours() durante su ciclo de ticks, buscando objetos cercanos del mismo tipo. Sin embargo, Vanilla prohíbe estrictamente la fusión si el recuento de objetos de la entidad de destino excediera itemStack.getMaxStackSize() (normalmente 64).
Item Clumps intercepta esta comprobación mediante ItemEntityMixin.java para eliminar esta restricción mientras preserva una rigurosa seguridad de componentes de datos.
┌──────────────────────────────┐
│ ItemEntity A Ticks In │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ Feasibility Check │
│ - Matching player target? │
│ - Identical DataComponents? │
└──────────────┬───────────────┘
│ (Pass)
▼
┌──────────────────────────────┐
│ Combine Stacks Math Check │
└──────────────┬───────────────┘
│
┌───────────────┴───────────────┐
│ │
thisCount + otherCount <= maxClump thisCount + otherCount > maxClump
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Complete Merge │ │ Partial Merge │
│ Larger absorbs │ │ Fill entity A to │
│ smaller entity; │ │ maxClumpSize; │
│ Younger age kept. │ │ B keeps remainder.│
└───────────────────┘ └───────────────────┘📐 Fórmulas matemáticas y cálculo de precisión
1. Expansión horizontal del cuadro delimitador (@ModifyArgs)
$$\text{args.set}(0, r), \quad \text{args.set}(2, r)$$
Donde:
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ bloques.
- El índice de altura vertical ($1$) no se modifica, preservando la separación vertical entre los diferentes niveles de piso.
2. Cálculo de transferencia de recuento de objetos
- Absorción completa ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = \text{thisCount} + \text{otherCount}$$
- Absorción parcial ($S > \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
💻 Implementación del código fuente Java real
De ItemEntityMixin.java en Minecraft 26.1.2:
@ModifyArgs(method = "mergeWithNeighbours", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/AABB;inflate(DDD)Lnet/minecraft/world/phys/AABB;"))
private void item_clumps$modifySearchRadius(Args args) {
if (DynamicGameRuleManager.getBoolean(this.level(), ItemClumpsFabric.ENABLE_CLUMPING)) {
double radius = DynamicGameRuleManager.getInt(this.level(), ItemClumpsFabric.MERGE_RADIUS);
args.set(0, radius);
args.set(2, radius);
}
}
@Inject(method = "tryToMerge", at = @At("HEAD"), cancellable = true)
private void item_clumps$customMerge(ItemEntity other, CallbackInfo ci) {
if (!DynamicGameRuleManager.getBoolean(this.level(), ItemClumpsFabric.ENABLE_CLUMPING)) return;
ItemStack thisStack = this.getItem();
ItemStack otherStack = other.getItem();
if (!Objects.equals(this.target, ((ItemEntityMixin)(Object)other).target) ||
!ItemStack.isSameItemSameComponents(thisStack, otherStack)) {
return;
}
int thisCount = thisStack.getCount();
int otherCount = otherStack.getCount();
int maxClump = DynamicGameRuleManager.getInt(this.level(), ItemClumpsFabric.MAX_CLUMP_SIZE);
if (thisCount + otherCount > maxClump) {
int spaceLeft = maxClump - thisCount;
if (spaceLeft > 0) {
thisStack.setCount(maxClump);
this.setItem(thisStack);
otherStack.setCount(otherCount - spaceLeft);
other.setItem(otherStack);
}
ci.cancel();
return;
}
// Full Merge: larger stack absorbs the smaller stack
if (otherCount < thisCount) {
thisStack.setCount(thisCount + otherCount);
this.setItem(thisStack);
this.pickupDelay = Math.max(this.pickupDelay, ((ItemEntityMixin)(Object)other).pickupDelay);
this.age = Math.min(this.age, ((ItemEntityMixin)(Object)other).age);
other.discard();
} else {
otherStack.setCount(thisCount + otherCount);
other.setItem(otherStack);
((ItemEntityMixin)(Object)other).pickupDelay = Math.max(((ItemEntityMixin)(Object)other).pickupDelay, this.pickupDelay);
((ItemEntityMixin)(Object)other).age = Math.min(((ItemEntityMixin)(Object)other).age, this.age);
this.discard();
}
ci.cancel();
}