Compare commits

..
5 Commits
Author SHA1 Message Date
Nassim Jahnke 806b0db07c Update release workflow
... but don't actually include the branch yet
2026-10-23 18:48:28 +02:00
Nassim Jahnke e13cf8870f Add new sound group method 2026-10-23 18:35:20 +02:00
Nassim Jahnke cb1e4cc603 Update mache 2026-10-23 18:23:59 +02:00
Nassim Jahnke 2fdccd0ddf Fix most compile errors 2026-10-20 20:51:52 +02:00
Nassim Jahnke 755ecce274 26.4 dev 2026-10-20 20:16:16 +02:00
400 changed files with 4219 additions and 6150 deletions

No files matched your search

+2 -25
View File
@@ -14,18 +14,11 @@
<property name="messageKey" value="annotation\.same\.line"/>
<property name="arguments" value="^(?!(${type_use_annotations}))"/>
</module>
<module name="io.papermc.checkstyle.filters.BetterSuppressionSingleFilter">
<!--Allows @deprecated on packages. Not in the javadoc spec (javadoc warns), but it is still rendered in the deprecation block and list-->
<property name="checks" value="InappropriateJavadocBlockTagsOnPackage"/>
<property name="messageKey" value="javadoc\.inappropriate\.tag"/>
<property name="arguments" value="^deprecated$"/>
</module>
<module name="SuppressWarningsFilter"/>
<!--Javadoc Comments-->
<module name="JavadocPackage"/>
<!--Misc-->
<module name="LineEnding"/>
<module name="NewlineAtEndOfFile"/>
<!--Whitespace-->
<module name="FileTabCharacter"/>
@@ -45,7 +38,6 @@
</module>
<module name="AnnotationUseStyle"/>
<module name="MissingDeprecated"/>
<module name="MissingOverrideOnRecordAccessor"/>
<!--Block Checks-->
<module name="AvoidNestedBlocks"/>
@@ -63,9 +55,7 @@
<!--Coding-->
<module name="AvoidDoubleBraceInitialization"/>
<module name="AvoidNoArgumentSuperConstructorCall"/>
<module name="ConstructorsDeclarationGrouping">
<property name="orderByIncreasingParameterCount" value="true"/>
</module>
<module name="ConstructorsDeclarationGrouping"/>
<module name="CovariantEquals"/>
<module name="DeclarationOrder">
<property name="ignoreConstructors" value="true"/>
@@ -73,7 +63,6 @@
<module name="DefaultComesLast"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="ExpressionOverBlockLambda"/>
<module name="FallThrough"/>
<!-- Use non-final variables as an identifier of old code for now -->
<!--<module name="FinalLocalVariable">
@@ -97,9 +86,6 @@
<module name="UnnecessarySemicolonInTryWithResources"/>
<module name="UnusedCatchParameterShouldBeUnnamed"/>
<module name="UnusedLocalVariable"/>
<module name="UnusedPrivateField"/>
<module name="UnusedTryResourceShouldBeUnnamed"/>
<module name="UseEnhancedSwitch"/>
<module name="WhenShouldBeUsed"/>
<!--Headers--> <!--N/A-->
@@ -128,13 +114,9 @@
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @see, @deprecated, @hidden"/>
</module>
<module name="InappropriateJavadocBlockTagsOnField"/>
<module name="InappropriateJavadocBlockTagsOnPackage"/>
<module name="InappropriateJavadocBlockTagsOnType"/>
<module name="InvalidJavadocPosition"/>
<module name="JavadocBlockTagLocation"/>
<module name="JavadocContentLocation"/>
<module name="JavadocEndCommentDelimiter"/>
<module name="JavadocLeadingAsteriskAlign"/>
<module name="JavadocMethod">
<!--checks all, but doesn't require. If we have a doc, it should be valid-->
@@ -142,11 +124,11 @@
</module>
<module name="JavadocMissingLeadingAsterisk"/>
<module name="JavadocMissingWhitespaceAfterAsterisk"/>
<module name="JavadocStyle"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
<module name="JavadocTagContinuationIndentation"/>
<module name="JavadocType"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
<module name="NonEmptyAtclauseDescription"/>
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
<module name="SummaryJavadoc"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
<!--Metrics--> <!--N/A-->
@@ -154,14 +136,12 @@
<module name="ArrayTypeStyle"/>
<module name="AvoidEscapedUnicodeCharacters"/>
<module name="CommentsIndentation"/>
<module name="HexLiteralCase"/>
<!-- Overlaps with the configured FinalLocalVariable, but is more strict -->
<!--<module name="FinalParameters">
<property name="tokens" value="METHOD_DEF,CTOR_DEF,LITERAL_CATCH,FOR_EACH_CLAUSE,PATTERN_VARIABLE_DEF"/>
</module>-->
<module name="NoCodeInFile"/>
<module name="NumericalPrefixesInfixesSuffixesCharacterCase"/>
<module name="OuterTypeFilename"/>
<module name="UpperEll"/>
@@ -200,7 +180,6 @@
<!--Size Violations--> <!--N/A-->
<!--Whitespace-->
<module name="ArrayBracketNoWhitespace"/>
<module name="EmptyForInitializerPad"/>
<module name="EmptyForIteratorPad"/>
<module name="EmptyLineSeparator">
@@ -218,11 +197,9 @@
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<module name="WhitespaceBeforeEmptyBody"/>
<!--Custom-->
<module name="JavadocAlignParameterDescription"/>
<module name="JavadocParamTagOrder"/>
<module name="NullabilityAnnotations"/>
<module name="RedundantNullability"/>
<module name="UnnecessaryFullyQualifiedImport"/>
@@ -1,4 +1,4 @@
name: Release 26.3
name: Release 26.4
on:
workflow_dispatch:
@@ -11,7 +11,7 @@ permissions:
concurrency:
cancel-in-progress: false
group: release-26.3
group: release-26.4
jobs:
release:
+3 -3
View File
@@ -1,4 +1,4 @@
Paper [![Version](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fartifactory.papermc.io%2Fartifactory%2Funiverse%2Fio%2Fpapermc%2Fpaper%2Fpaper-api%2Fmaven-metadata.xml&strategy=highestVersion&filter=26.3.*&label=version&color=%23344ceb
Paper [![Version](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fartifactory.papermc.io%2Fartifactory%2Funiverse%2Fio%2Fpapermc%2Fpaper%2Fpaper-api%2Fmaven-metadata.xml&strategy=highestVersion&filter=26.4.*&label=version&color=%23344ceb
)](https://papermc.io/downloads/paper)
[![Paper Build Status](https://img.shields.io/github/actions/workflow/status/PaperMC/Paper/release-26.3.yaml?branch=main)](https://github.com/PaperMC/Paper/actions)
[![Discord](https://img.shields.io/discord/289587909051416579.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/papermc)
@@ -39,7 +39,7 @@ repositories {
}
dependencies {
compileOnly("io.papermc.paper:paper-api:26.3.build.+")
compileOnly("io.papermc.paper:paper-api:26.4.build.+")
}
java {
@@ -59,7 +59,7 @@ java {
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>[26.3.build,)</version>
<version>[26.4.build,)</version>
<scope>provided</scope>
</dependency>
```
+2 -4
View File
@@ -477,10 +477,8 @@ public net.minecraft.world.inventory.AbstractMountInventoryMenu mount
public net.minecraft.world.inventory.AnvilMenu cost
public net.minecraft.world.inventory.AnvilMenu itemName
public net.minecraft.world.inventory.AnvilMenu repairItemCountCost
public net.minecraft.world.inventory.BrewingStandMenu DATA_COUNT
public net.minecraft.world.inventory.BrewingStandMenu brewingStandData
public net.minecraft.world.inventory.CraftingMenu access
public net.minecraft.world.inventory.LecternMenu DATA_COUNT
public net.minecraft.world.inventory.MerchantContainer selectionHint
public net.minecraft.world.item.AdventureModePredicate predicates
public net.minecraft.world.item.CrossbowItem FIREWORK_POWER
@@ -585,7 +583,6 @@ public net.minecraft.world.level.block.entity.LecternBlockEntity bookAccess
public net.minecraft.world.level.block.entity.LecternBlockEntity setPage(I)V
public net.minecraft.world.level.block.entity.SculkCatalystBlockEntity$CatalystListener bloom(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/util/RandomSource;)V
public net.minecraft.world.level.block.entity.SculkShriekerBlockEntity warningLevel
public net.minecraft.world.level.block.entity.SignBlockEntity allowOpFeatures
public net.minecraft.world.level.block.entity.SkullBlockEntity customName
public net.minecraft.world.level.block.entity.SkullBlockEntity noteBlockSound
public net.minecraft.world.level.block.entity.SkullBlockEntity owner
@@ -633,14 +630,15 @@ public net.minecraft.world.level.dimension.end.EnderDragonFight spawnNewGateway(
public net.minecraft.world.level.entity.PersistentEntitySectionManager ensureChunkQueuedForLoad(J)V
public net.minecraft.world.level.entity.PersistentEntitySectionManager permanentStorage
public net.minecraft.world.level.gamerules.GameRules rules
public net.minecraft.world.level.levelgen.material.MaterialRuleContext context
public net.minecraft.world.level.levelgen.structure.StructurePiece SHAPE_CHECK_BLOCKS
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement exclusionZone
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement frequency
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement frequencyReductionMethod
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement locateOffset
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement salt
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate <init>()V
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate entityInfoList
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate fillFromWorld(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Vec3i;ZLjava/util/List;)V
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate palettes
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager resourceManagerSource
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager structureRepository
+4 -4
View File
@@ -1,12 +1,12 @@
group=io.papermc.paper
mcVersion=26.3
mcVersion=26.4-snapshot-5
# This is the current API version for use in (paper-)plugin.yml files
# During snapshot cycles this should be the anticipated version of the release target
apiVersion=26.3
channel=STABLE
apiVersion=26.4
channel=ALPHA
# Set to true while updating Minecraft version
updatingMinecraft=false
updatingMinecraft=true
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -234,8 +234,7 @@ public class MaterialTags {
* Covers all music disc items.
*/
public static final MaterialSetTag MUSIC_DISCS = new MaterialSetTag(keyFor("music_discs"))
.startsWith("MUSIC_DISC_")
.ensureSize("MUSIC_DISCS", 22).lock();
.startsWith("MUSIC_DISC_").lock();
/**
* Covers all ores.
@@ -1,64 +0,0 @@
package com.destroystokyo.paper.block;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
/**
* Represents the sounds that a {@link Block} makes in certain situations
* <p>
* The sound group includes break, step, place, hit, and fall sounds.
* @deprecated use {@link org.bukkit.SoundGroup}
*/
@Deprecated(forRemoval = true, since = "1.18.2")
public interface BlockSoundGroup {
/**
* Gets the sound that plays when breaking this block
*
* @return The break sound
* @deprecated use {@link org.bukkit.SoundGroup#getBreakSound()}
*/
@NotNull
@Deprecated(forRemoval = true, since = "1.18.2")
Sound getBreakSound();
/**
* Gets the sound that plays when stepping on this block
*
* @return The step sound
* @deprecated use {@link org.bukkit.SoundGroup#getStepSound()}
*/
@NotNull
@Deprecated(forRemoval = true, since = "1.18.2")
Sound getStepSound();
/**
* Gets the sound that plays when placing this block
*
* @return The place sound
* @deprecated use {@link org.bukkit.SoundGroup#getPlaceSound()}
*/
@NotNull
@Deprecated(forRemoval = true, since = "1.18.2")
Sound getPlaceSound();
/**
* Gets the sound that plays when hitting this block
*
* @return The hit sound
* @deprecated use {@link org.bukkit.SoundGroup#getHitSound()}
*/
@NotNull
@Deprecated(forRemoval = true, since = "1.18.2")
Sound getHitSound();
/**
* Gets the sound that plays when this block falls
*
* @return The fall sound
* @deprecated use {@link org.bukkit.SoundGroup#getFallSound()}
*/
@NotNull
@Deprecated(forRemoval = true, since = "1.18.2")
Sound getFallSound();
}
@@ -1,14 +1,8 @@
package io.papermc.paper.command.brigadier;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.CommandNode;
import net.kyori.adventure.pointer.Pointered;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.GameRules;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
@@ -87,123 +81,4 @@ public interface CommandSourceStack {
* @see com.mojang.brigadier.builder.ArgumentBuilder#fork(CommandNode, RedirectModifier)
*/
CommandSourceStack withExecutor(Entity executor);
/**
* Sends a system message to the {@link #getExecutor()} if it is a {@link Player},
* otherwise sends a system message to the {@link #getSender()}.
*
* @param message the message to send
* @throws IllegalArgumentException if the message is null
*/
void sendReply(ComponentLike message);
/**
* Sends a system message with the MiniMessage format to the {@link #getExecutor()}
* if it is a {@link Player}, otherwise sends it to the {@link #getSender()}.
*
* <p>See <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a> and
* <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements/">MiniMessage Placeholders docs</a>
* for more information on the format.</p>
*
* <p>This method provides the target of the system message as a {@link net.kyori.adventure.pointer.Pointered}
* to the MiniMessage {@link MiniMessage#deserialize(String, Pointered, TagResolver...)} method.</p>
*
* @param message the MiniMessage message to send
* @param resolvers resolvers to use
* @throws IllegalArgumentException if the message is null
*/
default void sendRichReply(final String message, final TagResolver... resolvers) {
Preconditions.checkArgument(message != null, "message cannot be null.");
this.sendReply(MiniMessage.miniMessage().deserialize(
message,
this.getExecutor() instanceof final Player player ? player : this.getSender(),
resolvers
));
}
/**
* Sends a system message to the {@link #getSender()}, admins, and console indicating successful command execution
* according to vanilla semantics.
*
* <p>This currently includes checking for environments with suppressed output,
* {@link GameRules#SEND_COMMAND_FEEDBACK}, and {@link GameRules#LOG_ADMIN_COMMANDS}.</p>
*
* @param message the message to send
* @param allowInformingAdmins whether admins and console may be informed of this success
* @throws IllegalArgumentException if the message is null
*/
void sendSuccess(ComponentLike message, boolean allowInformingAdmins);
/**
* Sends a system message to the {@link #getSender()}, admins, and console indicating successful command execution
* according to vanilla semantics. This method informs admins and the console of this success.
*
* <p>This currently includes checking for environments with suppressed output,
* {@link GameRules#SEND_COMMAND_FEEDBACK}, and {@link GameRules#LOG_ADMIN_COMMANDS}.</p>
*
* @param message the message to send
* @see #sendSuccess(ComponentLike, boolean) to disable if admins and console should be informed
* @throws IllegalArgumentException if the message is null
*/
default void sendSuccess(final ComponentLike message) {
this.sendSuccess(message, true);
}
/**
* Sends a system message with the MiniMessage format to the {@link #getSender()}, admins, and console indicating
* successful command execution according to vanilla semantics. This method informs admins and the console of
* this success.
*
* <p>This currently includes checking for environments with suppressed output,
* {@link GameRules#SEND_COMMAND_FEEDBACK}, and {@link GameRules#LOG_ADMIN_COMMANDS}.</p>
*
* <p>See <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a> and
* <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements/">MiniMessage Placeholders docs</a>
* for more information on the format.</p>
*
* <p>This method provides the {@link CommandSender} of this command as a {@link net.kyori.adventure.pointer.Pointered}
* to the MiniMessage {@link MiniMessage#deserialize(String, Pointered, TagResolver...)} method.</p>
*
* @param message the MiniMessage message to send
* @param resolvers resolvers to use
* @see #sendSuccess(ComponentLike, boolean) to disable if admins and console should be informed
* @throws IllegalArgumentException if the message is null
*/
default void sendRichSuccess(final String message, final TagResolver... resolvers) {
Preconditions.checkArgument(message != null, "message cannot be null.");
this.sendSuccess(MiniMessage.miniMessage().deserialize(message, this.getSender(), resolvers), true);
}
/**
* Sends a system message indicating a failed command execution to the {@link #getSender()}.
* Does not apply red styling to the message as vanilla does to allow for custom failure message styling.
*
* <p>Respects vanilla semantics for accepting failure output and suppressed output environments.</p>
*
* @param message the message to send
* @throws IllegalArgumentException if the message is null
*/
void sendFailure(ComponentLike message);
/**
* Sends a system message with the MiniMessage format indicating a failed command execution to the{@link #getSender()}.
* Does not apply red styling to the message as vanilla does to allow for custom failure message styling.
*
* <p>Respects vanilla semantics for accepting failure output and suppressed output environments.</p>
*
* <p>See <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a> and
* <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements/">MiniMessage Placeholders docs</a>
* for more information on the format.</p>
*
* <p>This method provides the {@link CommandSender} of this command as a {@link net.kyori.adventure.pointer.Pointered}
* to the MiniMessage {@link MiniMessage#deserialize(String, Pointered, TagResolver...)} method.</p>
*
* @param message the MiniMessage message to send
* @param resolvers resolvers to use
* @throws IllegalArgumentException if the message is null
*/
default void sendRichFailure(final String message, final TagResolver... resolvers) {
Preconditions.checkArgument(message != null, "message cannot be null.");
this.sendFailure(MiniMessage.miniMessage().deserialize(message, this.getSender(), resolvers));
}
}
@@ -10,6 +10,7 @@ import org.checkerframework.checker.index.qual.NonNegative;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.Nullable;
/**
* Holds the properties for this item for when it is consumed.
@@ -30,7 +31,7 @@ public interface Consumable extends BuildableDataComponent<Consumable, Consumabl
ItemUseAnimation animation();
@Contract(pure = true)
Key sound();
@Nullable Key sound();
@Contract(pure = true)
boolean hasConsumeParticles();
@@ -65,11 +66,11 @@ public interface Consumable extends BuildableDataComponent<Consumable, Consumabl
/**
* Sets the sound played when consuming the item.
*
* @param sound the {@link Key} representing the sound to be used
* @param sound the {@link Key} representing the sound to be used, or {@code null} for no sound
* @return the builder for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Builder sound(Key sound);
Builder sound(@Nullable Key sound);
/**
* Sets whether consuming the item results in particle effects.
@@ -39,10 +39,10 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
/**
* Gets the equip sound key.
*
* @return the equip sound key
* @return the equip sound key, or {@code null} if none
*/
@Contract(pure = true)
Key equipSound();
@Nullable Key equipSound();
/**
* Gets the asset id if present.
@@ -112,10 +112,10 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
/**
* Returns the sound that is played when shearing this equipment off an entity.
*
* @return shear sound
* @return shear sound, or {@code null} if none
*/
@Contract(pure = true)
Key shearSound();
@Nullable Key shearSound();
/**
* Builder for {@link Equippable}.
@@ -126,11 +126,11 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
/**
* Sets the equip sound key for this item.
*
* @param sound the equip sound key
* @param sound the equip sound key, or {@code null} for no sound
* @return the builder for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Builder equipSound(Key sound);
Builder equipSound(@Nullable Key sound);
/**
* Sets the asset id for this item.
@@ -207,10 +207,10 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
/**
* Sets the sound that is played when shearing this equipment off an entity.
*
* @param shearSound the shear sound key
* @param shearSound the shear sound key, or {@code null} for no sound
* @return the builder for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Builder shearSound(Key shearSound);
Builder shearSound(@Nullable Key shearSound);
}
}
@@ -1,91 +0,0 @@
package io.papermc.paper.event.entity;
import com.google.common.base.Preconditions;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.checkerframework.checker.index.qual.NonNegative;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Called when an {@link Entity} lands on the block after falling.
* This event is called before {@link org.bukkit.event.entity.EntityDamageEvent}, {@link org.bukkit.event.entity.EntityChangeBlockEvent}, {@link org.bukkit.event.player.PlayerInteractEvent}
* and {@link org.bukkit.event.entity.EntityInteractEvent}.
*/
@NullMarked
public class EntityLandEvent extends EntityEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Block block;
private boolean cancelled;
private double fallDistance;
@ApiStatus.Internal
public EntityLandEvent(final Entity entity, final Block block, final double fallDistance) {
super(entity);
this.block = block;
this.fallDistance = fallDistance;
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
/**
* {@return the block that the entity landed on}
*/
public Block getBlock() {
return this.block;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* Sets whether to cancel the landing, cancelling this event will prevent
* the entity from taking fall damage and interact with the block. (ex: break turtle egg)
*
* @param cancel true if the event should be cancelled, false otherwise
*/
@Override
public void setCancelled(final boolean cancel) {
this.cancelled = cancel;
}
/**
* Gets the fall distance of the entity before landing.
*
* @return the fall distance
*/
public double getFallDistance() {
return this.fallDistance;
}
/**
* Sets the fall distance of the entity before landing.
* Fall distance in the event is used to calculate fall damage and
* the chance of transforming {@link org.bukkit.Material#FARMLAND} to {@link org.bukkit.Material#DIRT} and
* the type of falling sound when the entity lands on {@link org.bukkit.Material#POWDER_SNOW}.
*
* @param fallDistance the fall distance
* @throws IllegalArgumentException if the fall distance is negative or not finite
*/
public void setFallDistance(final @NonNegative double fallDistance) {
Preconditions.checkArgument(
Double.isFinite(fallDistance) && fallDistance >= 0.0,
"Fall distance must be finite and non-negative"
);
this.fallDistance = fallDistance;
}
}
@@ -1,69 +0,0 @@
package io.papermc.paper.event.entity;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Called when an entity is launched upward by a Potent Sulfur geyser.
*/
@NullMarked
public class EntityPotentSulfurLaunchEvent extends EntityEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Block sulfurBlock;
private final Location sourceLocation;
private final Vector launchVelocity;
@ApiStatus.Internal
public EntityPotentSulfurLaunchEvent(
final Entity entity,
final Block sulfurBlock,
final Location sourceLocation,
final Vector launchVelocity
) {
super(entity);
this.sulfurBlock = sulfurBlock;
this.sourceLocation = sourceLocation;
this.launchVelocity = launchVelocity;
}
/**
* Gets the Potent Sulfur block.
*/
public Block getSulfurBlock() {
return this.sulfurBlock;
}
/**
* Gets the location where the geyser exits the water column.
*/
public Location getSourceLocation() {
return this.sourceLocation.clone();
}
/**
* Gets the velocity that will be added to the entity.
* <br>
* This value is read-only, changes made to it <b>will not</b> have any
* effect on the final launch velocity.
*/
public Vector getLaunchVelocity() {
return this.launchVelocity.clone();
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}
@@ -1,7 +1,6 @@
package io.papermc.paper.persistence;
import java.util.Set;
import net.kyori.adventure.key.Key;
import com.google.common.base.Preconditions;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataAdapterContext;
@@ -178,23 +177,6 @@ public interface PersistentDataContainerView {
return this.getOrDefault(dataKey.key(), dataKey.dataType(), defaultValue);
}
/**
* Estimates the {@link PersistentDataType} of the value based on the primitive
* type that is stored for the given key.
* <br>
* <b>WARNING:</b> This method will only return data types
* defined in {@link PersistentDataType} which directly map to a specific
* nbt tag. The return value will be wrong if the data type is plugin
* specific or not represented by a specific nbt tag.
* Use {@link #has(NamespacedKey, PersistentDataType)} if you expect a
* specific data type.
*
* @param key the key to look up the value type of
* @return estimated data type or null if no value is present, the type is
* unknown or an empty list is encountered as value.
*/
@Nullable PersistentDataType<?, ?> getPrimitiveStorageType(Key key);
/**
* Get the set of keys present on this {@link PersistentDataContainer}
* instance.
@@ -182,16 +182,16 @@ public interface SulfurCubeArchetypeRegistryEntry {
// todo The sounds should take a RegistryHolder but need more thoughts on the create method
/**
* {@return the sound played once the sulfur cube is knocked}
* {@return the sound played once the sulfur cube is knocked, or {@code null} if none}
*/
@ApiStatus.Experimental
TypedKey<Sound> hitSound();
@Nullable TypedKey<Sound> hitSound();
/**
* {@return the sound played once the sulfur cube is pushed}
* {@return the sound played once the sulfur cube is pushed, or {@code null} if none}
*/
@ApiStatus.Experimental
TypedKey<Sound> pushSound();
@Nullable TypedKey<Sound> pushSound();
/**
* {@return the smallest impulse required to play the push sound}
@@ -214,8 +214,8 @@ public interface SulfurCubeArchetypeRegistryEntry {
*/
@Contract(value = "_, _, _, _ -> new", pure = true)
@ApiStatus.Experimental
static SoundSettings of(final TypedKey<Sound> hitSound, final TypedKey<Sound> pushSound, final float pushSoundImpulseThreshold, final float pushSoundCooldown) {
record Impl(TypedKey<Sound> hitSound, TypedKey<Sound> pushSound, float pushSoundImpulseThreshold, float pushSoundCooldown) implements SoundSettings {
static SoundSettings of(final @Nullable TypedKey<Sound> hitSound, final @Nullable TypedKey<Sound> pushSound, final float pushSoundImpulseThreshold, final float pushSoundCooldown) {
record Impl(@Nullable TypedKey<Sound> hitSound, @Nullable TypedKey<Sound> pushSound, float pushSoundImpulseThreshold, float pushSoundCooldown) implements SoundSettings {
}
return new Impl(hitSound, pushSound, pushSoundImpulseThreshold, pushSoundCooldown);
+4 -41
View File
@@ -961,7 +961,8 @@ public final class Bukkit {
* Create a new explorer map targeting the closest nearby structure of a
* given {@link StructureType}.
* <br>
* This method uses a radius of 100 and only searches for unexplored structures.
* This method uses implementation default values for radius and
* findUnexplored (usually 100, true).
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
@@ -985,7 +986,8 @@ public final class Bukkit {
* Create a new explorer map targeting the closest nearby structure of a
* given {@link org.bukkit.generator.structure.StructureType}.
* <br>
* This method uses a radius of 100 and only searches for unexplored structures.
* This method uses implementation default values for radius and
* findUnexplored (usually 100, true).
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
@@ -1019,45 +1021,6 @@ public final class Bukkit {
public static @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) {
return server.createExplorerMap(world, location, structureType, mapIcon, radius, findUnexplored);
}
/**
* Create a new explorer map targeting the closest nearby specific
* {@link org.bukkit.generator.structure.Structure}.
* <br>
* This method uses a radius of 100 and only searches for unexplored structures.
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
* @param structure the specific structure to find
* @param mapIcon the map icon to use on the map
* @return a newly created item stack or null if it can't find a location
*
* @see World#locateNearestStructure(org.bukkit.Location,
* org.bukkit.generator.structure.Structure, int, boolean)
*/
public static @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.Structure structure, @NotNull org.bukkit.map.MapCursor.Type mapIcon) {
return server.createExplorerMap(world, location, structure, mapIcon);
}
/**
* Create a new explorer map targeting the closest nearby specific
* {@link org.bukkit.generator.structure.Structure}.
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
* @param structure the specific structure to find
* @param mapIcon the map icon to use on the map
* @param radius radius to search, see World#locateNearestStructure for more
* information
* @param findUnexplored whether to find unexplored structures
* @return the newly created item stack or null if it can't find a location
*
* @see World#locateNearestStructure(org.bukkit.Location,
* org.bukkit.generator.structure.Structure, int, boolean)
*/
public static @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.Structure structure, @NotNull org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) {
return server.createExplorerMap(world, location, structure, mapIcon, radius, findUnexplored);
}
// Paper end
/**
+62 -116
View File
@@ -6,12 +6,10 @@ import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import net.kyori.adventure.util.ARGBLike;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.SerializableAs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
/**
* A container for a color palette. This class is immutable; the set methods
@@ -19,7 +17,7 @@ import org.jetbrains.annotations.Range;
* but subject to change.
*/
@SerializableAs("Color")
public final class Color implements ConfigurationSerializable, ARGBLike {
public final class Color implements ConfigurationSerializable {
private static final int BIT_MASK = 0xff;
private static final int DEFAULT_ALPHA = 255;
@@ -124,12 +122,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
*/
@NotNull
public static Color fromARGB(
final @Range(from = 0, to = 255) int alpha,
final @Range(from = 0, to = 255) int red,
final @Range(from = 0, to = 255) int green,
final @Range(from = 0, to = 255) int blue
) throws IllegalArgumentException {
public static Color fromARGB(int alpha, int red, int green, int blue) throws IllegalArgumentException {
return new Color(alpha, red, green, blue);
}
@@ -143,11 +136,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
*/
@NotNull
public static Color fromRGB(
final @Range(from = 0, to = 255) int red,
final @Range(from = 0, to = 255) int green,
final @Range(from = 0, to = 255) int blue
) throws IllegalArgumentException {
public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException {
return new Color(DEFAULT_ALPHA, red, green, blue);
}
@@ -161,11 +150,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
*/
@NotNull
public static Color fromBGR(
final @Range(from = 0, to = 255) int blue,
final @Range(from = 0, to = 255) int green,
final @Range(from = 0, to = 255) int red
) throws IllegalArgumentException {
public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException {
return new Color(DEFAULT_ALPHA, red, green, blue);
}
@@ -179,7 +164,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* bits
*/
@NotNull
public static Color fromRGB(final int rgb) throws IllegalArgumentException {
public static Color fromRGB(int rgb) throws IllegalArgumentException {
Preconditions.checkArgument((rgb >> 24) == 0, "Extraneous data in: %s", rgb);
return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb & BIT_MASK);
}
@@ -192,7 +177,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @return a new color object for specified values
*/
@NotNull
public static Color fromARGB(final int argb) {
public static Color fromARGB(int argb) {
return fromARGB(argb >> 24 & BIT_MASK, argb >> 16 & BIT_MASK, argb >> 8 & BIT_MASK, argb & BIT_MASK);
}
@@ -206,16 +191,16 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* bits
*/
@NotNull
public static Color fromBGR(final int bgr) throws IllegalArgumentException {
public static Color fromBGR(int bgr) throws IllegalArgumentException {
Preconditions.checkArgument((bgr >> 24) == 0, "Extrenuous data in: %s", bgr);
return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr & BIT_MASK);
}
private Color(final int red, final int green, final int blue) {
private Color(int red, int green, int blue) {
this(DEFAULT_ALPHA, red, green, blue);
}
private Color(final int alpha, final int red, final int green, final int blue) {
private Color(int alpha, int red, int green, int blue) {
Preconditions.checkArgument(alpha >= 0 && alpha <= 0xff, "Alpha[%s] is not between 0-255", alpha);
Preconditions.checkArgument(red >= 0 && red <= 0xff, "Red[%s] is not between 0-255", red);
Preconditions.checkArgument(green >= 0 && green <= 0xff, "Green[%s] is not between 0-255", green);
@@ -228,123 +213,83 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
}
/**
* Gets the alpha component.
* Gets the alpha component
*
* @return alpha component, from 0 to 255
*/
public @Range(from = 0, to = 255) int getAlpha() {
public int getAlpha() {
return BIT_MASK & alpha;
}
/**
* Gets the alpha component.
*
* @return alpha component, from 0 to 255
*/
@Override
public @Range(from = 0, to = 255) int alpha() {
return this.getAlpha();
}
/**
* Creates a new Color object with a specified component.
* Creates a new Color object with specified component
*
* @param alpha the alpha component, from 0 to 255
* @return a new color object with the alpha component
*/
@NotNull
public Color setAlpha(final @Range(from = 0, to = 255) int alpha) {
return fromARGB(alpha, this.getRed(), this.getGreen(), this.getBlue());
public Color setAlpha(int alpha) {
return fromARGB(alpha, getRed(), getGreen(), getBlue());
}
/**
* Gets the red component.
* Gets the red component
*
* @return red component, from 0 to 255
*/
public @Range(from = 0, to = 255) int getRed() {
return BIT_MASK & this.red;
public int getRed() {
return BIT_MASK & red;
}
/**
* Gets the red component.
*
* @return red component, from 0 to 255
*/
@Override
public @Range(from = 0, to = 255) int red() {
return this.getRed();
}
/**
* Creates a new Color object with a specified component.
* Creates a new Color object with specified component
*
* @param red the red component, from 0 to 255
* @return a new color object with the red component
*/
@NotNull
public Color setRed(final @Range(from = 0, to = 255) int red) {
return fromARGB(this.getAlpha(), red, this.getGreen(), this.getBlue());
public Color setRed(int red) {
return fromARGB(getAlpha(), red, getGreen(), getBlue());
}
/**
* Gets the green component.
* Gets the green component
*
* @return green component, from 0 to 255
*/
public @Range(from = 0, to = 255) int getGreen() {
return BIT_MASK & this.green;
public int getGreen() {
return BIT_MASK & green;
}
/**
* Gets the green component.
*
* @return green component, from 0 to 255
*/
@Override
public @Range(from = 0, to = 255) int green() {
return this.getGreen();
}
/**
* Creates a new Color object with a specified component.
* Creates a new Color object with specified component
*
* @param green the green component, from 0 to 255
* @return a new color object with the green component
*/
@NotNull
public Color setGreen(final @Range(from = 0, to = 255) int green) {
return fromARGB(this.getAlpha(), this.getRed(), green, this.getBlue());
public Color setGreen(int green) {
return fromARGB(getAlpha(), getRed(), green, getBlue());
}
/**
* Gets the blue component.
* Gets the blue component
*
* @return blue component, from 0 to 255
*/
public @Range(from = 0, to = 255) int getBlue() {
return BIT_MASK & this.blue;
public int getBlue() {
return BIT_MASK & blue;
}
/**
* Gets the blue component.
*
* @return blue component, from 0 to 255
*/
@Override
public @Range(from = 0, to = 255) int blue() {
return this.getBlue();
}
/**
* Creates a new Color object with a specified component.
* Creates a new Color object with specified component
*
* @param blue the blue component, from 0 to 255
* @return a new color object with the blue component
*/
@NotNull
public Color setBlue(final @Range(from = 0, to = 255) int blue) {
return fromARGB(this.getAlpha(), this.getRed(), this.getGreen(), blue);
public Color setBlue(int blue) {
return fromARGB(getAlpha(), getRed(), getGreen(), blue);
}
/**
@@ -353,7 +298,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @return An integer representation of this color, as 0xRRGGBB
*/
public int asRGB() {
return this.getRed() << 16 | this.getGreen() << 8 | this.getBlue();
return getRed() << 16 | getGreen() << 8 | getBlue();
}
/**
@@ -362,7 +307,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @return An integer representation of this color, as 0xAARRGGBB
*/
public int asARGB() {
return this.getAlpha() << 24 | this.getRed() << 16 | this.getGreen() << 8 | this.getBlue();
return getAlpha() << 24 | getRed() << 16 | getGreen() << 8 | getBlue();
}
/**
@@ -371,33 +316,33 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
* @return An integer representation of this color, as 0xBBGGRR
*/
public int asBGR() {
return this.getBlue() << 16 | this.getGreen() << 8 | this.getRed();
return getBlue() << 16 | getGreen() << 8 | getRed();
}
/**
* Creates a new color with its RGB components changed as if it was dyed
* with the colors passed in, replicating vanilla workbench dyeing.
* with the colors passed in, replicating vanilla workbench dyeing
*
* @param colors The DyeColors to dye with
* @return A new color with the changed rgb components
*/
// TODO: Javadoc what this method does, not what it mimics. API != Implementation
@NotNull
public Color mixDyes(final @NotNull DyeColor... colors) {
public Color mixDyes(@NotNull DyeColor... colors) {
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "DyeColor cannot be null or contain null values");
final Color[] toPass = new Color[colors.length];
Color[] toPass = new Color[colors.length];
for (int i = 0; i < colors.length; i++) {
toPass[i] = colors[i].getColor();
}
return this.mixColors(toPass);
return mixColors(toPass);
}
/**
* Creates a new color with its RGB components changed as if it was dyed
* with the colors passed in, replicating vanilla workbench dyeing.
* <br>
*
* <b>Note that this method does not currently take into account alpha
* components.</b>
*
@@ -406,7 +351,7 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
*/
// TODO: Javadoc what this method does, not what it mimics. API != Implementation
@NotNull
public Color mixColors(final @NotNull Color... colors) {
public Color mixColors(@NotNull Color... colors) {
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "Colors cannot be null");
int totalRed = this.getRed();
@@ -420,43 +365,44 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue());
}
final float averageRed = totalRed / (colors.length + 1);
final float averageGreen = totalGreen / (colors.length + 1);
final float averageBlue = totalBlue / (colors.length + 1);
final float averageMax = totalMax / (colors.length + 1);
float averageRed = totalRed / (colors.length + 1);
float averageGreen = totalGreen / (colors.length + 1);
float averageBlue = totalBlue / (colors.length + 1);
float averageMax = totalMax / (colors.length + 1);
final float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
final float gainFactor = averageMax / maximumOfAverages;
float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
float gainFactor = averageMax / maximumOfAverages;
return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor));
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof Color that)) {
public boolean equals(Object o) {
if (!(o instanceof Color)) {
return false;
}
final Color that = (Color) o;
return this.alpha == that.alpha && this.blue == that.blue && this.green == that.green && this.red == that.red;
}
@Override
public int hashCode() {
return this.asARGB() ^ Color.class.hashCode();
return asARGB() ^ Color.class.hashCode();
}
@Override
@NotNull
public Map<String, Object> serialize() {
return ImmutableMap.of(
"ALPHA", this.getAlpha(),
"RED", this.getRed(),
"BLUE", this.getBlue(),
"GREEN", this.getGreen()
"ALPHA", getAlpha(),
"RED", getRed(),
"BLUE", getBlue(),
"GREEN", getGreen()
);
}
@NotNull
public static Color deserialize(final @NotNull Map<String, Object> map) {
public static Color deserialize(@NotNull Map<String, Object> map) {
return fromARGB(
asInt("ALPHA", map, DEFAULT_ALPHA),
asInt("RED", map),
@@ -465,23 +411,23 @@ public final class Color implements ConfigurationSerializable, ARGBLike {
);
}
private static int asInt(final @NotNull String string, final @NotNull Map<String, Object> map) {
private static int asInt(@NotNull String string, @NotNull Map<String, Object> map) {
return asInt(string, map, null);
}
private static int asInt(final @NotNull String string, final @NotNull Map<String, Object> map, final @Nullable Object defaultValue) {
final Object value = map.getOrDefault(string, defaultValue);
private static int asInt(@NotNull String string, @NotNull Map<String, Object> map, @Nullable Object defaultValue) {
Object value = map.getOrDefault(string, defaultValue);
if (value == null) {
throw new IllegalArgumentException(string + " not in map " + map);
}
if (!(value instanceof Number number)) {
if (!(value instanceof Number)) {
throw new IllegalArgumentException(string + '(' + value + ") is not a number");
}
return number.intValue();
return ((Number) value).intValue();
}
@Override
public String toString() {
return "Color:[argb0x" + Integer.toHexString(this.asARGB()).toUpperCase(Locale.ROOT) + "]";
return "Color:[argb0x" + Integer.toHexString(asARGB()).toUpperCase(Locale.ROOT) + "]";
}
}
+18 -53
View File
@@ -61,8 +61,6 @@ import org.bukkit.profile.PlayerProfile;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scoreboard.Criteria;
import org.bukkit.scoreboard.ScoreboardManager;
import org.bukkit.generator.structure.Structure;
import org.bukkit.map.MapCursor;
import org.bukkit.structure.StructureManager;
import org.bukkit.util.CachedServerIcon;
import org.jetbrains.annotations.ApiStatus;
@@ -874,7 +872,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* Create a new explorer map targeting the closest nearby structure of a
* given {@link StructureType}.
* <br>
* This method uses a radius of 100 and only searches for unexplored structures.
* This method uses implementation default values for radius and
* findUnexplored (usually 100, true).
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
@@ -883,9 +882,9 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*
* @see World#locateNearestStructure(org.bukkit.Location,
* org.bukkit.StructureType, int, boolean)
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, MapCursor.Type)}
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, org.bukkit.map.MapCursor.Type)}
*/
@Deprecated
@Deprecated // Paper
@NotNull
default ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType) {
return this.createExplorerMap(world, location, structureType, 100, true);
@@ -905,16 +904,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*
* @see World#locateNearestStructure(org.bukkit.Location,
* org.bukkit.StructureType, int, boolean)
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, MapCursor.Type, int, boolean)}
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, org.bukkit.map.MapCursor.Type, int, boolean)}
*/
@Deprecated
@NotNull ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored);
@Deprecated // Paper
@NotNull
public ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored);
// Paper start
/**
* Create a new explorer map targeting the closest nearby structure of a
* given {@link StructureType}.
* given {@link org.bukkit.generator.structure.StructureType}.
* <br>
* This method uses a radius of 100 and only searches for unexplored structures.
* This method uses implementation default values for radius and
* findUnexplored (usually 100, true).
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
@@ -923,15 +924,15 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* @return a newly created item stack or null if it can't find a location
*
* @see World#locateNearestStructure(org.bukkit.Location,
* StructureType, int, boolean)
* org.bukkit.generator.structure.StructureType, int, boolean)
*/
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull MapCursor.Type mapIcon) {
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon) {
return this.createExplorerMap(world, location, structureType, mapIcon, 100, true);
}
/**
* Create a new explorer map targeting the closest nearby structure of a
* given {@link StructureType}.
* given {@link org.bukkit.generator.structure.StructureType}.
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
@@ -943,46 +944,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* @return the newly created item stack or null if it can't find a location
*
* @see World#locateNearestStructure(org.bukkit.Location,
* StructureType, int, boolean)
* org.bukkit.generator.structure.StructureType, int, boolean)
*/
@Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull MapCursor.Type mapIcon, int radius, boolean findUnexplored);
/**
* Create a new explorer map targeting the closest nearby specific
* {@link Structure}.
* <br>
* This method uses a radius of 100 and only searches for unexplored structures.
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
* @param structure the specific structure to find
* @param mapIcon the map icon to use on the map
* @return a newly created item stack or null if it can't find a location
*
* @see World#locateNearestStructure(org.bukkit.Location,
* Structure, int, boolean)
*/
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull Structure structure, @NotNull MapCursor.Type mapIcon) {
return this.createExplorerMap(world, location, structure, mapIcon, 100, true);
}
/**
* Create a new explorer map targeting the closest nearby specific
* {@link Structure}.
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
* @param structure the specific structure to find
* @param mapIcon the map icon to use on the map
* @param radius radius to search, see World#locateNearestStructure for more
* information
* @param findUnexplored whether to find unexplored structures
* @return the newly created item stack or null if it can't find a location
*
* @see World#locateNearestStructure(org.bukkit.Location,
* Structure, int, boolean)
*/
@Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull Structure structure, @NotNull MapCursor.Type mapIcon, int radius, boolean findUnexplored);
@Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored);
// Paper end
/**
* Reloads the server, refreshing settings and plugin information.
@@ -1,70 +1,112 @@
package org.bukkit;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* Represents a group of sounds for blocks that are played when various actions
* happen (ie stepping, breaking, hitting, etc).
* happen (ie stepping, breaking, hitting, etc.).
*/
@NullMarked
public interface SoundGroup {
/**
* Get the volume these sounds are played at.
*
* <p>
* Note that this volume does not always represent the actual volume
* received by the client.
*
* @return volume
*/
public float getVolume();
float getVolume();
/**
* Gets the pitch these sounds are played at.
*
* <p>
* Note that this pitch does not always represent the actual pitch received
* by the client.
*
* @return pitch
*/
public float getPitch();
float getPitch();
/**
* Gets the corresponding breaking sound for this group.
*
* @return the break sound
* @return the break sound, or {@link Sound#BLOCK_STONE_BREAK} if this group has none
* @deprecated sounds may now be nullable, use {@link #breakSound()} instead.
*/
@NotNull
public Sound getBreakSound();
@Deprecated(since = "26.4")
Sound getBreakSound();
/**
* Gets the corresponding step sound for this group.
*
* @return the step sound
* @return the step sound, or {@link Sound#BLOCK_STONE_STEP} if this group has none
* @deprecated sounds may now be nullable, use {@link #stepSound()} instead.
*/
@NotNull
public Sound getStepSound();
@Deprecated(since = "26.4")
Sound getStepSound();
/**
* Gets the corresponding place sound for this group.
*
* @return the place sound
* @return the place sound, or {@link Sound#BLOCK_STONE_PLACE} if this group has none
* @deprecated sounds may now be nullable, use {@link #placeSound()} instead.
*/
@NotNull
public Sound getPlaceSound();
@Deprecated(since = "26.4")
Sound getPlaceSound();
/**
* Gets the corresponding hit sound for this group.
*
* @return the hit sound
* @return the hit sound, or {@link Sound#BLOCK_STONE_HIT} if this group has none
* @deprecated sounds may now be nullable, use {@link #hitSound()} instead.
*/
@NotNull
public Sound getHitSound();
@Deprecated(since = "26.4")
Sound getHitSound();
/**
* Gets the corresponding fall sound for this group.
*
* @return the fall sound
* @return the fall sound, or {@link Sound#BLOCK_STONE_FALL} if this group has none
* @deprecated sounds may now be nullable, use {@link #fallSound()} instead.
*/
@NotNull
public Sound getFallSound();
@Deprecated(since = "26.4")
Sound getFallSound();
/**
* Gets the corresponding breaking sound for this group.
*
* @return the break sound, or {@code null} if this group has none
*/
@Nullable Sound breakSound();
/**
* Gets the corresponding step sound for this group.
*
* @return the step sound, or {@code null} if this group has none
*/
@Nullable Sound stepSound();
/**
* Gets the corresponding place sound for this group.
*
* @return the place sound, or {@code null} if this group has none
*/
@Nullable Sound placeSound();
/**
* Gets the corresponding hit sound for this group.
*
* @return the hit sound, or {@code null} if this group has none
*/
@Nullable Sound hitSound();
/**
* Gets the corresponding fall sound for this group.
*
* @return the fall sound, or {@code null} if this group has none
*/
@Nullable Sound fallSound();
}
@@ -110,15 +110,7 @@ public enum TreeType {
*/
PALE_OAK_CREAKING,
/**
* Poplar tree with red leaves
* Poplar tree
*/
RED_POPLAR,
/**
* Poplar tree with orange leaves
*/
ORANGE_POPLAR,
/**
* Poplar tree with yellow leaves
*/
YELLOW_POPLAR,
POPLAR,
}
@@ -778,19 +778,6 @@ public interface Block extends Metadatable, Translatable, net.kyori.adventure.tr
*/
boolean canPlace(@NotNull BlockData data);
// Paper start
/**
* Gets the {@link com.destroystokyo.paper.block.BlockSoundGroup} for this block.
* <p>
* This object contains the block, step, place, hit, and fall sounds.
*
* @return the sound group for this block
* @deprecated use {@link #getBlockSoundGroup()}
*/
@NotNull
@Deprecated(forRemoval = true, since = "1.18.2")
com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup();
/**
* Gets the {@link org.bukkit.SoundGroup} for this block.
*
@@ -1,34 +1,27 @@
package org.bukkit.block;
import io.papermc.paper.math.Position;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.bukkit.DyeColor;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.material.Colorable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a captured state of either a SignPost or a WallSign.
*/
@NullMarked
public interface Sign extends TileState, Colorable {
// Paper start
/**
* Gets all the lines of text currently on the {@link Side#FRONT} of this sign.
*
* @return List of components containing each line of text
* @deprecated A sign may have multiple writable sides now. Use {@link Sign#getSide(Side)} and {@link SignSide#lines()}.
*/
@Deprecated(since = "1.20")
default List<Component> lines() {
return this.getSide(Side.FRONT).lines();
}
@NotNull
@Deprecated
public java.util.List<net.kyori.adventure.text.Component> lines();
/**
* Gets the line of text at the specified index on the {@link Side#FRONT}.
@@ -36,14 +29,13 @@ public interface Sign extends TileState, Colorable {
* For example, getLine(0) will return the first line of text.
*
* @param index Line number to get the text from, starting at 0
* @return Text on the given line
* @throws IndexOutOfBoundsException Thrown when the line does not exist
* @return Text on the given line
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int)}.
*/
@Deprecated(since = "1.20")
default Component line(int index) throws IndexOutOfBoundsException {
return this.getSide(Side.FRONT).line(index);
}
@NotNull
@Deprecated
public net.kyori.adventure.text.Component line(int index) throws IndexOutOfBoundsException;
/**
* Sets the line of text at the specified index on the {@link Side#FRONT}.
@@ -52,25 +44,23 @@ public interface Sign extends TileState, Colorable {
* "Line One".
*
* @param index Line number to set the text at, starting from 0
* @param line New text to set at the specified index
* @param line New text to set at the specified index
* @throws IndexOutOfBoundsException If the index is out of the range 0..3
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int, Component)}.
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int, net.kyori.adventure.text.Component)}.
*/
@Deprecated(since = "1.20")
default void line(int index, Component line) throws IndexOutOfBoundsException {
this.getSide(Side.FRONT).line(index, line);
}
@Deprecated
public void line(int index, net.kyori.adventure.text.@NotNull Component line) throws IndexOutOfBoundsException;
// Paper end
/**
* Gets all the lines of text currently on the {@link Side#FRONT} of this sign.
*
* @return Array of Strings containing each line of text
* @deprecated A sign may have multiple writable sides now. Use {@link Sign#getSide(Side)} and {@link SignSide#lines()}.
* @deprecated A sign may have multiple writable sides now. Use {@link Sign#getSide(Side)} and {@link SignSide#lines()}.
*/
@Deprecated(since = "1.20")
default String[] getLines() {
return this.getSide(Side.FRONT).getLines();
}
@NotNull
public String[] getLines();
/**
* Gets the line of text at the specified index.
@@ -83,9 +73,8 @@ public interface Sign extends TileState, Colorable {
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int)}.
*/
@Deprecated(since = "1.20")
default String getLine(int index) throws IndexOutOfBoundsException {
return this.getSide(Side.FRONT).getLine(index);
}
@NotNull
public String getLine(int index) throws IndexOutOfBoundsException;
/**
* Sets the line of text at the specified index.
@@ -94,14 +83,12 @@ public interface Sign extends TileState, Colorable {
* "Line One".
*
* @param index Line number to set the text at, starting from 0
* @param line New text to set at the specified index
* @param line New text to set at the specified index
* @throws IndexOutOfBoundsException If the index is out of the range 0..3
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int, net.kyori.adventure.text.Component)}.
*/
@Deprecated(since = "1.20")
default void setLine(int index, String line) throws IndexOutOfBoundsException {
this.getSide(Side.FRONT).setLine(index, line);
}
public void setLine(int index, @NotNull String line) throws IndexOutOfBoundsException;
/**
* Marks whether this sign can be edited by players.
@@ -110,9 +97,7 @@ public interface Sign extends TileState, Colorable {
* @deprecated use {@link #isWaxed()} instead
*/
@Deprecated(since = "1.20.1")
default boolean isEditable() {
return !this.isWaxed();
}
public boolean isEditable();
/**
* Marks whether this sign can be edited by players.
@@ -121,9 +106,7 @@ public interface Sign extends TileState, Colorable {
* @deprecated use {@link #setWaxed(boolean)} instead
*/
@Deprecated(since = "1.20.1")
default void setEditable(boolean editable) {
this.setWaxed(!editable);
}
public void setEditable(boolean editable);
/**
* Gets whether or not this sign has been waxed. If a sign has been waxed, it
@@ -131,7 +114,7 @@ public interface Sign extends TileState, Colorable {
*
* @return if this sign is waxed
*/
boolean isWaxed();
public boolean isWaxed();
/**
* Sets whether or not this sign has been waxed. If a sign has been waxed, it
@@ -139,7 +122,7 @@ public interface Sign extends TileState, Colorable {
*
* @param waxed if this sign is waxed
*/
void setWaxed(boolean waxed);
public void setWaxed(boolean waxed);
/**
* Gets whether this sign has glowing text. Only affects the {@link Side#FRONT}.
@@ -148,9 +131,7 @@ public interface Sign extends TileState, Colorable {
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#isGlowingText()}.
*/
@Deprecated(since = "1.20")
default boolean isGlowingText() {
return this.getSide(Side.FRONT).isGlowingText();
}
public boolean isGlowingText();
/**
* Sets whether this sign has glowing text. Only affects the {@link Side#FRONT}.
@@ -159,20 +140,17 @@ public interface Sign extends TileState, Colorable {
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#setGlowingText(boolean)}.
*/
@Deprecated(since = "1.20")
default void setGlowingText(boolean glowing) {
this.getSide(Side.FRONT).setGlowingText(glowing);
}
public void setGlowingText(boolean glowing);
/**
* {@inheritDoc}
*
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#getColor()}.
*/
@NotNull
@Override
@Deprecated(since = "1.20")
default @Nullable DyeColor getColor() {
return this.getSide(Side.FRONT).getColor();
}
public DyeColor getColor();
/**
* {@inheritDoc}
@@ -181,9 +159,7 @@ public interface Sign extends TileState, Colorable {
*/
@Override
@Deprecated(since = "1.20")
default void setColor(DyeColor color) {
this.getSide(Side.FRONT).setColor(color);
}
public void setColor(@NotNull DyeColor color);
/**
* Return the side of the sign.
@@ -191,7 +167,8 @@ public interface Sign extends TileState, Colorable {
* @param side the side of the sign
* @return the selected side of the sign
*/
SignSide getSide(Side side);
@NotNull
public SignSide getSide(@NotNull Side side);
/**
* Gets the side of this sign the given player is currently standing on.
@@ -199,7 +176,8 @@ public interface Sign extends TileState, Colorable {
* @param player the player
* @return the side the player is standing on
*/
SignSide getTargetSide(Player player);
@NotNull
public SignSide getTargetSide(@NotNull Player player);
/**
* Gets the player that is currently allowed to edit this sign. <br>
@@ -209,15 +187,16 @@ public interface Sign extends TileState, Colorable {
*
* @return the player allowed to edit this sign, or null
*/
@Nullable Player getAllowedEditor();
@Nullable
public Player getAllowedEditor();
// Paper start - More Sign Block API
/**
* Gets the allowed editor's UUID.
* <br>Edits from other players will be rejected if this value is not null.
*
* @return the allowed editor's UUID, or null
*/
@Nullable UUID getAllowedEditorUniqueId();
@Nullable java.util.UUID getAllowedEditorUniqueId();
/**
* Sets the allowed editor's UUID.
@@ -226,7 +205,7 @@ public interface Sign extends TileState, Colorable {
*
* @param uuid the allowed editor's UUID
*/
void setAllowedEditorUniqueId(@Nullable UUID uuid);
void setAllowedEditorUniqueId(@Nullable java.util.UUID uuid);
/**
* Compute the side facing the specified entity.
@@ -234,7 +213,7 @@ public interface Sign extends TileState, Colorable {
* @param entity the entity
* @return the side it is facing
*/
default Side getInteractableSideFor(final Entity entity) {
default @NotNull Side getInteractableSideFor(org.bukkit.entity.@NotNull Entity entity) {
return this.getInteractableSideFor(entity.getLocation());
}
@@ -244,7 +223,7 @@ public interface Sign extends TileState, Colorable {
* @param position the position
* @return the side the position is facing
*/
default Side getInteractableSideFor(final Position position) {
default @NotNull Side getInteractableSideFor(io.papermc.paper.math.@NotNull Position position) {
return this.getInteractableSideFor(position.x(), position.z());
}
@@ -255,20 +234,6 @@ public interface Sign extends TileState, Colorable {
* @param z the z coord
* @return the side the coordinates are facing
*/
Side getInteractableSideFor(double x, double z);
/**
* Gets whether operator features are allowed on this sign.
* When enabled, supported click events in the root component of each line are executed when the relevant side is used.
*
* @return if operator features are allowed
*/
boolean isOperatorFeaturesAllowed();
/**
* Sets whether operator features are allowed on this sign.
*
* @param allowed whether operator features are allowed
*/
void setOperatorFeaturesAllowed(boolean allowed);
@NotNull Side getInteractableSideFor(double x, double z);
// Paper end - More Sign Block API
}
@@ -1,6 +1,5 @@
package org.bukkit.entity;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -1402,39 +1401,25 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
void setHurtDirection(float hurtDirection);
// Paper end - hurt direction API
// Paper start - swing hand API
/**
* Makes this entity swing their hand, using the {@link io.papermc.paper.datacomponent.DataComponentTypes#ATTACK_ANIMATION}
* component on the item held in that hand to determine the animation type and duration.
* If the held item does not have the component, the default {@link io.papermc.paper.datacomponent.item.SwingAnimation.Animation#WHACK}
* animation with a duration of {@code 6} ticks is used.
* Makes this entity swing their hand.
*
* <p>This method does nothing if this entity does not
* have an animation for swinging their hand.
*
* @param hand hand to be swung, either {@link org.bukkit.inventory.EquipmentSlot#HAND} or {@link org.bukkit.inventory.EquipmentSlot#OFF_HAND}
* @throws IllegalArgumentException if an invalid hand is passed
* @throws IllegalArgumentException if invalid hand is passed
*/
default void swingHand(@NotNull org.bukkit.inventory.EquipmentSlot hand) {
Preconditions.checkArgument(hand != null && hand.isHand(), "Expected a valid hand, got \"%s\" instead!", hand);
com.google.common.base.Preconditions.checkArgument(hand != null && hand.isHand(), String.format("Expected a valid hand, got \"%s\" instead!", hand));
if (hand == org.bukkit.inventory.EquipmentSlot.HAND) {
this.swingMainHand();
} else {
this.swingOffHand();
}
}
/**
* Makes this entity swing their hand using the provided {@link io.papermc.paper.datacomponent.item.SwingAnimation},
* instead of the swing animation component the held item may have.
*
* <p>This method does nothing if this entity does not
* have an animation for swinging their hand.
*
* @param hand hand to be swung, either {@link org.bukkit.inventory.EquipmentSlot#HAND} or {@link org.bukkit.inventory.EquipmentSlot#OFF_HAND}
* @param swingAnimation the swing animation to use
* @throws IllegalArgumentException if an invalid hand is passed
*/
void swingHand(@NotNull org.bukkit.inventory.EquipmentSlot hand, @NotNull io.papermc.paper.datacomponent.item.SwingAnimation swingAnimation);
// Paper end - swing hand API
// Paper start - knockback API
/**
@@ -30,8 +30,7 @@ public enum ClickType {
*/
WINDOW_BORDER_RIGHT,
/**
* The middle click action, bound to the middle mouse button by default.
* This may also be triggered by another key if rebound by the client.
* The middle mouse button, or a "scrollwheel click".
*/
MIDDLE,
/**
@@ -35,9 +35,9 @@ public interface EquippableComponent extends ConfigurationSerializable {
/**
* Gets the sound to play when the item is equipped.
*
* @return the sound
* @return the sound, or null if none
*/
@NotNull // Paper
@Nullable
Sound getEquipSound();
/**
+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
val testData = sourceSets.create("testData")
dependencies {
implementation("com.puppycrawl.tools:checkstyle:14.3.0")
implementation("com.puppycrawl.tools:checkstyle:13.8.0")
implementation("org.jspecify:jspecify:1.0.0")
testCompileOnly("org.jetbrains:annotations:26.1.0")
@@ -15,6 +15,7 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.lang3.tuple.Pair;
import org.jspecify.annotations.Nullable;
import static java.util.Objects.requireNonNull;
@@ -162,16 +163,7 @@ public final class Util {
return false;
}
/**
* A dotted name and the first identifier node it starts at.
*
* @param name the full dotted name
* @param startNode the first identifier node of the name
*/
public record QualifiedName(String name, DetailAST startNode) {
}
public static QualifiedName extractFullIdent(final DetailAST lastDot) {
public static Pair<String, DetailAST> extractFullIdent(final DetailAST lastDot) {
final List<String> parts = new ArrayList<>();
DetailAST dot = lastDot;
while (dot.getChildCount(TokenTypes.DOT) > 0) {
@@ -188,6 +180,6 @@ public final class Util {
}
parts.addFirst(ident.getText());
return new QualifiedName(String.join(".", parts), ident);
return Pair.of(String.join(".", parts), ident);
}
}
@@ -6,9 +6,7 @@ import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
import io.papermc.checkstyle.Util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -21,7 +19,7 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
@Override
public int[] getDefaultJavadocTokens() {
return new int[] {JavadocCommentsTokenTypes.JAVADOC_CONTENT};
return new int[]{JavadocCommentsTokenTypes.JAVADOC_CONTENT};
}
@Override
@@ -32,7 +30,6 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
final List<ParamDesc> params = new ArrayList<>();
int maxColumn = -1;
DetailNode child = detailNode.getFirstChild();
final Set<Integer> visitedLines = new HashSet<>();
for (final DetailNode javadocBlock : JavadocUtil.getAllNodesOfType(detailNode, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG)) {
final DetailNode paramBlock = JavadocUtil.findFirstToken(javadocBlock, JavadocCommentsTokenTypes.PARAM_BLOCK_TAG);
if (paramBlock == null) {
@@ -44,9 +41,6 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
}
// iterate over all text nodes (multiline)
for (final DetailNode textNode : JavadocUtil.getAllNodesOfType(paramDescription, JavadocCommentsTokenTypes.TEXT)) {
if (!visitedLines.add(textNode.getLineNumber())) {
continue;
}
final Matcher matcher = SPACE_PREFIX.matcher(textNode.getText());
int paramDescColNum = textNode.getColumnNumber();
if (matcher.find()) {
@@ -1,123 +0,0 @@
package io.papermc.checkstyle.checks;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.DetailNode;
import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
import com.puppycrawl.tools.checkstyle.utils.BlockCommentPosition;
import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
import io.papermc.checkstyle.Util;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
/**
* Checks that {@code @param} tags are in the same order as the parameters they document,
* with type parameters after regular parameters (the order IntelliJ generates them in).
* Only tags that move backward in the expected order are reported, unknown names are ignored.
*/
public final class JavadocParamTagOrderCheck extends AbstractJavadocCheck {
private static final int[] DECLARATIONS = {
TokenTypes.METHOD_DEF,
TokenTypes.CTOR_DEF,
TokenTypes.COMPACT_CTOR_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.RECORD_DEF,
};
@Override
public int[] getDefaultJavadocTokens() {
return new int[] {JavadocCommentsTokenTypes.JAVADOC_CONTENT};
}
@Override
public void visitJavadocToken(final DetailNode detailNode) {
final DetailAST declaration = documentedDeclaration(this.getBlockCommentAst());
if (declaration == null) {
return;
}
final List<String> expectedOrder = expectedParamOrder(declaration);
int furthestIndex = -1;
for (final DetailNode javadocBlock : JavadocUtil.getAllNodesOfType(detailNode, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG)) {
final DetailNode paramBlock = JavadocUtil.findFirstToken(javadocBlock, JavadocCommentsTokenTypes.PARAM_BLOCK_TAG);
if (paramBlock == null) {
continue;
}
final DetailNode paramName = JavadocUtil.findFirstToken(paramBlock, JavadocCommentsTokenTypes.PARAMETER_NAME);
if (paramName == null) {
continue;
}
final int index = expectedOrder.indexOf(paramName.getText());
if (index == -1) {
continue;
}
if (index < furthestIndex) {
this.log(
paramName.getLineNumber(),
paramName.getColumnNumber(),
"@param tag for %s should be in declaration order, with type parameters last".formatted(paramName.getText())
);
} else {
furthestIndex = index;
}
}
}
private static @Nullable DetailAST documentedDeclaration(final DetailAST blockComment) {
if (!BlockCommentPosition.isOnMethod(blockComment)
&& !BlockCommentPosition.isOnConstructor(blockComment)
&& !BlockCommentPosition.isOnCompactConstructor(blockComment)
&& !BlockCommentPosition.isOnClass(blockComment)
&& !BlockCommentPosition.isOnInterface(blockComment)
&& !BlockCommentPosition.isOnRecord(blockComment)) {
return null;
}
DetailAST parent = blockComment.getParent();
while (parent != null && !TokenUtil.isOfType(parent, DECLARATIONS)) {
parent = parent.getParent();
}
return parent;
}
private static List<String> expectedParamOrder(final DetailAST declaration) {
final List<String> names = new ArrayList<>();
if (declaration.getType() == TokenTypes.COMPACT_CTOR_DEF) {
// compact constructors document the components of the enclosing record
addRecordComponentNames(names, declaration.getParent().getParent());
return names;
}
final DetailAST parameters = declaration.findFirstToken(TokenTypes.PARAMETERS);
if (parameters != null) {
for (final DetailAST parameter : Util.childrenIterator(parameters, TokenTypes.PARAMETER_DEF)) {
names.add(identText(parameter));
}
}
addRecordComponentNames(names, declaration);
final DetailAST typeParameters = declaration.findFirstToken(TokenTypes.TYPE_PARAMETERS);
if (typeParameters != null) {
for (final DetailAST typeParameter : Util.childrenIterator(typeParameters, TokenTypes.TYPE_PARAMETER)) {
names.add("<" + identText(typeParameter) + ">");
}
}
return names;
}
private static void addRecordComponentNames(final List<String> names, final DetailAST declaration) {
final DetailAST recordComponents = declaration.findFirstToken(TokenTypes.RECORD_COMPONENTS);
if (recordComponents == null) {
return;
}
for (final DetailAST component : Util.childrenIterator(recordComponents, TokenTypes.RECORD_COMPONENT_DEF)) {
names.add(identText(component));
}
}
private static String identText(final DetailAST ast) {
final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
return ident == null ? "" : ident.getText();
}
}
@@ -28,7 +28,7 @@ public final class NullabilityAnnotationsCheck extends AbstractCheck {
@Override
public int[] getRequiredTokens() {
return new int[] {
return new int[]{
TokenTypes.METHOD_DEF,
TokenTypes.PARAMETER_DEF,
TokenTypes.ANNOTATION_FIELD_DEF,
@@ -20,7 +20,7 @@ public class RedundantNullabilityCheck extends AbstractCheck {
@Override
public int[] getRequiredTokens() {
return new int[] {
return new int[]{
TokenTypes.CLASS_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.ANNOTATION_DEF,
@@ -31,20 +31,10 @@ public class RedundantNullabilityCheck extends AbstractCheck {
@Override
public void visitToken(final DetailAST ast) {
final DetailAST nullMarkedAnnotation = NullabilityAnnotationsCheck.getNullMarkedAnnotation(ast);
if (nullMarkedAnnotation == null) {
return;
}
final boolean pkgIsNullMarked = Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation);
if (pkgIsNullMarked) {
final DetailAST nullMarkedAnnotation = NullabilityAnnotationsCheck.getNullMarkedAnnotation(ast);
if (pkgIsNullMarked && nullMarkedAnnotation != null) {
this.log(nullMarkedAnnotation.getLineNo(), ast.getColumnNo() - 1, "Redundant NullMarked annotation");
return;
}
for (DetailAST parentDef = Util.getEnclosingTypeDeclaration(ast); parentDef != null; parentDef = Util.getEnclosingTypeDeclaration(parentDef)) {
if (NullabilityAnnotationsCheck.getNullMarkedAnnotation(parentDef) != null) {
this.log(nullMarkedAnnotation.getLineNo(), ast.getColumnNo() - 1, "Redundant NullMarked annotation");
return;
}
}
}
}
@@ -1,26 +1,13 @@
package io.papermc.checkstyle.checks;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
import io.papermc.checkstyle.Util;
import io.papermc.checkstyle.checks.util.ImportAwareAbstractCheck;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import org.jspecify.annotations.Nullable;
/**
* Reports fully qualified type references that could be imported instead.
* <p>
* A reference is only reported if its simple name doesn't already refer to another type in the file,
* either through an import, a type declared in the file, or an unqualified use of that name.
* <p>
* Requires that the codebase be set up to follow standard naming conventions.
* <ul>
* <li>packages are always all lowercase</li>
@@ -29,35 +16,6 @@ import org.jspecify.annotations.Nullable;
*/
public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractCheck {
private static final String JAVA_LANG = "java.lang";
private static final int[] TYPE_CONTEXTS = {
TokenTypes.TYPE,
TokenTypes.TYPE_ARGUMENT,
TokenTypes.TYPE_PARAMETER,
TokenTypes.TYPE_UPPER_BOUNDS,
TokenTypes.TYPE_LOWER_BOUNDS,
TokenTypes.EXTENDS_CLAUSE,
TokenTypes.PERMITS_CLAUSE,
TokenTypes.IMPLEMENTS_CLAUSE,
TokenTypes.LITERAL_THROWS,
TokenTypes.LITERAL_NEW,
TokenTypes.ANNOTATION,
};
private final ThreadLocal<@Nullable FileScope> fileScope = new ThreadLocal<>();
/**
* Type names visible in the file being checked, other than imports.
*
* @param packageName the package of the file
* @param topLevelTypes simple names of top level types declared in the file
* @param nestedTypes simple names of nested and local types declared in the file
* @param unqualifiedTypes simple names of types referenced without qualification
*/
private record FileScope(String packageName, Set<String> topLevelTypes, Set<String> nestedTypes, Set<String> unqualifiedTypes) {
}
@Override
public int[] getDefaultTokens() {
return this.getRequiredTokens();
@@ -70,7 +28,7 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
@Override
public int[] getRequiredTokens() {
return new int[] {
return new int[]{
TokenTypes.TYPE,
TokenTypes.TYPE_ARGUMENT,
TokenTypes.TYPE_PARAMETER,
@@ -79,168 +37,55 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
TokenTypes.EXTENDS_CLAUSE,
TokenTypes.PERMITS_CLAUSE,
TokenTypes.IMPLEMENTS_CLAUSE,
TokenTypes.LITERAL_THROWS,
TokenTypes.LITERAL_NEW,
TokenTypes.ANNOTATION,
TokenTypes.METHOD_CALL,
TokenTypes.METHOD_REF,
TokenTypes.LITERAL_CLASS,
TokenTypes.DOT,
};
}
@Override
public void beginTree(final DetailAST rootAST) {
super.beginTree(rootAST);
String packageName = "";
final Set<String> topLevelTypes = new HashSet<>();
final Set<String> nestedTypes = new HashSet<>();
final Set<String> unqualifiedTypes = new HashSet<>();
final Deque<DetailAST> nodes = new ArrayDeque<>();
for (DetailAST node = rootAST; node != null; node = node.getNextSibling()) {
nodes.push(node);
private static @Nullable DetailAST fromMethodCall(final DetailAST methodCall) {
final DetailAST dot = methodCall.findFirstToken(TokenTypes.DOT);
if (dot == null) {
return null;
}
while (!nodes.isEmpty()) {
final DetailAST node = nodes.pop();
for (DetailAST child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
nodes.push(child);
}
if (node.getType() == TokenTypes.PACKAGE_DEF) {
packageName = FullIdent.createFullIdent(node.getLastChild().getPreviousSibling()).getText();
} else if (node.getType() == TokenTypes.IDENT) {
final DetailAST parent = node.getParent();
if (TokenUtil.isTypeDeclaration(parent.getType())) {
final DetailAST declarationParent = parent.getParent();
if (declarationParent == null || declarationParent.getType() == TokenTypes.COMPILATION_UNIT) {
topLevelTypes.add(node.getText());
} else {
nestedTypes.add(node.getText());
}
} else if (isUnqualifiedTypeReference(node, parent)) {
unqualifiedTypes.add(node.getText());
}
}
final DetailAST nextDot = dot.findFirstToken(TokenTypes.DOT);
if (nextDot == null) {
return null;
}
this.fileScope.set(new FileScope(packageName, topLevelTypes, nestedTypes, unqualifiedTypes));
final DetailAST ident = nextDot.findFirstToken(TokenTypes.IDENT);
if (ident == null || Character.isLowerCase(ident.getText().charAt(0))) {
// method call not on a type
return null;
}
return dot;
}
@Override
public void finishTree(final DetailAST rootAST) {
super.finishTree(rootAST);
this.fileScope.remove();
}
private static boolean isUnqualifiedTypeReference(final DetailAST ident, final DetailAST parent) {
if (TokenUtil.isOfType(parent, TYPE_CONTEXTS)) {
return true;
private static @Nullable DetailAST fromLiteralClass(final DetailAST literalClass) {
final DetailAST dot = literalClass.getParent();
if (dot.getType() != TokenTypes.DOT) {
return null;
}
// first part of a dotted name or method reference, like Type.member or Type::member
return Character.isUpperCase(ident.getText().charAt(0))
&& TokenUtil.isOfType(parent, TokenTypes.DOT, TokenTypes.METHOD_REF)
&& parent.getFirstChild() == ident;
return dot;
}
@Override
public void visitToken(final DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.METHOD_CALL -> {
final DetailAST callee = ast.findFirstToken(TokenTypes.DOT);
if (callee != null) {
this.checkExpressionChain(callee, true);
}
}
case TokenTypes.METHOD_REF -> this.checkExpressionChain(ast.getFirstChild(), false);
case TokenTypes.DOT -> {
if (isFieldAccess(ast)) {
this.checkExpressionChain(ast, true);
}
}
case TokenTypes.LITERAL_CLASS -> {
final DetailAST dot = ast.getParent();
if (dot.getType() == TokenTypes.DOT) {
this.checkChildren(dot);
}
}
default -> this.checkChildren(ast);
}
}
private void checkChildren(final DetailAST ast) {
// clauses like extends, implements, throws, and bounds can contain multiple types
for (final DetailAST dot : Util.childrenIterator(ast, TokenTypes.DOT)) {
this.checkQualifiedName(dot);
}
}
/**
* Checks the type qualifier in a dotted expression, like {@code java.util.Locale} in {@code java.util.Locale.ROOT}.
*
* @param chain the outermost node of the dotted expression
* @param lastIsMember whether the last part of the chain is a member (field or method) and not part of the type
*/
private void checkExpressionChain(final DetailAST chain, final boolean lastIsMember) {
final List<DetailAST> dots = new ArrayList<>();
DetailAST node = chain;
while (node.getType() == TokenTypes.DOT) {
dots.addFirst(node);
node = node.getFirstChild();
}
if (node.getType() != TokenTypes.IDENT) {
// starts with this, super, a method call, etc.
final DetailAST actualAst = switch (ast.getType()) {
case TokenTypes.METHOD_CALL -> fromMethodCall(ast);
case TokenTypes.LITERAL_CLASS -> fromLiteralClass(ast);
default -> ast;
};
if (actualAst == null) {
return;
}
final List<String> parts = new ArrayList<>();
parts.add(node.getText());
for (final DetailAST dot : dots) {
final DetailAST name = Util.getNextSibling(dot.getFirstChild(), TokenTypes.IDENT);
if (name == null) {
// .class, .this, .new, etc.
return;
}
parts.add(name.getText());
}
int packageEnd = 0;
while (packageEnd < parts.size() && !Character.isUpperCase(parts.get(packageEnd).charAt(0))) {
packageEnd++;
}
if (packageEnd == 0 || packageEnd == parts.size()) {
// not qualified by a package, or no type at all
final DetailAST dot = actualAst.findFirstToken(TokenTypes.DOT);
if (dot == null) {
return;
}
int typeEnd = packageEnd;
while (typeEnd + 1 < parts.size() && Character.isUpperCase(parts.get(typeEnd + 1).charAt(0))) {
typeEnd++;
}
if (lastIsMember && typeEnd == parts.size() - 1) {
typeEnd--;
}
if (typeEnd < packageEnd) {
// only a member, like local.inner.FIELD
return;
}
this.checkQualifiedName(dots.get(typeEnd - 1));
}
private static boolean isFieldAccess(final DetailAST dot) {
final DetailAST parent = dot.getParent();
if (TokenUtil.isOfType(parent, TokenTypes.DOT, TokenTypes.METHOD_CALL, TokenTypes.METHOD_REF) || TokenUtil.isOfType(parent, TYPE_CONTEXTS)) {
return false;
}
for (DetailAST ancestor = parent; ancestor != null; ancestor = ancestor.getParent()) {
if (ancestor.getType() == TokenTypes.EXPR) {
return true;
}
if (TokenUtil.isOfType(ancestor, TYPE_CONTEXTS)) {
return false;
}
}
return false;
}
private void checkQualifiedName(final DetailAST dot) {
final Util.QualifiedName fullIdent = Util.extractFullIdent(dot);
final DetailAST startNode = fullIdent.startNode();
final String fullIdentName = fullIdent.name();
final Pair<String, DetailAST> fullIdent = Util.extractFullIdent(dot);
final DetailAST startNode = fullIdent.getRight();
final String fullIdentName = fullIdent.getLeft();
if (Character.isUpperCase(fullIdentName.charAt(0))) {
// not a package
return;
@@ -251,7 +96,7 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
do {
// Check if we have an import for this exact type
if (this.hasImportFor(typeName)) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Fully qualified import used: " + withoutConstants(fullIdentName));
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Fully qualified import used: " + fullIdentName);
return;
}
previousTypeName = typeName;
@@ -259,61 +104,9 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
} while ((lastDotIdx = typeName.lastIndexOf('.')) != -1 && Character.isUpperCase(typeName.substring(0, lastDotIdx + 2).charAt(lastDotIdx + 1)));
final String outerSimpleName = previousTypeName.substring(previousTypeName.lastIndexOf('.') + 1);
final String outerPackage = previousTypeName.substring(0, previousTypeName.lastIndexOf('.'));
if (this.getFullImportBySimple(outerSimpleName) == null && !this.isNameTaken(outerSimpleName, outerPackage)) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Unnecessary fully qualified import (no conflict): " + withoutConstants(fullIdentName));
final String fullImportBySimple = this.getFullImportBySimple(outerSimpleName);
if (fullImportBySimple == null) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Unnecessary fully qualified import (no conflict): " + fullIdentName);
}
}
/**
* Checks if the simple name already refers to a different type in this file.
*
* @param simpleName the simple name of the outermost type
* @param packageName the package of the outermost type
* @return true if the type can't be referenced by its simple name
*/
private boolean isNameTaken(final String simpleName, final String packageName) {
final FileScope scope = this.fileScope.get();
if (scope == null) {
return false;
}
final boolean samePackage = packageName.equals(scope.packageName());
if (scope.nestedTypes().contains(simpleName)) {
// a nested type shadows any other type with the same name
return true;
}
if (scope.topLevelTypes().contains(simpleName)) {
// a top level type with the same name is only the same type if it's in the same package
return !samePackage;
}
if (scope.unqualifiedTypes().contains(simpleName)) {
// unqualified names resolve to the same package or java.lang (imports are already handled)
return !samePackage && !JAVA_LANG.equals(packageName);
}
return false;
}
/**
* Removes trailing constants from a dotted name, like {@code ROOT} in {@code java.util.Locale.ROOT}.
*
* @param name the dotted name
* @return the name without trailing constants
*/
private static String withoutConstants(final String name) {
String result = name;
int lastDot;
while ((lastDot = result.lastIndexOf('.')) != -1 && isConstantName(result.substring(lastDot + 1))) {
final String qualifier = result.substring(0, lastDot);
if (!Character.isUpperCase(qualifier.charAt(qualifier.lastIndexOf('.') + 1))) {
// the constant-like part is the outermost type, like java.util.UUID
break;
}
result = qualifier;
}
return result;
}
private static boolean isConstantName(final String name) {
return name.length() > 1 && name.equals(name.toUpperCase(Locale.ROOT)) && name.chars().anyMatch(Character::isLetter);
}
}
@@ -43,7 +43,6 @@ public abstract class ImportAwareAbstractCheck extends AbstractCheck {
@Override
public void finishTree(final DetailAST rootAST) {
this.imports.remove();
this.simpleImports.remove();
}
protected boolean hasImportFor(final String type) {
@@ -12,12 +12,12 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.intellij.lang.annotations.Language;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
@@ -27,7 +27,6 @@ import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junit.platform.commons.support.ReflectionSupport;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -69,11 +68,10 @@ public @interface CheckTest {
assertFalse(builder.checks.isEmpty(), "CheckTestBuilder must have at least one check");
final TreeWalker walker = new TreeWalker();
walker.setModuleFactory(new PackageObjectFactory("io.papermc.checkstyle.checks", Util.class.getClassLoader()));
final Method registerCheck = ReflectionSupport.findMethod(TreeWalker.class, "registerCheck", AbstractCheck.class).orElseThrow();
for (final AbstractCheck check : builder.checks) {
check.init();
check.configure(new DefaultConfiguration("Test"));
ReflectionSupport.invokeMethod(registerCheck, walker, check);
MethodUtils.invokeMethod(walker, true, "registerCheck", check);
}
final Path filePath = Path.of("src", "testData", "java", checkTest.value().replace('.', File.separatorChar) + ".java");
assertTrue(Files.exists(filePath), "File not found: " + filePath);
@@ -1,29 +0,0 @@
package io.papermc.checkstyle.checks;
import io.papermc.checkstyle.CheckTest;
import io.papermc.checkstyle.CheckTestBuilder;
class TestJavadocParamTagOrderCheck {
@CheckTest("testdata.JavadocParamTagOrder")
void test(final CheckTestBuilder builder) {
builder
.addCheck(new JavadocParamTagOrderCheck())
// regular parameters swapped
.addViolation("28:15: @param tag for a should be in declaration order, with type parameters last")
// type parameter before regular parameter
.addViolation("37:15: @param tag for a should be in declaration order, with type parameters last")
// type parameters swapped
.addViolation("47:15: @param tag for <T> should be in declaration order, with type parameters last")
// constructor
.addViolation("66:15: @param tag for a should be in declaration order, with type parameters last")
// class type parameters
.addViolation("75:15: @param tag for <A> should be in declaration order, with type parameters last")
// record component after type parameter
.addViolation("94:15: @param tag for a should be in declaration order, with type parameters last")
// record components swapped
.addViolation("111:15: @param tag for a should be in declaration order, with type parameters last")
// compact constructor
.addViolation("119:19: @param tag for a should be in declaration order, with type parameters last");
}
}
@@ -1,17 +0,0 @@
package io.papermc.checkstyle.checks;
import io.papermc.checkstyle.CheckTest;
import io.papermc.checkstyle.CheckTestBuilder;
class TestRedundantNullabilityCheck {
@CheckTest("testdata.RedundantNullability")
void test(final CheckTestBuilder builder) {
builder
.addViolation("10:8: Redundant NullMarked annotation")
.addViolation("14:8: Redundant NullMarked annotation")
.addViolation("18:8: Redundant NullMarked annotation")
.addViolation("22:8: Redundant NullMarked annotation")
.addCheck(new RedundantNullabilityCheck());
}
}
@@ -32,32 +32,6 @@ class TestUnnecessaryFullyQualifiedImportCheck {
.addViolation("66:5: Fully qualified import used: testdata.imports.testclasses.subpkg.OtherClass")
// class literal ".class"
.addViolation("70:28: Fully qualified import used: testdata.imports.testclasses.subpkg.OtherClass")
// throws clause, multiple types
.addViolation("74:39: Unnecessary fully qualified import (no conflict): java.io.IOException")
.addViolation("74:60: Unnecessary fully qualified import (no conflict): java.io.UncheckedIOException")
// type param with multiple bounds
.addViolation("77:36: Unnecessary fully qualified import (no conflict): testdata.imports.testclasses.subpkg.OtherInterface")
.addViolation("77:89: Unnecessary fully qualified import (no conflict): java.io.Serializable")
// implements clause, multiple types
.addViolation("80:57: Unnecessary fully qualified import (no conflict): testdata.imports.testclasses.subpkg.OtherInterface")
.addViolation("80:109: Unnecessary fully qualified import (no conflict): java.io.Serializable")
// static field access
.addViolation("84:36: Unnecessary fully qualified import (no conflict): java.util.Locale")
.addViolation("85:35: Fully qualified import used: testdata.imports.testclasses.subpkg.OtherClass")
// method call through a field
.addViolation("86:9: Unnecessary fully qualified import (no conflict): java.lang.System")
// method call on a constant, reported without the constant
.addViolation("87:29: Unnecessary fully qualified import (no conflict): java.util.Locale")
// method and constructor references
.addViolation("88:36: Unnecessary fully qualified import (no conflict): java.lang.Thread")
.addViolation("89:41: Unnecessary fully qualified import (no conflict): java.util.ArrayList")
// no violation for holder.inner.VALUE (91), java.util.Random conflicting with a declared type (103),
// or testdata.imports.testclasses.SamePackage conflicting with an unqualified use (108)
// reference to the declared type itself
.addViolation("104:5: Unnecessary fully qualified import (no conflict): testdata.imports.UnnecessaryFullyQualifiedImport")
// same package type, no conflict
.addViolation("109:5: Unnecessary fully qualified import (no conflict): testdata.imports.SamePackage")
// no violation for testdata.imports.Shadowed (115), the nested Shadowed hides the same package type
.addCheck(new UnnecessaryFullyQualifiedImportCheck());
}
}
@@ -45,26 +45,4 @@ public class JavadocParamAlignment {
public void method_multiline_correct(String a, String superLongParamName) {
}
/**
* A method that does something
*
* @param a this is a description
* with a {@link #method(String, String)} link tag
* @param superLongParamName a description
* blabla
*/
public void method_multiline_correct_with_link(String a, String superLongParamName) {
}
/**
* A method where line 2 starts with a link tag
*
* @param a this is a description
* {@link #method(String, String)} is at line start
* @param superLongParamName a description
* blabla
*/
public void method_line2_starts_with_link(String a, String superLongParamName) {
}
}
@@ -1,124 +0,0 @@
// @formatter:off
package testdata;
/**
* Class with type parameters.
*
* @param <A> first
* @param <B> second
*/
@SuppressWarnings("ALL")
public class JavadocParamTagOrder<A, B> {
/**
* Correct order.
*
* @param a first
* @param b second
* @param <T> type
* @param <U> type
*/
public <T, U> void correct(final int a, final int b) {
}
/**
* Regular parameters out of order.
*
* @param b second
* @param a first
*/
public void swapped(final int a, final int b) {
}
/**
* Type parameter before regular parameters.
*
* @param <T> type
* @param a first
*/
public <T> void typeParamFirst(final int a) {
}
/**
* Type parameters out of order.
*
* @param a first
* @param <U> type
* @param <T> type
*/
public <T, U> void typeParamsSwapped(final int a) {
}
/**
* Unknown names are ignored.
*
* @param a first
* @param unknown not a parameter
* @param b second
*/
public void unknown(final int a, final int b) {
}
/**
* Constructor out of order.
*
* @param b second
* @param a first
*/
public JavadocParamTagOrder(final int a, final int b) {
}
/**
* Class type parameters out of order.
*
* @param <B> second
* @param <A> first
*/
interface Swapped<A, B> {
}
/**
* Record components then type parameters.
*
* @param a first
* @param b second
* @param <T> type
*/
record CorrectRecord<T>(int a, int b) {
}
/**
* Record type parameter first.
*
* @param <T> type
* @param a first
*/
record TypeParamFirstRecord<T>(int a) {
/**
* Compact constructor ignores record type parameters.
*
* @param a first
*/
TypeParamFirstRecord {
}
}
/**
* Compact constructor out of order.
*
* @param b second
* @param a first
*/
record CompactRecord(int a, int b) {
/**
* Compact constructor out of order.
*
* @param b second
* @param a first
*/
CompactRecord {
}
}
}
@@ -1,36 +0,0 @@
package testdata;
import org.jspecify.annotations.NullMarked;
public class RedundantNullability {
@NullMarked
public static class OuterClass {
@NullMarked
public static class RedundantInnerClass {
}
@NullMarked
public interface RedundantInnerInterface {
}
@NullMarked
public record RedundantInnerRecord() {
}
@NullMarked
public enum RedundantInnerEnum {
}
public static class ValidInnerClass {
}
}
public static class NonAnnotatedOuter {
@NullMarked
public static class ValidAnnotatedInner {
}
}
}
@@ -1,4 +0,0 @@
package testdata.imports;
public class SamePackage {
}
@@ -1,4 +0,0 @@
package testdata.imports;
public class Shadowed {
}
@@ -70,47 +70,4 @@ public class UnnecessaryFullyQualifiedImport {
System.out.println(testdata.imports.testclasses.subpkg.OtherClass.class);
System.out.println(OtherClass.class);
}
public void throwsClause() throws java.io.IOException, java.io.UncheckedIOException {
}
public <T extends OtherClass & testdata.imports.testclasses.subpkg.OtherInterface & java.io.Serializable> void bounds() {
}
abstract static class MultipleInterfaces implements testdata.imports.testclasses.subpkg.OtherInterface, java.io.Serializable {
}
public void expressions() {
final Object fieldAccess = java.util.Locale.ENGLISH;
final int importedField = testdata.imports.testclasses.subpkg.OtherClass.CONSTANT;
java.lang.System.out.println();
final String root = java.util.Locale.ROOT.toString();
final Runnable methodRef = java.lang.Thread::yield;
final Runnable constructorRef = java.util.ArrayList::new;
final Holder holder = new Holder();
final Object notAType = holder.inner.VALUE;
}
static final class Holder {
static final int VALUE = 0;
Holder inner;
}
// declared in this file, so java.util.Random needs to be qualified
static final class Random {
}
java.util.Random declaredConflict;
testdata.imports.UnnecessaryFullyQualifiedImport self;
// SamePackage resolves to testdata.imports.SamePackage, so the other one needs to be qualified
SamePackage samePackage;
testdata.imports.testclasses.SamePackage otherPackage;
testdata.imports.SamePackage qualifiedSamePackage;
// the nested Shadowed hides testdata.imports.Shadowed, so it needs to be qualified even though it's in the same package
static final class Shadowed {
}
testdata.imports.Shadowed shadowedSamePackage;
}
@@ -1,4 +0,0 @@
package testdata.imports.testclasses;
public class SamePackage {
}
@@ -2,8 +2,6 @@ package testdata.imports.testclasses.subpkg;
public class OtherClass {
public static final int CONSTANT = 0;
public static class InnerClass {
}
+6 -6
View File
@@ -14,7 +14,7 @@ plugins {
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
dependencies {
mache("io.papermc:mache:26.3+build.2")
mache("io.papermc:mache:26.4-snapshot-5+build.2")
paperclip("io.papermc:paperclip:3.0.4")
}
@@ -23,7 +23,7 @@ paperweight {
gitFilePatches = false
updatingMinecraft {
//oldPaperCommit = "e5fe71723e2ffde7cc9fafc085ac3bb73e63175e"
oldPaperCommit = "8c0b8396d676f6abef3df8ab992ac726f6e01c9c"
}
}
@@ -110,7 +110,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
dependencies {
implementation(project(":paper-api"))
implementation(platform("ca.spottedleaf.leafpile:bom:1.3.2"))
implementation(platform("ca.spottedleaf.leafpile:bom:1.3.0"))
implementation("ca.spottedleaf.leafpile:common")
implementation("ca.spottedleaf.leafpile:concurrentutil")
implementation("ca.spottedleaf.leafpile:converter")
@@ -285,10 +285,10 @@ fun TaskContainer.registerRunTask(
.asFile
javaLauncher.set(project.javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(25))
@Suppress("UnstableApiUsage")
vendor.set(JvmVendorSpec.JETBRAINS)
// TODO - JB runtime 25 has issues with spark rn
// vendor.set(JvmVendorSpec.JETBRAINS)
})
jvmArgs("-XX:+AllowEnhancedClassRedefinition", "--enable-native-access=ALL-UNNAMED")
jvmArgs(/*"-XX:+AllowEnhancedClassRedefinition", */"--enable-native-access=ALL-UNNAMED")
if (rootProject.childProjects["test-plugin"] != null) {
val testPluginJar = rootProject.project(":test-plugin").tasks.jar.flatMap { it.archiveFile }
@@ -34050,10 +34050,10 @@ index 0000000000000000000000000000000000000000..863a9785c84af6e0c961ae32f0102b5d
+ }
+}
diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
index 130615b796d9568c8fc6db0cc838784f21976411..e8043e52b64ed565128bb18240694ad078911eed 100644
index 7050fd6efb6fec16ba5657e254c54de1c671c9a0..aa14dd369d5d2c5c843839f289afdc39c200f0c3 100644
--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -227,6 +227,11 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -216,6 +216,11 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@Override
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion) {
@@ -34065,7 +34065,7 @@ index 130615b796d9568c8fc6db0cc838784f21976411..e8043e52b64ed565128bb18240694ad0
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
@@ -268,4 +273,4 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -257,4 +262,4 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
public boolean addTicketForEnderPearls(final ServerLevel world) {
return !world.paperConfig().misc.legacyEnderPearlBehavior;
}
@@ -34086,10 +34086,10 @@ index 06103ffdf599af91bc1d3baf89772a1acc93a21a..fc1f82d313acb8bf9bd03b1f09c68035
return structureTemplate.save(new CompoundTag());
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index a9ee3ae3217ce7182cd8ab11ea455bbedca1c363..7bc5221da2707b53f0d710f80ea4bfa838195ad4 100644
index d40863262dd7cf0e7c5667fd836e80d4f47e25aa..c4adc7266f41057bc93e8c34929c079c9c468d0b 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -294,6 +294,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -293,6 +293,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private final ServerClockManager clockManager;
public static <S extends MinecraftServer> S spin(final Function<Thread, S> factory) {
@@ -463,7 +463,7 @@ index f74837dc0be560a1601132594587bcfc22dcf778..c5a212e73c2efeb1f440778cb3f1fa6e
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 0de6f540c5192dd152d2e0a55d6b26eee0f5107f..afb2c249078f85579f37a772c41c8138c5c8c521 100644
index bb61d4e4ecfe5bab6269fa3d4303dab95719d7bb..3d219969a55896b1495c54c0a12a25b49ba093f0 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -388,6 +388,15 @@ public abstract class Entity
@@ -496,7 +496,7 @@ index 0de6f540c5192dd152d2e0a55d6b26eee0f5107f..afb2c249078f85579f37a772c41c8138
SynchedEntityData.Builder entityDataBuilder = new SynchedEntityData.Builder(this);
entityDataBuilder.define(DATA_SHARED_FLAGS_ID, (byte)0);
entityDataBuilder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
@@ -1169,6 +1185,10 @@ public abstract class Entity
@@ -1168,6 +1184,10 @@ public abstract class Entity
} else {
if (moverType == MoverType.PISTON) {
delta = this.limitPistonMovement(delta);
@@ -507,7 +507,7 @@ index 0de6f540c5192dd152d2e0a55d6b26eee0f5107f..afb2c249078f85579f37a772c41c8138
if (delta.equals(Vec3.ZERO)) {
return;
}
@@ -1184,6 +1204,13 @@ public abstract class Entity
@@ -1183,6 +1203,13 @@ public abstract class Entity
this.stuckSpeedMultiplier = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
}
@@ -522,10 +522,10 @@ index 0de6f540c5192dd152d2e0a55d6b26eee0f5107f..afb2c249078f85579f37a772c41c8138
delta = this.maybeBackOffFromEdge(delta, moverType);
Vec3 movement = this.collide(delta);
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index db627b1d589e2c9d457b2f1b6429ea11849a1d3a..442bdb6b0bea65f4d2527eea43d2205cbaa4107a 100644
index 62ccee5c76303b679d591714e64ee93c1502f378..0ef6c4cefb457c87f4565701817d2f9034dfa0d8 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -3391,6 +3391,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
@@ -3380,6 +3380,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
protected void playAttackSound() {
}
@@ -822,7 +822,7 @@ index f46cca0467bb4da5511bc953ce5b43fa606bf978..445c5cafad71028171c1f26193966177
+
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index a828576457e0bb932750dba507bdef7e8903e144..2f5ec35bf7f104e259f85d15b17c8064daa05a5e 100644
index d327301693c70affc866d355a08efe935e3656b4..efcf399ced67201525ea6e6b3bd46b93b7e1823d 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -154,6 +154,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -28,7 +28,7 @@ and then catch exceptions and close if they fire.
Part of this commit was authored by: Spottedleaf, sandtechnology
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 91c1ff8b8234900fbe044bbe0c3c10122c27a91b..e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52 100644
index f7814801138d9547e693ad51ba83cb2cb4ef750e..e6a674eb859af1e98fe25b644c1c5f70728128d0 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -64,7 +64,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -267,7 +267,7 @@ index 595db570b0ade9cad7de77d2ab7b10d9e68699df..ac095f59d9f17d8239d6493d1c1e3942
+ // Paper end - Use Velocity cipher
}
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52..76596e87945f0b95af8032338a64817a8faed4d0 100644
index e6a674eb859af1e98fe25b644c1c5f70728128d0..936200d7620e1fe83eb45ddbcff86b6b6ecaab8f 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -796,10 +796,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -320,7 +320,7 @@ index e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52..76596e87945f0b95af8032338a64817a
this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
} else {
diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
index 100e7a2483f63497a1b757c77377262de7248996..42408dede2808447281b49f4006c1d8b03c10bf4 100644
index c2b09a693daf8b4d3d274c07b3be37ba4fc1a483..2f3b58491ef7be8cd2e081f2c44869cd1e50e6a6 100644
--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -71,6 +71,10 @@ public class ServerConnectionListener {
@@ -335,7 +335,7 @@ index 100e7a2483f63497a1b757c77377262de7248996..42408dede2808447281b49f4006c1d8b
.add(
new ServerBootstrap()
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index ab21339beb36388bffabb289546bbfe5732df855..eea7dd596eb9ac75dee29d843d786493d44034f3 100644
index dd2c5b7bd16ca17e253501fbc49cc21c8a06d603..a1855a257c3ac801bbd371ca4508af755550d737 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -256,9 +256,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
@@ -2321,7 +2321,7 @@ index 0000000000000000000000000000000000000000..505b5e39d0b4cf2a56c1c720f0c9ab01
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index a4e6225add32df93b0d6009d98ee322941b69938..e197ac0ee5e49d7768a9de889c972b3d590f7951 100644
index 7fa69d76bf2c802b538a21af40898b8abb79629e..d9daa3ea8c4dc21bbe0d0ea9f0ece3f6c6777ba4 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -238,6 +238,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2347,7 +2347,7 @@ index a4e6225add32df93b0d6009d98ee322941b69938..e197ac0ee5e49d7768a9de889c972b3d
return toLevel.dimension() != Level.NETHER || this.getGameRules().get(GameRules.ALLOW_ENTERING_NETHER_USING_PORTALS);
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 2f5ec35bf7f104e259f85d15b17c8064daa05a5e..7cbf8592ec5792e458fa07987cf258c51f9e1e8a 100644
index efcf399ced67201525ea6e6b3bd46b93b7e1823d..a1da101177a5a4fb2b4ba5e34be165daf4d147fb 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -2169,6 +2169,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5a66a6387 100644
index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02752b35f1 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -930,7 +930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -929,7 +929,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force, final boolean close) {
// Paper end - add close param
@@ -17,7 +17,7 @@ index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5
boolean result = false;
for (ServerLevel level : this.getAllLevels()) {
@@ -942,6 +942,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -941,6 +941,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
result = true;
}
@@ -31,7 +31,7 @@ index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5
GameProfile singleplayerProfile = this.getSingleplayerProfile();
this.storageSource.saveDataTag(this.worldData, singleplayerProfile == null ? null : singleplayerProfile.id());
if (flush) {
@@ -949,14 +956,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -948,14 +955,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
} else {
this.savedDataStorage.scheduleSave();
}
@@ -48,7 +48,7 @@ index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5
boolean result = this.saveAllChunks(silent, flush, force);
this.warnOnLowDiskSpace();
return result;
@@ -1602,9 +1608,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1600,9 +1606,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
this.ticksUntilAutosave--;
@@ -83,7 +83,7 @@ index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5
ProfilerFiller profiler = Profiler.get();
this.server.spark.executeMainThreadTasks(); // Paper - spark
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index e197ac0ee5e49d7768a9de889c972b3d590f7951..85ab77c3d1d5420df648fd173eaf5ad4f4775a80 100644
index e4d38cfdac2b926afae3b1b96679b6ce45ea256a..ca793f3f7300148cd55d2e873af5b43d9f4c708f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1470,6 +1470,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -20,10 +20,10 @@ index ba372f75dec3ee8b89c48df2cc646f071a47d25a..706f529a7d1064aad128b1ee8c82f631
@Override
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 482b428cab996c19664b57c341c55db5a66a6387..bd5efcf1dee77c204bf29ef4693ac54146e8d901 100644
index 487fcb8546ad24125c6573fb84f22a02752b35f1..b675e9322234761a69e71e9e8b9d41364caf34b6 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1743,32 +1743,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1741,32 +1741,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return new ServerStatus.Players(maxPlayers, players.size(), sample);
}
@@ -66,10 +66,10 @@ index 482b428cab996c19664b57c341c55db5a66a6387..bd5efcf1dee77c204bf29ef4693ac541
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b5503b25ef58c7b19592c2e90649c774b155a17b..ffbb54a8576877f2b477615d1e2f84457fdf31e1 100644
index 2cec345fdabc8e01e378e66c35093dec1fcb7c2c..0ae23c90594d85e94d17d8114a21fc62b79219d3 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5385,6 +5385,11 @@ public abstract class Entity
@@ -5379,6 +5379,11 @@ public abstract class Entity
this.getBukkitEntity().taskScheduler.retire();
}
// Paper end - Folia schedulers
@@ -0,0 +1,195 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 15 Feb 2019 01:08:19 -0500
Subject: [PATCH] Allow Saving of Oversized Chunks
The Minecraft World Region File format has a hard cap of 1MB per chunk.
This is due to the fact that the header of the file format only allocates
a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector.
This limit can be reached fairly easily with books, resulting in the chunk being unable
to save to the world. Worse off, is that nothing printed when this occured, and silently
performed a chunk rollback on next load.
This leads to security risk with duplication and is being actively exploited.
This patch catches the too large scenario, falls back and moves any large Entity
or Tile Entity into a new compound, and this compound is saved into a different file.
On Chunk Load, we check for oversized status, and if so, we load the extra file and
merge the Entities and Tile Entities from the oversized chunk back into the level to
then be loaded as normal.
Once a chunk is returned back to normal size, the oversized flag will clear, and no
extra data file will exist.
This fix maintains compatability with all existing Anvil Region Format tools as it
does not alter the save format. They will just not know about the extra entities.
This fix also maintains compatability if someone switches server jars to one without
this fix, as the data will remain in the oversized file. Once the server returns
to a jar with this fix, the data will be restored.
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index 7767e5420e3d2e569008747523d70147ea8d97fb..eddf7c3cef3b3209b47de97d93862937e9d2c82b 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -68,6 +68,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
this.info = info;
this.path = path;
this.version = version;
+ this.initOversizedState(); // Paper
if (!Files.isDirectory(externalFileDir)) {
throw new IllegalArgumentException("Expected directory, got " + externalFileDir.toAbsolutePath());
}
@@ -463,4 +464,75 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
private interface CommitOp {
void run() throws IOException;
}
+
+ // Paper start
+ private final byte[] oversized = new byte[1024];
+ private int oversizedCount;
+
+ private synchronized void initOversizedState() throws IOException {
+ Path metaFile = getOversizedMetaFile();
+ if (Files.exists(metaFile)) {
+ final byte[] read = Files.readAllBytes(metaFile);
+ System.arraycopy(read, 0, oversized, 0, oversized.length);
+ for (byte temp : oversized) {
+ oversizedCount += temp;
+ }
+ }
+ }
+
+ private static int getChunkIndex(int x, int z) {
+ return (x & 31) + (z & 31) * 32;
+ }
+
+ synchronized boolean isOversized(int x, int z) {
+ return this.oversized[getChunkIndex(x, z)] == 1;
+ }
+
+ synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
+ final int offset = getChunkIndex(x, z);
+ boolean previous = this.oversized[offset] == 1;
+ this.oversized[offset] = (byte) (oversized ? 1 : 0);
+ if (!previous && oversized) {
+ oversizedCount++;
+ } else if (!oversized && previous) {
+ oversizedCount--;
+ }
+ if (previous && !oversized) {
+ Path oversizedFile = getOversizedFile(x, z);
+ if (Files.exists(oversizedFile)) {
+ Files.delete(oversizedFile);
+ }
+ }
+ if (oversizedCount > 0) {
+ if (previous != oversized) {
+ writeOversizedMeta();
+ }
+ } else if (previous) {
+ Path oversizedMetaFile = getOversizedMetaFile();
+ if (Files.exists(oversizedMetaFile)) {
+ Files.delete(oversizedMetaFile);
+ }
+ }
+ }
+
+ private void writeOversizedMeta() throws IOException {
+ Files.write(this.getOversizedMetaFile(), this.oversized);
+ }
+
+ private Path getOversizedMetaFile() {
+ return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + ".oversized.nbt");
+ }
+
+ private Path getOversizedFile(int x, int z) {
+ return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
+ }
+
+ synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException {
+ Path file = getOversizedFile(x, z);
+ try (DataInputStream out = new DataInputStream(new java.util.zip.InflaterInputStream(Files.newInputStream(file)))) { // Paper - Optimize NBT reading/writing; FastNBTParser buffers internally
+ return ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead((java.io.DataInput) out); // Paper - Optimize NBT reading/writing
+ }
+
+ }
+ // Paper end
}
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index a78e27fa5733bdb21e06e89025fd23551b3912e7..939d9a660ca9c878d2c087bad07f64dab3c06fef 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -19,6 +19,7 @@ import net.minecraft.world.level.ChunkPos;
import org.jspecify.annotations.Nullable;
public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
public static final String ANVIL_EXTENSION = ".mca";
private static final int MAX_CACHE_SIZE = 256;
private final Long2ObjectLinkedOpenHashMap<Optional<RegionFile>> regionCache = new Long2ObjectLinkedOpenHashMap<>();
@@ -229,11 +230,54 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
return Objects.requireNonNull(this.getRegionFile(pos, true));
}
+ // Paper start
+ private static void printOversizedLog(String msg, Path file, int x, int z) {
+ LOGGER.error("{} ({} - {},{}) Go clean it up to remove this message. /minecraft:tp {} 128 {} - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.", msg, file.toString().replaceAll(".+[\\\\/]", ""), x, z, x << 4, z << 4);
+ }
+
+ private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
+ synchronized (regionfile) {
+ try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
+ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x(), chunkCoordinate.z());
+ CompoundTag chunk = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(datainputstream); // Paper - Optimize NBT reading/writing
+ if (oversizedData == null) {
+ return chunk;
+ }
+ CompoundTag oversizedLevel = oversizedData.getCompoundOrEmpty("Level");
+
+ mergeChunkList(chunk.getCompoundOrEmpty("Level"), oversizedLevel, "Entities", "Entities");
+ mergeChunkList(chunk.getCompoundOrEmpty("Level"), oversizedLevel, "TileEntities", "TileEntities");
+
+ return chunk;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw t;
+ }
+ }
+ }
+
+ private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key, String oversizedKey) {
+ net.minecraft.nbt.ListTag levelList = level.getListOrEmpty(key);
+ net.minecraft.nbt.ListTag oversizedList = oversizedLevel.getListOrEmpty(oversizedKey);
+
+ if (!oversizedList.isEmpty()) {
+ levelList.addAll(oversizedList);
+ level.put(key, levelList);
+ }
+ }
+ // Paper end
+
public @Nullable CompoundTag read(final ChunkPos pos) throws IOException {
RegionFile region = this.getRegionFile(pos, false);
if (region == null) {
return null;
}
+ // Paper start
+ if (region.isOversized(pos.x(), pos.z())) {
+ printOversizedLog("Loading Oversized Chunk!", region.getPath(), pos.x(), pos.z());
+ return readOversizedChunk(region, pos);
+ }
+ // Paper end
try (DataInputStream regionChunkInputStream = region.getChunkDataInputStream(pos)) {
return regionChunkInputStream == null ? null : ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(regionChunkInputStream); // Paper - Optimize NBT reading/writing
@@ -265,6 +309,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
} else {
try (DataOutputStream output = region.getChunkDataOutputStream(pos)) {
NbtIo.write(value, output);
+ region.setOversized(pos.x(), pos.z(), false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
}
}
}
@@ -8,7 +8,7 @@ This ensures at least a valid version of the chunk exists
on disk, even if outdated
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index 1077ff25ca0da7fb6f89dd2e16174f00b4986283..bd4b488cdf7a386bc458fad2ae659b77e60962ad 100644
index b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116da07c32c0 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -24,6 +24,7 @@ import org.slf4j.Logger;
@@ -19,7 +19,7 @@ index 1077ff25ca0da7fb6f89dd2e16174f00b4986283..bd4b488cdf7a386bc458fad2ae659b77
private static final int SECTOR_BYTES = 4096;
@VisibleForTesting
protected static final int SECTOR_INTS = 1024;
@@ -903,6 +904,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
@@ -451,6 +452,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
this.pos = pos;
}
@@ -45,7 +45,7 @@ index 1077ff25ca0da7fb6f89dd2e16174f00b4986283..bd4b488cdf7a386bc458fad2ae659b77
public void close() throws IOException {
ByteBuffer result = ByteBuffer.wrap(this.buf, 0, this.count);
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index 9f8b1d4235d2f268a523c028006114b61d8d67ef..7e815fee19ab2c283bf1fdc3d6a530d4101de3de 100644
index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b93011713c 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -95,11 +95,24 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
@@ -73,7 +73,7 @@ index 9f8b1d4235d2f268a523c028006114b61d8d67ef..7e815fee19ab2c283bf1fdc3d6a530d4
return writeData;
}
@@ -330,8 +343,17 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
@@ -307,9 +320,18 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
if (value == null) {
region.clear(pos);
} else {
@@ -82,6 +82,7 @@ index 9f8b1d4235d2f268a523c028006114b61d8d67ef..7e815fee19ab2c283bf1fdc3d6a530d4
+ DataOutputStream output = region.getChunkDataOutputStream(pos);
+ try { // Paper - Only write if successful
NbtIo.write(value, output);
region.setOversized(pos.x(), pos.z(), false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
+ // Paper start - don't write garbage data to disk if writing serialization fails
+ output.close();
+ } catch (final RegionFileSizeException ex) {
@@ -92,7 +93,7 @@ index 9f8b1d4235d2f268a523c028006114b61d8d67ef..7e815fee19ab2c283bf1fdc3d6a530d4
}
}
}
@@ -379,4 +401,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
@@ -357,4 +379,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
public RegionStorageInfo info() {
return this.info;
}
@@ -14,7 +14,7 @@ timestamp so that fs watchers can detect when RegionFiles are
modified.
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
index 48353259f9a17937f71951706e0172b5b4957d56..e9dc38d934ede23f17e18893ee207d9fbbd26449 100644
index 6dd6b4fb79870da6d3bf54dc813ae685237d1f0e..09205efff3a1a2042e67600510af270df9e46c67 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
@@ -1309,6 +1309,14 @@ public final class MoonriseRegionFileIO {
@@ -5,7 +5,7 @@ Subject: [PATCH] Remove debug-like logging
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 2b5377da8fc3d8582c47809a5e4815d7b0293780..d920a4905d044925c4c4ab980fc238c9c489d5ac 100644
index 951f5ab5a05dcedf323845f57fd061645466e563..19c7dffe5f9c45cd99cb9b14cae5ac908cae0afc 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1761,7 +1761,7 @@ public class ServerGamePacketListenerImpl
@@ -0,0 +1,845 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 2 Feb 2020 02:25:10 -0800
Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt
Instead of trying to relocate the chunk, which is seems to never
be the correct choice, so we end up duplicating or swapping chunks,
we instead drop the current regionfile header and recalculate -
hoping that at least then we don't swap chunks, and maybe recover
them all.
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
index 09205efff3a1a2042e67600510af270df9e46c67..775d93d35855e394e5eccf0ad80eca43b1ed9562 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
@@ -1547,7 +1547,7 @@ public final class MoonriseRegionFileIO {
public abstract void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException;
- public static record ReadData(ReadResult result, DataInputStream input, CompoundTag syncRead) {
+ public static record ReadData(ReadResult result, DataInputStream input, CompoundTag syncRead, int recalculateCount) { // Paper - Attempt to recalculate regionfile header if it is corrupt
public static enum ReadResult {
NO_DATA,
HAS_DATA,
diff --git a/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
index a6f9435a8e2a6c8cc05bd3873cb6fc0ec98973b8..d44fd9eea0ccb09c894d6431ede8a4c2aae85c5b 100644
--- a/net/minecraft/world/level/chunk/storage/RegionBitmap.java
+++ b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
@@ -9,6 +9,27 @@ import java.util.BitSet;
public class RegionBitmap {
private final BitSet used = new BitSet();
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ public final void copyFrom(RegionBitmap other) {
+ BitSet thisBitset = this.used;
+ BitSet otherBitset = other.used;
+
+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) {
+ thisBitset.set(i, otherBitset.get(i));
+ }
+ }
+
+ public final boolean tryAllocate(int from, int length) {
+ BitSet bitset = this.used;
+ int firstSet = bitset.nextSetBit(from);
+ if (firstSet > 0 && firstSet < (from + length)) {
+ return false;
+ }
+ bitset.set(from, from + length);
+ return true;
+ }
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
+
public void force(final int position, final int size) {
this.used.set(position, position + size);
}
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index d1ea7f442e258b06f5c879de7d57fd16caf09178..5e64762059846f14d4e8ddfec1d1640c199cece2 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -46,6 +46,363 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
@VisibleForTesting
protected final RegionBitmap usedSectors = new RegionBitmap();
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ private static long roundToSectors(long bytes) {
+ long sectors = bytes >>> 12; // 4096 = 2^12
+ long remainingBytes = bytes & 4095;
+ long sign = -remainingBytes; // sign is 1 if nonzero
+ return sectors + (sign >>> 63);
+ }
+
+ private static final net.minecraft.nbt.CompoundTag OVERSIZED_COMPOUND = new net.minecraft.nbt.CompoundTag();
+
+ private net.minecraft.nbt.@Nullable CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
+ try {
+ if (chunkDataLength < 0) {
+ return null;
+ }
+
+ long offset = sector * 4096L + 4L; // offset for chunk data
+
+ if ((offset + chunkDataLength) > fileLength) {
+ return null;
+ }
+
+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength);
+ if (chunkDataLength != this.file.read(chunkData, offset)) {
+ return null;
+ }
+
+ ((java.nio.Buffer)chunkData).flip();
+
+ byte compressionType = chunkData.get();
+ if (compressionType < 0) { // compressionType & 128 != 0
+ // oversized chunk
+ return OVERSIZED_COMPOUND;
+ }
+
+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType);
+ if (compression == null) {
+ return null;
+ }
+
+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
+
+ return ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(new DataInputStream(input)); // Paper - Optimize NBT reading/writing
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private int getLength(long sector) throws IOException {
+ ByteBuffer length = ByteBuffer.allocate(4);
+ if (4 != this.file.read(length, sector * 4096L)) {
+ return -1;
+ }
+
+ return length.getInt(0);
+ }
+
+ private void backupRegionFile() {
+ Path backup = this.path.getParent().resolve(this.path.getFileName() + "." + new java.util.Random().nextLong() + ".backup");
+ this.backupRegionFile(backup);
+ }
+
+ private void backupRegionFile(Path to) {
+ try {
+ this.file.force(true);
+ LOGGER.warn("Backing up regionfile \"{}\" to {}", this.path.toAbsolutePath(), to.toAbsolutePath());
+ Files.copy(this.path, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
+ LOGGER.warn("Backed up the regionfile to {}", to.toAbsolutePath());
+ } catch (IOException ex) {
+ LOGGER.error("Failed to backup to {}", to.toAbsolutePath(), ex);
+ }
+ }
+
+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) {
+ return (first.x() & ~31) == (second.x() & ~31) && (first.z() & ~31) == (second.z() & ~31);
+ }
+
+ // note: only call for CHUNK regionfiles
+ private final java.util.concurrent.atomic.AtomicInteger recalculateCount = new java.util.concurrent.atomic.AtomicInteger();
+
+ public int getRecalculateCount() {
+ return this.recalculateCount.get();
+ }
+
+ boolean recalculateHeader() throws IOException {
+ if (!this.canRecalcHeader) {
+ return false;
+ }
+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.path);
+ if (ourLowerLeftPosition == null) {
+ LOGGER.error("Unable to get chunk location of regionfile {}, cannot recover header", this.path.toAbsolutePath());
+ return false;
+ }
+ synchronized (this) {
+ this.recalculateCount.getAndIncrement();
+
+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile {}", this.path.toAbsolutePath(), new Throwable());
+
+ // try to backup file so maybe it could be sent to us for further investigation
+
+ this.backupRegionFile();
+ net.minecraft.nbt.CompoundTag[] compounds = new net.minecraft.nbt.CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
+ int[] sectorOffsets = new int[32 * 32]; // in sectors
+ boolean[] hasAikarOversized = new boolean[32 * 32];
+
+ long fileLength = this.file.size();
+ long totalSectors = roundToSectors(fileLength);
+
+ // search the regionfile from start to finish for the most up-to-date chunk data
+
+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
+ int chunkDataLength = this.getLength(i);
+ net.minecraft.nbt.CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
+ if (compound == null || compound == OVERSIZED_COMPOUND) {
+ continue;
+ }
+
+ ChunkPos chunkPos = SerializableChunkData.getChunkCoordinate(compound);
+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) {
+ LOGGER.error("Ignoring absolute chunk {} in regionfile as it is not contained in the bounds of the regionfile '{}'. It should be in regionfile ({},{})", chunkPos, this.path.toAbsolutePath(), chunkPos.x() >> 5, chunkPos.z() >> 5);
+ continue;
+ }
+ int location = (chunkPos.x() & 31) | ((chunkPos.z() & 31) << 5);
+
+ net.minecraft.nbt.CompoundTag otherCompound = compounds[location];
+
+ if (otherCompound != null && SerializableChunkData.getLastWorldSaveTime(otherCompound) > SerializableChunkData.getLastWorldSaveTime(compound)) {
+ continue; // don't overwrite newer data.
+ }
+
+ // aikar oversized?
+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x(), chunkPos.z());
+ boolean isAikarOversized = false;
+ if (Files.exists(aikarOversizedFile)) {
+ try {
+ net.minecraft.nbt.CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x(), chunkPos.z());
+ if (SerializableChunkData.getLastWorldSaveTime(compound) == SerializableChunkData.getLastWorldSaveTime(aikarOversizedCompound)) {
+ // best we got for an id. hope it's good enough
+ isAikarOversized = true;
+ }
+ } catch (Exception ex) {
+ LOGGER.error("Failed to read aikar oversized data for absolute chunk ({},{}) in regionfile {}, oversized data for this chunk will be lost", chunkPos.x(), chunkPos.z(), this.path.toAbsolutePath(), ex);
+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data
+ }
+ }
+
+ hasAikarOversized[location] = isAikarOversized;
+ compounds[location] = compound;
+ rawLengths[location] = chunkDataLength + 4;
+ sectorOffsets[location] = (int)i;
+
+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]);
+ i += chunkSectorLength;
+ --i; // gets incremented next iteration
+ }
+
+ // forge style oversized data is already handled by the local search, and aikar data we just hope
+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding
+ // local data compound
+
+ java.nio.file.Path containingFolder = this.externalFileDir;
+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new);
+ boolean[] oversized = new boolean[32 * 32];
+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32];
+
+ if (regionFiles != null) {
+ int lowerXBound = ourLowerLeftPosition.x(); // inclusive
+ int lowerZBound = ourLowerLeftPosition.z(); // inclusive
+ int upperXBound = lowerXBound + 32 - 1; // inclusive
+ int upperZBound = lowerZBound + 32 - 1; // inclusive
+
+ // read mojang oversized data
+ for (Path regionFile : regionFiles) {
+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile);
+ if (oversizedCoords == null) {
+ continue;
+ }
+
+ if ((oversizedCoords.x() < lowerXBound || oversizedCoords.x() > upperXBound) || (oversizedCoords.z() < lowerZBound || oversizedCoords.z() > upperZBound)) {
+ continue; // not in our regionfile
+ }
+
+ // ensure oversized data is valid & is newer than data in the regionfile
+
+ int location = (oversizedCoords.x() & 31) | ((oversizedCoords.z() & 31) << 5);
+
+ byte[] chunkData;
+ try {
+ chunkData = Files.readAllBytes(regionFile);
+ } catch (Exception ex) {
+ LOGGER.error("Failed to read oversized chunk data in file {}, data will be lost", regionFile.toAbsolutePath(), ex);
+ continue;
+ }
+
+ net.minecraft.nbt.CompoundTag compound = null;
+
+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
+ RegionFileVersion compression = null;
+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
+ try {
+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java
+ compound = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead((java.io.DataInput)in); // Paper - Optimize NBT reading/writing
+ compression = compressionType;
+ break; // reaches here iff readNBT does not throw
+ } catch (Exception ex) {
+ continue;
+ }
+ }
+
+ if (compound == null) {
+ LOGGER.error("Failed to read oversized chunk data in file {}, it's corrupt. Its data will be lost", regionFile.toAbsolutePath());
+ continue;
+ }
+
+ if (!SerializableChunkData.getChunkCoordinate(compound).equals(oversizedCoords)) {
+ LOGGER.error("Can't use oversized chunk stored in {}, got absolute chunkpos: {}, expected {}", regionFile.toAbsolutePath(), net.minecraft.world.level.chunk.storage.SerializableChunkData.getChunkCoordinate(compound), oversizedCoords);
+ continue;
+ }
+
+ if (compounds[location] == null || SerializableChunkData.getLastWorldSaveTime(compound) > SerializableChunkData.getLastWorldSaveTime(compounds[location])) {
+ oversized[location] = true;
+ oversizedCompressionTypes[location] = compression;
+ }
+ }
+ }
+
+ // now we need to calculate a new offset header
+
+ int[] calculatedOffsets = new int[32 * 32];
+ RegionBitmap newSectorAllocations = new RegionBitmap();
+ newSectorAllocations.force(0, 2); // make space for header
+
+ // allocate sectors for normal chunks
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ if (oversized[location]) {
+ continue;
+ }
+
+ int rawLength = rawLengths[location]; // bytes
+ int sectorOffset = sectorOffsets[location]; // sectors
+ int sectorLength = (int)roundToSectors(rawLength);
+
+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) {
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
+ } else {
+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at ({},{}) in regionfile {}, chunk will be regenerated", chunkX, chunkZ, this.path.toAbsolutePath());
+ }
+ }
+ }
+
+ // allocate sectors for oversized chunks
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ if (!oversized[location]) {
+ continue;
+ }
+
+ int sectorOffset = newSectorAllocations.allocate(1);
+ int sectorLength = 1;
+
+ try {
+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096);
+ // only allocate in the new offsets if the write succeeds
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
+ } catch (IOException ex) {
+ newSectorAllocations.free(sectorOffset, sectorLength);
+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at ({},{}) in regionfile {} will be regenerated", chunkX, chunkZ, this.path.toAbsolutePath());
+ }
+ }
+ }
+
+ // rewrite aikar oversized data
+
+ this.oversizedCount = 0;
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0;
+
+ this.oversizedCount += isAikarOversized;
+ this.oversized[location] = (byte)isAikarOversized;
+ }
+ }
+
+ if (this.oversizedCount > 0) {
+ try {
+ this.writeOversizedMeta();
+ } catch (Exception ex) {
+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile {}", this.path.toAbsolutePath(), ex);
+ Files.deleteIfExists(this.getOversizedMetaFile());
+ }
+ } else {
+ Files.deleteIfExists(this.getOversizedMetaFile());
+ }
+
+ this.usedSectors.copyFrom(newSectorAllocations);
+
+ // before we overwrite the old sectors, print a summary of the chunks that got changed.
+
+ LOGGER.info("Starting summary of changes for regionfile {}", this.path.toAbsolutePath());
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ int oldOffset = this.offsets.get(location);
+ int newOffset = calculatedOffsets[location];
+
+ if (oldOffset == newOffset) {
+ continue;
+ }
+
+ this.offsets.put(location, newOffset); // overwrite incorrect offset
+
+ if (oldOffset == 0) {
+ // found lost data
+ LOGGER.info("Found missing data for local chunk ({},{}) in regionfile {}", chunkX, chunkZ, this.path.toAbsolutePath());
+ } else if (newOffset == 0) {
+ LOGGER.warn("Data for local chunk ({},{}) could not be recovered in regionfile {}, it will be regenerated", chunkX, chunkZ, this.path.toAbsolutePath());
+ } else {
+ LOGGER.info("Local chunk ({},{}) changed to point to newer data or correct chunk in regionfile {}", chunkX, chunkZ, this.path.toAbsolutePath());
+ }
+ }
+ }
+
+ LOGGER.info("End of change summary for regionfile {}", this.path.toAbsolutePath());
+
+ // simply destroy the timestamp header, it's not used
+
+ for (int i = 0; i < 32 * 32; ++i) {
+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? RegionFile.getTimestamp() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this
+ }
+
+ // write new header
+ try {
+ this.flush();
+ this.file.force(true); // try to ensure it goes through...
+ LOGGER.info("Successfully wrote new header to disk for regionfile {}", this.path.toAbsolutePath());
+ } catch (IOException ex) {
+ LOGGER.error("Failed to write new header to disk for regionfile {}", this.path.toAbsolutePath(), ex);
+ }
+ }
+
+ return true;
+ }
+
+ final boolean canRecalcHeader; // final forces compile fail on new constructor
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
+
// Paper start - rewrite chunk system
@Override
public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final net.minecraft.nbt.CompoundTag data, final ChunkPos pos) throws IOException {
@@ -75,6 +432,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
}
this.externalFileDir = externalFileDir;
+ this.canRecalcHeader = info.dfuType()[0] == net.minecraft.util.datafix.DataFixTypes.CHUNK; // Paper - add can recalc flag
this.offsets = this.header.asIntBuffer();
this.offsets.limit(1024);
this.header.position(4096);
@@ -95,11 +453,13 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
long size = Files.size(path);
- for (int i = 0; i < 1024; i++) {
+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption
+ for (int i = 0; i < 1024; i++) { final int headerLocation = i; // Paper - diff on change, we expect this to be the header location
int offset = this.offsets.get(i);
if (offset != 0) {
- int sectorNumber = getSectorNumber(offset);
- int numSectors = getNumSectors(offset);
+ int sectorNumber = getSectorNumber(offset); // Paper - diff on change, we expect this to be offset in file in sectors
+ int numSectors = getNumSectors(offset); // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
// Spigot start
if (numSectors == 255) {
// We're maxed out, so we need to read the proper length from the section
@@ -110,18 +470,62 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
// Spigot end
if (sectorNumber < 2) {
LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", path, i, sectorNumber);
- this.offsets.put(i, 0);
+ //this.offsets.put(i, 0); // Paper - we catch this, but need it in the header for the summary change
} else if (numSectors == 0) {
LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, i);
- this.offsets.put(i, 0);
+ //this.offsets.put(i, 0); // Paper - we catch this, but need it in the header for the summary change
} else if (sectorNumber * 4096L > size) {
LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", path, i, sectorNumber);
- this.offsets.put(i, 0);
+ //this.offsets.put(i, 0); // Paper - we catch this, but need it in the header for the summary change
} else {
- this.usedSectors.force(sectorNumber, numSectors);
+ //this.usedSectors.force(sectorNumber, numSectors); // Paper - move this down so we can check if it fails to allocate
+ }
+ // Paper start - recalculate header on header corruption
+ if (sectorNumber < 2 || numSectors <= 0 || ((long)sectorNumber * 4096L) > size) {
+ if (this.canRecalcHeader) {
+ LOGGER.error("Detected invalid header for regionfile {}! Recalculating header...", this.path.toAbsolutePath());
+ needsHeaderRecalc = true;
+ break;
+ } else {
+ // location = chunkX | (chunkZ << 5);
+ LOGGER.error("Detected invalid header for regionfile {}! Cannot recalculate, removing local chunk ({},{}) from header",
+ this.path.toAbsolutePath(), headerLocation & 31, headerLocation >>> 5);
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.offsets.put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ }
+ boolean failedToAllocate = !this.usedSectors.tryAllocate(sectorNumber, numSectors);
+ if (failedToAllocate) {
+ LOGGER.error("Overlapping allocation by local chunk ({},{}) in regionfile {}", headerLocation & 31, headerLocation >>> 5, this.path.toAbsolutePath());
}
+ if (failedToAllocate & !this.canRecalcHeader) {
+ // location = chunkX | (chunkZ << 5);
+ LOGGER.error("Detected invalid header for regionfile {}! Cannot recalculate, removing local chunk ({},{}) from header",
+ this.path.toAbsolutePath(), headerLocation & 31, headerLocation >>> 5);
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.offsets.put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ needsHeaderRecalc |= failedToAllocate;
+ // Paper end - recalculate header on header corruption
}
}
+ // Paper start - recalculate header on header corruption
+ // we move the recalc here so comparison to old header is correct when logging to console
+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues
+ LOGGER.error("Recalculating regionfile {}, header gave erroneous offsets & locations", this.path.toAbsolutePath());
+ this.recalculateHeader();
+ }
+ // Paper end
}
}
@@ -130,10 +534,34 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
}
private Path getExternalChunkPath(final ChunkPos pos) {
- String externalFileName = "c." + pos.x() + "." + pos.z() + ".mcc";
+ String externalFileName = "c." + pos.x() + "." + pos.z() + ".mcc"; // Paper - diff on change
return this.externalFileDir.resolve(externalFileName);
}
+ // Paper start
+ private static @Nullable ChunkPos getOversizedChunkPair(Path file) {
+ String fileName = file.getFileName().toString();
+
+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
+ return null;
+ }
+
+ String[] split = fileName.split("\\.");
+
+ if (split.length != 4) {
+ return null;
+ }
+
+ try {
+ int x = Integer.parseInt(split[1]);
+ int z = Integer.parseInt(split[2]);
+
+ return new ChunkPos(x, z);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+
public synchronized @Nullable DataInputStream getChunkDataInputStream(final ChunkPos pos) throws IOException {
int offset = this.getOffset(pos);
if (offset == 0) {
@@ -155,6 +583,11 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
buffer.flip();
if (buffer.remaining() < 5) {
LOGGER.error("Chunk {} header is truncated: expected {} but read {}", pos, sectorsLength, buffer.remaining());
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
}
@@ -162,6 +595,11 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
byte versionId = buffer.get();
if (length == 0) {
LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
}
@@ -169,18 +607,45 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
if (isExternalStreamChunk(versionId)) {
if (streamLength != 0) {
LOGGER.warn("Chunk has both internal and external streams");
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
}
- return this.createExternalChunkInputStream(pos, getExternalChunkVersion(versionId));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createExternalChunkInputStream(pos, getExternalChunkVersion(versionId));
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ return ret;
+ // Paper end - recalculate header on regionfile corruption
} else if (streamLength > buffer.remaining()) {
LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", pos, streamLength, buffer.remaining());
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else if (streamLength < 0) {
LOGGER.error("Declared size {} of chunk {} is negative", length, pos);
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
JvmProfiler.INSTANCE.onRegionFileRead(this.info, pos, this.version, streamLength);
- return this.createChunkInputStream(pos, versionId, createStream(buffer, streamLength));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createChunkInputStream(pos, versionId, createStream(buffer, streamLength));
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
+ return this.getChunkDataInputStream(pos);
+ }
+ return ret;
+ // Paper end - recalculate header on regionfile corruption
}
}
@@ -357,9 +822,14 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
}
private ByteBuffer createExternalStub() {
+ // Paper start - add version param
+ return this.createExternalStub(this.version);
+ }
+ private ByteBuffer createExternalStub(RegionFileVersion version) {
+ // Paper end - add version param
ByteBuffer stub = ByteBuffer.allocate(5);
stub.putInt(1);
- stub.put((byte)(this.version.getId() | EXTERNAL_STREAM_FLAG));
+ stub.put((byte)(version.getId() | EXTERNAL_STREAM_FLAG)); // Paper - add version param
stub.flip();
return stub;
}
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index 4d3abebdb8b5f952837acb3093ff6e04c44b08e4..6db9314f12dd69ab82d689ab77b7cc6bd53af102 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -27,6 +27,32 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
private final Path folder;
private final boolean sync;
+ // Paper start - recalculate region file headers
+ private final boolean isChunkData;
+
+ @Nullable
+ public static ChunkPos getRegionFileCoordinates(Path file) {
+ String fileName = file.getFileName().toString();
+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
+ return null;
+ }
+
+ String[] split = fileName.split("\\.");
+
+ if (split.length != 4) {
+ return null;
+ }
+
+ try {
+ int x = Integer.parseInt(split[1]);
+ int z = Integer.parseInt(split[2]);
+
+ return new ChunkPos(x << 5, z << 5);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+ // Paper end
// Paper start - rewrite chunk system
private static final int REGION_SHIFT = 5;
private static final int MAX_NON_EXISTING_CACHE = 1024 * 4;
@@ -144,12 +170,12 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
if (input == null) {
return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData(
- ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.NO_DATA, null, null
+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.NO_DATA, null, null, regionFile == null ? 0 : regionFile.getRecalculateCount() // Paper - Attempt to recalculate regionfile header if it is corrupt
);
}
final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData ret = new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData(
- ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.HAS_DATA, input, null
+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.HAS_DATA, input, null, regionFile.getRecalculateCount() // Paper - Attempt to recalculate regionfile header if it is corrupt
);
if (!(input instanceof ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker)) {
@@ -165,7 +191,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
}
return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData(
- ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.SYNC_READ, null, syncRead
+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.SYNC_READ, null, syncRead, regionFile.getRecalculateCount() // Paper - Attempt to recalculate regionfile header if it is corrupt
);
}
@@ -175,7 +201,32 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
final int chunkX, final int chunkZ, final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData readData
) throws IOException {
try {
- return ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(readData.input()); // Paper - Optimize NBT reading/writing
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ final CompoundTag ret = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(readData.input()); // Paper - Optimize NBT reading/writing
+ if (!this.isChunkData) {
+ return ret;
+ }
+
+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
+ final ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(ret);
+ final RegionFile regionFile = this.getRegionFile(pos, true);
+
+ if (regionFile.getRecalculateCount() != readData.recalculateCount()) {
+ return null;
+ }
+
+ if (!headerChunkPos.equals(pos)) {
+ LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + headerChunkPos + " instead! Attempting regionfile recalculation " + regionFile.getPath().toAbsolutePath());
+ if (regionFile.recalculateHeader()) {
+ return null;
+ }
+
+ LOGGER.error(com.mojang.logging.LogUtils.FATAL_MARKER, "Can't recalculate regionfile header?");
+ return ret;
+ }
+
+ return ret;
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
} finally {
readData.input().close();
}
@@ -186,6 +237,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
this.folder = folder;
this.sync = sync;
this.info = info;
+ this.isChunkData = info.dfuType()[0] == net.minecraft.util.datafix.DataFixTypes.CHUNK; // Paper - recalculate region file headers
}
private @Nullable RegionFile getRegionFile(final ChunkPos pos, final boolean create) throws IOException {
@@ -293,7 +345,25 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
// Paper end
try (DataInputStream regionChunkInputStream = region.getChunkDataInputStream(pos)) {
- return regionChunkInputStream == null ? null : ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(regionChunkInputStream); // Paper - Optimize NBT reading/writing
+ // Paper start - recover from corrupt regionfile header
+ if (regionChunkInputStream == null) {
+ return null;
+ }
+
+ final CompoundTag serialisedChunkData = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(regionChunkInputStream); // Paper - Optimize NBT reading/writing
+ if (this.isChunkData) {
+ ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(serialisedChunkData);
+ if (!headerChunkPos.equals(pos)) {
+ LOGGER.error("Attempting to read chunk data at {} but got chunk data for {} instead! Attempting regionfile recalculation for regionfile {}", pos, headerChunkPos, region.getPath().toAbsolutePath());
+ if (region.recalculateHeader()) {
+ return this.read(pos);
+ }
+ LOGGER.error("Can't recalculate regionfile header, regenerating chunk {} for {}", pos, region.getPath().toAbsolutePath());
+ return null;
+ }
+ }
+ return serialisedChunkData;
+ // Paper end - recover from corrupt regionfile header
}
}
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
index ea229f63f81c5638ded74c28e1e13d8948f2e5e0..d991fccfc3d2cc51611f802a6d40429024e21d9a 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -21,7 +21,7 @@ import org.slf4j.Logger;
public class RegionFileVersion {
private static final Logger LOGGER = LogUtils.getLogger();
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
public static final RegionFileVersion VERSION_GZIP = register(
new RegionFileVersion(1, "gzip", in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out))) // Paper - Enable for Configurable region compression format
diff --git a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java b/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
index 590bd1ca60956e5c5be58481f97401e55f9e1aeb..4e0c9fa1eecfa9f21285b7911e25830bbcbc8d86 100644
--- a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
+++ b/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
@@ -3,7 +3,12 @@ package net.minecraft.world.level.chunk.storage;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
-public record RegionStorageInfo(String level, ResourceKey<Level> dimension, String type) {
+// Paper start - crazy hack to indicate what type the region file is
+public record RegionStorageInfo(String level, ResourceKey<Level> dimension, String type, net.minecraft.util.datafix.DataFixTypes[] dfuType) {
+ public RegionStorageInfo(String level, ResourceKey<Level> dimension, String type) {
+ this(level, dimension, type, new net.minecraft.util.datafix.DataFixTypes[1]);
+ }
+ // Paper end - crazy hack to indicate what type the region file is
public RegionStorageInfo withTypeSuffix(final String suffix) {
return new RegionStorageInfo(this.level, this.dimension, this.type + suffix);
}
diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
index 84a92dc868e61969460dddb156af332e6384bcd1..3fef98e8c3c9908c6f05ded33596cbcb18996c3d 100644
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
@@ -119,6 +119,18 @@ public record SerializableChunkData(
}
}
// Paper end - guard against serializing mismatching coordinates
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
+ // TODO: Check on update
+ public static long getLastWorldSaveTime(final CompoundTag chunkData) {
+ final int dataVersion = NbtUtils.getDataVersion(chunkData);
+ if (dataVersion < 2842) { // Level tag is removed after this version
+ final CompoundTag levelData = chunkData.getCompoundOrEmpty("Level");
+ return levelData.getLongOr("LastUpdate", 0L);
+ } else {
+ return chunkData.getLongOr("LastUpdate", 0L);
+ }
+ }
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
// Paper start - Do not let the server load chunks from newer versions
private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().dataVersion().version();
@@ -528,7 +540,7 @@ public record SerializableChunkData(
tag.putInt("xPos", this.chunkPos.x());
tag.putInt("yPos", this.minSectionY);
tag.putInt("zPos", this.chunkPos.z());
- tag.putLong("LastUpdate", this.lastUpdateTime);
+ tag.putLong("LastUpdate", this.lastUpdateTime); // Paper - Diff on change
tag.putLong("InhabitedTime", this.inhabitedTime);
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
tag.storeNullable("blending_data", BlendingData.Packed.CODEC, this.blendingData);
diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
index cdbe291d2053239e3b8dd0042dfb475306a6b6e5..12ac13394fc38f089be0ad7731eb4ea9487e1d17 100644
--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
@@ -34,6 +34,7 @@ public class SimpleRegionStorage implements ca.spottedleaf.moonrise.patches.chun
public SimpleRegionStorage(
final RegionStorageInfo info, final Path folder, final DataFixer fixerUpper, final boolean syncWrites, final DataFixTypes dataFixType
) {
+ info.dfuType()[0] = dataFixType; // Paper - crazy hack to indicate what type the region file is
this.fixerUpper = fixerUpper;
this.dataFixType = dataFixType;
this.storage = new IOWorker(info, folder, syncWrites).storage; // Paper - rewrite chunk system
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index d920a4905d044925c4c4ab980fc238c9c489d5ac..60decc2534c5bc52b1a22e23a416a7660f724cb0 100644
index 19c7dffe5f9c45cd99cb9b14cae5ac908cae0afc..1c92e33104cc131269be15333eeee0ca5f0c92b1 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -647,6 +647,7 @@ public class ServerGamePacketListenerImpl
@@ -42,7 +42,7 @@ index 0a60283625536515bfcf6c2a09ca0f4771d6b142..42500899812fe3ef18c09f68a85012b7
protected ChunkGenerator generator() {
return this.worldGenContext.generator();
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
index 8f2c37a3a534b3b0d4f3ffd2a0d7013a9b454244..f1ca359abcc422e791c81e036b4777643bce53b3 100644
index 2dc8b94c434e841b319ef5b77bd725fd66f3c10b..a5540612a4052d9e9d8b1b0ac3c1591777f26caa 100644
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -547,7 +547,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
@@ -75,7 +75,7 @@ index 8f2c37a3a534b3b0d4f3ffd2a0d7013a9b454244..f1ca359abcc422e791c81e036b477764
profiler.popPush("tickSpawningChunks");
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 60db677d06207b488aad0363af55012b2d76fe8b..eeeb4075a6bbc4e310c2ad685f4efeade702ee3f 100644
index 2cf8854636df72c1dccbb73dfe8f642dfdf1ea37..99403edc1eda1e238b66028b7d236e5fceb235a1 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -422,6 +422,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -37,7 +37,7 @@ index 42500899812fe3ef18c09f68a85012b705c1010d..fdcbf132e2045bc6828028556ca4c354
// Paper end - Optional per player mob spawns
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
index f1ca359abcc422e791c81e036b4777643bce53b3..ef53d4d17222a882dd3d33575096c864a0a00a06 100644
index a5540612a4052d9e9d8b1b0ac3c1591777f26caa..c4cfb53e145d69666ffc27ef4ee0c50b54eb9143 100644
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -552,7 +552,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
@@ -60,7 +60,7 @@ index f1ca359abcc422e791c81e036b4777643bce53b3..ef53d4d17222a882dd3d33575096c864
spawnCookie = NaturalSpawner.createState(chunkCount, this.level, this::getFullChunk, null, true);
} else {
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index eeeb4075a6bbc4e310c2ad685f4efeade702ee3f..1137be30f125cb79f05f3e5c104cdf8cca979ff1 100644
index 99403edc1eda1e238b66028b7d236e5fceb235a1..23b3a82ab3e1c6abc85000d9285470cb12300a6b 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -426,6 +426,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -5,7 +5,7 @@ Subject: [PATCH] DataConverter, Moonrise & co fixes
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 60decc2534c5bc52b1a22e23a416a7660f724cb0..8178922f5ecc7f6709335c7fbeb6dca505b044bb 100644
index 1c92e33104cc131269be15333eeee0ca5f0c92b1..6dc9e669a37ddc0a17099bcc4839b42fe237cf99 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1857,9 +1857,14 @@ public class ServerGamePacketListenerImpl
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
+ }
+}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index bd5efcf1dee77c204bf29ef4693ac54146e8d901..7517b872794f3bbe556f3abbeb294f426c8da899 100644
index b675e9322234761a69e71e9e8b9d41364caf34b6..c3bf41af0273e902f881cc070fcef6dd64abb063 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1799,6 +1799,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1797,6 +1797,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
level.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
level.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
level.updateLagCompensationTick(); // Paper - lag compensation
@@ -1,6 +1,6 @@
--- /dev/null
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -1,0 +_,271 @@
@@ -1,0 +_,260 @@
+package ca.spottedleaf.moonrise.paper;
+
+import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -13,7 +13,6 @@
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.FullChunkStatus;
+import net.minecraft.server.level.GenerationChunkHolder;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
@@ -89,11 +88,6 @@
+ }
+
+ @Override
+ public void onFullChunkStatusChange(final LevelChunk chunk, final FullChunkStatus from, final FullChunkStatus to) {
+
+ }
+
+ @Override
+ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
+
+ }
@@ -168,11 +162,6 @@
+ }
+
+ @Override
+ public boolean configFixMC119599(final ServerLevel world) {
+ return world.paperConfig().environment.fixMissingTreeSnow;
+ }
+
+ @Override
+ public boolean configAutoConfigSendDistance() {
+ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
+ }
@@ -1,25 +1,25 @@
--- a/com/mojang/authlib/services/MinecraftServicesDiscoveryService.java
+++ b/com/mojang/authlib/services/MinecraftServicesDiscoveryService.java
@@ -37,7 +_,7 @@
private final boolean servicesKeySetEnabled;
@@ -40,7 +_,7 @@
private final Supplier<DiscoveryResponse> discoverySupplier;
private final String clientVersion;
- private MinecraftServicesDiscoveryService(
+ protected MinecraftServicesDiscoveryService( // Paper - private->protected
final Proxy proxy,
final boolean servicesKeySetEnabled,
final Supplier<DiscoveryResponse> discoverySupplier
@@ -68,7 +_,7 @@
final Supplier<DiscoveryResponse> discoverySupplier,
@@ -75,7 +_,7 @@
);
}
- private static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment) {
+ protected static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment) { // Paper - private->protected
final MinecraftClient client = MinecraftClient.unauthenticated(proxy);
- private static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment, final String clientVersion) {
+ protected static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment, final String clientVersion) { // Paper - private->protected
final MinecraftClient client = MinecraftClient.unauthenticated(proxy, clientVersion);
return RetryableFetch.fetch(
() -> HttpDiscoveryService.constantURL(environment.discoveryUrl()),
@@ -84,7 +_,7 @@
return new MinecraftServicesDiscoveryService(proxy, false, DiscoveryResponse::offline);
@@ -95,7 +_,7 @@
return clientVersion;
}
- private static Environment determineEnvironment() {
@@ -1,6 +1,6 @@
--- a/com/mojang/authlib/services/MinecraftServicesProfileRepository.java
+++ b/com/mojang/authlib/services/MinecraftServicesProfileRepository.java
@@ -42,6 +_,8 @@
@@ -44,6 +_,8 @@
.filter(name -> !Strings.isNullOrEmpty(name))
.collect(Collectors.toSet());
@@ -9,7 +9,7 @@
for (final List<String> request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) {
final List<String> normalizedRequest = request.stream().map(MinecraftServicesProfileRepository::normalizeName).toList();
@@ -73,6 +_,13 @@
@@ -75,6 +_,13 @@
callback.onProfileLookupFailed(name, new ProfileNotFoundException("Server did not find the requested profile"));
}
@@ -23,7 +23,7 @@
try {
Thread.sleep(DELAY_BETWEEN_PAGES);
} catch (final InterruptedException ignored) {
@@ -102,7 +_,15 @@
@@ -104,7 +_,15 @@
try {
return Optional.ofNullable(client.get(HttpDiscoveryService.constantURL(discoveryService.getUrl(Service.PROFILES, "getByName").replace("{name}", normalizeName(name))), NameAndId.class));
} catch (final MinecraftClientException e) {
@@ -9,7 +9,7 @@
this.nodes.remove(node.holder().id());
}
@@ -46,7 +_,7 @@
@@ -47,7 +_,7 @@
}
}
@@ -113,8 +113,8 @@
@Override
public Collection<String> getOnlinePlayerNames() {
- return Lists.newArrayList(this.server.getPlayerNames());
+ return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().name()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players
- return List.of(this.server.getPlayerNames());
+ return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().name()).toList() : List.of(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players
}
@Override
@@ -1,6 +1,6 @@
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -159,6 +_,7 @@
@@ -160,6 +_,7 @@
import org.slf4j.Logger;
public class Commands {
@@ -8,7 +8,7 @@
public static final String COMMAND_PREFIX = "/";
private static final ThreadLocal<@Nullable ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal<>();
private static final Logger LOGGER = LogUtils.getLogger();
@@ -183,6 +_,7 @@
@@ -184,6 +_,7 @@
@Override
public boolean isRestricted(final CommandNode<CommandSourceStack> node) {
@@ -16,7 +16,7 @@
Predicate<CommandSourceStack> requirement = node.getRequirement();
return !requirement.test(this.noPermissionSource);
}
@@ -190,6 +_,11 @@
@@ -191,6 +_,11 @@
private final CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();
public Commands(final Commands.CommandSelection commandSelection, final CommandBuildContext context) {
@@ -28,7 +28,7 @@
AdvancementCommands.register(this.dispatcher);
AttributeCommand.register(this.dispatcher, context);
ExecuteCommand.register(this.dispatcher, context);
@@ -304,6 +_,42 @@
@@ -305,6 +_,42 @@
UnpublishCommand.register(this.dispatcher);
}
@@ -71,7 +71,7 @@
this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
}
@@ -323,6 +_,13 @@
@@ -324,6 +_,13 @@
}
public void performCommand(final ParseResults<CommandSourceStack> command, final String commandString) {
@@ -85,7 +85,7 @@
CommandSourceStack sender = command.getContext().getSource();
Profiler.get().push(() -> "/" + commandString);
ContextChain<CommandSourceStack> commandChain = finishParsing(command, commandString, sender);
@@ -336,10 +_,12 @@
@@ -337,10 +_,12 @@
)
);
}
@@ -101,7 +101,7 @@
StackTraceElement[] stackTrace = e.getStackTrace();
for (int i = 0; i < Math.min(stackTrace.length, 3); i++) {
@@ -370,12 +_,16 @@
@@ -371,12 +_,16 @@
return ContextChain.tryFlatten(command.getContext().build(commandString))
.orElseThrow(() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(command.getReader()));
} catch (CommandSyntaxException e) {
@@ -120,7 +120,7 @@
if (cursor > 10) {
context.append(CommonComponents.ELLIPSIS);
}
@@ -387,7 +_,17 @@
@@ -388,7 +_,17 @@
}
context.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
@@ -139,7 +139,7 @@
}
return null;
@@ -415,10 +_,65 @@
@@ -416,10 +_,65 @@
}
public void sendCommands(final ServerPlayer player) {
@@ -207,7 +207,7 @@
player.connection.send(new ClientboundCommandsPacket(root, COMMAND_NODE_INSPECTOR));
}
@@ -426,8 +_,49 @@
@@ -427,8 +_,49 @@
final CommandNode<S> source, final CommandNode<S> target, final S commandFilter, final Map<CommandNode<S>, CommandNode<S>> converted
) {
for (CommandNode<S> child : source.getChildren()) {
@@ -4,8 +4,8 @@
private final StringReader reader;
private final boolean forTesting;
private final boolean allowNbt;
- private final Map<Property<?>, Comparable<?>> properties = Maps.newHashMap();
+ private final Map<Property<?>, Comparable<?>> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable
private final Map<String, String> vagueProperties = Maps.newHashMap();
- private final Map<Property<?>, Comparable<?>> properties = new HashMap<>();
+ private final Map<Property<?>, Comparable<?>> properties = new java.util.LinkedHashMap<>(); // CraftBukkit - stable
private final Map<String, String> vagueProperties = new HashMap<>();
private Identifier id = Identifier.withDefaultNamespace("");
private @Nullable StateDefinition<Block, BlockState> definition;
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/BlockPos.java
+++ b/net/minecraft/core/BlockPos.java
@@ -141,67 +_,84 @@
@@ -139,67 +_,84 @@
@Override
public BlockPos above() {
@@ -98,7 +98,7 @@
}
@Override
@@ -667,9 +_,9 @@
@@ -698,9 +_,9 @@
}
public BlockPos.MutableBlockPos set(final int x, final int y, final int z) {
@@ -111,7 +111,7 @@
return this;
}
@@ -727,19 +_,19 @@
@@ -758,19 +_,19 @@
@Override
public BlockPos.MutableBlockPos setX(final int x) {
@@ -1,11 +0,0 @@
--- a/net/minecraft/core/Holder.java
+++ b/net/minecraft/core/Holder.java
@@ -252,7 +_,7 @@
}
void bindTags(final Collection<TagKey<T>> tags) {
- this.tags = Set.copyOf(tags);
+ this.tags = it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper - use reference set because TagKey are interned
}
public void bindComponents(final DataComponentMap components) {
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/MappedRegistry.java
+++ b/net/minecraft/core/MappedRegistry.java
@@ -33,17 +_,25 @@
@@ -34,17 +_,25 @@
public class MappedRegistry<T> implements WritableRegistry<T> {
private final ResourceKey<? extends Registry<T>> key;
private final ObjectList<Holder.Reference<T>> byId = new ObjectArrayList<>(256);
@@ -31,7 +31,7 @@
@Override
public Stream<HolderSet.Named<T>> listTags() {
@@ -117,6 +_,7 @@
@@ -118,6 +_,7 @@
this.toId.put(value, newId);
this.registrationInfos.put(key, registrationInfo);
this.registryLifecycle = this.registryLifecycle.add(registrationInfo.lifecycle());
@@ -39,7 +39,7 @@
return holder;
}
@@ -278,6 +_,7 @@
@@ -279,6 +_,7 @@
}
this.frozen = true;
@@ -47,7 +47,7 @@
this.byValue.forEach((value, holder) -> holder.bindValue((T)value));
List<Identifier> unboundEntries = this.byKey
.entrySet()
@@ -517,4 +_,13 @@
@@ -518,4 +_,13 @@
Stream<HolderSet.Named<T>> getTags();
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/Vec3i.java
+++ b/net/minecraft/core/Vec3i.java
@@ -22,9 +_,9 @@
@@ -20,9 +_,9 @@
ByteBufCodecs.VAR_INT, Vec3i::getX, ByteBufCodecs.VAR_INT, Vec3i::getY, ByteBufCodecs.VAR_INT, Vec3i::getZ, Vec3i::new
);
public static final Vec3i ZERO = new Vec3i(0, 0, 0);
@@ -13,7 +13,7 @@
public static Codec<Vec3i> offsetCodec(final int maxOffsetPerAxis) {
return CODEC.validate(
@@ -41,12 +_,12 @@
@@ -39,12 +_,12 @@
}
@Override
@@ -28,7 +28,7 @@
return (this.getY() + this.getZ() * 31) * 31 + this.getX();
}
@@ -59,15 +_,15 @@
@@ -57,15 +_,15 @@
}
}
@@ -47,7 +47,7 @@
return this.z;
}
@@ -251,4 +_,11 @@
@@ -249,4 +_,11 @@
public String toShortString() {
return this.getX() + ", " + this.getY() + ", " + this.getZ();
}
@@ -236,8 +236,8 @@
hand,
itemInHand,
Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3),
- SoundEvents.BUCKET_EMPTY_POWDER_SNOW
+ SoundEvents.BUCKET_EMPTY_POWDER_SNOW, hitDirection // Paper - add hitDirection
- SoundEvents.BUCKET_EMPTY_POWDER_SNOW.value()
+ SoundEvents.BUCKET_EMPTY_POWDER_SNOW.value(), hitDirection // Paper - add hitDirection
);
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/component/BlockTransformer.java
+++ b/net/minecraft/core/component/BlockTransformer.java
@@ -68,6 +_,14 @@
@@ -64,6 +_,14 @@
BlockState updatedShape = transformData.updateFromNeighbors ? Block.updateFromNeighbourShapes(newBlockState, level, pos) : newBlockState;
Player player = context.getPlayer();
ItemStack itemInHand = context.getItemInHand();
@@ -66,7 +66,7 @@
+ itemEntity.setItem(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
+ itemEntity.setDeltaMovement(org.bukkit.craftbukkit.util.CraftVector.toVec3(event.getVelocity()));
+
+ if (source.state().is(net.minecraft.world.level.block.Blocks.DISPENSER) && event.getItem().getType() != craftItem.getType()) {
+ if (source.state().is(net.minecraft.world.level.block.Blocks.DISPENSER) && !event.getItem().getType().equals(craftItem.getType())) {
+ // Chain to handler for new item
+ ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
+ DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(source, eventStack);
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -80,12 +_,38 @@
@@ -83,13 +_,39 @@
Direction direction = source.state().getValue(DispenserBlock.FACING);
BlockPos pos = source.pos().relative(direction);
ServerLevel serverLevel = source.level();
@@ -36,17 +36,17 @@
);
ArmorStand armorStand = EntityTypes.ARMOR_STAND.spawn(serverLevel, postSpawnConfig, pos, EntitySpawnReason.DISPENSER, false, false);
if (armorStand != null) {
ArmorStandItem.playSound(serverLevel, armorStand);
- dispensed.shrink(1);
+ if (shrink) dispensed.shrink(1); // Paper
}
return dispensed;
@@ -103,8 +_,33 @@
@@ -107,9 +_,34 @@
.getEntitiesOfClass(AbstractChestedHorse.class, new AABB(pos), entity -> entity.isAlive() && !entity.hasChest())) {
if (abstractChestedHorse.isTamed()) {
SlotAccess slot = abstractChestedHorse.getSlot(AbstractHorse.CHEST_SLOT_OFFSET);
- if (slot != null && slot.set(dispensed)) {
- dispensed.shrink(1);
+ // CraftBukkit start
+ if (slot != null/* && slot.set(dispensed)*/) {
+ ItemStack singleCopy = dispensed.copyWithCount(1);
@@ -72,12 +72,14 @@
+ }
+ slot.set(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
+ // CraftBukkit end
abstractChestedHorse.playChestEquipsSound();
- dispensed.shrink(1);
+
+ if (shrink) dispensed.shrink(1); // Paper - actually handle here
this.setSuccess(true);
return dispensed;
}
@@ -145,8 +_,45 @@
@@ -150,8 +_,45 @@
DispensibleContainerItem bucket = (DispensibleContainerItem)dispensed.getItem();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
Level level = source.level();
@@ -88,7 +90,7 @@
+ /* Taken from SolidBucketItem#emptyContents */
+ boolean willEmptyContentsSolidBucketItem = bucket instanceof net.minecraft.world.item.SolidBucketItem && level.isInWorldBounds(target) && state.isAir();
+ /* Taken from BucketItem#emptyContents */
+ boolean willEmptyBucketItem = bucket instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.getContent() instanceof net.minecraft.world.level.material.FlowingFluid && (state.isAir() || state.canBeReplaced(bucketItem.getContent()) || (state.getBlock() instanceof net.minecraft.world.level.block.LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, target, state, bucketItem.getContent())));
+ boolean willEmptyBucketItem = bucket instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.getContent() instanceof net.minecraft.world.level.material.FlowingFluid && (state.isAir() || state.canBeReplaced(bucketItem.getContent()) || (state.getBlock() instanceof net.minecraft.world.level.block.LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, target, state, bucketItem.getContent().defaultFluidState())));
+ if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) {
+ // Paper end - correctly check if the bucket place will succeed
+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, source.pos());
@@ -124,7 +126,7 @@
return this.consumeWithRemainder(source, dispensed, new ItemStack(Items.BUCKET));
} else {
return this.defaultDispenseItemBehavior.dispense(source, dispensed);
@@ -170,13 +_,20 @@
@@ -175,7 +_,7 @@
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
BlockState blockState = level.getBlockState(target);
if (blockState.getBlock() instanceof BucketPickup bucket) {
@@ -133,20 +135,21 @@
if (pickup.isEmpty()) {
return super.execute(source, dispensed);
}
@@ -183,6 +_,13 @@
bucket.getPickupSound().ifPresent(soundEvent -> level.playSound(null, target, soundEvent, SoundSource.BLOCKS));
level.gameEvent(null, GameEvent.FLUID_PICKUP, target);
Item targetType = pickup.getItem();
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
level.gameEvent(null, GameEvent.FLUID_PICKUP, target);
Item targetType = pickup.getItem();
+ pickup = bucket.pickupBlock(null, level, target, blockState); // CraftBukkit - from above
return this.consumeWithRemainder(source, dispensed, new ItemStack(targetType));
} else {
return super.execute(source, dispensed);
@@ -190,11 +_,54 @@
@@ -196,11 +_,54 @@
this.setSuccess(true);
Level level = source.level();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
@@ -201,7 +204,7 @@
return dispensed;
}
@@ -209,15 +_,41 @@
@@ -215,15 +_,41 @@
}
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
@@ -250,21 +253,21 @@
this.setSuccess(true);
return dispensed;
}
@@ -231,6 +_,13 @@
@@ -236,6 +_,13 @@
Level level = source.level();
Direction direction = source.state().getValue(DispenserBlock.FACING);
BlockPos target = source.pos().relative(direction);
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
if (level.isEmptyBlock(target) && WitherSkullBlock.canSpawnMob(level, target, dispensed)) {
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
level.setBlockAndUpdate(
target, Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(direction))
);
@@ -242,7 +_,7 @@
@@ -248,7 +_,7 @@
dispensed.shrink(1);
this.setSuccess(true);
} else {
@@ -273,21 +276,21 @@
}
return dispensed;
@@ -256,6 +_,13 @@
@@ -261,6 +_,13 @@
Level level = source.level();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
CarvedPumpkinBlock pumpkinBlock = (CarvedPumpkinBlock)Blocks.CARVED_PUMPKIN;
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
if (level.isEmptyBlock(target) && pumpkinBlock.canSpawnGolem(level, target)) {
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
if (!level.isClientSide()) {
level.setBlockAndUpdate(target, pumpkinBlock.defaultBlockState());
level.gameEvent(null, GameEvent.BLOCK_PLACE, target);
@@ -264,7 +_,7 @@
@@ -270,7 +_,7 @@
dispensed.shrink(1);
this.setSuccess(true);
} else {
@@ -296,31 +299,20 @@
}
return dispensed;
@@ -289,11 +_,23 @@
@@ -296,6 +_,12 @@
ServerLevel level = source.level();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
BlockState state = level.getBlockState(target);
if (state.is(BlockTags.BEEHIVES, s -> s.hasProperty(BeehiveBlock.HONEY_LEVEL) && s.getBlock() instanceof BeehiveBlock)
&& state.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) {
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
if (state.is(BlockTags.BEEHIVES, s -> s.getBlock() instanceof BeehiveBlock) && state.getValueOrElse(BeehiveBlock.HONEY_LEVEL, 0) >= 5) {
((BeehiveBlock)state.getBlock())
.releaseBeesAndResetHoneyLevel(level, state, target, null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED);
this.setSuccess(true);
return this.takeLiquid(source, dispensed, new ItemStack(Items.HONEY_BOTTLE));
} else if (level.getFluidState(target).is(FluidTags.WATER)) {
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, target, dispensed, this);
+ if (result != null) {
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
this.setSuccess(true);
return this.takeLiquid(source, dispensed, PotionContents.createItemStack(Items.POTION, Potions.WATER));
} else {
@@ -312,6 +_,13 @@
@@ -320,6 +_,13 @@
this.setSuccess(true);
if (blockState.is(Blocks.RESPAWN_ANCHOR)) {
if (blockState.getValue(RespawnAnchorBlock.CHARGE) != 4) {
@@ -334,7 +326,7 @@
RespawnAnchorBlock.charge(null, level, pos, blockState);
dispensed.shrink(1);
} else {
@@ -336,6 +_,28 @@
@@ -344,6 +_,28 @@
return dispensed;
}
@@ -363,7 +355,7 @@
for (Armadillo armadillo : armadillos) {
if (armadillo.brushOffScute(null, dispensed)) {
dispensed.hurtAndBreak(16, level, null, item -> {});
@@ -355,6 +_,13 @@
@@ -363,6 +_,13 @@
BlockState blockState = level.getBlockState(pos);
Optional<BlockState> maybeWaxed = HoneycombItem.getWaxed(blockState);
if (maybeWaxed.isPresent()) {
@@ -377,7 +369,7 @@
level.setBlockAndUpdate(pos, maybeWaxed.get());
level.levelEvent(LevelEvent.PARTICLES_WAX_ON, pos, 0);
level.playSound(null, pos, SoundEvents.HONEYCOMB_WAX_ON, SoundSource.BLOCKS, 1.0F, 1.0F);
@@ -385,6 +_,12 @@
@@ -393,6 +_,12 @@
return this.defaultDispenseItemBehavior.dispense(source, dispensed);
}
@@ -1,80 +1,45 @@
--- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
@@ -25,21 +_,55 @@
@@ -24,8 +_,15 @@
ServerLevel level = source.level();
Direction facing = source.state().getValue(DispenserBlock.FACING);
BlockPos targetPos = source.pos().relative(facing);
BlockState target = level.getBlockState(targetPos);
- if (!tryIgniteExplosiveEntities(level, targetPos)) {
+ // Paper start - Call BlockDispenseEvent
+ final @org.jspecify.annotations.Nullable ItemStack[] blockDispenseEventResult = new ItemStack[1];
+ if (!tryIgniteExplosiveEntities(level, targetPos, () ->
+ (blockDispenseEventResult[0] = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this)) == null)
+ ) {
+ // Paper end - Call BlockDispenseEvent
if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) {
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
+ // CraftBukkit start - Ignition by dispensing flint and steel
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, targetPos, source.pos()).isCancelled()) {
level.setBlockAndUpdate(targetPos, BaseFireBlock.getState(level, targetPos));
level.gameEvent(null, GameEvent.BLOCK_PLACE, targetPos);
+ }
+ // CraftBukkit end
} else if (CampfireBlock.canLight(target) || CandleBlock.canLight(target) || CandleCakeBlock.canLight(target)) {
+ // Paper start - Call BlockDispenseEvent
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent
level.setBlockAndUpdate(targetPos, target.setValue(BlockStateProperties.LIT, true));
level.gameEvent(null, GameEvent.BLOCK_CHANGE, targetPos);
} else if (target.getBlock() instanceof TntBlock) {
- if (TntBlock.prime(level, targetPos, null, dispensed)) {
+ // Paper start - Call BlockDispenseEvent / TNTPrimeEvent
+ final java.util.function.BooleanSupplier tntPrimedEvents = () -> (
+ blockDispenseEventResult[0] = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this)) == null
+ && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, source.pos());
+ if (TntBlock.prime(level, targetPos, null, dispensed, tntPrimedEvents)) {
+ // Paper end - Call BlockDispenseEvent / TNTPrimeEvent
level.removeBlock(targetPos, false);
} else {
this.setSuccess(false);
}
}
}
+ // Paper start - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispense events
+ if (blockDispenseEventResult[0] != null) {
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this);
+ if (result != null) {
+ this.setSuccess(false);
+ return blockDispenseEventResult[0];
+ return result;
+ }
+ // Paper end - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispense events
+ // Paper end - Call BlockDispenseEvent
BlockState target = level.getBlockState(targetPos);
- boolean ignited = tryIgnite(dispensed, level, targetPos, facing, target);
+ boolean ignited = tryIgnite(dispensed, level, targetPos, facing, target, source.pos()); // CraftBukkit
this.setSuccess(ignited);
if (this.isSuccess()) {
dispensed.hurtAndBreak(1, level, null, item -> {});
@@ -48,11 +_,18 @@
return dispensed;
FlintAndSteelItem.playSound(level, targetPos);
@@ -36,19 +_,23 @@
}
- private static boolean tryIgniteExplosiveEntities(final ServerLevel level, final BlockPos pos) {
+ private static boolean tryIgniteExplosiveEntities(final ServerLevel level, final BlockPos pos, final java.util.function.BooleanSupplier eventSupplier) { // Paper - call BlockDispenseEvent
List<SulfurCube> entities = level.getEntitiesOfClass(SulfurCube.class, new AABB(pos), SulfurCube::canExplode);
if (entities.isEmpty()) {
return false;
}
+
+ // Paper start - call BlockDispenseEvent
+ // If the event was canceled, we return true and stop further event calls.
+ // Vanilla would only consider other flint and steel dispense behaviors if no sulfur cube was in the AABB
+ // that could explode, which was not the case.
+ if (!eventSupplier.getAsBoolean()) return true;
+ // Paper end - call BlockDispenseEvent
entities.getFirst().primeTime(false);
return true;
private static boolean tryIgnite(
- final ItemStack dispensed, final ServerLevel level, final BlockPos targetPos, final Direction facing, final BlockState target
+ final ItemStack dispensed, final ServerLevel level, final BlockPos targetPos, final Direction facing, final BlockState target, final BlockPos sourcePos // CraftBukkit
) {
if (tryIgniteExplosiveEntities(level, targetPos)) {
return true;
} else if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) {
+ // CraftBukkit start - Ignition by dispensing flint and steel
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, targetPos, sourcePos).isCancelled()) {
level.setBlockAndUpdate(targetPos, BaseFireBlock.getState(level, targetPos));
level.gameEvent(null, GameEvent.BLOCK_PLACE, targetPos);
+ }
+ // CraftBukkit end
return true;
} else if (CampfireBlock.canLight(target) || CandleBlock.canLight(target) || CandleCakeBlock.canLight(target)) {
level.setBlockAndUpdate(targetPos, target.setValue(BlockStateProperties.LIT, true));
level.gameEvent(null, GameEvent.BLOCK_CHANGE, targetPos);
return true;
- } else if (target.getBlock() instanceof TntBlock && TntBlock.prime(level, targetPos, null, dispensed)) {
+ } else if (target.getBlock() instanceof TntBlock && TntBlock.prime(level, targetPos, null, dispensed, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, sourcePos))) { // CraftBukkit - TNTPrimeEvent
level.removeBlock(targetPos, false);
return true;
} else {
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java
@@ -12,14 +_,19 @@
@@ -12,12 +_,19 @@
@Override
protected ItemStack execute(final BlockSource source, final ItemStack dispensed) {
@@ -11,15 +11,14 @@
}
- public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed) {
+ public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed, final BlockSource source, final @org.jspecify.annotations.Nullable DispenseItemBehavior currentBehavior) { // Paper - Call BlockDispenseEvent
+ // Paper start - Call BlockDispenseEvent
+ public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed, final BlockSource source, final @org.jspecify.annotations.Nullable DispenseItemBehavior currentBehavior) {
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, pos, dispensed, currentBehavior);
+ if (result != null) { // todo - snapshot - setItem should probably override the equipped item + possible event in the for loop
+ return false;
+ }
+ // Paper end - Call BlockDispenseEvent
+
for (SulfurCube entity : level.getEntitiesOfClass(SulfurCube.class, new AABB(pos))) {
- if (entity.equipItem(dispensed)) {
+ // Paper start - Call BlockDispenseEvent
+ if (entity.canEquipItem(dispensed)) {
+ final io.papermc.paper.event.block.PaperBlockDispenseEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEventInspectable(source, pos, dispensed, currentBehavior);
+ if (event.result.preventBehaviour()) return true; // Pretend that we succeeded this so we do not call the normal flow.
+ entity.equipItem(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
+ // Paper end - Call BlockDispenseEvent
if (entity.equipItem(dispensed)) {
dispensed.shrink(1);
return true;
}
@@ -1,65 +1,23 @@
--- a/net/minecraft/core/registries/BuiltInRegistries.java
+++ b/net/minecraft/core/registries/BuiltInRegistries.java
@@ -367,6 +_,11 @@
public static final Registry<ContextKeySet> CONTEXT_KEY_SET = registerSimple(Registries.CONTEXT_KEY_SET, LootContextParamSets::bootstrap);
public static final Registry<Consumer<GameTestHelper>> TEST_FUNCTION = registerSimple(Registries.TEST_FUNCTION, BuiltinTestFunctions::bootstrap);
public static final Registry<? extends Registry<?>> REGISTRY = WRITABLE_REGISTRY;
@@ -373,9 +_,19 @@
public static final Registry<ContextKeySet> CONTEXT_KEY_SET = BUILDER.registerSimple(Registries.CONTEXT_KEY_SET, LootContextParamSets::bootstrap);
public static final Registry<Consumer<GameTestHelper>> TEST_FUNCTION = BUILDER.registerSimple(Registries.TEST_FUNCTION, BuiltinTestFunctions::bootstrap);
public static final Registry<? extends Registry<?>> REGISTRY = BUILDER.build();
+ // Paper start - add built-in registry conversions
+ public static final io.papermc.paper.registry.data.util.Conversions STATIC_ACCESS_CONVERSIONS = new io.papermc.paper.registry.data.util.Conversions(
+ new net.minecraft.resources.RegistryOps.HolderLookupAdapter(net.minecraft.server.RegistryLayer.STATIC_ACCESS)
+ );
+ // Paper end - add built-in registry conversions
private static <T> Registry<T> registerSimple(final ResourceKey<? extends Registry<T>> name, final BuiltInRegistries.RegistryBootstrap<T> loader) {
return internalRegister(name, new MappedRegistry<>(name, Lifecycle.stable(), false), loader);
@@ -394,6 +_,7 @@
final ResourceKey<? extends Registry<T>> name, final R registry, final BuiltInRegistries.RegistryBootstrap<T> loader
) {
Bootstrap.checkBootstrapCalled(() -> "registry " + name.identifier());
+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry); // Paper - initialize API registry
Identifier key = name.identifier();
LOADERS.put(key, () -> loader.run(registry));
WRITABLE_REGISTRY.register((ResourceKey)name, registry, RegistrationInfo.BUILT_IN);
@@ -401,16 +_,34 @@
}
public static void bootStrap() {
- BUILDER.bootStrap();
+ // Paper start
+ bootStrap(() -> {});
+ }
+ public static void bootStrap(Runnable runnable) {
+ BUILDER.bootStrap(runnable);
+ // Paper end
+ REGISTRY.freeze(); // Paper - freeze main registry early
createContents();
+ runnable.run(); // Paper
freeze();
validate(REGISTRY);
}
private static void createContents() {
+ // Paper start - class-load org.bukkit.Registry
+ // we have to class-load Registry here to create all the CraftRegistry instances
+ // that will be created when Registry is class-loaded before RegistryAccess#getRegistry
+ // would try to create them in lockReferenceHolder
+ try {
+ Class.forName(org.bukkit.Registry.class.getName());
+ } catch (final ClassNotFoundException ex) {
+ throw new RuntimeException(ex);
+ }
+ // Paper end - class-load org.bukkit.Registry
LOADERS.forEach((key, value) -> {
if (value.get() == null) {
LOGGER.error("Unable to bootstrap registry '{}'", key);
}
+ io.papermc.paper.registry.PaperRegistryAccess.instance().lockReferenceHolders(ResourceKey.createRegistryKey(key)); // Paper - lock reference holder creation
});
}
@@ -419,6 +_,7 @@
for (Registry<?> registry : REGISTRY) {
bindBootstrappedTagsToEmpty(registry);
+ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), STATIC_ACCESS_CONVERSIONS); // Paper
registry.freeze();
}
}
public static <T> HolderGetter<T> acquireBootstrapRegistrationLookup(final Registry<T> registry) {
Loaded 100 of 400 files, more files were not shown because too many files have changed in this diff. Show more