package net.minecraft.server.commands; import com.google.common.collect.Lists; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.SuggestionProvider; import java.util.Collection; import java.util.List; import java.util.Optional; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.commands.arguments.ResourceOrIdArgument; import net.minecraft.commands.arguments.SlotArgument; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.commands.arguments.coordinates.Vec3Argument; import net.minecraft.commands.arguments.item.ItemArgument; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.server.ReloadableServerRegistries; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.Container; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.SlotAccess; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.phys.Vec3; public class LootCommand { public static final SuggestionProvider SUGGEST_LOOT_TABLE = (commandContext, suggestionsBuilder) -> { ReloadableServerRegistries.Holder holder = commandContext.getSource().getServer().reloadableRegistries(); return SharedSuggestionProvider.suggestResource(holder.getKeys(Registries.LOOT_TABLE), suggestionsBuilder); }; private static final DynamicCommandExceptionType ERROR_NO_HELD_ITEMS = new DynamicCommandExceptionType( object -> Component.translatableEscape("commands.drop.no_held_items", object) ); private static final DynamicCommandExceptionType ERROR_NO_ENTITY_LOOT_TABLE = new DynamicCommandExceptionType( object -> Component.translatableEscape("commands.drop.no_loot_table.entity", object) ); private static final DynamicCommandExceptionType ERROR_NO_BLOCK_LOOT_TABLE = new DynamicCommandExceptionType( object -> Component.translatableEscape("commands.drop.no_loot_table.block", object) ); public static void register(CommandDispatcher dispatcher, CommandBuildContext context) { dispatcher.register( addTargets( Commands.literal("loot").requires(commandSourceStack -> commandSourceStack.hasPermission(2)), (argumentBuilder, dropConsumer) -> argumentBuilder.then( Commands.literal("fish") .then( Commands.argument("loot_table", ResourceOrIdArgument.lootTable(context)) .suggests(SUGGEST_LOOT_TABLE) .then( Commands.argument("pos", BlockPosArgument.blockPos()) .executes( commandContext -> dropFishingLoot( commandContext, ResourceOrIdArgument.getLootTable(commandContext, "loot_table"), BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), ItemStack.EMPTY, dropConsumer ) ) .then( Commands.argument("tool", ItemArgument.item(context)) .executes( commandContext -> dropFishingLoot( commandContext, ResourceOrIdArgument.getLootTable(commandContext, "loot_table"), BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), ItemArgument.getItem(commandContext, "tool").createItemStack(1, false), dropConsumer ) ) ) .then( Commands.literal("mainhand") .executes( commandContext -> dropFishingLoot( commandContext, ResourceOrIdArgument.getLootTable(commandContext, "loot_table"), BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), getSourceHandItem(commandContext.getSource(), EquipmentSlot.MAINHAND), dropConsumer ) ) ) .then( Commands.literal("offhand") .executes( commandContext -> dropFishingLoot( commandContext, ResourceOrIdArgument.getLootTable(commandContext, "loot_table"), BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), getSourceHandItem(commandContext.getSource(), EquipmentSlot.OFFHAND), dropConsumer ) ) ) ) ) ) .then( Commands.literal("loot") .then( Commands.argument("loot_table", ResourceOrIdArgument.lootTable(context)) .suggests(SUGGEST_LOOT_TABLE) .executes(commandContext -> dropChestLoot(commandContext, ResourceOrIdArgument.getLootTable(commandContext, "loot_table"), dropConsumer)) ) ) .then( Commands.literal("kill") .then( Commands.argument("target", EntityArgument.entity()) .executes(commandContext -> dropKillLoot(commandContext, EntityArgument.getEntity(commandContext, "target"), dropConsumer)) ) ) .then( Commands.literal("mine") .then( Commands.argument("pos", BlockPosArgument.blockPos()) .executes(commandContext -> dropBlockLoot(commandContext, BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), ItemStack.EMPTY, dropConsumer)) .then( Commands.argument("tool", ItemArgument.item(context)) .executes( commandContext -> dropBlockLoot( commandContext, BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), ItemArgument.getItem(commandContext, "tool").createItemStack(1, false), dropConsumer ) ) ) .then( Commands.literal("mainhand") .executes( commandContext -> dropBlockLoot( commandContext, BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), getSourceHandItem(commandContext.getSource(), EquipmentSlot.MAINHAND), dropConsumer ) ) ) .then( Commands.literal("offhand") .executes( commandContext -> dropBlockLoot( commandContext, BlockPosArgument.getLoadedBlockPos(commandContext, "pos"), getSourceHandItem(commandContext.getSource(), EquipmentSlot.OFFHAND), dropConsumer ) ) ) ) ) ) ); } private static > T addTargets(T builder, LootCommand.TailProvider tailProvider) { return builder.then( Commands.literal("replace") .then( Commands.literal("entity") .then( Commands.argument("entities", EntityArgument.entities()) .then( tailProvider.construct( Commands.argument("slot", SlotArgument.slot()), (commandContext, list, callback) -> entityReplace( EntityArgument.getEntities(commandContext, "entities"), SlotArgument.getSlot(commandContext, "slot"), list.size(), list, callback ) ) .then( tailProvider.construct( Commands.argument("count", IntegerArgumentType.integer(0)), (commandContext, list, callback) -> entityReplace( EntityArgument.getEntities(commandContext, "entities"), SlotArgument.getSlot(commandContext, "slot"), IntegerArgumentType.getInteger(commandContext, "count"), list, callback ) ) ) ) ) ) .then( Commands.literal("block") .then( Commands.argument("targetPos", BlockPosArgument.blockPos()) .then( tailProvider.construct( Commands.argument("slot", SlotArgument.slot()), (commandContext, list, callback) -> blockReplace( commandContext.getSource(), BlockPosArgument.getLoadedBlockPos(commandContext, "targetPos"), SlotArgument.getSlot(commandContext, "slot"), list.size(), list, callback ) ) .then( tailProvider.construct( Commands.argument("count", IntegerArgumentType.integer(0)), (commandContext, list, callback) -> blockReplace( commandContext.getSource(), BlockPosArgument.getLoadedBlockPos(commandContext, "targetPos"), IntegerArgumentType.getInteger(commandContext, "slot"), IntegerArgumentType.getInteger(commandContext, "count"), list, callback ) ) ) ) ) ) ) .then( Commands.literal("insert") .then( tailProvider.construct( Commands.argument("targetPos", BlockPosArgument.blockPos()), (commandContext, list, callback) -> blockDistribute( commandContext.getSource(), BlockPosArgument.getLoadedBlockPos(commandContext, "targetPos"), list, callback ) ) ) ) .then( Commands.literal("give") .then( tailProvider.construct( Commands.argument("players", EntityArgument.players()), (commandContext, list, callback) -> playerGive(EntityArgument.getPlayers(commandContext, "players"), list, callback) ) ) ) .then( Commands.literal("spawn") .then( tailProvider.construct( Commands.argument("targetPos", Vec3Argument.vec3()), (commandContext, list, callback) -> dropInWorld(commandContext.getSource(), Vec3Argument.getVec3(commandContext, "targetPos"), list, callback) ) ) ); } private static Container getContainer(CommandSourceStack source, BlockPos pos) throws CommandSyntaxException { BlockEntity blockEntity = source.getLevel().getBlockEntity(pos); if (!(blockEntity instanceof Container)) { throw ItemCommands.ERROR_TARGET_NOT_A_CONTAINER.create(pos.getX(), pos.getY(), pos.getZ()); } else { return (Container)blockEntity; } } private static int blockDistribute(CommandSourceStack source, BlockPos pos, List items, LootCommand.Callback callback) throws CommandSyntaxException { Container container = getContainer(source, pos); List list = Lists.newArrayListWithCapacity(items.size()); for (ItemStack itemStack : items) { if (distributeToContainer(container, itemStack.copy())) { container.setChanged(); list.add(itemStack); } } callback.accept(list); return list.size(); } private static boolean distributeToContainer(Container container, ItemStack item) { boolean bl = false; for (int i = 0; i < container.getContainerSize() && !item.isEmpty(); i++) { ItemStack itemStack = container.getItem(i); if (container.canPlaceItem(i, item)) { if (itemStack.isEmpty()) { container.setItem(i, item); bl = true; break; } if (canMergeItems(itemStack, item)) { int j = item.getMaxStackSize() - itemStack.getCount(); int k = Math.min(item.getCount(), j); item.shrink(k); itemStack.grow(k); bl = true; } } } return bl; } private static int blockReplace(CommandSourceStack source, BlockPos pos, int slot, int numSlots, List items, LootCommand.Callback callback) throws CommandSyntaxException { Container container = getContainer(source, pos); int i = container.getContainerSize(); if (slot >= 0 && slot < i) { List list = Lists.newArrayListWithCapacity(items.size()); for (int j = 0; j < numSlots; j++) { int k = slot + j; ItemStack itemStack = j < items.size() ? (ItemStack)items.get(j) : ItemStack.EMPTY; if (container.canPlaceItem(k, itemStack)) { container.setItem(k, itemStack); list.add(itemStack); } } callback.accept(list); return list.size(); } else { throw ItemCommands.ERROR_TARGET_INAPPLICABLE_SLOT.create(slot); } } private static boolean canMergeItems(ItemStack first, ItemStack second) { return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second); } private static int playerGive(Collection targets, List items, LootCommand.Callback callback) throws CommandSyntaxException { List list = Lists.newArrayListWithCapacity(items.size()); for (ItemStack itemStack : items) { for (ServerPlayer serverPlayer : targets) { if (serverPlayer.getInventory().add(itemStack.copy())) { list.add(itemStack); } } } callback.accept(list); return list.size(); } private static void setSlots(Entity target, List items, int startSlot, int numSlots, List setItems) { for (int i = 0; i < numSlots; i++) { ItemStack itemStack = i < items.size() ? (ItemStack)items.get(i) : ItemStack.EMPTY; SlotAccess slotAccess = target.getSlot(startSlot + i); if (slotAccess != SlotAccess.NULL && slotAccess.set(itemStack.copy())) { setItems.add(itemStack); } } } private static int entityReplace(Collection targets, int startSlot, int numSlots, List items, LootCommand.Callback callback) throws CommandSyntaxException { List list = Lists.newArrayListWithCapacity(items.size()); for (Entity entity : targets) { if (entity instanceof ServerPlayer serverPlayer) { setSlots(entity, items, startSlot, numSlots, list); serverPlayer.containerMenu.broadcastChanges(); } else { setSlots(entity, items, startSlot, numSlots, list); } } callback.accept(list); return list.size(); } private static int dropInWorld(CommandSourceStack source, Vec3 pos, List items, LootCommand.Callback callback) throws CommandSyntaxException { ServerLevel serverLevel = source.getLevel(); items.forEach(itemStack -> { ItemEntity itemEntity = new ItemEntity(serverLevel, pos.x, pos.y, pos.z, itemStack.copy()); itemEntity.setDefaultPickUpDelay(); serverLevel.addFreshEntity(itemEntity); }); callback.accept(items); return items.size(); } private static void callback(CommandSourceStack source, List items) { if (items.size() == 1) { ItemStack itemStack = (ItemStack)items.get(0); source.sendSuccess(() -> Component.translatable("commands.drop.success.single", itemStack.getCount(), itemStack.getDisplayName()), false); } else { source.sendSuccess(() -> Component.translatable("commands.drop.success.multiple", items.size()), false); } } private static void callback(CommandSourceStack source, List items, ResourceKey lootTable) { if (items.size() == 1) { ItemStack itemStack = (ItemStack)items.get(0); source.sendSuccess( () -> Component.translatable( "commands.drop.success.single_with_table", itemStack.getCount(), itemStack.getDisplayName(), Component.translationArg(lootTable.location()) ), false ); } else { source.sendSuccess( () -> Component.translatable("commands.drop.success.multiple_with_table", items.size(), Component.translationArg(lootTable.location())), false ); } } private static ItemStack getSourceHandItem(CommandSourceStack source, EquipmentSlot slot) throws CommandSyntaxException { Entity entity = source.getEntityOrException(); if (entity instanceof LivingEntity) { return ((LivingEntity)entity).getItemBySlot(slot); } else { throw ERROR_NO_HELD_ITEMS.create(entity.getDisplayName()); } } private static int dropBlockLoot(CommandContext context, BlockPos pos, ItemStack tool, LootCommand.DropConsumer dropConsumer) throws CommandSyntaxException { CommandSourceStack commandSourceStack = context.getSource(); ServerLevel serverLevel = commandSourceStack.getLevel(); BlockState blockState = serverLevel.getBlockState(pos); BlockEntity blockEntity = serverLevel.getBlockEntity(pos); Optional> optional = blockState.getBlock().getLootTable(); if (optional.isEmpty()) { throw ERROR_NO_BLOCK_LOOT_TABLE.create(blockState.getBlock().getName()); } else { LootParams.Builder builder = new LootParams.Builder(serverLevel) .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) .withParameter(LootContextParams.BLOCK_STATE, blockState) .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity) .withOptionalParameter(LootContextParams.THIS_ENTITY, commandSourceStack.getEntity()) .withParameter(LootContextParams.TOOL, tool); List list = blockState.getDrops(builder); return dropConsumer.accept(context, list, listx -> callback(commandSourceStack, listx, (ResourceKey)optional.get())); } } private static int dropKillLoot(CommandContext context, Entity entity, LootCommand.DropConsumer dropConsumer) throws CommandSyntaxException { Optional> optional = entity.getLootTable(); if (optional.isEmpty()) { throw ERROR_NO_ENTITY_LOOT_TABLE.create(entity.getDisplayName()); } else { CommandSourceStack commandSourceStack = context.getSource(); LootParams.Builder builder = new LootParams.Builder(commandSourceStack.getLevel()); Entity entity2 = commandSourceStack.getEntity(); if (entity2 instanceof Player player) { builder.withParameter(LootContextParams.LAST_DAMAGE_PLAYER, player); } builder.withParameter(LootContextParams.DAMAGE_SOURCE, entity.damageSources().magic()); builder.withOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY, entity2); builder.withOptionalParameter(LootContextParams.ATTACKING_ENTITY, entity2); builder.withParameter(LootContextParams.THIS_ENTITY, entity); builder.withParameter(LootContextParams.ORIGIN, commandSourceStack.getPosition()); LootParams lootParams = builder.create(LootContextParamSets.ENTITY); LootTable lootTable = commandSourceStack.getServer().reloadableRegistries().getLootTable((ResourceKey)optional.get()); List list = lootTable.getRandomItems(lootParams); return dropConsumer.accept(context, list, listx -> callback(commandSourceStack, listx, (ResourceKey)optional.get())); } } private static int dropChestLoot(CommandContext context, Holder lootTable, LootCommand.DropConsumer dropCOnsimer) throws CommandSyntaxException { CommandSourceStack commandSourceStack = context.getSource(); LootParams lootParams = new LootParams.Builder(commandSourceStack.getLevel()) .withOptionalParameter(LootContextParams.THIS_ENTITY, commandSourceStack.getEntity()) .withParameter(LootContextParams.ORIGIN, commandSourceStack.getPosition()) .create(LootContextParamSets.CHEST); return drop(context, lootTable, lootParams, dropCOnsimer); } private static int dropFishingLoot( CommandContext context, Holder lootTable, BlockPos pos, ItemStack tool, LootCommand.DropConsumer dropConsumet ) throws CommandSyntaxException { CommandSourceStack commandSourceStack = context.getSource(); LootParams lootParams = new LootParams.Builder(commandSourceStack.getLevel()) .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) .withParameter(LootContextParams.TOOL, tool) .withOptionalParameter(LootContextParams.THIS_ENTITY, commandSourceStack.getEntity()) .create(LootContextParamSets.FISHING); return drop(context, lootTable, lootParams, dropConsumet); } private static int drop(CommandContext context, Holder lootTable, LootParams params, LootCommand.DropConsumer dropConsumer) throws CommandSyntaxException { CommandSourceStack commandSourceStack = context.getSource(); List list = lootTable.value().getRandomItems(params); return dropConsumer.accept(context, list, listx -> callback(commandSourceStack, listx)); } @FunctionalInterface interface Callback { void accept(List list) throws CommandSyntaxException; } @FunctionalInterface interface DropConsumer { int accept(CommandContext commandContext, List list, LootCommand.Callback callback) throws CommandSyntaxException; } @FunctionalInterface interface TailProvider { ArgumentBuilder construct(ArgumentBuilder argumentBuilder, LootCommand.DropConsumer dropConsumer); } }