Compare commits

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

No files matched your search

@@ -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);
}
}
@@ -0,0 +1,68 @@
package io.papermc.paper.registry.keys.tags;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.annotation.GeneratedClass;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.world.Timeline;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla tag keys for {@link RegistryKey#TIMELINE}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedClass
public final class TimelineTagKeys {
/**
* {@code #minecraft:in_end}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> IN_END = create(key("in_end"));
/**
* {@code #minecraft:in_nether}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> IN_NETHER = create(key("in_nether"));
/**
* {@code #minecraft:in_overworld}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> IN_OVERWORLD = create(key("in_overworld"));
/**
* {@code #minecraft:universal}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<Timeline> UNIVERSAL = create(key("universal"));
private TimelineTagKeys() {
}
/**
* Creates a tag key for {@link Timeline} in the registry {@code minecraft:timeline}.
*
* @param key the tag key's key
* @return a new tag key
*/
public static TagKey<Timeline> create(final Key key) {
return TagKey.create(RegistryKey.TIMELINE, key);
}
}
@@ -3,6 +3,8 @@ package io.papermc.paper.registry;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.dialog.Dialog;
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;
@@ -251,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 {
}
@@ -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.
*
+26 -4
View File
@@ -1,6 +1,8 @@
package org.bukkit;
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;
@@ -2071,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);
/**
@@ -2090,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
/**
@@ -1,6 +1,13 @@
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 org.bukkit.command.CommandSender;
import org.bukkit.generator.BiomeProvider;
@@ -25,6 +32,8 @@ public class WorldCreator {
private String generatorSettings = "";
private boolean hardcore = false;
private boolean bonusChest = false;
private TypedKey<WorldClock> clock = null;
private List<TypedKey<Timeline>> timelines = null;
/**
* Creates an empty WorldCreationOptions for the given world name.
@@ -152,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;
}
@@ -175,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;
}
@@ -513,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>
@@ -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
}
}
@@ -17,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;
@@ -65,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;
@@ -72,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;
@@ -201,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 ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf164195b730e22 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;
@@ -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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f4597103e7a111 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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
// 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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
// 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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
}
@@ -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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
return false;
} else {
this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
@@ -2103,7 +2414,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
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()));
@@ -2121,13 +2432,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
Path entities = rootDir.resolve("entities.csv");
@@ -2222,8 +2533,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
this.blockEntityTickers.size(),
getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
this.getBlockTicks().count(),
@@ -2256,15 +2567,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
public void startTickingChunk(final LevelChunk levelChunk) {
@@ -2281,8 +2602,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
for (ChunkPos chunk : chunks) {
if (!this.areEntitiesLoaded(chunk.pack())) {
@@ -2303,28 +2624,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
public boolean anyPlayerCloseEnoughForSpawning(final BlockPos pos) {
@@ -2341,7 +2672,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 bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
@Override
@@ -2389,7 +2723,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();
@@ -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 81c3f9f04040b6cfa014ce55e517bd9b374ec252..0958fe0438c3e934b885acf78a0f8003ef8bb694 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 81c3f9f04040b6cfa014ce55e517bd9b374ec252..0958fe0438c3e934b885acf78a0f8003
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() {
@@ -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");
@@ -816,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
@@ -2326,7 +2326,7 @@ index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 6d847ff3646b27fe9bc75e792d813a7f611ab3e5..cfc30654917703a9278143a2c36d37eb719ee061 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 6d847ff3646b27fe9bc75e792d813a7f611ab3e5..cfc30654917703a9278143a2c36d37eb
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
@@ -2766,6 +2767,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 6d847ff3646b27fe9bc75e792d813a7f611ab3e5..cfc30654917703a9278143a2c36d37eb
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;
}
@@ -5,7 +5,7 @@ Subject: [PATCH] Incremental chunk and player saving
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba12bb86bad 100644
index 9a014a1a198293c9ce02354d8128cb19666055b8..c6691f02a1b2c1540b959fcc07eb8390906541ec 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -958,7 +958,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -94,10 +94,10 @@ index def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba1
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 cfc30654917703a9278143a2c36d37eb719ee061..78021db10860396fef4eb2e01926fbad123916fa 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)));
}
@@ -48,7 +48,7 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
+ }
+}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 2b6717833b56c3e33f3ac656a376dd5c841dc1bc..2142757965b7345a011a92de08038473fa8ae320 100644
index fbd70bcdc2efb42505fd5b3e5a6f703782c4060d..000943733f07cef2e508f78aadf5d28dec70e90c 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1836,6 +1836,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -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
@@ -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);
}
@@ -1534,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);
@@ -1556,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;
}
@@ -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
}
@@ -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,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) {
@@ -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"),
@@ -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;
}
@@ -19,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;
@@ -52,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;
@@ -146,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();
}
}
}
@@ -3,11 +3,14 @@ package io.papermc.paper.world.saveddata;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Dynamic;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.Mth;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.clock.WorldClock;
import net.minecraft.world.Difficulty;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
@@ -28,7 +31,8 @@ public final class PaperLevelOverrides extends SavedData implements ServerLevelD
Codec.LONG.fieldOf("game_time").forGetter(levelOverrides -> levelOverrides.gameTime),
Codec.BOOL.fieldOf("initialized").forGetter(levelOverrides -> levelOverrides.initialized),
LEGACY_GAME_TYPE_CODEC.fieldOf("game_type").forGetter(levelOverrides -> levelOverrides.gameType),
LevelSettings.DifficultySettings.CODEC.fieldOf("difficulty_settings").forGetter(levelOverrides -> levelOverrides.difficultySettings)
LevelSettings.DifficultySettings.CODEC.fieldOf("difficulty_settings").forGetter(levelOverrides -> levelOverrides.difficultySettings),
ResourceKey.codec(Registries.WORLD_CLOCK).optionalFieldOf("clock").forGetter(levelOverrides -> levelOverrides.clock)
).apply(instance, PaperLevelOverrides::new));
public static final SavedDataType<PaperLevelOverrides> TYPE = new SavedDataType<>(
Identifier.fromNamespaceAndPath(Identifier.PAPER_NAMESPACE, "level_overrides"),
@@ -42,11 +46,12 @@ public final class PaperLevelOverrides extends SavedData implements ServerLevelD
private boolean initialized;
private GameType gameType;
private LevelSettings.DifficultySettings difficultySettings;
private Optional<ResourceKey<WorldClock>> clock;
private transient @Nullable PrimaryLevelData rootData;
private transient ResourceKey<Level> dimension = Level.OVERWORLD;
private PaperLevelOverrides() {
this(RespawnData.DEFAULT, 0L, false, GameType.SURVIVAL, LevelSettings.DifficultySettings.DEFAULT);
this(RespawnData.DEFAULT, 0L, false, GameType.SURVIVAL, LevelSettings.DifficultySettings.DEFAULT, Optional.empty());
}
private PaperLevelOverrides(
@@ -54,13 +59,15 @@ public final class PaperLevelOverrides extends SavedData implements ServerLevelD
final long gameTime,
final boolean initialized,
final GameType gameType,
final LevelSettings.DifficultySettings difficultySettings
final LevelSettings.DifficultySettings difficultySettings,
final Optional<ResourceKey<WorldClock>> clock
) {
this.respawnData = respawnData.normalized();
this.gameTime = gameTime;
this.initialized = initialized;
this.gameType = gameType;
this.difficultySettings = difficultySettings;
this.clock = clock;
}
public static PaperLevelOverrides createFromLiveLevelData(final PrimaryLevelData rootData) {
@@ -69,7 +76,8 @@ public final class PaperLevelOverrides extends SavedData implements ServerLevelD
rootData.getGameTime(),
false,
rootData.getGameType(),
new LevelSettings.DifficultySettings(rootData.getDifficulty(), rootData.isHardcore(), rootData.isDifficultyLocked())
new LevelSettings.DifficultySettings(rootData.getDifficulty(), rootData.isHardcore(), rootData.isDifficultyLocked()),
Optional.empty()
);
}
@@ -82,7 +90,8 @@ public final class PaperLevelOverrides extends SavedData implements ServerLevelD
levelData.get("Time").asLong(0L),
levelData.get("initialized").asBoolean(true),
GameType.byId(levelData.get("GameType").asInt(GameType.SURVIVAL.getId())),
levelData.get("difficulty_settings").read(LevelSettings.DifficultySettings.CODEC).result().orElse(LevelSettings.DifficultySettings.DEFAULT)
levelData.get("difficulty_settings").read(LevelSettings.DifficultySettings.CODEC).result().orElse(LevelSettings.DifficultySettings.DEFAULT),
Optional.empty()
);
}
@@ -113,6 +122,18 @@ public final class PaperLevelOverrides extends SavedData implements ServerLevelD
}
}
public Optional<ResourceKey<WorldClock>> clock() {
return this.clock;
}
public void setClock(final @Nullable ResourceKey<WorldClock> clock) {
final Optional<ResourceKey<WorldClock>> newClock = Optional.ofNullable(clock);
if (!this.clock.equals(newClock)) {
this.clock = newClock;
this.setDirty();
}
}
public void setDifficultyLocked(final boolean difficultyLocked) {
if (this.difficultySettings.locked() != difficultyLocked) {
this.setDifficultySettings(new LevelSettings.DifficultySettings(this.difficultySettings.difficulty(), this.difficultySettings.hardcore(), difficultyLocked));
@@ -13,7 +13,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.papermc.paper.configuration.GlobalConfiguration;
import io.papermc.paper.configuration.PaperServerConfiguration;
import io.papermc.paper.configuration.ServerConfiguration;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.world.PaperWorldLoader;
import io.papermc.paper.world.WorldClock;
import io.papermc.paper.world.migration.WorldFolderMigration;
import io.papermc.paper.world.saveddata.PaperLevelOverrides;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -45,6 +47,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import net.minecraft.SharedConstants;
import net.md_5.bungee.api.chat.BaseComponent;
import net.minecraft.Optionull;
import net.minecraft.advancements.AdvancementHolder;
@@ -52,6 +55,8 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
@@ -99,6 +104,7 @@ import net.minecraft.world.level.CustomSpawner;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.PatrolSpawner;
import net.minecraft.world.level.levelgen.PhantomSpawner;
@@ -211,6 +217,7 @@ import org.bukkit.entity.SpawnCategory;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.server.BroadcastMessageEvent;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.event.world.ClockTimeSkipEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
@@ -908,6 +915,58 @@ public final class CraftServer implements Server {
return new ArrayList<World>(this.worlds.values());
}
@Override
public long getTime(final WorldClock clock) {
long time = this.getFullTime(clock) % SharedConstants.TICKS_PER_GAME_DAY;
if (time < 0) time += SharedConstants.TICKS_PER_GAME_DAY;
return time;
}
@Override
public void setTime(final WorldClock clock, final long time) {
long margin = (time - this.getFullTime(clock)) % SharedConstants.TICKS_PER_GAME_DAY;
if (margin < 0) margin += SharedConstants.TICKS_PER_GAME_DAY;
this.setFullTime(clock, this.getFullTime(clock) + margin);
}
@Override
public long getFullTime(final WorldClock clock) {
return this.console.clockManager().getTotalTicks(CraftWorldClock.bukkitToMinecraftHolder(clock));
}
@Override
public void setFullTime(final WorldClock clock, final long time) {
final Holder<net.minecraft.world.clock.WorldClock> nmsClock = CraftWorldClock.bukkitToMinecraftHolder(clock);
final long currentClockTime = this.console.clockManager().getTotalTicks(nmsClock);
final ClockTimeSkipEvent event = new ClockTimeSkipEvent(clock, ClockTimeSkipEvent.SkipReason.CUSTOM, time - currentClockTime);
this.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
this.console.clockManager().setTotalTicks(nmsClock, currentClockTime + event.getSkipAmount());
}
@Override
public boolean isClockPaused(final WorldClock clock) {
return this.console.clockManager().isPaused(CraftWorldClock.bukkitToMinecraftHolder(clock));
}
@Override
public void setClockPaused(final WorldClock clock, final boolean paused) {
this.console.clockManager().setPaused(CraftWorldClock.bukkitToMinecraftHolder(clock), paused);
}
@Override
public float getClockRate(final WorldClock clock) {
return this.console.clockManager().rate(CraftWorldClock.bukkitToMinecraftHolder(clock));
}
@Override
public void setClockRate(final WorldClock clock, final float rate) {
this.console.clockManager().setRate(CraftWorldClock.bukkitToMinecraftHolder(clock), rate);
}
@Override
public boolean isTickingWorlds() {
return console.isIteratingOverLevels;
@@ -1267,6 +1326,17 @@ public final class CraftServer implements Server {
if (customStem == null) {
throw new IllegalStateException("Missing level stem for world " + name + " using key " + actualDimension);
}
boolean dimensionTypeChanged = false;
if (creator.timelines() != null) {
customStem = this.createLevelStemWithClockAndTimelines(customStem, creator, this.console.registryAccess());
dimensionTypeChanged = true;
}
WorldGenSettings savedWorldGenSettings = genSettingsFinal;
if (dimensionTypeChanged) {
final Map<ResourceKey<LevelStem>, LevelStem> dimensions = new LinkedHashMap<>(genSettingsFinal.dimensions().dimensions());
dimensions.put(actualDimension, customStem);
savedWorldGenSettings = new WorldGenSettings(genSettingsFinal.options(), new WorldDimensions(dimensions));
}
WorldInfo worldInfo = new CraftWorldInfo(loadedWorldData.bukkitName(), CraftNamespacedKey.fromMinecraft(dimensionKey.identifier()), genSettingsFinal.options().seed(), primaryLevelData.enabledFeatures(), creator.environment(), customStem.type().value(), customStem.generator(), this.getHandle().getServer().registryAccess(), loadedWorldData.uuid());
if (biomeProvider == null && chunkGenerator != null) {
@@ -1274,7 +1344,8 @@ public final class CraftServer implements Server {
}
final SavedDataStorage savedDataStorage = new SavedDataStorage(this.console.storageSource.getDimensionPath(dimensionKey).resolve(LevelResource.DATA.id()), this.console.getFixerUpper(), this.console.registryAccess());
savedDataStorage.set(WorldGenSettings.TYPE, new WorldGenSettings(genSettingsFinal.options(), genSettingsFinal.dimensions()));
loadedWorldData.levelOverrides().setClock(creator.clock() == null ? null : PaperRegistries.toNms(creator.clock()));
savedDataStorage.set(WorldGenSettings.TYPE, savedWorldGenSettings);
List<CustomSpawner> list = ImmutableList.of(
new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(savedDataStorage)
);
@@ -1312,6 +1383,33 @@ public final class CraftServer implements Server {
return serverLevel.getWorld();
}
private LevelStem createLevelStemWithClockAndTimelines(final LevelStem stem, final WorldCreator creator, final RegistryAccess registryAccess) {
final DimensionType type = stem.type().value();
final List<io.papermc.paper.registry.TypedKey<io.papermc.paper.world.Timeline>> timelineKeys = creator.timelines();
final HolderSet<net.minecraft.world.timeline.Timeline> timelines = timelineKeys == null
? type.timelines()
: HolderSet.direct(timelineKeys.stream().map(key -> registryAccess.lookupOrThrow(Registries.TIMELINE).getOrThrow(PaperRegistries.toNms(key))).toList());
final DimensionType newType = new DimensionType(
type.hasFixedTime(),
type.hasSkyLight(),
type.hasCeiling(),
type.hasEnderDragonFight(),
type.coordinateScale(),
type.minY(),
type.height(),
type.logicalHeight(),
type.infiniburn(),
type.ambientLight(),
type.monsterSettings(),
type.skybox(),
type.cardinalLightType(),
type.attributes(),
timelines,
type.defaultClock()
);
return new LevelStem(Holder.direct(newType), stem.generator());
}
@Override
public boolean unloadWorld(String name, boolean save) {
return this.unloadWorld(this.getWorld(name), save);
@@ -0,0 +1,21 @@
package org.bukkit.craftbukkit;
import io.papermc.paper.registry.HolderableBase;
import io.papermc.paper.world.Timeline;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
public class CraftTimeline extends HolderableBase<net.minecraft.world.timeline.Timeline> implements Timeline {
public static Timeline minecraftHolderToBukkit(final Holder<net.minecraft.world.timeline.Timeline> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.TIMELINE);
}
public static Holder<net.minecraft.world.timeline.Timeline> bukkitToMinecraftHolder(final Timeline bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftTimeline(final Holder<net.minecraft.world.timeline.Timeline> holder) {
super(holder);
}
}
@@ -13,6 +13,8 @@ import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilderImpl;
import io.papermc.paper.raytracing.RayTraceTarget;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -139,8 +141,6 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import org.bukkit.event.weather.LightningStrikeEvent;
import org.bukkit.event.weather.ThunderChangeEvent;
import org.bukkit.event.weather.WeatherChangeEvent;
import org.bukkit.event.world.SpawnChangeEvent;
import org.bukkit.event.world.TimeSkipEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
@@ -793,24 +793,31 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public long getFullTime() {
return this.world.getDefaultClockTime();
final WorldClock clock = this.getClock();
return clock == null ? 0L : this.server.getFullTime(clock);
}
@Override
public void setFullTime(long time) {
if (this.world.dimensionType().defaultClock().isEmpty()) {
this.server.setFullTime(this.requireDefaultClock(), time);
}
@Override
public WorldClock getClock() {
return this.world.getClock().map(CraftWorldClock::minecraftHolderToBukkit).orElse(null);
}
@Override
public List<Timeline> getTimelines() {
return this.world.dimensionType().timelines().stream().map(CraftTimeline::minecraftHolderToBukkit).toList();
}
private WorldClock requireDefaultClock() {
final WorldClock clock = this.getClock();
if (clock == null) {
throw new IllegalArgumentException("Cannot set time in world without world clock");
}
final long currentClockTime = this.world.getDefaultClockTime();
final TimeSkipEvent event = new TimeSkipEvent(this, TimeSkipEvent.SkipReason.CUSTOM, time - currentClockTime);
this.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
// Updates the clock for all players
this.world.clockManager().setTotalTicks(this.world.dimensionType().defaultClock().get(), currentClockTime + event.getSkipAmount());
return clock;
}
// Paper start
@@ -0,0 +1,21 @@
package org.bukkit.craftbukkit;
import io.papermc.paper.registry.HolderableBase;
import io.papermc.paper.world.WorldClock;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
public class CraftWorldClock extends HolderableBase<net.minecraft.world.clock.WorldClock> implements WorldClock {
public static WorldClock minecraftHolderToBukkit(final Holder<net.minecraft.world.clock.WorldClock> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.WORLD_CLOCK);
}
public static Holder<net.minecraft.world.clock.WorldClock> bukkitToMinecraftHolder(final WorldClock bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public CraftWorldClock(final Holder<net.minecraft.world.clock.WorldClock> holder) {
super(holder);
}
}
@@ -1537,13 +1537,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa
this.getHandle().relativeTime = tickTime;
final ServerLevel level = this.getHandle().level();
if (this.getHandle().connection == null || level.dimensionType().defaultClock().isEmpty()) {
final java.util.Optional<Holder<WorldClock>> clock = level.getClock();
if (this.getHandle().connection == null || clock.isEmpty()) {
return;
}
final long gameTime = level.getGameTime();
final long playerClockTime = this.getHandle().getPlayerTime();
final Holder<WorldClock> worldClock = level.dimensionType().defaultClock().get();
final Holder<WorldClock> worldClock = clock.get();
final boolean paused = !this.getHandle().relativeTime || !level.getGameRules().get(GameRules.ADVANCE_TIME) || level.clockManager().isPaused(worldClock);
final ClockNetworkState clockState = new ClockNetworkState(
playerClockTime,
@@ -261,6 +261,7 @@ import org.bukkit.event.raid.RaidStopEvent;
import org.bukkit.event.raid.RaidTriggerEvent;
import org.bukkit.event.vehicle.VehicleCreateEvent;
import org.bukkit.event.weather.LightningStrikeEvent;
import org.bukkit.event.world.ClockTimeSkipEvent;
import org.bukkit.event.world.EntitiesLoadEvent;
import org.bukkit.event.world.EntitiesUnloadEvent;
import org.bukkit.event.world.LootGenerateEvent;
@@ -2417,4 +2418,9 @@ public class CraftEventFactory {
}
return true;
}
public static ClockTimeSkipEvent callTimeSkipEvent(final net.minecraft.core.Holder<net.minecraft.world.clock.WorldClock> clock, final long skipAmount) {
final io.papermc.paper.world.WorldClock bukkitClock = org.bukkit.craftbukkit.CraftWorldClock.minecraftHolderToBukkit(clock);
return new ClockTimeSkipEvent(bukkitClock, ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
}
}