Merge remote-tracking branch 'public/main' into dev/snapshot

This commit is contained in:
Jason Penilla 2025-11-12 13:47:01 -07:00
commit 05145d0981
No known key found for this signature in database
GPG Key ID: 0E75A301420E48F8
6 changed files with 82 additions and 84 deletions

View File

@ -476,6 +476,17 @@ If you use Maven to build your plugin:
If you use Windows and don't usually build using WSL, you might not need to
do this.
## Tips and Tricks
### IntelliJ IDEA
- Under `Settings > Appearance & Behavior > System Settings`, disable
`Sync external changes: Periodically when the IDE is inactive (experimental)`.
When disabled, the IDE will not attempt to reindex files while patches are applying
unless you interact with the IDE during the process. This avoids severe slowdowns and freezes.
- Under `Settings > Appearance & Behavior > System Settings`, you may also want to
disable `Reopen projects on startup` to avoid freeze loops.
## Frequently Asked Questions
### My commit doesn't need a build, what do I do?

View File

@ -422,7 +422,7 @@ public class ParticleBuilder implements Cloneable {
/**
* Sets the particle Color.
* Only valid for particles with a data type of {@link Color} or {@link Particle.DustOptions}.
* Only valid for particles with a data type of {@link Color}, {@link Particle.DustOptions} or {@link Particle.Spell}.
*
* @param color the new particle color
* @return a reference to this object.
@ -435,33 +435,37 @@ public class ParticleBuilder implements Cloneable {
}
/**
* Sets the particle Color and size.
* Only valid for particles with a data type of {@link Particle.DustOptions}.
* Sets the particle Color and size or power.
* Only valid for particles with a data type of {@link Particle.DustOptions} or {@link Particle.Spell}.
*
* @param color the new particle color
* @param size the size of the particle
* @param value the size or power of the particle
* @return a reference to this object.
*/
public ParticleBuilder color(final @Nullable Color color, final float size) {
if (this.particle.getDataType() != Particle.DustOptions.class && color != null) {
throw new IllegalStateException("The combination of Color and size cannot be set on this particle type.");
public ParticleBuilder color(final @Nullable Color color, final float value) {
if (this.particle.getDataType() != Particle.DustOptions.class && this.particle.getDataType() != Particle.Spell.class && color != null) {
throw new IllegalStateException("The combination of Color and float value cannot be set on this particle type.");
}
// We don't officially support reusing these objects, but here we go
if (color == null) {
if (this.data instanceof Particle.DustOptions) {
if (this.data instanceof Particle.DustOptions || this.data instanceof Particle.Spell) {
return this.data(null);
} else {
return this;
}
}
return this.data(new Particle.DustOptions(color, size));
if (this.particle.getDataType() == Particle.DustOptions.class) {
return this.data(new Particle.DustOptions(color, value));
} else {
return this.data(new Particle.Spell(color, value));
}
}
/**
* Sets the particle Color.
* Only valid for particles with a data type of {@link Color} or {@link Particle.DustOptions}.
* Only valid for particles with a data type of {@link Color}, {@link Particle.DustOptions} or {@link Particle.Spell}.
*
* @param r red color component
* @param g green color component
@ -474,7 +478,7 @@ public class ParticleBuilder implements Cloneable {
/**
* Sets the particle Color.
* Only valid for particles with a data type of {@link Color} or {@link Particle.DustOptions}.
* Only valid for particles with a data type of {@link Color}, {@link Particle.DustOptions} or {@link Particle.Spell}.
* <p>
* This method detects if the provided color integer is in RGB or ARGB format.
* If the alpha channel is zero, it treats the color as RGB. Otherwise, it treats it as ARGB.
@ -493,7 +497,7 @@ public class ParticleBuilder implements Cloneable {
/**
* Sets the particle Color.
* Only valid for particles with a data type of {@link Color} or {@link Particle.DustOptions}.
* Only valid for particles with a data type of {@link Color}, {@link Particle.DustOptions} or {@link Particle.Spell}.
*
* @param a alpha color component
* @param r red color component

View File

@ -3490,11 +3490,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
public void updateCommands();
/**
* Open a {@link Material#WRITTEN_BOOK} for a Player
* Open an ItemStack with {@link io.papermc.paper.datacomponent.DataComponentTypes#WRITTEN_BOOK_CONTENT} for a Player
*
* @param book The book to open for this player
* @param book the item with written book content to open for this player
* @throws IllegalArgumentException if the ItemStack is null, empty or doesn't have a {@link io.papermc.paper.datacomponent.DataComponentTypes#WRITTEN_BOOK_CONTENT}
*/
public void openBook(ItemStack book);
void openBook(ItemStack book);
/**
* Open a Sign for editing by the Player.

View File

@ -16,7 +16,7 @@ This lets us get faster foreach iteration, as well as avoids map lookups on
the values when needed.
diff --git a/net/minecraft/world/level/pathfinder/PathFinder.java b/net/minecraft/world/level/pathfinder/PathFinder.java
index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..f90c2bb95e304bd6b9b8539d89f38d2d718893d0 100644
index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..5894260481ea395c26a5b506e38530f7b2032b33 100644
--- a/net/minecraft/world/level/pathfinder/PathFinder.java
+++ b/net/minecraft/world/level/pathfinder/PathFinder.java
@@ -48,8 +48,12 @@ public class PathFinder {
@ -52,15 +52,14 @@ index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..f90c2bb95e304bd6b9b8539d89f38d2d
this.openSet.clear();
this.openSet.insert(node);
boolean asBoolean = this.captureDebug.getAsBoolean();
- Set<Node> set1 = asBoolean ? new HashSet<>() : Set.of();
+ // Set<Node> set1 = asBoolean ? new HashSet<>() : Set.of(); // Paper - unused debug
Set<Node> set1 = asBoolean ? new HashSet<>() : Set.of();
int i = 0;
- Set<Target> set2 = Sets.newHashSetWithExpectedSize(set.size());
+ List<Map.Entry<Target, BlockPos>> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection
int i1 = (int)(this.maxVisitedNodes * maxVisitedNodesMultiplier);
while (!this.openSet.isEmpty()) {
@@ -81,20 +85,21 @@ public class PathFinder {
@@ -81,14 +85,18 @@ public class PathFinder {
Node node1 = this.openSet.pop();
node1.closed = true;
@ -82,13 +81,7 @@ index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..f90c2bb95e304bd6b9b8539d89f38d2d
break;
}
- if (asBoolean) {
- set1.add(node1);
- }
if (!(node1.distanceTo(node) >= maxRange)) {
int neighbors = this.nodeEvaluator.getNeighbors(this.neighbors, node1);
@@ -107,7 +112,7 @@ public class PathFinder {
@@ -107,7 +115,7 @@ public class PathFinder {
if (node2.walkedDistance < maxRange && (!node2.inOpenSet() || f1 < node2.g)) {
node2.cameFrom = node1;
node2.g = f1;
@ -97,7 +90,7 @@ index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..f90c2bb95e304bd6b9b8539d89f38d2d
if (node2.inOpenSet()) {
this.openSet.changeCost(node2, node2.g + node2.h);
} else {
@@ -119,34 +124,34 @@ public class PathFinder {
@@ -119,34 +127,41 @@ public class PathFinder {
}
}
@ -108,13 +101,6 @@ index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..f90c2bb95e304bd6b9b8539d89f38d2d
- : set.stream()
- .map(pathfinder -> this.reconstructPath(pathfinder.getBestNode(), targets.get(pathfinder), false))
- .min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount));
- profilerFiller.pop();
- if (optional.isEmpty()) {
- return null;
- } else {
- Path path = optional.get();
- if (asBoolean) {
- path.setDebug(this.openSet.getHeap(), set1.toArray(Node[]::new), set);
+ // Paper start - Perf: remove streams and optimize collection
+ Path best = null;
+ boolean entryListIsEmpty = entryList.isEmpty();
@ -125,11 +111,24 @@ index 1a9d8e6dd55ce30aca1c65b32417d61fd0a10f84..f90c2bb95e304bd6b9b8539d89f38d2d
+ Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !entryListIsEmpty);
+ if (best == null || comparator.compare(path, best) < 0) {
+ best = path;
+ }
+ }
profilerFiller.pop();
- if (optional.isEmpty()) {
- return null;
- } else {
- Path path = optional.get();
- if (asBoolean) {
- path.setDebug(this.openSet.getHeap(), set1.toArray(Node[]::new), set);
+ if(asBoolean && best != null) {
+ Set<Target> set = Sets.newHashSet();
+ for(Map.Entry<Target, BlockPos> entry : positions) {
+ set.add(entry.getKey());
}
-
- return path;
+ best.setDebug(this.openSet.getHeap(), set1.toArray(Node[]::new), set);
}
+ profilerFiller.pop();
+ return best;
+ // Paper end - Perf: remove streams and optimize collection
}

View File

@ -17,7 +17,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.sound.Sound;
@ -41,7 +40,6 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.locale.Language;
import net.minecraft.nbt.CompoundTag;
@ -55,13 +53,10 @@ import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.Identifier;
import net.minecraft.server.network.Filterable;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.BossEvent;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.WrittenBookContent;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
@ -333,28 +328,6 @@ public final class PaperAdventure {
}
}
// Book
public static ItemStack asItemStack(final Book book, final Locale locale) {
final ItemStack item = new ItemStack(net.minecraft.world.item.Items.WRITTEN_BOOK, 1);
item.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(
Filterable.passThrough(validateField(asPlain(book.title(), locale), WrittenBookContent.TITLE_MAX_LENGTH, "title")),
asPlain(book.author(), locale),
0,
book.pages().stream().map(c -> Filterable.passThrough(PaperAdventure.asVanilla(c))).toList(), // TODO should we validate length?
false
));
return item;
}
private static String validateField(final String content, final int length, final String name) {
final int actual = content.length();
if (actual > length) {
throw new IllegalArgumentException("Field '" + name + "' has a maximum length of " + length + " but was passed '" + content + "', which was " + actual + " characters long.");
}
return content;
}
// Sounds
public static SoundSource asVanilla(final Sound.Source source) {

View File

@ -11,6 +11,8 @@ import com.mojang.logging.LogUtils;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.connection.PlayerGameConnection;
import io.papermc.paper.connection.PluginMessageBridgeImpl;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.WrittenBookContent;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.PaperDialog;
import io.papermc.paper.entity.LookAnchor;
@ -48,6 +50,7 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import net.kyori.adventure.dialog.DialogLike;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.pointer.PointersSupplier;
import net.kyori.adventure.util.TriState;
import net.md_5.bungee.api.chat.BaseComponent;
@ -69,6 +72,7 @@ import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket;
import net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
@ -76,6 +80,7 @@ import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket;
import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
import net.minecraft.network.protocol.game.ClientboundOpenBookPacket;
import net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
@ -90,6 +95,7 @@ import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPac
import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket;
import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket;
import net.minecraft.network.protocol.game.ClientboundSetHealthPacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerInventoryPacket;
import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket;
@ -110,6 +116,7 @@ import net.minecraft.server.permissions.PermissionLevel;
import net.minecraft.server.players.UserWhiteListEntry;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
@ -203,6 +210,7 @@ import org.bukkit.event.player.PlayerShowEntityEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.InventoryView.Property;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ItemType;
import org.bukkit.map.MapCursor;
import org.bukkit.map.MapView;
import org.bukkit.metadata.MetadataValue;
@ -2711,17 +2719,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa
this.server.getServer().getCommands().sendCommands(this.getHandle());
}
@Override
public void openBook(ItemStack book) {
Preconditions.checkArgument(book != null, "ItemStack cannot be null");
Preconditions.checkArgument(book.getType() == Material.WRITTEN_BOOK, "ItemStack Material (%s) must be Material.WRITTEN_BOOK", book.getType());
ItemStack hand = this.getInventory().getItemInMainHand();
this.getInventory().setItemInMainHand(book);
this.getHandle().openItemGui(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(book), net.minecraft.world.InteractionHand.MAIN_HAND);
this.getInventory().setItemInMainHand(hand);
}
@Override
public void openSign(@NonNull Sign sign, @NonNull Side side) {
CraftSign.openSign(sign, this, side);
@ -2960,17 +2957,30 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa
}
@Override
public void openBook(final net.kyori.adventure.inventory.Book book) {
final java.util.Locale locale = this.getHandle().adventure$locale;
final net.minecraft.world.item.ItemStack item = io.papermc.paper.adventure.PaperAdventure.asItemStack(book, locale);
final ServerPlayer player = this.getHandle();
final ServerGamePacketListenerImpl connection = player.connection;
final net.minecraft.world.entity.player.Inventory inventory = player.getInventory();
final int slot = inventory.getNonEquipmentItems().size() + inventory.getSelectedSlot();
final int stateId = getHandle().containerMenu.getStateId();
connection.send(new net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket(0, stateId, slot, item));
connection.send(new net.minecraft.network.protocol.game.ClientboundOpenBookPacket(net.minecraft.world.InteractionHand.MAIN_HAND));
connection.send(new net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket(0, stateId, slot, inventory.getSelectedItem()));
public void openBook(ItemStack book) {
Preconditions.checkArgument(book != null, "ItemStack cannot be null");
Preconditions.checkArgument(book.hasData(DataComponentTypes.WRITTEN_BOOK_CONTENT), "ItemStack must have a 'written_book_content' component");
final ItemStack previousItem = this.getInventory().getItemInMainHand();
this.getInventory().setItemInMainHand(book);
this.getHandle().openItemGui(CraftItemStack.asNMSCopy(book), InteractionHand.MAIN_HAND);
this.getInventory().setItemInMainHand(previousItem);
}
@Override
public void openBook(final Book book) {
final ItemStack mutatedItem = ItemType.WRITTEN_BOOK.createItemStack(); // dummy item
mutatedItem.setData(DataComponentTypes.WRITTEN_BOOK_CONTENT, WrittenBookContent.writtenBookContent("", "").addPages(book.pages()));
final net.minecraft.world.item.ItemStack selectedItem = this.getHandle().getInventory().getSelectedItem();
final int slot = this.getHandle().getInventory().getSelectedSlot();
this.getHandle().connection.send(new ClientboundBundlePacket(
List.of(
new ClientboundSetPlayerInventoryPacket(slot, CraftItemStack.unwrap(mutatedItem)),
new ClientboundOpenBookPacket(InteractionHand.MAIN_HAND),
new ClientboundSetPlayerInventoryPacket(slot, selectedItem)
)
));
}
@Override