Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Player#give #11995

Merged
merged 5 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.papermc.paper.entity;

import java.util.Collection;
import org.bukkit.entity.Item;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;

/**
* A result type used by {@link org.bukkit.entity.Player#give(ItemStack...)} and its overloads.
*/
@NullMarked
public interface PlayerGiveResult {

/**
* A collection of itemstacks that were not added to the player's inventory as they did not fit.
* The collection is derived from the collections of items to add by creating copies of each stack that was not
* fully added to the inventory and assigning the non-added count as their amount.
* <p>
* Itemstacks found here *may* also be found as item entities in the {@link #drops()} collection, as the
* give logic may have dropped them.
*
* @return the unmodifiable collection of itemstacks that are leftover as they could not be added. Each element is a
* copy of the input stack they are derived from.
*/
@Unmodifiable
Collection<ItemStack> leftovers();

/**
* A collection of item entities dropped as a result of this call to {@link org.bukkit.entity.Player#give(ItemStack...)}.
* The item entities contained here are not guaranteed to match the {@link #leftovers()} as plugins may cancel the
* spawning of item entities.
*
* @return the unmodifiable collection of dropped item entities.
*/
@Unmodifiable
Collection<Item> drops();

}
36 changes: 36 additions & 0 deletions paper-api/src/main/java/org/bukkit/entity/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import io.papermc.paper.entity.LookAnchor;
import io.papermc.paper.entity.PlayerGiveResult;
import org.bukkit.BanEntry;
import org.bukkit.DyeColor;
import org.bukkit.Effect;
Expand Down Expand Up @@ -3892,4 +3894,38 @@ default boolean isChunkSent(@NotNull org.bukkit.Chunk chunk) {
*/
void sendEntityEffect(org.bukkit.@NotNull EntityEffect effect, @NotNull Entity target);
// Paper end - entity effect API

/**
* Gives the player the items following full vanilla logic,
* making the player drop the items that did not fit into
* the inventory.
*
* @param items the items to give.
* @return the result of this method, holding leftovers and spawned items.
*/
default @NotNull PlayerGiveResult give(@NotNull final ItemStack @NotNull ... items) {
return this.give(List.of(items));
}

/**
* Gives the player those items following full vanilla logic,
* making the player drop the items that did not fit into
* the inventory.
*
* @param items the items to give
* @return the result of this method, holding leftovers and spawned items.
*/
default @NotNull PlayerGiveResult give(@NotNull final Collection<@NotNull ItemStack> items) {
return this.give(items, true);
}

/**
* Gives the player those items following full vanilla logic.
*
* @param items the items to give
* @param dropIfFull whether the player should drop items that
* did not fit the inventory
* @return the result of this method, holding leftovers and spawned items.
*/
@NotNull PlayerGiveResult give(@NotNull Collection<@NotNull ItemStack> items, boolean dropIfFull);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.papermc.paper.entity;

import java.util.Collection;
import java.util.Collections;
import org.bukkit.entity.Item;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;

@NullMarked
public record PaperPlayerGiveResult(
@Unmodifiable Collection<ItemStack> leftovers,
@Unmodifiable Collection<Item> drops
) implements PlayerGiveResult {

public static final PlayerGiveResult EMPTY = new PaperPlayerGiveResult(
Collections.emptyList(), Collections.emptyList()
);

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package org.bukkit.craftbukkit.entity;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.mojang.authlib.GameProfile;
import com.mojang.datafixers.util.Pair;
import io.netty.buffer.Unpooled;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.entity.LookAnchor;
import io.papermc.paper.entity.PaperPlayerGiveResult;
import io.papermc.paper.entity.PlayerGiveResult;
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
import it.unimi.dsi.fastutil.shorts.ShortSet;
import java.io.ByteArrayOutputStream;
Expand All @@ -21,6 +24,7 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -101,6 +105,7 @@
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeMap;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.GameType;
Expand Down Expand Up @@ -176,6 +181,7 @@
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerExpCooldownChangeEvent;
Expand Down Expand Up @@ -3545,4 +3551,35 @@ public void sendEntityEffect(final org.bukkit.EntityEffect effect, final org.buk
this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData()));
}
// Paper end - entity effect API

@Override
public @NotNull PlayerGiveResult give(@NotNull final Collection<@NotNull ItemStack> items, final boolean dropIfFull) {
Preconditions.checkNotNull(items, "items cannot be null");
if (items.isEmpty()) return PaperPlayerGiveResult.EMPTY; // Early opt out for empty input.

// Validate all items before attempting to spawn any.
for (final ItemStack item : items) {
Preconditions.checkNotNull(item, "ItemStack cannot be null");
Preconditions.checkArgument(!item.isEmpty(), "ItemStack cannot be empty");
Preconditions.checkArgument(item.getAmount() <= item.getMaxStackSize(), "ItemStack amount cannot be greater than its max stack size");
masmc05 marked this conversation as resolved.
Show resolved Hide resolved
}

final ServerPlayer handle = this.getHandle();
final ImmutableList.Builder<Item> drops = ImmutableList.builder();
final ImmutableList.Builder<ItemStack> leftovers = ImmutableList.builder();
for (final ItemStack item : items) {
final net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(item);
final boolean added = handle.getInventory().add(nmsStack);
if (added && nmsStack.isEmpty()) continue; // Item was fully added, neither a drop nor a leftover is needed.

leftovers.add(CraftItemStack.asBukkitCopy(nmsStack)); // Insert copy to avoid mutation to the dropped item from affecting leftovers
if (!dropIfFull) continue;

final ItemEntity entity = handle.drop(nmsStack, false, true, false);
if (entity != null) drops.add((Item) entity.getBukkitEntity());
}

handle.containerMenu.broadcastChanges();
return new PaperPlayerGiveResult(leftovers.build(), drops.build());
}
}
Loading