mirror of
https://github.com/PaperMC/Paper.git
synced 2026-11-04 17:00:09 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5050b0f333 | ||
|
|
6b6835847e | ||
|
|
13565bc811 | ||
|
|
df120fd8b5 | ||
|
|
7e88a0da38 | ||
|
|
f11902627b | ||
|
|
bb10cf0d5b | ||
|
|
55bebded5f | ||
|
|
f728a121f7 | ||
|
|
2401035c89 | ||
|
|
174574d871 | ||
|
|
8a170a38c6 | ||
|
|
804dd0c3f0 | ||
|
|
ff31a2c997 | ||
|
|
2e549d651c | ||
|
|
7f9c79794e | ||
|
|
9d950517d9 | ||
|
|
4f4904928f | ||
|
|
4623163095 | ||
|
|
eb9098a546 | ||
|
|
e415f5e030 | ||
|
|
e3ba7b1dc8 | ||
|
|
05c2798ec0 | ||
|
|
02378ab582 | ||
|
|
67912d0827 | ||
|
|
e8a1f8fd68 | ||
|
|
1b9bf5c8e9 | ||
|
|
325f9071f0 | ||
|
|
d86d427405 | ||
|
|
2f1ce01e87 | ||
|
|
0d9bf08a23 | ||
|
|
e65c0765c6 | ||
|
|
ac9a1b2c72 | ||
|
|
0db5c93a3d | ||
|
|
2d602d68c1 | ||
|
|
2766dd30b1 | ||
|
|
03af8b4291 | ||
|
|
45fac22871 |
No files matched your search
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+69
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) + "]";
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -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()) {
|
||||
|
||||
+123
@@ -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();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+13
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+242
-35
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
@@ -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);
|
||||
|
||||
+29
@@ -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");
|
||||
}
|
||||
}
|
||||
+17
@@ -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());
|
||||
}
|
||||
}
|
||||
+26
@@ -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 {
|
||||
}
|
||||
+43
@@ -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;
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package testdata.imports.testclasses;
|
||||
|
||||
public class SamePackage {
|
||||
}
|
||||
+2
@@ -2,6 +2,8 @@ package testdata.imports.testclasses.subpkg;
|
||||
|
||||
public class OtherClass {
|
||||
|
||||
public static final int CONSTANT = 0;
|
||||
|
||||
public static class InnerClass {
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ plugins {
|
||||
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
|
||||
|
||||
dependencies {
|
||||
mache("io.papermc:mache:26.3+build.1")
|
||||
mache("io.papermc:mache:26.3+build.2")
|
||||
paperclip("io.papermc:paperclip:3.0.4")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
+1
-1
@@ -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<?>> {
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+5
-6
@@ -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;
|
||||
}
|
||||
File renamed without changes.
+1
-1
@@ -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 {
|
||||
+1
-1
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -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
|
||||
-845
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
+2
-2
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
-1
@@ -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);
|
||||
|
||||
+40
-29
@@ -134,14 +134,14 @@
|
||||
return super.execute(source, dispensed);
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -250,20 +250,20 @@
|
||||
this.setSuccess(true);
|
||||
return dispensed;
|
||||
}
|
||||
@@ -230,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))
|
||||
);
|
||||
@@ -242,7 +_,7 @@
|
||||
dispensed.shrink(1);
|
||||
this.setSuccess(true);
|
||||
@@ -273,20 +273,20 @@
|
||||
}
|
||||
|
||||
return dispensed;
|
||||
@@ -255,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());
|
||||
level.gameEvent(null, GameEvent.BLOCK_PLACE, target);
|
||||
@@ -264,7 +_,7 @@
|
||||
dispensed.shrink(1);
|
||||
this.setSuccess(true);
|
||||
@@ -296,19 +296,30 @@
|
||||
}
|
||||
|
||||
return dispensed;
|
||||
@@ -287,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.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);
|
||||
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)) {
|
||||
|
||||
+60
-11
@@ -1,19 +1,24 @@
|
||||
--- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
|
||||
+++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
|
||||
@@ -24,16 +_,27 @@
|
||||
this.setSuccess(true);
|
||||
@@ -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);
|
||||
if (!tryIgniteExplosiveEntities(level, targetPos)) {
|
||||
- if (!tryIgniteExplosiveEntities(level, targetPos)) {
|
||||
+ // Paper start - Call BlockDispenseEvent
|
||||
+ final @org.jspecify.annotations.Nullable ItemStack[] blockDispenseEventResult = new ItemStack[1];
|
||||
+ if (!tryIgniteExplosiveEntities(level, targetPos, () ->
|
||||
+ (blockDispenseEventResult[0] = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this)) == null)
|
||||
+ ) {
|
||||
+ // Paper end - Call BlockDispenseEvent
|
||||
if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) {
|
||||
+ // Paper start - Call BlockDispenseEvent
|
||||
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, targetPos, dispensed, this);
|
||||
+ if (result != null) {
|
||||
+ this.setSuccess(false);
|
||||
+ return result;
|
||||
+ }
|
||||
+ // Paper end - Call BlockDispenseEvent
|
||||
+ // CraftBukkit start - Ignition by dispensing flint and steel
|
||||
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, targetPos, source.pos()).isCancelled()) {
|
||||
level.setBlockAndUpdate(targetPos, BaseFireBlock.getState(level, targetPos));
|
||||
@@ -21,11 +26,55 @@
|
||||
+ }
|
||||
+ // 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)) {
|
||||
+ if (TntBlock.prime(level, targetPos, null, dispensed, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, source.pos()))) { // CraftBukkit - TNTPrimeEvent
|
||||
+ // 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()) {
|
||||
dispensed.hurtAndBreak(1, level, null, item -> {});
|
||||
@@ -48,11 +_,18 @@
|
||||
return dispensed;
|
||||
}
|
||||
|
||||
- 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;
|
||||
+11
-10
@@ -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;
|
||||
}
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
WorldStem worldStem;
|
||||
try {
|
||||
@@ -169,17 +_,31 @@
|
||||
@@ -169,17 +_,40 @@
|
||||
executor -> WorldLoader.load(
|
||||
worldLoadConfig,
|
||||
context -> {
|
||||
@@ -161,8 +161,16 @@
|
||||
+ }
|
||||
+ // Paper end - migrate startup world
|
||||
LevelDataAndDimensions worldData = LevelStorageSource.getLevelDataAndDimensions(
|
||||
- access, levelDataTag, context.dataConfiguration(), datapackDimensions, context.datapackWorldRegistries()
|
||||
+ access, levelDataTag, context.dataConfiguration(), datapackDimensions, context.datapackWorldRegistries(), net.minecraft.world.level.Level.OVERWORLD // Paper
|
||||
access, levelDataTag, context.dataConfiguration(), datapackDimensions, context.datapackWorldRegistries()
|
||||
+ // Paper start - pass dimension and regenerate missing world gen settings
|
||||
+ , net.minecraft.world.level.Level.OVERWORLD
|
||||
+ , () -> {
|
||||
+ final DedicatedServerProperties properties = settings.getProperties();
|
||||
+ return new WorldGenSettings(
|
||||
+ properties.worldOptions, properties.createDimensions(context.datapackWorldRegistries())
|
||||
+ );
|
||||
+ }
|
||||
+ // Paper end - pass dimension and regenerate missing world gen settings
|
||||
);
|
||||
return new WorldLoader.DataLoadOutput<>(
|
||||
worldData.worldDataAndGenSettings(), worldData.dimensions().dimensionsRegistryAccess()
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
private @Nullable ServerStatus status;
|
||||
private ServerStatus.@Nullable Favicon statusIcon;
|
||||
private final RandomSource random = RandomSource.create();
|
||||
@@ -234,13 +_,14 @@
|
||||
@@ -234,13 +_,15 @@
|
||||
private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
|
||||
private PlayerList playerList;
|
||||
private volatile boolean running = true;
|
||||
+ public volatile boolean hasStarted = false; // Paper
|
||||
+ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
|
||||
private boolean stopped;
|
||||
private int tickCount;
|
||||
@@ -758,7 +759,7 @@
|
||||
protected void runServer() {
|
||||
try {
|
||||
if (!this.initServer()) {
|
||||
@@ -710,30 +_,73 @@
|
||||
@@ -710,30 +_,74 @@
|
||||
}
|
||||
|
||||
this.nextTickTimeNanos = Util.getNanos();
|
||||
@@ -778,6 +779,7 @@
|
||||
+ // tasks are default scheduled at -1 + delay, and first tick will tick at 1
|
||||
+ final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Improve startup message
|
||||
+ LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Improve startup message
|
||||
+ this.hasStarted = true;
|
||||
+ org.spigotmc.WatchdogThread.tick();
|
||||
+ // Paper end
|
||||
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
--- a/net/minecraft/server/commands/RandomCommand.java
|
||||
+++ b/net/minecraft/server/commands/RandomCommand.java
|
||||
@@ -42,7 +_,7 @@
|
||||
.then(drawRandomValueTree("roll", true))
|
||||
.then(
|
||||
Commands.literal("reset")
|
||||
- .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
|
||||
+ .requires(stack -> stack.getBukkitSender().hasPermission("minecraft.command.random.reset")) // Paper - specific permissions for sub command
|
||||
.then(
|
||||
Commands.literal("*")
|
||||
.executes(c -> resetAllSequences(c.getSource()))
|
||||
@@ -122,7 +_,7 @@
|
||||
.then(
|
||||
Commands.argument("sequence", IdentifierArgument.id())
|
||||
.suggests(RandomCommand::suggestRandomSequence)
|
||||
- .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
|
||||
+ .requires(stack -> stack.getBukkitSender().hasPermission("minecraft.command.random.sequence." + (announce ? "roll" : "value"))) // Paper - specific permissions for sub command
|
||||
.executes(
|
||||
c -> randomSample(c.getSource(), RangeArgument.Ints.getRange(c, "range"), IdentifierArgument.getId(c, "sequence"), announce)
|
||||
)
|
||||
+48
-1
@@ -1,6 +1,26 @@
|
||||
--- a/net/minecraft/server/level/PlayerSpawnFinder.java
|
||||
+++ b/net/minecraft/server/level/PlayerSpawnFinder.java
|
||||
@@ -48,7 +_,7 @@
|
||||
@@ -32,6 +_,7 @@
|
||||
private final BlockPos spawnSuggestion;
|
||||
private final int radius;
|
||||
private final int candidateCount;
|
||||
+ private final int candidateArea; // Paper - Sample respawn candidates from the whole square (MC-302446)
|
||||
private final int coprime;
|
||||
private final int offset;
|
||||
private int nextCandidateIndex;
|
||||
@@ -42,13 +_,16 @@
|
||||
this.spawnSuggestion = spawnSuggestion;
|
||||
this.radius = radius;
|
||||
long squareSide = radius * 2L + 1L;
|
||||
- this.candidateCount = (int)Math.min(1024L, squareSide * squareSide);
|
||||
- this.coprime = getCoprime(this.candidateCount);
|
||||
- this.offset = RandomSource.createThreadLocalInstance().nextInt(this.candidateCount);
|
||||
+ // Paper start - Sample respawn candidates from the whole square (MC-302446); only the number of attempts is capped
|
||||
+ this.candidateArea = (int)Math.min(Integer.MAX_VALUE, squareSide * squareSide);
|
||||
+ this.candidateCount = Math.min(ABSOLUTE_MAX_ATTEMPTS, this.candidateArea);
|
||||
+ this.coprime = getCoprime(this.candidateArea);
|
||||
+ this.offset = RandomSource.createThreadLocalInstance().nextInt(this.candidateArea);
|
||||
+ // Paper end - Sample respawn candidates from the whole square (MC-302446)
|
||||
}
|
||||
|
||||
public static CompletableFuture<Vec3> findSpawn(final ServerLevel level, final BlockPos spawnSuggestion) {
|
||||
@@ -9,3 +29,30 @@
|
||||
return CompletableFuture.completedFuture(fixupSpawnHeight(level, spawnSuggestion));
|
||||
}
|
||||
|
||||
@@ -70,7 +_,7 @@
|
||||
private void scheduleNext() {
|
||||
int candidateIndex = this.nextCandidateIndex++;
|
||||
if (candidateIndex < this.candidateCount) {
|
||||
- int value = (this.offset + this.coprime * candidateIndex) % this.candidateCount;
|
||||
+ int value = (int)((this.offset + (long)this.coprime * candidateIndex) % this.candidateArea); // Paper - Sample respawn candidates from the whole square (MC-302446)
|
||||
int deltaX = value % (this.radius * 2 + 1);
|
||||
int deltaZ = value / (this.radius * 2 + 1);
|
||||
int targetX = this.spawnSuggestion.getX() + deltaX - this.radius;
|
||||
@@ -108,7 +_,16 @@
|
||||
}
|
||||
|
||||
private static int getCoprime(final int possibleOrigins) {
|
||||
- return possibleOrigins <= 16 ? possibleOrigins - 1 : 17;
|
||||
+ // Paper start - Sample respawn candidates from the whole square (MC-302446); 17 divides some areas, e.g. 51 * 51
|
||||
+ if (possibleOrigins <= 16) {
|
||||
+ return possibleOrigins - 1;
|
||||
+ }
|
||||
+ int coprime = 17;
|
||||
+ while (com.google.common.math.IntMath.gcd(coprime, possibleOrigins) != 1) {
|
||||
+ coprime++;
|
||||
+ }
|
||||
+ return coprime;
|
||||
+ // Paper end - Sample respawn candidates from the whole square (MC-302446)
|
||||
}
|
||||
|
||||
private void scheduleCandidate(final int candidateX, final int candidateZ, final int candidateIndex, final Supplier<Optional<Vec3>> candidateChecker) {
|
||||
+7
-2
@@ -1222,7 +1222,12 @@
|
||||
this.player
|
||||
.swingAndResetAttackStrength(hand, swingAnimation, success.swingSource() != InteractionResult.SwingSource.PREDICTED);
|
||||
}
|
||||
@@ -1462,20 +_,24 @@
|
||||
@@ -1458,24 +_,28 @@
|
||||
} else if (direction == Direction.DOWN && pos.getY() <= minY) {
|
||||
this.player.sendBuildLimitMessage(false, minY);
|
||||
}
|
||||
- } else if (interactionResult instanceof InteractionResult.Success success && success.shouldSwing()) {
|
||||
+ } else if (interactionResult instanceof InteractionResult.Success success && success.shouldSwing() && !this.player.gameMode.interactResult) { // Paper - Call interact event
|
||||
this.player
|
||||
.swingAndResetAttackStrength(hand, swingAnimation, success.swingSource() != InteractionResult.SwingSource.PREDICTED);
|
||||
}
|
||||
@@ -2109,7 +2114,7 @@
|
||||
+ }
|
||||
+ break;
|
||||
+ case CLONE:
|
||||
+ if (packet.buttonNum() == 2) {
|
||||
+ if (packet.buttonNum() == 0 || packet.buttonNum() == 2) { // Paper - See #11465, workaround for rebound middle clicks in survival, regular middle clicks still don't send a packet
|
||||
+ click = ClickType.MIDDLE;
|
||||
+ if (slotIndex < 0) {
|
||||
+ action = InventoryAction.NOTHING;
|
||||
|
||||
@@ -224,12 +224,13 @@
|
||||
}
|
||||
|
||||
protected abstract void defineSynchedData(SynchedEntityData.Builder entityData);
|
||||
@@ -423,6 +_,48 @@
|
||||
@@ -423,6 +_,49 @@
|
||||
return this.entityData;
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ public void refreshEntityData(ServerPlayer to) {
|
||||
+ if (this.isRemoved()) return; // Paper - Don't send entity data for removed entities
|
||||
+ List<SynchedEntityData.DataValue<?>> values = this.entityData.packAll(); // Paper - Update EVERYTHING not just not default
|
||||
+
|
||||
+ if (to.getBukkitEntity().canSee(this.getBukkitEntity())) { // Paper
|
||||
@@ -604,6 +605,27 @@
|
||||
state.getFluidState().entityInside(this.level(), blockIntersection, this, effectCollector);
|
||||
}
|
||||
|
||||
@@ -1594,7 +_,11 @@
|
||||
|
||||
if (onGround) {
|
||||
if (this.fallDistance > 0.0) {
|
||||
- onState.getBlock().fallOn(this.level(), onState, pos, this, this.fallDistance);
|
||||
+ // Paper start - Add EntityLandEvent
|
||||
+ double distance = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityLandEvent(this.level(), pos, this, this.fallDistance);
|
||||
+ if (distance > 0) {
|
||||
+ onState.getBlock().fallOn(this.level(), onState, pos, this, distance);
|
||||
+ // Paper end - Add EntityLandEvent
|
||||
this.level()
|
||||
.gameEvent(
|
||||
GameEvent.HIT_GROUND,
|
||||
@@ -1603,6 +_,7 @@
|
||||
this, this.mainSupportingBlockPos.<BlockState>map(blockPos -> this.level().getBlockState(blockPos)).orElse(onState)
|
||||
)
|
||||
);
|
||||
+ } // Paper - Add EntityLandEvent
|
||||
}
|
||||
|
||||
this.resetFallDistance();
|
||||
@@ -1818,6 +_,7 @@
|
||||
this.setXRot(Mth.clamp(xRot, -90.0F, 90.0F) % 360.0F);
|
||||
this.yRotO = this.getYRot();
|
||||
|
||||
@@ -208,12 +208,13 @@
|
||||
this.effectsDirty = true;
|
||||
}
|
||||
|
||||
- this.setHealth(input.getFloatOr("Health", this.getMaxHealth()));
|
||||
+ // CraftBukkit start
|
||||
+ input.read("Bukkit.MaxHealth", com.mojang.serialization.Codec.DOUBLE).ifPresent(maxHealth -> {
|
||||
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(maxHealth);
|
||||
+ });
|
||||
+ // CraftBukkit end
|
||||
this.setHealth(input.getFloatOr("Health", this.getMaxHealth()));
|
||||
+ this.setHealth(input.getFloatOr("Health", this.getMaxHealth()), false); // Paper - Fix MC-17876
|
||||
this.hurtTime = input.getShortOr("HurtTime", (short)0);
|
||||
this.deathTime = input.getShortOr("DeathTime", (short)0);
|
||||
this.setSharedFlag(Entity.FLAG_FALL_FLYING, input.getBooleanOr("FallFlying", false));
|
||||
@@ -423,7 +424,7 @@
|
||||
if (effectInstance != null) {
|
||||
this.onEffectsRemoved(List.of(effectInstance));
|
||||
return true;
|
||||
@@ -1152,17 +_,62 @@
|
||||
@@ -1152,18 +_,74 @@
|
||||
}
|
||||
|
||||
public void heal(final float heal) {
|
||||
@@ -463,9 +464,8 @@
|
||||
return this.entityData.get(DATA_HEALTH_ID);
|
||||
}
|
||||
|
||||
- public void setHealth(final float health) {
|
||||
+ // Paper start - Check for NaN
|
||||
+ public void setHealth(float health) {
|
||||
+ public void setHealth(float health, final boolean clamp) { // Paper - Fix MC-17876 - add clamp parameter
|
||||
+ if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) {
|
||||
+ System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set");
|
||||
+ } } // Paper end - Check for NaN
|
||||
@@ -475,7 +475,7 @@
|
||||
+ // Squeeze
|
||||
+ if (health < 0.0F) {
|
||||
+ player.setRealHealth(0.0);
|
||||
+ } else if (health > player.getMaxHealth()) {
|
||||
+ } else if (clamp && health > player.getMaxHealth()) { // Paper - Fix MC-17876
|
||||
+ player.setRealHealth(player.getMaxHealth());
|
||||
+ } else {
|
||||
+ player.setRealHealth(health);
|
||||
@@ -485,9 +485,22 @@
|
||||
+ return;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
this.entityData.set(DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
|
||||
+ // Paper start - Fix MC-17876
|
||||
+ if (clamp) {
|
||||
+ health = Math.clamp(health, 0.0F, this.getMaxHealth());
|
||||
+ } else {
|
||||
+ health = Math.max(health, 0.0F);
|
||||
+ }
|
||||
+
|
||||
+ this.entityData.set(DATA_HEALTH_ID, health);
|
||||
+ }
|
||||
public void setHealth(final float health) {
|
||||
- this.entityData.set(DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
|
||||
+ this.setHealth(health, true);
|
||||
+ // Paper end - Fix MC-17876
|
||||
}
|
||||
|
||||
public boolean isDeadOrDying() {
|
||||
@@ -1180,7 +_,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@
|
||||
+ this.inventory.getItems().stream().filter(item -> !item.isEmpty()).forEach(stack -> this.spawnAtLocation(level, stack)); // Paper - delay removal post event - taken from removeAllItems
|
||||
+ this.postDeathEventTasks.add(this.inventory::clearContent); // Paper - only clear after death event
|
||||
ItemStack itemStack = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
||||
if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
||||
- if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
||||
+ if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) && !this.droppedEquipmentSlots.contains(EquipmentSlot.MAINHAND)) { // Paper
|
||||
this.spawnAtLocation(level, itemStack);
|
||||
- this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
||||
+ this.postDeathEventTasks.add(() -> this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY)); // Paper
|
||||
|
||||
+30
@@ -128,6 +128,36 @@
|
||||
return itWorked ? InteractionResult.SUCCESS_SERVER : InteractionResult.PASS;
|
||||
} else {
|
||||
return Bucketable.bucketMobPickup(player, hand, this).orElse(super.mobInteract(player, hand));
|
||||
@@ -490,16 +_,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ // Paper start - move out equip item checks
|
||||
+ public boolean canEquipItem(final ItemStack heldItem) {
|
||||
+ if (this.isBaby()) return false;
|
||||
+
|
||||
+ final Item swallowedItem = this.getItemBySlot(EquipmentSlot.BODY).getItem();
|
||||
+ if (heldItem.is(swallowedItem)) return false;
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Paper end - move out equip item checks
|
||||
+
|
||||
public boolean equipItem(final ItemStack heldItem) {
|
||||
- if (this.isBaby()) {
|
||||
- return false;
|
||||
- }
|
||||
+ if (!canEquipItem(heldItem)) return false; // Paper - move out equip item checks
|
||||
|
||||
if (this.hasBodyItem()) {
|
||||
- Item swallowedItem = this.getItemBySlot(EquipmentSlot.BODY).getItem();
|
||||
- if (heldItem.is(swallowedItem)) {
|
||||
- return false;
|
||||
- }
|
||||
+ // Paper - move out equip item checks
|
||||
|
||||
Vec3 equipmentSpawnOffset = this.getAttachments().getAverage(EntityAttachment.PASSENGER);
|
||||
if (this.level() instanceof ServerLevel serverLevel) {
|
||||
@@ -525,7 +_,7 @@
|
||||
if (this.level() instanceof ServerLevel serverLevel) {
|
||||
for (SulfurCubeArchetype.ContactDamage damage : this.contactDamages) {
|
||||
|
||||
+16
-1
@@ -1,5 +1,16 @@
|
||||
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/AbstractThrownPotion.java
|
||||
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/AbstractThrownPotion.java
|
||||
@@ -47,9 +_,7 @@
|
||||
return 0.05;
|
||||
}
|
||||
|
||||
- @Override
|
||||
- protected void onHitBlock(final BlockHitResult hitResult) {
|
||||
- super.onHitBlock(hitResult);
|
||||
+ protected void extinguishFires(final BlockHitResult hitResult) { // Paper - move after PotionSplashEvent call to prevent putting out fires for cancelled events
|
||||
if (!this.level().isClientSide()) {
|
||||
ItemStack potionItemStack = this.getItem();
|
||||
Direction hitDirection = hitResult.getDirection();
|
||||
@@ -70,14 +_,21 @@
|
||||
@Override
|
||||
protected void onHit(final HitResult hitResult) {
|
||||
@@ -25,7 +36,7 @@
|
||||
if (potion.potion().isPresent() && potion.potion().get().value().hasInstantEffects()) {
|
||||
level.levelEvent(LevelEvent.PARTICLES_INSTANT_POTION_SPLASH, this.blockPosition(), potion.getColor());
|
||||
if (!this.isSilent()) {
|
||||
@@ -89,49 +_,84 @@
|
||||
@@ -89,49 +_,88 @@
|
||||
level.levelEvent(LevelEvent.SOUND_SPELL_POTION_SPLASH, this.blockPosition(), 0);
|
||||
}
|
||||
}
|
||||
@@ -82,6 +93,10 @@
|
||||
+ this, hitResult, affected, rehydrate, extinguish
|
||||
+ );
|
||||
+ if (!event.isCancelled()) {
|
||||
+ if (hitResult != null && hitResult.getType() == HitResult.Type.BLOCK) {
|
||||
+ this.extinguishFires((BlockHitResult) hitResult);
|
||||
+ }
|
||||
+
|
||||
+ for (org.bukkit.entity.LivingEntity affectedEntity : event.getToDamage()) {
|
||||
+ ((org.bukkit.craftbukkit.entity.CraftLivingEntity) affectedEntity).getHandle().hurtServer(level, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
|
||||
+ }
|
||||
|
||||
@@ -29,3 +29,20 @@
|
||||
return this.container.stillValid(player);
|
||||
}
|
||||
|
||||
@@ -107,7 +_,15 @@
|
||||
if (this.player instanceof ServerPlayer serverPlayer) {
|
||||
ServerLevel level = serverPlayer.level();
|
||||
CraftingInput craftInput = this.container.asCraftInput();
|
||||
- ItemStack result = CrafterBlock.getPotentialResults(level, craftInput).map(recipe -> recipe.value().assemble(craftInput)).orElse(ItemStack.EMPTY);
|
||||
+ // Paper start - Fire PrepareItemCraftEvent for Crafter preview
|
||||
+ final java.util.Optional<net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe>> potentialResults = CrafterBlock.getPotentialResults(level, craftInput);
|
||||
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(
|
||||
+ this.container,
|
||||
+ this.resultContainer,
|
||||
+ potentialResults.map(recipe -> recipe.value().assemble(craftInput)).orElse(ItemStack.EMPTY),
|
||||
+ this.getBukkitView(),
|
||||
+ potentialResults.map(net.minecraft.world.item.crafting.RecipeHolder::value).orElse(null) instanceof net.minecraft.world.item.crafting.RepairItemRecipe);
|
||||
+ // Paper end - Fire PrepareItemCraftEvent for Crafter preview
|
||||
this.resultContainer.setItem(0, result);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class BucketItem extends Item implements DispensibleContainerItem {
|
||||
+ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
|
||||
+ static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
|
||||
protected final Fluid content;
|
||||
|
||||
public BucketItem(final Fluid content, final Item.Properties properties) {
|
||||
@@ -65,7 +65,7 @@
|
||||
+ return this.emptyContents(user, level, pos, hitResult, null, null, null, InteractionHand.MAIN_HAND);
|
||||
+ }
|
||||
+
|
||||
+ public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult, final Direction direction, final BlockPos clicked, final ItemStack itemStack, final InteractionHand hand) {
|
||||
+ public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult, final @Nullable Direction direction, final @Nullable BlockPos clicked, final @Nullable ItemStack itemStack, final InteractionHand hand) {
|
||||
+ // CraftBukkit end
|
||||
if (!(this.content instanceof FlowingFluid flowingFluid)) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/world/item/MobBucketItem.java
|
||||
+++ b/net/minecraft/world/item/MobBucketItem.java
|
||||
@@ -52,18 +_,18 @@
|
||||
@@ -52,18 +_,28 @@
|
||||
}
|
||||
|
||||
if (mob != null) {
|
||||
@@ -14,6 +14,16 @@
|
||||
- public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult) {
|
||||
+ public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult, final net.minecraft.core.Direction direction, final BlockPos clicked, final ItemStack itemStack, final net.minecraft.world.InteractionHand hand) { // Paper
|
||||
if (this.content == Fluids.EMPTY) {
|
||||
+ // Paper start - call PlayerBucketEmptyEvent for mobs in buckets without fluids
|
||||
+ if (user instanceof net.minecraft.world.entity.player.Player player) {
|
||||
+ org.bukkit.event.player.PlayerBucketEmptyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent(level, player, pos, clicked, direction, itemStack, hand);
|
||||
+ if (event.isCancelled()) {
|
||||
+ player.containerMenu.sendAllDataToRemote(); // SPIGOT-4541
|
||||
+ return false;
|
||||
+ }
|
||||
+ BucketItem.itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(org.bukkit.inventory.ItemStack.of(org.bukkit.Material.BUCKET)) ? null : org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.playEmptySound(user, level, pos);
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -70,16 +70,18 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -189,7 +_,8 @@
|
||||
@@ -189,8 +_,9 @@
|
||||
|
||||
@Override
|
||||
public void sendSystemMessage(final Component message) {
|
||||
- if (!this.closed) {
|
||||
- BaseCommandBlock.this.lastOutput = Component.literal("[" + TIME_FORMAT.format(ZonedDateTime.now()) + "] ").append(message);
|
||||
+ if (this.trackOutput && !this.closed) { // Paper - add back source when output disabled
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks
|
||||
BaseCommandBlock.this.lastOutput = Component.literal("[" + TIME_FORMAT.format(ZonedDateTime.now()) + "] ").append(message);
|
||||
+ BaseCommandBlock.this.lastOutput = Component.literal("[" + this.level.paperConfig().commandBlocks.timeFormatPattern.or(TIME_FORMAT).format(ZonedDateTime.now()) + "] ").append(message); // Paper - per-world configurable time format
|
||||
BaseCommandBlock.this.onUpdated(this.level);
|
||||
}
|
||||
}
|
||||
@@ -199,5 +_,12 @@
|
||||
public void close() throws Exception {
|
||||
this.closed = true;
|
||||
|
||||
+4
-2
@@ -1,13 +1,15 @@
|
||||
--- a/net/minecraft/world/level/block/BaseFireBlock.java
|
||||
+++ b/net/minecraft/world/level/block/BaseFireBlock.java
|
||||
@@ -133,12 +_,13 @@
|
||||
@@ -133,12 +_,14 @@
|
||||
final InsideBlockEffectApplier effectApplier,
|
||||
final boolean isPrecise
|
||||
) {
|
||||
+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
|
||||
+ final BlockPos savedPos = pos.immutable(); // Paper - track fire contact
|
||||
effectApplier.apply(InsideBlockEffectType.CLEAR_FREEZE);
|
||||
effectApplier.apply(InsideBlockEffectType.FIRE_IGNITE);
|
||||
effectApplier.runAfter(InsideBlockEffectType.FIRE_IGNITE, e -> e.hurt(e.level().damageSources().inFire(), this.fireDamage));
|
||||
- effectApplier.runAfter(InsideBlockEffectType.FIRE_IGNITE, e -> e.hurt(e.level().damageSources().inFire(), this.fireDamage));
|
||||
+ effectApplier.runAfter(InsideBlockEffectType.FIRE_IGNITE, e -> e.hurt(e.level().damageSources().inFire().eventBlockDamager(level, savedPos), this.fireDamage)); // Paper
|
||||
}
|
||||
|
||||
- public static void fireIgnite(final Entity entity) {
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
--- a/net/minecraft/world/level/block/entity/PotentSulfurBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/PotentSulfurBlockEntity.java
|
||||
@@ -127,7 +_,19 @@
|
||||
&& !entityToBeLaunched.isPassenger()
|
||||
&& !entityToBeLaunched.is(EntityTypeTags.NOT_AFFECTED_BY_GEYSERS)
|
||||
&& entityVelocity.y < 0.3F + waterBlocks * 0.1) {
|
||||
- entityToBeLaunched.addDeltaMovement(new Vec3(0.0, 0.2F, 0.0));
|
||||
+ // Paper start - add EntityPotentSulfurLaunchEvent
|
||||
+ final Vec3 launchVelocity = new Vec3(0.0, GEYSER_LAUNCH_FORCE, 0.0);
|
||||
+ if (io.papermc.paper.event.entity.EntityPotentSulfurLaunchEvent.getHandlerList().hasListeners()) {
|
||||
+ io.papermc.paper.event.entity.EntityPotentSulfurLaunchEvent event = new io.papermc.paper.event.entity.EntityPotentSulfurLaunchEvent(
|
||||
+ entityToBeLaunched.getBukkitEntity(),
|
||||
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
|
||||
+ org.bukkit.craftbukkit.util.CraftLocation.toBukkit(sourceBlock, level.getWorld()),
|
||||
+ org.bukkit.craftbukkit.util.CraftVector.toBukkit(launchVelocity)
|
||||
+ );
|
||||
+ event.callEvent();
|
||||
+ }
|
||||
+ entityToBeLaunched.addDeltaMovement(launchVelocity);
|
||||
+ // Paper end - add EntityPotentSulfurLaunchEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-52
@@ -16,64 +16,14 @@
|
||||
Feature feature = featureHolder.value();
|
||||
removeSapling(level, pos);
|
||||
if (feature.place(level, generator, random, pos)) {
|
||||
@@ -229,4 +_,60 @@
|
||||
@@ -229,4 +_,10 @@
|
||||
|
||||
private record TwoByTwoSaplingPos(int offsetX, int offsetZ, List<Pair<BlockState, BlockPos>> groundLevelSurroundingBlocks) {
|
||||
}
|
||||
+
|
||||
+ // CraftBukkit start
|
||||
+ private void setTreeType(Holder<Feature> feature) {
|
||||
+ if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE;
|
||||
+ } else if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM;
|
||||
+ } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM;
|
||||
+ } else if (feature.is(TreeFeatures.JUNGLE_TREE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE;
|
||||
+ } else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE;
|
||||
+ } else if (feature.is(TreeFeatures.PINE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD;
|
||||
+ } else if (feature.is(TreeFeatures.SPRUCE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD;
|
||||
+ } else if (feature.is(TreeFeatures.ACACIA)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA;
|
||||
+ } else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH;
|
||||
+ } else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH;
|
||||
+ } else if (feature.is(TreeFeatures.SWAMP_OAK)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP;
|
||||
+ } else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE;
|
||||
+ } else if (feature.is(TreeFeatures.JUNGLE_BUSH)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH;
|
||||
+ } else if (feature.is(TreeFeatures.DARK_OAK)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK;
|
||||
+ } else if (feature.is(TreeFeatures.MEGA_SPRUCE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD;
|
||||
+ } else if (feature.is(TreeFeatures.MEGA_PINE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE;
|
||||
+ } else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE;
|
||||
+ } else if (feature.is(TreeFeatures.AZALEA_TREE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA;
|
||||
+ } else if (feature.is(TreeFeatures.MANGROVE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE;
|
||||
+ } else if (feature.is(TreeFeatures.TALL_MANGROVE)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE;
|
||||
+ } else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY;
|
||||
+ } else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK;
|
||||
+ } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING;
|
||||
+ } else if (feature.is(TreeFeatures.RED_POPLAR) || feature.is(TreeFeatures.ORANGE_POPLAR) || feature.is(TreeFeatures.YELLOW_POPLAR)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.POPLAR;
|
||||
+ } else {
|
||||
+ throw new IllegalArgumentException("Unknown tree generator " + feature);
|
||||
+ }
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.craftbukkit.CraftTreeType.fromFeature(feature.unwrapKey().orElseThrow());
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
+4
-4
@@ -8,8 +8,8 @@
|
||||
+ if (numSectors == 255) {
|
||||
+ // We're maxed out, so we need to read the proper length from the section
|
||||
+ ByteBuffer realLen = ByteBuffer.allocate(4);
|
||||
+ this.file.read(realLen, sectorNumber * 4096);
|
||||
+ numSectors = (realLen.getInt(0) + 4) / 4096 + 1;
|
||||
+ this.file.read(realLen, sectorNumber * 4096L);
|
||||
+ numSectors = sizeToSectors(realLen.getInt(0) + 4);
|
||||
+ }
|
||||
+ // Spigot end
|
||||
if (sectorNumber < 2) {
|
||||
@@ -22,8 +22,8 @@
|
||||
+ // Spigot start
|
||||
+ if (numSectors == 255) {
|
||||
+ ByteBuffer realLen = ByteBuffer.allocate(4);
|
||||
+ this.file.read(realLen, sectorNumber * 4096);
|
||||
+ numSectors = (realLen.getInt(0) + 4) / 4096 + 1;
|
||||
+ this.file.read(realLen, sectorNumber * 4096L);
|
||||
+ numSectors = sizeToSectors(realLen.getInt(0) + 4);
|
||||
+ }
|
||||
+ // Spigot end
|
||||
int sectorsLength = numSectors * 4096;
|
||||
|
||||
+41
-2
@@ -1,11 +1,32 @@
|
||||
--- a/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
@@ -145,13 +_,14 @@
|
||||
@@ -145,21 +_,48 @@
|
||||
final WorldDataConfiguration dataConfiguration,
|
||||
final Registry<LevelStem> datapackDimensions,
|
||||
final HolderLookup.Provider registryAccess
|
||||
- ) {
|
||||
+ , final ResourceKey<Level> dimension // Paper - pass dimension
|
||||
) {
|
||||
+ ) {
|
||||
+ // Paper start - Regenerate missing overworld world gen settings
|
||||
+ return getLevelDataAndDimensions(worldAccess, levelDataTag, dataConfiguration, datapackDimensions, registryAccess, dimension, () -> {
|
||||
+ // Preserve dimension settings if we got them from the datapack
|
||||
+ WorldDimensions defaultDimensions = datapackDimensions.containsKey(LevelStem.OVERWORLD)
|
||||
+ ? new WorldDimensions(datapackDimensions)
|
||||
+ : net.minecraft.world.level.levelgen.presets.WorldPresets.createNormalWorldDimensions(registryAccess);
|
||||
+ return new WorldGenSettings(WorldOptions.defaultWithRandomSeed(), defaultDimensions);
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ public static LevelDataAndDimensions getLevelDataAndDimensions(
|
||||
+ final LevelStorageSource.LevelStorageAccess worldAccess,
|
||||
+ final Dynamic<?> levelDataTag,
|
||||
+ final WorldDataConfiguration dataConfiguration,
|
||||
+ final Registry<LevelStem> datapackDimensions,
|
||||
+ final HolderLookup.Provider registryAccess,
|
||||
+ final ResourceKey<Level> dimension,
|
||||
+ final java.util.function.Supplier<WorldGenSettings> worldGenSettingsFactory
|
||||
+ ) {
|
||||
+ // Paper end - Regenerate missing overworld world gen settings
|
||||
if (DataFixers.getFileFixer().requiresFileFixing(NbtUtils.getDataVersion(levelDataTag))) {
|
||||
throw new IllegalStateException("Cannot get level data without file fixing first");
|
||||
}
|
||||
@@ -16,6 +37,24 @@
|
||||
.mapOrElse(
|
||||
Function.identity(),
|
||||
error -> {
|
||||
- LOGGER.error(
|
||||
- "Unable to read or access the world gen settings file! Falling back to the default settings with a random world seed. {}",
|
||||
- error.message()
|
||||
+ // Paper start - Regenerate missing overworld world gen settings
|
||||
+ Path dataLocation = WorldGenSettings.TYPE.id().withSuffix(".dat").resolveAgainst(worldAccess.getDimensionPath(dimension).resolve(LevelResource.DATA.id()));
|
||||
+ if (!Files.notExists(dataLocation, java.nio.file.LinkOption.NOFOLLOW_LINKS)) {
|
||||
+ throw new IllegalStateException("Unable to read or access the world gen settings file " + dataLocation + ". " + error.message());
|
||||
+ }
|
||||
+ LOGGER.warn(
|
||||
+ "World gen settings file is missing! Regenerating settings from server.properties: {}",
|
||||
+ dataLocation
|
||||
);
|
||||
- return new WorldGenSettings(WorldOptions.defaultWithRandomSeed(), new WorldDimensions(datapackDimensions));
|
||||
+ return worldGenSettingsFactory.get();
|
||||
+ // Paper end - Regenerate missing overworld world gen settings
|
||||
}
|
||||
);
|
||||
LevelSettings settings = LevelSettings.parse(dataTag, dataConfiguration);
|
||||
@@ -170,9 +_,9 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
--- a/net/minecraft/world/scores/Scoreboard.java
|
||||
+++ b/net/minecraft/world/scores/Scoreboard.java
|
||||
@@ -267,6 +_,7 @@
|
||||
}
|
||||
|
||||
this.onTeamRemoved(team);
|
||||
+ team.getPlayers().clear(); // Paper - Clear scoreboard team entries on unregister
|
||||
}
|
||||
|
||||
public boolean addPlayerToTeam(final String player, final PlayerTeam team) {
|
||||
@@ -373,7 +_,7 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.mojang.datafixers.DataFixer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
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;
|
||||
@@ -53,6 +54,8 @@ public interface PlatformHooks extends ChunkSystemHooks {
|
||||
|
||||
public void chunkUnloadFromWorld(final LevelChunk chunk);
|
||||
|
||||
public void onFullChunkStatusChange(final LevelChunk chunk, final FullChunkStatus from, final FullChunkStatus to);
|
||||
|
||||
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data);
|
||||
|
||||
public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player);
|
||||
@@ -70,6 +73,8 @@ public interface PlatformHooks extends ChunkSystemHooks {
|
||||
|
||||
public boolean configFixMC224294();
|
||||
|
||||
public boolean configFixMC119599(final ServerLevel world);
|
||||
|
||||
public boolean configAutoConfigSendDistance();
|
||||
|
||||
public double configPlayerMaxLoadRate();
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.PlatformHooks;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
import oshi.SystemInfo;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class FMA {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
static {
|
||||
final Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
FMAInit.init();
|
||||
}
|
||||
};
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.setName(PlatformHooks.get().getBrand() + " FMA initialiser");
|
||||
t.setDaemon(true);
|
||||
t.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> {
|
||||
LOGGER.error("Uncaught exception in thread '" + thread.getName() + "': ", thr);
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
public static void init() {}
|
||||
|
||||
public static boolean isFMASupported() {
|
||||
return FMAInit.FMA_SUPPORTED;
|
||||
}
|
||||
|
||||
private static final class FMAInit {
|
||||
|
||||
public static final boolean FMA_SUPPORTED = detectFma();
|
||||
|
||||
private static void init() {}
|
||||
|
||||
private static boolean detectFma() {
|
||||
final String arch = System.getProperty("os.arch", "").toLowerCase(Locale.ROOT);
|
||||
|
||||
final boolean is64 = arch.contains("64");
|
||||
final boolean arm = arch.contains("aarch64") || arch.contains("arm64");
|
||||
if (arm) {
|
||||
// FMA is ARMv8 baseline, Apple Silicon included
|
||||
LOGGER.info("FMA supported: " + true + " for all ARMv8 platforms");
|
||||
return true;
|
||||
}
|
||||
if (!is64) {
|
||||
LOGGER.info("FMA supported: " + false + " for all non 64-bit platforms");
|
||||
return false;
|
||||
}
|
||||
if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("linux")) {
|
||||
try {
|
||||
for (final String line : new SystemInfo().getHardware().getProcessor().getFeatureFlags()) {
|
||||
if (line.toLowerCase(Locale.ROOT).matches("(?s).*\\bfma\\b.*")) {
|
||||
LOGGER.info("FMA supported: " + true + " for Linux platform with feature flag: " + line);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.info("FMA supported: " + false + " for Linux platform without any matching feature flag");
|
||||
return false;
|
||||
} catch (final Throwable thr) {
|
||||
LOGGER.debug("Error while retrieving platform flags: ", thr);
|
||||
LOGGER.info("FMA supported: " + false + " for Linux platform: unable to retrieve feature flags");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Windows, macOS: measure the actual thing
|
||||
final boolean fma = testForFMA();
|
||||
LOGGER.info("FMA supported: " + fma + " as determined by performance comparison test");
|
||||
return fma;
|
||||
}
|
||||
|
||||
private static boolean testForFMA() {
|
||||
// warm into C2
|
||||
for (int i = 0; i < 3; i++) {
|
||||
timeFma(Sink.X);
|
||||
timeMul(Sink.Y);
|
||||
}
|
||||
final long fma = Math.min(
|
||||
timeFma(Sink.X),
|
||||
Math.min(
|
||||
timeFma(Sink.X), timeFma(Sink.X)
|
||||
)
|
||||
);
|
||||
final long mul = Math.min(
|
||||
timeMul(Sink.Y),
|
||||
Math.min(
|
||||
timeMul(Sink.Y), timeMul(Sink.Y)
|
||||
)
|
||||
);
|
||||
// intrinsic fma ~= mul speed; software is 10x+
|
||||
return fma < mul * 3;
|
||||
}
|
||||
|
||||
private static final double A = 1.000001;
|
||||
private static final double B = 0.999999;
|
||||
private static final int LOOPS = 200_000;
|
||||
|
||||
private static long timeFma(final double[] s) {
|
||||
double x = s[0];
|
||||
long t = System.nanoTime();
|
||||
for (int i = 0; i < LOOPS; i++) {
|
||||
x = Math.fma(A, B, x);
|
||||
}
|
||||
s[0] = x;
|
||||
return System.nanoTime() - t;
|
||||
}
|
||||
|
||||
private static long timeMul(final double[] s) {
|
||||
double y = s[0];
|
||||
long t = System.nanoTime();
|
||||
for (int i = 0; i < LOOPS; i++) {
|
||||
y = y * A + B;
|
||||
}
|
||||
s[0] = y;
|
||||
return System.nanoTime() - t;
|
||||
}
|
||||
|
||||
static final class Sink {
|
||||
static final double[] X = { 0.5 };
|
||||
static final double[] Y = { 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
private FMA() {}
|
||||
}
|
||||
+2
-1
@@ -9,7 +9,8 @@ public final class MoonriseConstants {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
|
||||
// note: only allows increasing the max view distance, as lower values are smaller than the chunk generation radius
|
||||
public static final int MAX_VIEW_DISTANCE = Math.max(32, Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32));
|
||||
public static final boolean NUMA_ENABLE;
|
||||
static {
|
||||
final boolean numaScheduling = Boolean.getBoolean(PlatformHooks.get().getBrand() + ".NumaScheduling");
|
||||
|
||||
@@ -4,7 +4,11 @@ import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import io.papermc.paper.console.BrigadierCompletionMatcher;
|
||||
import io.papermc.paper.console.BrigadierConsoleParser;
|
||||
import io.papermc.paper.util.PaperCacheDir;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.util.StringUtil;
|
||||
import net.minecrell.terminalconsole.SimpleTerminalConsole;
|
||||
import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
|
||||
import org.jline.reader.LineReader;
|
||||
@@ -12,6 +16,8 @@ import org.jline.reader.LineReaderBuilder;
|
||||
|
||||
public final class PaperConsole extends SimpleTerminalConsole {
|
||||
|
||||
private static final ComponentLogger LOGGER = ComponentLogger.logger(PaperConsole.class);
|
||||
|
||||
private final DedicatedServer server;
|
||||
|
||||
public PaperConsole(DedicatedServer server) {
|
||||
@@ -43,7 +49,27 @@ public final class PaperConsole extends SimpleTerminalConsole {
|
||||
|
||||
@Override
|
||||
protected void runCommand(String command) {
|
||||
this.server.handleConsoleInput(command, this.server.createCommandSourceStack());
|
||||
// terminals interpret pressing [enter] and pasting a multi-line string differently,
|
||||
// the latter makes the line reader read it as a single line - and we don't want that
|
||||
// https://github.com/PaperMC/Paper/issues/13006
|
||||
command.lines()
|
||||
.filter(line -> !line.isEmpty())
|
||||
.forEach(line -> {
|
||||
for (char character : line.toCharArray()) {
|
||||
if (!StringUtil.isAllowedChatCharacter(character)) {
|
||||
LOGGER.info(
|
||||
Component.text()
|
||||
.append(Component.text("Illegal console input character: 0x"))
|
||||
.append(Component.text(Integer.toHexString(character)))
|
||||
.color(NamedTextColor.RED)
|
||||
.build()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.server.handleConsoleInput(line, this.server.createCommandSourceStack());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-4
@@ -3,17 +3,14 @@ package com.destroystokyo.paper.loottable;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import io.papermc.paper.configuration.type.DurationOrDisabled;
|
||||
import io.papermc.paper.configuration.type.time.DurationOrDisabled;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.minecraft.core.UUIDUtil;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.world.RandomizableContainer;
|
||||
import net.minecraft.world.entity.vehicle.ContainerEntity;
|
||||
import net.minecraft.world.level.storage.ValueInput;
|
||||
|
||||
+20
@@ -2,6 +2,8 @@ package io.papermc.paper.command.brigadier;
|
||||
|
||||
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.adventure.PaperAdventure;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
@@ -49,6 +51,24 @@ public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBriga
|
||||
return this.getHandle().withEntity(((CraftEntity) executor).getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendReply(final ComponentLike message) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.getHandle().sendSystemMessage(PaperAdventure.asVanilla(message.asComponent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendSuccess(final ComponentLike message, final boolean allowInformingAdmins) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.getHandle().sendSuccess(() -> PaperAdventure.asVanilla(message.asComponent()), allowInformingAdmins);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendFailure(final ComponentLike message) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.getHandle().sendFailure(PaperAdventure.asVanilla(message.asComponent()), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Player getPlayerOrThrow() throws CommandSyntaxException {
|
||||
return this.getHandle().getPlayerOrException().getBukkitEntity();
|
||||
|
||||
@@ -218,6 +218,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
ca.spottedleaf.moonrise.common.util.FMA.init();
|
||||
ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,13 @@ import io.papermc.paper.configuration.transformation.world.versioned.V29_ZeroWor
|
||||
import io.papermc.paper.configuration.transformation.world.versioned.V30_RenameFilterNbtFromSpawnEgg;
|
||||
import io.papermc.paper.configuration.type.BooleanOrDefault;
|
||||
import io.papermc.paper.configuration.type.DespawnRange;
|
||||
import io.papermc.paper.configuration.type.Duration;
|
||||
import io.papermc.paper.configuration.type.DurationOrDisabled;
|
||||
import io.papermc.paper.configuration.type.EngineMode;
|
||||
import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.configuration.type.time.DateTimeFormatterOrDefault;
|
||||
import io.papermc.paper.configuration.type.time.Duration;
|
||||
import io.papermc.paper.configuration.type.time.DurationOrDisabled;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
||||
@@ -193,6 +194,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
.register(DurationOrDisabled.SERIALIZER)
|
||||
.register(NbtPathSerializer.SERIALIZER)
|
||||
.register(IdentifierSerializer.INSTANCE)
|
||||
.register(DateTimeFormatterOrDefault.class, DateTimeFormatterOrDefault.SERIALIZER)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,15 @@ import io.papermc.paper.configuration.serializer.collection.map.ThrowExceptions;
|
||||
import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
|
||||
import io.papermc.paper.configuration.type.BooleanOrDefault;
|
||||
import io.papermc.paper.configuration.type.DespawnRange;
|
||||
import io.papermc.paper.configuration.type.Duration;
|
||||
import io.papermc.paper.configuration.type.DurationOrDisabled;
|
||||
import io.papermc.paper.configuration.type.EngineMode;
|
||||
import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate;
|
||||
import io.papermc.paper.configuration.type.fallback.AutosavePeriod;
|
||||
import io.papermc.paper.configuration.type.number.BelowZeroToEmpty;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.configuration.type.time.DateTimeFormatterOrDefault;
|
||||
import io.papermc.paper.configuration.type.time.Duration;
|
||||
import io.papermc.paper.configuration.type.time.DurationOrDisabled;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
||||
@@ -34,11 +35,11 @@ import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraft.commands.arguments.NbtPathArgument;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.Display;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
@@ -417,6 +418,8 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public boolean optimizeExplosions = false;
|
||||
public boolean disableExplosionKnockback = false;
|
||||
public boolean generateFlatBedrock = false;
|
||||
@Comment("Fixes MC-119599. Vanilla places snow on a chunk while generating that chunk's features, so tree parts that neighbouring chunks generate into it afterwards are left without snow, causing snowless patches of leaves with straight edges at chunk borders. Vanilla also only places snow on the top block of each column, so lower leaf layers and the ground under leaves never get snow. When enabled, snow is placed again as neighbouring chunks generate their features, including on lower leaf layers and the ground under leaves. This changes world generation and only affects newly generated chunks.")
|
||||
public boolean fixMissingTreeSnow = false;
|
||||
public FrostedIce frostedIce;
|
||||
public DoubleOr.Disabled voidDamageAmount = new DoubleOr.Disabled(OptionalDouble.of(4));
|
||||
public double voidDamageMinBuildHeightOffset = -64.0;
|
||||
@@ -574,6 +577,8 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public class CommandBlocks extends ConfigurationPart {
|
||||
public int permissionsLevel = 2;
|
||||
public boolean forceFollowPermLevel = true;
|
||||
@Comment("The time format to use in command block outputs, accepts any DateTimeFormatter pattern.")
|
||||
public DateTimeFormatterOrDefault timeFormatPattern = DateTimeFormatterOrDefault.USE_DEFAULT;
|
||||
}
|
||||
|
||||
public Misc misc;
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package io.papermc.paper.configuration.type.time;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class DateTimeFormatterOrDefault {
|
||||
public static final Serializer SERIALIZER = new Serializer();
|
||||
public static final DateTimeFormatterOrDefault USE_DEFAULT = new DateTimeFormatterOrDefault(Optional.empty(), Serializer.DEFAULT_VALUE);
|
||||
|
||||
private final Optional<DateTimeFormatter> dateTimeFormatter;
|
||||
private final String value;
|
||||
|
||||
public boolean isDefined() {
|
||||
return this.dateTimeFormatter.isPresent();
|
||||
}
|
||||
|
||||
public DateTimeFormatter or(final DateTimeFormatter def) {
|
||||
return this.dateTimeFormatter.orElse(def);
|
||||
}
|
||||
|
||||
public DateTimeFormatter orElseThrow() throws NoSuchElementException {
|
||||
return this.dateTimeFormatter.orElseThrow();
|
||||
}
|
||||
|
||||
private DateTimeFormatterOrDefault(final Optional<DateTimeFormatter> dateTimeFormatter, final String value) {
|
||||
this.dateTimeFormatter = dateTimeFormatter;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static final class Serializer implements TypeSerializer<DateTimeFormatterOrDefault> {
|
||||
private static final String DEFAULT_VALUE = "default";
|
||||
|
||||
@Override
|
||||
public DateTimeFormatterOrDefault deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
|
||||
final String value = node.getString(DEFAULT_VALUE);
|
||||
|
||||
if (value.equals(DEFAULT_VALUE)) {
|
||||
return new DateTimeFormatterOrDefault(Optional.empty(), DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
try {
|
||||
return new DateTimeFormatterOrDefault(Optional.of(DateTimeFormatter.ofPattern(value, Locale.ROOT)), value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SerializationException(DateTimeFormatter.class, "Failed to parse pattern '" + value + "' to a valid DateTimeFormatter format", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(final Type type, @Nullable final DateTimeFormatterOrDefault obj, final ConfigurationNode node) throws SerializationException {
|
||||
node.set(obj == null ? DEFAULT_VALUE : obj.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.papermc.paper.configuration.type;
|
||||
package io.papermc.paper.configuration.type.time;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Objects;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.papermc.paper.configuration.type;
|
||||
package io.papermc.paper.configuration.type.time;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Optional;
|
||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user