Compare commits

..
Author SHA1 Message Date
Nassim Jahnke d385dff295 Clock API and level clock overrides 2026-04-19 20:35:33 +02:00
119 changed files with 1511 additions and 4837 deletions

No files matched your search

@@ -1,181 +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.entity.poi.PoiType;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla keys for {@link RegistryKey#POINT_OF_INTEREST_TYPE}.
*
* @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 PoiTypeKeys {
/**
* {@code minecraft:armorer}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> ARMORER = create(key("armorer"));
/**
* {@code minecraft:bee_nest}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> BEE_NEST = create(key("bee_nest"));
/**
* {@code minecraft:beehive}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> BEEHIVE = create(key("beehive"));
/**
* {@code minecraft:butcher}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> BUTCHER = create(key("butcher"));
/**
* {@code minecraft:cartographer}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> CARTOGRAPHER = create(key("cartographer"));
/**
* {@code minecraft:cleric}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> CLERIC = create(key("cleric"));
/**
* {@code minecraft:farmer}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> FARMER = create(key("farmer"));
/**
* {@code minecraft:fisherman}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> FISHERMAN = create(key("fisherman"));
/**
* {@code minecraft:fletcher}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> FLETCHER = create(key("fletcher"));
/**
* {@code minecraft:home}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> HOME = create(key("home"));
/**
* {@code minecraft:leatherworker}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LEATHERWORKER = create(key("leatherworker"));
/**
* {@code minecraft:librarian}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LIBRARIAN = create(key("librarian"));
/**
* {@code minecraft:lightning_rod}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LIGHTNING_ROD = create(key("lightning_rod"));
/**
* {@code minecraft:lodestone}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LODESTONE = create(key("lodestone"));
/**
* {@code minecraft:mason}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> MASON = create(key("mason"));
/**
* {@code minecraft:meeting}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> MEETING = create(key("meeting"));
/**
* {@code minecraft:nether_portal}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> NETHER_PORTAL = create(key("nether_portal"));
/**
* {@code minecraft:shepherd}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> SHEPHERD = create(key("shepherd"));
/**
* {@code minecraft:test_instance}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> TEST_INSTANCE = create(key("test_instance"));
/**
* {@code minecraft:toolsmith}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> TOOLSMITH = create(key("toolsmith"));
/**
* {@code minecraft:weaponsmith}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> WEAPONSMITH = create(key("weaponsmith"));
private PoiTypeKeys() {
}
private static TypedKey<PoiType> create(final Key key) {
return TypedKey.create(RegistryKey.POINT_OF_INTEREST_TYPE, key);
}
}
@@ -0,0 +1,68 @@
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);
}
}
@@ -0,0 +1,54 @@
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);
}
}
@@ -3,14 +3,14 @@ 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.entity.poi.PoiType;
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#POINT_OF_INTEREST_TYPE}.
* 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
@@ -24,38 +24,45 @@ import org.jspecify.annotations.NullMarked;
})
@NullMarked
@GeneratedClass
public final class PoiTypeTagKeys {
public final class TimelineTagKeys {
/**
* {@code #minecraft:acquirable_job_site}
* {@code #minecraft:in_end}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<PoiType> ACQUIRABLE_JOB_SITE = create(key("acquirable_job_site"));
public static final TagKey<Timeline> IN_END = create(key("in_end"));
/**
* {@code #minecraft:bee_home}
* {@code #minecraft:in_nether}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<PoiType> BEE_HOME = create(key("bee_home"));
public static final TagKey<Timeline> IN_NETHER = create(key("in_nether"));
/**
* {@code #minecraft:village}
* {@code #minecraft:in_overworld}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<PoiType> VILLAGE = create(key("village"));
public static final TagKey<Timeline> IN_OVERWORLD = create(key("in_overworld"));
private PoiTypeTagKeys() {
/**
* {@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 PoiType} in the registry {@code minecraft:point_of_interest_type}.
* 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<PoiType> create(final Key key) {
return TagKey.create(RegistryKey.POINT_OF_INTEREST_TYPE, key);
public static TagKey<Timeline> create(final Key key) {
return TagKey.create(RegistryKey.TIMELINE, key);
}
}
@@ -75,7 +75,7 @@ public final class NamespacedTag implements com.destroystokyo.paper.Namespaced {
Preconditions.checkArgument(key != null, "Key cannot be null");
this.namespace = plugin.getName().toLowerCase(Locale.ROOT);
this.key = key.toLowerCase(Locale.ROOT);
this.key = key.toLowerCase().toLowerCase(Locale.ROOT);
// Check validity after normalization
Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", this.namespace);
@@ -257,16 +257,16 @@ public interface PlayerProfile extends org.bukkit.profile.PlayerProfile, PlayerH
@Override
default void applySkinToPlayerHeadContents(final PlayerHeadObjectContents.Builder builder) {
if (this.getProperties().isEmpty()) {
if (this.getProperties().isEmpty() && (this.getName() != null) != (this.getId() != null)) {
if (this.getId() != null) {
builder.id(this.getId());
} else if (this.getName() != null) {
} else {
builder.name(this.getName());
}
} else {
builder.id(this.getId())
.name(this.getName())
.profileProperties(this.getProperties().stream().map(prop -> property(prop.getName(), prop.getValue(), prop.getSignature())).toList());
return;
}
builder.id(this.getId())
.name(this.getName())
.profileProperties(this.getProperties().stream().map(prop -> property(prop.getName(), prop.getValue(), prop.getSignature())).toList());
}
}
@@ -3,7 +3,6 @@ package io.papermc.paper;
import com.destroystokyo.paper.SkinParts;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.datacomponent.item.ResolvableProfile;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.world.damagesource.CombatEntry;
import io.papermc.paper.world.damagesource.FallLocationType;
import java.util.Set;
@@ -53,14 +52,6 @@ public interface InternalAPIBridge {
*/
DamageEffect getDamageEffect(String key);
/**
* Creates an occupancy instance for the passed enum entry name.
*
* @param enumNameEntry the enum entry
* @return the occupancy
*/
PoiType.Occupancy createOccupancy(String enumNameEntry);
/**
* Constructs the legacy custom biome instance for the biome enum.
*
@@ -1,30 +0,0 @@
package io.papermc.paper.entity.poi;
import java.util.function.Predicate;
import org.bukkit.Location;
import org.bukkit.World;
import org.jspecify.annotations.NullMarked;
/**
* Holds the result of searching for a point of interest.
*
* @see World#locateAllPoiInRange(Location, Predicate, int)
* @see World#locateAllPoiInRange(Location, Predicate, int, PoiType.Occupancy)
*/
@NullMarked
public interface PoiSearchResult {
/**
* Returns the {@link PoiType}.
*
* @return the {@link PoiType}
*/
PoiType poiType();
/**
* Return the location of the {@link PoiType}.
*
* @return the location the {@link PoiType} was found at
*/
Location location();
}
@@ -1,66 +0,0 @@
package io.papermc.paper.entity.poi;
import io.papermc.paper.InternalAPIBridge;
import org.bukkit.Keyed;
import org.bukkit.block.data.BlockData;
import org.jspecify.annotations.NullMarked;
/**
* Represents a point of interest (POI).
* <p>
* Many PoiTypes act as a marker vs storing occupants examples of this are most
* explicitly {@link PoiTypes#NETHER_PORTAL}. This state can be checked using the method
* {@link #hasOccupants()} which will return {@code true} if the PoiType can have occupants.
*
* @see PoiTypes
*/
@NullMarked
public interface PoiType extends Keyed {
/**
* Determines whether the provided BlockState is relevant to this
* point of interest.
*
* @param data the BlockData to check
* @return {@code true} if the BlockState is relevant, otherwise {@code false}
*/
boolean is(BlockData data);
/**
* Determines whether this PoiType can actually hold occupants.
*
* @return {@code true} if this PoiType can hold occupants
*/
boolean hasOccupants();
/**
* Determines the type of occupancy the point of interest has.
* <p>
* A PoiType occupancy is defined by how much "space" a point of interest
* has.
* <p>
* With context to a Villager workstation if a villager is currently using
* a workstation as a source of its profession the occupancy would be
* {@link Occupancy#IS_OCCUPIED}, however, if no villager was attached to
* a workstation its occupancy would be {@link Occupancy#HAS_SPACE}.
*/
interface Occupancy {
/**
* The poi has space.
*/
Occupancy HAS_SPACE = occupancy("HAS_SPACE");
/**
* The poi is occupied.
*/
Occupancy IS_OCCUPIED = occupancy("IS_OCCUPIED");
/**
* The poi is either occupied or has space.
*/
Occupancy ANY = occupancy("ANY");
private static Occupancy occupancy(final String enumEntryName) {
return InternalAPIBridge.get().createOccupancy(enumEntryName);
}
}
}
@@ -1,64 +0,0 @@
package io.papermc.paper.entity.poi;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Registry;
import org.jspecify.annotations.NullMarked;
/**
* All the built-in point of interest types.
*/
@NullMarked
public final class PoiTypes {
// Start generate - PoiTypes
public static final PoiType ARMORER = get("armorer");
public static final PoiType BEE_NEST = get("bee_nest");
public static final PoiType BEEHIVE = get("beehive");
public static final PoiType BUTCHER = get("butcher");
public static final PoiType CARTOGRAPHER = get("cartographer");
public static final PoiType CLERIC = get("cleric");
public static final PoiType FARMER = get("farmer");
public static final PoiType FISHERMAN = get("fisherman");
public static final PoiType FLETCHER = get("fletcher");
public static final PoiType HOME = get("home");
public static final PoiType LEATHERWORKER = get("leatherworker");
public static final PoiType LIBRARIAN = get("librarian");
public static final PoiType LIGHTNING_ROD = get("lightning_rod");
public static final PoiType LODESTONE = get("lodestone");
public static final PoiType MASON = get("mason");
public static final PoiType MEETING = get("meeting");
public static final PoiType NETHER_PORTAL = get("nether_portal");
public static final PoiType SHEPHERD = get("shepherd");
public static final PoiType TEST_INSTANCE = get("test_instance");
public static final PoiType TOOLSMITH = get("toolsmith");
public static final PoiType WEAPONSMITH = get("weaponsmith");
// End generate - PoiTypes
private static PoiType get(@KeyPattern.Value final String key) {
return Registry.POINT_OF_INTEREST_TYPE.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key));
}
private PoiTypes() {
}
}
@@ -2,8 +2,9 @@ package io.papermc.paper.registry;
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;
@@ -93,11 +94,6 @@ public sealed interface RegistryKey<T> extends Keyed permits RegistryKeyImpl {
* @see io.papermc.paper.registry.keys.VillagerProfessionKeys
*/
RegistryKey<Villager.Profession> VILLAGER_PROFESSION = create("villager_profession");
/**
* Built-in registry for poi types.
* @see io.papermc.paper.registry.keys.PoiTypeKeys
*/
RegistryKey<PoiType> POINT_OF_INTEREST_TYPE = create("point_of_interest_type");
/**
* Built-in registry for villager types.
* @see io.papermc.paper.registry.keys.VillagerTypeKeys
@@ -257,6 +253,16 @@ 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");
/* ******************* *
@@ -0,0 +1,103 @@
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;
/**
* 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.Experimental
@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.
*
* @param ticks the tick this marker occurs on
* @param showInCommands whether the marker should be suggested in commands
*/
record TimeMarker(@Range(from = 0, to = Integer.MAX_VALUE) int ticks, boolean showInCommands) {
}
}
@@ -0,0 +1,21 @@
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.Experimental
@ApiStatus.NonExtendable
public interface WorldClockRegistryEntry {
/**
* A mutable builder for the {@link WorldClockRegistryEntry} plugins may change in applicable registry events.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
interface Builder extends WorldClockRegistryEntry, RegistryBuilder<WorldClock> {
}
}
@@ -14,9 +14,13 @@ import io.papermc.paper.registry.data.InstrumentRegistryEntry;
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.TimelineRegistryEntry;
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;
@@ -56,6 +60,8 @@ public final class RegistryEvents {
public static final RegistryEventProvider<Pig.Variant, PigVariantRegistryEntry.Builder> PIG_VARIANT = create(RegistryKey.PIG_VARIANT);
public static final RegistryEventProvider<ZombieNautilus.Variant, ZombieNautilusVariantRegistryEntry.Builder> ZOMBIE_NAUTILUS_VARIANT = create(RegistryKey.ZOMBIE_NAUTILUS_VARIANT);
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() {
@@ -0,0 +1,11 @@
package io.papermc.paper.world;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a timeline that can apply world attributes or define time markers for a {@link WorldClock}.
*/
@ApiStatus.Experimental
public interface Timeline extends Keyed {
}
@@ -0,0 +1,11 @@
package io.papermc.paper.world;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a world clock.
*/
@ApiStatus.Experimental
public interface WorldClock extends Keyed {
}
@@ -859,13 +859,9 @@ public final class Bukkit {
/**
* Gets the world with the given name.
*
* <p>This method is considered obsolete and is a candidate for future deprecation.
* Prefer using {@link #getWorld(NamespacedKey)}.</p>
*
* @param name the name of the world to retrieve
* @return a world with the given name, or null if none exists
*/
@ApiStatus.Obsolete
@Nullable
public static World getWorld(@NotNull String name) {
return server.getWorld(name);
@@ -881,7 +877,7 @@ public final class Bukkit {
public static World getWorld(@NotNull UUID uid) {
return server.getWorld(uid);
}
// Paper start
/**
* Gets the world from the given NamespacedKey
*
@@ -903,6 +899,7 @@ public final class Bukkit {
public static World getWorld(@NotNull net.kyori.adventure.key.Key worldKey) {
return server.getWorld(worldKey);
}
// Paper end
/**
* Create a new virtual {@link WorldBorder}.
@@ -5,7 +5,6 @@ import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Predicate;
import io.papermc.paper.world.MoonPhase;
import org.bukkit.block.Biome;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
@@ -527,12 +526,12 @@ public interface RegionAccessor extends Keyed, io.papermc.paper.world.flag.Featu
@NotNull
public <T extends Entity> T addEntity(@NotNull T entity);
// Paper start
/**
* @return the current moon phase at the current time in the world
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
@NotNull
MoonPhase getMoonPhase();
io.papermc.paper.world.MoonPhase getMoonPhase();
/**
* Get the world's key
@@ -560,4 +559,5 @@ public interface RegionAccessor extends Keyed, io.papermc.paper.world.flag.Featu
* @return collides or not
*/
boolean hasCollisionsIn(@NotNull org.bukkit.util.BoundingBox boundingBox);
// Paper end
}
@@ -5,7 +5,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
@@ -281,12 +280,6 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
* @see Villager.Profession
*/
Registry<Villager.Profession> VILLAGER_PROFESSION = registryFor(RegistryKey.VILLAGER_PROFESSION);
/**
* Point of interest types.
*
* @see PoiType
*/
Registry<PoiType> POINT_OF_INTEREST_TYPE = registryFor(RegistryKey.POINT_OF_INTEREST_TYPE);
/**
* Villager type.
*
@@ -16,6 +16,7 @@ import java.util.UUID;
import java.util.function.Consumer;
import java.util.logging.Logger;
import io.papermc.paper.configuration.ServerConfiguration;
import io.papermc.paper.world.WorldClock;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
@@ -165,6 +166,85 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
@NotNull
public Collection<? extends Player> getOnlinePlayers();
/**
* Gets the relative in-game time of the specified clock.
* <p>
* The relative time is analogous to hours * 1000
*
* @param clock the clock
* @return The current relative time
* @see #getFullTime(WorldClock) Returns an absolute time of the clock
*/
public long getTime(@NotNull WorldClock clock);
/**
* Sets the relative in-game time of the specified clock.
* <p>
* The relative time is analogous to hours * 1000
* <p>
* Note that setting the relative time below the current relative time
* will actually move the clock forward a day. If you require to rewind
* time, please see {@link #setFullTime(WorldClock, long)}
*
* @param clock the clock
* @param time The new relative time to set the clock to (in hours*1000)
* @see #setFullTime(WorldClock, long) Sets the absolute time of a specific clock
*/
public void setTime(@NotNull WorldClock clock, long time);
/**
* Gets the full in-game time of the specified clock.
*
* @param clock the clock
* @return The current absolute time
* @see #getTime(WorldClock) Returns a relative time of the clock
*/
public long getFullTime(@NotNull WorldClock clock);
/**
* Sets the in-game time of the specified clock.
* <p>
* Note that this sets the full time of the clock, which may cause adverse
* effects such as breaking redstone clocks and any scheduled events
*
* @param clock the clock
* @param time The new absolute time to set this clock to
* @see #setTime(WorldClock, long) Sets the relative time of a specific clock
*/
public void setFullTime(@NotNull WorldClock clock, long time);
/**
* Gets whether the specified clock is paused.
*
* @param clock the clock
* @return whether the clock is paused
*/
public boolean isClockPaused(@NotNull WorldClock clock);
/**
* Sets whether the specified clock is paused.
*
* @param clock the clock
* @param paused whether the clock is paused
*/
public void setClockPaused(@NotNull WorldClock clock, boolean paused);
/**
* Gets the rate of the specified clock.
*
* @param clock the clock
* @return the clock rate
*/
public float getClockRate(@NotNull WorldClock clock);
/**
* Sets the rate of the specified clock.
*
* @param clock the clock
* @param rate the clock rate
*/
public void setClockRate(@NotNull WorldClock clock, float rate);
/**
* Get the maximum amount of players which can login to this server.
*
+30 -78
View File
@@ -1,8 +1,8 @@
package org.bukkit;
import io.papermc.paper.entity.poi.PoiSearchResult;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -45,7 +45,6 @@ import org.bukkit.util.BoundingBox;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.StructureSearchResult;
import org.bukkit.util.Vector;
import org.checkerframework.checker.index.qual.Positive;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -2074,8 +2073,10 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
*
* @param time The new relative time to set the in-game time to (in
* hours*1000)
* @see #setFullTime(long) Sets the absolute time of this world
* @see Server#setTime(WorldClock, long) Sets the relative time of a specific clock
* @deprecated use {@link Server#setTime(WorldClock, long)} with {@link #getClock()} instead
*/
@Deprecated(since = "26.1")
public void setTime(long time);
/**
@@ -2093,13 +2094,31 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* effects such as breaking redstone clocks and any scheduled events
*
* @param time The new absolute time to set this world to
* @see #setTime(long) Sets the relative time of this world
* @deprecated all overworlds share the same world clock by default now
* @see Server#setFullTime(WorldClock, long) Sets the absolute time of a specific clock
* @deprecated use {@link Server#setFullTime(WorldClock, long)} with {@link #getClock()} instead
* @throws IllegalArgumentException if this world does not have a world clock (e.g. the nether)
*/
@Deprecated // TODO world clock API with links to it
@Deprecated(since = "26.1")
public void setFullTime(long time);
/**
* Gets the clock of this world.
* <p>
* This may be a world-specific override of the dimension type's clock.
*
* @return the clock, or null if this world does not have one
*/
@Nullable
public WorldClock getClock();
/**
* Gets the timelines applied by this world's dimension type.
*
* @return the timelines
*/
@NotNull
public List<Timeline> getTimelines();
// Paper start
/**
@@ -2772,11 +2791,10 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
/**
* Gets if this world is natural.
* <p>
* When true, eyeblossoms cycle open/close, nether portals can spawn
* zombified piglins and creaking heart works
* When false, the moon is not visible and eyeblossoms do not open/close
*
* @return true if world is natural
* @deprecated replaced by the gameplay/nether_portal_spawns_piglin, gameplay/eyeblossom_open and gameplay/creaking_active environmental attributes
* @deprecated replaced by the gameplay/eyeblossom_open and gameplay/creaking_active environment attributes
*/
@Deprecated(since = "1.21.11")
public boolean isNatural();
@@ -2788,7 +2806,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* not always be the case.
*
* @return true if beds work in this world
* @deprecated due to 1.21.11 beds changes, a boolean no longer
* @deprecated Due to 1.21.11 beds changes, a boolean no longer
* represents if they work. There is no replacement API yet
*/
@ApiStatus.Obsolete(since = "1.21.11")
@@ -2813,7 +2831,6 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* transforming to zombified piglins.
*
* @return true if piglins will not transform to zombified piglins
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
public boolean isPiglinSafe();
@@ -2821,7 +2838,6 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* Gets if this world allows players to charge and use respawn anchors.
*
* @return true if players can charge and use respawn anchors
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
public boolean isRespawnAnchorWorks();
@@ -2830,7 +2846,6 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* raid.
*
* @return true if raids will be triggered
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
public boolean hasRaids();
@@ -2844,7 +2859,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* </ul>
*
* @return true if this world has the above mechanics
* @deprecated replaced by the gameplay/water_evaporates and gameplay/fast_lava environmental attributes
* @deprecated as of 1.21.11, ultra warm is replaced by the WATER_EVAPORATES, FAST_LAVA, and DEFAULT_DRIPSTONE_PARTICLE environment attributes.
*/
@Deprecated(since = "1.21.11")
public boolean isUltraWarm();
@@ -4302,69 +4317,6 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
return java.util.Optional.ofNullable(this.locateNearestBiome(origin, radius, step, step, biome)).map(BiomeSearchResult::getLocation).orElse(null);
}
/**
* Finds the nearest point of interest closest to the given location
* without any occupancy restriction.
*
* @param origin where to start looking for a new point of interest at
* @param poiType the poi type to find
* @param radius the radius
* @return a location at the nearest PoiType or {@code null} if no poi was found
*/
@Nullable
default Location locateNearestPoi(@NotNull Location origin, @NotNull PoiType poiType, @Positive int radius) {
return this.locateNearestPoi(origin, poiType, radius, PoiType.Occupancy.ANY);
}
/**
* Finds the nearest point of interest closest to the given location.
* <p>
* {@link PoiType} that return {@code false} for {@link PoiType#hasOccupants()}
* may not behave as expected for given occupancies other than
* {@link PoiType.Occupancy#ANY}.
*
* @param origin where to start looking for a new point of interest at
* @param poiType the poi type to find
* @param radius the radius
* @param occupancy the current required occupancy of the point of interest
* @return a location at the nearest PoiType or {@code null} if no poi was found
*/
@Nullable
Location locateNearestPoi(@NotNull Location origin, @NotNull PoiType poiType, @Positive int radius, @NotNull PoiType.Occupancy occupancy);
/**
* Finds all valid {@link PoiType} in the provided radius and returns them
* in a list format without any occupancy restriction.
*
* @param origin the center point of the radius
* @param poiTypePredicate the predicate to test whether a PoiType
* can be collected into the result
* @param radius the radius
* @return a list of search results containing all found Poi's in the range
*/
@NotNull
default List<PoiSearchResult> locateAllPoiInRange(@NotNull Location origin, @NotNull Predicate<PoiType> poiTypePredicate, @Positive int radius) {
return this.locateAllPoiInRange(origin, poiTypePredicate, radius, PoiType.Occupancy.ANY);
}
/**
* Finds all valid {@link PoiType} in the provided radius and returns them
* in a list format.
* <p>
* {@link PoiType} that return false for {@link PoiType#hasOccupants()}
* may not behave as expected for given occupancies other than
* {@link PoiType.Occupancy#ANY}.
*
* @param origin the center point of the radius
* @param poiTypePredicate the predicate to test whether a PoiType
* can be collected into the result
* @param radius the radius
* @param occupancy the current required occupancy of the point of interest
* @return a list of search results containing all found Poi's in the range
*/
@NotNull
List<PoiSearchResult> locateAllPoiInRange(@NotNull Location origin, @NotNull Predicate<PoiType> poiTypePredicate, @Positive int radius, @NotNull PoiType.Occupancy occupancy);
/**
* Gets the coordinate scaling of this world.
*
@@ -1,8 +1,14 @@
package org.bukkit;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import io.papermc.paper.math.Position;
import org.bukkit.command.CommandSender;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
@@ -26,10 +32,8 @@ public class WorldCreator {
private String generatorSettings = "";
private boolean hardcore = false;
private boolean bonusChest = false;
private @Nullable Position spawnPositionOverride;
private @Nullable Float spawnYawOverride;
private @Nullable Float spawnPitchOverride;
private TypedKey<WorldClock> clock = null;
private List<TypedKey<Timeline>> timelines = null;
/**
* Creates an empty WorldCreationOptions for the given world name.
@@ -157,6 +161,8 @@ public class WorldCreator {
generateStructures = world.canGenerateStructures();
hardcore = world.isHardcore();
bonusChest = world.hasBonusChest();
clock = world.getClock() == null ? null : TypedKey.create(RegistryKey.WORLD_CLOCK, world.getClock().getKey());
timelines = world.getTimelines().stream().map(timeline -> TypedKey.create(RegistryKey.TIMELINE, timeline.getKey())).toList();
return this;
}
@@ -180,6 +186,8 @@ public class WorldCreator {
generatorSettings = creator.generatorSettings();
hardcore = creator.hardcore();
bonusChest = creator.bonusChest();
clock = creator.clock();
timelines = creator.timelines() == null ? null : new ArrayList<>(creator.timelines());
return this;
}
@@ -266,80 +274,6 @@ public class WorldCreator {
return this;
}
/**
* Sets the forced spawn position for the world created by this {@link WorldCreator}.
* <p>
* This overrides vanilla and custom generator behavior without loading any chunks.
* When a forced spawn is specified, the bonus chest will not be generated.
*
* @param position the spawn position
* @param yaw the yaw rotation at spawn
* @param pitch the pitch rotation at spawn
* @return this object, for chaining
*/
@NotNull
public WorldCreator forcedSpawnPosition(@NotNull Position position, float yaw, float pitch) {
this.spawnPositionOverride = position; // If you set this to null, it wont do anything!
this.spawnYawOverride = yaw;
this.spawnPitchOverride = pitch;
return this;
}
/**
* Clears any previously forced spawn position.
* <p>
* After calling this, vanilla spawn selection behavior is used.
*
* @return this object, for chaining
*/
@NotNull
public WorldCreator clearForcedSpawnPosition() {
this.spawnPositionOverride = null;
this.spawnYawOverride = null;
this.spawnPitchOverride = null;
return this;
}
/**
* Gets the forced spawn position that will be applied when this world is created.
* <p>
* If this returns {@code null}, vanilla or custom generator behavior will be used
* to determine the spawn position.
*
* @return the forced spawn position, or {@code null} for the vanilla behavior
*/
public @Nullable Position forcedSpawnPosition() {
return this.spawnPositionOverride;
}
/**
* Gets the forced spawn yaw that will be applied when this world is created.
* <p>
* If this returns {@code null}, the spawn yaw will be determined by vanilla behavior
* or the world generator.
* <p>
* This value is only meaningful if a forced spawn position is present.
*
* @return the forced spawn yaw, or {@code null} for the vanilla behavior
*/
public @Nullable Float forcedSpawnYaw() {
return this.spawnYawOverride;
}
/**
* Gets the forced spawn pitch that will be applied when this world is created.
* <p>
* If this returns {@code null}, the spawn pitch will be determined by vanilla behavior
* or the world generator.
* <p>
* This value is only meaningful if a forced spawn position is present.
*
* @return the forced spawn pitch, or {@code null} for the vanilla behavior
*/
public @Nullable Float forcedSpawnPitch() {
return this.spawnPitchOverride;
}
/**
* Gets the generator that will be used to create or load the world.
* <p>
@@ -592,6 +526,56 @@ public class WorldCreator {
return bonusChest;
}
/**
* Sets the clock that will be used by this world.
* <p>
* If unset, the clock from the selected environment's dimension type will be used.
* If set, this overrides the selected environment dimension type's clock.
*
* @param clock the clock, or null to use the environment default
* @return This object, for chaining
*/
@NotNull
public WorldCreator clock(@Nullable TypedKey<WorldClock> clock) {
this.clock = clock;
return this;
}
/**
* Gets the clock override that will be used by this world.
*
* @return the clock override, or null to use the environment default
*/
@Nullable
public TypedKey<WorldClock> clock() {
return this.clock;
}
/**
* Sets the timelines that will be used by this world's dimension type.
* <p>
* If unset, the timelines from the selected environment's dimension type will be used.
* Passing an empty collection will create or load the world with no timelines.
*
* @param timelines the timelines, or null to use the environment default
* @return This object, for chaining
*/
@NotNull
public WorldCreator timelines(@Nullable Collection<TypedKey<Timeline>> timelines) {
this.timelines = timelines == null ? null : new ArrayList<>(timelines);
return this;
}
/**
* Gets the timeline overrides that will be used by this world's dimension type.
*
* @return the timeline overrides, or null to use the environment default
*/
@Nullable
public List<TypedKey<Timeline>> timelines() {
return this.timelines == null ? null : List.copyOf(this.timelines);
}
/**
* Creates a world with the specified options.
* <p>
@@ -2,15 +2,12 @@ package org.bukkit.entity;
import org.bukkit.attribute.Attribute;
import org.bukkit.damage.DamageSource;
import org.bukkit.damage.DamageType;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents an {@link Entity} that has health and can take damage.
*/
@NullMarked
public interface Damageable extends Entity {
/**
* Deals the given amount of damage to this entity.
@@ -35,24 +32,7 @@ public interface Damageable extends Entity {
* @param amount amount of damage to deal
* @param damageSource source to which the damage should be attributed
*/
void damage(double amount, DamageSource damageSource);
/**
* Sets the entity's health to 0 and kill the entity with a generic DamageSource.
*
* @throws IllegalStateException if is used in world generation
*/
default void kill() {
this.kill(DamageSource.builder(DamageType.GENERIC_KILL).build());
}
/**
* Sets the entity's health to 0 and kill the entity with the specified DamageSource.
*
* @param damageSource the DamageSource to use for the kill
* @throws IllegalStateException if is used in world generation
*/
void kill(DamageSource damageSource);
void damage(double amount, @NotNull DamageSource damageSource);
/**
* Gets the entity's health from 0 to {@link #getMaxHealth()}, where 0 is dead.
@@ -77,7 +57,7 @@ public interface Damageable extends Entity {
* @param amount heal amount
*/
default void heal(final double amount) {
this.heal(amount, EntityRegainHealthEvent.RegainReason.CUSTOM);
this.heal(amount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.CUSTOM);
}
/**
@@ -86,7 +66,7 @@ public interface Damageable extends Entity {
* @param amount heal amount
* @param reason heal reason
*/
void heal(double amount, EntityRegainHealthEvent.RegainReason reason);
void heal(double amount, @NotNull org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason);
/**
* Gets the entity's absorption amount.
@@ -10,7 +10,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when a block is ignited.
* Called when a block is ignited. If you want to catch when a Player places
* fire, you need to use {@link BlockPlaceEvent}.
* <p>
* If this event is cancelled, the block will not be ignited.
*/
@@ -1,30 +1,27 @@
package org.bukkit.event.hanging;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Hanging;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Triggered when a hanging entity is removed by an entity
*/
@NullMarked
public class HangingBreakByEntityEvent extends HangingBreakEvent {
private final Entity remover;
private final DamageSource damageSource;
@ApiStatus.Internal
public HangingBreakByEntityEvent(final Hanging hanging, final Entity remover, final DamageSource damageSource) {
this(hanging, remover, damageSource, HangingBreakEvent.RemoveCause.ENTITY);
public HangingBreakByEntityEvent(@NotNull final Hanging hanging, @NotNull final Entity remover) {
this(hanging, remover, HangingBreakEvent.RemoveCause.ENTITY);
}
@ApiStatus.Internal
public HangingBreakByEntityEvent(final Hanging hanging, final Entity remover, final DamageSource damageSource, final HangingBreakEvent.RemoveCause cause) {
public HangingBreakByEntityEvent(@NotNull final Hanging hanging, @NotNull final Entity remover, @NotNull final HangingBreakEvent.RemoveCause cause) {
super(hanging, cause);
this.remover = remover;
this.damageSource = damageSource;
}
/**
@@ -32,16 +29,8 @@ public class HangingBreakByEntityEvent extends HangingBreakEvent {
*
* @return the entity that removed the hanging entity
*/
@NotNull
public Entity getRemover() {
return this.remover;
}
/**
* Gets the {@link DamageSource} that caused the hanging entity to be removed.
*
* @return the damage source
*/
public DamageSource getDamageSource() {
return this.damageSource;
}
}
@@ -6,26 +6,24 @@ import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* Called when a player gets kicked from the server
*/
@NullMarked
public class PlayerKickEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private Component kickReason;
private @Nullable Component leaveMessage;
private Component leaveMessage;
private final Cause cause;
private boolean cancelled;
@ApiStatus.Internal
@Deprecated(forRemoval = true)
public PlayerKickEvent(final Player playerKicked, final String kickReason, final String leaveMessage) {
public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final String kickReason, @NotNull final String leaveMessage) {
super(playerKicked);
this.kickReason = LegacyComponentSerializer.legacySection().deserialize(kickReason);
this.leaveMessage = LegacyComponentSerializer.legacySection().deserialize(leaveMessage);
@@ -34,7 +32,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
@ApiStatus.Internal
@Deprecated(forRemoval = true)
public PlayerKickEvent(final Player playerKicked, final Component kickReason, final Component leaveMessage) {
public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final Component kickReason, @NotNull final Component leaveMessage) {
super(playerKicked);
this.kickReason = kickReason;
this.leaveMessage = leaveMessage;
@@ -42,7 +40,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
}
@ApiStatus.Internal
public PlayerKickEvent(final Player playerKicked, final Component kickReason, final Component leaveMessage, final Cause cause) {
public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final Component kickReason, @NotNull final Component leaveMessage, @NotNull final Cause cause) {
super(playerKicked);
this.kickReason = kickReason;
this.leaveMessage = leaveMessage;
@@ -54,7 +52,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
*
* @return string kick reason
*/
public Component reason() {
public @NotNull Component reason() {
return this.kickReason;
}
@@ -63,7 +61,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
*
* @param kickReason kick reason
*/
public void reason(Component kickReason) {
public void reason(@NotNull Component kickReason) {
this.kickReason = kickReason;
}
@@ -73,6 +71,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
* @return string kick reason
* @deprecated in favour of {@link #reason()}
*/
@NotNull
@Deprecated
public String getReason() {
return LegacyComponentSerializer.legacySection().serialize(this.kickReason);
@@ -82,10 +81,10 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
* Sets the reason why the player is getting kicked
*
* @param kickReason kick reason
* @deprecated in favour of {@link #reason(Component)}
* @deprecated in favour of {@link #reason(net.kyori.adventure.text.Component)}
*/
@Deprecated
public void setReason(String kickReason) {
public void setReason(@NotNull String kickReason) {
this.kickReason = LegacyComponentSerializer.legacySection().deserialize(kickReason);
}
@@ -94,16 +93,16 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
*
* @return string kick reason
*/
public @Nullable Component leaveMessage() {
public @NotNull Component leaveMessage() {
return this.leaveMessage;
}
/**
* Sets the leave message send to all online players
*
* @param leaveMessage leave message. If {@code null}, no message will be sent
* @param leaveMessage leave message
*/
public void leaveMessage(@Nullable Component leaveMessage) {
public void leaveMessage(@NotNull Component leaveMessage) {
this.leaveMessage = leaveMessage;
}
@@ -113,26 +112,28 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
* @return string kick reason
* @deprecated in favour of {@link #leaveMessage()}
*/
@NotNull
@Deprecated
public @Nullable String getLeaveMessage() {
return LegacyComponentSerializer.legacySection().serializeOrNull(this.leaveMessage);
public String getLeaveMessage() {
return LegacyComponentSerializer.legacySection().serialize(this.leaveMessage);
}
/**
* Sets the leave message send to all online players
*
* @param leaveMessage leave message. If {@code null}, no message will be sent
* @deprecated in favour of {@link #leaveMessage(Component)}
* @param leaveMessage leave message
* @deprecated in favour of {@link #leaveMessage(net.kyori.adventure.text.Component)}
*/
@Deprecated
public void setLeaveMessage(@Nullable String leaveMessage) {
this.leaveMessage = LegacyComponentSerializer.legacySection().deserializeOrNull(leaveMessage);
public void setLeaveMessage(@NotNull String leaveMessage) {
this.leaveMessage = LegacyComponentSerializer.legacySection().deserialize(leaveMessage);
}
/**
* Gets the cause of this kick
*/
public PlayerKickEvent.Cause getCause() {
@NotNull
public org.bukkit.event.player.PlayerKickEvent.Cause getCause() {
return this.cause;
}
@@ -146,11 +147,13 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
@@ -1,6 +1,5 @@
package org.bukkit.event.vehicle;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Cancellable;
@@ -16,30 +15,18 @@ public class VehicleDamageEvent extends VehicleEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final DamageSource damageSource;
private final Entity attacker;
private double damage;
private boolean cancelled;
@ApiStatus.Internal
public VehicleDamageEvent(final @NotNull Vehicle vehicle, final @NotNull DamageSource damageSource, final @Nullable Entity attacker, final double damage) {
public VehicleDamageEvent(@NotNull final Vehicle vehicle, @Nullable final Entity attacker, final double damage) {
super(vehicle);
this.damageSource = damageSource;
this.attacker = attacker;
this.damage = damage;
}
/**
* Gets the DamageSource that caused the damage.
*
* @return the DamageSource that caused the damage
*/
@NotNull
public DamageSource getDamageSource() {
return this.damageSource;
}
/**
* Gets the Entity that is attacking the vehicle
*
@@ -1,6 +1,5 @@
package org.bukkit.event.vehicle;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Cancellable;
@@ -18,27 +17,15 @@ public class VehicleDestroyEvent extends VehicleEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final DamageSource damageSource;
private final Entity attacker;
private boolean cancelled;
@ApiStatus.Internal
public VehicleDestroyEvent(final @NotNull Vehicle vehicle, final @NotNull DamageSource damageSource, final @Nullable Entity attacker) {
public VehicleDestroyEvent(@NotNull final Vehicle vehicle, @Nullable final Entity attacker) {
super(vehicle);
this.damageSource = damageSource;
this.attacker = attacker;
}
/**
* Gets the DamageSource that has destroyed the vehicle.
*
* @return the DamageSource that has destroyed the vehicle
*/
@NotNull
public DamageSource getDamageSource() {
return this.damageSource;
}
/**
* Gets the Entity that has destroyed the vehicle, potentially null
*
@@ -0,0 +1,110 @@
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;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Called when the time skips for a world clock.
* <p>
* If the event is cancelled the time will not change.
*/
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(@NotNull WorldClock clock, @NotNull SkipReason skipReason, long skipAmount) {
this.clock = clock;
this.skipReason = skipReason;
this.skipAmount = skipAmount;
}
/**
* Gets the clock whose time is changing.
*
* @return the clock, or null if the change does not target a specific clock
*/
@NotNull
public WorldClock getClock() {
return this.clock;
}
/**
* Gets the reason why the time has skipped.
*
* @return a SkipReason value detailing why the time has skipped
*/
@NotNull
public SkipReason getSkipReason() {
return this.skipReason;
}
/**
* Gets the amount of time that was skipped.
*
* @return Amount of time skipped
*/
public long getSkipAmount() {
return this.skipAmount;
}
/**
* Sets the amount of time to skip.
*
* @param skipAmount Amount of time to skip
*/
public void setSkipAmount(long skipAmount) {
this.skipAmount = skipAmount;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
/**
* An enum specifying the reason the time skipped.
*/
public enum SkipReason {
/**
* When time is changed using the vanilla /time command.
*/
COMMAND,
/**
* When time is changed by a plugin.
*/
CUSTOM,
/**
* When time is changed by all players sleeping in their beds and the
* night skips.
*/
NIGHT_SKIP
}
}
@@ -1,7 +1,7 @@
package org.bukkit.event.world;
import io.papermc.paper.world.WorldClock;
import org.bukkit.World;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -11,59 +11,26 @@ import org.jetbrains.annotations.NotNull;
* <p>
* If the event is cancelled the time will not change.
*/
// TODO - snapshot - 26.1 API needed for clock!
public class TimeSkipEvent extends WorldEvent implements Cancellable {
public class TimeSkipEvent extends ClockTimeSkipEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final SkipReason skipReason;
private long skipAmount;
private boolean cancelled;
private final World world;
@ApiStatus.Internal
public TimeSkipEvent(@NotNull World world, @NotNull SkipReason skipReason, long skipAmount) {
super(world);
this.skipReason = skipReason;
this.skipAmount = skipAmount;
public TimeSkipEvent(@NotNull World world, @NotNull WorldClock clock, @NotNull SkipReason skipReason, long skipAmount) {
super(clock, skipReason, skipAmount);
this.world = world;
}
/**
* Gets the reason why the time has skipped.
* Returns the world that time is skipped in.
*
* @return a SkipReason value detailing why the time has skipped
* @return world that time is skipped in
*/
@NotNull
public SkipReason getSkipReason() {
return this.skipReason;
}
/**
* Gets the amount of time that was skipped.
*
* @return Amount of time skipped
*/
public long getSkipAmount() {
return this.skipAmount;
}
/**
* Sets the amount of time to skip.
*
* @param skipAmount Amount of time to skip
*/
public void setSkipAmount(long skipAmount) {
this.skipAmount = skipAmount;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
public World getWorld() {
return world;
}
@NotNull
@@ -76,24 +43,4 @@ public class TimeSkipEvent extends WorldEvent implements Cancellable {
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
/**
* An enum specifying the reason the time skipped.
*/
public enum SkipReason {
/**
* When time is changed using the vanilla /time command.
*/
COMMAND,
/**
* When time is changed by a plugin.
*/
CUSTOM,
/**
* When time is changed by all players sleeping in their beds and the
* night skips.
*/
NIGHT_SKIP
}
}
@@ -133,74 +133,26 @@ public class PotionEffect implements ConfigurationSerializable {
}
// Paper start
/**
* Returns a new potion effect with the provided type and this effect's
* duration, amplifier, ambient, particles, and icon.
*
* @param type effect type
* @return a new potion effect with the provided type
*/
@NotNull
public PotionEffect withType(@NotNull PotionEffectType type) {
return new PotionEffect(type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided duration and this effect's
* type, amplifier, ambient, particles, and icon.
*
* @param duration measured in ticks, see {@link
* PotionEffect#getDuration()}
* @return a new potion effect with the provided duration
*/
@NotNull
public PotionEffect withDuration(int duration) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided amplifier and this effect's
* type, duration, ambient, particles, and icon.
*
* @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()}
* @return a new potion effect with the provided amplifier
*/
@NotNull
public PotionEffect withAmplifier(int amplifier) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided ambient and this effect's
* type, duration, amplifier, particles, and icon.
*
* @param ambient the ambient status, see {@link PotionEffect#isAmbient()}
* @return a new potion effect with the provided ambient
*/
@NotNull
public PotionEffect withAmbient(boolean ambient) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided particles and this effect's
* type, duration, amplifier, ambient, and icon.
*
* @param particles the particle status, see {@link PotionEffect#hasParticles()}
* @return a new potion effect with the provided particles
*/
@NotNull
public PotionEffect withParticles(boolean particles) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided icon and this effect's
* type, duration, amplifier, ambient, and particles.
*
* @param icon the icon status, see {@link PotionEffect#hasIcon()}
* @return a new potion effect with the provided icon
*/
@NotNull
public PotionEffect withIcon(boolean icon) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
@@ -27,7 +27,6 @@ import io.papermc.paper.datacomponent.item.SwingAnimation;
import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.item.MapPostProcessing;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.world.WeatheringCopperState;
import io.papermc.typewriter.preset.EnumCloneRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
@@ -232,7 +231,6 @@ public final class Rewriters {
.register("PigSoundVariant", Pig.SoundVariant.class, new RegistryFieldRewriter<>(Registries.PIG_SOUND_VARIANT, "getSoundVariant"))
.register("ZombieNautilusVariant", ZombieNautilus.Variant.class, new RegistryFieldRewriter<>(Registries.ZOMBIE_NAUTILUS_VARIANT, "getVariant"))
.register("Dialog", Dialog.class, new RegistryFieldRewriter<>(Registries.DIALOG, "getDialog"))
.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
.register("BlockType", BlockType.class, new BlockTypeRewriter())
@@ -4,7 +4,6 @@ import io.papermc.generator.utils.ClassHelper;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.registry.data.BannerPatternRegistryEntry;
import io.papermc.paper.registry.data.CatTypeRegistryEntry;
import io.papermc.paper.registry.data.ChickenVariantRegistryEntry;
@@ -18,9 +17,13 @@ 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.SoundEventRegistryEntry;
import io.papermc.paper.registry.data.TimelineRegistryEntry;
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;
@@ -66,6 +69,7 @@ import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.item.equipment.trim.TrimMaterials;
import net.minecraft.world.item.equipment.trim.TrimPatterns;
import net.minecraft.world.clock.WorldClocks;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BannerPatterns;
@@ -73,6 +77,7 @@ 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;
@@ -176,8 +181,7 @@ public final class RegistryEntries {
entry(Registries.FLUID, Fluids.class, Fluid.class),
entry(Registries.SOUND_EVENT, SoundEvents.class, Sound.class).allowDirect().apiRegistryField("SOUNDS").apiRegistryBuilder(SoundEventRegistryEntry.Builder.class, "PaperSoundEventRegistryEntry.PaperBuilder", RegistryEntry.RegistryModificationApiSupport.NONE),
entry(Registries.DATA_COMPONENT_TYPE, DataComponents.class, DataComponentType.class, "Paper").preload(DataComponentTypes.class).apiAccessName("of"),
entry(Registries.GAME_RULE, GameRules.class, GameRule.class).genericArgCount(1)/*.preload(org.bukkit.GameRules.class)*/, // only preload once the old names are removed
entry(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.class, io.papermc.paper.entity.poi.PoiType.class, "Paper").preload(io.papermc.paper.entity.poi.PoiTypes.class)
entry(Registries.GAME_RULE, GameRules.class, GameRule.class).genericArgCount(1)/*.preload(org.bukkit.GameRules.class)*/ // only preload once the old names are removed
);
public static final List<RegistryEntry<?>> DATA_DRIVEN = List.of(
@@ -203,7 +207,9 @@ public final class RegistryEntries {
entry(Registries.PIG_VARIANT, PigVariants.class, Pig.Variant.class).writableApiRegistryBuilder(PigVariantRegistryEntry.Builder.class, "PaperPigVariantRegistryEntry.PaperBuilder"),
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.DIALOG, Dialogs.class, Dialog.class, "Paper").allowDirect().writableApiRegistryBuilder(DialogRegistryEntry.Builder.class, "PaperDialogRegistryEntry.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()
);
public static final List<RegistryEntry<?>> API_ONLY = List.of(
@@ -23838,7 +23838,7 @@ index 3c1490ac7c259da04031db2f170e0c0a5f512191..470d7c770ae9d045b97e2df145cfe3cf
}
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda79e93f6f4 100644
index 2d9106648f8921ef60c2a24e166d17eb8ce01b48..cea3bd30d326ece39298262401681d77fe064464 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -191,7 +191,7 @@ import net.minecraft.world.scores.ScoreboardSaveData;
@@ -23943,7 +23943,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
public MinecraftServer(
// CraftBukkit start
@@ -853,7 +939,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -843,7 +929,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
level.setSpawnSettings(level.isSpawningMonsters()); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
this.updateEffectiveRespawnData();
this.forceTicks = false; // CraftBukkit
@@ -23952,7 +23952,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
new org.bukkit.event.world.WorldLoadEvent(level.getWorld()).callEvent(); // Paper - call WorldLoadEvent
}
@@ -876,6 +962,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -866,6 +952,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public abstract boolean shouldRconBroadcast();
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force) {
@@ -23964,7 +23964,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
this.scoreboard.storeToSaveDataIfDirty(this.getDataStorage().computeIfAbsent(ScoreboardSaveData.TYPE));
boolean result = false;
@@ -884,7 +975,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -874,7 +965,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
LOGGER.info("Saving chunks for level '{}'/{}", level, level.dimension().identifier());
}
@@ -23973,7 +23973,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
result = true;
}
@@ -981,7 +1072,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -971,7 +1062,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -23982,7 +23982,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
for (ServerLevel levelx : this.getAllLevels()) {
@@ -992,17 +1083,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -982,17 +1073,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.waitUntilNextTick();
}
@@ -24007,7 +24007,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
this.isSaving = false;
this.savedDataStorage.close();
@@ -1023,6 +1111,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1013,6 +1101,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.services().nameToIdCache().save(false); // Paper - Perf: Async GameProfileCache saving
}
// Spigot end
@@ -24022,7 +24022,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
// Paper start - Improved watchdog support - move final shutdown items here
Util.shutdownExecutors();
this.onServerExit();
@@ -1117,16 +1213,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1107,16 +1203,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// execute small amounts of other tasks just in case the number of tasks we are
// draining is large - chunk system and packet processing may be latency sensitive
@@ -24057,7 +24057,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
profiler.pop(); // moonrise:run_all_chunk
profiler.pop(); // moonrise:run_all_tasks
@@ -1427,6 +1538,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1417,6 +1528,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private boolean pollTaskInternal() {
if (super.pollTask()) {
@@ -24065,7 +24065,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
return true;
} else {
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
@@ -1568,6 +1680,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1558,6 +1670,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper - improve tick loop - moved into runAllTasksAtTickStart
this.runAllTasksAtTickStart(); // Paper - improve tick loop
this.tickServer(sprinting ? () -> false : this::haveTime);
@@ -24079,7 +24079,7 @@ index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda7
this.tickFrame.end();
this.recordEndOfTick(); // Paper - improve tick loop
profiler.pop();
@@ -2610,6 +2729,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2600,6 +2719,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -27004,7 +27004,7 @@ index de75dda2e6f17f0ae6e7f6a5620fd3eac9d389e5..6774d12d04fa848624fb8bf7a3c2b8bc
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 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3bb953056 100644
index e17e391862a6b888ed0d0a3c4ab136d9ac47375d..ed52a5e099b74b71ae05f21a4ad0d01dee9c2a60 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -182,7 +182,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
@@ -27420,7 +27420,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
}
@@ -607,8 +865,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -615,8 +873,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push("checkDespawn");
entity.checkDespawn();
profiler.pop();
@@ -27430,7 +27430,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
Entity vehicle = entity.getVehicle();
if (vehicle != null) {
if (!vehicle.isRemoved() && vehicle.hasPassenger(entity)) {
@@ -632,7 +889,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -640,7 +897,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
profiler.push("entityManagement");
@@ -27439,7 +27439,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
profiler.pop();
profiler.push("debugSynchronizers");
if (this.debugSynchronizers.hasAnySubscriberFor(DebugSubscriptions.NEIGHBOR_UPDATES)) {
@@ -649,7 +906,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -657,7 +914,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public boolean shouldTickBlocksAt(final long chunkPos) {
@@ -27451,7 +27451,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
protected void tickTime() {
@@ -673,7 +933,60 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -681,7 +941,60 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
}
@@ -27512,7 +27512,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
ChunkPos chunkPos = chunk.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
@@ -682,7 +995,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -690,7 +1003,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++) {
@@ -27521,7 +27521,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
this.tickPrecipitation(this.getBlockRandomPos(minX, 0, minZ, 15));
}
}
@@ -690,31 +1003,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -698,31 +1011,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.popPush("tickBlocks");
if (tickSpeed > 0) {
@@ -27554,7 +27554,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
profiler.pop();
@@ -1037,6 +1326,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1045,6 +1334,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (fluidState.is(type)) {
fluidState.tick(this, pos, blockState);
}
@@ -27567,7 +27567,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
private void tickBlock(final BlockPos pos, final Block type) {
@@ -1044,6 +1339,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1052,6 +1347,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (state.is(type)) {
state.tick(this, pos, this.random);
}
@@ -27580,7 +27580,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
// Paper start - log detailed entity tick information
@@ -1128,6 +1429,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1136,6 +1437,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public void save(final @Nullable ProgressListener progressListener, final boolean flush, final boolean noSave) {
@@ -27592,7 +27592,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
ServerChunkCache chunkSource = this.getChunkSource();
if (!noSave) {
new org.bukkit.event.world.WorldSaveEvent(this.getWorld()).callEvent(); // CraftBukkit
@@ -1140,13 +1446,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1148,13 +1454,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
progressListener.progressStage(Component.translatable("menu.savingChunks"));
}
@@ -27616,7 +27616,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
private void saveLevelData(final boolean sync) {
@@ -1267,7 +1578,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1275,7 +1586,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.removePlayerImmediately((ServerPlayer)existing, Entity.RemovalReason.DISCARDED);
}
@@ -27625,7 +27625,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
// CraftBukkit start
@@ -1298,7 +1609,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1306,7 +1617,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// CraftBukkit end
@@ -27634,7 +27634,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
}
@@ -1309,7 +1620,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1317,7 +1628,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
@@ -27643,7 +27643,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
return false;
} else {
this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
@@ -2102,7 +2413,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2111,7 +2422,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
}
@@ -27652,7 +27652,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
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()));
@@ -2120,13 +2431,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2129,13 +2440,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Path chunks = rootDir.resolve("chunks.csv");
try (Writer output = Files.newBufferedWriter(chunks)) {
@@ -27668,7 +27668,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
Path entities = rootDir.resolve("entities.csv");
@@ -2221,8 +2532,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2230,8 +2541,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(),
@@ -27679,7 +27679,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
this.blockEntityTickers.size(),
getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
this.getBlockTicks().count(),
@@ -2255,15 +2566,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2264,15 +2575,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public LevelEntityGetter<Entity> getEntities() {
org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -27708,7 +27708,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
public void startTickingChunk(final LevelChunk levelChunk) {
@@ -2280,8 +2601,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2289,8 +2610,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();
@@ -27719,7 +27719,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
for (ChunkPos chunk : chunks) {
if (!this.areEntitiesLoaded(chunk.pack())) {
@@ -2302,28 +2623,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2311,28 +2632,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public void close() throws IOException {
super.close();
@@ -27764,7 +27764,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
public boolean anyPlayerCloseEnoughForSpawning(final BlockPos pos) {
@@ -2340,7 +2671,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2349,7 +2680,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public boolean canSpawnEntitiesInChunk(final ChunkPos pos) {
@@ -27776,7 +27776,7 @@ index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3
}
@Override
@@ -2388,7 +2722,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2397,7 +2731,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public CrashReportCategory fillReportDetails(final CrashReport report) {
CrashReportCategory category = super.fillReportDetails(report);
WeatherData weatherData = this.getWeatherData();
@@ -28893,7 +28893,7 @@ index 1ceb7e4a3abd4d9de5133d182d3267d2164918f6..eb2fa32cff6824c14f865c8731df7d08
+ // Paper end - block counting
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4e81a3d3a 100644
index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e3980bced 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -167,7 +167,7 @@ public abstract class Entity
@@ -29366,7 +29366,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4
}
public InteractionResult interact(final Player player, final InteractionHand hand, final Vec3 location) {
@@ -4363,15 +4600,17 @@ public abstract class Entity
@@ -4364,15 +4601,17 @@ public abstract class Entity
}
public Iterable<Entity> getIndirectPassengers() {
@@ -29392,7 +29392,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4
}
public int countPlayerPassengers() {
@@ -4682,6 +4921,15 @@ public abstract class Entity
@@ -4683,6 +4922,15 @@ public abstract class Entity
}
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -29408,7 +29408,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4
if (!checkPosition(this, x, y, z)) {
return;
}
@@ -4831,6 +5079,12 @@ public abstract class Entity
@@ -4832,6 +5080,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
@@ -29421,7 +29421,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4
org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit
final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
if (this.removalReason == null) {
@@ -4841,7 +5095,7 @@ public abstract class Entity
@@ -4842,7 +5096,7 @@ public abstract class Entity
this.stopRiding();
}
@@ -29430,7 +29430,7 @@ index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4
this.levelCallback.onRemove(reason);
this.onRemoval(reason);
// Paper start - Folia schedulers
@@ -4875,7 +5129,7 @@ public abstract class Entity
@@ -4876,7 +5130,7 @@ public abstract class Entity
public boolean shouldBeSaved() {
return (this.removalReason == null || this.removalReason.shouldSave())
&& !this.isPassenger()
@@ -30053,7 +30053,7 @@ index e07a7bda45146686a6ac2d20507df9fc8630514f..f84e651e2a34e76a0a71993332e4be1b
// Paper start - Affects Spawning API
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 30183d8752aa397a5c1fc554da49f595be286534..5566f449fb79b04822e533df4fd6de42116ffd14 100644
index 506efca7cec0e268f03c6c070d69afbc853aaf03..995b51d859316c6128b30d930e58dd47224514b2 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -87,6 +87,7 @@ import net.minecraft.world.level.storage.LevelData;
@@ -30941,7 +30941,7 @@ index 30183d8752aa397a5c1fc554da49f595be286534..5566f449fb79b04822e533df4fd6de42
public <T extends Entity> boolean hasEntities(final EntityTypeTest<Entity, T> type, final AABB bb, final Predicate<? super T> selector) {
Profiler.get().incrementCounter("hasEntities");
@@ -1465,13 +2169,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@@ -1469,13 +2173,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// Paper start - allow patching this logic
public final int getEntityCount() {
@@ -33593,7 +33593,7 @@ index cc5d366f7b485f98901572b1aaf57d8b650d521f..7adb4be57bd9fdc9980b5b7e91e9d9ca
private final Long2ObjectLinkedOpenHashMap<CompletableFuture<BitSet>> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>();
private static final int REGION_CACHE_SIZE = 1024;
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index 9409cc5c8dbd12ce6bdf34843da70170ebce41bd..c9acbdce97cb605a45124bb9d3944070396adc03 100644
index 728ec122b7af090427cc7511a168336d539835a1..5149635679ec03d881e63effbd79fc193d7e52c8 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -23,7 +23,7 @@ import net.minecraft.world.level.ChunkPos;
@@ -33625,7 +33625,7 @@ index 9409cc5c8dbd12ce6bdf34843da70170ebce41bd..c9acbdce97cb605a45124bb9d3944070
+ // Paper end - rewrite chunk system
+
public RegionFile(final RegionStorageInfo info, final Path path, final Path externalFileDir, final boolean sync) throws IOException {
this(info, path, externalFileDir, RegionFileVersion.getSelected(), sync);
this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
}
@@ -201,6 +216,15 @@ public class RegionFile implements AutoCloseable {
}
@@ -33491,7 +33491,7 @@ index 109d7b563ee799d998a5773c5a36e61ca3607d46..747755082639436731989d3ee4b3c30a
return structureTemplate.save(new CompoundTag());
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index e11862c7e0617acfb36212cfe349bdade3a5164b..7ae2d491a3b2f379df99ccc649b8bb8cbf2304b9 100644
index 1b064f00c33712c3d2d85ed8520f1eda02abfbf9..65a971379f35c4b1b519b93787f690d891e29523 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -299,6 +299,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -354,10 +354,10 @@ index 0000000000000000000000000000000000000000..ce6b57eeeeb1bd652f4bb53c19dcfbc0
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 3cd2fcc38f3989bb5124ba5bc2f4597103e7a111..6d847ff3646b27fe9bc75e792d813a7f611ab3e5 100644
index ed52a5e099b74b71ae05f21a4ad0d01dee9c2a60..9f43d29aa4fecb03d6ef49e71e4de846dca187de 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -857,6 +857,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -865,6 +865,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.pop();
}
@@ -365,7 +365,7 @@ index 3cd2fcc38f3989bb5124ba5bc2f4597103e7a111..6d847ff3646b27fe9bc75e792d813a7f
this.entityTickList
.forEach(
entity -> {
@@ -1373,12 +1374,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1381,12 +1382,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
entity.totalEntityAge++; // Paper - age-like counter for all entities
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickNonPassenger");
@@ -382,7 +382,7 @@ index 3cd2fcc38f3989bb5124ba5bc2f4597103e7a111..6d847ff3646b27fe9bc75e792d813a7f
}
// Paper start - log detailed entity tick information
} finally {
@@ -1389,7 +1393,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1397,7 +1401,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - log detailed entity tick information
}
@@ -391,7 +391,7 @@ index 3cd2fcc38f3989bb5124ba5bc2f4597103e7a111..6d847ff3646b27fe9bc75e792d813a7f
if (entity.isRemoved() || entity.getVehicle() != vehicle) {
entity.stopRiding();
} else if (entity instanceof Player || this.entityTickList.contains(entity)) {
@@ -1399,12 +1403,21 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1407,12 +1411,21 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
ProfilerFiller profiler = Profiler.get();
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickPassenger");
@@ -462,7 +462,7 @@ index 0df8332933203a904bd9ef9efb3c9bce21e65441..1a502cbd8acea9420fa6dd8d716018b5
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index e087d5596979044fe7fbcf7f2cccdae4e81a3d3a..fea6c3b48c4eb162fbdc099fa775bc3161a4dd4e 100644
index f7b4da570ee28b7128b4d2cd1c67b89e3980bced..93d3fb313d538cc1067b1db010458169a1320122 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -382,6 +382,15 @@ public abstract class Entity
@@ -540,10 +540,10 @@ index c4da41f3b5ba6d8492d3776654b1ce62a5015895..6a0ebbdfcc0bc07915574c1cf87463e8
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
index c7c53ef16e104ebc48aeb8719783fb4c45d39fc2..5bbb5cbb51953399c7bce547cd6968eb3c6bd130 100644
index c7c53ef16e104ebc48aeb8719783fb4c45d39fc2..90eaa775dc134041647618c3b965334a9bb9fd3b 100644
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -211,6 +211,20 @@ public abstract class Mob extends LivingEntity implements Targeting, EquipmentUs
@@ -211,6 +211,19 @@ public abstract class Mob extends LivingEntity implements Targeting, EquipmentUs
return this.lookControl;
}
@@ -551,7 +551,6 @@ index c7c53ef16e104ebc48aeb8719783fb4c45d39fc2..5bbb5cbb51953399c7bce547cd6968eb
+ @Override
+ public void inactiveTick() {
+ super.inactiveTick();
+ if (!this.aware) return; // Paper - Do not tick AI for inactive unaware mobs
+ if (this.goalSelector.inactiveTick()) {
+ this.goalSelector.tick();
+ }
@@ -674,7 +673,7 @@ index c482c3348267ca2b88109e34291aa860e0cde916..b11b59d80e37d272770ab51a63028fdd
public void tick() {
if (this.getItem().isEmpty()) {
diff --git a/net/minecraft/world/entity/npc/villager/Villager.java b/net/minecraft/world/entity/npc/villager/Villager.java
index a80e6a23cf29cb44943339101f15565dbc19af1f..26108ac0543c7b4195f149c07d8348b5b15b8bdb 100644
index a80e6a23cf29cb44943339101f15565dbc19af1f..f481fd661440f77c98940cbde4a8b95818e4a22c 100644
--- a/net/minecraft/world/entity/npc/villager/Villager.java
+++ b/net/minecraft/world/entity/npc/villager/Villager.java
@@ -244,11 +244,35 @@ public class Villager extends AbstractVillager implements VillagerDataHolder, Re
@@ -688,7 +687,7 @@ index a80e6a23cf29cb44943339101f15565dbc19af1f..26108ac0543c7b4195f149c07d8348b5
+ if (this.getUnhappyCounter() > 0) {
+ this.setUnhappyCounter(this.getUnhappyCounter() - 1);
+ }
+ if (this.aware && this.isEffectiveAi()) { // Paper - Do not tick AI for inactive unaware mobs
+ if (this.isEffectiveAi()) {
+ if (this.level().spigotConfig.tickInactiveVillagers) {
+ this.customServerAiStep(this.level().getMinecraftWorld());
+ } else {
@@ -817,7 +816,7 @@ index 3a590d4dc980a2912e9cc043d8c8db4cf9d60803..06ab7c48b18c03af494ab10fc2b584ce
+
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 0958fe0438c3e934b885acf78a0f8003ef8bb694..31c7223d2d01573c24d4ad83737fa2c2752db028 100644
index 995b51d859316c6128b30d930e58dd47224514b2..329aef0e55664fbdd98c84d4d7cee74cd633e9c3 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -155,6 +155,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -9,7 +9,7 @@ creating too large of a packet to sed.
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
index 6ecda939755534ce0f35680be16a04c0a0e0a312..488d5654f5414c9950f19e86a791507366366e01 100644
index 2bfc4cb47fc51345fc733b6ab3b19ace4f8bc171..dad2b89355b05900251955d015451fe195bfdb49 100644
--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
@@ -32,6 +32,14 @@ public class ClientboundLevelChunkPacketData {
@@ -2326,7 +2326,7 @@ index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 26c05173e7fb2bf055afda1717af0dd2e997118d..ffe3ae41dd3d8c4141f81edf0b92001db957ebac 100644
index 9f43d29aa4fecb03d6ef49e71e4de846dca187de..2e76ab96c66fd39b5e6619c3dbc75559d5c9b04a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -232,6 +232,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2337,7 +2337,7 @@ index 26c05173e7fb2bf055afda1717af0dd2e997118d..ffe3ae41dd3d8c4141f81edf0b92001d
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
@@ -2765,6 +2766,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2774,6 +2775,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return this.debugSynchronizers;
}
@@ -2352,10 +2352,10 @@ index 26c05173e7fb2bf055afda1717af0dd2e997118d..ffe3ae41dd3d8c4141f81edf0b92001d
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 31c7223d2d01573c24d4ad83737fa2c2752db028..8df070a7135411806d42ea692b566c4d8389a47b 100644
index 329aef0e55664fbdd98c84d4d7cee74cd633e9c3..b9e07d5bd36993c5ac83b843866fabbea71c4281 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -2152,6 +2152,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -2156,6 +2156,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return this.palettedContainerFactory;
}
@@ -55,7 +55,7 @@ index a6f9435a8e2a6c8cc05bd3873cb6fc0ec98973b8..d44fd9eea0ccb09c894d6431ede8a4c2
this.used.set(position, position + size);
}
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index 5e77906d77da82b23fbd6e8d16fbd0847c83f4da..e077797cefffcb9db627cab12c7d88245e5baa69 100644
index fc712f9dbc893a6dee8ccecc54d0af07659dee62..b4a59b855b45018853e8122a5829a48ebd220a5f 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -47,6 +47,363 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
@@ -760,7 +760,7 @@ index 5fa64ff30b8252f139ca7f77c14352c384c8c0e5..eae4963b6d18c5f92871477b93f81c2c
return var4;
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
index ea229f63f81c5638ded74c28e1e13d8948f2e5e0..d991fccfc3d2cc51611f802a6d40429024e21d9a 100644
index 97d3da90c270a06d85e70d261658f71d97ae4e3c..11b65c688144e7ef5b7cba603949843a11ccd084 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -21,7 +21,7 @@ import org.slf4j.Logger;
@@ -771,7 +771,7 @@ index ea229f63f81c5638ded74c28e1e13d8948f2e5e0..d991fccfc3d2cc51611f802a6d404290
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
public static final RegionFileVersion VERSION_GZIP = register(
new RegionFileVersion(1, "gzip", in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out))) // Paper - Enable for Configurable region compression format
new RegionFileVersion(1, null, in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out)))
diff --git a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java b/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
index 590bd1ca60956e5c5be58481f97401e55f9e1aeb..4e0c9fa1eecfa9f21285b7911e25830bbcbc8d86 100644
--- a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
@@ -791,7 +791,7 @@ index 590bd1ca60956e5c5be58481f97401e55f9e1aeb..4e0c9fa1eecfa9f21285b7911e25830b
return new RegionStorageInfo(this.level, this.dimension, this.type + suffix);
}
diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
index 1e0afa9590de7fe492cf29aded96be3db498c51e..df0bb06176ad5cce3ed8785d22d68b2dcbf5a8ca 100644
index 22a83f312d357176a5489e5b6b71c3ef3e737df7..74158a5d7ea8058c99ab215265987746aa043fc7 100644
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
@@ -120,6 +120,18 @@ public record SerializableChunkData(
@@ -823,7 +823,7 @@ index 1e0afa9590de7fe492cf29aded96be3db498c51e..df0bb06176ad5cce3ed8785d22d68b2d
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
tag.storeNullable("blending_data", BlendingData.Packed.CODEC, this.blendingData);
diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
index 6e55cb304fd9a5a01bbe7666dddb882e684cdcf1..6d954064f409ddd2cb7cfd0d0c428ac186f5ca4d 100644
index 884fa9545a93008fa92863c845aef8f18d88e25a..15da3686bb1b5fddd388a88a7b300aa221c83f58 100644
--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
@@ -34,6 +34,7 @@ public class SimpleRegionStorage implements ca.spottedleaf.moonrise.patches.chun
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006ac7ab27c7 100644
index 9a014a1a198293c9ce02354d8128cb19666055b8..c6691f02a1b2c1540b959fcc07eb8390906541ec 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -968,7 +968,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -958,7 +958,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force, final boolean close) {
// Paper end - add close param
@@ -17,7 +17,7 @@ index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006a
boolean result = false;
for (ServerLevel level : this.getAllLevels()) {
@@ -980,13 +980,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -970,13 +970,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
result = true;
}
@@ -32,7 +32,7 @@ index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006a
if (flush) {
for (ServerLevel level : this.getAllLevels()) {
@@ -1000,11 +994,25 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -990,11 +984,25 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return result;
}
@@ -59,7 +59,7 @@ index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006a
boolean result = this.saveAllChunks(silent, flush, force);
this.warnOnLowDiskSpace();
var5 = result;
@@ -1651,9 +1659,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1641,9 +1649,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
this.ticksUntilAutosave--;
@@ -94,10 +94,10 @@ index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006a
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 ffe3ae41dd3d8c4141f81edf0b92001db957ebac..98461e8468a7eeaef2544ee907ff64de1fd42a49 100644
index 2e76ab96c66fd39b5e6619c3dbc75559d5c9b04a..b4a3c61a370939db94d57a911cfa5cfa1008a93a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1441,6 +1441,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1449,6 +1449,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)));
}
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 0b86c393b041607278c1b712a62ec9ae3640f4de..80162ffef5d54d00663dcb58a087884ec8a3eaaa 100644
index 05a700ffc844b41f4fa129226f78dc6c52520f32..1d93a864c5fc040b5ec7c48b4db0bd54cd6255ff 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -634,6 +634,7 @@ public class ServerGamePacketListenerImpl
@@ -13,10 +13,10 @@ average over the last 5 seconds of keepalive transactions.
diff --git a/io/papermc/paper/util/KeepAlive.java b/io/papermc/paper/util/KeepAlive.java
new file mode 100644
index 0000000000000000000000000000000000000000..66d6af2833d76af28fbeb2ce70c194a559572460
index 0000000000000000000000000000000000000000..4a2520f554c2ee74faf86d7c93baccf0f391a6b3
--- /dev/null
+++ b/io/papermc/paper/util/KeepAlive.java
@@ -0,0 +1,84 @@
@@ -0,0 +1,67 @@
+package io.papermc.paper.util;
+
+public class KeepAlive {
@@ -34,14 +34,6 @@ index 0000000000000000000000000000000000000000..66d6af2833d76af28fbeb2ce70c194a5
+ public final PingCalculator pingCalculator1m = new PingCalculator(java.util.concurrent.TimeUnit.MINUTES.toNanos(1L));
+ public final PingCalculator pingCalculator5s = new PingCalculator(java.util.concurrent.TimeUnit.SECONDS.toNanos(5L));
+
+ public KeepAlive copyForListenerHandoff() {
+ KeepAlive copy = new KeepAlive();
+ copy.lastKeepAliveTx = this.lastKeepAliveTx;
+ copy.pingCalculator1m.copyFrom(this.pingCalculator1m);
+ copy.pingCalculator5s.copyFrom(this.pingCalculator5s);
+ return copy;
+ }
+
+ public static final class PingCalculator {
+
+ private final long intervalNS;
@@ -55,15 +47,6 @@ index 0000000000000000000000000000000000000000..66d6af2833d76af28fbeb2ce70c194a5
+ this.intervalNS = intervalNS;
+ }
+
+ private void copyFrom(PingCalculator other) {
+ for (KeepAliveResponse response : other.responses) {
+ this.responses.add(response);
+ }
+ this.timeSumNS = other.timeSumNS;
+ this.timeSumCount = other.timeSumCount;
+ this.lastAverageNS = other.lastAverageNS;
+ }
+
+ public void update(KeepAliveResponse response) {
+ long currTime = response.txTimeNS;
+
@@ -117,7 +100,7 @@ index d87a00ee3cd04ce25dbe46ae6b386b7f0799102c..ace440c2b82a6368aa27a85ff433ba2c
}
}
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index 079ab378920c0e52ef4e42ef20b37bd389f40870..dc9020f210c21a626c948d013cc6c972cbc093f3 100644
index f54d99c59bbca7fb5e0615f8fee9c3f6fbffa18a..d9b6f3826e2126d339e926afed782f6d0e890508 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -39,12 +39,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -240,19 +223,11 @@ index 079ab378920c0e52ef4e42ef20b37bd389f40870..dc9020f210c21a626c948d013cc6c972
}
}
@@ -424,6 +457,14 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -424,6 +457,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
}
protected CommonListenerCookie createCookie(final ClientInformation clientInformation) {
- return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand, this.pluginMessagerChannels); // Paper
+ return new CommonListenerCookie(
+ this.playerProfile(),
+ this.latency,
+ clientInformation,
+ this.transferred,
+ this.playerBrand,
+ this.pluginMessagerChannels,
+ this.keepAlive.copyForListenerHandoff()
+ ); // Paper - listener handoff should reset pending keepalive expectations
+ return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand, this.pluginMessagerChannels, this.keepAlive); // Paper // Paper
}
}
@@ -20,10 +20,10 @@ index 2bc436cdf5180a7943c45fabb9fbbedae6f7db56..f312a7f5b1b2a777ab36b94ce7cbf387
@Override
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 8c4b40362f77cdea6e57315a6639006ac7ab27c7..a5217deb236ee922d020a7a6a03141a5f67acd77 100644
index a3edf217dd2ffce19dc1111402975ba12bb86bad..2b6717833b56c3e33f3ac656a376dd5c841dc1bc 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1794,32 +1794,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1784,32 +1784,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -66,10 +66,10 @@ index 8c4b40362f77cdea6e57315a6639006ac7ab27c7..a5217deb236ee922d020a7a6a03141a5
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 fea6c3b48c4eb162fbdc099fa775bc3161a4dd4e..82f6582a92f17bdeb3218c4c1fcac86981fd9c37 100644
index 93d3fb313d538cc1067b1db010458169a1320122..9cd46a8619e575a865f4d00ce71d249c9b028b2f 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5146,6 +5146,11 @@ public abstract class Entity
@@ -5147,6 +5147,11 @@ public abstract class Entity
this.getBukkitEntity().taskScheduler.retire();
}
// Paper end - Folia schedulers
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
+ }
+}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index a5217deb236ee922d020a7a6a03141a5f67acd77..4fdf7354bc01789cf7480b14c2fc8fe75b748563 100644
index fbd70bcdc2efb42505fd5b3e5a6f703782c4060d..000943733f07cef2e508f78aadf5d28dec70e90c 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1846,6 +1846,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1836,6 +1836,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
level.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
level.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
level.updateLagCompensationTick(); // Paper - lag compensation
@@ -155,7 +155,7 @@ index a94e6ca1d396b6b0781de5d550c4a804274ee003..a1018e9ca9dfb978d6e6633577abd7d5
private ClientboundLevelChunkWithLightPacket(final RegistryFriendlyByteBuf input) {
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 78021db10860396fef4eb2e01926fbad123916fa..3f02da2014ed8e4169f4bb819b4d9e61154b7b06 100644
index b4a3c61a370939db94d57a911cfa5cfa1008a93a..be0e0b3a70bde1e2217ba4d3f90031e243d86941 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -630,7 +630,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -209,7 +209,7 @@ index b69bcaa1e27a4fbe9a9568f3c9e1843167abe1f5..9218322801c41657c00928ef90b0de99
}
// Paper end - Send empty chunk
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 06451a21c4bfbcd95063f96246013d63f65af8a3..e34e901dce06c6bc6060418593010dcfb38e9396 100644
index 630aec19b88a519fa4ec382fb2f5e9aebda59208..f765bc3d805de16909b0407da633208bb0dd9edc 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -145,6 +145,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -1,119 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 20 Apr 2026 02:56:22 -0700
Subject: [PATCH] Delay open/close callbacks for chests
The logic for chests may invoke block updates, which may not
be safe to perform. For example, inventory closing due to
player logout (which restricts chunk loading due to being
inside an entity status callback) or during shutdown (which has
chunk loading restrictions due to the chunk system being halted).
diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
index 9eb6cb86ff70f04863cae3def1006f82bcb4fa6b..da5fdf77dd26d97c8b92bac9a6af8d922c31eb9c 100644
--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
@@ -66,6 +66,13 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
Objects.requireNonNull(BarrelBlockEntity.this);
}
+ // Paper start - delay open/close callbacks
+ @Override
+ public boolean delayCallbacks() {
+ return true;
+ }
+ // Paper end - delay open/close callbacks
+
@Override
protected void onOpen(final Level level, final BlockPos pos, final BlockState state) {
BarrelBlockEntity.this.playSound(state, SoundEvents.BARREL_OPEN);
diff --git a/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
index 81a293cc13653a9a0336cb25d75273969fee4d16..8ebcce4c4971a11d367fb39e9c7fd280f63594e3 100644
--- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
@@ -35,6 +35,13 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
Objects.requireNonNull(ChestBlockEntity.this);
}
+ // Paper start - delay open/close callbacks
+ @Override
+ public boolean delayCallbacks() {
+ return true;
+ }
+ // Paper end - delay open/close callbacks
+
@Override
protected void onOpen(final Level level, final BlockPos pos, final BlockState blockState) {
if (blockState.getBlock() instanceof ChestBlock chestBlock) {
diff --git a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
index 9af046cbe2c5024040057118a0e9ecd539815781..aa8b384b05738139fa3492b3c09084cda09448ce 100644
--- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
@@ -37,11 +37,23 @@ public abstract class ContainerOpenersCounter {
protected abstract void openerCountChanged(final Level level, final BlockPos pos, final BlockState blockState, int previous, int current);
+ // Paper start - delay open/close callbacks
+ public boolean delayCallbacks() {
+ return false;
+ }
+ // Paper end - delay open/close callbacks
+
public abstract boolean isOwnContainer(final Player player);
public void incrementOpeners(
final LivingEntity entity, final Level level, final BlockPos pos, final BlockState blockState, final double maxInteractionRange
) {
+ // Paper start - delay open/close callbacks
+ if (this.delayCallbacks()) {
+ scheduleRecheck(level, pos, blockState, 1);
+ return;
+ }
+ // Paper end - delay open/close callbacks
int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
int previous = this.openCount++;
@@ -66,6 +78,12 @@ public abstract class ContainerOpenersCounter {
}
public void decrementOpeners(final LivingEntity entity, final Level level, final BlockPos pos, final BlockState blockState) {
+ // Paper start - delay open/close callbacks
+ if (this.delayCallbacks()) {
+ scheduleRecheck(level, pos, blockState, 1);
+ return;
+ }
+ // Paper end - delay open/close callbacks
int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative
int previous = this.openCount--;
@@ -151,6 +169,11 @@ public abstract class ContainerOpenersCounter {
}
private static void scheduleRecheck(final Level level, final BlockPos blockPos, final BlockState blockState) {
- level.scheduleTick(blockPos, blockState.getBlock(), 5);
+ // Paper start - delay open/close callbacks
+ scheduleRecheck(level, blockPos, blockState, 5);
+ }
+ private static void scheduleRecheck(final Level level, final BlockPos blockPos, final BlockState blockState, final int delay) {
+ level.scheduleTick(blockPos, blockState.getBlock(), delay);
+ // Paper end - delay open/close callbacks
}
}
diff --git a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
index eeac644f60760570244380d3c454fd33e37b5a7f..751625124a87de63d933d9c7809e46361e3136a5 100644
--- a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
@@ -18,6 +18,13 @@ public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity
Objects.requireNonNull(EnderChestBlockEntity.this);
}
+ // Paper start - delay open/close callbacks
+ @Override
+ public boolean delayCallbacks() {
+ return true;
+ }
+ // Paper end - delay open/close callbacks
+
@Override
protected void onOpen(final Level level, final BlockPos pos, final BlockState blockState) {
level.playSound(
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,11 @@
--- a/net/minecraft/gametest/framework/GameTestHelper.java
+++ b/net/minecraft/gametest/framework/GameTestHelper.java
@@ -929,7 +_,7 @@
public void setTime(final long ticks) {
ServerLevel level = this.getLevel();
- Holder<WorldClock> clock = level.dimensionType().defaultClock().orElseThrow();
+ Holder<WorldClock> clock = level.getClock().orElseThrow();
level.clockManager().setTotalTicks(clock, ticks);
}
@@ -14,9 +14,9 @@
+ // Paper start - Validate Identifier
+ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short)
+ final int length = namespace.length() + path.length() + 1;
+ if (length > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(length) > 2 * Short.MAX_VALUE + 1) {
+ throw new IdentifierException("Identifier too long: " + length);
+ final String id = namespace + ":" + path;
+ if (id.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(id) > 2 * Short.MAX_VALUE + 1) {
+ throw new IdentifierException("Identifier too long: " + id);
+ }
+ // Paper end - Validate Identifier
this.namespace = namespace;
@@ -12,7 +12,7 @@
OptionParser parser = new OptionParser();
OptionSpec<Void> nogui = parser.accepts("nogui");
OptionSpec<Void> initSettings = parser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
@@ -89,39 +_,95 @@
@@ -89,39 +_,96 @@
parser.printHelpOn(System.err);
return;
}
@@ -72,14 +72,15 @@
}
- if (!eula.hasAgreedToEULA()) {
+ // Paper start - eula system property
+ // Spigot start
+ boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree");
+ if (eulaAgreed) {
+ LOGGER.warn("You have used the Paper command line EULA agreement flag.");
+ LOGGER.warn("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
+ LOGGER.warn("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
+ } else if (!eula.hasAgreedToEULA()) {
+ // Paper end - eula system property
+ LOGGER.error("You have used the Spigot command line EULA agreement flag.");
+ LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
+ LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
+ }
+ if (!eula.hasAgreedToEULA() && !eulaAgreed) {
+ // Spigot end
LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
return;
}
@@ -300,7 +300,7 @@
if (profiledDuration != null) {
profiledDuration.finish(true);
}
@@ -422,33 +_,150 @@
@@ -422,33 +_,140 @@
}
}
@@ -430,9 +430,9 @@
+ );
+ }
+ this.addLevel(serverLevel);
+ this.initWorld(serverLevel, null);
+ this.initWorld(serverLevel);
+ }
+ public void initWorld(ServerLevel overworld, org.bukkit.@Nullable WorldCreator worldCreator) {
+ public void initWorld(ServerLevel overworld) {
+ final net.minecraft.world.level.storage.ServerLevelData levelData = overworld.serverLevelData;
+ final WorldOptions worldOptions = overworld.worldGenSettings.options();
+ final boolean isDebug = this.worldData.isDebugWorld();
@@ -447,17 +447,7 @@
if (!levelData.isInitialized()) {
try {
- setInitialSpawn(overworld, levelData, worldOptions.generateBonusChest(), isDebug, this.levelLoadListener);
+ // Paper start - Allow direct setting spawn location
+ if (worldCreator != null && worldCreator.forcedSpawnPosition() != null) {
+ levelData.setSpawn(LevelData.RespawnData.of(overworld.dimension(),
+ io.papermc.paper.util.MCUtil.toBlockPos(worldCreator.forcedSpawnPosition()),
+ Objects.requireNonNullElse(worldCreator.forcedSpawnYaw(), LevelData.RespawnData.DEFAULT.yaw()),
+ Objects.requireNonNullElse(worldCreator.forcedSpawnPitch(), LevelData.RespawnData.DEFAULT.pitch())
+ ));
+ } else {
+ setInitialSpawn(overworld, levelData, worldOptions.generateBonusChest(), isDebug, overworld.levelLoadListener); // Paper - per world level load listener
+ }
+ // Paper end - Allow direct setting spawn location
+ setInitialSpawn(overworld, levelData, worldOptions.generateBonusChest(), isDebug, overworld.levelLoadListener); // Paper - per world level load listener
levelData.setInitialized(true);
if (isDebug) {
- this.setupDebugLevel(this.worldData);
@@ -503,7 +493,7 @@
+
+ if (spawn != null) {
+ if (spawn.getWorld() != level.getWorld()) {
+ throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + (spawn.getWorld() != null ? spawn.getWorld().key() : null) + ")");
+ throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + io.papermc.paper.util.MCUtil.getLevelName(level) + ")");
+ } else {
+ levelData.setSpawn(
+ net.minecraft.world.level.storage.LevelData.RespawnData.of(
@@ -1544,7 +1534,7 @@
player.connection.send(new ClientboundEntityEventPacket(player, event));
}
} else if (rule == GameRules.LIMITED_CRAFTING || rule == GameRules.IMMEDIATE_RESPAWN) {
@@ -2131,19 +_,21 @@
@@ -2131,18 +_,19 @@
? ClientboundGameEventPacket.LIMITED_CRAFTING
: ClientboundGameEventPacket.IMMEDIATE_RESPAWN;
ClientboundGameEventPacket packet = new ClientboundGameEventPacket(eventType, (Boolean)value ? 1.0F : 0.0F);
@@ -1566,10 +1556,8 @@
- this.updateMobSpawningFlags();
+ level.setSpawnSettings(level.isSpawningMonsters()); // Paper - per-world game rules
} else if (rule == GameRules.ADVANCE_TIME) {
+ // TODO - snapshot - time handling
this.getPlayerList().broadcastAll(this.clockManager().createFullSyncPacket());
}
}
@@ -2157,12 +_,14 @@
return this.savedDataStorage;
}
@@ -5,7 +5,7 @@
list.deop(player);
count++;
- source.sendSuccess(() -> Component.translatable("commands.deop.success", players.iterator().next().name()), true);
+ source.sendSuccess(() -> Component.translatable("commands.deop.success", player.name()), true); // Paper - fixes MC-307513
+ source.sendSuccess(() -> Component.translatable("commands.deop.success", player.name()), true); // Paper - fixes MC-253721
}
}
@@ -1,67 +1,56 @@
--- a/net/minecraft/server/commands/TimeCommand.java
+++ b/net/minecraft/server/commands/TimeCommand.java
@@ -170,23 +_,61 @@
@@ -169,16 +_,24 @@
}
}
+ // Paper start - time skip event
private static int setTotalTicks(final CommandSourceStack source, final Holder<WorldClock> clock, final int totalTicks) {
+ // Paper start
+ // TODO - snapshot - 26.1
+ for (net.minecraft.server.level.ServerLevel level : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) {
+ long currentTime = level.clockManager().getTotalTicks(clock);
+ org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(level.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, totalTicks - currentTime);
+ if (event.callEvent()) {
+ level.clockManager().setTotalTicks(clock, currentTime + event.getSkipAmount());
+ }
+ }
+ // Paper end
ServerClockManager clockManager = source.getServer().clockManager();
- clockManager.setTotalTicks(clock, totalTicks);
+ // clockManager.setTotalTicks(clock, totalTicks);
+ long currentTotalTicks = clockManager.getTotalTicks(clock);
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callTimeSkipEvent(clock, totalTicks - currentTotalTicks);
+ if (event.callEvent()) {
+ clockManager.setTotalTicks(clock, currentTotalTicks + event.getSkipAmount());
+ }
source.sendSuccess(() -> Component.translatable("commands.time.set.absolute", clock.getRegisteredName(), totalTicks), true);
return totalTicks;
}
private static int addTime(final CommandSourceStack source, final Holder<WorldClock> clock, final int time) {
+ // Paper start
+ // TODO - snapshot - 26.1
+ for (net.minecraft.server.level.ServerLevel level : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) {
+ org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(level.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, time);;
+ if (event.callEvent()) {
+ level.clockManager().addTicks(clock, event.getSkipAmount());
+ }
+ }
+ // Paper end
ServerClockManager clockManager = source.getServer().clockManager();
- clockManager.addTicks(clock, time);
+ // Paper - move up
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callTimeSkipEvent(clock, time);
+ if (event.callEvent()) {
+ clockManager.addTicks(clock, event.getSkipAmount());
+ }
long totalTicks = clockManager.getTotalTicks(clock);
source.sendSuccess(() -> Component.translatable("commands.time.set.absolute", clock.getRegisteredName(), totalTicks), true);
return wrapTime(totalTicks);
}
@@ -186,9 +_,16 @@
private static int setTimeToTimeMarker(final CommandSourceStack source, final Holder<WorldClock> clock, final ResourceKey<ClockTimeMarker> timeMarkerId) throws CommandSyntaxException {
+ // Paper start
+ // TODO - snapshot - 26.1
+ boolean foundMarker = false;
+ for (net.minecraft.server.level.ServerLevel level : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) {
+ java.util.OptionalLong targetTime = level.clockManager().getTotalTicksToTimeMarker(clock, timeMarkerId);
+ if (targetTime.isEmpty()) {
+ continue;
+ }
+
+ foundMarker = true;
+ long currentTime = level.clockManager().getTotalTicks(clock);
+ long delta = targetTime.getAsLong() - currentTime;
+ org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(level.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, delta);
+ if (event.callEvent()) {
+ level.clockManager().setTotalTicks(clock, currentTime + event.getSkipAmount());
+ }
+ }
+ // Paper end
+
ServerClockManager clockManager = source.getServer().clockManager();
- if (!clockManager.moveToTimeMarker(clock, timeMarkerId)) {
+ if (!foundMarker) { // Paper
+ java.util.OptionalLong targetTime = clockManager.getTotalTicksToTimeMarker(clock, timeMarkerId);
+ if (targetTime.isEmpty()) {
throw ERROR_NO_TIME_MARKER_FOUND.create(clock.getRegisteredName(), timeMarkerId);
} else {
+ final long currentTime = clockManager.getTotalTicks(clock);
+ final org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callTimeSkipEvent(clock, targetTime.getAsLong() - currentTime);
+ if (event.callEvent()) {
+ clockManager.setTotalTicks(clock, currentTime + event.getSkipAmount());
+ }
+ // Paper end - time skip event
source.sendSuccess(
() -> Component.translatable("commands.time.set.time_marker", clock.getRegisteredName(), timeMarkerId.identifier().toString()), true
);
@@ -214,7 +_,7 @@
private static Holder<WorldClock> getDefaultClock(final CommandSourceStack source) throws CommandSyntaxException {
Holder<DimensionType> dimensionType = source.getLevel().dimensionTypeRegistration();
- return dimensionType.value().defaultClock().orElseThrow(() -> ERROR_NO_DEFAULT_CLOCK.create(dimensionType.getRegisteredName()));
+ return source.getLevel().getClock().orElseThrow(() -> ERROR_NO_DEFAULT_CLOCK.create(dimensionType.getRegisteredName())); // Paper - clock override
}
private interface ClockGetter {
@@ -262,24 +262,42 @@
@Deprecated
@VisibleForTesting
@@ -354,11 +_,24 @@
@@ -326,6 +_,13 @@
}
@Override
+ public Optional<Holder<WorldClock>> getClock() {
+ return this.serverLevelData.clock()
+ .<Holder<WorldClock>>map(clock -> this.registryAccess().lookupOrThrow(Registries.WORLD_CLOCK).getOrThrow(clock))
+ .or(() -> this.dimensionType().defaultClock());
+ }
+
+ @Override
public EnvironmentAttributeSystem environmentAttributes() {
return this.environmentAttributes;
}
@@ -353,12 +_,26 @@
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();
+ org.bukkit.event.world.TimeSkipEvent event = null; // Paper - time skip event
if (this.getGameRules().get(GameRules.ADVANCE_TIME) && defaultClock.isPresent()) {
- Optional<Holder<WorldClock>> defaultClock = this.dimensionType().defaultClock();
- if (this.getGameRules().get(GameRules.ADVANCE_TIME) && defaultClock.isPresent()) {
- this.server.clockManager().moveToTimeMarker(defaultClock.get(), ClockTimeMarkers.WAKE_UP_FROM_SLEEP);
+ Optional<Holder<WorldClock>> clock = this.getClock();
+ org.bukkit.event.world.TimeSkipEvent event = null; // Paper - time skip event
+ if (this.getGameRules().get(GameRules.ADVANCE_TIME) && clock.isPresent()) {
+ // Paper start - time skip event
+ long currentTime = this.server.clockManager().getTotalTicks(defaultClock.get());
+ long delta = this.server.clockManager().getTotalTicksToTimeMarker(defaultClock.get(), ClockTimeMarkers.WAKE_UP_FROM_SLEEP).orElse(0L) - currentTime;
+ long currentTime = this.server.clockManager().getTotalTicks(clock.get());
+ long delta = this.server.clockManager().getTotalTicksToTimeMarker(clock.get(), ClockTimeMarkers.WAKE_UP_FROM_SLEEP).orElse(0L) - currentTime;
+ event = new org.bukkit.event.world.TimeSkipEvent(
+ this.getWorld(),
+ org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP,
+ org.bukkit.craftbukkit.CraftWorldClock.minecraftHolderToBukkit(clock.get()),
+ org.bukkit.event.world.ClockTimeSkipEvent.SkipReason.NIGHT_SKIP,
+ delta
+ );
+
+ if (event.callEvent()) {
+ this.server.clockManager().setTotalTicks(defaultClock.get(), currentTime + event.getSkipAmount()); // TODO - snapshot - per world time
+ this.server.clockManager().setTotalTicks(clock.get(), currentTime + event.getSkipAmount());
+ }
+ // Paper end - time skip event
}
@@ -987,15 +1005,16 @@
this.getServer().getDataStorage().set(MapItemSavedData.type(id), data);
}
@@ -1492,7 +_,20 @@
@@ -1492,7 +_,21 @@
@Override
public void setRespawnData(final LevelData.RespawnData respawnData) {
- this.getServer().setRespawnData(respawnData);
+ // Paper start
+ if (!this.serverLevelData.getRespawnData().positionEquals(respawnData)) {
+ final LevelData.RespawnData previousRespawnData = this.serverLevelData.getRespawnData();
+ this.serverLevelData.setSpawn(respawnData);
+ if (!previousRespawnData.positionEquals(respawnData)) {
+ org.bukkit.Location previousLocation = this.getWorld().getSpawnLocation();
+ this.serverLevelData.setSpawn(respawnData);
+ this.server.getPlayerList().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket(respawnData), this.dimension());
+ this.server.updateEffectiveRespawnData();
+ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), previousLocation).callEvent();
@@ -243,7 +243,7 @@
+ if (!this.cserver.isPrimaryThread()) {
+ org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() {
+ @Override
+ protected @Nullable Object evaluate() {
+ protected Object evaluate() {
+ ServerCommonPacketListenerImpl.this.disconnect(details);
+ return null;
+ }
@@ -285,7 +285,7 @@
+ }
+
+ reason = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason());
+ leaveMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.leaveMessage());
+ leaveMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.leaveMessage());
+ serverGamePacketListener.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason
+ // Log kick to console *after* event was processed.
+ switch (cause) {
@@ -27,10 +27,18 @@
this.setDirty();
for (ServerLevel level : this.server.getAllLevels()) {
@@ -126,7 +_,23 @@
return this.getInstance(definition).totalTicks;
@@ -127,12 +_,29 @@
}
public ClientboundSetTimePacket createFullSyncPacket() {
+ // TODO - snapshot: 26.1 per-player time
return new ClientboundSetTimePacket(this.getGameTime(), Util.mapValues(this.clocks, clock -> clock.packNetworkState(this.server)));
}
private long getGameTime() {
return this.server.overworld().getGameTime();
}
+
+ // Paper start
+ // TODO - snapshot: just make the inner class, fields and getter public
+ public boolean isPaused(final Holder<WorldClock> definition) {
@@ -44,10 +52,8 @@
+ public float rate(final Holder<WorldClock> definition) {
+ return this.getInstance(definition).rate;
+ }
+ // Paper end
+
public ClientboundSetTimePacket createFullSyncPacket() {
+ // TODO - snapshot: 26.1 per-player time
return new ClientboundSetTimePacket(this.getGameTime(), Util.mapValues(this.clocks, clock -> clock.packNetworkState(this.server)));
}
+ // Paper end
public boolean isAtTimeMarker(final Holder<WorldClock> clock, final ResourceKey<ClockTimeMarker> timeMarkerId) {
ServerClockManager.ClockInstance clockInstance = this.getInstance(clock);
@@ -1,14 +1,6 @@
--- a/net/minecraft/world/damagesource/CombatTracker.java
+++ b/net/minecraft/world/damagesource/CombatTracker.java
@@ -22,22 +_,40 @@
private static final Style INTENTIONAL_GAME_DESIGN_STYLE = Style.EMPTY
.withClickEvent(new ClickEvent.OpenUrl(CommonLinks.INTENTIONAL_GAME_DESIGN_BUG))
.withHoverEvent(new HoverEvent.ShowText(Component.literal("MCPE-28723")));
- public final List<CombatEntry> entries = Lists.newArrayList();
+ public final List<CombatEntry> entries = new net.minecraft.util.ArrayListDeque<>(); // Paper - Fix mem leak (MC-301114); Use ArrayListDeque
public final LivingEntity mob;
private int lastDamageTime;
private int combatStartTime;
@@ -29,15 +_,24 @@
private int combatEndTime;
public boolean inCombat;
public boolean takingDamage;
@@ -30,15 +22,6 @@
+ public void recordDamageAndCheckCombatState(final CombatEntry entry) {
+ final DamageSource source = entry.source();
+ // Paper end - Combat tracker API
+ // Paper start - Fix mem leak (MC-301114)
+ final io.papermc.paper.configuration.type.number.IntOr.Disabled maxTrackingCombatEntries;
+ if (!this.entries.isEmpty() && (maxTrackingCombatEntries = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxTrackingCombatEntries).enabled()) {
+ final int maxEntries = maxTrackingCombatEntries.intValue();
+ while (this.entries.size() > maxEntries) {
+ this.entries.removeFirst();
+ }
+ }
+ // Paper end - Fix mem leak (MC-301114)
this.entries.add(entry);
this.lastDamageTime = this.mob.tickCount;
this.takingDamage = true;
@@ -1355,7 +1355,7 @@
this.setTicksFrozen(0);
}
@@ -2762,11 +_,42 @@
@@ -2762,11 +_,43 @@
public void thunderHit(final ServerLevel level, final LightningBolt lightningBolt) {
this.setRemainingFireTicks(this.remainingFireTicks + 1);
@@ -1383,10 +1383,11 @@
+ }
+
+ // CraftBukkit start
+ final DamageSource damageSource = this.damageSources().lightningBolt().eventEntityDamager(lightningBolt);
+ if (thisBukkitEntity instanceof org.bukkit.entity.Hanging) {
+ org.bukkit.event.hanging.HangingBreakByEntityEvent hangingEvent = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) thisBukkitEntity, stormBukkitEntity, new org.bukkit.craftbukkit.damage.CraftDamageSource(damageSource));
+ if (!hangingEvent.callEvent()) {
+ org.bukkit.event.hanging.HangingBreakByEntityEvent hangingEvent = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) thisBukkitEntity, stormBukkitEntity);
+ pluginManager.callEvent(hangingEvent);
+
+ if (hangingEvent.isCancelled()) {
+ return;
+ }
+ }
@@ -1395,7 +1396,7 @@
+ return;
+ }
+
+ if (!this.hurtServer(level, damageSource, 5.0F)) { // Paper - fix DamageSource API
+ if (!this.hurtServer(level, this.damageSources().lightningBolt().eventEntityDamager(lightningBolt), 5.0F)) { // Paper - fix DamageSource API
+ return;
+ }
+ // CraftBukkit end
@@ -1,5 +1,16 @@
--- a/net/minecraft/world/entity/ai/village/VillageSiege.java
+++ b/net/minecraft/world/entity/ai/village/VillageSiege.java
@@ -34,8 +_,8 @@
@Override
public void tick(final ServerLevel level, final boolean spawnEnemies) {
if (!level.isBrightOutside() && spawnEnemies) {
- Optional<Holder<WorldClock>> defaultClock = level.dimensionType().defaultClock();
- if (defaultClock.isPresent() && level.clockManager().isAtTimeMarker(defaultClock.get(), ClockTimeMarkers.ROLL_VILLAGE_SIEGE)) {
+ Optional<Holder<WorldClock>> clock = level.getClock();
+ if (clock.isPresent() && level.clockManager().isAtTimeMarker(clock.get(), ClockTimeMarkers.ROLL_VILLAGE_SIEGE)) {
this.siegeState = level.getRandom().nextInt(10) == 0 ? VillageSiege.State.SIEGE_TONIGHT : VillageSiege.State.SIEGE_DONE;
}
@@ -102,11 +_,12 @@
zombie.finalizeSpawn(level, level.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.EVENT, null);
} catch (Exception var5) {
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/net/minecraft/world/entity/animal/goat/Goat.java
@@ -213,8 +_,16 @@
@@ -213,13 +_,22 @@
public InteractionResult mobInteract(final Player player, final InteractionHand hand) {
ItemStack heldItem = player.getItemInHand(hand);
if (heldItem.is(Items.BUCKET) && !this.isBaby()) {
@@ -18,6 +18,13 @@
player.setItemInHand(hand, bucketOrMilkBucket);
return InteractionResult.SUCCESS;
} else {
+ boolean isFood = this.isFood(heldItem); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739)
InteractionResult interactionResult = super.mobInteract(player, hand);
- if (interactionResult.consumesAction() && this.isFood(heldItem)) {
+ if (interactionResult.consumesAction() && isFood) { // Paper
this.playEatingSound();
}
@@ -328,8 +_,7 @@
double deltaY = Mth.randomBetween(this.random, 0.3F, 0.7F);
double deltaZ = Mth.randomBetween(this.random, -0.2F, 0.2F);
@@ -1,15 +1,6 @@
--- a/net/minecraft/world/entity/animal/happyghast/HappyGhast.java
+++ b/net/minecraft/world/entity/animal/happyghast/HappyGhast.java
@@ -300,7 +_,7 @@
@Override
protected void addPassenger(final Entity passenger) {
if (!this.isVehicle()) {
- this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_DOWN, this.getSoundSource(), 1.0F, 1.0F);
+ this.playSound(SoundEvents.HARNESS_GOGGLES_DOWN, 1.0F, 1.0F); // Paper - don't play sound if silenced (Fixes MC-297169)
}
super.addPassenger(passenger);
@@ -314,16 +_,21 @@
@@ -314,8 +_,12 @@
}
@Override
@@ -24,11 +15,9 @@
if (!this.level().isClientSide()) {
this.setServerStillTimeout(10);
}
if (!this.isVehicle()) {
@@ -324,6 +_,7 @@
this.clearHome();
- this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_UP, this.getSoundSource(), 1.0F, 1.0F);
+ this.playSound(SoundEvents.HARNESS_GOGGLES_UP, 1.0F, 1.0F); // Paper - don't play sound if silenced (Fixes MC-297169)
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_UP, this.getSoundSource(), 1.0F, 1.0F);
}
+ return true; // Paper - cancellable passengers
}
@@ -43,15 +43,15 @@
} else {
if (!this.isRemoved()) {
+ // CraftBukkit start - fire break events
+ Entity damager = source.getDirectEntity();
+ final org.bukkit.event.hanging.HangingBreakEvent event;
+ Entity damager = (!source.isDirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API
+ org.bukkit.event.hanging.HangingBreakEvent event;
+ if (damager != null) {
+ event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), new org.bukkit.craftbukkit.damage.CraftDamageSource(source), source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.ENTITY);
+ event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.ENTITY);
+ } else {
+ event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.DEFAULT);
+ }
+
+ event.callEvent();
+ this.level().getCraftServer().getPluginManager().callEvent(event);
+
+ if (this.isRemoved() || event.isCancelled()) {
+ return true;
@@ -8,7 +8,7 @@
+ this.splash(hitResult);
+ }
+
+ public void splash(final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper
+ public void splash(final HitResult hitResult) {
+ // Paper end - More projectile API
if (this.level() instanceof ServerLevel level) {
ItemStack potionItemStack = this.getItem();
@@ -37,7 +37,7 @@
- private void onHitAsWater(final ServerLevel level) {
+ private static final Predicate<LivingEntity> APPLY_WATER_GET_ENTITIES_PREDICATE = AbstractThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events
+
+ private boolean onHitAsWater(final ServerLevel level, final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper - Fix potions splash events
+ private boolean onHitAsWater(final ServerLevel level, final HitResult hitResult) { // Paper - Fix potions splash events
AABB aabb = this.getBoundingBox().inflate(4.0, 2.0, 4.0);
- for (LivingEntity entity : this.level().getEntitiesOfClass(LivingEntity.class, aabb, WATER_SENSITIVE_OR_ON_FIRE)) {
@@ -88,7 +88,7 @@
}
- protected abstract void onHitAsPotion(ServerLevel level, ItemStack potionItem, HitResult hitResult);
+ protected abstract boolean onHitAsPotion(ServerLevel level, ItemStack potionItem, @org.jspecify.annotations.Nullable HitResult hitResult); // Paper - Fix potions splash events & More Projectile API
+ protected abstract boolean onHitAsPotion(ServerLevel level, ItemStack potionItem, HitResult hitResult); // Paper - Fix potions splash events & More Projectile API
private void dowseFire(final BlockPos pos) {
BlockState blockState = this.level().getBlockState(pos);
@@ -5,7 +5,7 @@
@Override
- public void onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) {
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper - More projectile API
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) { // Paper - More projectile API
AreaEffectCloud cloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
if (this.getOwner() instanceof LivingEntity owner) {
cloud.setOwner(owner);
@@ -5,12 +5,11 @@
@Override
- public void onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) {
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper - More projectile API
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) { // Paper - More projectile API
PotionContents contents = potionItem.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
float durationScale = potionItem.getOrDefault(DataComponents.POTION_DURATION_SCALE, 1.0F);
Iterable<MobEffectInstance> mobEffects = contents.getAllEffects();
- AABB potionAabb = this.getBoundingBox().move(hitResult.getLocation().subtract(this.position()));
+ AABB potionAabb = hitResult == null ? this.getBoundingBox() : this.getBoundingBox().move(hitResult.getLocation().subtract(this.position())); // Paper - More projectile API
AABB potionAabb = this.getBoundingBox().move(hitResult.getLocation().subtract(this.position()));
AABB effectAabb = potionAabb.inflate(4.0, 2.0, 4.0);
List<LivingEntity> entities = this.level().getEntitiesOfClass(LivingEntity.class, effectAabb);
+ java.util.Map<org.bukkit.entity.LivingEntity, Double> affected = new java.util.HashMap<>(); // CraftBukkit
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/entity/vehicle/VehicleEntity.java
+++ b/net/minecraft/world/entity/vehicle/VehicleEntity.java
@@ -32,12 +_,22 @@
@@ -32,12 +_,20 @@
}
@Override
@@ -13,11 +13,9 @@
} else {
+ // CraftBukkit start
+ org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
+ org.bukkit.craftbukkit.damage.CraftDamageSource damageSourceBukkit = new org.bukkit.craftbukkit.damage.CraftDamageSource(source);
+ org.bukkit.entity.Entity attacker = net.minecraft.Optionull.map(
+ source.eventEntityDamager() != null ? source.eventEntityDamager() : source.getDirectEntity(), Entity::getBukkitEntity
+ );
+ org.bukkit.event.vehicle.VehicleDamageEvent event = new org.bukkit.event.vehicle.VehicleDamageEvent(vehicle, damageSourceBukkit, attacker, damage);
+ org.bukkit.entity.Entity attacker = (source.getEntity() == null) ? null : source.getEntity().getBukkitEntity();
+
+ org.bukkit.event.vehicle.VehicleDamageEvent event = new org.bukkit.event.vehicle.VehicleDamageEvent(vehicle, attacker, damage);
+ if (!event.callEvent()) return false;
+ damage = (float) event.getDamage();
+ // CraftBukkit end
@@ -30,7 +28,7 @@
if (creativePlayer) {
- this.discard();
+ // CraftBukkit start
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, damageSourceBukkit, attacker);
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, attacker);
+ if (!destroyEvent.callEvent()) {
+ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
+ return true;
@@ -40,7 +38,7 @@
}
} else {
+ // CraftBukkit start
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, damageSourceBukkit, attacker);
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, attacker);
+ if (!destroyEvent.callEvent()) {
+ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
+ return true;
@@ -90,7 +90,7 @@
+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(bukkitName, CraftNamespacedKey.fromMinecraft(dimension.identifier())); // Spigot
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.generator = generator;
+ this.world = new CraftWorld((ServerLevel) this, dimension.identifier(), biomeProvider, environment);
+ this.world = new CraftWorld((ServerLevel) this, generator, biomeProvider, environment);
+
+ for (SpawnCategory spawnCategory : SpawnCategory.values()) {
+ if (org.bukkit.craftbukkit.util.CraftSpawnCategory.isValidForLimits(spawnCategory)) {
@@ -528,6 +528,26 @@
this.getChunkAt(pos).addAndRegisterBlockEntity(blockEntity);
}
}
@@ -883,7 +_,7 @@
}
public long getDefaultClockTime() {
- return this.getClockTimeTicks(this.dimensionType().defaultClock());
+ return this.getClockTimeTicks(this.getClock());
}
private long getClockTimeTicks(final Optional<? extends Holder<WorldClock>> clock) {
@@ -1032,6 +_,10 @@
return this.dimensionTypeRegistration;
}
+ public Optional<Holder<WorldClock>> getClock() {
+ return this.dimensionType().defaultClock();
+ }
+
public ResourceKey<Level> dimension() {
return this.dimension;
}
@@ -1110,7 +_,8 @@
BLOCK("block"),
MOB("mob"),
@@ -1,14 +1,5 @@
--- a/net/minecraft/world/level/block/ComparatorBlock.java
+++ b/net/minecraft/world/level/block/ComparatorBlock.java
@@ -137,7 +_,7 @@
float pitch = state.getValue(MODE) == ComparatorMode.SUBTRACT ? 0.55F : 0.5F;
level.playSound(player, pos, SoundEvents.COMPARATOR_CLICK, SoundSource.BLOCKS, 0.3F, pitch);
level.setBlock(pos, state, Block.UPDATE_CLIENTS);
- this.refreshOutputState(level, pos, state);
+ if (level.getBlockState(pos).is(this)) { this.refreshOutputState(level, pos, state); } // Paper - Fix blockstate validation issues
return InteractionResult.SUCCESS;
}
}
@@ -168,8 +_,18 @@
boolean sourceOn = this.shouldTurnOn(level, pos, state);
boolean isOn = state.getValue(POWERED);
@@ -19,11 +19,3 @@
level.setBlock(pos, state.setValue(POWERED, true), Block.UPDATE_CLIENTS);
if (!shouldTurnOn) {
level.scheduleTick(pos, this, this.getDelay(state), TickPriority.VERY_HIGH);
@@ -85,6 +_,7 @@
protected void neighborChanged(
final BlockState state, final Level level, final BlockPos pos, final Block block, final @Nullable Orientation orientation, final boolean movedByPiston
) {
+ if (!level.getBlockState(pos).is(this)) return; // Paper - Fix blockstate validation issues
if (state.canSurvive(level, pos)) {
this.checkTickOnNeighbor(level, pos, state);
} else {
@@ -17,7 +17,7 @@
}
}
@@ -60,12 +_,17 @@
@@ -60,7 +_,7 @@
}
}
@@ -26,13 +26,3 @@
}
private boolean slightlyMelt(final BlockState state, final Level level, final BlockPos pos) {
int age = state.getValue(AGE);
if (age < 3) {
+ // Paper start - FrostedIce BlockFadeEvent
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, state.setValue(AGE, age + 1)).isCancelled()) {
+ return false;
+ }
+ // Paper end - FrostedIce BlockFadeEvent
level.setBlock(pos, state.setValue(AGE, age + 1), Block.UPDATE_CLIENTS);
return false;
} else {
@@ -8,7 +8,7 @@
public BeehiveBlockEntity(final BlockPos worldPosition, final BlockState blockState) {
super(BlockEntityType.BEEHIVE, worldPosition, blockState);
@@ -119,16 +_,17 @@
@@ -119,7 +_,7 @@
}
public boolean isFull() {
@@ -17,9 +17,7 @@
}
public void emptyAllLivingFromHive(final @Nullable Player player, final BlockState state, final BeehiveBlockEntity.BeeReleaseStatus releaseReason) {
+ if (this.level == null) return; // Paper - do not fire from worldgen thread
List<Entity> releasedFromHive = this.releaseAllOccupants(state, releaseReason);
if (player != null) {
@@ -128,7 +_,7 @@
for (Entity released : releasedFromHive) {
if (released instanceof Bee bee && player.position().distanceToSqr(released.position()) <= 16.0) {
if (!this.isSedated()) {
@@ -62,24 +62,11 @@
if (this.openCount == 0) {
this.onClose(level, pos, blockState);
level.gameEvent(entity, GameEvent.CONTAINER_CLOSE, pos);
@@ -74,8 +_,20 @@
@@ -74,6 +_,7 @@
}
int openCount = containerUsers.size();
+ if (this.opened) openCount++; // CraftBukkit - add dummy count from API
int prevCount = this.openCount;
if (prevCount != openCount) {
+ // CraftBukkit start - Call redstone event
+ if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
+ int oldPower = net.minecraft.util.Mth.clamp(prevCount, 0, 15);
+ int newPower = net.minecraft.util.Mth.clamp(openCount, 0, 15);
+
+ if (oldPower != newPower) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
+ }
+ }
+ // CraftBukkit end
+
boolean isOpen = openCount != 0;
boolean wasOpen = prevCount != 0;
if (isOpen && !wasOpen) {
@@ -1,5 +1,14 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -47,7 +_,7 @@
protected final RegionBitmap usedSectors = new RegionBitmap();
public RegionFile(final RegionStorageInfo info, final Path path, final Path externalFileDir, final boolean sync) throws IOException {
- this(info, path, externalFileDir, RegionFileVersion.getSelected(), sync);
+ this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
}
public RegionFile(final RegionStorageInfo info, final Path path, final Path externalFileDir, final RegionFileVersion version, final boolean sync) throws IOException {
@@ -83,6 +_,14 @@
if (offset != 0) {
int sectorNumber = getSectorNumber(offset);
@@ -1,11 +1,19 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -24,7 +_,7 @@
private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
public static final RegionFileVersion VERSION_GZIP = register(
- new RegionFileVersion(1, null, in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out)))
+ new RegionFileVersion(1, "gzip", in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out))) // Paper - Enable for Configurable region compression format
);
public static final RegionFileVersion VERSION_DEFLATE = register(
new RegionFileVersion(
@@ -49,6 +_,16 @@
private final RegionFileVersion.StreamWrapper<InputStream> inputWrapper;
private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
+ // Paper start - Configurable region compression format
+ public static RegionFileVersion getCompressionFormat() {
+ return switch (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.compressionFormat) {
+ case GZIP -> VERSION_GZIP;
+ case ZLIB -> VERSION_DEFLATE;
+ case LZ4 -> VERSION_LZ4;
+ case NONE -> VERSION_NONE;
+ };
+ }
+ // Paper end - Configurable region compression format
private RegionFileVersion(
final int id,
final @Nullable String optionName,
@@ -5,7 +5,7 @@
if (spawnEnemies) {
if (level.getGameRules().get(GameRules.SPAWN_PHANTOMS)) {
+ // Paper start - Ability to control player's insomnia and phantoms
+ if (level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0 || level.paperConfig().entities.behavior.playerInsomniaStartTicks < 0) {
+ if (level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) {
+ return;
+ }
+ // Paper end - Ability to control player's insomnia and phantoms
@@ -25,15 +25,6 @@
BlockPos playerPos = player.blockPosition();
if (!level.dimensionType().hasSkyLight() || playerPos.getY() >= level.getSeaLevel() && level.canSeeSky(playerPos)) {
DifficultyInstance difficulty = level.getCurrentDifficultyAt(playerPos);
@@ -39,7 +_,7 @@
ServerStatsCounter stats = player.getStats();
int value = Mth.clamp(stats.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
int dayLength = 24000;
- if (random.nextInt(value) >= 72000) {
+ if (random.nextInt(value) >= level.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms
BlockPos spawnPos = playerPos.above(20 + random.nextInt(15))
.east(-10 + random.nextInt(21))
.south(-10 + random.nextInt(21));
@@ -50,11 +_,21 @@
int groupSize = 1 + random.nextInt(difficulty.getDifficulty().getId() + 1);
@@ -15,7 +15,7 @@
.mapOrElse(
Function.identity(),
error -> {
@@ -174,13 +_,13 @@
@@ -174,9 +_,9 @@
}
public static <T extends SavedData> DataResult<T> readExistingSavedData(
@@ -27,11 +27,6 @@
CompoundTag fileContents;
try {
- fileContents = NbtIo.readCompressed(dataLocation, NbtAccounter.defaultQuota());
+ fileContents = NbtIo.readCompressed(dataLocation, NbtAccounter.create(net.minecraft.nbt.NbtAccounter.DEFAULT_NBT_QUOTA * 50L)); // Paper - raise quota to account for custom data
} catch (IOException var6) {
return DataResult.error(var6::getMessage);
}
@@ -396,7 +_,7 @@
public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(final String levelId) throws IOException, ContentValidationException {
@@ -0,0 +1,23 @@
--- a/net/minecraft/world/timeline/Timeline.java
+++ b/net/minecraft/world/timeline/Timeline.java
@@ -49,7 +_,7 @@
public static final Codec<Timeline> NETWORK_CODEC = DIRECT_CODEC.xmap(Timeline::filterSyncableTracks, Timeline::filterSyncableTracks);
private final Holder<WorldClock> clock;
private final Optional<Integer> periodTicks;
- private final Map<EnvironmentAttribute<?>, AttributeTrack<?, ?>> tracks;
+ public final Map<EnvironmentAttribute<?>, AttributeTrack<?, ?>> tracks;
private final Map<ResourceKey<ClockTimeMarker>, Timeline.TimeMarkerInfo> timeMarkers;
private static Timeline filterSyncableTracks(final Timeline timeline) {
@@ -179,6 +_,11 @@
KeyframeTrack.Builder<Argument> argumentTrack = new KeyframeTrack.Builder<>();
builder.accept(argumentTrack);
this.tracks.put(attribute, new AttributeTrack<>(modifier, argumentTrack.build()));
+ return this;
+ }
+
+ public Timeline.Builder addTracks(final Map<EnvironmentAttribute<?>, AttributeTrack<?, ?>> tracks) {
+ this.tracks.putAll(tracks);
return this;
}
@@ -22,9 +22,6 @@ import org.bukkit.GameRule;
import org.bukkit.block.Biome;
import org.bukkit.craftbukkit.CraftGameRule;
import org.bukkit.craftbukkit.block.CraftBiome;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import org.bukkit.craftbukkit.damage.CraftDamageEffect;
import org.bukkit.craftbukkit.damage.CraftDamageSource;
import org.bukkit.craftbukkit.entity.CraftLivingEntity;
@@ -65,11 +62,6 @@ public class PaperServerInternalAPIBridge implements InternalAPIBridge {
);
}
@Override
public PoiType.Occupancy createOccupancy(final String enumNameEntry) {
return new PaperPoiType.PaperOccupancy(PoiManager.Occupancy.valueOf(enumNameEntry));
}
@Override
public CombatEntry createCombatEntry(
final DamageSource damageSource,
@@ -94,9 +94,8 @@ public final class EntityCommand implements PaperSubcommand {
return;
}
Map<Identifier, MutablePair<Integer, Map<ChunkPos, Integer>>> list = Maps.newHashMap();
@Nullable NamespacedKey worldKey = NamespacedKey.fromString(worldName);
@Nullable World bukkitWorld;
if (worldKey == null || (bukkitWorld = Bukkit.getWorld(worldKey)) == null) {
@Nullable World bukkitWorld = Bukkit.getWorld(NamespacedKey.fromString(worldName));
if (bukkitWorld == null) {
sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED));
sender.sendMessage(text("Usage: /paper entity list [filter] [worldKey]", RED));
return;
@@ -8,7 +8,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.ToIntFunction;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.JoinConfiguration;
@@ -92,7 +91,7 @@ public final class MobcapsCommand implements PaperSubcommand {
if (sender instanceof Player player) {
worlds = List.of(player.getWorld());
} else {
sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps overworld'", NamedTextColor.RED));
sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED));
return;
}
} else if (args.length == 1) {
@@ -100,9 +99,8 @@ public final class MobcapsCommand implements PaperSubcommand {
if (input.equals("*")) {
worlds = Bukkit.getWorlds();
} else {
final @Nullable Key worldKey = NamespacedKey.fromString(input);
final @Nullable World world;
if (worldKey == null || (world = Bukkit.getWorld(worldKey)) == null) {
final @Nullable World world = Bukkit.getWorld(input);
if (world == null) {
sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED));
return;
} else {
@@ -1,6 +1,7 @@
package io.papermc.paper.configuration;
import com.mojang.logging.LogUtils;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.configuration.constraint.Constraints;
import io.papermc.paper.configuration.serializer.collection.map.WriteKeyBack;
import io.papermc.paper.configuration.type.number.DoubleOr;
@@ -182,10 +183,19 @@ public class GlobalConfiguration extends ConfigurationPart {
public boolean allowHeadlessPistons = false;
@Comment("This setting controls if the vanilla damage tick should be skipped if damage was blocked via a shield.")
public boolean skipVanillaDamageTickWhenShieldBlocked = false;
@Comment("This setting controls what compression format is used for region files.")
public CompressionFormat compressionFormat = CompressionFormat.ZLIB;
@Comment("This setting controls if equipment should be updated when handling certain player actions.")
public boolean updateEquipmentOnPlayerActions = true;
@Comment("This setting controls what item data components don't need to be sanitized in oversized item obfuscation. Adding them re-enables exploits, but may be needed for certain resource packs. (Expected: minecraft:container, minecraft:charged_projectiles and minecraft:bundle_contents)")
public OversizedItemComponentSanitizer.AssetOversizedItemComponentSanitizerConfiguration oversizedItemComponentSanitizer = new OversizedItemComponentSanitizer.AssetOversizedItemComponentSanitizerConfiguration(Set.of());
public enum CompressionFormat {
GZIP,
ZLIB,
LZ4,
NONE
}
}
public Commands commands;
@@ -334,8 +344,6 @@ public class GlobalConfiguration extends ConfigurationPart {
public boolean enableNether = true;
@Comment("Keeps Paper's fix for MC-159283 enabled. Disable to use vanilla End ring terrain.")
public boolean fixFarEndTerrainGeneration = true;
@Comment("Fix for MC-301114. This removes the oldest combat entry when it hits the cap, to fix a memory leak on constant entity damage.")
public IntOr.Disabled maxTrackingCombatEntries = new IntOr.Disabled(OptionalInt.of(10240));
}
public BlockUpdates blockUpdates;
@@ -51,7 +51,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -151,7 +150,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
""";
@VisibleForTesting
public static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255), Key.key(RandomStringUtils.randomAlphabetic(255).toLowerCase(Locale.ROOT))) {
public static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255), Key.key(RandomStringUtils.randomAlphabetic(255).toLowerCase())) {
@Override // override to ensure "verbose" is false
public void init() {
SpigotConfig.readConfig(SpigotWorldConfig.class, this);
@@ -86,7 +86,6 @@ interface RemovedConfigurations {
path("misc", "fix-entity-position-desync"),
path("chunk-system", "gen-parallelism"),
path("logging"), // server is no longer obfuscated since 26.1
path("unsupported-settings", "compression-format"),
};
}
@@ -1,20 +0,0 @@
package io.papermc.paper.entity.poi;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record PaperPoiSearchResult(PoiType poiType, Location location) implements PoiSearchResult {
@Override
public Location location() {
return location.clone();
}
public static PoiSearchResult from(PoiRecord record, World world) {
return new PaperPoiSearchResult(PaperPoiType.minecraftHolderToBukkit(record.getPoiType()), CraftLocation.toBukkit(record.getPos(), world));
}
}
@@ -1,50 +0,0 @@
package io.papermc.paper.entity.poi;
import io.papermc.paper.registry.HolderableBase;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PaperPoiType extends HolderableBase<net.minecraft.world.entity.ai.village.poi.PoiType> implements PoiType {
public static PaperPoiType minecraftHolderToBukkit(final Holder<net.minecraft.world.entity.ai.village.poi.PoiType> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.POINT_OF_INTEREST_TYPE);
}
public static Holder<net.minecraft.world.entity.ai.village.poi.PoiType> bukkitToMinecraftHolder(final PoiType bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public PaperPoiType(final Holder<net.minecraft.world.entity.ai.village.poi.PoiType> holder) {
super(holder);
}
@Override
public boolean is(final BlockData data) {
return this.getHandle().is(((CraftBlockData) data).getState());
}
@Override
public boolean hasOccupants() {
return this.getHandle().maxTickets() != 0;
}
public record PaperOccupancy(PoiManager.Occupancy handle) implements Occupancy {
public static PoiManager.Occupancy bukkitToMinecraft(final Occupancy occupancy) {
return ((PaperOccupancy) occupancy).handle();
}
public static Occupancy minecraftToBukkit(final PoiManager.Occupancy occupancy) {
return switch (occupancy) {
case ANY -> Occupancy.ANY;
case HAS_SPACE -> Occupancy.HAS_SPACE;
case IS_OCCUPIED -> Occupancy.IS_OCCUPIED;
};
}
}
}
@@ -6,8 +6,6 @@ import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.PaperDataComponentType;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.PaperDialog;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.registry.data.PaperBannerPatternRegistryEntry;
import io.papermc.paper.registry.data.PaperCatTypeRegistryEntry;
import io.papermc.paper.registry.data.PaperChickenVariantRegistryEntry;
@@ -21,12 +19,16 @@ import io.papermc.paper.registry.data.PaperJukeboxSongRegistryEntry;
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.PaperTimelineRegistryEntry;
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;
@@ -54,6 +56,8 @@ 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;
@@ -123,7 +127,6 @@ public final class PaperRegistries {
start(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT).craft(Sound.class, CraftSound::new, true).create(PaperSoundEventRegistryEntry.PaperBuilder::new, RegistryEntryMeta.RegistryModificationApiSupport.NONE),
start(Registries.DATA_COMPONENT_TYPE, RegistryKey.DATA_COMPONENT_TYPE).craft(DataComponentTypes.class, PaperDataComponentType::of).build(),
start(Registries.GAME_RULE, RegistryKey.GAME_RULE).craft(GameRule.class, CraftGameRule::new).build(),
start(Registries.POINT_OF_INTEREST_TYPE, RegistryKey.POINT_OF_INTEREST_TYPE).craft(PoiTypes.class, PaperPoiType::new).build(),
// data-driven
start(Registries.BIOME, RegistryKey.BIOME).craft(Biome.class, CraftBiome::new).build().delayed(),
@@ -149,6 +152,8 @@ public final class PaperRegistries {
start(Registries.PIG_SOUND_VARIANT, RegistryKey.PIG_SOUND_VARIANT).craft(Pig.SoundVariant.class, CraftPig.CraftSoundVariant::new).build(),
start(Registries.ZOMBIE_NAUTILUS_VARIANT, RegistryKey.ZOMBIE_NAUTILUS_VARIANT).craft(ZombieNautilus.Variant.class, CraftZombieNautilus.CraftVariant::new).writable(PaperZombieNautilusVariantRegistryEntry.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),
@@ -0,0 +1,101 @@
package io.papermc.paper.registry.data;
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.asArgumentMin;
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 OptionalInt periodTicks = OptionalInt.empty();
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();
internal.periodTicks().ifPresent(period -> this.periodTicks = OptionalInt.of(period));
internal.registerTimeMarkers((key, marker) -> this.timeMarkers.put(PaperAdventure.asAdventure(key.identifier()), new TimeMarker(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;
}
@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) {
this.periodTicks = OptionalInt.of(asArgumentMin(periodTicks, "periodTicks", 1));
return this;
}
@Override
public Builder clearPeriodTicks() {
this.periodTicks = OptionalInt.empty();
return this;
}
@Override
public Builder addTimeMarker(final Key key, final @Range(from = 0, to = Integer.MAX_VALUE) int ticks, final boolean showInCommands) {
this.timeMarkers.put(asArgument(key, "key"), new TimeMarker(asArgumentMin(ticks, "ticks", 0), showInCommands));
return this;
}
@Override
public Timeline build() {
final Timeline.Builder builder = Timeline.builder(asConfigured(this.clock, "clock"));
if (this.periodTicks.isPresent()) {
builder.setPeriodTicks(this.periodTicks.getAsInt());
}
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();
}
}
}
@@ -0,0 +1,24 @@
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();
}
}
}
@@ -145,37 +145,6 @@ public final class EntityScheduler {
}
}
/**
* Attempts to invoke the callback immediately if the current thread owns the Entity and the Entity is not retired,
* returning {@code null} when it happens. If the current thread neither owns the Entity nor the Entity is retired,
* then this function has the same effect as invoking {@link #schedule(Consumer, Consumer, long)} with no retired
* callback and a delay of {@code 1L}.
*
* @param run The callback to run, may not be null.
* @return {@code Boolean.TRUE} if the task was scheduled, which means that either the run function or the retired function
* will be invoked (but never both), or {@code Boolean.FALSE} indicating neither the run nor retired function will be invoked
* since the scheduler has been retired. Returns {@code null} if the callback was executed immediately.
*/
public Boolean scheduleOrExecute(final Consumer<? extends Entity> run) {
Objects.requireNonNull(run, "Run task may not be null");
final Entity handle = this.entity.getHandleRaw();
if (!TickThread.isTickThreadFor(handle)) {
return Boolean.valueOf(this.schedule(run, null, 1L));
}
synchronized (this.stateLock) {
if (this.tickCount == RETIRED_TICK_COUNT) {
return Boolean.FALSE;
}
}
((Consumer<Entity>)run).accept(handle);
return null;
}
/**
* Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity
* removed), then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay,
@@ -1,34 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public interface BlockPlacementPredictor {
Optional<BlockState> getLatestBlockAt(BlockPos pos);
Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos);
Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos);
record BlockEntityPlacement(boolean removed, @Nullable BlockEntity blockEntity) {
public static final Optional<BlockEntityPlacement> ABSENT = Optional.of(new BlockEntityPlacement(false, null));
public @Nullable BlockEntity res() {
return this.removed ? null : this.blockEntity;
}
}
record LoadedBlockState(boolean present, @Nullable BlockState state) {
public static final Optional<LoadedBlockState> UNLOADED = Optional.of(new LoadedBlockState(false, null));
public @Nullable BlockState res() {
return this.present ? this.state : null;
}
}
}
@@ -1,142 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.block.CraftBlockStates;
import org.jspecify.annotations.Nullable;
/*
The premise of this storage is to essentially "mock" block placement in the game.
This should attempt to mirror block placement the best it can, in order to provide a read solution of possibly what blocks
are going to be placed in the world.
The reason why we use a custom capture map rather than just storing the blocks placed in the world is to ensure
that blocks are placed in the correct order, and that the vanilla block chain can occur, which includes physics updates.
*/
public final class CaptureRecordMap {
private final Map<BlockPos, CaptureRecord> recordsByPos = new HashMap<>();
public void setLatestBlockStateAt(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
this.add(new CaptureRecord(pos, blockState, updateFlags));
}
public void setLatestBlockEntityAt(BlockPos pos, boolean clearPreviousBlockEntity, @Nullable BlockEntity add) {
CaptureRecord oldRecord = this.recordsByPos.get(pos);
if (oldRecord != null) {
oldRecord.setBlockEntity(clearPreviousBlockEntity, add);
}
}
private void add(CaptureRecord record) {
this.recordsByPos.put(record.pos, record);
}
public boolean isEmpty() {
return this.recordsByPos.isEmpty();
}
public @Nullable BlockState getLatestBlockStateAt(BlockPos pos) {
CaptureRecord record = this.recordsByPos.get(pos);
if (record == null) {
return null;
}
return record.blockState;
}
// Null indicates that it's not present, no override
// Optional empty indicates its being removed
public @Nullable Optional<BlockEntity> getLatestBlockEntityAt(BlockPos pos) {
CaptureRecord record = this.recordsByPos.get(pos);
if (record == null) {
return null;
}
return Optional.ofNullable(record.blockEntity);
}
public void applyBlockEntities(ServerLevel parent) {
this.recordsByPos.keySet().forEach((pos) -> {
Optional<BlockEntity> res = this.getLatestBlockEntityAt(pos);
if (res != null && res.isPresent()) {
parent.setBlockEntity(res.get());
}
});
}
public void applyApiPatch(ServerLevel level) {
this.recordsByPos.keySet().forEach((pos) -> {
this.recordsByPos.get(pos).applyApiPatch(level);
});
}
// TODO: Clean this up
public List<org.bukkit.block.BlockState> calculateLatestSnapshots(ServerLevel level) {
List<org.bukkit.block.BlockState> out = new ArrayList<>();
for (Map.Entry<BlockPos, CaptureRecord> entry : this.recordsByPos.entrySet()) {
CaptureRecord captureRecord = entry.getValue();
out.add(CraftBlockStates.getBlockState(level.getWorld(), entry.getKey(), captureRecord.blockState, captureRecord.blockEntity));
}
return out;
}
public Stream<org.bukkit.block.Block> getAffectedBlocks(ServerLevel level) {
return this.recordsByPos.keySet().stream().map(pos -> CraftBlock.at(level, pos));
}
public static class CaptureRecord {
private final BlockPos pos;
private BlockState blockState;
private @Nullable BlockEntity blockEntity;
private boolean clearPreviousBlockEntity;
private @Block.UpdateFlags int updateFlags;
public CaptureRecord(BlockPos pos, BlockState blockState, BlockEntity blockEntity) {
this.pos = pos;
this.blockState = blockState;
this.blockEntity = blockEntity;
}
public CaptureRecord(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
this.pos = pos;
this.blockState = blockState;
this.updateFlags = updateFlags;
}
public CaptureRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) {
this.clearPreviousBlockEntity = remove;
this.blockEntity = add;
this.pos = pos;
}
public void applyApiPatch(ServerLevel level) {
if (this.clearPreviousBlockEntity) {
level.removeBlockEntity(this.pos);
}
level.setBlock(this.pos, this.blockState, this.updateFlags);
if (this.blockEntity != null) {
level.setBlockEntity(this.blockEntity);
}
}
public void setBlockEntity(boolean clearPreviousBlockEntity, @Nullable BlockEntity add) {
this.clearPreviousBlockEntity = clearPreviousBlockEntity;
this.blockEntity = add;
}
}
}
@@ -1,100 +0,0 @@
package io.papermc.paper.util.capture;
import net.minecraft.Optionull;
import net.minecraft.core.Holder;
import net.minecraft.data.worldgen.features.TreeFeatures;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import org.bukkit.TreeType;
import org.jspecify.annotations.Nullable;
public final class GrowthContext {
public static GrowthContext empty() {
return new GrowthContext(null, false);
}
public static GrowthContext usingBoneMeal(@Nullable Player user) {
return new GrowthContext(user, true);
}
private final @Nullable Player player;
private final boolean usedBoneMeal;
public boolean cancelled = false;
public Holder<ConfiguredFeature<?, ?>> feature; // just make this a field
private GrowthContext(@Nullable Player player, boolean usedBoneMeal) {
this.player = player;
this.usedBoneMeal = usedBoneMeal;
}
public org.bukkit.entity.@Nullable Player getBukkitPlayer() {
return (org.bukkit.entity.Player) Optionull.map(this.player, Entity::getBukkitEntity);
}
public boolean usedBoneMeal() {
return this.usedBoneMeal;
}
public TreeType getTreeSpecies() {
return getTreeSpecies(this.feature);
}
private static TreeType getTreeSpecies(Holder<ConfiguredFeature<?, ?>> feature) {
if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) {
return TreeType.RED_MUSHROOM;
} else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) {
return TreeType.BROWN_MUSHROOM;
} else if (feature.is(TreeFeatures.WARPED_FUNGUS_PLANTED)) {
return TreeType.WARPED_FUNGUS;
} else if (feature.is(TreeFeatures.CRIMSON_FUNGUS_PLANTED)) {
return TreeType.CRIMSON_FUNGUS;
} else if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) {
return TreeType.TREE;
} else if (feature.is(TreeFeatures.JUNGLE_TREE)) {
return TreeType.COCOA_TREE;
} else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) {
return TreeType.SMALL_JUNGLE;
} else if (feature.is(TreeFeatures.PINE)) {
return TreeType.TALL_REDWOOD;
} else if (feature.is(TreeFeatures.SPRUCE)) {
return TreeType.REDWOOD;
} else if (feature.is(TreeFeatures.ACACIA)) {
return TreeType.ACACIA;
} else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) {
return TreeType.BIRCH;
} else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) {
return TreeType.TALL_BIRCH;
} else if (feature.is(TreeFeatures.SWAMP_OAK)) {
return TreeType.SWAMP;
} else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) {
return TreeType.BIG_TREE;
} else if (feature.is(TreeFeatures.JUNGLE_BUSH)) {
return TreeType.JUNGLE_BUSH;
} else if (feature.is(TreeFeatures.DARK_OAK)) {
return TreeType.DARK_OAK;
} else if (feature.is(TreeFeatures.MEGA_SPRUCE)) {
return TreeType.MEGA_REDWOOD;
} else if (feature.is(TreeFeatures.MEGA_PINE)) {
return TreeType.MEGA_PINE;
} else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) {
return TreeType.JUNGLE;
} else if (feature.is(TreeFeatures.AZALEA_TREE)) {
return TreeType.AZALEA;
} else if (feature.is(TreeFeatures.MANGROVE)) {
return TreeType.MANGROVE;
} else if (feature.is(TreeFeatures.TALL_MANGROVE)) {
return TreeType.TALL_MANGROVE;
} else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) {
return TreeType.CHERRY;
} else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) {
return TreeType.PALE_OAK;
} else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) {
return TreeType.PALE_OAK_CREAKING;
} else {
throw new IllegalArgumentException("Unknown tree feature: " + feature);
}
}
}
@@ -1,46 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
public record LayeredBlockPlacementPredictor(
BlockPlacementPredictor... predictors
) implements BlockPlacementPredictor {
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
for (BlockPlacementPredictor predictor : this.predictors) {
Optional<BlockState> state = predictor.getLatestBlockAt(pos);
if (state.isPresent()) {
return state;
}
}
return Optional.empty();
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
for (BlockPlacementPredictor predictor : this.predictors) {
Optional<LoadedBlockState> state = predictor.getLatestBlockAtIfLoaded(pos);
if (state.isPresent()) {
return state;
}
}
return Optional.empty();
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
for (BlockPlacementPredictor predictor : this.predictors) {
Optional<BlockEntityPlacement> state = predictor.getLatestBlockEntityAt(pos);
if (state.isPresent()) {
return state;
}
}
return Optional.empty();
}
}
@@ -1,46 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public record LiveBlockPlacementLayer(WorldCapturer capturer, ServerLevel level) implements BlockPlacementPredictor {
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
return Optional.of(provideLive(() -> this.level.getBlockState(pos)));
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
BlockState state = provideLive(() -> this.level.getBlockStateIfLoaded(pos));
if (state == null) {
return LoadedBlockState.UNLOADED;
}
return Optional.of(new LoadedBlockState(true, state));
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
BlockEntity blockEntity = provideLive(() -> this.level.getBlockEntity(pos));
if (blockEntity == null) {
return BlockEntityPlacement.ABSENT;
}
return Optional.of(new BlockEntityPlacement(false, blockEntity));
}
public <T> @Nullable T provideLive(Supplier<@Nullable T> valueProvider) {
SimpleBlockCapture blockCapture = this.capturer.getCapture();
this.capturer.releaseCapture(null);
T value = valueProvider.get();
this.capturer.releaseCapture(blockCapture);
return value;
}
}
@@ -1,448 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.attribute.EnvironmentAttributeReader;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gamerules.GameRules;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraft.world.ticks.ScheduledTick;
import org.jspecify.annotations.Nullable;
public class MinecraftCaptureBridge implements PaperCapturingWorldLevel {
private final ServerLevel parent;
private final List<Runnable> queuedTasks = new ArrayList<>();
// Effective represents plugin set blocks -> predicted blocks -> actual server level
private final BlockPlacementPredictor effectiveReadLayer;
private SimpleBlockPlacementPredictor writeLayer;
// This is the layer that is written to in the server level potentially.
// Mostly plugin set blocks
private final SimpleBlockPlacementPredictor serverLevelOverlayLayer;
private final CapturingTickAccess<Block> blocks;
private final CapturingTickAccess<Fluid> liquids;
private Consumer<Runnable> sink = this.queuedTasks::add;
public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor baseReadLayer) {
this.parent = parent;
this.serverLevelOverlayLayer = new SimpleBlockPlacementPredictor();
SimpleBlockPlacementPredictor predictedBlocks = new SimpleBlockPlacementPredictor();
this.effectiveReadLayer = new LayeredBlockPlacementPredictor(
this.serverLevelOverlayLayer, // The overlay layer represents plugin set blocks!
predictedBlocks, // Now predicted blocks
baseReadLayer
);
this.writeLayer = predictedBlocks; // Predicting
this.blocks = new CapturingTickAccess<>(parent.getBlockTicks());
this.liquids = new CapturingTickAccess<>(parent.getFluidTicks());
}
@Override
public long getSeed() {
return this.parent.getSeed();
}
@Override
public ServerLevel getLevel() {
return this.parent;
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) {
return this.parent.getCurrentDifficultyAt(pos);
}
@Override
public long nextSubTickCount() {
return this.parent.nextSubTickCount();
}
@Override
public LevelData getLevelData() {
return this.parent.getLevelData();
}
@Override
public @Nullable MinecraftServer getServer() {
return this.parent.getServer();
}
@Override
public ServerChunkCache getChunkSource() {
return this.parent.getChunkSource();
}
@Override
public boolean setBlockAndUpdate(BlockPos pos, BlockState blockState) {
return this.setBlock(pos, blockState, Block.UPDATE_ALL);
}
@Override
public SimpleBlockCapture forkCaptureSession() {
return this.parent.capturer.createCaptureSession(new BlockPlacementPredictor() {
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAt(pos);
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos);
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockEntityAt(pos);
}
});
}
@Override
public RandomSource getRandom() {
// TODO: Support rolling this back?
return this.parent.getRandom();
}
@Override
public void playSound(@Nullable Entity except, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) {
this.addTask((level) -> level.playSound(except, pos, sound, source, volume, pitch));
}
@Override
public void addParticle(ParticleOptions particle, double x, double y, double z, double xd, double yd, double zd) {
this.addTask((level) -> level.addParticle(particle, x, y, z, xd, yd, zd));
}
@Override
public void levelEvent(@Nullable Entity source, int type, BlockPos pos, int data) {
this.addTask((level) -> level.levelEvent(source, type, pos, data));
}
@Override
public void gameEvent(Holder<GameEvent> gameEvent, Vec3 position, GameEvent.Context context) {
this.addTask((level) -> level.gameEvent(gameEvent, position, context));
}
@Override
public LevelLightEngine getLightEngine() {
return this.parent.getLightEngine();
}
@Override
public WorldBorder getWorldBorder() {
return this.parent.getWorldBorder();
}
@Override
public @Nullable BlockEntity getBlockEntity(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockEntityAt(pos)
.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity)
.orElse(null);
}
@Override
public BlockState getBlockState(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockAt(pos).orElseThrow(); // Should not ever be null, parent should pass value
}
@Override
public @Nullable BlockState getBlockStateIfLoaded(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos).map(BlockPlacementPredictor.LoadedBlockState::state).orElse(null);
}
@Override
public @Nullable FluidState getFluidIfLoaded(BlockPos pos) {
return this.getBlockStateIfLoaded(pos).getFluidState();
}
@Override
public FluidState getFluidState(BlockPos pos) {
return this.getBlockState(pos).getFluidState();
}
@Override
public List<Entity> getEntities(@Nullable Entity except, AABB bb, Predicate<? super Entity> selector) {
return this.parent.getEntities(except, bb, selector);
}
@Override
public <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> entityTypeTest, AABB bb, Predicate<? super T> selector) {
return this.parent.getEntities(entityTypeTest, bb, selector);
}
@Override
public List<? extends Player> players() {
return this.parent.players();
}
@Override
public @Nullable ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status, boolean loadOrGenerate) {
return this.parent.getChunk(chunkX, chunkZ, status, loadOrGenerate);
}
@Override
public @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
return this.parent.getChunkIfLoadedImmediately(x, z);
}
@Override
public int getHeight(Heightmap.Types type, int x, int z) {
return this.parent.getHeight(type, x, z); // TODO?
}
@Override
public int getSkyDarken() {
return this.parent.getSkyDarken();
}
@Override
public BiomeManager getBiomeManager() {
return this.parent.getBiomeManager();
}
@Override
public Holder<Biome> getUncachedNoiseBiome(int quartX, int quartY, int quartZ) {
return this.parent.getUncachedNoiseBiome(quartX, quartY, quartZ);
}
@Override
public boolean isClientSide() {
return this.parent.isClientSide();
}
@Override
public int getSeaLevel() {
return this.parent.getSeaLevel();
}
@Override
public DimensionType dimensionType() {
return this.parent.dimensionType();
}
@Override
public RegistryAccess registryAccess() {
return this.parent.registryAccess();
}
@Override
public FeatureFlagSet enabledFeatures() {
return this.parent.enabledFeatures();
}
@Override
public EnvironmentAttributeReader environmentAttributes() {
return this.parent.environmentAttributes();
}
@Override
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> predicate) {
return predicate.test(this.getBlockState(pos));
}
@Override
public boolean isFluidAtPosition(BlockPos pos, Predicate<FluidState> predicate) {
return predicate.test(this.getFluidState(pos));
}
public boolean silentSet(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
return this.writeLayer.setBlockState(this.effectiveReadLayer, pos, blockState, updateFlags);
}
@Override
public boolean setBlock(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.setBlock(copy, blockState, updateFlags, updateLimit));
return this.writeLayer.setBlockState(this.effectiveReadLayer, copy, blockState, updateFlags);
}
@Override
public boolean removeBlock(BlockPos pos, boolean movedByPiston) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.removeBlock(copy, movedByPiston));
FluidState fluidState = this.getFluidState(copy);
return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL | (movedByPiston ? Block.UPDATE_MOVE_BY_PISTON : 0));
}
@Override
public boolean destroyBlock(BlockPos pos, boolean dropResources, @Nullable Entity breaker, int updateLimit) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.destroyBlock(copy, dropResources, breaker, updateLimit));
BlockState blockState = this.getBlockState(copy);
if (blockState.isAir()) {
return false;
} else {
FluidState fluidState = this.getFluidState(copy);
return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL);
}
}
@Override
public void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.sendBlockUpdated(copy, old, current, updateFlags));
}
@Override
public void setBlockEntity(BlockEntity blockEntity) {
this.writeLayer.getRecordMap().setLatestBlockEntityAt(blockEntity.getBlockPos().immutable(), false, blockEntity);
}
@Override
public boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
BlockPos copy = pos.immutable();
return this.silentSet(copy, blockState, updateFlags);
}
@Override
public LevelTickAccess<Block> getBlockTicks() {
return this.blocks;
}
@Override
public LevelTickAccess<Fluid> getFluidTicks() {
return this.liquids;
}
@Override
public GameRules getGameRules() {
return this.parent.getGameRules();
}
@Override
public void addTask(Consumer<ServerLevel> level) {
this.sink.accept(() -> level.accept(this.parent));
}
public @Nullable BlockState getLatestBlockState(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockAt(pos).orElse(null);
}
public @Nullable Optional<BlockEntity> getLatestBlockEntity(BlockPos pos) {
Optional<BlockPlacementPredictor.BlockEntityPlacement> placement = this.effectiveReadLayer.getLatestBlockEntityAt(pos);
if (placement.isEmpty() || (placement.get().blockEntity() == null && !placement.get().removed())) {
return null;
}
return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity);
}
public List<org.bukkit.block.BlockState> calculateLatestSnapshots(ServerLevel level) {
return this.writeLayer.getRecordMap().calculateLatestSnapshots(level);
}
public Stream<org.bukkit.block.Block> getAffectedBlocks(ServerLevel level) {
return this.writeLayer.getRecordMap().getAffectedBlocks(level);
}
public void applyTasks() {
this.sink = Runnable::run;
for (Runnable runnable : this.queuedTasks) {
runnable.run();
}
this.blocks.apply();
this.liquids.apply();
// If we have changes that the plugin applied on top of the already existing changes, we know that we can apply them.
// So, do that!
if (!this.serverLevelOverlayLayer.isEmpty()) {
this.serverLevelOverlayLayer.getRecordMap().applyApiPatch(this.parent);
}
// Apply block entities, those may have been written in our estimation. So just apply them.
// I don't really like this, as I in general don't really want to apply things from the prediction layer.
// But, these may be mutated by anything.
if (!this.writeLayer.getRecordMap().isEmpty()) {
this.writeLayer.getRecordMap().applyBlockEntities(this.parent);
}
}
public void allowWriteOnLevel() {
this.writeLayer = this.serverLevelOverlayLayer;
}
public static class CapturingTickAccess<T> implements LevelTickAccess<T> {
private final LevelTickAccess<T> wrapped;
private final Set<BlockPos> scheduled = new HashSet<>();
private final List<ScheduledTick<T>> ticks = new ArrayList<>();
public CapturingTickAccess(LevelTickAccess<T> wrapped) {
this.wrapped = wrapped;
}
@Override
public boolean willTickThisTick(BlockPos pos, T type) {
return this.wrapped.willTickThisTick(pos, type);
}
@Override
public void schedule(ScheduledTick<T> tick) {
this.scheduled.add(tick.pos());
this.ticks.add(tick);
}
@Override
public boolean hasScheduledTick(BlockPos pos, T type) {
return this.wrapped.hasScheduledTick(pos, type) || this.scheduled.contains(pos);
}
@Override
public int count() {
return this.wrapped.count() + this.scheduled.size();
}
public void apply() {
this.ticks.forEach(this.wrapped::schedule);
}
}
}
@@ -1,30 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.function.Consumer;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gamerules.GameRules;
public interface PaperCapturingWorldLevel extends WorldGenLevel {
GameRules getGameRules();
void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags);
void setBlockEntity(BlockEntity blockEntity);
boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit);
ServerChunkCache getChunkSource();
boolean setBlockAndUpdate(BlockPos pos, BlockState blockState);
void addTask(Consumer<ServerLevel> level);
SimpleBlockCapture forkCaptureSession();
}
@@ -1,47 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.function.Consumer;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gamerules.GameRules;
public interface ServerLevelPaperCapturingWorldLevel extends PaperCapturingWorldLevel {
@Override
default GameRules getGameRules() {
return this.getLevel().getGameRules();
}
@Override
default void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags) {
this.getLevel().sendBlockUpdated(pos, old, current, updateFlags);
}
@Override
default void setBlockEntity(BlockEntity blockEntity) {
this.getLevel().setBlockEntity(blockEntity);
}
@Override
default boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
return this.getLevel().setBlock(pos, blockState, updateFlags, updateLimit);
}
@Override
default boolean setBlockAndUpdate(BlockPos pos, BlockState blockState) {
return this.getLevel().setBlockAndUpdate(pos, blockState);
}
@Override
default void addTask(Consumer<ServerLevel> level) {
level.accept(this.getLevel());
}
@Override
default SimpleBlockCapture forkCaptureSession() {
return this.getLevel().capturer.createCaptureSession();
}
}
@@ -1,74 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.jspecify.annotations.Nullable;
public class SimpleBlockCapture implements AutoCloseable {
private final MinecraftCaptureBridge capturingWorldLevel;
private final ServerLevel level;
private final @Nullable SimpleBlockCapture oldCapture;
private boolean isOverlayingCaptureOnLevel = false;
public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel level, @Nullable SimpleBlockCapture oldCapture) {
this.capturingWorldLevel = new MinecraftCaptureBridge(level, base);
this.level = level;
this.oldCapture = oldCapture;
}
public MinecraftCaptureBridge capturingWorldLevel() {
return this.capturingWorldLevel;
}
public boolean isCapturing() {
return true;
}
public List<BlockState> getCapturedSnapshots() {
return this.capturingWorldLevel.calculateLatestSnapshots(this.level);
}
public Stream<Block> getAffectedBlocks() {
return this.capturingWorldLevel.getAffectedBlocks(this.level);
}
public net.minecraft.world.level.block.state.@Nullable BlockState getOverlayBlockState(BlockPos pos) {
return this.capturingWorldLevel.getLatestBlockState(pos);
}
public @Nullable Optional<BlockEntity> getOverlayBlockEntity(BlockPos pos) {
return this.capturingWorldLevel.getLatestBlockEntity(pos);
}
// This is done so that the captured blocks appear ontop of the world.
public void overlayCaptureOnLevel() {
this.isOverlayingCaptureOnLevel = true;
this.capturingWorldLevel.allowWriteOnLevel();
}
public boolean isOverlayingCaptureOnLevel() {
return this.isOverlayingCaptureOnLevel;
}
public void finalizePlacement() {
this.level.capturer.releaseCapture(this.oldCapture);
this.capturingWorldLevel.applyTasks();
}
@Override
public void close() {
this.level.capturer.releaseCapture(this.oldCapture);
}
public net.minecraft.world.level.block.state.@Nullable BlockState getCaptureBlockStateIfLoaded(BlockPos pos) {
return this.capturingWorldLevel.getBlockStateIfLoaded(pos);
}
}
@@ -1,109 +0,0 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
// Block state
class SimpleBlockPlacementPredictor implements BlockPlacementPredictor {
private final CaptureRecordMap guesstimationMap = new CaptureRecordMap();
public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
BlockState oldState = layer.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState());
// Don't do any processing if the same
if (oldState == blockState) {
return false;
} else {
Block newBlock = blockState.getBlock();
this.setLatestBlockAt(pos, blockState, updateFlags);
// TODO: local heightmaps?
// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, blockState);
// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, blockState);
// this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(i, y, i2, blockState);
// this.heightmaps.get(Heightmap.Types.WORLD_SURFACE).update(i, y, i2, blockState);
// LIGHT ENGINE CALCULATIONS
boolean blockChanged = !oldState.is(newBlock);
if (blockChanged && oldState.hasBlockEntity() && !blockState.shouldChangedStateKeepBlockEntity(oldState)) {
this.removeBlockEntity(pos);
}
// if ((blockChanged || newBlock instanceof BaseRailBlock) && ((updateFlags & Block.UPDATE_NEIGHBORS) != 0 || updateMoveByPiston)) {
// BlockState finalOldState = oldState;
// this.capturingWorldLevel.addTask((level) -> {
// finalOldState.affectNeighborsAfterRemoval(level, pos, updateMoveByPiston);
// });
// }
if (blockState.hasBlockEntity()) {
BlockEntity blockEntity = this.getLatestBlockEntityAt(pos).map(BlockEntityPlacement::blockEntity).orElse(null);
if (blockEntity != null && !blockEntity.isValidBlockState(blockState)) {
blockEntity = null;
}
if (blockEntity == null) {
blockEntity = ((EntityBlock) newBlock).newBlockEntity(pos, blockState);
if (blockEntity != null) {
this.addAndRegisterBlockEntity(blockEntity);
}
} else {
blockEntity.setBlockState(blockState);
}
}
}
return true;
}
private void addAndRegisterBlockEntity(BlockEntity blockEntity) {
this.guesstimationMap.setLatestBlockEntityAt(blockEntity.getBlockPos(), false, blockEntity);
}
private void removeBlockEntity(BlockPos pos) {
this.guesstimationMap.setLatestBlockEntityAt(pos, true, null);
}
public boolean isEmpty() {
return this.guesstimationMap.isEmpty();
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
Optional<BlockEntity> value = this.guesstimationMap.getLatestBlockEntityAt(pos);
if (value == null) {
return Optional.empty();
} else {
return value
.map(block -> new BlockEntityPlacement(false, block))
.or(() -> BlockEntityPlacement.ABSENT);
}
}
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos));
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos))
.map((state) -> new LoadedBlockState(true, state));
}
public void setLatestBlockAt(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
this.guesstimationMap.setLatestBlockStateAt(pos, blockState, updateFlags);
}
public CaptureRecordMap getRecordMap() {
return this.guesstimationMap;
}
}
@@ -1,38 +0,0 @@
package io.papermc.paper.util.capture;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.Nullable;
// TODO: Cleanup this state, because its held on the server world. I had proper state handling but threw it away at some point
public class WorldCapturer {
public final ServerLevel level;
private @Nullable SimpleBlockCapture capture;
public WorldCapturer(Level level) {
this.level = (ServerLevel) level;
}
public SimpleBlockCapture createCaptureSession(BlockPlacementPredictor blockPlacementPredictor) {
this.capture = new SimpleBlockCapture(blockPlacementPredictor, this.level, this.capture);
return this.capture;
}
public SimpleBlockCapture createCaptureSession() {
return this.createCaptureSession(new LiveBlockPlacementLayer(this, this.level));
}
public void releaseCapture(@Nullable SimpleBlockCapture oldCapture) {
this.capture = oldCapture;
}
public @Nullable SimpleBlockCapture getCapture() {
return this.capture;
}
public boolean isCapturing() {
return this.capture != null;
}
}
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more