mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-28 07:20:24 +01:00
a207d14a0e
Signed-off-by: Mariell Hoversholm <proximyst@proximyst.com>
605 lines
33 KiB
Diff
605 lines
33 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Wed, 27 Apr 2016 22:09:52 -0400
|
|
Subject: [PATCH] Optimize Hoppers
|
|
|
|
* Removes unnecessary extra calls to .update() that are very expensive
|
|
* Lots of itemstack cloning removed. Only clone if the item is actually moved
|
|
* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items.
|
|
However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on.
|
|
* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory
|
|
* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration
|
|
* Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried)
|
|
* Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins)
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
index edda2121f8c1046478beaa77030ebb36d403b334..7fbd501d70dccf869a4454e2789a5d68f2e15754 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -585,4 +585,13 @@ public class PaperWorldConfig {
|
|
private void entitiesTargetWithFollowRange() {
|
|
entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange);
|
|
}
|
|
+
|
|
+ public boolean cooldownHopperWhenFull = true;
|
|
+ public boolean disableHopperMoveEvents = false;
|
|
+ private void hopperOptimizations() {
|
|
+ cooldownHopperWhenFull = getBoolean("hopper.cooldown-when-full", cooldownHopperWhenFull);
|
|
+ log("Cooldown Hoppers when Full: " + (cooldownHopperWhenFull ? "enabled" : "disabled"));
|
|
+ disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents);
|
|
+ log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled"));
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 753e6f609189c589514739bea80007bace3c89d2..7038897b8fb4c18ca97b95a3b24c30b40b62b005 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -131,6 +131,7 @@ import net.minecraft.world.level.LevelSettings;
|
|
import net.minecraft.world.level.biome.BiomeManager;
|
|
import net.minecraft.world.level.biome.BiomeSource;
|
|
import net.minecraft.world.level.block.Block;
|
|
+import net.minecraft.world.level.block.entity.HopperBlockEntity;
|
|
import net.minecraft.world.level.border.WorldBorder;
|
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
import net.minecraft.world.level.dimension.DimensionType;
|
|
@@ -1360,6 +1361,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
while (iterator.hasNext()) {
|
|
ServerLevel worldserver = (ServerLevel) iterator.next();
|
|
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
|
|
+ HopperBlockEntity.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
|
|
|
|
this.profiler.push(() -> {
|
|
return worldserver + " " + worldserver.dimension().location();
|
|
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
|
|
index 02bfa4fb8055e60a84e878ffbf18303c0ee25b1d..ac996d581925c8f92832009945c766962e5b51c5 100644
|
|
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
|
|
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
|
|
@@ -538,11 +538,12 @@ public final class ItemStack {
|
|
return this.getItem().interactLivingEntity(this, user, entity, hand);
|
|
}
|
|
|
|
- public ItemStack copy() {
|
|
- if (this.isEmpty()) {
|
|
+ public ItemStack copy() { return cloneItemStack(false); } // Paper
|
|
+ public ItemStack cloneItemStack(boolean origItem) { // Paper
|
|
+ if (!origItem && this.isEmpty()) { // Paper
|
|
return ItemStack.EMPTY;
|
|
} else {
|
|
- ItemStack itemstack = new ItemStack(this.getItem(), this.count);
|
|
+ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper
|
|
|
|
itemstack.setPopTime(this.getPopTime());
|
|
if (this.tag != null) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index 3e2cd6c7a34c1a792d7346019a8b039d1f4a7c04..6b79f8cd9258af47afa6efa7b1f97c3780be58b0 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -1162,8 +1162,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
return list;
|
|
}
|
|
|
|
- @Override
|
|
- public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> entityClass, AABB box, @Nullable Predicate<? super T> predicate) {
|
|
+ public <T extends Entity> List<T> getEntities(Class<? extends T> oclass, AABB axisalignedbb, @Nullable Predicate<? super T> predicate) { return getEntitiesOfClass(oclass, axisalignedbb, predicate); } // Paper - OBFHELPER
|
|
+ @Override public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> entityClass, AABB box, @Nullable Predicate<? super T> predicate) {
|
|
this.getProfiler().incrementCounter("getEntities");
|
|
int i = Mth.floor((box.minX - 2.0D) / 16.0D);
|
|
int j = Mth.ceil((box.maxX + 2.0D) / 16.0D);
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
|
|
index 84012c2d12817e657b046bc168cc8eddebcd3831..05fa76c02ce61e26891ad995fe89e925ea086557 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
|
|
@@ -77,6 +77,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject {
|
|
public void setCurrentChunk(LevelChunk chunk) {
|
|
this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null;
|
|
}
|
|
+ static boolean IGNORE_TILE_UPDATES = false;
|
|
// Paper end
|
|
|
|
@Nullable
|
|
@@ -155,6 +156,7 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject {
|
|
|
|
public void setChanged() {
|
|
if (this.level != null) {
|
|
+ if (IGNORE_TILE_UPDATES) return; // Paper
|
|
this.blockState = this.level.getBlockState(this.worldPosition);
|
|
this.level.blockEntityChanged(this.worldPosition, this);
|
|
if (!this.blockState.isAir()) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java
|
|
index f8e4a42bed265822666141683e36e6696694925b..fc8bb72f7d677f65db505016ad6a4cd6146de29f 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java
|
|
@@ -1,6 +1,7 @@
|
|
package net.minecraft.world.level.block.entity;
|
|
|
|
import javax.annotation.Nullable;
|
|
+import net.minecraft.core.BlockPos;
|
|
import net.minecraft.world.Container;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
@@ -17,12 +18,13 @@ public interface Hopper extends Container {
|
|
return Hopper.SUCK;
|
|
}
|
|
|
|
- @Nullable
|
|
+ //@Nullable // Paper - it's annoying
|
|
Level getLevel();
|
|
+ default BlockPos getBlockPosition() { return new BlockPos(getX(), getY(), getZ()); } // Paper
|
|
|
|
- double getLevelX();
|
|
+ double getLevelX(); default double getX() { return this.getLevelX(); } // Paper - OBFHELPER
|
|
|
|
- double getLevelY();
|
|
+ double getLevelY(); default double getY() { return this.getLevelY(); } // Paper - OBFHELPER
|
|
|
|
- double getLevelZ();
|
|
+ double getLevelZ(); default double getZ() { return this.getLevelZ(); } // Paper - OBFHELPER
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
|
|
index 04b0f0de43dfd95e82d402068da8a97bdb86f758..70718fcbaa6f671061479957b7608f7639dab54b 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
|
|
@@ -193,6 +193,160 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
return false;
|
|
}
|
|
|
|
+ // Paper start - Optimize Hoppers
|
|
+ private static boolean skipPullModeEventFire = false;
|
|
+ private static boolean skipPushModeEventFire = false;
|
|
+ public static boolean skipHopperEvents = false;
|
|
+
|
|
+ private boolean hopperPush(Container iinventory, Direction enumdirection) {
|
|
+ skipPushModeEventFire = skipHopperEvents;
|
|
+ boolean foundItem = false;
|
|
+ for (int i = 0; i < this.getContainerSize(); ++i) {
|
|
+ ItemStack item = this.getItem(i);
|
|
+ if (!item.isEmpty()) {
|
|
+ foundItem = true;
|
|
+ ItemStack origItemStack = item;
|
|
+ ItemStack itemstack = origItemStack;
|
|
+
|
|
+ final int origCount = origItemStack.getCount();
|
|
+ final int moved = Math.min(level.spigotConfig.hopperAmount, origCount);
|
|
+ origItemStack.setCount(moved);
|
|
+
|
|
+ // We only need to fire the event once to give protection plugins a chance to cancel this event
|
|
+ // Because nothing uses getItem, every event call should end up the same result.
|
|
+ if (!skipPushModeEventFire) {
|
|
+ itemstack = callPushMoveEvent(iinventory, itemstack);
|
|
+ if (itemstack == null) { // cancelled
|
|
+ origItemStack.setCount(origCount);
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ final ItemStack itemstack2 = addItem(this, iinventory, itemstack, enumdirection);
|
|
+ final int remaining = itemstack2.getCount();
|
|
+ if (remaining != moved) {
|
|
+ origItemStack = origItemStack.cloneItemStack(true);
|
|
+ origItemStack.setCount(origCount);
|
|
+ if (!origItemStack.isEmpty()) {
|
|
+ origItemStack.setCount(origCount - moved + remaining);
|
|
+ }
|
|
+ this.setItem(i, origItemStack);
|
|
+ iinventory.setChanged();
|
|
+ return true;
|
|
+ }
|
|
+ origItemStack.setCount(origCount);
|
|
+ }
|
|
+ }
|
|
+ if (foundItem && level.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown
|
|
+ this.setCooldown(level.spigotConfig.hopperTransfer);
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ private static boolean hopperPull(Hopper ihopper, Container iinventory, ItemStack origItemStack, int i) {
|
|
+ ItemStack itemstack = origItemStack;
|
|
+ final int origCount = origItemStack.getCount();
|
|
+ final Level world = ihopper.getLevel();
|
|
+ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount);
|
|
+ itemstack.setCount(moved);
|
|
+
|
|
+ if (!skipPullModeEventFire) {
|
|
+ itemstack = callPullMoveEvent(ihopper, iinventory, itemstack);
|
|
+ if (itemstack == null) { // cancelled
|
|
+ origItemStack.setCount(origCount);
|
|
+ // Drastically improve performance by returning true.
|
|
+ // No plugin could of relied on the behavior of false as the other call
|
|
+ // site for IMIE did not exhibit the same behavior
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final ItemStack itemstack2 = addItem(iinventory, ihopper, itemstack, null);
|
|
+ final int remaining = itemstack2.getCount();
|
|
+ if (remaining != moved) {
|
|
+ origItemStack = origItemStack.cloneItemStack(true);
|
|
+ origItemStack.setCount(origCount);
|
|
+ if (!origItemStack.isEmpty()) {
|
|
+ origItemStack.setCount(origCount - moved + remaining);
|
|
+ }
|
|
+ IGNORE_TILE_UPDATES = true;
|
|
+ iinventory.setItem(i, origItemStack);
|
|
+ IGNORE_TILE_UPDATES = false;
|
|
+ iinventory.setChanged();
|
|
+ return true;
|
|
+ }
|
|
+ origItemStack.setCount(origCount);
|
|
+
|
|
+ if (world.paperConfig.cooldownHopperWhenFull) {
|
|
+ cooldownHopper(ihopper);
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ private ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack) {
|
|
+ Inventory destinationInventory = getInventory(iinventory);
|
|
+ InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner(false).getInventory(),
|
|
+ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true);
|
|
+ boolean result = event.callEvent();
|
|
+ if (!event.calledGetItem && !event.calledSetItem) {
|
|
+ skipPushModeEventFire = true;
|
|
+ }
|
|
+ if (!result) {
|
|
+ cooldownHopper(this);
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ if (event.calledSetItem) {
|
|
+ return CraftItemStack.asNMSCopy(event.getItem());
|
|
+ } else {
|
|
+ return itemstack;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static ItemStack callPullMoveEvent(Hopper hopper, Container iinventory, ItemStack itemstack) {
|
|
+ Inventory sourceInventory = getInventory(iinventory);
|
|
+ Inventory destination = getInventory(hopper);
|
|
+
|
|
+ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory,
|
|
+ // Mirror is safe as we no plugins ever use this item
|
|
+ CraftItemStack.asCraftMirror(itemstack), destination, false);
|
|
+ boolean result = event.callEvent();
|
|
+ if (!event.calledGetItem && !event.calledSetItem) {
|
|
+ skipPullModeEventFire = true;
|
|
+ }
|
|
+ if (!result) {
|
|
+ cooldownHopper(hopper);
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ if (event.calledSetItem) {
|
|
+ return CraftItemStack.asNMSCopy(event.getItem());
|
|
+ } else {
|
|
+ return itemstack;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static Inventory getInventory(Container iinventory) {
|
|
+ Inventory sourceInventory;// Have to special case large chests as they work oddly
|
|
+ if (iinventory instanceof CompoundContainer) {
|
|
+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory);
|
|
+ } else if (iinventory instanceof BlockEntity) {
|
|
+ sourceInventory = ((BlockEntity) iinventory).getOwner(false).getInventory();
|
|
+ } else {
|
|
+ sourceInventory = iinventory.getOwner().getInventory();
|
|
+ }
|
|
+ return sourceInventory;
|
|
+ }
|
|
+
|
|
+ private static void cooldownHopper(Hopper hopper) {
|
|
+ if (hopper instanceof HopperBlockEntity) {
|
|
+ ((HopperBlockEntity) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer);
|
|
+ } else if (hopper instanceof MinecartHopper) {
|
|
+ ((MinecartHopper) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer / 2);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
private boolean ejectItems() {
|
|
Container iinventory = this.getAttachedContainer();
|
|
|
|
@@ -204,27 +358,28 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
if (this.isFullContainer(iinventory, enumdirection)) {
|
|
return false;
|
|
} else {
|
|
- for (int i = 0; i < this.getContainerSize(); ++i) {
|
|
+ return hopperPush(iinventory, enumdirection); /* // Paper - disable rest
|
|
+ for (int i = 0; i < this.getSize(); ++i) {
|
|
if (!this.getItem(i).isEmpty()) {
|
|
- ItemStack itemstack = this.getItem(i).copy();
|
|
+ ItemStack itemstack = this.getItem(i).cloneItemStack();
|
|
// ItemStack itemstack1 = addItem(this, iinventory, this.splitStack(i, 1), enumdirection);
|
|
|
|
// CraftBukkit start - Call event when pushing items into other inventories
|
|
- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.removeItem(i, level.spigotConfig.hopperAmount)); // Spigot
|
|
+ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.splitStack(i, world.spigotConfig.hopperAmount)); // Spigot
|
|
|
|
Inventory destinationInventory;
|
|
// Have to special case large chests as they work oddly
|
|
- if (iinventory instanceof CompoundContainer) {
|
|
- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory);
|
|
+ if (iinventory instanceof InventoryLargeChest) {
|
|
+ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
|
|
} else {
|
|
destinationInventory = iinventory.getOwner().getInventory();
|
|
}
|
|
|
|
InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true);
|
|
- this.getLevel().getCraftServer().getPluginManager().callEvent(event);
|
|
+ this.getWorld().getServer().getPluginManager().callEvent(event);
|
|
if (event.isCancelled()) {
|
|
this.setItem(i, itemstack);
|
|
- this.setCooldown(level.spigotConfig.hopperTransfer); // Spigot
|
|
+ this.setCooldown(world.spigotConfig.hopperTransfer); // Spigot
|
|
return false;
|
|
}
|
|
int origCount = event.getItem().getAmount(); // Spigot
|
|
@@ -232,16 +387,16 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
// CraftBukkit end
|
|
|
|
if (itemstack1.isEmpty()) {
|
|
- iinventory.setChanged();
|
|
+ iinventory.update();
|
|
return true;
|
|
}
|
|
|
|
- itemstack.shrink(origCount - itemstack1.getCount()); // Spigot
|
|
+ itemstack.subtract(origCount - itemstack1.getCount()); // Spigot
|
|
this.setItem(i, itemstack);
|
|
}
|
|
}
|
|
|
|
- return false;
|
|
+ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations
|
|
}
|
|
}
|
|
}
|
|
@@ -250,18 +405,54 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
return inventory instanceof WorldlyContainer ? IntStream.of(((WorldlyContainer) inventory).getSlotsForFace(side)) : IntStream.range(0, inventory.getContainerSize());
|
|
}
|
|
|
|
- private boolean isFullContainer(Container inv, Direction enumdirection) {
|
|
- return getSlots(inv, enumdirection).allMatch((i) -> {
|
|
- ItemStack itemstack = inv.getItem(i);
|
|
+ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
|
|
+ if (iinventory instanceof WorldlyContainer) {
|
|
+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) {
|
|
+ if (!test.test(iinventory.getItem(i), i)) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ int size = iinventory.getContainerSize();
|
|
+ for (int i = 0; i < size; i++) {
|
|
+ if (!test.test(iinventory.getItem(i), i)) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
|
|
- return itemstack.getCount() >= itemstack.getMaxStackSize();
|
|
- });
|
|
+ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
|
|
+ if (iinventory instanceof WorldlyContainer) {
|
|
+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) {
|
|
+ if (test.test(iinventory.getItem(i), i)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ int size = iinventory.getContainerSize();
|
|
+ for (int i = 0; i < size; i++) {
|
|
+ if (test.test(iinventory.getItem(i), i)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ private static final java.util.function.BiPredicate<ItemStack, Integer> STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize();
|
|
+ private static final java.util.function.BiPredicate<ItemStack, Integer> IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty();
|
|
+
|
|
+ // Paper end
|
|
+
|
|
+ private boolean isFullContainer(Container inv, Direction enumdirection) {
|
|
+ // Paper start - no streams
|
|
+ return allMatch(inv, enumdirection, STACK_SIZE_TEST);
|
|
+ // Paper end
|
|
}
|
|
|
|
private static boolean isEmptyContainer(Container inv, Direction facing) {
|
|
- return getSlots(inv, facing).allMatch((i) -> {
|
|
- return inv.getItem(i).isEmpty();
|
|
- });
|
|
+ return allMatch(inv, facing, IS_EMPTY_TEST);
|
|
}
|
|
|
|
public static boolean suckInItems(Hopper hopper) {
|
|
@@ -270,9 +461,17 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
if (iinventory != null) {
|
|
Direction enumdirection = Direction.DOWN;
|
|
|
|
- return isEmptyContainer(iinventory, enumdirection) ? false : getSlots(iinventory, enumdirection).anyMatch((i) -> {
|
|
- return tryTakeInItemFromSlot(hopper, iinventory, i, enumdirection);
|
|
+ // Paper start - optimize hoppers and remove streams
|
|
+ skipPullModeEventFire = skipHopperEvents;
|
|
+ return !isEmptyContainer(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> {
|
|
+ // Logic copied from below to avoid extra getItem calls
|
|
+ if (!item.isEmpty() && canTakeItem(iinventory, item, i, enumdirection)) {
|
|
+ return hopperPull(hopper, iinventory, item, i);
|
|
+ } else {
|
|
+ return false;
|
|
+ }
|
|
});
|
|
+ // Paper end
|
|
} else {
|
|
Iterator iterator = getItemsAtAndAbove(hopper).iterator();
|
|
|
|
@@ -290,47 +489,48 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
}
|
|
}
|
|
|
|
- private static boolean tryTakeInItemFromSlot(Hopper hopper, Container inventory, int slot, Direction side) {
|
|
+ private static boolean tryTakeInItemFromSlot(Hopper hopper, Container inventory, int slot, Direction side) {// Paper - method unused as logic is inlined above
|
|
ItemStack itemstack = inventory.getItem(slot);
|
|
|
|
- if (!itemstack.isEmpty() && canTakeItemFromContainer(inventory, itemstack, slot, side)) {
|
|
- ItemStack itemstack1 = itemstack.copy();
|
|
+ if (!itemstack.isEmpty() && canTakeItemFromContainer(inventory, itemstack, slot, side)) { // If this logic changes, update above. this is left inused incase reflective plugins
|
|
+ return hopperPull(hopper, inventory, itemstack, slot); /* // Paper - disable rest
|
|
+ ItemStack itemstack1 = itemstack.cloneItemStack();
|
|
// ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null);
|
|
// CraftBukkit start - Call event on collection of items from inventories into the hopper
|
|
- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(inventory.removeItem(slot, hopper.getLevel().spigotConfig.hopperAmount)); // Spigot
|
|
+ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.splitStack(i, ihopper.getWorld().spigotConfig.hopperAmount)); // Spigot
|
|
|
|
Inventory sourceInventory;
|
|
// Have to special case large chests as they work oddly
|
|
- if (inventory instanceof CompoundContainer) {
|
|
- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) inventory);
|
|
+ if (iinventory instanceof InventoryLargeChest) {
|
|
+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
|
|
} else {
|
|
- sourceInventory = inventory.getOwner().getInventory();
|
|
+ sourceInventory = iinventory.getOwner().getInventory();
|
|
}
|
|
|
|
- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), hopper.getOwner().getInventory(), false);
|
|
+ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false);
|
|
|
|
- hopper.getLevel().getCraftServer().getPluginManager().callEvent(event);
|
|
+ ihopper.getWorld().getServer().getPluginManager().callEvent(event);
|
|
if (event.isCancelled()) {
|
|
- inventory.setItem(slot, itemstack1);
|
|
+ iinventory.setItem(i, itemstack1);
|
|
|
|
- if (hopper instanceof HopperBlockEntity) {
|
|
- ((HopperBlockEntity) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer); // Spigot
|
|
- } else if (hopper instanceof MinecartHopper) {
|
|
- ((MinecartHopper) hopper).setCooldown(hopper.getLevel().spigotConfig.hopperTransfer / 2); // Spigot
|
|
+ if (ihopper instanceof TileEntityHopper) {
|
|
+ ((TileEntityHopper) ihopper).setCooldown(ihopper.getWorld().spigotConfig.hopperTransfer); // Spigot
|
|
+ } else if (ihopper instanceof EntityMinecartHopper) {
|
|
+ ((EntityMinecartHopper) ihopper).setCooldown(ihopper.getWorld().spigotConfig.hopperTransfer / 2); // Spigot
|
|
}
|
|
return false;
|
|
}
|
|
int origCount = event.getItem().getAmount(); // Spigot
|
|
- ItemStack itemstack2 = addItem(inventory, hopper, CraftItemStack.asNMSCopy(event.getItem()), null);
|
|
+ ItemStack itemstack2 = addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
|
|
// CraftBukkit end
|
|
|
|
if (itemstack2.isEmpty()) {
|
|
- inventory.setChanged();
|
|
+ iinventory.update();
|
|
return true;
|
|
}
|
|
|
|
- itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot
|
|
- inventory.setItem(slot, itemstack1);
|
|
+ itemstack1.subtract(origCount - itemstack2.getCount()); // Spigot
|
|
+ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations
|
|
}
|
|
|
|
return false;
|
|
@@ -339,7 +539,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
public static boolean addItem(Container inventory, ItemEntity itemEntity) {
|
|
boolean flag = false;
|
|
// CraftBukkit start
|
|
- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
|
|
+ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation
|
|
itemEntity.level.getCraftServer().getPluginManager().callEvent(event);
|
|
if (event.isCancelled()) {
|
|
return false;
|
|
@@ -381,6 +581,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
return !inventory.canPlaceItem(slot, stack) ? false : !(inventory instanceof WorldlyContainer) || ((WorldlyContainer) inventory).canPlaceItemThroughFace(slot, stack, side);
|
|
}
|
|
|
|
+ private static boolean canTakeItem(Container iinventory, ItemStack itemstack, int i, Direction enumdirection) { return canTakeItemFromContainer(iinventory, itemstack, i, enumdirection); } // Paper - OBFHELPER
|
|
private static boolean canTakeItemFromContainer(Container inv, ItemStack stack, int slot, Direction facing) {
|
|
return !(inv instanceof WorldlyContainer) || ((WorldlyContainer) inv).canTakeItemThroughFace(slot, stack, facing);
|
|
}
|
|
@@ -393,7 +594,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
boolean flag1 = to.isEmpty();
|
|
|
|
if (itemstack1.isEmpty()) {
|
|
+ IGNORE_TILE_UPDATES = true; // Paper
|
|
to.setItem(slot, stack);
|
|
+ IGNORE_TILE_UPDATES = false; // Paper
|
|
stack = ItemStack.EMPTY;
|
|
flag = true;
|
|
} else if (canMergeItems(itemstack1, stack)) {
|
|
@@ -444,20 +647,26 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
}
|
|
|
|
public static List<ItemEntity> getItemsAtAndAbove(Hopper ihopper) {
|
|
- return (List) ihopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> {
|
|
- return ihopper.getLevel().getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(ihopper.getLevelX() - 0.5D, ihopper.getLevelY() - 0.5D, ihopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream();
|
|
- }).collect(Collectors.toList());
|
|
+ // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?!
|
|
+ Level world = ihopper.getLevel();
|
|
+ double d0 = ihopper.getX();
|
|
+ double d1 = ihopper.getY();
|
|
+ double d2 = ihopper.getZ();
|
|
+ AABB bb = new AABB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D);
|
|
+ return world.getEntities(ItemEntity.class, bb, Entity::isAlive);
|
|
+ // Paper end
|
|
}
|
|
|
|
@Nullable
|
|
public static Container getContainerAt(Level world, BlockPos blockposition) {
|
|
- return getContainerAt(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D);
|
|
+ return a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, true); // Paper
|
|
}
|
|
|
|
@Nullable
|
|
- public static Container getContainerAt(Level world, double x, double y, double z) {
|
|
+ public static Container getContainerAt(Level world, double x, double y, double z) { return a(world, x, y, z, false); } // Paper - overload to default false
|
|
+ public static Container a(Level world, double d0, double d1, double d2, boolean optimizeEntities) { // Paper
|
|
Object object = null;
|
|
- BlockPos blockposition = new BlockPos(x, y, z);
|
|
+ BlockPos blockposition = new BlockPos(d0, d1, d2);
|
|
if ( !world.hasChunkAt( blockposition ) ) return null; // Spigot
|
|
BlockState iblockdata = world.getBlockState(blockposition);
|
|
Block block = iblockdata.getBlock();
|
|
@@ -475,8 +684,8 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
}
|
|
}
|
|
|
|
- if (object == null) {
|
|
- List<Entity> list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR);
|
|
+ if (object == null && (!optimizeEntities || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper
|
|
+ List<Entity> list = world.getEntities((Entity) null, new AABB(d0 - 0.5D, d1 - 0.5D, d2 - 0.5D, d0 + 0.5D, d1 + 0.5D, d2 + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR);
|
|
|
|
if (!list.isEmpty()) {
|
|
object = (Container) list.get(world.random.nextInt(list.size()));
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
|
|
index 5ad419941ff1113ef29b9a4593f44d8f35ba8424..4525032232b5a89de13c6a46dc489a07428e3f21 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
|
|
@@ -97,12 +97,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
|
|
@Override
|
|
public boolean isEmpty() {
|
|
this.unpackLootTable((Player) null);
|
|
- return this.getItems().stream().allMatch(ItemStack::isEmpty);
|
|
+ // Paper start
|
|
+ for (ItemStack itemStack : this.getItems()) {
|
|
+ if (!itemStack.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+ return true;
|
|
}
|
|
|
|
@Override
|
|
public ItemStack getItem(int slot) {
|
|
- this.unpackLootTable((Player) null);
|
|
+ if (slot == 0) this.unpackLootTable((Player) null); // Paper
|
|
return (ItemStack) this.getItems().get(slot);
|
|
}
|
|
|