Механика объединения в мега-стаки (MC 26.1.2)
Этот документ содержит технический и математический разбор агрегации предметов на земле, вычисления радиуса поиска и слияния сущностей в Item Clumps для Minecraft 26.1.2.
📊 Информационный блок функции
| Свойство | Спецификация |
|---|---|
| Имя системы | Агрегатор мега-стаков предметов на земле |
| Целевой класс | net.minecraft.world.entity.item.ItemEntity |
| Лимит по умолчанию | 9 999 предметов (настраивается до $2,147,483,647$) |
| Радиус поиска ($r$) | По горизонтали $1\text{–}10\text{ блоков}$ (по умолч.: $1\text{ блок}$) |
| Точка внедрения радиуса | @ModifyArgs на AABB.inflate(DDD) |
| Управляющие GameRules | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Как работает объединение в мега-стаки
В ванильном Minecraft выброшенные предметы каждый тик вызывают mergeWithNeighbours(). Однако слияние запрещено, если количество превысит itemStack.getMaxStackSize() (обычно 64).
Item Clumps снимает это ограничение через ItemEntityMixin.java, строго сохраняя целостность компонентов данных.
┌──────────────────────────────┐
│ 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.│
└───────────────────┘ └───────────────────┘📐 Математические формулы и точные вычисления
1. Расширение горизонтального хитбокса (@ModifyArgs)
$$\text{args.set}(0, r), \quad \text{args.set}(2, r)$$
Где:
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ блоков.
- Вертикальный индекс ($1$) остается неизменным, исключая смешивание предметов с разных этажей.
2. Математика передачи количества предметов
- Полное поглощение ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = \text{thisCount} + \text{otherCount}$$
- Частичное поглощение ($S > \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
💻 Исходный код реализации на Java
Из ItemEntityMixin.java в Minecraft 26.1.2:
java
@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();
}