Compare commits

..
38 Commits
Author SHA1 Message Date
CreeamandWarrior 5050b0f333 Fix health capped at their base max health upon join (MC-17876) (#13500)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-11-04 08:40:23 -03:00
Jason Penilla 6b6835847e Update Mache to 26.3+build.2 (#14461) 2026-11-03 21:04:01 -07:00
Photoπ 13565bc811 Fix cancelled PlayerInteractEvent causing swing packets (#14433) 2026-11-03 19:50:40 -03:00
Pedro df120fd8b5 Add OperatorFeaturesAllowed API for Signs (#14451) 2026-11-02 20:55:58 -03:00
Jason Penilla 7e88a0da38 Sync latest Moonrise changes (#14452) 2026-11-01 20:07:58 +01:00
Bjarne Koll f11902627b Correct data counts for custom inventories (#14449)
Update the hardcoded data counts in CraftContainer to reference the constants used by the menus themselves.
2026-11-01 12:09:17 +00:00
ClexusandWarrior bb10cf0d5b Add EntityLandEvent (#14204)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-10-31 11:17:23 -03:00
metabrix 55bebded5f Parse pasted multi-line commands separately line by line (#13007) 2026-10-31 10:45:30 -03:00
Michał Fox f728a121f7 Fix respawn radius only covering part of the square (#14390) 2026-10-31 10:31:53 -03:00
balugaq 2401035c89 Add missing ensureSize in MaterialTags (#14438) 2026-10-31 13:27:55 +00:00
Daksh Rawat 174574d871 [ci skip] Fix RedundantNullabilityCheck for nested types (#14430) 2026-10-30 09:15:58 -03:00
Jake Potrebic 8a170a38c6 Update checkstyle (#14431) 2026-10-29 18:53:15 -07:00
Shane Freeder 804dd0c3f0 Centralise TreeType <-> tree feature mapping and test it against TreeGrower (#14417) 2026-10-29 11:28:37 +00:00
Tamion ff31a2c997 Fix cancelled PotionSplashEvent putting out fires (#12194) 2026-10-29 09:29:38 +01:00
Lulu13022002 2e549d651c Call EntityDamageByBlockEvent for fire damage (#12235) 2026-10-29 09:14:15 +01:00
7f9c79794e Fix duplicate dispense events for some items (#13922)
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
Co-authored-by: Bjarne Koll <git@lynxplay.dev>
2026-10-27 19:22:34 +00:00
Daksh Rawat 9d950517d9 [ci skip] Fix JavadocAlignParameterDescriptionCheck with inline tags (#14427) 2026-10-27 16:27:57 +01:00
Marlon Pohl 4f4904928f Add API to query type of PersistentDataContainer value (#14143) 2026-10-26 22:03:51 +01:00
TiggerTakkyTTT 4623163095 Detect rebound middle click clone actions in survival (#14249) 2026-10-26 21:54:48 +01:00
Johan-HynellandWarrior eb9098a546 Made time format output prefix for command block configurable (#14322)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-10-26 11:18:17 -03:00
Cubicake e415f5e030 Don't refresh entity data if its removed (#14423) 2026-10-25 09:46:40 -03:00
Creeam e3ba7b1dc8 Regen missing worldgen settings file from server.properties (#14233) 2026-10-24 10:18:07 -03:00
metabrix 05c2798ec0 Fix cancelled running scheduled task state (#14109) 2026-10-24 10:13:14 -03:00
TiggerTakkyTTTandDoc 02378ab582 Fire PrepareItemCraftEvent for Crafter preview (#14320)
Co-authored-by: Doc <git@mrdoc.dev>
2026-10-24 09:52:52 -03:00
Warrior 67912d0827 [ci skip] Re-enable using JBR for local run tasks again (#14318) 2026-10-24 09:13:46 -03:00
personalized-advertising e8a1f8fd68 Configurable plugin message channel limit (#14160) 2026-10-24 09:07:27 -03:00
personalized-advertising 1b9bf5c8e9 Add EntityPotentSulfurLaunchEvent (#14237) 2026-10-23 10:47:23 -03:00
Marco N. 325f9071f0 Clear scoreboard team entries on unregister (#14159) 2026-10-23 10:21:43 -03:00
PedroandWarrior d86d427405 Call PlayerBucketEmptyEvent for Sulfur Cube in Bucket (#14150)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-10-23 09:45:41 -03:00
Gotze 2f1ce01e87 Improve swing hand API to respect item ATTACK_ANIMATION component (#14363) 2026-10-23 09:38:43 -03:00
Bjarne Koll 0d9bf08a23 [ci skip] Fix broken test plugin refs 2026-10-22 18:10:53 +01:00
Mickey42302 e65c0765c6 Add permissions for reset and sequence arguments in /random (#14128) 2026-10-22 15:30:22 +02:00
H. Boyd ac9a1b2c72 Make Color implement adventure ARGBLike (#14349) 2026-10-22 09:59:01 -03:00
Strokkur24andJason Penilla 0db5c93a3d Add helper methods for vanilla-style command feedback (#14331)
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2026-10-22 09:54:34 -03:00
Shane Freeder 2d602d68c1 Split TreeType.POPLAR into per-color poplar tree types (#14416) 2026-10-21 14:12:43 +01:00
TiggerTakkyTTT 2766dd30b1 Add API for explorer maps using specific structures (#14290) 2026-10-21 09:31:41 -03:00
Warrior 03af8b4291 Fix shutdown thread deadlock if it tries to close async (#14410) 2026-10-20 21:49:26 +00:00
Jason Penilla 45fac22871 Sync latest Moonrise changes (#14409) 2026-10-20 21:41:39 +00:00
400 changed files with 6165 additions and 4234 deletions

No files matched your search

+25 -2
View File
@@ -14,11 +14,18 @@
<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"/>
@@ -38,6 +45,7 @@
</module>
<module name="AnnotationUseStyle"/>
<module name="MissingDeprecated"/>
<module name="MissingOverrideOnRecordAccessor"/>
<!--Block Checks-->
<module name="AvoidNestedBlocks"/>
@@ -55,7 +63,9 @@
<!--Coding-->
<module name="AvoidDoubleBraceInitialization"/>
<module name="AvoidNoArgumentSuperConstructorCall"/>
<module name="ConstructorsDeclarationGrouping"/>
<module name="ConstructorsDeclarationGrouping">
<property name="orderByIncreasingParameterCount" value="true"/>
</module>
<module name="CovariantEquals"/>
<module name="DeclarationOrder">
<property name="ignoreConstructors" value="true"/>
@@ -63,6 +73,7 @@
<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">
@@ -86,6 +97,9 @@
<module name="UnnecessarySemicolonInTryWithResources"/>
<module name="UnusedCatchParameterShouldBeUnnamed"/>
<module name="UnusedLocalVariable"/>
<module name="UnusedPrivateField"/>
<module name="UnusedTryResourceShouldBeUnnamed"/>
<module name="UseEnhancedSwitch"/>
<module name="WhenShouldBeUsed"/>
<!--Headers--> <!--N/A-->
@@ -114,9 +128,13 @@
<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-->
@@ -124,11 +142,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-->
@@ -136,12 +154,14 @@
<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"/>
@@ -180,6 +200,7 @@
<!--Size Violations--> <!--N/A-->
<!--Whitespace-->
<module name="ArrayBracketNoWhitespace"/>
<module name="EmptyForInitializerPad"/>
<module name="EmptyForIteratorPad"/>
<module name="EmptyLineSeparator">
@@ -197,9 +218,11 @@
<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.4
name: Release 26.3
on:
workflow_dispatch:
@@ -11,7 +11,7 @@ permissions:
concurrency:
cancel-in-progress: false
group: release-26.4
group: release-26.3
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.4.*&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.3.*&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.4.build.+")
compileOnly("io.papermc.paper:paper-api:26.3.build.+")
}
java {
@@ -59,7 +59,7 @@ java {
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>[26.4.build,)</version>
<version>[26.3.build,)</version>
<scope>provided</scope>
</dependency>
```
+4 -2
View File
@@ -477,8 +477,10 @@ 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
@@ -583,6 +585,7 @@ 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
@@ -630,15 +633,14 @@ 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.4-snapshot-5
mcVersion=26.3
# 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.4
channel=ALPHA
apiVersion=26.3
channel=STABLE
# Set to true while updating Minecraft version
updatingMinecraft=true
updatingMinecraft=false
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -234,7 +234,8 @@ public class MaterialTags {
* Covers all music disc items.
*/
public static final MaterialSetTag MUSIC_DISCS = new MaterialSetTag(keyFor("music_discs"))
.startsWith("MUSIC_DISC_").lock();
.startsWith("MUSIC_DISC_")
.ensureSize("MUSIC_DISCS", 22).lock();
/**
* Covers all ores.
@@ -0,0 +1,64 @@
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,8 +1,14 @@
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;
@@ -81,4 +87,123 @@ 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,7 +10,6 @@ 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.
@@ -31,7 +30,7 @@ public interface Consumable extends BuildableDataComponent<Consumable, Consumabl
ItemUseAnimation animation();
@Contract(pure = true)
@Nullable Key sound();
Key sound();
@Contract(pure = true)
boolean hasConsumeParticles();
@@ -66,11 +65,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, or {@code null} for no sound
* @param sound the {@link Key} representing the sound to be used
* @return the builder for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Builder sound(@Nullable Key sound);
Builder sound(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, or {@code null} if none
* @return the equip sound key
*/
@Contract(pure = true)
@Nullable Key equipSound();
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, or {@code null} if none
* @return shear sound
*/
@Contract(pure = true)
@Nullable Key shearSound();
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, or {@code null} for no sound
* @param sound the equip sound key
* @return the builder for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Builder equipSound(@Nullable Key sound);
Builder equipSound(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, or {@code null} for no sound
* @param shearSound the shear sound key
* @return the builder for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Builder shearSound(@Nullable Key shearSound);
Builder shearSound(Key shearSound);
}
}
@@ -0,0 +1,91 @@
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;
}
}
@@ -0,0 +1,69 @@
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,6 +1,7 @@
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;
@@ -177,6 +178,23 @@ 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, or {@code null} if none}
* {@return the sound played once the sulfur cube is knocked}
*/
@ApiStatus.Experimental
@Nullable TypedKey<Sound> hitSound();
TypedKey<Sound> hitSound();
/**
* {@return the sound played once the sulfur cube is pushed, or {@code null} if none}
* {@return the sound played once the sulfur cube is pushed}
*/
@ApiStatus.Experimental
@Nullable TypedKey<Sound> pushSound();
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 @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 {
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 {
}
return new Impl(hitSound, pushSound, pushSoundImpulseThreshold, pushSoundCooldown);
+41 -4
View File
@@ -961,8 +961,7 @@ public final class Bukkit {
* Create a new explorer map targeting the closest nearby structure of a
* given {@link StructureType}.
* <br>
* This method uses implementation default values for radius and
* findUnexplored (usually 100, true).
* 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
@@ -986,8 +985,7 @@ 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 implementation default values for radius and
* findUnexplored (usually 100, true).
* 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
@@ -1021,6 +1019,45 @@ 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
/**
+131 -77
View File
@@ -6,10 +6,12 @@ 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
@@ -17,7 +19,7 @@ import org.jetbrains.annotations.Nullable;
* but subject to change.
*/
@SerializableAs("Color")
public final class Color implements ConfigurationSerializable {
public final class Color implements ConfigurationSerializable, ARGBLike {
private static final int BIT_MASK = 0xff;
private static final int DEFAULT_ALPHA = 255;
@@ -122,7 +124,12 @@ public final class Color implements ConfigurationSerializable {
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
*/
@NotNull
public static Color fromARGB(int alpha, int red, int green, int blue) throws IllegalArgumentException {
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 {
return new Color(alpha, red, green, blue);
}
@@ -136,7 +143,11 @@ public final class Color implements ConfigurationSerializable {
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
*/
@NotNull
public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException {
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 {
return new Color(DEFAULT_ALPHA, red, green, blue);
}
@@ -150,7 +161,11 @@ public final class Color implements ConfigurationSerializable {
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
*/
@NotNull
public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException {
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 {
return new Color(DEFAULT_ALPHA, red, green, blue);
}
@@ -164,7 +179,7 @@ public final class Color implements ConfigurationSerializable {
* bits
*/
@NotNull
public static Color fromRGB(int rgb) throws IllegalArgumentException {
public static Color fromRGB(final 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);
}
@@ -177,7 +192,7 @@ public final class Color implements ConfigurationSerializable {
* @return a new color object for specified values
*/
@NotNull
public static Color fromARGB(int argb) {
public static Color fromARGB(final int argb) {
return fromARGB(argb >> 24 & BIT_MASK, argb >> 16 & BIT_MASK, argb >> 8 & BIT_MASK, argb & BIT_MASK);
}
@@ -191,16 +206,16 @@ public final class Color implements ConfigurationSerializable {
* bits
*/
@NotNull
public static Color fromBGR(int bgr) throws IllegalArgumentException {
public static Color fromBGR(final 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(int red, int green, int blue) {
private Color(final int red, final int green, final int blue) {
this(DEFAULT_ALPHA, red, green, blue);
}
private Color(int alpha, int red, int green, int blue) {
private Color(final int alpha, final int red, final int green, final 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);
@@ -213,83 +228,123 @@ public final class Color implements ConfigurationSerializable {
}
/**
* Gets the alpha component
* Gets the alpha component.
*
* @return alpha component, from 0 to 255
*/
public int getAlpha() {
public @Range(from = 0, to = 255) int getAlpha() {
return BIT_MASK & alpha;
}
/**
* Creates a new Color object with specified component
* 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.
*
* @param alpha the alpha component, from 0 to 255
* @return a new color object with the alpha component
*/
@NotNull
public Color setAlpha(int alpha) {
return fromARGB(alpha, getRed(), getGreen(), getBlue());
public Color setAlpha(final @Range(from = 0, to = 255) int alpha) {
return fromARGB(alpha, this.getRed(), this.getGreen(), this.getBlue());
}
/**
* Gets the red component
* Gets the red component.
*
* @return red component, from 0 to 255
*/
public int getRed() {
return BIT_MASK & red;
public @Range(from = 0, to = 255) int getRed() {
return BIT_MASK & this.red;
}
/**
* Creates a new Color object with specified component
* 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.
*
* @param red the red component, from 0 to 255
* @return a new color object with the red component
*/
@NotNull
public Color setRed(int red) {
return fromARGB(getAlpha(), red, getGreen(), getBlue());
public Color setRed(final @Range(from = 0, to = 255) int red) {
return fromARGB(this.getAlpha(), red, this.getGreen(), this.getBlue());
}
/**
* Gets the green component
* Gets the green component.
*
* @return green component, from 0 to 255
*/
public int getGreen() {
return BIT_MASK & green;
public @Range(from = 0, to = 255) int getGreen() {
return BIT_MASK & this.green;
}
/**
* Creates a new Color object with specified component
* 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.
*
* @param green the green component, from 0 to 255
* @return a new color object with the green component
*/
@NotNull
public Color setGreen(int green) {
return fromARGB(getAlpha(), getRed(), green, getBlue());
public Color setGreen(final @Range(from = 0, to = 255) int green) {
return fromARGB(this.getAlpha(), this.getRed(), green, this.getBlue());
}
/**
* Gets the blue component
* Gets the blue component.
*
* @return blue component, from 0 to 255
*/
public int getBlue() {
return BIT_MASK & blue;
public @Range(from = 0, to = 255) int getBlue() {
return BIT_MASK & this.blue;
}
/**
* Creates a new Color object with specified component
* 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.
*
* @param blue the blue component, from 0 to 255
* @return a new color object with the blue component
*/
@NotNull
public Color setBlue(int blue) {
return fromARGB(getAlpha(), getRed(), getGreen(), blue);
public Color setBlue(final @Range(from = 0, to = 255) int blue) {
return fromARGB(this.getAlpha(), this.getRed(), this.getGreen(), blue);
}
/**
@@ -298,7 +353,7 @@ public final class Color implements ConfigurationSerializable {
* @return An integer representation of this color, as 0xRRGGBB
*/
public int asRGB() {
return getRed() << 16 | getGreen() << 8 | getBlue();
return this.getRed() << 16 | this.getGreen() << 8 | this.getBlue();
}
/**
@@ -307,7 +362,7 @@ public final class Color implements ConfigurationSerializable {
* @return An integer representation of this color, as 0xAARRGGBB
*/
public int asARGB() {
return getAlpha() << 24 | getRed() << 16 | getGreen() << 8 | getBlue();
return this.getAlpha() << 24 | this.getRed() << 16 | this.getGreen() << 8 | this.getBlue();
}
/**
@@ -316,33 +371,33 @@ public final class Color implements ConfigurationSerializable {
* @return An integer representation of this color, as 0xBBGGRR
*/
public int asBGR() {
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
*
* @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(@NotNull DyeColor... colors) {
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "DyeColor cannot be null or contain null values");
Color[] toPass = new Color[colors.length];
for (int i = 0; i < colors.length; i++) {
toPass[i] = colors[i].getColor();
}
return mixColors(toPass);
return this.getBlue() << 16 | this.getGreen() << 8 | this.getRed();
}
/**
* Creates a new color with its RGB components changed as if it was dyed
* 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) {
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];
for (int i = 0; i < colors.length; i++) {
toPass[i] = colors[i].getColor();
}
return this.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>
*
@@ -351,7 +406,7 @@ public final class Color implements ConfigurationSerializable {
*/
// TODO: Javadoc what this method does, not what it mimics. API != Implementation
@NotNull
public Color mixColors(@NotNull Color... colors) {
public Color mixColors(final @NotNull Color... colors) {
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "Colors cannot be null");
int totalRed = this.getRed();
@@ -365,44 +420,43 @@ public final class Color implements ConfigurationSerializable {
totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue());
}
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 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 maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
float gainFactor = averageMax / maximumOfAverages;
final float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
final float gainFactor = averageMax / maximumOfAverages;
return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor));
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Color)) {
public boolean equals(final Object o) {
if (!(o instanceof Color that)) {
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 asARGB() ^ Color.class.hashCode();
return this.asARGB() ^ Color.class.hashCode();
}
@Override
@NotNull
public Map<String, Object> serialize() {
return ImmutableMap.of(
"ALPHA", getAlpha(),
"RED", getRed(),
"BLUE", getBlue(),
"GREEN", getGreen()
"ALPHA", this.getAlpha(),
"RED", this.getRed(),
"BLUE", this.getBlue(),
"GREEN", this.getGreen()
);
}
@NotNull
public static Color deserialize(@NotNull Map<String, Object> map) {
public static Color deserialize(final @NotNull Map<String, Object> map) {
return fromARGB(
asInt("ALPHA", map, DEFAULT_ALPHA),
asInt("RED", map),
@@ -411,23 +465,23 @@ public final class Color implements ConfigurationSerializable {
);
}
private static int asInt(@NotNull String string, @NotNull Map<String, Object> map) {
private static int asInt(final @NotNull String string, final @NotNull Map<String, Object> map) {
return asInt(string, map, null);
}
private static int asInt(@NotNull String string, @NotNull Map<String, Object> map, @Nullable Object defaultValue) {
Object value = map.getOrDefault(string, defaultValue);
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);
if (value == null) {
throw new IllegalArgumentException(string + " not in map " + map);
}
if (!(value instanceof Number)) {
if (!(value instanceof Number number)) {
throw new IllegalArgumentException(string + '(' + value + ") is not a number");
}
return ((Number) value).intValue();
return number.intValue();
}
@Override
public String toString() {
return "Color:[argb0x" + Integer.toHexString(asARGB()).toUpperCase(Locale.ROOT) + "]";
return "Color:[argb0x" + Integer.toHexString(this.asARGB()).toUpperCase(Locale.ROOT) + "]";
}
}
+53 -18
View File
@@ -61,6 +61,8 @@ 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;
@@ -872,8 +874,7 @@ 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 implementation default values for radius and
* findUnexplored (usually 100, true).
* 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
@@ -882,9 +883,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, org.bukkit.map.MapCursor.Type)}
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, MapCursor.Type)}
*/
@Deprecated // Paper
@Deprecated
@NotNull
default ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType) {
return this.createExplorerMap(world, location, structureType, 100, true);
@@ -904,18 +905,16 @@ 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, org.bukkit.map.MapCursor.Type, int, boolean)}
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, MapCursor.Type, int, boolean)}
*/
@Deprecated // Paper
@NotNull
public ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored);
// Paper start
@Deprecated
@NotNull ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored);
/**
* Create a new explorer map targeting the closest nearby structure of a
* given {@link org.bukkit.generator.structure.StructureType}.
* given {@link StructureType}.
* <br>
* This method uses implementation default values for radius and
* findUnexplored (usually 100, true).
* 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
@@ -924,15 +923,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,
* org.bukkit.generator.structure.StructureType, int, boolean)
* StructureType, int, boolean)
*/
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon) {
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull 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 org.bukkit.generator.structure.StructureType}.
* given {@link StructureType}.
*
* @param world the world the map will belong to
* @param location the origin location to find the nearest structure
@@ -944,10 +943,46 @@ 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,
* org.bukkit.generator.structure.StructureType, int, boolean)
* StructureType, int, boolean)
*/
@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
@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);
/**
* Reloads the server, refreshing settings and plugin information.
@@ -1,112 +1,70 @@
package org.bukkit;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* 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
*/
float getVolume();
public 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
*/
float getPitch();
public float getPitch();
/**
* Gets the corresponding breaking sound for this group.
*
* @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.
* @return the break sound
*/
@Deprecated(since = "26.4")
Sound getBreakSound();
@NotNull
public Sound getBreakSound();
/**
* Gets the corresponding step sound for this group.
*
* @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.
* @return the step sound
*/
@Deprecated(since = "26.4")
Sound getStepSound();
@NotNull
public Sound getStepSound();
/**
* Gets the corresponding place sound for this group.
*
* @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.
* @return the place sound
*/
@Deprecated(since = "26.4")
Sound getPlaceSound();
@NotNull
public Sound getPlaceSound();
/**
* Gets the corresponding hit sound for this group.
*
* @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.
* @return the hit sound
*/
@Deprecated(since = "26.4")
Sound getHitSound();
@NotNull
public Sound getHitSound();
/**
* Gets the corresponding fall sound for this group.
*
* @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.
* @return the fall sound
*/
@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();
@NotNull
public Sound getFallSound();
}
@@ -110,7 +110,15 @@ public enum TreeType {
*/
PALE_OAK_CREAKING,
/**
* Poplar tree
* Poplar tree with red leaves
*/
POPLAR,
RED_POPLAR,
/**
* Poplar tree with orange leaves
*/
ORANGE_POPLAR,
/**
* Poplar tree with yellow leaves
*/
YELLOW_POPLAR,
}
@@ -778,6 +778,19 @@ 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,27 +1,34 @@
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.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()}.
*/
@NotNull
@Deprecated
public java.util.List<net.kyori.adventure.text.Component> lines();
@Deprecated(since = "1.20")
default List<Component> lines() {
return this.getSide(Side.FRONT).lines();
}
/**
* Gets the line of text at the specified index on the {@link Side#FRONT}.
@@ -29,13 +36,14 @@ 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
* @throws IndexOutOfBoundsException Thrown when the line does not exist
* @return Text on the given line
* @throws IndexOutOfBoundsException Thrown when the line does not exist
* @deprecated A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int)}.
*/
@NotNull
@Deprecated
public net.kyori.adventure.text.Component line(int index) throws IndexOutOfBoundsException;
@Deprecated(since = "1.20")
default Component line(int index) throws IndexOutOfBoundsException {
return this.getSide(Side.FRONT).line(index);
}
/**
* Sets the line of text at the specified index on the {@link Side#FRONT}.
@@ -44,23 +52,25 @@ 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 A sign may have multiple writable sides now. Use {@link #getSide(Side)} and {@link SignSide#line(int, Component)}.
*/
@Deprecated
public void line(int index, net.kyori.adventure.text.@NotNull Component line) throws IndexOutOfBoundsException;
// Paper end
@Deprecated(since = "1.20")
default void line(int index, Component line) throws IndexOutOfBoundsException {
this.getSide(Side.FRONT).line(index, line);
}
/**
* 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")
@NotNull
public String[] getLines();
default String[] getLines() {
return this.getSide(Side.FRONT).getLines();
}
/**
* Gets the line of text at the specified index.
@@ -73,8 +83,9 @@ 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")
@NotNull
public String getLine(int index) throws IndexOutOfBoundsException;
default String getLine(int index) throws IndexOutOfBoundsException {
return this.getSide(Side.FRONT).getLine(index);
}
/**
* Sets the line of text at the specified index.
@@ -83,12 +94,14 @@ 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")
public void setLine(int index, @NotNull String line) throws IndexOutOfBoundsException;
default void setLine(int index, String line) throws IndexOutOfBoundsException {
this.getSide(Side.FRONT).setLine(index, line);
}
/**
* Marks whether this sign can be edited by players.
@@ -97,7 +110,9 @@ public interface Sign extends TileState, Colorable {
* @deprecated use {@link #isWaxed()} instead
*/
@Deprecated(since = "1.20.1")
public boolean isEditable();
default boolean isEditable() {
return !this.isWaxed();
}
/**
* Marks whether this sign can be edited by players.
@@ -106,7 +121,9 @@ public interface Sign extends TileState, Colorable {
* @deprecated use {@link #setWaxed(boolean)} instead
*/
@Deprecated(since = "1.20.1")
public void setEditable(boolean editable);
default void setEditable(boolean editable) {
this.setWaxed(!editable);
}
/**
* Gets whether or not this sign has been waxed. If a sign has been waxed, it
@@ -114,7 +131,7 @@ public interface Sign extends TileState, Colorable {
*
* @return if this sign is waxed
*/
public boolean isWaxed();
boolean isWaxed();
/**
* Sets whether or not this sign has been waxed. If a sign has been waxed, it
@@ -122,7 +139,7 @@ public interface Sign extends TileState, Colorable {
*
* @param waxed if this sign is waxed
*/
public void setWaxed(boolean waxed);
void setWaxed(boolean waxed);
/**
* Gets whether this sign has glowing text. Only affects the {@link Side#FRONT}.
@@ -131,7 +148,9 @@ 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")
public boolean isGlowingText();
default boolean isGlowingText() {
return this.getSide(Side.FRONT).isGlowingText();
}
/**
* Sets whether this sign has glowing text. Only affects the {@link Side#FRONT}.
@@ -140,17 +159,20 @@ 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")
public void setGlowingText(boolean glowing);
default void setGlowingText(boolean glowing) {
this.getSide(Side.FRONT).setGlowingText(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")
public DyeColor getColor();
default @Nullable DyeColor getColor() {
return this.getSide(Side.FRONT).getColor();
}
/**
* {@inheritDoc}
@@ -159,7 +181,9 @@ public interface Sign extends TileState, Colorable {
*/
@Override
@Deprecated(since = "1.20")
public void setColor(@NotNull DyeColor color);
default void setColor(DyeColor color) {
this.getSide(Side.FRONT).setColor(color);
}
/**
* Return the side of the sign.
@@ -167,8 +191,7 @@ public interface Sign extends TileState, Colorable {
* @param side the side of the sign
* @return the selected side of the sign
*/
@NotNull
public SignSide getSide(@NotNull Side side);
SignSide getSide(Side side);
/**
* Gets the side of this sign the given player is currently standing on.
@@ -176,8 +199,7 @@ public interface Sign extends TileState, Colorable {
* @param player the player
* @return the side the player is standing on
*/
@NotNull
public SignSide getTargetSide(@NotNull Player player);
SignSide getTargetSide(Player player);
/**
* Gets the player that is currently allowed to edit this sign. <br>
@@ -187,16 +209,15 @@ public interface Sign extends TileState, Colorable {
*
* @return the player allowed to edit this sign, or null
*/
@Nullable
public Player getAllowedEditor();
// Paper start - More Sign Block API
@Nullable Player getAllowedEditor();
/**
* 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 java.util.UUID getAllowedEditorUniqueId();
@Nullable UUID getAllowedEditorUniqueId();
/**
* Sets the allowed editor's UUID.
@@ -205,7 +226,7 @@ public interface Sign extends TileState, Colorable {
*
* @param uuid the allowed editor's UUID
*/
void setAllowedEditorUniqueId(@Nullable java.util.UUID uuid);
void setAllowedEditorUniqueId(@Nullable UUID uuid);
/**
* Compute the side facing the specified entity.
@@ -213,7 +234,7 @@ public interface Sign extends TileState, Colorable {
* @param entity the entity
* @return the side it is facing
*/
default @NotNull Side getInteractableSideFor(org.bukkit.entity.@NotNull Entity entity) {
default Side getInteractableSideFor(final Entity entity) {
return this.getInteractableSideFor(entity.getLocation());
}
@@ -223,7 +244,7 @@ public interface Sign extends TileState, Colorable {
* @param position the position
* @return the side the position is facing
*/
default @NotNull Side getInteractableSideFor(io.papermc.paper.math.@NotNull Position position) {
default Side getInteractableSideFor(final Position position) {
return this.getInteractableSideFor(position.x(), position.z());
}
@@ -234,6 +255,20 @@ public interface Sign extends TileState, Colorable {
* @param z the z coord
* @return the side the coordinates are facing
*/
@NotNull Side getInteractableSideFor(double x, double z);
// Paper end - More Sign Block API
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);
}
@@ -1,5 +1,6 @@
package org.bukkit.entity;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -1401,25 +1402,39 @@ 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.
* 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.
*
* <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 invalid hand is passed
* @throws IllegalArgumentException if an invalid hand is passed
*/
default void swingHand(@NotNull org.bukkit.inventory.EquipmentSlot hand) {
com.google.common.base.Preconditions.checkArgument(hand != null && hand.isHand(), String.format("Expected a valid hand, got \"%s\" instead!", hand));
Preconditions.checkArgument(hand != null && hand.isHand(), "Expected a valid hand, got \"%s\" instead!", hand);
if (hand == org.bukkit.inventory.EquipmentSlot.HAND) {
this.swingMainHand();
} else {
this.swingOffHand();
}
}
// Paper end - swing hand API
/**
* 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 start - knockback API
/**
@@ -30,7 +30,8 @@ public enum ClickType {
*/
WINDOW_BORDER_RIGHT,
/**
* The middle mouse button, or a "scrollwheel click".
* 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.
*/
MIDDLE,
/**
@@ -35,9 +35,9 @@ public interface EquippableComponent extends ConfigurationSerializable {
/**
* Gets the sound to play when the item is equipped.
*
* @return the sound, or null if none
* @return the sound
*/
@Nullable
@NotNull // Paper
Sound getEquipSound();
/**
+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
val testData = sourceSets.create("testData")
dependencies {
implementation("com.puppycrawl.tools:checkstyle:13.8.0")
implementation("com.puppycrawl.tools:checkstyle:14.3.0")
implementation("org.jspecify:jspecify:1.0.0")
testCompileOnly("org.jetbrains:annotations:26.1.0")
@@ -15,7 +15,6 @@ 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;
@@ -163,7 +162,16 @@ public final class Util {
return false;
}
public static Pair<String, DetailAST> extractFullIdent(final DetailAST lastDot) {
/**
* 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) {
final List<String> parts = new ArrayList<>();
DetailAST dot = lastDot;
while (dot.getChildCount(TokenTypes.DOT) > 0) {
@@ -180,6 +188,6 @@ public final class Util {
}
parts.addFirst(ident.getText());
return Pair.of(String.join(".", parts), ident);
return new QualifiedName(String.join(".", parts), ident);
}
}
@@ -6,7 +6,9 @@ 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;
@@ -19,7 +21,7 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
@Override
public int[] getDefaultJavadocTokens() {
return new int[]{JavadocCommentsTokenTypes.JAVADOC_CONTENT};
return new int[] {JavadocCommentsTokenTypes.JAVADOC_CONTENT};
}
@Override
@@ -30,6 +32,7 @@ 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) {
@@ -41,6 +44,9 @@ 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()) {
@@ -0,0 +1,123 @@
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,10 +31,20 @@ public class RedundantNullabilityCheck extends AbstractCheck {
@Override
public void visitToken(final DetailAST ast) {
final boolean pkgIsNullMarked = Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation);
final DetailAST nullMarkedAnnotation = NullabilityAnnotationsCheck.getNullMarkedAnnotation(ast);
if (pkgIsNullMarked && nullMarkedAnnotation != null) {
if (nullMarkedAnnotation == null) {
return;
}
final boolean pkgIsNullMarked = Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation);
if (pkgIsNullMarked) {
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,13 +1,26 @@
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 org.apache.commons.lang3.tuple.Pair;
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.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>
@@ -16,6 +29,35 @@ 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();
@@ -28,7 +70,7 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
@Override
public int[] getRequiredTokens() {
return new int[]{
return new int[] {
TokenTypes.TYPE,
TokenTypes.TYPE_ARGUMENT,
TokenTypes.TYPE_PARAMETER,
@@ -37,55 +79,168 @@ 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,
};
}
private static @Nullable DetailAST fromMethodCall(final DetailAST methodCall) {
final DetailAST dot = methodCall.findFirstToken(TokenTypes.DOT);
if (dot == null) {
return null;
@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);
}
final DetailAST nextDot = dot.findFirstToken(TokenTypes.DOT);
if (nextDot == 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 ident = nextDot.findFirstToken(TokenTypes.IDENT);
if (ident == null || Character.isLowerCase(ident.getText().charAt(0))) {
// method call not on a type
return null;
}
return dot;
this.fileScope.set(new FileScope(packageName, topLevelTypes, nestedTypes, unqualifiedTypes));
}
private static @Nullable DetailAST fromLiteralClass(final DetailAST literalClass) {
final DetailAST dot = literalClass.getParent();
if (dot.getType() != TokenTypes.DOT) {
return null;
@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;
}
return dot;
// 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;
}
@Override
public void visitToken(final DetailAST ast) {
final DetailAST actualAst = switch (ast.getType()) {
case TokenTypes.METHOD_CALL -> fromMethodCall(ast);
case TokenTypes.LITERAL_CLASS -> fromLiteralClass(ast);
default -> ast;
};
if (actualAst == null) {
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.
return;
}
final DetailAST dot = actualAst.findFirstToken(TokenTypes.DOT);
if (dot == null) {
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
return;
}
final Pair<String, DetailAST> fullIdent = Util.extractFullIdent(dot);
final DetailAST startNode = fullIdent.getRight();
final String fullIdentName = fullIdent.getLeft();
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();
if (Character.isUpperCase(fullIdentName.charAt(0))) {
// not a package
return;
@@ -96,7 +251,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: " + fullIdentName);
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Fully qualified import used: " + withoutConstants(fullIdentName));
return;
}
previousTypeName = typeName;
@@ -104,9 +259,61 @@ 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 fullImportBySimple = this.getFullImportBySimple(outerSimpleName);
if (fullImportBySimple == null) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Unnecessary fully qualified import (no conflict): " + fullIdentName);
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));
}
}
/**
* 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,6 +43,7 @@ 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,6 +27,7 @@ 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;
@@ -68,10 +69,11 @@ 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"));
MethodUtils.invokeMethod(walker, true, "registerCheck", check);
ReflectionSupport.invokeMethod(registerCheck, walker, check);
}
final Path filePath = Path.of("src", "testData", "java", checkTest.value().replace('.', File.separatorChar) + ".java");
assertTrue(Files.exists(filePath), "File not found: " + filePath);
@@ -0,0 +1,29 @@
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");
}
}
@@ -0,0 +1,17 @@
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,6 +32,32 @@ 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,4 +45,26 @@ 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) {
}
}
@@ -0,0 +1,124 @@
// @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 {
}
}
}
@@ -0,0 +1,36 @@
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 {
}
}
}
@@ -0,0 +1,4 @@
package testdata.imports;
public class SamePackage {
}
@@ -0,0 +1,4 @@
package testdata.imports;
public class Shadowed {
}
@@ -70,4 +70,47 @@ 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;
}
@@ -0,0 +1,4 @@
package testdata.imports.testclasses;
public class SamePackage {
}
@@ -2,6 +2,8 @@ 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.4-snapshot-5+build.2")
mache("io.papermc:mache:26.3+build.2")
paperclip("io.papermc:paperclip:3.0.4")
}
@@ -23,7 +23,7 @@ paperweight {
gitFilePatches = false
updatingMinecraft {
oldPaperCommit = "8c0b8396d676f6abef3df8ab992ac726f6e01c9c"
//oldPaperCommit = "e5fe71723e2ffde7cc9fafc085ac3bb73e63175e"
}
}
@@ -110,7 +110,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
dependencies {
implementation(project(":paper-api"))
implementation(platform("ca.spottedleaf.leafpile:bom:1.3.0"))
implementation(platform("ca.spottedleaf.leafpile:bom:1.3.2"))
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))
// TODO - JB runtime 25 has issues with spark rn
// vendor.set(JvmVendorSpec.JETBRAINS)
@Suppress("UnstableApiUsage")
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 7050fd6efb6fec16ba5657e254c54de1c671c9a0..aa14dd369d5d2c5c843839f289afdc39c200f0c3 100644
index 130615b796d9568c8fc6db0cc838784f21976411..e8043e52b64ed565128bb18240694ad078911eed 100644
--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -216,6 +216,11 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -227,6 +227,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 7050fd6efb6fec16ba5657e254c54de1c671c9a0..aa14dd369d5d2c5c843839f289afdc39
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
@@ -257,4 +262,4 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -268,4 +273,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 d40863262dd7cf0e7c5667fd836e80d4f47e25aa..c4adc7266f41057bc93e8c34929c079c9c468d0b 100644
index a9ee3ae3217ce7182cd8ab11ea455bbedca1c363..7bc5221da2707b53f0d710f80ea4bfa838195ad4 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -293,6 +293,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -294,6 +294,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 bb61d4e4ecfe5bab6269fa3d4303dab95719d7bb..3d219969a55896b1495c54c0a12a25b49ba093f0 100644
index 0de6f540c5192dd152d2e0a55d6b26eee0f5107f..afb2c249078f85579f37a772c41c8138c5c8c521 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 bb61d4e4ecfe5bab6269fa3d4303dab95719d7bb..3d219969a55896b1495c54c0a12a25b4
SynchedEntityData.Builder entityDataBuilder = new SynchedEntityData.Builder(this);
entityDataBuilder.define(DATA_SHARED_FLAGS_ID, (byte)0);
entityDataBuilder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
@@ -1168,6 +1184,10 @@ public abstract class Entity
@@ -1169,6 +1185,10 @@ public abstract class Entity
} else {
if (moverType == MoverType.PISTON) {
delta = this.limitPistonMovement(delta);
@@ -507,7 +507,7 @@ index bb61d4e4ecfe5bab6269fa3d4303dab95719d7bb..3d219969a55896b1495c54c0a12a25b4
if (delta.equals(Vec3.ZERO)) {
return;
}
@@ -1183,6 +1203,13 @@ public abstract class Entity
@@ -1184,6 +1204,13 @@ public abstract class Entity
this.stuckSpeedMultiplier = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
}
@@ -522,10 +522,10 @@ index bb61d4e4ecfe5bab6269fa3d4303dab95719d7bb..3d219969a55896b1495c54c0a12a25b4
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 62ccee5c76303b679d591714e64ee93c1502f378..0ef6c4cefb457c87f4565701817d2f9034dfa0d8 100644
index db627b1d589e2c9d457b2f1b6429ea11849a1d3a..442bdb6b0bea65f4d2527eea43d2205cbaa4107a 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -3380,6 +3380,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
@@ -3391,6 +3391,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 d327301693c70affc866d355a08efe935e3656b4..efcf399ced67201525ea6e6b3bd46b93b7e1823d 100644
index a828576457e0bb932750dba507bdef7e8903e144..2f5ec35bf7f104e259f85d15b17c8064daa05a5e 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 f7814801138d9547e693ad51ba83cb2cb4ef750e..e6a674eb859af1e98fe25b644c1c5f70728128d0 100644
index 91c1ff8b8234900fbe044bbe0c3c10122c27a91b..e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52 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 e6a674eb859af1e98fe25b644c1c5f70728128d0..936200d7620e1fe83eb45ddbcff86b6b6ecaab8f 100644
index e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52..76596e87945f0b95af8032338a64817a8faed4d0 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 e6a674eb859af1e98fe25b644c1c5f70728128d0..936200d7620e1fe83eb45ddbcff86b6b
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 c2b09a693daf8b4d3d274c07b3be37ba4fc1a483..2f3b58491ef7be8cd2e081f2c44869cd1e50e6a6 100644
index 100e7a2483f63497a1b757c77377262de7248996..42408dede2808447281b49f4006c1d8b03c10bf4 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 c2b09a693daf8b4d3d274c07b3be37ba4fc1a483..2f3b58491ef7be8cd2e081f2c44869cd
.add(
new ServerBootstrap()
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index dd2c5b7bd16ca17e253501fbc49cc21c8a06d603..a1855a257c3ac801bbd371ca4508af755550d737 100644
index ab21339beb36388bffabb289546bbfe5732df855..eea7dd596eb9ac75dee29d843d786493d44034f3 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 7fa69d76bf2c802b538a21af40898b8abb79629e..d9daa3ea8c4dc21bbe0d0ea9f0ece3f6c6777ba4 100644
index a4e6225add32df93b0d6009d98ee322941b69938..e197ac0ee5e49d7768a9de889c972b3d590f7951 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 7fa69d76bf2c802b538a21af40898b8abb79629e..d9daa3ea8c4dc21bbe0d0ea9f0ece3f6
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 efcf399ced67201525ea6e6b3bd46b93b7e1823d..a1da101177a5a4fb2b4ba5e34be165daf4d147fb 100644
index 2f5ec35bf7f104e259f85d15b17c8064daa05a5e..7cbf8592ec5792e458fa07987cf258c51f9e1e8a 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 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02752b35f1 100644
index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5a66a6387 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -929,7 +929,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -930,7 +930,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 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
boolean result = false;
for (ServerLevel level : this.getAllLevels()) {
@@ -941,6 +941,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -942,6 +942,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
result = true;
}
@@ -31,7 +31,7 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
GameProfile singleplayerProfile = this.getSingleplayerProfile();
this.storageSource.saveDataTag(this.worldData, singleplayerProfile == null ? null : singleplayerProfile.id());
if (flush) {
@@ -948,14 +955,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -949,14 +956,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
} else {
this.savedDataStorage.scheduleSave();
}
@@ -48,7 +48,7 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
boolean result = this.saveAllChunks(silent, flush, force);
this.warnOnLowDiskSpace();
return result;
@@ -1600,9 +1606,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1602,9 +1608,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
this.ticksUntilAutosave--;
@@ -83,7 +83,7 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
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 e4d38cfdac2b926afae3b1b96679b6ce45ea256a..ca793f3f7300148cd55d2e873af5b43d9f4c708f 100644
index e197ac0ee5e49d7768a9de889c972b3d590f7951..85ab77c3d1d5420df648fd173eaf5ad4f4775a80 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 487fcb8546ad24125c6573fb84f22a02752b35f1..b675e9322234761a69e71e9e8b9d41364caf34b6 100644
index 482b428cab996c19664b57c341c55db5a66a6387..bd5efcf1dee77c204bf29ef4693ac54146e8d901 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1741,32 +1741,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1743,32 +1743,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return new ServerStatus.Players(maxPlayers, players.size(), sample);
}
@@ -66,10 +66,10 @@ index 487fcb8546ad24125c6573fb84f22a02752b35f1..b675e9322234761a69e71e9e8b9d4136
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 2cec345fdabc8e01e378e66c35093dec1fcb7c2c..0ae23c90594d85e94d17d8114a21fc62b79219d3 100644
index b5503b25ef58c7b19592c2e90649c774b155a17b..ffbb54a8576877f2b477615d1e2f84457fdf31e1 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5379,6 +5379,11 @@ public abstract class Entity
@@ -5385,6 +5385,11 @@ public abstract class Entity
this.getBukkitEntity().taskScheduler.retire();
}
// Paper end - Folia schedulers
@@ -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 b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116da07c32c0 100644
index 1077ff25ca0da7fb6f89dd2e16174f00b4986283..bd4b488cdf7a386bc458fad2ae659b77e60962ad 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 b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116d
private static final int SECTOR_BYTES = 4096;
@VisibleForTesting
protected static final int SECTOR_INTS = 1024;
@@ -451,6 +452,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
@@ -903,6 +904,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
this.pos = pos;
}
@@ -45,7 +45,7 @@ index b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116d
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 c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b93011713c 100644
index 9f8b1d4235d2f268a523c028006114b61d8d67ef..7e815fee19ab2c283bf1fdc3d6a530d4101de3de 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 c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b9
return writeData;
}
@@ -307,9 +320,18 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
@@ -330,8 +343,17 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
if (value == null) {
region.clear(pos);
} else {
@@ -82,7 +82,6 @@ index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b9
+ 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) {
@@ -93,7 +92,7 @@ index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b9
}
}
}
@@ -357,4 +379,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
@@ -379,4 +401,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 6dd6b4fb79870da6d3bf54dc813ae685237d1f0e..09205efff3a1a2042e67600510af270df9e46c67 100644
index 48353259f9a17937f71951706e0172b5b4957d56..e9dc38d934ede23f17e18893ee207d9fbbd26449 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 951f5ab5a05dcedf323845f57fd061645466e563..19c7dffe5f9c45cd99cb9b14cae5ac908cae0afc 100644
index 2b5377da8fc3d8582c47809a5e4815d7b0293780..d920a4905d044925c4c4ab980fc238c9c489d5ac 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1761,7 +1761,7 @@ public class ServerGamePacketListenerImpl
@@ -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 19c7dffe5f9c45cd99cb9b14cae5ac908cae0afc..1c92e33104cc131269be15333eeee0ca5f0c92b1 100644
index d920a4905d044925c4c4ab980fc238c9c489d5ac..60decc2534c5bc52b1a22e23a416a7660f724cb0 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 2dc8b94c434e841b319ef5b77bd725fd66f3c10b..a5540612a4052d9e9d8b1b0ac3c1591777f26caa 100644
index 8f2c37a3a534b3b0d4f3ffd2a0d7013a9b454244..f1ca359abcc422e791c81e036b4777643bce53b3 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 2dc8b94c434e841b319ef5b77bd725fd66f3c10b..a5540612a4052d9e9d8b1b0ac3c15917
profiler.popPush("tickSpawningChunks");
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 2cf8854636df72c1dccbb73dfe8f642dfdf1ea37..99403edc1eda1e238b66028b7d236e5fceb235a1 100644
index 60db677d06207b488aad0363af55012b2d76fe8b..eeeb4075a6bbc4e310c2ad685f4efeade702ee3f 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 a5540612a4052d9e9d8b1b0ac3c1591777f26caa..c4cfb53e145d69666ffc27ef4ee0c50b54eb9143 100644
index f1ca359abcc422e791c81e036b4777643bce53b3..ef53d4d17222a882dd3d33575096c864a0a00a06 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 a5540612a4052d9e9d8b1b0ac3c1591777f26caa..c4cfb53e145d69666ffc27ef4ee0c50b
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 99403edc1eda1e238b66028b7d236e5fceb235a1..23b3a82ab3e1c6abc85000d9285470cb12300a6b 100644
index eeeb4075a6bbc4e310c2ad685f4efeade702ee3f..1137be30f125cb79f05f3e5c104cdf8cca979ff1 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 1c92e33104cc131269be15333eeee0ca5f0c92b1..6dc9e669a37ddc0a17099bcc4839b42fe237cf99 100644
index 60decc2534c5bc52b1a22e23a416a7660f724cb0..8178922f5ecc7f6709335c7fbeb6dca505b044bb 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 b675e9322234761a69e71e9e8b9d41364caf34b6..c3bf41af0273e902f881cc070fcef6dd64abb063 100644
index bd5efcf1dee77c204bf29ef4693ac54146e8d901..7517b872794f3bbe556f3abbeb294f426c8da899 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1797,6 +1797,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1799,6 +1799,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,195 +0,0 @@
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
}
}
}
@@ -1,845 +0,0 @@
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
@@ -1,6 +1,6 @@
--- /dev/null
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -1,0 +_,260 @@
@@ -1,0 +_,271 @@
+package ca.spottedleaf.moonrise.paper;
+
+import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -13,6 +13,7 @@
+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;
@@ -88,6 +89,11 @@
+ }
+
+ @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) {
+
+ }
@@ -162,6 +168,11 @@
+ }
+
+ @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
@@ -40,7 +_,7 @@
@@ -37,7 +_,7 @@
private final boolean servicesKeySetEnabled;
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,
@@ -75,7 +_,7 @@
final Supplier<DiscoveryResponse> discoverySupplier
@@ -68,7 +_,7 @@
);
}
- 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);
- 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);
return RetryableFetch.fetch(
() -> HttpDiscoveryService.constantURL(environment.discoveryUrl()),
@@ -95,7 +_,7 @@
return clientVersion;
@@ -84,7 +_,7 @@
return new MinecraftServicesDiscoveryService(proxy, false, DiscoveryResponse::offline);
}
- private static Environment determineEnvironment() {
@@ -1,6 +1,6 @@
--- a/com/mojang/authlib/services/MinecraftServicesProfileRepository.java
+++ b/com/mojang/authlib/services/MinecraftServicesProfileRepository.java
@@ -44,6 +_,8 @@
@@ -42,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();
@@ -75,6 +_,13 @@
@@ -73,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) {
@@ -104,7 +_,15 @@
@@ -102,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());
}
@@ -47,7 +_,7 @@
@@ -46,7 +_,7 @@
}
}
@@ -113,8 +113,8 @@
@Override
public Collection<String> getOnlinePlayerNames() {
- 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
- 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
}
@Override
@@ -1,6 +1,6 @@
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -160,6 +_,7 @@
@@ -159,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();
@@ -184,6 +_,7 @@
@@ -183,6 +_,7 @@
@Override
public boolean isRestricted(final CommandNode<CommandSourceStack> node) {
@@ -16,7 +16,7 @@
Predicate<CommandSourceStack> requirement = node.getRequirement();
return !requirement.test(this.noPermissionSource);
}
@@ -191,6 +_,11 @@
@@ -190,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);
@@ -305,6 +_,42 @@
@@ -304,6 +_,42 @@
UnpublishCommand.register(this.dispatcher);
}
@@ -71,7 +71,7 @@
this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
}
@@ -324,6 +_,13 @@
@@ -323,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);
@@ -337,10 +_,12 @@
@@ -336,10 +_,12 @@
)
);
}
@@ -101,7 +101,7 @@
StackTraceElement[] stackTrace = e.getStackTrace();
for (int i = 0; i < Math.min(stackTrace.length, 3); i++) {
@@ -371,12 +_,16 @@
@@ -370,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);
}
@@ -388,7 +_,17 @@
@@ -387,7 +_,17 @@
}
context.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
@@ -139,7 +139,7 @@
}
return null;
@@ -416,10 +_,65 @@
@@ -415,10 +_,65 @@
}
public void sendCommands(final ServerPlayer player) {
@@ -207,7 +207,7 @@
player.connection.send(new ClientboundCommandsPacket(root, COMMAND_NODE_INSPECTOR));
}
@@ -427,8 +_,49 @@
@@ -426,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 = new HashMap<>();
+ private final Map<Property<?>, Comparable<?>> properties = new java.util.LinkedHashMap<>(); // CraftBukkit - stable
private final Map<String, String> vagueProperties = new HashMap<>();
- 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 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
@@ -139,67 +_,84 @@
@@ -141,67 +_,84 @@
@Override
public BlockPos above() {
@@ -98,7 +98,7 @@
}
@Override
@@ -698,9 +_,9 @@
@@ -667,9 +_,9 @@
}
public BlockPos.MutableBlockPos set(final int x, final int y, final int z) {
@@ -111,7 +111,7 @@
return this;
}
@@ -758,19 +_,19 @@
@@ -727,19 +_,19 @@
@Override
public BlockPos.MutableBlockPos setX(final int x) {
@@ -0,0 +1,11 @@
--- 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
@@ -34,17 +_,25 @@
@@ -33,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() {
@@ -118,6 +_,7 @@
@@ -117,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;
}
@@ -279,6 +_,7 @@
@@ -278,6 +_,7 @@
}
this.frozen = true;
@@ -47,7 +47,7 @@
this.byValue.forEach((value, holder) -> holder.bindValue((T)value));
List<Identifier> unboundEntries = this.byKey
.entrySet()
@@ -518,4 +_,13 @@
@@ -517,4 +_,13 @@
Stream<HolderSet.Named<T>> getTags();
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/Vec3i.java
+++ b/net/minecraft/core/Vec3i.java
@@ -20,9 +_,9 @@
@@ -22,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(
@@ -39,12 +_,12 @@
@@ -41,12 +_,12 @@
}
@Override
@@ -28,7 +28,7 @@
return (this.getY() + this.getZ() * 31) * 31 + this.getX();
}
@@ -57,15 +_,15 @@
@@ -59,15 +_,15 @@
}
}
@@ -47,7 +47,7 @@
return this.z;
}
@@ -249,4 +_,11 @@
@@ -251,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.value()
+ SoundEvents.BUCKET_EMPTY_POWDER_SNOW.value(), hitDirection // Paper - add hitDirection
- SoundEvents.BUCKET_EMPTY_POWDER_SNOW
+ SoundEvents.BUCKET_EMPTY_POWDER_SNOW, hitDirection // Paper - add hitDirection
);
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/component/BlockTransformer.java
+++ b/net/minecraft/core/component/BlockTransformer.java
@@ -64,6 +_,14 @@
@@ -68,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().equals(craftItem.getType())) {
+ if (source.state().is(net.minecraft.world.level.block.Blocks.DISPENSER) && event.getItem().getType() != 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
@@ -83,13 +_,39 @@
@@ -80,12 +_,38 @@
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;
@@ -107,9 +_,34 @@
@@ -103,8 +_,33 @@
.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,14 +72,12 @@
+ }
+ 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;
}
@@ -150,8 +_,45 @@
@@ -145,8 +_,45 @@
DispensibleContainerItem bucket = (DispensibleContainerItem)dispensed.getItem();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
Level level = source.level();
@@ -90,7 +88,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().defaultFluidState())));
+ 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())));
+ 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());
@@ -126,7 +124,7 @@
return this.consumeWithRemainder(source, dispensed, new ItemStack(Items.BUCKET));
} else {
return this.defaultDispenseItemBehavior.dispense(source, dispensed);
@@ -175,7 +_,7 @@
@@ -170,13 +_,20 @@
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
BlockState blockState = level.getBlockState(target);
if (blockState.getBlock() instanceof BucketPickup bucket) {
@@ -135,21 +133,20 @@
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);
@@ -196,11 +_,54 @@
@@ -190,11 +_,54 @@
this.setSuccess(true);
Level level = source.level();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
@@ -204,7 +201,7 @@
return dispensed;
}
@@ -215,15 +_,41 @@
@@ -209,15 +_,41 @@
}
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
@@ -253,21 +250,21 @@
this.setSuccess(true);
return dispensed;
}
@@ -236,6 +_,13 @@
Level level = source.level();
@@ -231,6 +_,13 @@
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))
@@ -248,7 +_,7 @@
);
@@ -242,7 +_,7 @@
dispensed.shrink(1);
this.setSuccess(true);
} else {
@@ -276,21 +273,21 @@
}
return dispensed;
@@ -261,6 +_,13 @@
Level level = source.level();
@@ -256,6 +_,13 @@
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());
@@ -270,7 +_,7 @@
level.gameEvent(null, GameEvent.BLOCK_PLACE, target);
@@ -264,7 +_,7 @@
dispensed.shrink(1);
this.setSuccess(true);
} else {
@@ -299,20 +296,31 @@
}
return dispensed;
@@ -296,6 +_,12 @@
ServerLevel level = source.level();
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
@@ -289,11 +_,23 @@
BlockState state = level.getBlockState(target);
+ // 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) {
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
((BeehiveBlock)state.getBlock())
.releaseBeesAndResetHoneyLevel(level, state, target, null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED);
@@ -320,6 +_,13 @@
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 @@
this.setSuccess(true);
if (blockState.is(Blocks.RESPAWN_ANCHOR)) {
if (blockState.getValue(RespawnAnchorBlock.CHARGE) != 4) {
@@ -326,7 +334,7 @@
RespawnAnchorBlock.charge(null, level, pos, blockState);
dispensed.shrink(1);
} else {
@@ -344,6 +_,28 @@
@@ -336,6 +_,28 @@
return dispensed;
}
@@ -355,7 +363,7 @@
for (Armadillo armadillo : armadillos) {
if (armadillo.brushOffScute(null, dispensed)) {
dispensed.hurtAndBreak(16, level, null, item -> {});
@@ -363,6 +_,13 @@
@@ -355,6 +_,13 @@
BlockState blockState = level.getBlockState(pos);
Optional<BlockState> maybeWaxed = HoneycombItem.getWaxed(blockState);
if (maybeWaxed.isPresent()) {
@@ -369,7 +377,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);
@@ -393,6 +_,12 @@
@@ -385,6 +_,12 @@
return this.defaultDispenseItemBehavior.dispense(source, dispensed);
}
@@ -1,45 +1,80 @@
--- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
@@ -24,8 +_,15 @@
ServerLevel level = source.level();
@@ -25,21 +_,55 @@
Direction facing = source.state().getValue(DispenserBlock.FACING);
BlockPos targetPos = source.pos().relative(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
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 (!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) {
+ this.setSuccess(false);
+ return blockDispenseEventResult[0];
+ }
+ // Paper end - Call BlockDispenseEvent - yield back possibly changed result stack from sulfur or tnt dispense events
if (this.isSuccess()) {
FlintAndSteelItem.playSound(level, targetPos);
@@ -36,19 +_,23 @@
dispensed.hurtAndBreak(1, level, null, item -> {});
@@ -48,11 +_,18 @@
return dispensed;
}
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 {
- 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;
@@ -1,6 +1,6 @@
--- a/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/SulfurCubeBlockDispenseItemBehavior.java
@@ -12,12 +_,19 @@
@@ -12,14 +_,19 @@
@Override
protected ItemStack execute(final BlockSource source, final ItemStack dispensed) {
@@ -11,14 +11,15 @@
}
- public static boolean dispenseBlock(final ServerLevel level, final BlockPos pos, final ItemStack dispensed) {
+ // 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
+
+ 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
for (SulfurCube entity : level.getEntitiesOfClass(SulfurCube.class, new AABB(pos))) {
if (entity.equipItem(dispensed)) {
- 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
dispensed.shrink(1);
return true;
}
@@ -1,23 +1,65 @@
--- a/net/minecraft/core/registries/BuiltInRegistries.java
+++ b/net/minecraft/core/registries/BuiltInRegistries.java
@@ -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();
@@ -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;
+ // 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);
}
public static <T> HolderGetter<T> acquireBootstrapRegistrationLookup(final Registry<T> 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();
}
}
Loaded 100 of 400 files, more files were not shown because too many files have changed in this diff. Show more