Compare commits

..
Author SHA1 Message Date
CreeamandWarrior 5050b0f333 Fix health capped at their base max health upon join (MC-17876) (#13500)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-11-04 08:40:23 -03:00
Jason Penilla 6b6835847e Update Mache to 26.3+build.2 (#14461) 2026-11-03 21:04:01 -07:00
Photoπ 13565bc811 Fix cancelled PlayerInteractEvent causing swing packets (#14433) 2026-11-03 19:50:40 -03:00
Pedro df120fd8b5 Add OperatorFeaturesAllowed API for Signs (#14451) 2026-11-02 20:55:58 -03:00
Jason Penilla 7e88a0da38 Sync latest Moonrise changes (#14452) 2026-11-01 20:07:58 +01:00
Bjarne Koll f11902627b Correct data counts for custom inventories (#14449)
Update the hardcoded data counts in CraftContainer to reference the constants used by the menus themselves.
2026-11-01 12:09:17 +00:00
ClexusandWarrior bb10cf0d5b Add EntityLandEvent (#14204)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-10-31 11:17:23 -03:00
metabrix 55bebded5f Parse pasted multi-line commands separately line by line (#13007) 2026-10-31 10:45:30 -03:00
Michał Fox f728a121f7 Fix respawn radius only covering part of the square (#14390) 2026-10-31 10:31:53 -03:00
balugaq 2401035c89 Add missing ensureSize in MaterialTags (#14438) 2026-10-31 13:27:55 +00:00
Daksh Rawat 174574d871 [ci skip] Fix RedundantNullabilityCheck for nested types (#14430) 2026-10-30 09:15:58 -03:00
Jake Potrebic 8a170a38c6 Update checkstyle (#14431) 2026-10-29 18:53:15 -07:00
Shane Freeder 804dd0c3f0 Centralise TreeType <-> tree feature mapping and test it against TreeGrower (#14417) 2026-10-29 11:28:37 +00:00
Tamion ff31a2c997 Fix cancelled PotionSplashEvent putting out fires (#12194) 2026-10-29 09:29:38 +01:00
Lulu13022002 2e549d651c Call EntityDamageByBlockEvent for fire damage (#12235) 2026-10-29 09:14:15 +01:00
7f9c79794e Fix duplicate dispense events for some items (#13922)
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
Co-authored-by: Bjarne Koll <git@lynxplay.dev>
2026-10-27 19:22:34 +00:00
Daksh Rawat 9d950517d9 [ci skip] Fix JavadocAlignParameterDescriptionCheck with inline tags (#14427) 2026-10-27 16:27:57 +01:00
Marlon Pohl 4f4904928f Add API to query type of PersistentDataContainer value (#14143) 2026-10-26 22:03:51 +01:00
TiggerTakkyTTT 4623163095 Detect rebound middle click clone actions in survival (#14249) 2026-10-26 21:54:48 +01:00
Johan-HynellandWarrior eb9098a546 Made time format output prefix for command block configurable (#14322)
Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2026-10-26 11:18:17 -03:00
Cubicake e415f5e030 Don't refresh entity data if its removed (#14423) 2026-10-25 09:46:40 -03:00
96 changed files with 2130 additions and 1359 deletions

No files matched your search

+25 -2
View File
@@ -14,11 +14,18 @@
<property name="messageKey" value="annotation\.same\.line"/>
<property name="arguments" value="^(?!(${type_use_annotations}))"/>
</module>
<module name="io.papermc.checkstyle.filters.BetterSuppressionSingleFilter">
<!--Allows @deprecated on packages. Not in the javadoc spec (javadoc warns), but it is still rendered in the deprecation block and list-->
<property name="checks" value="InappropriateJavadocBlockTagsOnPackage"/>
<property name="messageKey" value="javadoc\.inappropriate\.tag"/>
<property name="arguments" value="^deprecated$"/>
</module>
<module name="SuppressWarningsFilter"/>
<!--Javadoc Comments-->
<module name="JavadocPackage"/>
<!--Misc-->
<module name="LineEnding"/>
<module name="NewlineAtEndOfFile"/>
<!--Whitespace-->
<module name="FileTabCharacter"/>
@@ -38,6 +45,7 @@
</module>
<module name="AnnotationUseStyle"/>
<module name="MissingDeprecated"/>
<module name="MissingOverrideOnRecordAccessor"/>
<!--Block Checks-->
<module name="AvoidNestedBlocks"/>
@@ -55,7 +63,9 @@
<!--Coding-->
<module name="AvoidDoubleBraceInitialization"/>
<module name="AvoidNoArgumentSuperConstructorCall"/>
<module name="ConstructorsDeclarationGrouping"/>
<module name="ConstructorsDeclarationGrouping">
<property name="orderByIncreasingParameterCount" value="true"/>
</module>
<module name="CovariantEquals"/>
<module name="DeclarationOrder">
<property name="ignoreConstructors" value="true"/>
@@ -63,6 +73,7 @@
<module name="DefaultComesLast"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="ExpressionOverBlockLambda"/>
<module name="FallThrough"/>
<!-- Use non-final variables as an identifier of old code for now -->
<!--<module name="FinalLocalVariable">
@@ -86,6 +97,9 @@
<module name="UnnecessarySemicolonInTryWithResources"/>
<module name="UnusedCatchParameterShouldBeUnnamed"/>
<module name="UnusedLocalVariable"/>
<module name="UnusedPrivateField"/>
<module name="UnusedTryResourceShouldBeUnnamed"/>
<module name="UseEnhancedSwitch"/>
<module name="WhenShouldBeUsed"/>
<!--Headers--> <!--N/A-->
@@ -114,9 +128,13 @@
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @see, @deprecated, @hidden"/>
</module>
<module name="InappropriateJavadocBlockTagsOnField"/>
<module name="InappropriateJavadocBlockTagsOnPackage"/>
<module name="InappropriateJavadocBlockTagsOnType"/>
<module name="InvalidJavadocPosition"/>
<module name="JavadocBlockTagLocation"/>
<module name="JavadocContentLocation"/>
<module name="JavadocEndCommentDelimiter"/>
<module name="JavadocLeadingAsteriskAlign"/>
<module name="JavadocMethod">
<!--checks all, but doesn't require. If we have a doc, it should be valid-->
@@ -124,11 +142,11 @@
</module>
<module name="JavadocMissingLeadingAsterisk"/>
<module name="JavadocMissingWhitespaceAfterAsterisk"/>
<module name="JavadocStyle"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
<module name="JavadocTagContinuationIndentation"/>
<module name="JavadocType"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
<module name="NonEmptyAtclauseDescription"/>
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
<module name="SummaryJavadoc"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
<!--Metrics--> <!--N/A-->
@@ -136,12 +154,14 @@
<module name="ArrayTypeStyle"/>
<module name="AvoidEscapedUnicodeCharacters"/>
<module name="CommentsIndentation"/>
<module name="HexLiteralCase"/>
<!-- Overlaps with the configured FinalLocalVariable, but is more strict -->
<!--<module name="FinalParameters">
<property name="tokens" value="METHOD_DEF,CTOR_DEF,LITERAL_CATCH,FOR_EACH_CLAUSE,PATTERN_VARIABLE_DEF"/>
</module>-->
<module name="NoCodeInFile"/>
<module name="NumericalPrefixesInfixesSuffixesCharacterCase"/>
<module name="OuterTypeFilename"/>
<module name="UpperEll"/>
@@ -180,6 +200,7 @@
<!--Size Violations--> <!--N/A-->
<!--Whitespace-->
<module name="ArrayBracketNoWhitespace"/>
<module name="EmptyForInitializerPad"/>
<module name="EmptyForIteratorPad"/>
<module name="EmptyLineSeparator">
@@ -197,9 +218,11 @@
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<module name="WhitespaceBeforeEmptyBody"/>
<!--Custom-->
<module name="JavadocAlignParameterDescription"/>
<module name="JavadocParamTagOrder"/>
<module name="NullabilityAnnotations"/>
<module name="RedundantNullability"/>
<module name="UnnecessaryFullyQualifiedImport"/>
+3 -1
View File
@@ -477,8 +477,10 @@ public net.minecraft.world.inventory.AbstractMountInventoryMenu mount
public net.minecraft.world.inventory.AnvilMenu cost
public net.minecraft.world.inventory.AnvilMenu itemName
public net.minecraft.world.inventory.AnvilMenu repairItemCountCost
public net.minecraft.world.inventory.BrewingStandMenu DATA_COUNT
public net.minecraft.world.inventory.BrewingStandMenu brewingStandData
public net.minecraft.world.inventory.CraftingMenu access
public net.minecraft.world.inventory.LecternMenu DATA_COUNT
public net.minecraft.world.inventory.MerchantContainer selectionHint
public net.minecraft.world.item.AdventureModePredicate predicates
public net.minecraft.world.item.CrossbowItem FIREWORK_POWER
@@ -583,6 +585,7 @@ public net.minecraft.world.level.block.entity.LecternBlockEntity bookAccess
public net.minecraft.world.level.block.entity.LecternBlockEntity setPage(I)V
public net.minecraft.world.level.block.entity.SculkCatalystBlockEntity$CatalystListener bloom(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/util/RandomSource;)V
public net.minecraft.world.level.block.entity.SculkShriekerBlockEntity warningLevel
public net.minecraft.world.level.block.entity.SignBlockEntity allowOpFeatures
public net.minecraft.world.level.block.entity.SkullBlockEntity customName
public net.minecraft.world.level.block.entity.SkullBlockEntity noteBlockSound
public net.minecraft.world.level.block.entity.SkullBlockEntity owner
@@ -654,7 +657,6 @@ public net.minecraft.world.level.storage.SavedDataStorage cache
public net.minecraft.world.level.storage.TagValueInput input
public net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt$Reference getProvider(Lnet/minecraft/world/level/storage/loot/LootContext;)Ljava/util/Optional;
public net.minecraft.world.scores.criteria.ObjectiveCriteria CRITERIA_CACHE
public net.minecraft.world.timeline.Timeline tracks
public-f net.minecraft.server.MinecraftServer storageSource
public-f net.minecraft.server.ServerAdvancementManager advancements
public-f net.minecraft.server.dedicated.DedicatedServer serverLinks
@@ -1,68 +0,0 @@
package io.papermc.paper.registry.keys;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.annotation.GeneratedClass;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.world.Timeline;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla keys for {@link RegistryKey#TIMELINE}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedClass
public final class TimelineKeys {
/**
* {@code minecraft:day}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Timeline> DAY = create(key("day"));
/**
* {@code minecraft:early_game}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Timeline> EARLY_GAME = create(key("early_game"));
/**
* {@code minecraft:moon}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Timeline> MOON = create(key("moon"));
/**
* {@code minecraft:villager_schedule}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Timeline> VILLAGER_SCHEDULE = create(key("villager_schedule"));
private TimelineKeys() {
}
/**
* Creates a typed key for {@link Timeline} in the registry {@code minecraft:timeline}.
*
* @param key the value's key in the registry
* @return a new typed key
*/
public static TypedKey<Timeline> create(final Key key) {
return TypedKey.create(RegistryKey.TIMELINE, key);
}
}
@@ -1,54 +0,0 @@
package io.papermc.paper.registry.keys;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.annotation.GeneratedClass;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.world.WorldClock;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla keys for {@link RegistryKey#WORLD_CLOCK}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedClass
public final class WorldClockKeys {
/**
* {@code minecraft:overworld}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<WorldClock> OVERWORLD = create(key("overworld"));
/**
* {@code minecraft:the_end}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<WorldClock> THE_END = create(key("the_end"));
private WorldClockKeys() {
}
/**
* Creates a typed key for {@link WorldClock} in the registry {@code minecraft:world_clock}.
*
* @param key the value's key in the registry
* @return a new typed key
*/
public static TypedKey<WorldClock> create(final Key key) {
return TypedKey.create(RegistryKey.WORLD_CLOCK, key);
}
}
@@ -1,68 +0,0 @@
package io.papermc.paper.registry.keys.tags;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.annotation.GeneratedClass;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.world.Timeline;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla tag keys for {@link RegistryKey#TIMELINE}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedClass
public final class TimelineTagKeys {
/**
* {@code #minecraft:in_end}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> IN_END = create(key("in_end"));
/**
* {@code #minecraft:in_nether}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> IN_NETHER = create(key("in_nether"));
/**
* {@code #minecraft:in_overworld}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> IN_OVERWORLD = create(key("in_overworld"));
/**
* {@code #minecraft:universal}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> UNIVERSAL = create(key("universal"));
private TimelineTagKeys() {
}
/**
* Creates a tag key for {@link Timeline} in the registry {@code minecraft:timeline}.
*
* @param key the tag key's key
* @return a new tag key
*/
public static TagKey<Timeline> create(final Key key) {
return TagKey.create(RegistryKey.TIMELINE, key);
}
}
@@ -234,7 +234,8 @@ public class MaterialTags {
* Covers all music disc items.
*/
public static final MaterialSetTag MUSIC_DISCS = new MaterialSetTag(keyFor("music_discs"))
.startsWith("MUSIC_DISC_").lock();
.startsWith("MUSIC_DISC_")
.ensureSize("MUSIC_DISCS", 22).lock();
/**
* Covers all ores.
@@ -0,0 +1,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;
}
}
@@ -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.
@@ -5,8 +5,6 @@ import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import net.kyori.adventure.key.Keyed;
@@ -44,7 +42,6 @@ import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.map.MapCursor;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import static io.papermc.paper.registry.RegistryKeyImpl.create;
@@ -272,16 +269,6 @@ public sealed interface RegistryKey<T> extends Keyed permits RegistryKeyImpl {
* @see io.papermc.paper.registry.keys.DialogKeys
*/
RegistryKey<Dialog> DIALOG = create("dialog");
/**
* Data-driven registry for world clocks.
* @see io.papermc.paper.registry.keys.WorldClockKeys
*/
RegistryKey<WorldClock> WORLD_CLOCK = create("world_clock");
/**
* Data-driven registry for timelines.
* @see io.papermc.paper.registry.keys.TimelineKeys
*/
RegistryKey<Timeline> TIMELINE = create("timeline");
/* ******************* *
@@ -1,132 +0,0 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.util.Map;
import java.util.OptionalInt;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import static io.papermc.paper.util.BoundChecker.requireNonNegative;
/**
* A data-centric version-specific registry entry for the {@link Timeline} type.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface TimelineRegistryEntry {
/**
* Gets the clock that drives this timeline.
*
* @return the clock
*/
@Contract(pure = true)
TypedKey<WorldClock> clock();
/**
* Gets the timeline period, if any.
*
* @return the timeline period
*/
@Contract(pure = true)
OptionalInt periodTicks();
/**
* Gets the time markers defined by this timeline.
*
* @return the time markers
*/
@Contract(pure = true)
Map<Key, TimeMarker> timeMarkers();
/**
* A mutable builder for the {@link TimelineRegistryEntry} plugins may change in applicable registry events.
* <p>
* The following values are required for each builder:
* <ul>
* <li>{@link #clock(TypedKey)}</li>
* </ul>
*/
@ApiStatus.NonExtendable
interface Builder extends TimelineRegistryEntry, RegistryBuilder<Timeline> {
/**
* Sets the clock that drives this timeline.
*
* @param clock the clock
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder clock(TypedKey<WorldClock> clock);
/**
* Sets the timeline period.
*
* @param periodTicks the period in ticks
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder periodTicks(@Range(from = 1, to = Integer.MAX_VALUE) int periodTicks);
/**
* Clears the timeline period.
*
* @return this builder
*/
@Contract(value = "-> this", mutates = "this")
Builder clearPeriodTicks();
/**
* Adds or replaces a time marker on this timeline.
*
* @param key the time marker key
* @param ticks the marker tick
* @param showInCommands whether the marker should be suggested in commands
* @return this builder
*/
@Contract(value = "_, _, _ -> this", mutates = "this")
Builder addTimeMarker(Key key, @Range(from = 0, to = Integer.MAX_VALUE) int ticks, boolean showInCommands);
}
/**
* A time marker defined by a timeline.
*/
@ApiStatus.NonExtendable
interface TimeMarker {
/**
* Gets the tick this marker occurs on.
*
* @return the marker tick
*/
@Range(from = 0, to = Integer.MAX_VALUE)
int ticks();
/**
* Gets whether this marker should be suggested in commands.
*
* @return whether the marker should be suggested in commands
*/
boolean showInCommands();
/**
* Creates a new time marker instance based on the passed values.
*
* @param ticks the tick this marker occurs on, as returned by {@link #ticks()}
* @param showInCommands whether the marker should be suggested in commands, as returned by {@link #showInCommands()}
* @return the created instance
*/
@Contract(value = "_, _ -> new", pure = true)
static TimeMarker of(@Range(from = 0, to = Integer.MAX_VALUE) final int ticks, final boolean showInCommands) {
record Impl(int ticks, boolean showInCommands) implements TimeMarker {
}
return new Impl(requireNonNegative(ticks, "ticks"), showInCommands);
}
}
}
@@ -1,19 +0,0 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.world.WorldClock;
import org.jetbrains.annotations.ApiStatus;
/**
* A data-centric version-specific registry entry for the {@link WorldClock} type.
*/
@ApiStatus.NonExtendable
public interface WorldClockRegistryEntry {
/**
* A mutable builder for the {@link WorldClockRegistryEntry} plugins may change in applicable registry events.
*/
@ApiStatus.NonExtendable
interface Builder extends WorldClockRegistryEntry, RegistryBuilder<WorldClock> {
}
}
@@ -17,15 +17,11 @@ import io.papermc.paper.registry.data.JukeboxSongRegistryEntry;
import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PigVariantRegistryEntry;
import io.papermc.paper.registry.data.SulfurCubeArchetypeRegistryEntry;
import io.papermc.paper.registry.data.TimelineRegistryEntry;
import io.papermc.paper.registry.data.TrimMaterialRegistryEntry;
import io.papermc.paper.registry.data.TrimPatternRegistryEntry;
import io.papermc.paper.registry.data.WolfVariantRegistryEntry;
import io.papermc.paper.registry.data.WorldClockRegistryEntry;
import io.papermc.paper.registry.data.ZombieNautilusVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import org.bukkit.Art;
import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
@@ -72,8 +68,6 @@ public final class RegistryEvents {
public static final RegistryEventProvider<ZombieNautilus.Variant, ZombieNautilusVariantRegistryEntry.Builder> ZOMBIE_NAUTILUS_VARIANT = create(RegistryKey.ZOMBIE_NAUTILUS_VARIANT);
public static final RegistryEventProvider<SulfurCube.Archetype, SulfurCubeArchetypeRegistryEntry.Builder> SULFUR_CUBE_ARCHETYPE = create(RegistryKey.SULFUR_CUBE_ARCHETYPE);
public static final RegistryEventProvider<Dialog, DialogRegistryEntry.Builder> DIALOG = create(RegistryKey.DIALOG);
public static final RegistryEventProvider<WorldClock, WorldClockRegistryEntry.Builder> WORLD_CLOCK = create(RegistryKey.WORLD_CLOCK);
public static final RegistryEventProvider<Timeline, TimelineRegistryEntry.Builder> TIMELINE = create(RegistryKey.TIMELINE);
// End generate - RegistryEvents
private RegistryEvents() {
@@ -1,31 +0,0 @@
package io.papermc.paper.world;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Represents a timeline that can apply environment attributes or define time markers for a {@link WorldClock}.
*/
@NullMarked
@ApiStatus.NonExtendable
public interface Timeline extends Keyed {
// Start generate - Timeline
Timeline DAY = getTimeline("day");
Timeline EARLY_GAME = getTimeline("early_game");
Timeline MOON = getTimeline("moon");
Timeline VILLAGER_SCHEDULE = getTimeline("villager_schedule");
// End generate - Timeline
private static Timeline getTimeline(@KeyPattern.Value final String key) {
return RegistryAccess.registryAccess().getRegistry(RegistryKey.TIMELINE).getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key));
}
}
@@ -1,27 +0,0 @@
package io.papermc.paper.world;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Represents a world clock that tracks time for worlds and drives timelines.
*/
@NullMarked
@ApiStatus.NonExtendable
public interface WorldClock extends Keyed {
// Start generate - WorldClock
WorldClock OVERWORLD = getWorldClock("overworld");
WorldClock THE_END = getWorldClock("the_end");
// End generate - WorldClock
private static WorldClock getWorldClock(@KeyPattern.Value final String key) {
return RegistryAccess.registryAccess().getRegistry(RegistryKey.WORLD_CLOCK).getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key));
}
}
+4 -67
View File
@@ -4,7 +4,6 @@ import io.papermc.paper.entity.poi.PoiSearchResult;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.math.Position;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import io.papermc.paper.world.WorldClock;
import io.papermc.paper.world.chunk.ChunkGenerationState;
import java.io.File;
import java.nio.file.Path;
@@ -2117,7 +2116,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* @return The current relative time
* @see #getFullTime() Returns an absolute time of this world
*/
long getTime();
public long getTime();
/**
* Sets the relative in-game time on the server.
@@ -2132,7 +2131,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* hours*1000)
* @see #setFullTime(long) Sets the absolute time of this world
*/
void setTime(long time);
public void setTime(long time);
/**
* Gets the full in-game time on this world, or {@code 0} if this world does not have a world clock.
@@ -2140,7 +2139,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* @return The current absolute time
* @see #getTime() Returns a relative time of this world
*/
long getFullTime();
public long getFullTime();
/**
* Sets the in-game time on the server
@@ -2152,69 +2151,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* @see #setTime(long) Sets the relative time of this world
* @throws IllegalArgumentException if this world does not have a world clock (e.g. the nether)
*/
void setFullTime(long time);
/**
* Gets the default clock of this world's dimension type.
* <p>
* This is the clock used by {@link #getTime()}, {@link #getFullTime()} and their setters.
*
* @return the default clock, or {@code null} if this world does not have one (e.g. the nether)
*/
@Nullable WorldClock getDefaultClock();
/**
* Gets the total number of ticks the given clock has advanced in this world.
* <p>
* Clocks are per world unless the server is configured to share them between all worlds.
*
* @param clock the clock
* @return the total ticks of the clock
*/
long getFullTime(@NotNull WorldClock clock);
/**
* Sets the total number of ticks of the given clock in this world.
* <p>
* This calls a {@link org.bukkit.event.world.ClockTimeSkipEvent} and does nothing if it is cancelled.
* Clocks are per world unless the server is configured to share them between all worlds.
*
* @param clock the clock
* @param time the new total ticks of the clock, must not be negative
*/
void setFullTime(@NotNull WorldClock clock, long time);
/**
* Gets whether the given clock is paused in this world.
*
* @param clock the clock
* @return whether the clock is paused
*/
boolean isClockPaused(@NotNull WorldClock clock);
/**
* Sets whether the given clock is paused in this world.
*
* @param clock the clock
* @param paused whether the clock should be paused
*/
void setClockPaused(@NotNull WorldClock clock, boolean paused);
/**
* Gets the rate at which the given clock advances in this world, in clock ticks per game tick.
*
* @param clock the clock
* @return the clock rate
*/
float getClockRate(@NotNull WorldClock clock);
/**
* Sets the rate at which the given clock advances in this world, in clock ticks per game tick.
*
* @param clock the clock
* @param rate the new clock rate, must be positive
*/
void setClockRate(@NotNull WorldClock clock, float rate);
public void setFullTime(long time);
// Paper start
@@ -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);
}
@@ -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,
/**
@@ -1,6 +1,5 @@
package org.bukkit.event.world;
import io.papermc.paper.world.WorldClock;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
@@ -12,33 +11,24 @@ import org.jspecify.annotations.NullMarked;
* <p>
* If the event is cancelled the time will not change.
*/
// TODO - snapshot - 26.1 clock
@ApiStatus.Experimental
@NullMarked
public class ClockTimeSkipEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final WorldClock clock;
private final SkipReason skipReason;
private long skipAmount;
private boolean cancelled;
@ApiStatus.Internal
public ClockTimeSkipEvent(final WorldClock clock, final SkipReason skipReason, final long skipAmount) {
this.clock = clock;
public ClockTimeSkipEvent(final SkipReason skipReason, final long skipAmount) {
this.skipReason = skipReason;
this.skipAmount = skipAmount;
}
/**
* Gets the clock whose time is skipping.
*
* @return the clock
*/
public WorldClock getClock() {
return this.clock;
}
/**
* Gets the reason why the time has skipped.
*
@@ -1,6 +1,5 @@
package org.bukkit.event.world;
import io.papermc.paper.world.WorldClock;
import org.bukkit.World;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
@@ -20,8 +19,8 @@ public class TimeSkipEvent extends ClockTimeSkipEvent {
private final World world;
@ApiStatus.Internal
public TimeSkipEvent(@NotNull World world, @NotNull WorldClock clock, @NotNull SkipReason skipReason, long skipAmount) {
super(clock, skipReason, skipAmount);
public TimeSkipEvent(@NotNull World world, @NotNull SkipReason skipReason, long skipAmount) {
super(skipReason, skipAmount);
this.world = world;
}
+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
val testData = sourceSets.create("testData")
dependencies {
implementation("com.puppycrawl.tools:checkstyle:13.8.0")
implementation("com.puppycrawl.tools:checkstyle:14.3.0")
implementation("org.jspecify:jspecify:1.0.0")
testCompileOnly("org.jetbrains:annotations:26.1.0")
@@ -15,7 +15,6 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.lang3.tuple.Pair;
import org.jspecify.annotations.Nullable;
import static java.util.Objects.requireNonNull;
@@ -163,7 +162,16 @@ public final class Util {
return false;
}
public static Pair<String, DetailAST> extractFullIdent(final DetailAST lastDot) {
/**
* A dotted name and the first identifier node it starts at.
*
* @param name the full dotted name
* @param startNode the first identifier node of the name
*/
public record QualifiedName(String name, DetailAST startNode) {
}
public static QualifiedName extractFullIdent(final DetailAST lastDot) {
final List<String> parts = new ArrayList<>();
DetailAST dot = lastDot;
while (dot.getChildCount(TokenTypes.DOT) > 0) {
@@ -180,6 +188,6 @@ public final class Util {
}
parts.addFirst(ident.getText());
return Pair.of(String.join(".", parts), ident);
return new QualifiedName(String.join(".", parts), ident);
}
}
@@ -6,7 +6,9 @@ import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
import io.papermc.checkstyle.Util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -19,7 +21,7 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
@Override
public int[] getDefaultJavadocTokens() {
return new int[]{JavadocCommentsTokenTypes.JAVADOC_CONTENT};
return new int[] {JavadocCommentsTokenTypes.JAVADOC_CONTENT};
}
@Override
@@ -30,6 +32,7 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
final List<ParamDesc> params = new ArrayList<>();
int maxColumn = -1;
DetailNode child = detailNode.getFirstChild();
final Set<Integer> visitedLines = new HashSet<>();
for (final DetailNode javadocBlock : JavadocUtil.getAllNodesOfType(detailNode, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG)) {
final DetailNode paramBlock = JavadocUtil.findFirstToken(javadocBlock, JavadocCommentsTokenTypes.PARAM_BLOCK_TAG);
if (paramBlock == null) {
@@ -41,6 +44,9 @@ public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadoc
}
// iterate over all text nodes (multiline)
for (final DetailNode textNode : JavadocUtil.getAllNodesOfType(paramDescription, JavadocCommentsTokenTypes.TEXT)) {
if (!visitedLines.add(textNode.getLineNumber())) {
continue;
}
final Matcher matcher = SPACE_PREFIX.matcher(textNode.getText());
int paramDescColNum = textNode.getColumnNumber();
if (matcher.find()) {
@@ -0,0 +1,123 @@
package io.papermc.checkstyle.checks;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.DetailNode;
import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
import com.puppycrawl.tools.checkstyle.utils.BlockCommentPosition;
import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
import io.papermc.checkstyle.Util;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
/**
* Checks that {@code @param} tags are in the same order as the parameters they document,
* with type parameters after regular parameters (the order IntelliJ generates them in).
* Only tags that move backward in the expected order are reported, unknown names are ignored.
*/
public final class JavadocParamTagOrderCheck extends AbstractJavadocCheck {
private static final int[] DECLARATIONS = {
TokenTypes.METHOD_DEF,
TokenTypes.CTOR_DEF,
TokenTypes.COMPACT_CTOR_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.RECORD_DEF,
};
@Override
public int[] getDefaultJavadocTokens() {
return new int[] {JavadocCommentsTokenTypes.JAVADOC_CONTENT};
}
@Override
public void visitJavadocToken(final DetailNode detailNode) {
final DetailAST declaration = documentedDeclaration(this.getBlockCommentAst());
if (declaration == null) {
return;
}
final List<String> expectedOrder = expectedParamOrder(declaration);
int furthestIndex = -1;
for (final DetailNode javadocBlock : JavadocUtil.getAllNodesOfType(detailNode, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG)) {
final DetailNode paramBlock = JavadocUtil.findFirstToken(javadocBlock, JavadocCommentsTokenTypes.PARAM_BLOCK_TAG);
if (paramBlock == null) {
continue;
}
final DetailNode paramName = JavadocUtil.findFirstToken(paramBlock, JavadocCommentsTokenTypes.PARAMETER_NAME);
if (paramName == null) {
continue;
}
final int index = expectedOrder.indexOf(paramName.getText());
if (index == -1) {
continue;
}
if (index < furthestIndex) {
this.log(
paramName.getLineNumber(),
paramName.getColumnNumber(),
"@param tag for %s should be in declaration order, with type parameters last".formatted(paramName.getText())
);
} else {
furthestIndex = index;
}
}
}
private static @Nullable DetailAST documentedDeclaration(final DetailAST blockComment) {
if (!BlockCommentPosition.isOnMethod(blockComment)
&& !BlockCommentPosition.isOnConstructor(blockComment)
&& !BlockCommentPosition.isOnCompactConstructor(blockComment)
&& !BlockCommentPosition.isOnClass(blockComment)
&& !BlockCommentPosition.isOnInterface(blockComment)
&& !BlockCommentPosition.isOnRecord(blockComment)) {
return null;
}
DetailAST parent = blockComment.getParent();
while (parent != null && !TokenUtil.isOfType(parent, DECLARATIONS)) {
parent = parent.getParent();
}
return parent;
}
private static List<String> expectedParamOrder(final DetailAST declaration) {
final List<String> names = new ArrayList<>();
if (declaration.getType() == TokenTypes.COMPACT_CTOR_DEF) {
// compact constructors document the components of the enclosing record
addRecordComponentNames(names, declaration.getParent().getParent());
return names;
}
final DetailAST parameters = declaration.findFirstToken(TokenTypes.PARAMETERS);
if (parameters != null) {
for (final DetailAST parameter : Util.childrenIterator(parameters, TokenTypes.PARAMETER_DEF)) {
names.add(identText(parameter));
}
}
addRecordComponentNames(names, declaration);
final DetailAST typeParameters = declaration.findFirstToken(TokenTypes.TYPE_PARAMETERS);
if (typeParameters != null) {
for (final DetailAST typeParameter : Util.childrenIterator(typeParameters, TokenTypes.TYPE_PARAMETER)) {
names.add("<" + identText(typeParameter) + ">");
}
}
return names;
}
private static void addRecordComponentNames(final List<String> names, final DetailAST declaration) {
final DetailAST recordComponents = declaration.findFirstToken(TokenTypes.RECORD_COMPONENTS);
if (recordComponents == null) {
return;
}
for (final DetailAST component : Util.childrenIterator(recordComponents, TokenTypes.RECORD_COMPONENT_DEF)) {
names.add(identText(component));
}
}
private static String identText(final DetailAST ast) {
final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
return ident == null ? "" : ident.getText();
}
}
@@ -28,7 +28,7 @@ public final class NullabilityAnnotationsCheck extends AbstractCheck {
@Override
public int[] getRequiredTokens() {
return new int[]{
return new int[] {
TokenTypes.METHOD_DEF,
TokenTypes.PARAMETER_DEF,
TokenTypes.ANNOTATION_FIELD_DEF,
@@ -20,7 +20,7 @@ public class RedundantNullabilityCheck extends AbstractCheck {
@Override
public int[] getRequiredTokens() {
return new int[]{
return new int[] {
TokenTypes.CLASS_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.ANNOTATION_DEF,
@@ -31,10 +31,20 @@ public class RedundantNullabilityCheck extends AbstractCheck {
@Override
public void visitToken(final DetailAST ast) {
final boolean pkgIsNullMarked = Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation);
final DetailAST nullMarkedAnnotation = NullabilityAnnotationsCheck.getNullMarkedAnnotation(ast);
if (pkgIsNullMarked && nullMarkedAnnotation != null) {
if (nullMarkedAnnotation == null) {
return;
}
final boolean pkgIsNullMarked = Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation);
if (pkgIsNullMarked) {
this.log(nullMarkedAnnotation.getLineNo(), ast.getColumnNo() - 1, "Redundant NullMarked annotation");
return;
}
for (DetailAST parentDef = Util.getEnclosingTypeDeclaration(ast); parentDef != null; parentDef = Util.getEnclosingTypeDeclaration(parentDef)) {
if (NullabilityAnnotationsCheck.getNullMarkedAnnotation(parentDef) != null) {
this.log(nullMarkedAnnotation.getLineNo(), ast.getColumnNo() - 1, "Redundant NullMarked annotation");
return;
}
}
}
}
@@ -1,13 +1,26 @@
package io.papermc.checkstyle.checks;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
import io.papermc.checkstyle.Util;
import io.papermc.checkstyle.checks.util.ImportAwareAbstractCheck;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.jspecify.annotations.Nullable;
/**
* Reports fully qualified type references that could be imported instead.
* <p>
* A reference is only reported if its simple name doesn't already refer to another type in the file,
* either through an import, a type declared in the file, or an unqualified use of that name.
* <p>
* Requires that the codebase be set up to follow standard naming conventions.
* <ul>
* <li>packages are always all lowercase</li>
@@ -16,6 +29,35 @@ import org.jspecify.annotations.Nullable;
*/
public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractCheck {
private static final String JAVA_LANG = "java.lang";
private static final int[] TYPE_CONTEXTS = {
TokenTypes.TYPE,
TokenTypes.TYPE_ARGUMENT,
TokenTypes.TYPE_PARAMETER,
TokenTypes.TYPE_UPPER_BOUNDS,
TokenTypes.TYPE_LOWER_BOUNDS,
TokenTypes.EXTENDS_CLAUSE,
TokenTypes.PERMITS_CLAUSE,
TokenTypes.IMPLEMENTS_CLAUSE,
TokenTypes.LITERAL_THROWS,
TokenTypes.LITERAL_NEW,
TokenTypes.ANNOTATION,
};
private final ThreadLocal<@Nullable FileScope> fileScope = new ThreadLocal<>();
/**
* Type names visible in the file being checked, other than imports.
*
* @param packageName the package of the file
* @param topLevelTypes simple names of top level types declared in the file
* @param nestedTypes simple names of nested and local types declared in the file
* @param unqualifiedTypes simple names of types referenced without qualification
*/
private record FileScope(String packageName, Set<String> topLevelTypes, Set<String> nestedTypes, Set<String> unqualifiedTypes) {
}
@Override
public int[] getDefaultTokens() {
return this.getRequiredTokens();
@@ -28,7 +70,7 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
@Override
public int[] getRequiredTokens() {
return new int[]{
return new int[] {
TokenTypes.TYPE,
TokenTypes.TYPE_ARGUMENT,
TokenTypes.TYPE_PARAMETER,
@@ -37,55 +79,168 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
TokenTypes.EXTENDS_CLAUSE,
TokenTypes.PERMITS_CLAUSE,
TokenTypes.IMPLEMENTS_CLAUSE,
TokenTypes.LITERAL_THROWS,
TokenTypes.LITERAL_NEW,
TokenTypes.ANNOTATION,
TokenTypes.METHOD_CALL,
TokenTypes.METHOD_REF,
TokenTypes.LITERAL_CLASS,
TokenTypes.DOT,
};
}
private static @Nullable DetailAST fromMethodCall(final DetailAST methodCall) {
final DetailAST dot = methodCall.findFirstToken(TokenTypes.DOT);
if (dot == null) {
return null;
@Override
public void beginTree(final DetailAST rootAST) {
super.beginTree(rootAST);
String packageName = "";
final Set<String> topLevelTypes = new HashSet<>();
final Set<String> nestedTypes = new HashSet<>();
final Set<String> unqualifiedTypes = new HashSet<>();
final Deque<DetailAST> nodes = new ArrayDeque<>();
for (DetailAST node = rootAST; node != null; node = node.getNextSibling()) {
nodes.push(node);
}
final DetailAST nextDot = dot.findFirstToken(TokenTypes.DOT);
if (nextDot == null) {
return null;
while (!nodes.isEmpty()) {
final DetailAST node = nodes.pop();
for (DetailAST child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
nodes.push(child);
}
if (node.getType() == TokenTypes.PACKAGE_DEF) {
packageName = FullIdent.createFullIdent(node.getLastChild().getPreviousSibling()).getText();
} else if (node.getType() == TokenTypes.IDENT) {
final DetailAST parent = node.getParent();
if (TokenUtil.isTypeDeclaration(parent.getType())) {
final DetailAST declarationParent = parent.getParent();
if (declarationParent == null || declarationParent.getType() == TokenTypes.COMPILATION_UNIT) {
topLevelTypes.add(node.getText());
} else {
nestedTypes.add(node.getText());
}
} else if (isUnqualifiedTypeReference(node, parent)) {
unqualifiedTypes.add(node.getText());
}
}
}
final DetailAST ident = nextDot.findFirstToken(TokenTypes.IDENT);
if (ident == null || Character.isLowerCase(ident.getText().charAt(0))) {
// method call not on a type
return null;
}
return dot;
this.fileScope.set(new FileScope(packageName, topLevelTypes, nestedTypes, unqualifiedTypes));
}
private static @Nullable DetailAST fromLiteralClass(final DetailAST literalClass) {
final DetailAST dot = literalClass.getParent();
if (dot.getType() != TokenTypes.DOT) {
return null;
@Override
public void finishTree(final DetailAST rootAST) {
super.finishTree(rootAST);
this.fileScope.remove();
}
private static boolean isUnqualifiedTypeReference(final DetailAST ident, final DetailAST parent) {
if (TokenUtil.isOfType(parent, TYPE_CONTEXTS)) {
return true;
}
return dot;
// first part of a dotted name or method reference, like Type.member or Type::member
return Character.isUpperCase(ident.getText().charAt(0))
&& TokenUtil.isOfType(parent, TokenTypes.DOT, TokenTypes.METHOD_REF)
&& parent.getFirstChild() == ident;
}
@Override
public void visitToken(final DetailAST ast) {
final DetailAST actualAst = switch (ast.getType()) {
case TokenTypes.METHOD_CALL -> fromMethodCall(ast);
case TokenTypes.LITERAL_CLASS -> fromLiteralClass(ast);
default -> ast;
};
if (actualAst == null) {
switch (ast.getType()) {
case TokenTypes.METHOD_CALL -> {
final DetailAST callee = ast.findFirstToken(TokenTypes.DOT);
if (callee != null) {
this.checkExpressionChain(callee, true);
}
}
case TokenTypes.METHOD_REF -> this.checkExpressionChain(ast.getFirstChild(), false);
case TokenTypes.DOT -> {
if (isFieldAccess(ast)) {
this.checkExpressionChain(ast, true);
}
}
case TokenTypes.LITERAL_CLASS -> {
final DetailAST dot = ast.getParent();
if (dot.getType() == TokenTypes.DOT) {
this.checkChildren(dot);
}
}
default -> this.checkChildren(ast);
}
}
private void checkChildren(final DetailAST ast) {
// clauses like extends, implements, throws, and bounds can contain multiple types
for (final DetailAST dot : Util.childrenIterator(ast, TokenTypes.DOT)) {
this.checkQualifiedName(dot);
}
}
/**
* Checks the type qualifier in a dotted expression, like {@code java.util.Locale} in {@code java.util.Locale.ROOT}.
*
* @param chain the outermost node of the dotted expression
* @param lastIsMember whether the last part of the chain is a member (field or method) and not part of the type
*/
private void checkExpressionChain(final DetailAST chain, final boolean lastIsMember) {
final List<DetailAST> dots = new ArrayList<>();
DetailAST node = chain;
while (node.getType() == TokenTypes.DOT) {
dots.addFirst(node);
node = node.getFirstChild();
}
if (node.getType() != TokenTypes.IDENT) {
// starts with this, super, a method call, etc.
return;
}
final DetailAST dot = actualAst.findFirstToken(TokenTypes.DOT);
if (dot == null) {
final List<String> parts = new ArrayList<>();
parts.add(node.getText());
for (final DetailAST dot : dots) {
final DetailAST name = Util.getNextSibling(dot.getFirstChild(), TokenTypes.IDENT);
if (name == null) {
// .class, .this, .new, etc.
return;
}
parts.add(name.getText());
}
int packageEnd = 0;
while (packageEnd < parts.size() && !Character.isUpperCase(parts.get(packageEnd).charAt(0))) {
packageEnd++;
}
if (packageEnd == 0 || packageEnd == parts.size()) {
// not qualified by a package, or no type at all
return;
}
final Pair<String, DetailAST> fullIdent = Util.extractFullIdent(dot);
final DetailAST startNode = fullIdent.getRight();
final String fullIdentName = fullIdent.getLeft();
int typeEnd = packageEnd;
while (typeEnd + 1 < parts.size() && Character.isUpperCase(parts.get(typeEnd + 1).charAt(0))) {
typeEnd++;
}
if (lastIsMember && typeEnd == parts.size() - 1) {
typeEnd--;
}
if (typeEnd < packageEnd) {
// only a member, like local.inner.FIELD
return;
}
this.checkQualifiedName(dots.get(typeEnd - 1));
}
private static boolean isFieldAccess(final DetailAST dot) {
final DetailAST parent = dot.getParent();
if (TokenUtil.isOfType(parent, TokenTypes.DOT, TokenTypes.METHOD_CALL, TokenTypes.METHOD_REF) || TokenUtil.isOfType(parent, TYPE_CONTEXTS)) {
return false;
}
for (DetailAST ancestor = parent; ancestor != null; ancestor = ancestor.getParent()) {
if (ancestor.getType() == TokenTypes.EXPR) {
return true;
}
if (TokenUtil.isOfType(ancestor, TYPE_CONTEXTS)) {
return false;
}
}
return false;
}
private void checkQualifiedName(final DetailAST dot) {
final Util.QualifiedName fullIdent = Util.extractFullIdent(dot);
final DetailAST startNode = fullIdent.startNode();
final String fullIdentName = fullIdent.name();
if (Character.isUpperCase(fullIdentName.charAt(0))) {
// not a package
return;
@@ -96,7 +251,7 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
do {
// Check if we have an import for this exact type
if (this.hasImportFor(typeName)) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Fully qualified import used: " + fullIdentName);
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Fully qualified import used: " + withoutConstants(fullIdentName));
return;
}
previousTypeName = typeName;
@@ -104,9 +259,61 @@ public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractChe
} while ((lastDotIdx = typeName.lastIndexOf('.')) != -1 && Character.isUpperCase(typeName.substring(0, lastDotIdx + 2).charAt(lastDotIdx + 1)));
final String outerSimpleName = previousTypeName.substring(previousTypeName.lastIndexOf('.') + 1);
final String fullImportBySimple = this.getFullImportBySimple(outerSimpleName);
if (fullImportBySimple == null) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Unnecessary fully qualified import (no conflict): " + fullIdentName);
final String outerPackage = previousTypeName.substring(0, previousTypeName.lastIndexOf('.'));
if (this.getFullImportBySimple(outerSimpleName) == null && !this.isNameTaken(outerSimpleName, outerPackage)) {
this.log(startNode.getLineNo(), startNode.getColumnNo(), "Unnecessary fully qualified import (no conflict): " + withoutConstants(fullIdentName));
}
}
/**
* Checks if the simple name already refers to a different type in this file.
*
* @param simpleName the simple name of the outermost type
* @param packageName the package of the outermost type
* @return true if the type can't be referenced by its simple name
*/
private boolean isNameTaken(final String simpleName, final String packageName) {
final FileScope scope = this.fileScope.get();
if (scope == null) {
return false;
}
final boolean samePackage = packageName.equals(scope.packageName());
if (scope.nestedTypes().contains(simpleName)) {
// a nested type shadows any other type with the same name
return true;
}
if (scope.topLevelTypes().contains(simpleName)) {
// a top level type with the same name is only the same type if it's in the same package
return !samePackage;
}
if (scope.unqualifiedTypes().contains(simpleName)) {
// unqualified names resolve to the same package or java.lang (imports are already handled)
return !samePackage && !JAVA_LANG.equals(packageName);
}
return false;
}
/**
* Removes trailing constants from a dotted name, like {@code ROOT} in {@code java.util.Locale.ROOT}.
*
* @param name the dotted name
* @return the name without trailing constants
*/
private static String withoutConstants(final String name) {
String result = name;
int lastDot;
while ((lastDot = result.lastIndexOf('.')) != -1 && isConstantName(result.substring(lastDot + 1))) {
final String qualifier = result.substring(0, lastDot);
if (!Character.isUpperCase(qualifier.charAt(qualifier.lastIndexOf('.') + 1))) {
// the constant-like part is the outermost type, like java.util.UUID
break;
}
result = qualifier;
}
return result;
}
private static boolean isConstantName(final String name) {
return name.length() > 1 && name.equals(name.toUpperCase(Locale.ROOT)) && name.chars().anyMatch(Character::isLetter);
}
}
@@ -43,6 +43,7 @@ public abstract class ImportAwareAbstractCheck extends AbstractCheck {
@Override
public void finishTree(final DetailAST rootAST) {
this.imports.remove();
this.simpleImports.remove();
}
protected boolean hasImportFor(final String type) {
@@ -12,12 +12,12 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.intellij.lang.annotations.Language;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junit.platform.commons.support.ReflectionSupport;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -68,10 +69,11 @@ public @interface CheckTest {
assertFalse(builder.checks.isEmpty(), "CheckTestBuilder must have at least one check");
final TreeWalker walker = new TreeWalker();
walker.setModuleFactory(new PackageObjectFactory("io.papermc.checkstyle.checks", Util.class.getClassLoader()));
final Method registerCheck = ReflectionSupport.findMethod(TreeWalker.class, "registerCheck", AbstractCheck.class).orElseThrow();
for (final AbstractCheck check : builder.checks) {
check.init();
check.configure(new DefaultConfiguration("Test"));
MethodUtils.invokeMethod(walker, true, "registerCheck", check);
ReflectionSupport.invokeMethod(registerCheck, walker, check);
}
final Path filePath = Path.of("src", "testData", "java", checkTest.value().replace('.', File.separatorChar) + ".java");
assertTrue(Files.exists(filePath), "File not found: " + filePath);
@@ -0,0 +1,29 @@
package io.papermc.checkstyle.checks;
import io.papermc.checkstyle.CheckTest;
import io.papermc.checkstyle.CheckTestBuilder;
class TestJavadocParamTagOrderCheck {
@CheckTest("testdata.JavadocParamTagOrder")
void test(final CheckTestBuilder builder) {
builder
.addCheck(new JavadocParamTagOrderCheck())
// regular parameters swapped
.addViolation("28:15: @param tag for a should be in declaration order, with type parameters last")
// type parameter before regular parameter
.addViolation("37:15: @param tag for a should be in declaration order, with type parameters last")
// type parameters swapped
.addViolation("47:15: @param tag for <T> should be in declaration order, with type parameters last")
// constructor
.addViolation("66:15: @param tag for a should be in declaration order, with type parameters last")
// class type parameters
.addViolation("75:15: @param tag for <A> should be in declaration order, with type parameters last")
// record component after type parameter
.addViolation("94:15: @param tag for a should be in declaration order, with type parameters last")
// record components swapped
.addViolation("111:15: @param tag for a should be in declaration order, with type parameters last")
// compact constructor
.addViolation("119:19: @param tag for a should be in declaration order, with type parameters last");
}
}
@@ -0,0 +1,17 @@
package io.papermc.checkstyle.checks;
import io.papermc.checkstyle.CheckTest;
import io.papermc.checkstyle.CheckTestBuilder;
class TestRedundantNullabilityCheck {
@CheckTest("testdata.RedundantNullability")
void test(final CheckTestBuilder builder) {
builder
.addViolation("10:8: Redundant NullMarked annotation")
.addViolation("14:8: Redundant NullMarked annotation")
.addViolation("18:8: Redundant NullMarked annotation")
.addViolation("22:8: Redundant NullMarked annotation")
.addCheck(new RedundantNullabilityCheck());
}
}
@@ -32,6 +32,32 @@ class TestUnnecessaryFullyQualifiedImportCheck {
.addViolation("66:5: Fully qualified import used: testdata.imports.testclasses.subpkg.OtherClass")
// class literal ".class"
.addViolation("70:28: Fully qualified import used: testdata.imports.testclasses.subpkg.OtherClass")
// throws clause, multiple types
.addViolation("74:39: Unnecessary fully qualified import (no conflict): java.io.IOException")
.addViolation("74:60: Unnecessary fully qualified import (no conflict): java.io.UncheckedIOException")
// type param with multiple bounds
.addViolation("77:36: Unnecessary fully qualified import (no conflict): testdata.imports.testclasses.subpkg.OtherInterface")
.addViolation("77:89: Unnecessary fully qualified import (no conflict): java.io.Serializable")
// implements clause, multiple types
.addViolation("80:57: Unnecessary fully qualified import (no conflict): testdata.imports.testclasses.subpkg.OtherInterface")
.addViolation("80:109: Unnecessary fully qualified import (no conflict): java.io.Serializable")
// static field access
.addViolation("84:36: Unnecessary fully qualified import (no conflict): java.util.Locale")
.addViolation("85:35: Fully qualified import used: testdata.imports.testclasses.subpkg.OtherClass")
// method call through a field
.addViolation("86:9: Unnecessary fully qualified import (no conflict): java.lang.System")
// method call on a constant, reported without the constant
.addViolation("87:29: Unnecessary fully qualified import (no conflict): java.util.Locale")
// method and constructor references
.addViolation("88:36: Unnecessary fully qualified import (no conflict): java.lang.Thread")
.addViolation("89:41: Unnecessary fully qualified import (no conflict): java.util.ArrayList")
// no violation for holder.inner.VALUE (91), java.util.Random conflicting with a declared type (103),
// or testdata.imports.testclasses.SamePackage conflicting with an unqualified use (108)
// reference to the declared type itself
.addViolation("104:5: Unnecessary fully qualified import (no conflict): testdata.imports.UnnecessaryFullyQualifiedImport")
// same package type, no conflict
.addViolation("109:5: Unnecessary fully qualified import (no conflict): testdata.imports.SamePackage")
// no violation for testdata.imports.Shadowed (115), the nested Shadowed hides the same package type
.addCheck(new UnnecessaryFullyQualifiedImportCheck());
}
}
@@ -45,4 +45,26 @@ public class JavadocParamAlignment {
public void method_multiline_correct(String a, String superLongParamName) {
}
/**
* A method that does something
*
* @param a this is a description
* with a {@link #method(String, String)} link tag
* @param superLongParamName a description
* blabla
*/
public void method_multiline_correct_with_link(String a, String superLongParamName) {
}
/**
* A method where line 2 starts with a link tag
*
* @param a this is a description
* {@link #method(String, String)} is at line start
* @param superLongParamName a description
* blabla
*/
public void method_line2_starts_with_link(String a, String superLongParamName) {
}
}
@@ -0,0 +1,124 @@
// @formatter:off
package testdata;
/**
* Class with type parameters.
*
* @param <A> first
* @param <B> second
*/
@SuppressWarnings("ALL")
public class JavadocParamTagOrder<A, B> {
/**
* Correct order.
*
* @param a first
* @param b second
* @param <T> type
* @param <U> type
*/
public <T, U> void correct(final int a, final int b) {
}
/**
* Regular parameters out of order.
*
* @param b second
* @param a first
*/
public void swapped(final int a, final int b) {
}
/**
* Type parameter before regular parameters.
*
* @param <T> type
* @param a first
*/
public <T> void typeParamFirst(final int a) {
}
/**
* Type parameters out of order.
*
* @param a first
* @param <U> type
* @param <T> type
*/
public <T, U> void typeParamsSwapped(final int a) {
}
/**
* Unknown names are ignored.
*
* @param a first
* @param unknown not a parameter
* @param b second
*/
public void unknown(final int a, final int b) {
}
/**
* Constructor out of order.
*
* @param b second
* @param a first
*/
public JavadocParamTagOrder(final int a, final int b) {
}
/**
* Class type parameters out of order.
*
* @param <B> second
* @param <A> first
*/
interface Swapped<A, B> {
}
/**
* Record components then type parameters.
*
* @param a first
* @param b second
* @param <T> type
*/
record CorrectRecord<T>(int a, int b) {
}
/**
* Record type parameter first.
*
* @param <T> type
* @param a first
*/
record TypeParamFirstRecord<T>(int a) {
/**
* Compact constructor ignores record type parameters.
*
* @param a first
*/
TypeParamFirstRecord {
}
}
/**
* Compact constructor out of order.
*
* @param b second
* @param a first
*/
record CompactRecord(int a, int b) {
/**
* Compact constructor out of order.
*
* @param b second
* @param a first
*/
CompactRecord {
}
}
}
@@ -0,0 +1,36 @@
package testdata;
import org.jspecify.annotations.NullMarked;
public class RedundantNullability {
@NullMarked
public static class OuterClass {
@NullMarked
public static class RedundantInnerClass {
}
@NullMarked
public interface RedundantInnerInterface {
}
@NullMarked
public record RedundantInnerRecord() {
}
@NullMarked
public enum RedundantInnerEnum {
}
public static class ValidInnerClass {
}
}
public static class NonAnnotatedOuter {
@NullMarked
public static class ValidAnnotatedInner {
}
}
}
@@ -0,0 +1,4 @@
package testdata.imports;
public class SamePackage {
}
@@ -0,0 +1,4 @@
package testdata.imports;
public class Shadowed {
}
@@ -70,4 +70,47 @@ public class UnnecessaryFullyQualifiedImport {
System.out.println(testdata.imports.testclasses.subpkg.OtherClass.class);
System.out.println(OtherClass.class);
}
public void throwsClause() throws java.io.IOException, java.io.UncheckedIOException {
}
public <T extends OtherClass & testdata.imports.testclasses.subpkg.OtherInterface & java.io.Serializable> void bounds() {
}
abstract static class MultipleInterfaces implements testdata.imports.testclasses.subpkg.OtherInterface, java.io.Serializable {
}
public void expressions() {
final Object fieldAccess = java.util.Locale.ENGLISH;
final int importedField = testdata.imports.testclasses.subpkg.OtherClass.CONSTANT;
java.lang.System.out.println();
final String root = java.util.Locale.ROOT.toString();
final Runnable methodRef = java.lang.Thread::yield;
final Runnable constructorRef = java.util.ArrayList::new;
final Holder holder = new Holder();
final Object notAType = holder.inner.VALUE;
}
static final class Holder {
static final int VALUE = 0;
Holder inner;
}
// declared in this file, so java.util.Random needs to be qualified
static final class Random {
}
java.util.Random declaredConflict;
testdata.imports.UnnecessaryFullyQualifiedImport self;
// SamePackage resolves to testdata.imports.SamePackage, so the other one needs to be qualified
SamePackage samePackage;
testdata.imports.testclasses.SamePackage otherPackage;
testdata.imports.SamePackage qualifiedSamePackage;
// the nested Shadowed hides testdata.imports.Shadowed, so it needs to be qualified even though it's in the same package
static final class Shadowed {
}
testdata.imports.Shadowed shadowedSamePackage;
}
@@ -0,0 +1,4 @@
package testdata.imports.testclasses;
public class SamePackage {
}
@@ -2,6 +2,8 @@ package testdata.imports.testclasses.subpkg;
public class OtherClass {
public static final int CONSTANT = 0;
public static class InnerClass {
}
@@ -29,9 +29,7 @@ import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.entity.RemovalReason;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.item.MapPostProcessing;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WeatheringCopperState;
import io.papermc.paper.world.WorldClock;
import io.papermc.typewriter.preset.EnumCloneRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
import io.papermc.typewriter.replace.SearchMetadata;
@@ -245,8 +243,6 @@ public final class Rewriters {
.register("ZombieNautilusVariant", ZombieNautilus.Variant.class, new RegistryFieldRewriter<>(Registries.ZOMBIE_NAUTILUS_VARIANT, "getVariant"))
.register("SulfurCubeArchetype", SulfurCube.Archetype.class, new RegistryFieldRewriter<>(Registries.SULFUR_CUBE_ARCHETYPE, "getArchetype"))
.register("Dialog", Dialog.class, new RegistryFieldRewriter<>(Registries.DIALOG, "getDialog"))
.register("WorldClock", WorldClock.class, new RegistryFieldRewriter<>(Registries.WORLD_CLOCK, "getWorldClock"))
.register("Timeline", Timeline.class, new RegistryFieldRewriter<>(Registries.TIMELINE, "getTimeline"))
.register("PoiTypes", PoiTypes.class, new RegistryFieldRewriter<>(Registries.POINT_OF_INTEREST_TYPE, "get"))
.register("MemoryKey", MemoryKey.class, new MemoryKeyRewriter())
// .register("ItemType", org.bukkit.inventory.ItemType.class, new io.papermc.generator.rewriter.types.simple.ItemTypeRewriter()) // - disable for now, lynx want the generic type
@@ -21,15 +21,11 @@ import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PigVariantRegistryEntry;
import io.papermc.paper.registry.data.SoundEventRegistryEntry;
import io.papermc.paper.registry.data.SulfurCubeArchetypeRegistryEntry;
import io.papermc.paper.registry.data.TimelineRegistryEntry;
import io.papermc.paper.registry.data.TrimMaterialRegistryEntry;
import io.papermc.paper.registry.data.TrimPatternRegistryEntry;
import io.papermc.paper.registry.data.WolfVariantRegistryEntry;
import io.papermc.paper.registry.data.WorldClockRegistryEntry;
import io.papermc.paper.registry.data.ZombieNautilusVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@@ -49,7 +45,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dialog.Dialogs;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.clock.WorldClocks;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.SulfurCubeArchetypes;
@@ -85,7 +80,6 @@ import net.minecraft.world.level.gamerules.GameRules;
import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.saveddata.maps.MapDecorationTypes;
import net.minecraft.world.timeline.Timelines;
import org.bukkit.Art;
import org.bukkit.Fluid;
import org.bukkit.GameEvent;
@@ -219,9 +213,7 @@ public final class RegistryEntries {
entry(Registries.PIG_SOUND_VARIANT, PigSoundVariants.class, Pig.SoundVariant.class),
entry(Registries.ZOMBIE_NAUTILUS_VARIANT, ZombieNautilusVariants.class, ZombieNautilus.Variant.class).writableApiRegistryBuilder(ZombieNautilusVariantRegistryEntry.Builder.class, "PaperZombieNautilusVariantRegistryEntry.PaperBuilder"),
entry(Registries.SULFUR_CUBE_ARCHETYPE, SulfurCubeArchetypes.class, SulfurCube.Archetype.class).writableApiRegistryBuilder(SulfurCubeArchetypeRegistryEntry.Builder.class, "PaperSulfurCubeArchetypeRegistryEntry.PaperBuilder"),
entry(Registries.DIALOG, Dialogs.class, Dialog.class, "Paper").allowDirect().writableApiRegistryBuilder(DialogRegistryEntry.Builder.class, "PaperDialogRegistryEntry.PaperBuilder"),
entry(Registries.WORLD_CLOCK, WorldClocks.class, WorldClock.class).writableApiRegistryBuilder(WorldClockRegistryEntry.Builder.class, "PaperWorldClockRegistryEntry.PaperBuilder").delayed(),
entry(Registries.TIMELINE, Timelines.class, Timeline.class).writableApiRegistryBuilder(TimelineRegistryEntry.Builder.class, "PaperTimelineRegistryEntry.PaperBuilder").delayed()
entry(Registries.DIALOG, Dialogs.class, Dialog.class, "Paper").allowDirect().writableApiRegistryBuilder(DialogRegistryEntry.Builder.class, "PaperDialogRegistryEntry.PaperBuilder")
);
public static final List<RegistryEntry<?>> API_ONLY = List.of(
+1 -1
View File
@@ -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")
}
@@ -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;
}
@@ -300,10 +300,10 @@ index 0000000000000000000000000000000000000000..fff7a796a61542213bdaa417d44e25cd
+ }
+}
diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
index aa14dd369d5d2c5c843839f289afdc39c200f0c3..76f990f150f040b1ad6d7d5b91cfb34ebd6f436a 100644
index e8043e52b64ed565128bb18240694ad078911eed..26e252d229dd1a63afa70b41299bc92fc367afdf 100644
--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -13,6 +13,7 @@ import net.minecraft.server.level.ChunkHolder;
@@ -14,6 +14,7 @@ import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -311,7 +311,7 @@ index aa14dd369d5d2c5c843839f289afdc39c200f0c3..76f990f150f040b1ad6d7d5b91cfb34e
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.enderdragon.EnderDragonPart;
import net.minecraft.world.level.BlockGetter;
@@ -150,7 +151,15 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -156,7 +157,15 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@Override
public long[] getCounterTypesUncached(final net.minecraft.server.level.TicketType type) {
@@ -328,7 +328,7 @@ index aa14dd369d5d2c5c843839f289afdc39c200f0c3..76f990f150f040b1ad6d7d5b91cfb34e
}
@Override
@@ -249,7 +258,7 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -260,7 +269,7 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@Override
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
try (final net.minecraft.util.ProblemReporter.ScopedCollector scopedCollector = new net.minecraft.util.ProblemReporter.ScopedCollector(chunk.problemPath(), LOGGER)) {
@@ -876,10 +876,10 @@ index 0000000000000000000000000000000000000000..5916388854ba79ea1261f071c0712e0a
+}
diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb923a2d9a7
index 0000000000000000000000000000000000000000..ad0ef11297ff8875dd08e81a8376b37d81b0a20d
--- /dev/null
+++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
@@ -0,0 +1,12 @@
@@ -0,0 +1,14 @@
+package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess;
+
+public interface PropertyAccess<T> {
@@ -891,6 +891,8 @@ index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb9
+ public T moonrise$getById(final int id);
+
+ public void moonrise$setById(final T[] values);
+
+ public void moonrise$ensureById();
+}
diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
new file mode 100644
@@ -911,10 +913,10 @@ index 0000000000000000000000000000000000000000..5c9f02e82a9ce3511e710d8d00fc03bb
+}
diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bb4008f2a09f0acd47dc7fd21c5a6d6f2c294fd
index 0000000000000000000000000000000000000000..fc41e96de69d5bab8ae1cffb24a5f43e4a137161
--- /dev/null
+++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
@@ -0,0 +1,166 @@
@@ -0,0 +1,167 @@
+package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util;
+
+import ca.spottedleaf.common.util.IntegerUtil;
@@ -951,6 +953,7 @@ index 0000000000000000000000000000000000000000..6bb4008f2a09f0acd47dc7fd21c5a6d6
+
+ int currentMultiple = 1;
+ for (final Property<?> property : sortedProperties) {
+ ((PropertyAccess<?>)property).moonrise$ensureById();
+ final int totalValues = property.getPossibleValues().size();
+
+ this.propertyToIndexer.put(
@@ -1425,18 +1428,21 @@ index 0000000000000000000000000000000000000000..b9b8b4cef442b2788770957c7ecb169c
+}
diff --git a/ca/spottedleaf/moonrise/patches/chunk_gen/noise/NoiseGradients.java b/ca/spottedleaf/moonrise/patches/chunk_gen/noise/NoiseGradients.java
new file mode 100644
index 0000000000000000000000000000000000000000..15c0893979c86a1295928e3e5687f8470639ba02
index 0000000000000000000000000000000000000000..66dd1a6844db8bbf0ca6f752e1f53402eaaa929b
--- /dev/null
+++ b/ca/spottedleaf/moonrise/patches/chunk_gen/noise/NoiseGradients.java
@@ -0,0 +1,131 @@
@@ -0,0 +1,134 @@
+package ca.spottedleaf.moonrise.patches.chunk_gen.noise;
+
+import ca.spottedleaf.moonrise.common.util.FMA;
+import net.minecraft.world.level.levelgen.synth.GradientNoise;
+
+public final class NoiseGradients {
+
+ public static final boolean DEBUG_NOISE = false;
+
+ public static final boolean USE_FMA = FMA.isFMASupported();
+
+ public static final int PERM_SHIFT = 8;
+ public static final int PERM_SIZE = 1 << PERM_SHIFT;
+ public static final int PERM_MASK = PERM_SIZE - 1;
@@ -1486,7 +1492,7 @@ index 0000000000000000000000000000000000000000..15c0893979c86a1295928e3e5687f847
+ final float gz = GRADIENTF_FLAT[(hash << 2) + 2];
+ final float gy = GRADIENTF_FLAT[(hash << 2) + 1];
+ final float gx = GRADIENTF_FLAT[(hash << 2) + 0];
+ if (false) {
+ if (USE_FMA) {
+ return Math.fma(gz, z, Math.fma(gy, y, gx * x));
+ } else {
+ return gx * x + gy * y + gz * z;
@@ -1496,7 +1502,7 @@ index 0000000000000000000000000000000000000000..15c0893979c86a1295928e3e5687f847
+ public static float dotXz(final int hash, final float x, final float z) {
+ final float gz = GRADIENTF_FLAT[(hash << 2) + 2];
+ final float gx = GRADIENTF_FLAT[(hash << 2) + 0];
+ if (false) {
+ if (USE_FMA) {
+ return Math.fma(gz, z, gx * x);
+ } else {
+ return gx * x + gz * z;
@@ -1509,14 +1515,14 @@ index 0000000000000000000000000000000000000000..15c0893979c86a1295928e3e5687f847
+
+ public record GradientF(float x, float y, float z) {
+ public float dot(final float x, final float y, final float z) {
+ if (false) {
+ if (USE_FMA) {
+ return Math.fma(this.z, z, Math.fma(this.y, y, this.x * x));
+ } else {
+ return this.x * x + this.y * y + this.z * z;
+ }
+ }
+ public float dotXz(final float x, final float z) {
+ if (false) {
+ if (USE_FMA) {
+ return Math.fma(this.z, z, this.x * x);
+ } else {
+ return this.x * x + this.z * z;
@@ -1542,7 +1548,7 @@ index 0000000000000000000000000000000000000000..15c0893979c86a1295928e3e5687f847
+
+ public record GradientD(double x, double y, double z) {
+ public double dot(final double x, final double y, final double z) {
+ if (false) {
+ if (USE_FMA) {
+ return Math.fma(this.z, z, Math.fma(this.y, y, this.x * x));
+ } else {
+ return this.x * x + this.y * y + this.z * z;
@@ -10460,10 +10466,10 @@ index 0000000000000000000000000000000000000000..7f75cc224d1dea04405f19880b6a1b57
+}
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a0006f9b56d1e43b06f9f7452e1b5bb0ec53e72
index 0000000000000000000000000000000000000000..1dc745d058d605dde0d10dea82c31b3303d670be
--- /dev/null
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
@@ -0,0 +1,1986 @@
@@ -0,0 +1,1992 @@
+package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
+
+import ca.spottedleaf.concurrentutil.completable.CallbackCompletable;
@@ -11718,6 +11724,7 @@ index 0000000000000000000000000000000000000000..7a0006f9b56d1e43b06f9f7452e1b5bb
+ this.changeEntityChunkStatus(FullChunkStatus.FULL);
+ PlatformHooks.get().onChunkBorder(chunk, this.vanillaChunkHolder);
+ this.onFullChunkLoadChange(true, changedFullStatus);
+ PlatformHooks.get().onFullChunkStatusChange(chunk, FullChunkStatus.INACCESSIBLE, FullChunkStatus.FULL);
+ this.completeFullStatusConsumers(FullChunkStatus.FULL, chunk);
+ }
+
@@ -11725,6 +11732,7 @@ index 0000000000000000000000000000000000000000..7a0006f9b56d1e43b06f9f7452e1b5bb
+ this.updateCurrentState(FullChunkStatus.BLOCK_TICKING);
+ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING);
+ PlatformHooks.get().onChunkTicking(chunk, this.vanillaChunkHolder);
+ PlatformHooks.get().onFullChunkStatusChange(chunk, FullChunkStatus.FULL, FullChunkStatus.BLOCK_TICKING);
+ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, chunk);
+ }
+
@@ -11732,22 +11740,26 @@ index 0000000000000000000000000000000000000000..7a0006f9b56d1e43b06f9f7452e1b5bb
+ this.updateCurrentState(FullChunkStatus.ENTITY_TICKING);
+ this.changeEntityChunkStatus(FullChunkStatus.ENTITY_TICKING);
+ PlatformHooks.get().onChunkEntityTicking(chunk, this.vanillaChunkHolder);
+ PlatformHooks.get().onFullChunkStatusChange(chunk, FullChunkStatus.BLOCK_TICKING, FullChunkStatus.ENTITY_TICKING);
+ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, chunk);
+ }
+ } else {
+ if (current.isOrAfter(FullChunkStatus.ENTITY_TICKING) && !pending.isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
+ PlatformHooks.get().onFullChunkStatusChange(chunk, FullChunkStatus.ENTITY_TICKING, FullChunkStatus.BLOCK_TICKING);
+ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING);
+ PlatformHooks.get().onChunkNotEntityTicking(chunk, this.vanillaChunkHolder);
+ this.updateCurrentState(FullChunkStatus.BLOCK_TICKING);
+ }
+
+ if (current.isOrAfter(FullChunkStatus.BLOCK_TICKING) && !pending.isOrAfter(FullChunkStatus.BLOCK_TICKING)) {
+ PlatformHooks.get().onFullChunkStatusChange(chunk, FullChunkStatus.BLOCK_TICKING, FullChunkStatus.FULL);
+ this.changeEntityChunkStatus(FullChunkStatus.FULL);
+ PlatformHooks.get().onChunkNotTicking(chunk, this.vanillaChunkHolder);
+ this.updateCurrentState(FullChunkStatus.FULL);
+ }
+
+ if (current.isOrAfter(FullChunkStatus.FULL) && !pending.isOrAfter(FullChunkStatus.FULL)) {
+ PlatformHooks.get().onFullChunkStatusChange(chunk, FullChunkStatus.FULL, FullChunkStatus.INACCESSIBLE);
+ this.onFullChunkLoadChange(false, changedFullStatus);
+ this.changeEntityChunkStatus(FullChunkStatus.INACCESSIBLE);
+ PlatformHooks.get().onChunkNotBorder(chunk, this.vanillaChunkHolder);
@@ -29475,10 +29487,10 @@ index 1cd13461a4229eae64ea11f7a5711ce044432719..36defbdc77c0d2b04729e9a69241046f
}
}
diff --git a/net/minecraft/server/level/PlayerSpawnFinder.java b/net/minecraft/server/level/PlayerSpawnFinder.java
index a701288146a1d7cd258faae69f959edf466ec71c..3509df9dd423b38d1ca3e4a420d1352ae95d3551 100644
index 3e9ed8217b04993b30ee43fea08db30dc980b3cf..f9778d150f9499fac1faa4a53d5d91ee5d64066d 100644
--- a/net/minecraft/server/level/PlayerSpawnFinder.java
+++ b/net/minecraft/server/level/PlayerSpawnFinder.java
@@ -115,9 +115,9 @@ public class PlayerSpawnFinder {
@@ -128,9 +128,9 @@ public class PlayerSpawnFinder {
if (!this.finishedFuture.isDone()) {
int chunkX = SectionPos.blockToSectionCoord(candidateX);
int chunkZ = SectionPos.blockToSectionCoord(candidateZ);
@@ -29491,7 +29503,7 @@ index a701288146a1d7cd258faae69f959edf466ec71c..3509df9dd423b38d1ca3e4a420d1352a
.whenCompleteAsync((ignored, throwable) -> {
if (throwable == null) {
try {
@@ -141,7 +141,7 @@ public class PlayerSpawnFinder {
@@ -154,7 +154,7 @@ public class PlayerSpawnFinder {
details.setDetail("Progress", () -> candidateIndex + " out of " + this.candidateCount);
this.finishedFuture.completeExceptionally(new ReportedException(report));
}
@@ -30067,7 +30079,7 @@ index e7ddd956a7f942f682f60062eecf0fad95b18749..267259a79c7668da85d206f9dcbde16b
List<Entity> passengers = this.entity.getPassengers();
if (!passengers.equals(this.lastPassengers)) {
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a 100644
index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1af034d73 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -183,7 +183,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
@@ -30489,7 +30501,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
}
@@ -638,8 +896,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -637,8 +895,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push("checkDespawn");
entity.checkDespawn();
profiler.pop();
@@ -30499,7 +30511,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
Entity vehicle = entity.getVehicle();
if (vehicle != null) {
if (!vehicle.isRemoved() && vehicle.hasPassenger(entity)) {
@@ -666,7 +923,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -665,7 +922,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
profiler.push("entityManagement");
@@ -30508,7 +30520,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
profiler.pop();
profiler.push("debugSynchronizers");
if (this.debugSynchronizers.hasAnySubscriberFor(DebugSubscriptions.NEIGHBOR_UPDATES)) {
@@ -683,7 +940,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -682,7 +939,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public boolean shouldTickBlocksAt(final long chunkPos) {
@@ -30520,7 +30532,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
protected void tickTime() {
@@ -707,7 +967,60 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -706,7 +966,60 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
}
@@ -30581,7 +30593,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
ChunkPos chunkPos = chunk.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
@@ -716,7 +1029,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -715,7 +1028,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
for (int i = 0; i < tickSpeed; i++) {
@@ -30590,7 +30602,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
this.tickPrecipitation(this.getBlockRandomPos(minX, 0, minZ, 15));
}
}
@@ -724,31 +1037,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -723,31 +1036,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.popPush("tickBlocks");
if (tickSpeed > 0) {
@@ -30623,7 +30635,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
profiler.pop();
@@ -1071,6 +1360,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1070,6 +1359,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (fluidState.is(type)) {
fluidState.tick(this, pos, blockState);
}
@@ -30636,7 +30648,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
private void tickBlock(final BlockPos pos, final Block type) {
@@ -1078,6 +1373,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1077,6 +1372,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (state.is(type)) {
state.tick(this, pos, this.random);
}
@@ -30649,7 +30661,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
// Paper start - log detailed entity tick information
@@ -1158,6 +1459,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1157,6 +1458,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public void save(final @Nullable ProgressListener progressListener, final boolean flush, final boolean noSave) {
@@ -30661,7 +30673,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
ServerChunkCache chunkSource = this.getChunkSource();
if (!noSave) {
new org.bukkit.event.world.WorldSaveEvent(this.getWorld()).callEvent(); // CraftBukkit
@@ -1170,13 +1476,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1169,13 +1475,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
progressListener.progressStage(Component.translatable("menu.savingChunks"));
}
@@ -30685,7 +30697,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
private void saveLevelData(final boolean sync) {
@@ -1297,7 +1608,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1296,7 +1607,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.removePlayerImmediately((ServerPlayer)existing, Entity.RemovalReason.DISCARDED);
}
@@ -30694,7 +30706,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
// CraftBukkit start
@@ -1328,7 +1639,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1327,7 +1638,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// CraftBukkit end
@@ -30703,7 +30715,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
}
@@ -1339,7 +1650,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1338,7 +1649,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean tryAddFreshEntityWithPassengers(final Entity entity, final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
// CraftBukkit end
@@ -30712,7 +30724,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
return false;
}
@@ -2390,7 +2701,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2389,7 +2700,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
}
@@ -30721,7 +30733,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
output.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
output.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
output.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
@@ -2408,13 +2719,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2407,13 +2718,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Path chunks = rootDir.resolve("chunks.csv");
try (Writer output = Files.newBufferedWriter(chunks)) {
@@ -30737,7 +30749,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
Path entities = rootDir.resolve("entities.csv");
@@ -2509,8 +2820,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2508,8 +2819,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Locale.ROOT,
"players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s",
this.players.size(),
@@ -30748,7 +30760,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
this.blockEntityTickers.size(),
getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
this.getBlockTicks().count(),
@@ -2543,15 +2854,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2542,15 +2853,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public LevelEntityGetter<Entity> getEntities() {
org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -30777,7 +30789,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
public void startTickingChunk(final LevelChunk levelChunk) {
@@ -2568,8 +2889,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2567,8 +2888,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public void waitForEntities(final ChunkPos centerChunk, final int radius) {
List<ChunkPos> chunks = ChunkPos.rangeClosed(centerChunk, radius).toList();
@@ -30788,7 +30800,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
for (ChunkPos chunk : chunks) {
if (!this.areEntitiesLoaded(chunk.pack())) {
@@ -2588,28 +2909,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2587,28 +2908,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public void close() throws IOException {
super.close();
@@ -30833,7 +30845,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
public boolean anyPlayerCloseEnoughForSpawning(final BlockPos pos) {
@@ -2626,7 +2957,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2625,7 +2956,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public boolean canSpawnEntitiesInChunk(final ChunkPos pos) {
@@ -30845,7 +30857,7 @@ index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450
}
@Override
@@ -2664,7 +2998,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2663,7 +2997,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public CrashReportCategory fillReportDetails(final CrashReport report) {
CrashReportCategory category = super.fillReportDetails(report);
WeatherData weatherData = this.getWeatherData();
@@ -32059,7 +32071,7 @@ index e3644ac93ffc2cfdc5e788456e9d54a9975272f3..6111ecdfaac7c003859040d49d91cc74
+ // Paper end - block counting
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb336ae567b 100644
index 5fb9ac4b0fdd245207c15677795566c81b629a20..02ddc534e534311c9f9b752007b30f16ff69a740 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -166,7 +166,7 @@ public abstract class Entity
@@ -32310,7 +32322,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
public Entity(final EntityType<?> type, final Level level) {
this.type = type;
@@ -1521,34 +1627,81 @@ public abstract class Entity
@@ -1522,34 +1628,81 @@ public abstract class Entity
}
private Vec3 collide(final Vec3 movement) {
@@ -32384,7 +32396,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
+
+ final double stepHeight;
+
+ if ((!collidedDownwards && !this.onGround) || (!collidedX && !collidedZ) || (stepHeight = (double)this.maxUpStep()) <= 0.0) {
+ if ((!collidedDownwards && !this.onGround()) || (!collidedX && !collidedZ) || (stepHeight = (double)this.maxUpStep()) <= 0.0) {
+ return collided;
+ }
+
@@ -32415,7 +32427,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
}
private static float[] collectCandidateStepUpHeights(
@@ -2898,17 +3051,106 @@ public abstract class Entity
@@ -2904,17 +3057,106 @@ public abstract class Entity
return false;
}
@@ -32532,7 +32544,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
}
public InteractionResult interact(final Player player, final InteractionHand hand, final Vec3 location) {
@@ -4573,15 +4815,17 @@ public abstract class Entity
@@ -4579,15 +4821,17 @@ public abstract class Entity
}
public Iterable<Entity> getIndirectPassengers() {
@@ -32558,7 +32570,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
}
public int countPlayerPassengers() {
@@ -4892,6 +5136,15 @@ public abstract class Entity
@@ -4898,6 +5142,15 @@ public abstract class Entity
}
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -32574,7 +32586,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
if (!checkPosition(this, x, y, z)) {
return;
}
@@ -5054,6 +5307,12 @@ public abstract class Entity
@@ -5060,6 +5313,12 @@ public abstract class Entity
@Override
public final void setRemoved(final Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.@Nullable Cause cause) { // CraftBukkit - add Bukkit remove cause
@@ -32587,7 +32599,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit
final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
if (this.removalReason == null) {
@@ -5065,7 +5324,7 @@ public abstract class Entity
@@ -5071,7 +5330,7 @@ public abstract class Entity
this.stopRiding();
}
@@ -32596,7 +32608,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
this.levelCallback.onRemove(reason);
this.onRemoval(reason);
// Paper start - Folia schedulers
@@ -5103,7 +5362,7 @@ public abstract class Entity
@@ -5109,7 +5368,7 @@ public abstract class Entity
public boolean shouldBeSaved() {
return (this.removalReason == null || this.removalReason.shouldSave())
&& !this.isPassenger()
@@ -32606,7 +32618,7 @@ index adc255a80a2c8c4526bfbd1b1506939c878e206a..71ce072c2c9a6349d895054184321cb3
@Override
diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index 81ad9099d2325a07b0b853b99e1d67b0caebd951..e2eb02d8a0a9481be2308c9052582591bc4f9850 100644
index 81ad9099d2325a07b0b853b99e1d67b0caebd951..019a5cbef59465147cbc59e2b8580624cac379e8 100644
--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -94,12 +94,16 @@ public class AcquirePoi {
@@ -32621,7 +32633,7 @@ index 81ad9099d2325a07b0b853b99e1d67b0caebd951..e2eb02d8a0a9481be2308c9052582591
- .collect(Collectors.toSet());
+ // Paper start - optimise POI searches
+ java.util.List<Pair<Holder<PoiType>, BlockPos>> poiPositionsRaw = new java.util.ArrayList<>();
+ ca.spottedleaf.moonrise.patches.poi_lookup.PoiAccess.findNearestPoiPositions(poiManager, poiType, cacheTest, body.blockPosition(), SCAN_RANGE, Double.MAX_VALUE, PoiManager.Occupancy.HAS_SPACE, ca.spottedleaf.moonrise.patches.poi_lookup.PoiAccess.LOAD_FOR_SEARCHING, 5, poiPositionsRaw);
+ ca.spottedleaf.moonrise.patches.poi_lookup.PoiAccess.findNearestPoiPositions(poiManager, poiType, cacheTest, body.blockPosition(), SCAN_RANGE, (double)((long)SCAN_RANGE * (long)SCAN_RANGE), PoiManager.Occupancy.HAS_SPACE, ca.spottedleaf.moonrise.patches.poi_lookup.PoiAccess.LOAD_FOR_SEARCHING, 5, poiPositionsRaw);
+ Set<Pair<Holder<PoiType>, BlockPos>> poiPositions = new java.util.HashSet<>(poiPositionsRaw.size());
+ for (Pair<Holder<PoiType>, BlockPos> pair : poiPositionsRaw) {
+ if (validPoi.test(level, pair.getSecond())) {
@@ -33213,7 +33225,7 @@ index 892b338a2b0a185511c352ca2bb76ee3890836fd..cc938f43c4a5e947ed87ca2001cbaca4
// Paper start - Affects Spawning API
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index f83bfcaca787e74125763cb893b9c162031463ad..a828576457e0bb932750dba507bdef7e8903e144 100644
index f83bfcaca787e74125763cb893b9c162031463ad..c356736c779fc20b6a77ddd442b71b6ecb9745c2 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -85,6 +85,7 @@ import net.minecraft.world.level.storage.LevelData;
@@ -33264,7 +33276,7 @@ index f83bfcaca787e74125763cb893b9c162031463ad..a828576457e0bb932750dba507bdef7e
+ }
+
+ @Override
+ public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
+ public <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
+ Profiler.get().incrementCounter("getEntities");
+ final List<T> ret = new java.util.ArrayList<>();
+
@@ -35679,7 +35691,7 @@ index 1338213c0c918301a8fbc232b25d55804bda9cf9..8e89405ed73e68b641fd8a47963c60a1
@Override
diff --git a/net/minecraft/world/level/block/state/properties/Property.java b/net/minecraft/world/level/block/state/properties/Property.java
index b95665559d646ab8de313a19dadc197a1e8be02c..b82b935272efbc7a7518e148f665f4f24fa96814 100644
index b95665559d646ab8de313a19dadc197a1e8be02c..5d249a6197c4b18bf92e180fb318636e5ae5d203 100644
--- a/net/minecraft/world/level/block/state/properties/Property.java
+++ b/net/minecraft/world/level/block/state/properties/Property.java
@@ -10,7 +10,7 @@ import java.util.stream.Stream;
@@ -35691,7 +35703,7 @@ index b95665559d646ab8de313a19dadc197a1e8be02c..b82b935272efbc7a7518e148f665f4f2
private final Class<T> clazz;
private final String name;
private @Nullable Integer hashCode;
@@ -23,9 +23,38 @@ public abstract class Property<T extends Comparable<T>> {
@@ -23,9 +23,66 @@ public abstract class Property<T extends Comparable<T>> {
);
private final Codec<Property.Value<T>> valueCodec = this.codec.xmap(this::value, Property.Value::value);
@@ -35699,6 +35711,7 @@ index b95665559d646ab8de313a19dadc197a1e8be02c..b82b935272efbc7a7518e148f665f4f2
+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger();
+ private final int id;
+ private T[] byId;
+ private volatile boolean byIdInitialised;
+
+ @Override
+ public final int moonrise$getId() {
@@ -35717,10 +35730,37 @@ index b95665559d646ab8de313a19dadc197a1e8be02c..b82b935272efbc7a7518e148f665f4f2
+ throw new IllegalStateException();
+ }
+ this.byId = byId;
+ this.byIdInitialised = true;
+ }
+
+ @Override
+ public abstract int moonrise$getIdFor(final T value);
+ public final void moonrise$ensureById() {
+ if (this.byIdInitialised) {
+ return;
+ }
+ // properties without a specialised implementation (i.e. custom Property subclasses) build their table here,
+ // as subclass fields are not yet initialised when the Property constructor returns
+ synchronized (this) {
+ if (this.byIdInitialised) {
+ return;
+ }
+ final List<T> values = this.getPossibleValues();
+ final T[] byId = (T[])values.toArray(new Comparable[0]);
+ for (int i = 0; i < byId.length; ++i) {
+ final int id = this.moonrise$getIdFor(byId[i]);
+ if (id != i) {
+ throw new IllegalStateException("Property " + this + " returned internal index " + id + " for value " + byId[i] + ", expected " + i);
+ }
+ }
+ this.moonrise$setById(byId);
+ }
+ }
+
+ @Override
+ public int moonrise$getIdFor(final T value) {
+ // fallback for properties without a specialised implementation (i.e. custom Property subclasses)
+ return this.getInternalIndex(value);
+ }
+ // Paper end - optimise blockstate property access
+
protected Property(final String name, final Class<T> clazz) {
@@ -35978,7 +36018,7 @@ index 12882e7448ff9b7febdb8475e6e14e87d23d87ae..9e66f6202439cc7780af1016289f3b08
public static ProblemReporter.PathElement problemPath(final ChunkPos pos) {
diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java
index 3061b7335af71453dd75ee98539cc7535df747ae..cc4800823c20e8ecbf0b4d74fd85a22bcb70c74c 100644
index 3061b7335af71453dd75ee98539cc7535df747ae..221657b7387f4ab0a1862127c90499f97bcc2276 100644
--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -130,11 +130,14 @@ public abstract class ChunkGenerator {
@@ -36041,6 +36081,105 @@ index 3061b7335af71453dd75ee98539cc7535df747ae..cc4800823c20e8ecbf0b4d74fd85a22b
}
});
possibleBiomes.retainAll(this.biomeSource.possibleBiomes());
@@ -480,7 +499,98 @@ public abstract class ChunkGenerator {
throw new ReportedException(report);
}
}
+ this.fixSnow(level, chunk); // Paper - fix MC-119599
}
+
+ // Paper start - fix MC-119599
+ private static final int FEATURES_RADIUS = 1;
+
+ private void fixSnow(final WorldGenLevel level, final ChunkAccess center) {
+ final ChunkPos centerPos = center.getPos();
+ if (SharedConstants.debugVoidTerrain(centerPos)) {
+ return;
+ }
+ if (!ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC119599(level.getLevel())) {
+ return;
+ }
+
+ final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
+
+ final int cx = centerPos.x();
+ final int cz = centerPos.z();
+
+ // Biome lookups use the same zoom as Vanilla's level.getBiome, which reads noise biomes up to one chunk
+ // outside the chunk being processed, so up to 2 chunks from the center. The region only allows status
+ // checked access to its direct dependencies (TERRAIN within radius 1), so read the cached chunks directly:
+ // FEATURES requires TERRAIN at radius 1, which requires BIOMES at radius 1, so every chunk within
+ // radius 2 has completed BIOMES.
+ final BiomeManager biomeManager = new BiomeManager((final int quartX, final int quartY, final int quartZ) -> {
+ final int chunkX = QuartPos.toSection(quartX);
+ final int chunkZ = QuartPos.toSection(quartZ);
+ if (Math.max(Math.abs(chunkX - cx), Math.abs(chunkZ - cz)) > (FEATURES_RADIUS + 1)) {
+ throw new IllegalStateException("Biome lookup at chunk (" + chunkX + "," + chunkZ + ") is outside radius " + (FEATURES_RADIUS + 1) + " of (" + cx + "," + cz + ")");
+ }
+ return level.getChunk(chunkX, chunkZ).getNoiseBiome(quartX, quartY, quartZ);
+ }, BiomeManager.obfuscateSeed(level.getSeed()));
+ for (int dz = -FEATURES_RADIUS; dz <= FEATURES_RADIUS; ++dz) {
+ for (int dx = -FEATURES_RADIUS; dx <= FEATURES_RADIUS; ++dx) {
+ final int chunkX = dx + cx;
+ final int chunkZ = dz + cz;
+ final ChunkAccess chunk = level.getChunk(chunkX, chunkZ);
+
+ if (!(chunk instanceof ProtoChunk protoChunk) || (protoChunk instanceof ImposterProtoChunk || protoChunk.isUpgrading())
+ || ((dx != 0 || dz != 0) && protoChunk.getPersistedStatus().isBefore(ChunkStatus.FEATURES))) {
+ continue;
+ }
+
+ final int seaLevel = level.getSeaLevel();
+ final net.minecraft.world.level.block.state.BlockState defaultSnowBlock = net.minecraft.world.level.block.Blocks.SNOW.defaultBlockState();
+ for (int lz = 0; lz < 16; ++lz) {
+ for (int lx = 0; lx < 16; ++lx) {
+ final int blockX = (chunkX << 4) | lx;
+ final int blockZ = (chunkZ << 4) | lz;
+
+ final int heightWithoutLeaves = protoChunk.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockX, blockZ) + 1;
+
+ pos.set(blockX, heightWithoutLeaves, blockZ);
+
+ final Biome biome = biomeManager.getBiome(pos).value();
+
+ if (biome.getPrecipitationAt(pos, seaLevel) != Biome.Precipitation.SNOW) {
+ continue;
+ }
+
+ final int heightWithLeaves = protoChunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockX, blockZ) + 1;
+
+ boolean first = true;
+ for (int y = heightWithoutLeaves; y <= heightWithLeaves; ++y) {
+ pos.setY(y);
+
+ final net.minecraft.world.level.block.state.BlockState current = protoChunk.getBlockState(pos);
+
+ // note: light is not generated here (TERRAIN), so really it's 0...
+ if (!current.isAir() || !defaultSnowBlock.canSurvive(level, pos)) {
+ continue;
+ }
+
+ level.setBlock(pos, defaultSnowBlock, net.minecraft.world.level.block.Block.UPDATE_CLIENTS);
+
+ if (first) {
+ first = false;
+ pos.setY(y - 1);
+ final net.minecraft.world.level.block.state.BlockState below = protoChunk.getBlockState(pos);
+ if (below.hasProperty(net.minecraft.world.level.block.SnowyBlock.SNOWY)) {
+ level.setBlock(pos, below.setValue(net.minecraft.world.level.block.SnowyBlock.SNOWY, true), net.minecraft.world.level.block.Block.UPDATE_CLIENTS);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Paper end - fix MC-119599
+
// CraftBukkit start
public void applyBiomeDecoration(final WorldGenLevel level, final ChunkAccess chunk, final StructureManager structureManager) {
this.applyBiomeDecoration(level, chunk, structureManager, true);
diff --git a/net/minecraft/world/level/chunk/Configuration.java b/net/minecraft/world/level/chunk/Configuration.java
index 35e8ea13af811742f0e44afe66b53c3a513f210c..2521b46b128f175d82a9c8d8991346180583588b 100644
--- a/net/minecraft/world/level/chunk/Configuration.java
@@ -39465,16 +39604,17 @@ index d7b2a91df8c5c0ce480bc537b3457fcd144162e1..6da4d95f6a5f6043aaea6d62c550cd9f
return this.x * x + this.y * y + this.z * z;
}
diff --git a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java
index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb4c7ee1b3 100644
index ecbd95204e4650b514d341f79c5756430e08e70f..12be06b795343d0f61cd908dd3f62fab70592a81 100644
--- a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java
+++ b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java
@@ -6,13 +6,39 @@ import net.minecraft.util.Mth;
@@ -6,13 +6,40 @@ import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.densityfunction.DensityBuffer;
import net.minecraft.world.level.levelgen.densityfunction.DensityVolume;
+// Paper start - optimise chunk generation
+import ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.DEBUG_NOISE;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.USE_FMA;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.PERM_MASK;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.PERM_SIZE;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.PERM_SHIFT;
@@ -39509,7 +39649,7 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
}
@Override
@@ -52,7 +78,55 @@ public class PerlinNoise extends GradientNoise {
@@ -52,7 +79,55 @@ public class PerlinNoise extends GradientNoise {
return this.sampleWithDerivative(floorX, floorY, floorZ, relativeX, relativeY, relativeZ, derivativeOut);
}
@@ -39566,7 +39706,7 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
final int x, final int y, final int z, final float relativeX, final float relativeY, final float relativeZ, final float originalRelativeY
) {
int x0 = this.permute(x);
@@ -75,8 +149,172 @@ public class PerlinNoise extends GradientNoise {
@@ -75,8 +150,186 @@ public class PerlinNoise extends GradientNoise {
return Mth.lerp3(xAlpha, yAlpha, zAlpha, d000, d100, d010, d110, d001, d101, d011, d111);
}
@@ -39615,7 +39755,7 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
+ for (int x = 0; x < sX; ++x, xv += xi) {
+ final double val = wrap(xv * xzScale) + this.offsetX;
+ final double floorD = Math.floor(val);
+ final int floor = (int)floorD;
+ final int floor = (int)(long)floorD;
+ final float relative = (float)(val - floorD);
+ final float alpha = Mth.smoothstep(relative);
+
@@ -39629,7 +39769,7 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
+ for (int y = 0; y < sY; ++y, yv += yi) {
+ final double val = wrap(yv * yScale) + this.offsetY;
+ final double floorD = Math.floor(val);
+ final int floor = (int)floorD;
+ final int floor = (int)(long)floorD;
+ final float relative = (float)(val - floorD);
+ final float alpha = Mth.smoothstep(relative);
+
@@ -39643,7 +39783,7 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
+ for (int z = 0; z < sZ; ++z, zv += zi) {
+ final double val = wrap(zv * xzScale) + this.offsetZ;
+ final double floorD = Math.floor(val);
+ final int floor = (int)floorD;
+ final int floor = (int)(long)floorD;
+ final float relative = (float)(val - floorD);
+ final float alpha = Mth.smoothstep(relative);
+
@@ -39723,7 +39863,21 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
+ g111y = NoiseGradients.gradY(g111);
+ }
+
+ buffer.values[index++] += (amplitude * Mth.lerp3(alphaX, alphaY, alphaZ, d000xz + g000y * relativeY, d100xz + g100y * relativeY, d010xz + g010y * (relativeY - 1.0F), d110xz + g110y * (relativeY - 1.0F), d001xz + g001y * relativeY, d101xz + g101y * relativeY, d011xz + g011y * (relativeY - 1.0F), d111xz + g111y * (relativeY - 1.0F)));
+ if (USE_FMA) {
+ buffer.values[index++] += amplitude * Mth.lerp3(
+ alphaX, alphaY, alphaZ,
+ Math.fma(g000y, relativeY, d000xz), Math.fma(g100y, relativeY, d100xz), Math.fma(g010y, (relativeY - 1.0F), d010xz),
+ Math.fma(g110y, (relativeY - 1.0F), d110xz), Math.fma(g001y, relativeY, d001xz), Math.fma(g101y, relativeY, d101xz),
+ Math.fma(g011y, (relativeY - 1.0F), d011xz), Math.fma(g111y, (relativeY - 1.0F), d111xz)
+ );
+ } else {
+ buffer.values[index++] += amplitude * Mth.lerp3(
+ alphaX, alphaY, alphaZ,
+ d000xz + g000y * relativeY, d100xz + g100y * relativeY, d010xz + g010y * (relativeY - 1.0F),
+ d110xz + g110y * (relativeY - 1.0F), d001xz + g001y * relativeY, d101xz + g101y * relativeY,
+ d011xz + g011y * (relativeY - 1.0F), d111xz + g111y * (relativeY - 1.0F)
+ );
+ }
+ }
+ }
+ }
@@ -39741,16 +39895,17 @@ index ecbd95204e4650b514d341f79c5756430e08e70f..8e5ba8f7bed5754b6b657db30cba92fb
float d100xz = 0.0F;
float d010xz = 0.0F;
diff --git a/net/minecraft/world/level/levelgen/synth/SmearedPerlinNoise.java b/net/minecraft/world/level/levelgen/synth/SmearedPerlinNoise.java
index 4884b35596ea7ab327f75f0537affab97df492be..64868bdc6554dea2b1b45fbdac8da6551f6ecc08 100644
index 4884b35596ea7ab327f75f0537affab97df492be..e8eafc7063c7563b0e97ec508dd5401efa3d2b31 100644
--- a/net/minecraft/world/level/levelgen/synth/SmearedPerlinNoise.java
+++ b/net/minecraft/world/level/levelgen/synth/SmearedPerlinNoise.java
@@ -5,6 +5,13 @@ import net.minecraft.util.Mth;
@@ -5,6 +5,14 @@ import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.densityfunction.DensityBuffer;
import net.minecraft.world.level.levelgen.densityfunction.DensityVolume;
+// Paper start - optimise chunk generation
+import ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.DEBUG_NOISE;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.USE_FMA;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.PERM_MASK;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.PERM_SIZE;
+import static ca.spottedleaf.moonrise.patches.chunk_gen.noise.NoiseGradients.PERM_SHIFT;
@@ -39758,7 +39913,7 @@ index 4884b35596ea7ab327f75f0537affab97df492be..64868bdc6554dea2b1b45fbdac8da655
@Deprecated
public class SmearedPerlinNoise extends PerlinNoise {
@@ -51,8 +58,175 @@ public class SmearedPerlinNoise extends PerlinNoise {
@@ -51,8 +59,189 @@ public class SmearedPerlinNoise extends PerlinNoise {
return Mth.floor(fudgeLimit / this.fudgeYScale + 1.0E-7F) * this.fudgeYScale;
}
@@ -39808,7 +39963,7 @@ index 4884b35596ea7ab327f75f0537affab97df492be..64868bdc6554dea2b1b45fbdac8da655
+ for (int x = 0; x < sX; ++x, xv += xi) {
+ final double val = wrap(xv * xzScale) + this.offsetX;
+ final double floorD = Math.floor(val);
+ final int floor = (int)floorD;
+ final int floor = (int)(long)floorD;
+ final float relative = (float)(val - floorD);
+ final float alpha = Mth.smoothstep(relative);
+
@@ -39823,7 +39978,7 @@ index 4884b35596ea7ab327f75f0537affab97df492be..64868bdc6554dea2b1b45fbdac8da655
+ final double original = yv * yScale;
+ final double val = wrap(original) + this.offsetY;
+ final double floorD = Math.floor(val);
+ final int floor = (int)floorD;
+ final int floor = (int)(long)floorD;
+ final double relative = val - floorD;
+ final float fudgedRelative = (float)(relative - this.computeFudgeY(original, relative));
+ final float alpha = Mth.smoothstep((float)relative);
@@ -39838,7 +39993,7 @@ index 4884b35596ea7ab327f75f0537affab97df492be..64868bdc6554dea2b1b45fbdac8da655
+ for (int z = 0; z < sZ; ++z, zv += zi) {
+ final double val = wrap(zv * xzScale) + this.offsetZ;
+ final double floorD = Math.floor(val);
+ final int floor = (int)floorD;
+ final int floor = (int)(long)floorD;
+ final float relative = (float)(val - floorD);
+ final float alpha = Mth.smoothstep(relative);
+
@@ -39918,7 +40073,21 @@ index 4884b35596ea7ab327f75f0537affab97df492be..64868bdc6554dea2b1b45fbdac8da655
+ g111y = NoiseGradients.gradY(g111);
+ }
+
+ buffer.values[index++] += (amplitude * Mth.lerp3(alphaX, alphaY, alphaZ, d000xz + g000y * relativeY, d100xz + g100y * relativeY, d010xz + g010y * (relativeY - 1.0F), d110xz + g110y * (relativeY - 1.0F), d001xz + g001y * relativeY, d101xz + g101y * relativeY, d011xz + g011y * (relativeY - 1.0F), d111xz + g111y * (relativeY - 1.0F)));
+ if (USE_FMA) {
+ buffer.values[index++] += amplitude * Mth.lerp3(
+ alphaX, alphaY, alphaZ,
+ Math.fma(g000y, relativeY, d000xz), Math.fma(g100y, relativeY, d100xz), Math.fma(g010y, (relativeY - 1.0F), d010xz),
+ Math.fma(g110y, (relativeY - 1.0F), d110xz), Math.fma(g001y, relativeY, d001xz), Math.fma(g101y, relativeY, d101xz),
+ Math.fma(g011y, (relativeY - 1.0F), d011xz), Math.fma(g111y, (relativeY - 1.0F), d111xz)
+ );
+ } else {
+ buffer.values[index++] += amplitude * Mth.lerp3(
+ alphaX, alphaY, alphaZ,
+ d000xz + g000y * relativeY, d100xz + g100y * relativeY, d010xz + g010y * (relativeY - 1.0F),
+ d110xz + g110y * (relativeY - 1.0F), d001xz + g001y * relativeY, d101xz + g101y * relativeY,
+ d011xz + g011y * (relativeY - 1.0F), d111xz + g111y * (relativeY - 1.0F)
+ );
+ }
+ }
+ }
+ }
@@ -355,10 +355,10 @@ index 0000000000000000000000000000000000000000..829f7685904fd741e00403647ae76d32
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a..1170fb4c074c495b1a8afb4c5d50a3904b11e0f7 100644
index 5090c9780ada3c8a7fd070bc6aec3ff1af034d73..a4e6225add32df93b0d6009d98ee322941b69938 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -888,6 +888,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -887,6 +887,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.pop();
}
@@ -366,7 +366,7 @@ index d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a..1170fb4c074c495b1a8afb4c5d50a390
this.entityTickList
.forEach(
entity -> {
@@ -1405,12 +1406,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1404,12 +1405,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickNonPassenger");
entity.commonTick();
@@ -383,7 +383,7 @@ index d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a..1170fb4c074c495b1a8afb4c5d50a390
}
// Paper start - log detailed entity tick information
} finally {
@@ -1421,7 +1425,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1420,7 +1424,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - log detailed entity tick information
}
@@ -392,7 +392,7 @@ index d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a..1170fb4c074c495b1a8afb4c5d50a390
if (entity.isRemoved() || entity.getVehicle() != vehicle) {
entity.stopRiding();
} else if (entity instanceof Player || this.entityTickList.contains(entity)) {
@@ -1429,12 +1433,21 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1428,12 +1432,21 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickPassenger");
entity.commonTick();
@@ -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 71ce072c2c9a6349d895054184321cb336ae567b..62d534132f6a2d69df28e244fa296f24a007181f 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 71ce072c2c9a6349d895054184321cb336ae567b..62d534132f6a2d69df28e244fa296f24
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 71ce072c2c9a6349d895054184321cb336ae567b..62d534132f6a2d69df28e244fa296f24
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 71ce072c2c9a6349d895054184321cb336ae567b..62d534132f6a2d69df28e244fa296f24
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() {
}
@@ -28,7 +28,7 @@ and then catch exceptions and close if they fire.
Part of this commit was authored by: Spottedleaf, sandtechnology
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index f7814801138d9547e693ad51ba83cb2cb4ef750e..e6a674eb859af1e98fe25b644c1c5f70728128d0 100644
index 91c1ff8b8234900fbe044bbe0c3c10122c27a91b..e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -64,7 +64,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -267,7 +267,7 @@ index 595db570b0ade9cad7de77d2ab7b10d9e68699df..ac095f59d9f17d8239d6493d1c1e3942
+ // Paper end - Use Velocity cipher
}
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index e6a674eb859af1e98fe25b644c1c5f70728128d0..936200d7620e1fe83eb45ddbcff86b6b6ecaab8f 100644
index e226a4b3ae089c3685ea7e5ae052d7a7e96c8d52..76596e87945f0b95af8032338a64817a8faed4d0 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -796,10 +796,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -320,7 +320,7 @@ index e6a674eb859af1e98fe25b644c1c5f70728128d0..936200d7620e1fe83eb45ddbcff86b6b
this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
} else {
diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
index c2b09a693daf8b4d3d274c07b3be37ba4fc1a483..2f3b58491ef7be8cd2e081f2c44869cd1e50e6a6 100644
index 100e7a2483f63497a1b757c77377262de7248996..42408dede2808447281b49f4006c1d8b03c10bf4 100644
--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -71,6 +71,10 @@ public class ServerConnectionListener {
@@ -335,7 +335,7 @@ index c2b09a693daf8b4d3d274c07b3be37ba4fc1a483..2f3b58491ef7be8cd2e081f2c44869cd
.add(
new ServerBootstrap()
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index dd2c5b7bd16ca17e253501fbc49cc21c8a06d603..a1855a257c3ac801bbd371ca4508af755550d737 100644
index ab21339beb36388bffabb289546bbfe5732df855..eea7dd596eb9ac75dee29d843d786493d44034f3 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -256,9 +256,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
@@ -2321,7 +2321,7 @@ index 0000000000000000000000000000000000000000..505b5e39d0b4cf2a56c1c720f0c9ab01
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 1170fb4c074c495b1a8afb4c5d50a3904b11e0f7..3560df07024e4423835bb724318fcea2adfd4cfe 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
@@ -2332,7 +2332,7 @@ index 1170fb4c074c495b1a8afb4c5d50a3904b11e0f7..3560df07024e4423835bb724318fcea2
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
@@ -3041,6 +3042,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -3040,6 +3041,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return this.debugSynchronizers;
}
@@ -2347,7 +2347,7 @@ index 1170fb4c074c495b1a8afb4c5d50a3904b11e0f7..3560df07024e4423835bb724318fcea2
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
@@ -83,10 +83,10 @@ index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5
ProfilerFiller profiler = Profiler.get();
this.server.spark.executeMainThreadTasks(); // Paper - spark
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 3560df07024e4423835bb724318fcea2adfd4cfe..377c4373785ec5c14a7d7ce9e710c02943546fbe 100644
index e197ac0ee5e49d7768a9de889c972b3d590f7951..85ab77c3d1d5420df648fd173eaf5ad4f4775a80 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1471,6 +1471,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1470,6 +1470,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean mayInteract(final Entity entity, final BlockPos pos) {
return !(entity instanceof Player player && (this.server.isUnderSpawnProtection(this, pos, player) || !this.getWorldBorder().isWithinBounds(pos)));
}
@@ -66,10 +66,10 @@ index 482b428cab996c19664b57c341c55db5a66a6387..bd5efcf1dee77c204bf29ef4693ac541
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 62d534132f6a2d69df28e244fa296f24a007181f..5cc94652112c67ca4e7ae6e8b324e9c9f8908d7d 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
@@ -5,7 +5,7 @@ Subject: [PATCH] Pre-load data off-main during startup
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index b515d8be0e2f0639f7fc843d6ce074b0a583c673..984cf7143e3809507c557e0e70ab271b753db235 100644
index c22cc4387e7cc5da7d38ee14ed7f187b5d9507f4..aa7f2ba4d931f98ce15c52dc2b99f07504149582 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -102,7 +102,10 @@ public class Main {
@@ -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;
+ }
@@ -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);
@@ -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)) {
@@ -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;
@@ -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;
}
@@ -45,11 +45,11 @@
+ // Paper start - per-world time
+ ServerClockManager clockManager = source.getLevel().clockManager();
+ long currentTotalTicks = clockManager.getInstance(clock).totalTicks();
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, clock, totalTicks - currentTotalTicks);
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, totalTicks - currentTotalTicks);
+ final long newTotalTicks;
+ final boolean cancelled = !event.callEvent();
+ if (!cancelled) {
+ newTotalTicks = Math.max(currentTotalTicks + event.getSkipAmount(), 0L);
+ if (event.callEvent()) {
+ newTotalTicks = currentTotalTicks + event.getSkipAmount();
+ clockManager.setTotalTicks(clock, newTotalTicks);
+ } else {
+ newTotalTicks = totalTicks; // just pass it through
+ }
@@ -60,7 +60,7 @@
- clockManager.setTotalTicks(clock, totalTicks);
- source.sendSuccess(() -> Component.translatable("commands.time.set.absolute", clock.getRegisteredName(), totalTicks), true);
- return totalTicks;
+ if (!cancelled) clockManager.setTotalTicks(clock, newTotalTicks);
+ clockManager.setTotalTicks(clock, newTotalTicks);
+ source.sendSuccess(() -> Component.translatable("commands.time.set.absolute", clock.getRegisteredName(), newTotalTicks), true);
+ return wrapTime(newTotalTicks);
+ // Paper end - per-world time
@@ -71,7 +71,7 @@
- clockManager.addTicks(clock, time);
+ // Paper start - per-world time
+ ServerClockManager clockManager = source.getLevel().clockManager();
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, clock, time);
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, time);
+ if (event.callEvent()) {
+ clockManager.addTicks(clock, event.getSkipAmount());
+ }
@@ -99,7 +99,7 @@
+ if (timerMove == null) return ServerClockManager.MoveResult.NO_TIME_MARKER_FOUND;
+
+ // We consider it an attempted move even if the marker occurs at this point in time for event calling purposes.
+ final org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, clock, timerMove.targetTime() - timerMove.currentTime());
+ final org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, timerMove.targetTime() - timerMove.currentTime());
+ if (!event.callEvent() || event.getSkipAmount() == 0) return ServerClockManager.MoveResult.NOT_MOVED;
+
+ clockManager.setTotalTicks(clock, timerMove.currentTime() + event.getSkipAmount());
@@ -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) {
@@ -276,7 +276,7 @@
}
@Override
@@ -373,11 +_,26 @@
@@ -373,11 +_,25 @@
int percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE);
if (this.sleepStatus.areEnoughSleeping(percentage) && this.sleepStatus.areEnoughDeepSleeping(percentage, this.players)) {
Optional<Holder<WorldClock>> defaultClock = this.dimensionType().defaultClock();
@@ -286,9 +286,8 @@
+ // Paper start - per-world time
+ ServerClockManager.TimerMove timerMove = this.clockManager().resolveTimerMove(defaultClock.get(), ClockTimeMarkers.WAKE_UP_FROM_SLEEP);
+ if (timerMove != null) {
+ event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(
+ this,
+ defaultClock.get(),
+ event = new org.bukkit.event.world.TimeSkipEvent(
+ this.getWorld(),
+ org.bukkit.event.world.ClockTimeSkipEvent.SkipReason.NIGHT_SKIP,
+ timerMove.targetTime() - timerMove.currentTime()
+ );
@@ -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;
}
@@ -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) {
@@ -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);
+ }
@@ -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;
@@ -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) {
@@ -16,68 +16,14 @@
Feature feature = featureHolder.value();
removeSapling(level, pos);
if (feature.place(level, generator, random, pos)) {
@@ -229,4 +_,64 @@
@@ -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)) {
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_POPLAR;
+ } else if (feature.is(TreeFeatures.ORANGE_POPLAR)) {
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ORANGE_POPLAR;
+ } else if (feature.is(TreeFeatures.YELLOW_POPLAR)) {
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.YELLOW_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
}
@@ -1,40 +0,0 @@
--- a/net/minecraft/world/timeline/Timeline.java
+++ b/net/minecraft/world/timeline/Timeline.java
@@ -142,6 +_,23 @@
}
}
+ // Paper start - clock API - value equality for registry builder round-trips
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (!(o instanceof final Timeline other)) return false;
+ return this.clock.equals(other.clock)
+ && this.periodTicks.equals(other.periodTicks)
+ && this.tracks.equals(other.tracks)
+ && this.timeMarkers.equals(other.timeMarkers);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(this.clock, this.periodTicks, this.tracks, this.timeMarkers);
+ }
+ // Paper end - clock API - value equality for registry builder round-trips
+
public Set<EnvironmentAttribute<?>> attributes() {
return this.tracks.keySet();
}
@@ -181,6 +_,13 @@
this.tracks.put(attribute, new AttributeTrack<>(modifier, argumentTrack.build()));
return this;
}
+
+ // Paper start - clock API
+ public Timeline.Builder addTracks(final Map<EnvironmentAttribute<?>, AttributeTrack<?, ?>> tracks) {
+ this.tracks.putAll(tracks);
+ return this;
+ }
+ // Paper end - clock API
public <Value> Timeline.Builder addTrack(final EnvironmentAttribute<Value> attribute, final Consumer<KeyframeTrack.Builder<Value>> builder) {
return this.addModifierTrack(attribute, AttributeModifier.override(), builder);
@@ -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() {}
}
@@ -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
@@ -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;
@@ -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;
@@ -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,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,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;
@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.configuration.type.time;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,40 @@
package io.papermc.paper.event.block;
import org.bukkit.block.Block;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.jspecify.annotations.NullMarked;
/**
* Server side subtype of the block dispense event used to track more state through the call chain.
*/
@NullMarked
public class PaperBlockDispenseEvent extends BlockDispenseEvent {
public enum Result {
CANCELLED,
ITEM_CHANGE_REROUTED,
ITEM_CHANGE_NO_REROUTE,
NO_REROUTED,
;
public boolean preventBehaviour() {
return switch (this) {
case CANCELLED, ITEM_CHANGE_REROUTED -> true;
case ITEM_CHANGE_NO_REROUTE, NO_REROUTED -> false;
};
}
}
public Result result = Result.NO_REROUTED;
public PaperBlockDispenseEvent(final Block block, final ItemStack item, final Vector velocity) {
super(block, item, velocity);
}
public PaperBlockDispenseEvent trackResult(final Result result) {
this.result = result;
return this;
}
}
@@ -1,11 +0,0 @@
package io.papermc.paper.inventory;
import net.minecraft.world.inventory.SimpleContainerData;
public class BrewingSimpleContainerData extends SimpleContainerData {
public BrewingSimpleContainerData() {
super(3);
this.set(2, 400);
}
}
@@ -7,13 +7,16 @@ import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.kyori.adventure.key.Key;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataAdapterContext;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
import org.bukkit.persistence.ListPersistentDataType;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
@@ -73,6 +76,18 @@ public abstract class PaperPersistentDataContainerView implements PersistentData
return c != null ? c : defaultValue;
}
@Override
public @Nullable PersistentDataType<?, ?> getPrimitiveStorageType(final Key key) {
Preconditions.checkArgument(key != null, "The key cannot be null");
final Tag value = this.getTag(key.asString());
if (value == null) {
return null;
}
return getTagType(value);
}
@Override
public Set<NamespacedKey> getKeys() {
final Set<String> names = this.toTagCompound().keySet();
@@ -117,4 +132,62 @@ public abstract class PaperPersistentDataContainerView implements PersistentData
return byteArrayOutput.toByteArray();
}
}
private static @Nullable PersistentDataType<?, ?> getTagType(final Tag value) {
return switch (value.getId()) {
case Tag.TAG_BYTE -> PersistentDataType.BYTE;
case Tag.TAG_SHORT -> PersistentDataType.SHORT;
case Tag.TAG_INT -> PersistentDataType.INTEGER;
case Tag.TAG_LONG -> PersistentDataType.LONG;
case Tag.TAG_FLOAT -> PersistentDataType.FLOAT;
case Tag.TAG_DOUBLE -> PersistentDataType.DOUBLE;
case Tag.TAG_BYTE_ARRAY -> PersistentDataType.BYTE_ARRAY;
case Tag.TAG_STRING -> PersistentDataType.STRING;
case Tag.TAG_COMPOUND -> PersistentDataType.TAG_CONTAINER;
case Tag.TAG_INT_ARRAY -> PersistentDataType.INTEGER_ARRAY;
case Tag.TAG_LONG_ARRAY -> PersistentDataType.LONG_ARRAY;
case Tag.TAG_LIST -> {
if (!(value instanceof ListTag listTag)) {
yield null;
}
yield getListTagType(listTag);
}
default -> null;
};
}
private static @Nullable ListPersistentDataType<?, ?> getListTagType(final ListTag value) {
return switch (value.identifyRawElementType()) {
case Tag.TAG_BYTE -> PersistentDataType.LIST.bytes();
case Tag.TAG_SHORT -> PersistentDataType.LIST.shorts();
case Tag.TAG_INT -> PersistentDataType.LIST.integers();
case Tag.TAG_LONG -> PersistentDataType.LIST.longs();
case Tag.TAG_FLOAT -> PersistentDataType.LIST.floats();
case Tag.TAG_DOUBLE -> PersistentDataType.LIST.doubles();
case Tag.TAG_BYTE_ARRAY -> PersistentDataType.LIST.byteArrays();
case Tag.TAG_STRING -> PersistentDataType.LIST.strings();
case Tag.TAG_COMPOUND -> PersistentDataType.LIST.dataContainers();
case Tag.TAG_INT_ARRAY -> PersistentDataType.LIST.integerArrays();
case Tag.TAG_LONG_ARRAY -> PersistentDataType.LIST.longArrays();
case Tag.TAG_LIST -> {
if (value.isEmpty()) {
yield null;
}
final Tag nestedTag = value.getFirst();
if (!(nestedTag instanceof ListTag nestedListTag)) {
yield null;
}
ListPersistentDataType<?, ?> nestedListType = getListTagType(nestedListTag);
if (nestedListType == null) {
yield null;
}
yield PersistentDataType.LIST.listTypeFrom(nestedListType);
}
default -> null;
};
}
}
@@ -25,18 +25,14 @@ import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperPigVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperSoundEventRegistryEntry;
import io.papermc.paper.registry.data.PaperSulfurCubeArchetypeRegistryEntry;
import io.papermc.paper.registry.data.PaperTimelineRegistryEntry;
import io.papermc.paper.registry.data.PaperTrimMaterialRegistryEntry;
import io.papermc.paper.registry.data.PaperTrimPatternRegistryEntry;
import io.papermc.paper.registry.data.PaperWolfVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperWorldClockRegistryEntry;
import io.papermc.paper.registry.data.PaperZombieNautilusVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.PaperDialogRegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntryMeta;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -64,8 +60,6 @@ import org.bukkit.craftbukkit.CraftGameRule;
import org.bukkit.craftbukkit.CraftJukeboxSong;
import org.bukkit.craftbukkit.CraftMusicInstrument;
import org.bukkit.craftbukkit.CraftSound;
import org.bukkit.craftbukkit.CraftTimeline;
import org.bukkit.craftbukkit.CraftWorldClock;
import org.bukkit.craftbukkit.attribute.CraftAttribute;
import org.bukkit.craftbukkit.block.CraftBiome;
import org.bukkit.craftbukkit.block.CraftBlockType;
@@ -165,8 +159,6 @@ public final class PaperRegistries {
start(Registries.ZOMBIE_NAUTILUS_VARIANT, RegistryKey.ZOMBIE_NAUTILUS_VARIANT).craft(ZombieNautilus.Variant.class, CraftZombieNautilus.CraftVariant::new).writable(PaperZombieNautilusVariantRegistryEntry.PaperBuilder::new),
start(Registries.SULFUR_CUBE_ARCHETYPE, RegistryKey.SULFUR_CUBE_ARCHETYPE).craft(SulfurCube.Archetype.class, CraftSulfurCube.CraftArchetype::new).writable(PaperSulfurCubeArchetypeRegistryEntry.PaperBuilder::new),
start(Registries.DIALOG, RegistryKey.DIALOG).craft(Dialog.class, PaperDialog::new, true).writable(PaperDialogRegistryEntry.PaperBuilder::new),
start(Registries.WORLD_CLOCK, RegistryKey.WORLD_CLOCK).craft(WorldClock.class, CraftWorldClock::new).writable(PaperWorldClockRegistryEntry.PaperBuilder::new).delayed(),
start(Registries.TIMELINE, RegistryKey.TIMELINE).craft(Timeline.class, CraftTimeline::new).writable(PaperTimelineRegistryEntry.PaperBuilder::new).delayed(),
// api-only
start(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE).apiOnly(PaperSimpleRegistry::entityType),
@@ -1,103 +0,0 @@
package io.papermc.paper.registry.data;
import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.world.WorldClock;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.OptionalInt;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.timeline.Timeline;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
import static io.papermc.paper.registry.data.util.Checks.asArgument;
import static io.papermc.paper.registry.data.util.Checks.asConfigured;
public class PaperTimelineRegistryEntry implements TimelineRegistryEntry {
protected final Conversions conversions;
protected @Nullable Timeline internal;
protected @Nullable Holder<net.minecraft.world.clock.WorldClock> clock;
protected @Nullable Integer periodTicks;
protected final Map<Key, TimeMarker> timeMarkers = new LinkedHashMap<>();
public PaperTimelineRegistryEntry(final Conversions conversions, final @Nullable Timeline internal) {
this.conversions = conversions;
this.internal = internal;
if (internal == null) {
return;
}
this.clock = internal.clock();
this.periodTicks = internal.periodTicks().orElse(null);
internal.registerTimeMarkers((key, marker) -> this.timeMarkers.put(PaperAdventure.asAdventure(key.identifier()), TimeMarker.of(marker.ticks(), marker.showInCommands())));
}
@Override
public TypedKey<WorldClock> clock() {
return PaperRegistries.fromNms(asConfigured(this.clock, "clock").unwrapKey().orElseThrow());
}
@Override
public OptionalInt periodTicks() {
return this.periodTicks != null ? OptionalInt.of(this.periodTicks) : OptionalInt.empty();
}
@Override
public Map<Key, TimeMarker> timeMarkers() {
return Map.copyOf(this.timeMarkers);
}
public static final class PaperBuilder extends PaperTimelineRegistryEntry implements TimelineRegistryEntry.Builder, PaperRegistryBuilder<Timeline, io.papermc.paper.world.Timeline> {
public PaperBuilder(final Conversions conversions, final @Nullable Timeline internal) {
super(conversions, internal);
}
@Override
public Builder clock(final TypedKey<WorldClock> clock) {
this.clock = this.conversions.getReferenceHolder(PaperRegistries.toNms(asArgument(clock, "clock")));
return this;
}
@Override
public Builder periodTicks(final @Range(from = 1, to = Integer.MAX_VALUE) int periodTicks) {
Preconditions.checkArgument(periodTicks >= 1, "periodTicks must be at least 1, was %s", periodTicks);
this.periodTicks = periodTicks;
return this;
}
@Override
public Builder clearPeriodTicks() {
this.periodTicks = null;
return this;
}
@Override
public Builder addTimeMarker(final Key key, final @Range(from = 0, to = Integer.MAX_VALUE) int ticks, final boolean showInCommands) {
Preconditions.checkArgument(ticks >= 0, "ticks must be at least 0, was %s", ticks);
this.timeMarkers.put(asArgument(key, "key"), TimeMarker.of(ticks, showInCommands));
return this;
}
@Override
public Timeline build() {
final Timeline.Builder builder = Timeline.builder(asConfigured(this.clock, "clock"));
if (this.periodTicks != null) {
builder.setPeriodTicks(this.periodTicks);
}
if (this.internal != null) {
builder.addTracks(this.internal.tracks);
}
this.timeMarkers.forEach((key, marker) -> builder.addTimeMarker(ResourceKey.create(net.minecraft.world.clock.ClockTimeMarkers.ROOT_ID, PaperAdventure.asVanilla(key)), marker.ticks(), marker.showInCommands()));
return builder.build();
}
}
}
@@ -1,24 +0,0 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.data.util.Conversions;
import net.minecraft.world.clock.WorldClock;
import org.jspecify.annotations.Nullable;
public class PaperWorldClockRegistryEntry implements WorldClockRegistryEntry {
public PaperWorldClockRegistryEntry(final Conversions ignoredConversions, final @Nullable WorldClock ignoredInternal) {
}
public static final class PaperBuilder extends PaperWorldClockRegistryEntry implements WorldClockRegistryEntry.Builder, PaperRegistryBuilder<WorldClock, io.papermc.paper.world.WorldClock> {
public PaperBuilder(final Conversions conversions, final @Nullable WorldClock internal) {
super(conversions, internal);
}
@Override
public WorldClock build() {
return new WorldClock();
}
}
}
@@ -10,7 +10,6 @@ import java.util.function.Predicate;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.features.TreeFeatures;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
@@ -162,98 +161,12 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
}
public boolean generateTree(WorldGenLevel access, ChunkGenerator chunkGenerator, BlockPos pos, RandomSource random, TreeType treeType) {
ResourceKey<Feature> gen;
switch (treeType) {
case BIG_TREE:
gen = TreeFeatures.FANCY_OAK;
break;
case BIRCH:
gen = TreeFeatures.BIRCH;
break;
case REDWOOD:
gen = TreeFeatures.SPRUCE;
break;
case TALL_REDWOOD:
gen = TreeFeatures.PINE;
break;
case JUNGLE:
gen = TreeFeatures.MEGA_JUNGLE_TREE;
break;
case SMALL_JUNGLE:
gen = TreeFeatures.JUNGLE_TREE_NO_VINE;
break;
case COCOA_TREE:
gen = TreeFeatures.JUNGLE_TREE;
break;
case JUNGLE_BUSH:
gen = TreeFeatures.JUNGLE_BUSH;
break;
case RED_MUSHROOM:
gen = TreeFeatures.HUGE_RED_MUSHROOM;
break;
case BROWN_MUSHROOM:
gen = TreeFeatures.HUGE_BROWN_MUSHROOM;
break;
case SWAMP:
gen = TreeFeatures.SWAMP_OAK;
break;
case ACACIA:
gen = TreeFeatures.ACACIA;
break;
case DARK_OAK:
gen = TreeFeatures.DARK_OAK;
break;
case MEGA_REDWOOD:
gen = TreeFeatures.MEGA_SPRUCE;
break;
case MEGA_PINE:
gen = TreeFeatures.MEGA_PINE;
break;
case TALL_BIRCH:
gen = TreeFeatures.SUPER_BIRCH_BEES_0002;
break;
case CHORUS_PLANT:
ChorusFlowerBlock.generatePlant(access, pos, random, 8);
return true;
case CRIMSON_FUNGUS:
gen = this.isNormalWorld() ? TreeFeatures.CRIMSON_FUNGUS_PLANTED : TreeFeatures.CRIMSON_FUNGUS; // Paper - Fix async entity add due to fungus trees; if world gen, don't use planted version
break;
case WARPED_FUNGUS:
gen = this.isNormalWorld() ? TreeFeatures.WARPED_FUNGUS_PLANTED : TreeFeatures.WARPED_FUNGUS; // Paper - Fix async entity add due to fungus trees; if world gen, don't use planted version
break;
case AZALEA:
gen = TreeFeatures.AZALEA_TREE;
break;
case MANGROVE:
gen = TreeFeatures.MANGROVE;
break;
case TALL_MANGROVE:
gen = TreeFeatures.TALL_MANGROVE;
break;
case CHERRY:
gen = TreeFeatures.CHERRY;
break;
case PALE_OAK:
gen = TreeFeatures.PALE_OAK;
break;
case PALE_OAK_CREAKING:
gen = TreeFeatures.PALE_OAK_CREAKING;
break;
case RED_POPLAR:
gen = TreeFeatures.RED_POPLAR;
break;
case ORANGE_POPLAR:
gen = TreeFeatures.ORANGE_POPLAR;
break;
case YELLOW_POPLAR:
gen = TreeFeatures.YELLOW_POPLAR;
break;
case TREE:
default:
gen = TreeFeatures.OAK;
break;
if (treeType == TreeType.CHORUS_PLANT) {
ChorusFlowerBlock.generatePlant(access, pos, random, 8);
return true;
}
ResourceKey<Feature> gen = CraftTreeType.toFeature(treeType, this.isNormalWorld());
Holder<Feature> holder = access.registryAccess().lookupOrThrow(Registries.FEATURE).get(gen).orElse(null);
return holder != null && holder.value().place(access, chunkGenerator, random, pos);
}
@@ -1,21 +0,0 @@
package org.bukkit.craftbukkit;
import io.papermc.paper.registry.HolderableBase;
import io.papermc.paper.world.Timeline;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
public class CraftTimeline extends HolderableBase<net.minecraft.world.timeline.Timeline> implements Timeline {
public static Timeline minecraftHolderToBukkit(final Holder<net.minecraft.world.timeline.Timeline> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.TIMELINE);
}
public static Holder<net.minecraft.world.timeline.Timeline> bukkitToMinecraftHolder(final Timeline bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftTimeline(final Holder<net.minecraft.world.timeline.Timeline> holder) {
super(holder);
}
}
@@ -0,0 +1,98 @@
package org.bukkit.craftbukkit;
import java.util.Map;
import net.minecraft.data.worldgen.features.TreeFeatures;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.levelgen.feature.Feature;
import org.bukkit.TreeType;
import org.jspecify.annotations.Nullable;
public final class CraftTreeType {
private static final Map<ResourceKey<Feature>, TreeType> BY_FEATURE = Map.ofEntries(
Map.entry(TreeFeatures.OAK, TreeType.TREE),
Map.entry(TreeFeatures.OAK_BEES_005, TreeType.TREE),
Map.entry(TreeFeatures.FANCY_OAK, TreeType.BIG_TREE),
Map.entry(TreeFeatures.FANCY_OAK_BEES_005, TreeType.BIG_TREE),
Map.entry(TreeFeatures.SPRUCE, TreeType.REDWOOD),
Map.entry(TreeFeatures.PINE, TreeType.TALL_REDWOOD),
Map.entry(TreeFeatures.MEGA_SPRUCE, TreeType.MEGA_REDWOOD),
Map.entry(TreeFeatures.MEGA_PINE, TreeType.MEGA_PINE),
Map.entry(TreeFeatures.BIRCH, TreeType.BIRCH),
Map.entry(TreeFeatures.BIRCH_BEES_005, TreeType.BIRCH),
Map.entry(TreeFeatures.SUPER_BIRCH_BEES_0002, TreeType.TALL_BIRCH),
Map.entry(TreeFeatures.MEGA_JUNGLE_TREE, TreeType.JUNGLE),
Map.entry(TreeFeatures.JUNGLE_TREE_NO_VINE, TreeType.SMALL_JUNGLE),
Map.entry(TreeFeatures.JUNGLE_TREE, TreeType.COCOA_TREE),
Map.entry(TreeFeatures.JUNGLE_BUSH, TreeType.JUNGLE_BUSH),
Map.entry(TreeFeatures.HUGE_RED_MUSHROOM, TreeType.RED_MUSHROOM),
Map.entry(TreeFeatures.HUGE_BROWN_MUSHROOM, TreeType.BROWN_MUSHROOM),
Map.entry(TreeFeatures.SWAMP_OAK, TreeType.SWAMP),
Map.entry(TreeFeatures.ACACIA, TreeType.ACACIA),
Map.entry(TreeFeatures.DARK_OAK, TreeType.DARK_OAK),
Map.entry(TreeFeatures.CRIMSON_FUNGUS, TreeType.CRIMSON_FUNGUS),
Map.entry(TreeFeatures.CRIMSON_FUNGUS_PLANTED, TreeType.CRIMSON_FUNGUS),
Map.entry(TreeFeatures.WARPED_FUNGUS, TreeType.WARPED_FUNGUS),
Map.entry(TreeFeatures.WARPED_FUNGUS_PLANTED, TreeType.WARPED_FUNGUS),
Map.entry(TreeFeatures.AZALEA_TREE, TreeType.AZALEA),
Map.entry(TreeFeatures.MANGROVE, TreeType.MANGROVE),
Map.entry(TreeFeatures.TALL_MANGROVE, TreeType.TALL_MANGROVE),
Map.entry(TreeFeatures.CHERRY, TreeType.CHERRY),
Map.entry(TreeFeatures.CHERRY_BEES_005, TreeType.CHERRY),
Map.entry(TreeFeatures.PALE_OAK, TreeType.PALE_OAK),
Map.entry(TreeFeatures.PALE_OAK_BONEMEAL, TreeType.PALE_OAK),
Map.entry(TreeFeatures.PALE_OAK_CREAKING, TreeType.PALE_OAK_CREAKING),
Map.entry(TreeFeatures.RED_POPLAR, TreeType.RED_POPLAR),
Map.entry(TreeFeatures.ORANGE_POPLAR, TreeType.ORANGE_POPLAR),
Map.entry(TreeFeatures.YELLOW_POPLAR, TreeType.YELLOW_POPLAR)
);
private CraftTreeType() {
}
public static TreeType fromFeature(ResourceKey<Feature> feature) {
TreeType treeType = BY_FEATURE.get(feature);
if (treeType == null) {
throw new IllegalArgumentException("Unknown tree generator " + feature);
}
return treeType;
}
/**
* @return the feature for the tree type, or {@code null} for {@link TreeType#CHORUS_PLANT}
* which isn't placed through a feature
*/
public static @Nullable ResourceKey<Feature> toFeature(TreeType treeType, boolean normalWorld) {
return switch (treeType) {
case TREE -> TreeFeatures.OAK;
case BIG_TREE -> TreeFeatures.FANCY_OAK;
case REDWOOD -> TreeFeatures.SPRUCE;
case TALL_REDWOOD -> TreeFeatures.PINE;
case JUNGLE -> TreeFeatures.MEGA_JUNGLE_TREE;
case SMALL_JUNGLE -> TreeFeatures.JUNGLE_TREE_NO_VINE;
case COCOA_TREE -> TreeFeatures.JUNGLE_TREE;
case JUNGLE_BUSH -> TreeFeatures.JUNGLE_BUSH;
case RED_MUSHROOM -> TreeFeatures.HUGE_RED_MUSHROOM;
case BROWN_MUSHROOM -> TreeFeatures.HUGE_BROWN_MUSHROOM;
case SWAMP -> TreeFeatures.SWAMP_OAK;
case ACACIA -> TreeFeatures.ACACIA;
case DARK_OAK -> TreeFeatures.DARK_OAK;
case MEGA_REDWOOD -> TreeFeatures.MEGA_SPRUCE;
case MEGA_PINE -> TreeFeatures.MEGA_PINE;
case BIRCH -> TreeFeatures.BIRCH;
case TALL_BIRCH -> TreeFeatures.SUPER_BIRCH_BEES_0002;
case CHORUS_PLANT -> null;
case CRIMSON_FUNGUS -> normalWorld ? TreeFeatures.CRIMSON_FUNGUS_PLANTED : TreeFeatures.CRIMSON_FUNGUS; // fix async entity add due to fungus trees; if world gen, don't use planted version
case WARPED_FUNGUS -> normalWorld ? TreeFeatures.WARPED_FUNGUS_PLANTED : TreeFeatures.WARPED_FUNGUS; // fix async entity add due to fungus trees; if world gen, don't use planted version
case AZALEA -> TreeFeatures.AZALEA_TREE;
case MANGROVE -> TreeFeatures.MANGROVE;
case TALL_MANGROVE -> TreeFeatures.TALL_MANGROVE;
case CHERRY -> TreeFeatures.CHERRY;
case PALE_OAK -> TreeFeatures.PALE_OAK;
case PALE_OAK_CREAKING -> TreeFeatures.PALE_OAK_CREAKING;
case RED_POPLAR -> TreeFeatures.RED_POPLAR;
case ORANGE_POPLAR -> TreeFeatures.ORANGE_POPLAR;
case YELLOW_POPLAR -> TreeFeatures.YELLOW_POPLAR;
};
}
}
@@ -143,6 +143,7 @@ import org.bukkit.event.weather.ThunderChangeEvent;
import org.bukkit.event.weather.WeatherChangeEvent;
import org.bukkit.event.world.ClockTimeSkipEvent;
import org.bukkit.event.world.SpawnChangeEvent;
import org.bukkit.event.world.TimeSkipEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
@@ -822,63 +823,19 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void setFullTime(long time) {
final Holder<net.minecraft.world.clock.WorldClock> clock = this.world.dimensionType().defaultClock()
.orElseThrow(() -> new IllegalArgumentException("Cannot set time in world without world clock"));
this.setFullTime(clock, time);
}
if (this.world.dimensionType().defaultClock().isEmpty()) {
throw new IllegalArgumentException("Cannot set time in world without world clock");
}
@Override
public io.papermc.paper.world.@Nullable WorldClock getDefaultClock() {
return this.world.dimensionType().defaultClock().map(CraftWorldClock::minecraftHolderToBukkit).orElse(null);
}
@Override
public long getFullTime(final io.papermc.paper.world.WorldClock clock) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
return this.world.clockManager().getInstance(CraftWorldClock.bukkitToMinecraftHolder(clock)).totalTicks();
}
@Override
public void setFullTime(final io.papermc.paper.world.WorldClock clock, final long time) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
this.setFullTime(CraftWorldClock.bukkitToMinecraftHolder(clock), time);
}
private void setFullTime(final Holder<net.minecraft.world.clock.WorldClock> clock, final long time) {
Preconditions.checkArgument(time >= 0, "time cannot be negative, was %s", time);
final long currentClockTime = this.world.clockManager().getInstance(clock).totalTicks();
final ClockTimeSkipEvent event = CraftEventFactory.createTimeSkipEvent(this.world, clock, ClockTimeSkipEvent.SkipReason.CUSTOM, time - currentClockTime);
if (!event.callEvent()) {
final long currentClockTime = this.world.getDefaultClockTime();
final ClockTimeSkipEvent event = new TimeSkipEvent(this, ClockTimeSkipEvent.SkipReason.CUSTOM, time - currentClockTime);
this.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
// Updates the clock for all players
this.world.clockManager().setTotalTicks(clock, Math.max(currentClockTime + event.getSkipAmount(), 0L));
}
@Override
public boolean isClockPaused(final io.papermc.paper.world.WorldClock clock) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
return this.world.clockManager().getInstance(CraftWorldClock.bukkitToMinecraftHolder(clock)).isPaused();
}
@Override
public void setClockPaused(final io.papermc.paper.world.WorldClock clock, final boolean paused) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
this.world.clockManager().setPaused(CraftWorldClock.bukkitToMinecraftHolder(clock), paused);
}
@Override
public float getClockRate(final io.papermc.paper.world.WorldClock clock) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
return this.world.clockManager().getInstance(CraftWorldClock.bukkitToMinecraftHolder(clock)).rate();
}
@Override
public void setClockRate(final io.papermc.paper.world.WorldClock clock, final float rate) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
Preconditions.checkArgument(Float.isFinite(rate) && rate > 0.0F, "rate must be positive and finite, was %s", rate);
this.world.clockManager().setRate(CraftWorldClock.bukkitToMinecraftHolder(clock), rate);
this.world.clockManager().setTotalTicks(this.world.dimensionType().defaultClock().get(), currentClockTime + event.getSkipAmount());
}
// Paper start
@@ -1,21 +0,0 @@
package org.bukkit.craftbukkit;
import io.papermc.paper.registry.HolderableBase;
import io.papermc.paper.world.WorldClock;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
public class CraftWorldClock extends HolderableBase<net.minecraft.world.clock.WorldClock> implements WorldClock {
public static WorldClock minecraftHolderToBukkit(final Holder<net.minecraft.world.clock.WorldClock> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.WORLD_CLOCK);
}
public static Holder<net.minecraft.world.clock.WorldClock> bukkitToMinecraftHolder(final WorldClock bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftWorldClock(final Holder<net.minecraft.world.clock.WorldClock> holder) {
super(holder);
}
}
@@ -1,12 +1,14 @@
package org.bukkit.craftbukkit.block;
import com.google.common.base.Preconditions;
import io.papermc.paper.event.player.PlayerOpenSignEvent;
import java.util.List;
import java.util.UUID;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.entity.SignText;
import net.minecraft.world.level.block.entity.SignTextSlot;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Sign;
@@ -19,107 +21,53 @@ import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerSignOpenEvent;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<T> implements Sign {
private @MonotonicNonNull CraftSignSide front;
private @MonotonicNonNull CraftSignSide back;
public CraftSign(World world, T blockEntity, boolean useSnapshot) {
public CraftSign(final World world, final T blockEntity, final boolean useSnapshot) {
super(world, blockEntity, useSnapshot);
}
protected CraftSign(CraftSign<T> state, Location location) {
protected CraftSign(final CraftSign<T> state, final @Nullable Location location) {
super(state, location);
}
@Override
protected void load(T blockEntity) {
protected void load(final T blockEntity) {
super.load(blockEntity);
this.front = new CraftSignSide(blockEntity.getText(SignTextSlot.FRONT));
this.back = new CraftSignSide(blockEntity.getText(SignTextSlot.BACK));
}
// Paper start
@Override
public java.util.@NotNull List<net.kyori.adventure.text.Component> lines() {
return this.front.lines();
}
@Override
public net.kyori.adventure.text.@NotNull Component line(int index) {
return this.front.line(index);
}
@Override
public void line(int index, net.kyori.adventure.text.@NotNull Component line) {
this.front.line(index, line);
}
// Paper end
@Override
public String[] getLines() {
return this.front.getLines();
}
@Override
public String getLine(int index) throws IndexOutOfBoundsException {
return this.front.getLine(index);
}
@Override
public void setLine(int index, String line) throws IndexOutOfBoundsException {
this.front.setLine(index, line);
}
@Override
public boolean isEditable() {
return !this.isWaxed();
}
@Override
public void setEditable(boolean editable) {
this.setWaxed(!editable);
}
@Override
public boolean isWaxed() {
return this.getSnapshot().isWaxed();
}
@Override
public void setWaxed(boolean waxed) {
public void setWaxed(final boolean waxed) {
this.getSnapshot().setWaxed(waxed);
}
@Override
public boolean isGlowingText() {
return this.front.isGlowingText();
}
@Override
public void setGlowingText(boolean glowing) {
this.front.setGlowingText(glowing);
}
@NotNull
@Override
public SignSide getSide(Side side) {
public SignSide getSide(final Side side) {
Preconditions.checkArgument(side != null, "side == null");
switch (side) {
case FRONT:
return this.front;
case BACK:
return this.back;
default:
throw new IllegalArgumentException();
}
return switch (side) {
case FRONT -> this.front;
case BACK -> this.back;
default -> throw new IllegalArgumentException("Unknown side: " + side);
};
}
@Override
public SignSide getTargetSide(Player player) {
public SignSide getTargetSide(final Player player) {
this.ensureNoWorldGeneration();
Preconditions.checkArgument(player != null, "player cannot be null");
@@ -131,26 +79,16 @@ public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<
}
@Override
public Player getAllowedEditor() {
public @Nullable Player getAllowedEditor() {
this.ensureNoWorldGeneration();
// getPlayerWhoMayEdit is always null for the snapshot, so we use the wrapped BlockEntity
UUID id = this.getBlockEntity().getPlayerWhoMayEdit();
final UUID id = this.getBlockEntity().getPlayerWhoMayEdit();
return (id == null) ? null : Bukkit.getPlayer(id);
}
@Override
public DyeColor getColor() {
return this.front.getColor();
}
@Override
public void setColor(DyeColor color) {
this.front.setColor(color);
}
@Override
public void applyTo(T blockEntity) {
public void applyTo(final T blockEntity) {
this.getSnapshot().setText(this.front.applyLegacyStringToSignSide(), SignTextSlot.FRONT);
this.getSnapshot().setText(this.back.applyLegacyStringToSignSide(), SignTextSlot.BACK);
@@ -159,38 +97,37 @@ public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<
@Override
public CraftSign<T> copy() {
return new CraftSign<T>(this, null);
return new CraftSign<>(this, null);
}
@Override
public CraftSign<T> copy(Location location) {
return new CraftSign<T>(this, location);
public CraftSign<T> copy(final Location location) {
return new CraftSign<>(this, location);
}
public static void openSign(Sign sign, Player player, Side side) {
public static void openSign(final Sign sign, final Player player, final Side side) {
Preconditions.checkArgument(sign != null, "sign == null");
Preconditions.checkArgument(side != null, "side == null");
Preconditions.checkArgument(sign.isPlaced(), "Sign must be placed");
Preconditions.checkArgument(sign.getWorld() == player.getWorld(), "Sign must be in same world as Player");
io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent(player, sign, side, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLUGIN);
final PlayerOpenSignEvent event = new PlayerOpenSignEvent(player, sign, side, PlayerOpenSignEvent.Cause.PLUGIN);;
if (!event.callEvent()) return;
if (PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) {
if (PlayerSignOpenEvent.getHandlerList().hasListeners()) {
if (!CraftEventFactory.callPlayerSignOpenEvent(player, sign, side, PlayerSignOpenEvent.Cause.PLUGIN)) {
return;
}
}
SignBlockEntity blockEntity = ((CraftSign<?>) sign).getBlockEntity();
final SignBlockEntity blockEntity = ((CraftSign<?>) sign).getBlockEntity();
blockEntity.setAllowedPlayerEditor(player.getUniqueId());
((CraftPlayer) player).getHandle().openTextEdit(blockEntity, CraftSignSide.toVanilla(side));
}
// Paper start
public static Component[] sanitizeLines(java.util.List<? extends net.kyori.adventure.text.Component> lines) {
Component[] components = new Component[4];
for (int i = 0; i < 4; i++) {
public static Component[] sanitizeLines(final List<? extends net.kyori.adventure.text.Component> lines) {
Component[] components = new Component[SignText.LINES];
for (int i = 0; i < SignText.LINES; i++) {
if (i < lines.size() && lines.get(i) != null) {
components[i] = io.papermc.paper.adventure.PaperAdventure.asVanilla(lines.get(i));
} else {
@@ -199,17 +136,15 @@ public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<
}
return components;
}
// Paper end
// Paper start - More Sign Block API
@Override
public java.util.UUID getAllowedEditorUniqueId() {
public @Nullable UUID getAllowedEditorUniqueId() {
this.ensureNoWorldGeneration();
return this.getBlockEntity().getPlayerWhoMayEdit();
}
@Override
public void setAllowedEditorUniqueId(java.util.UUID uuid) {
public void setAllowedEditorUniqueId(final @Nullable UUID uuid) {
this.ensureNoWorldGeneration();
this.getBlockEntity().setAllowedPlayerEditor(uuid);
}
@@ -219,12 +154,21 @@ public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<
this.requirePlaced();
return CraftSignSide.fromVanilla(this.getSnapshot().getSlotPlayerIsFacing(x, z));
}
// Paper end - More Sign Block API
public static Component[] sanitizeLines(String[] lines) {
Component[] components = new Component[4];
@Override
public void setOperatorFeaturesAllowed(final boolean allowed) {
this.getSnapshot().allowOpFeatures = allowed;
}
for (int i = 0; i < 4; i++) {
@Override
public boolean isOperatorFeaturesAllowed() {
return this.getSnapshot().allowOpFeatures;
}
public static Component[] sanitizeLines(final String[] lines) {
final Component[] components = new Component[SignText.LINES];
for (int i = 0; i < SignText.LINES; i++) {
if (i < lines.length && lines[i] != null) {
components[i] = CraftChatMessage.fromString(lines[i])[0];
} else {
@@ -235,15 +179,15 @@ public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<
return components;
}
public static String[] revertComponents(Component[] components) {
String[] lines = new String[components.length];
public static String[] revertComponents(final Component[] components) {
final String[] lines = new String[components.length];
for (int i = 0; i < lines.length; i++) {
lines[i] = CraftSign.revertComponent(components[i]);
}
return lines;
}
private static String revertComponent(Component component) {
private static String revertComponent(final Component component) {
return CraftChatMessage.fromComponent(component);
}
}
@@ -10,8 +10,10 @@ import io.papermc.paper.block.bed.BedEnterProblem;
import io.papermc.paper.connection.HorriblePlayerLoginEventHack;
import io.papermc.paper.connection.PlayerConnection;
import io.papermc.paper.event.block.BlockLockCheckEvent;
import io.papermc.paper.event.block.PaperBlockDispenseEvent;
import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent;
import io.papermc.paper.event.entity.EntityIgniteEvent;
import io.papermc.paper.event.entity.EntityLandEvent;
import io.papermc.paper.event.entity.ItemTransportingEntitySortEvent;
import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent;
import io.papermc.paper.event.inventory.PlayerBundleItemSelectEvent;
@@ -30,7 +32,8 @@ import net.minecraft.Optionull;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.dispenser.BlockSource;
import net.minecraft.core.dispenser.DispenseItemBehavior;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
@@ -1124,8 +1127,6 @@ public class CraftEventFactory {
DamageCause cause;
if (source.knownCause() != null) {
cause = source.knownCause();
} else if (source.is(DamageTypes.IN_FIRE)) { // todo could be called above
cause = DamageCause.FIRE;
} else if (source.is(DamageTypes.STARVE)) {
cause = DamageCause.STARVATION;
} else if (source.is(DamageTypes.WITHER)) {
@@ -2305,25 +2306,33 @@ public class CraftEventFactory {
return event.callEvent();
}
public static @Nullable ItemStack handleBlockDispenseEvent(net.minecraft.core.dispenser.BlockSource pointer, BlockPos to, ItemStack dispensed, net.minecraft.core.dispenser.DispenseItemBehavior instance) {
org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos());
org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitMirror(dispensed.isDamageableItem() ? dispensed : dispensed.copyWithCount(1));
public static @Nullable ItemStack handleBlockDispenseEvent(final BlockSource pointer, final BlockPos to, final ItemStack dispensed, final DispenseItemBehavior instance) {
return switch (handleBlockDispenseEventInspectable(pointer, to, dispensed, instance).result) {
case CANCELLED, ITEM_CHANGE_REROUTED -> dispensed;
case NO_REROUTED, ITEM_CHANGE_NO_REROUTE -> null;
};
}
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to));
public static PaperBlockDispenseEvent handleBlockDispenseEventInspectable(final BlockSource pointer, final BlockPos to, final ItemStack dispensed, final DispenseItemBehavior instance) {
final org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos());
final org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitMirror(dispensed.isDamageableItem() ? dispensed : dispensed.copyWithCount(1));
final PaperBlockDispenseEvent event = new PaperBlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to));
if (!event.callEvent()) {
return dispensed;
return event.trackResult(PaperBlockDispenseEvent.Result.CANCELLED);
}
if (!event.getItem().equals(craftItem)) {
// Chain to handler for new item
ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.getDispenseBehavior(pointer, eventStack);
if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) {
final ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
final DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.getDispenseBehavior(pointer, eventStack);
if (itemBehavior != DispenseItemBehavior.NOOP && itemBehavior != instance) {
itemBehavior.dispense(pointer, eventStack);
return dispensed;
return event.trackResult(PaperBlockDispenseEvent.Result.ITEM_CHANGE_REROUTED);
}
return event.trackResult(PaperBlockDispenseEvent.Result.ITEM_CHANGE_NO_REROUTE);
}
return null;
return event.trackResult(PaperBlockDispenseEvent.Result.NO_REROUTED);
}
/**
@@ -2470,16 +2479,11 @@ public class CraftEventFactory {
return new ItemTransportingEntitySortEvent(mob.getBukkitEntity(), CraftItemStack.asBukkitCopy(mob.getMainHandItem()), new CraftInventory(container));
}
public static ClockTimeSkipEvent createTimeSkipEvent(final CommandSourceStack source, final Holder<net.minecraft.world.clock.WorldClock> clock, final long skipAmount) {
return createTimeSkipEvent(source.getLevel(), clock, ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
}
public static ClockTimeSkipEvent createTimeSkipEvent(final ServerLevel level, final Holder<net.minecraft.world.clock.WorldClock> clock, final ClockTimeSkipEvent.SkipReason reason, final long skipAmount) {
final io.papermc.paper.world.WorldClock bukkitClock = org.bukkit.craftbukkit.CraftWorldClock.minecraftHolderToBukkit(clock);
public static ClockTimeSkipEvent createTimeSkipEvent(final CommandSourceStack source, final long skipAmount) {
if (io.papermc.paper.configuration.GlobalConfiguration.get().time.affectsAllWorlds) {
return new ClockTimeSkipEvent(bukkitClock, reason, skipAmount);
return new ClockTimeSkipEvent(ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
}
return new TimeSkipEvent(level.getWorld(), bukkitClock, reason, skipAmount);
return new TimeSkipEvent(source.getLevel().getWorld(), ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
}
public static int callEntityIgniteEvent(Entity entity, int fuseTime) {
@@ -2493,4 +2497,12 @@ public class CraftEventFactory {
}
return event.getFuseTime();
}
public static double callEntityLandEvent(final Level level, final BlockPos pos, final Entity entity, final double fallDistance) {
if (!EntityLandEvent.getHandlerList().hasListeners()) {
return fallDistance; // No listeners, skip event creation
}
final EntityLandEvent event = new EntityLandEvent(entity.getBukkitEntity(), CraftBlock.at(level, pos), fallDistance);
return event.callEvent() ? event.getFallDistance() : -1;
}
}
@@ -3,6 +3,7 @@ package org.bukkit.craftbukkit.inventory;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.AbstractFurnaceMenu;
import net.minecraft.world.inventory.AnvilMenu;
import net.minecraft.world.inventory.BeaconMenu;
import net.minecraft.world.inventory.BlastFurnaceMenu;
@@ -156,7 +157,7 @@ public class CraftContainer extends AbstractContainerMenu {
this.delegate = new DispenserMenu(windowId, bottom, top);
break;
case FURNACE:
this.delegate = new FurnaceMenu(windowId, bottom, top, new SimpleContainerData(4));
this.delegate = new FurnaceMenu(windowId, bottom, top, new SimpleContainerData(AbstractFurnaceMenu.DATA_COUNT));
break;
case CRAFTING: // TODO: This should be an error?
case WORKBENCH:
@@ -166,7 +167,7 @@ public class CraftContainer extends AbstractContainerMenu {
this.delegate = new EnchantmentMenu(windowId, bottom);
break;
case BREWING:
this.delegate = new BrewingStandMenu(windowId, bottom, top, new io.papermc.paper.inventory.BrewingSimpleContainerData()); // Paper - Add recipeBrewTime
this.delegate = new BrewingStandMenu(windowId, bottom, top, new SimpleContainerData(BrewingStandMenu.DATA_COUNT));
break;
case HOPPER:
this.delegate = new HopperMenu(windowId, bottom, top);
@@ -181,13 +182,13 @@ public class CraftContainer extends AbstractContainerMenu {
this.delegate = new ShulkerBoxMenu(windowId, bottom, top);
break;
case BLAST_FURNACE:
this.delegate = new BlastFurnaceMenu(windowId, bottom, top, new SimpleContainerData(4));
this.delegate = new BlastFurnaceMenu(windowId, bottom, top, new SimpleContainerData(AbstractFurnaceMenu.DATA_COUNT));
break;
case LECTERN:
this.delegate = new LecternMenu(windowId, top, new SimpleContainerData(1), bottom);
this.delegate = new LecternMenu(windowId, top, new SimpleContainerData(LecternMenu.DATA_COUNT), bottom);
break;
case SMOKER:
this.delegate = new SmokerMenu(windowId, bottom, top, new SimpleContainerData(4));
this.delegate = new SmokerMenu(windowId, bottom, top, new SimpleContainerData(AbstractFurnaceMenu.DATA_COUNT));
break;
case LOOM:
this.delegate = new LoomMenu(windowId, bottom);
@@ -0,0 +1,73 @@
package org.bukkit.craftbukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import net.minecraft.data.worldgen.features.TreeFeatures;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.random.Weighted;
import net.minecraft.util.random.WeightedList;
import net.minecraft.world.level.block.grower.TreeGrower;
import net.minecraft.world.level.levelgen.feature.Feature;
import org.bukkit.TreeType;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Normal
public class CraftTreeTypeTest {
// grower features that intentionally share a tree type with another feature
private static final Map<ResourceKey<Feature>, ResourceKey<Feature>> ALIASES = Map.of(
TreeFeatures.OAK_BEES_005, TreeFeatures.OAK,
TreeFeatures.FANCY_OAK_BEES_005, TreeFeatures.FANCY_OAK,
TreeFeatures.BIRCH_BEES_005, TreeFeatures.BIRCH,
TreeFeatures.CHERRY_BEES_005, TreeFeatures.CHERRY,
TreeFeatures.PALE_OAK_BONEMEAL, TreeFeatures.PALE_OAK
);
@SuppressWarnings("unchecked")
public static Stream<ResourceKey<Feature>> growerFeatures() throws ReflectiveOperationException {
List<ResourceKey<Feature>> features = new ArrayList<>();
for (Field growerField : TreeGrower.class.getDeclaredFields()) {
if (!Modifier.isStatic(growerField.getModifiers()) || growerField.getType() != TreeGrower.class) {
continue;
}
TreeGrower grower = (TreeGrower) growerField.get(null);
for (Field listField : TreeGrower.class.getDeclaredFields()) {
if (Modifier.isStatic(listField.getModifiers()) || listField.getType() != WeightedList.class) {
continue;
}
listField.setAccessible(true);
for (Weighted<ResourceKey<Feature>> weighted : ((WeightedList<ResourceKey<Feature>>) listField.get(grower)).unwrap()) {
features.add(weighted.value());
}
}
}
return features.stream().distinct();
}
@ParameterizedTest
@MethodSource("growerFeatures")
public void growerFeatureRoundTrips(ResourceKey<Feature> feature) {
TreeType treeType = assertDoesNotThrow(() -> CraftTreeType.fromFeature(feature), "No TreeType for grower feature " + feature);
assertEquals(ALIASES.getOrDefault(feature, feature), CraftTreeType.toFeature(treeType, true), "TreeType " + treeType + " does not generate grower feature " + feature);
}
@ParameterizedTest
@EnumSource(value = TreeType.class, mode = EnumSource.Mode.EXCLUDE, names = "CHORUS_PLANT")
public void treeTypeRoundTrips(TreeType treeType) {
for (boolean normalWorld : new boolean[] {true, false}) {
assertEquals(treeType, CraftTreeType.fromFeature(CraftTreeType.toFeature(treeType, normalWorld)), "TreeType " + treeType + " does not map back from its feature");
}
}
}
@@ -170,8 +170,6 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
register(Registries.GAME_RULE, GameRule.class, GameRules.class, CraftGameRule.class, net.minecraft.world.level.gamerules.GameRule.class);
register(Registries.POINT_OF_INTEREST_TYPE, PoiType.class, PoiTypes.class, PaperPoiType.class, net.minecraft.world.entity.ai.village.poi.PoiType.class);
register(Registries.SULFUR_CUBE_ARCHETYPE, SulfurCube.Archetype.class, CraftSulfurCube.CraftArchetype.class, SulfurCubeArchetype.class);
register(Registries.WORLD_CLOCK, io.papermc.paper.world.WorldClock.class, org.bukkit.craftbukkit.CraftWorldClock.class, net.minecraft.world.clock.WorldClock.class);
register(Registries.TIMELINE, io.papermc.paper.world.Timeline.class, org.bukkit.craftbukkit.CraftTimeline.class, net.minecraft.world.timeline.Timeline.class);
}
private static <M, B extends Keyed> void register(ResourceKey<? extends Registry<? extends M>> registryKey, Class<B> api, Class<? extends B> impl, Class<M> internal) {