Compare commits

...
Author SHA1 Message Date
david 8f1fad8687 No API records 2026-10-25 10:38:10 +01:00
david 431b4245ea Rebase 2026-10-25 10:22:03 +01:00
david 8f5f29305d AT instead of patch 2026-10-25 10:22:03 +01:00
david 4e8df4a990 Not experimental anymore :) 2026-10-25 10:22:03 +01:00
david 3612df6d9c Add world clock API 2026-10-25 10:22:00 +01:00
30 changed files with 839 additions and 65 deletions

No files matched your search

+1
View File
@@ -654,6 +654,7 @@ public net.minecraft.world.level.storage.SavedDataStorage cache
public net.minecraft.world.level.storage.TagValueInput input
public net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt$Reference getProvider(Lnet/minecraft/world/level/storage/loot/LootContext;)Ljava/util/Optional;
public net.minecraft.world.scores.criteria.ObjectiveCriteria CRITERIA_CACHE
public net.minecraft.world.timeline.Timeline tracks
public-f net.minecraft.server.MinecraftServer storageSource
public-f net.minecraft.server.ServerAdvancementManager advancements
public-f net.minecraft.server.dedicated.DedicatedServer serverLinks
@@ -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);
}
}
@@ -5,6 +5,8 @@ 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;
@@ -42,6 +44,7 @@ import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.map.MapCursor;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import static io.papermc.paper.registry.RegistryKeyImpl.create;
@@ -269,6 +272,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,132 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.util.Map;
import java.util.OptionalInt;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
import static io.papermc.paper.util.BoundChecker.requireNonNegative;
/**
* A data-centric version-specific registry entry for the {@link Timeline} type.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface TimelineRegistryEntry {
/**
* Gets the clock that drives this timeline.
*
* @return the clock
*/
@Contract(pure = true)
TypedKey<WorldClock> clock();
/**
* Gets the timeline period, if any.
*
* @return the timeline period
*/
@Contract(pure = true)
OptionalInt periodTicks();
/**
* Gets the time markers defined by this timeline.
*
* @return the time markers
*/
@Contract(pure = true)
Map<Key, TimeMarker> timeMarkers();
/**
* A mutable builder for the {@link TimelineRegistryEntry} plugins may change in applicable registry events.
* <p>
* The following values are required for each builder:
* <ul>
* <li>{@link #clock(TypedKey)}</li>
* </ul>
*/
@ApiStatus.NonExtendable
interface Builder extends TimelineRegistryEntry, RegistryBuilder<Timeline> {
/**
* Sets the clock that drives this timeline.
*
* @param clock the clock
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder clock(TypedKey<WorldClock> clock);
/**
* Sets the timeline period.
*
* @param periodTicks the period in ticks
* @return this builder
*/
@Contract(value = "_ -> this", mutates = "this")
Builder periodTicks(@Range(from = 1, to = Integer.MAX_VALUE) int periodTicks);
/**
* Clears the timeline period.
*
* @return this builder
*/
@Contract(value = "-> this", mutates = "this")
Builder clearPeriodTicks();
/**
* Adds or replaces a time marker on this timeline.
*
* @param key the time marker key
* @param ticks the marker tick
* @param showInCommands whether the marker should be suggested in commands
* @return this builder
*/
@Contract(value = "_, _, _ -> this", mutates = "this")
Builder addTimeMarker(Key key, @Range(from = 0, to = Integer.MAX_VALUE) int ticks, boolean showInCommands);
}
/**
* A time marker defined by a timeline.
*/
@ApiStatus.NonExtendable
interface TimeMarker {
/**
* Gets the tick this marker occurs on.
*
* @return the marker tick
*/
@Range(from = 0, to = Integer.MAX_VALUE)
int ticks();
/**
* Gets whether this marker should be suggested in commands.
*
* @return whether the marker should be suggested in commands
*/
boolean showInCommands();
/**
* Creates a new time marker instance based on the passed values.
*
* @param ticks the tick this marker occurs on, as returned by {@link #ticks()}
* @param showInCommands whether the marker should be suggested in commands, as returned by {@link #showInCommands()}
* @return the created instance
*/
@Contract(value = "_, _ -> new", pure = true)
static TimeMarker of(@Range(from = 0, to = Integer.MAX_VALUE) final int ticks, final boolean showInCommands) {
record Impl(int ticks, boolean showInCommands) implements TimeMarker {
}
return new Impl(requireNonNegative(ticks, "ticks"), showInCommands);
}
}
}
@@ -0,0 +1,19 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.world.WorldClock;
import org.jetbrains.annotations.ApiStatus;
/**
* A data-centric version-specific registry entry for the {@link WorldClock} type.
*/
@ApiStatus.NonExtendable
public interface WorldClockRegistryEntry {
/**
* A mutable builder for the {@link WorldClockRegistryEntry} plugins may change in applicable registry events.
*/
@ApiStatus.NonExtendable
interface Builder extends WorldClockRegistryEntry, RegistryBuilder<WorldClock> {
}
}
@@ -17,11 +17,15 @@ import io.papermc.paper.registry.data.JukeboxSongRegistryEntry;
import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PigVariantRegistryEntry;
import io.papermc.paper.registry.data.SulfurCubeArchetypeRegistryEntry;
import io.papermc.paper.registry.data.TimelineRegistryEntry;
import io.papermc.paper.registry.data.TrimMaterialRegistryEntry;
import io.papermc.paper.registry.data.TrimPatternRegistryEntry;
import io.papermc.paper.registry.data.WolfVariantRegistryEntry;
import io.papermc.paper.registry.data.WorldClockRegistryEntry;
import io.papermc.paper.registry.data.ZombieNautilusVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import org.bukkit.Art;
import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
@@ -68,6 +72,8 @@ public final class RegistryEvents {
public static final RegistryEventProvider<ZombieNautilus.Variant, ZombieNautilusVariantRegistryEntry.Builder> ZOMBIE_NAUTILUS_VARIANT = create(RegistryKey.ZOMBIE_NAUTILUS_VARIANT);
public static final RegistryEventProvider<SulfurCube.Archetype, SulfurCubeArchetypeRegistryEntry.Builder> SULFUR_CUBE_ARCHETYPE = create(RegistryKey.SULFUR_CUBE_ARCHETYPE);
public static final RegistryEventProvider<Dialog, DialogRegistryEntry.Builder> DIALOG = create(RegistryKey.DIALOG);
public static final RegistryEventProvider<WorldClock, WorldClockRegistryEntry.Builder> WORLD_CLOCK = create(RegistryKey.WORLD_CLOCK);
public static final RegistryEventProvider<Timeline, TimelineRegistryEntry.Builder> TIMELINE = create(RegistryKey.TIMELINE);
// End generate - RegistryEvents
private RegistryEvents() {
@@ -0,0 +1,31 @@
package io.papermc.paper.world;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Represents a timeline that can apply environment attributes or define time markers for a {@link WorldClock}.
*/
@NullMarked
@ApiStatus.NonExtendable
public interface Timeline extends Keyed {
// Start generate - Timeline
Timeline DAY = getTimeline("day");
Timeline EARLY_GAME = getTimeline("early_game");
Timeline MOON = getTimeline("moon");
Timeline VILLAGER_SCHEDULE = getTimeline("villager_schedule");
// End generate - Timeline
private static Timeline getTimeline(@KeyPattern.Value final String key) {
return RegistryAccess.registryAccess().getRegistry(RegistryKey.TIMELINE).getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key));
}
}
@@ -0,0 +1,27 @@
package io.papermc.paper.world;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Keyed;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Represents a world clock that tracks time for worlds and drives timelines.
*/
@NullMarked
@ApiStatus.NonExtendable
public interface WorldClock extends Keyed {
// Start generate - WorldClock
WorldClock OVERWORLD = getWorldClock("overworld");
WorldClock THE_END = getWorldClock("the_end");
// End generate - WorldClock
private static WorldClock getWorldClock(@KeyPattern.Value final String key) {
return RegistryAccess.registryAccess().getRegistry(RegistryKey.WORLD_CLOCK).getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key));
}
}
+67 -4
View File
@@ -4,6 +4,7 @@ import io.papermc.paper.entity.poi.PoiSearchResult;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.math.Position;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import io.papermc.paper.world.WorldClock;
import io.papermc.paper.world.chunk.ChunkGenerationState;
import java.io.File;
import java.nio.file.Path;
@@ -2116,7 +2117,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* @return The current relative time
* @see #getFullTime() Returns an absolute time of this world
*/
public long getTime();
long getTime();
/**
* Sets the relative in-game time on the server.
@@ -2131,7 +2132,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* hours*1000)
* @see #setFullTime(long) Sets the absolute time of this world
*/
public void setTime(long time);
void setTime(long time);
/**
* Gets the full in-game time on this world, or {@code 0} if this world does not have a world clock.
@@ -2139,7 +2140,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* @return The current absolute time
* @see #getTime() Returns a relative time of this world
*/
public long getFullTime();
long getFullTime();
/**
* Sets the in-game time on the server
@@ -2151,7 +2152,69 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* @see #setTime(long) Sets the relative time of this world
* @throws IllegalArgumentException if this world does not have a world clock (e.g. the nether)
*/
public void setFullTime(long time);
void setFullTime(long time);
/**
* Gets the default clock of this world's dimension type.
* <p>
* This is the clock used by {@link #getTime()}, {@link #getFullTime()} and their setters.
*
* @return the default clock, or {@code null} if this world does not have one (e.g. the nether)
*/
@Nullable WorldClock getDefaultClock();
/**
* Gets the total number of ticks the given clock has advanced in this world.
* <p>
* Clocks are per world unless the server is configured to share them between all worlds.
*
* @param clock the clock
* @return the total ticks of the clock
*/
long getFullTime(@NotNull WorldClock clock);
/**
* Sets the total number of ticks of the given clock in this world.
* <p>
* This calls a {@link org.bukkit.event.world.ClockTimeSkipEvent} and does nothing if it is cancelled.
* Clocks are per world unless the server is configured to share them between all worlds.
*
* @param clock the clock
* @param time the new total ticks of the clock, must not be negative
*/
void setFullTime(@NotNull WorldClock clock, long time);
/**
* Gets whether the given clock is paused in this world.
*
* @param clock the clock
* @return whether the clock is paused
*/
boolean isClockPaused(@NotNull WorldClock clock);
/**
* Sets whether the given clock is paused in this world.
*
* @param clock the clock
* @param paused whether the clock should be paused
*/
void setClockPaused(@NotNull WorldClock clock, boolean paused);
/**
* Gets the rate at which the given clock advances in this world, in clock ticks per game tick.
*
* @param clock the clock
* @return the clock rate
*/
float getClockRate(@NotNull WorldClock clock);
/**
* Sets the rate at which the given clock advances in this world, in clock ticks per game tick.
*
* @param clock the clock
* @param rate the new clock rate, must be positive
*/
void setClockRate(@NotNull WorldClock clock, float rate);
// Paper start
@@ -1,5 +1,6 @@
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;
@@ -11,24 +12,33 @@ import org.jspecify.annotations.NullMarked;
* <p>
* If the event is cancelled the time will not change.
*/
// TODO - snapshot - 26.1 clock
@ApiStatus.Experimental
@NullMarked
public class ClockTimeSkipEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final WorldClock clock;
private final SkipReason skipReason;
private long skipAmount;
private boolean cancelled;
@ApiStatus.Internal
public ClockTimeSkipEvent(final SkipReason skipReason, final long skipAmount) {
public ClockTimeSkipEvent(final WorldClock clock, final SkipReason skipReason, final long skipAmount) {
this.clock = clock;
this.skipReason = skipReason;
this.skipAmount = skipAmount;
}
/**
* Gets the clock whose time is skipping.
*
* @return the clock
*/
public WorldClock getClock() {
return this.clock;
}
/**
* Gets the reason why the time has skipped.
*
@@ -1,5 +1,6 @@
package org.bukkit.event.world;
import io.papermc.paper.world.WorldClock;
import org.bukkit.World;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
@@ -19,8 +20,8 @@ public class TimeSkipEvent extends ClockTimeSkipEvent {
private final World world;
@ApiStatus.Internal
public TimeSkipEvent(@NotNull World world, @NotNull SkipReason skipReason, long skipAmount) {
super(skipReason, skipAmount);
public TimeSkipEvent(@NotNull World world, @NotNull WorldClock clock, @NotNull SkipReason skipReason, long skipAmount) {
super(clock, skipReason, skipAmount);
this.world = world;
}
@@ -29,7 +29,9 @@ import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.entity.RemovalReason;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.item.MapPostProcessing;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WeatheringCopperState;
import io.papermc.paper.world.WorldClock;
import io.papermc.typewriter.preset.EnumCloneRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
import io.papermc.typewriter.replace.SearchMetadata;
@@ -243,6 +245,8 @@ public final class Rewriters {
.register("ZombieNautilusVariant", ZombieNautilus.Variant.class, new RegistryFieldRewriter<>(Registries.ZOMBIE_NAUTILUS_VARIANT, "getVariant"))
.register("SulfurCubeArchetype", SulfurCube.Archetype.class, new RegistryFieldRewriter<>(Registries.SULFUR_CUBE_ARCHETYPE, "getArchetype"))
.register("Dialog", Dialog.class, new RegistryFieldRewriter<>(Registries.DIALOG, "getDialog"))
.register("WorldClock", WorldClock.class, new RegistryFieldRewriter<>(Registries.WORLD_CLOCK, "getWorldClock"))
.register("Timeline", Timeline.class, new RegistryFieldRewriter<>(Registries.TIMELINE, "getTimeline"))
.register("PoiTypes", PoiTypes.class, new RegistryFieldRewriter<>(Registries.POINT_OF_INTEREST_TYPE, "get"))
.register("MemoryKey", MemoryKey.class, new MemoryKeyRewriter())
// .register("ItemType", org.bukkit.inventory.ItemType.class, new io.papermc.generator.rewriter.types.simple.ItemTypeRewriter()) // - disable for now, lynx want the generic type
@@ -21,11 +21,15 @@ import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PigVariantRegistryEntry;
import io.papermc.paper.registry.data.SoundEventRegistryEntry;
import io.papermc.paper.registry.data.SulfurCubeArchetypeRegistryEntry;
import io.papermc.paper.registry.data.TimelineRegistryEntry;
import io.papermc.paper.registry.data.TrimMaterialRegistryEntry;
import io.papermc.paper.registry.data.TrimPatternRegistryEntry;
import io.papermc.paper.registry.data.WolfVariantRegistryEntry;
import io.papermc.paper.registry.data.WorldClockRegistryEntry;
import io.papermc.paper.registry.data.ZombieNautilusVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.DialogRegistryEntry;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@@ -45,6 +49,7 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dialog.Dialogs;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.clock.WorldClocks;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.SulfurCubeArchetypes;
@@ -80,6 +85,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;
@@ -213,7 +219,9 @@ public final class RegistryEntries {
entry(Registries.PIG_SOUND_VARIANT, PigSoundVariants.class, Pig.SoundVariant.class),
entry(Registries.ZOMBIE_NAUTILUS_VARIANT, ZombieNautilusVariants.class, ZombieNautilus.Variant.class).writableApiRegistryBuilder(ZombieNautilusVariantRegistryEntry.Builder.class, "PaperZombieNautilusVariantRegistryEntry.PaperBuilder"),
entry(Registries.SULFUR_CUBE_ARCHETYPE, SulfurCubeArchetypes.class, SulfurCube.Archetype.class).writableApiRegistryBuilder(SulfurCubeArchetypeRegistryEntry.Builder.class, "PaperSulfurCubeArchetypeRegistryEntry.PaperBuilder"),
entry(Registries.DIALOG, Dialogs.class, Dialog.class, "Paper").allowDirect().writableApiRegistryBuilder(DialogRegistryEntry.Builder.class, "PaperDialogRegistryEntry.PaperBuilder")
entry(Registries.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(
@@ -30067,7 +30067,7 @@ index e7ddd956a7f942f682f60062eecf0fad95b18749..267259a79c7668da85d206f9dcbde16b
List<Entity> passengers = this.entity.getPassengers();
if (!passengers.equals(this.lastPassengers)) {
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1af034d73 100644
index c4a9f7436de72d54cf67519be584f8862d289112..d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -183,7 +183,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
@@ -30489,7 +30489,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
}
@@ -637,8 +895,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -638,8 +896,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push("checkDespawn");
entity.checkDespawn();
profiler.pop();
@@ -30499,7 +30499,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
Entity vehicle = entity.getVehicle();
if (vehicle != null) {
if (!vehicle.isRemoved() && vehicle.hasPassenger(entity)) {
@@ -665,7 +922,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -666,7 +923,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
profiler.push("entityManagement");
@@ -30508,7 +30508,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
profiler.pop();
profiler.push("debugSynchronizers");
if (this.debugSynchronizers.hasAnySubscriberFor(DebugSubscriptions.NEIGHBOR_UPDATES)) {
@@ -682,7 +939,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -683,7 +940,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public boolean shouldTickBlocksAt(final long chunkPos) {
@@ -30520,7 +30520,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
protected void tickTime() {
@@ -706,7 +966,60 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -707,7 +967,60 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
}
@@ -30581,7 +30581,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
ChunkPos chunkPos = chunk.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
@@ -715,7 +1028,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -716,7 +1029,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
for (int i = 0; i < tickSpeed; i++) {
@@ -30590,7 +30590,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
this.tickPrecipitation(this.getBlockRandomPos(minX, 0, minZ, 15));
}
}
@@ -723,31 +1036,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -724,31 +1037,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.popPush("tickBlocks");
if (tickSpeed > 0) {
@@ -30623,7 +30623,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
profiler.pop();
@@ -1070,6 +1359,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1071,6 +1360,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (fluidState.is(type)) {
fluidState.tick(this, pos, blockState);
}
@@ -30636,7 +30636,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
private void tickBlock(final BlockPos pos, final Block type) {
@@ -1077,6 +1372,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1078,6 +1373,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (state.is(type)) {
state.tick(this, pos, this.random);
}
@@ -30649,7 +30649,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
// Paper start - log detailed entity tick information
@@ -1157,6 +1458,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1158,6 +1459,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public void save(final @Nullable ProgressListener progressListener, final boolean flush, final boolean noSave) {
@@ -30661,7 +30661,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
ServerChunkCache chunkSource = this.getChunkSource();
if (!noSave) {
new org.bukkit.event.world.WorldSaveEvent(this.getWorld()).callEvent(); // CraftBukkit
@@ -1169,13 +1475,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1170,13 +1476,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
progressListener.progressStage(Component.translatable("menu.savingChunks"));
}
@@ -30685,7 +30685,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
private void saveLevelData(final boolean sync) {
@@ -1296,7 +1607,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1297,7 +1608,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.removePlayerImmediately((ServerPlayer)existing, Entity.RemovalReason.DISCARDED);
}
@@ -30694,7 +30694,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
// CraftBukkit start
@@ -1327,7 +1638,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1328,7 +1639,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// CraftBukkit end
@@ -30703,7 +30703,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
}
@@ -1338,7 +1649,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1339,7 +1650,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean tryAddFreshEntityWithPassengers(final Entity entity, final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
// CraftBukkit end
@@ -30712,7 +30712,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
return false;
}
@@ -2389,7 +2700,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2390,7 +2701,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
}
@@ -30721,7 +30721,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
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()));
@@ -2407,13 +2718,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2408,13 +2719,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Path chunks = rootDir.resolve("chunks.csv");
try (Writer output = Files.newBufferedWriter(chunks)) {
@@ -30737,7 +30737,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
Path entities = rootDir.resolve("entities.csv");
@@ -2508,8 +2819,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2509,8 +2820,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Locale.ROOT,
"players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s",
this.players.size(),
@@ -30748,7 +30748,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
this.blockEntityTickers.size(),
getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
this.getBlockTicks().count(),
@@ -2542,15 +2853,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2543,15 +2854,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public LevelEntityGetter<Entity> getEntities() {
org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -30777,7 +30777,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
public void startTickingChunk(final LevelChunk levelChunk) {
@@ -2567,8 +2888,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2568,8 +2889,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public void waitForEntities(final ChunkPos centerChunk, final int radius) {
List<ChunkPos> chunks = ChunkPos.rangeClosed(centerChunk, radius).toList();
@@ -30788,7 +30788,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
for (ChunkPos chunk : chunks) {
if (!this.areEntitiesLoaded(chunk.pack())) {
@@ -2587,28 +2908,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2588,28 +2909,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public void close() throws IOException {
super.close();
@@ -30833,7 +30833,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
public boolean anyPlayerCloseEnoughForSpawning(final BlockPos pos) {
@@ -2625,7 +2956,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2626,7 +2957,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public boolean canSpawnEntitiesInChunk(final ChunkPos pos) {
@@ -30845,7 +30845,7 @@ index 9193fc073a6dbf0f7dcdd2b06ddf59a4e4355428..5090c9780ada3c8a7fd070bc6aec3ff1
}
@Override
@@ -2663,7 +2997,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2664,7 +2998,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public CrashReportCategory fillReportDetails(final CrashReport report) {
CrashReportCategory category = super.fillReportDetails(report);
WeatherData weatherData = this.getWeatherData();
@@ -355,10 +355,10 @@ index 0000000000000000000000000000000000000000..829f7685904fd741e00403647ae76d32
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 5090c9780ada3c8a7fd070bc6aec3ff1af034d73..a4e6225add32df93b0d6009d98ee322941b69938 100644
index d7b6c1d9058df4615bf2ba807e5f3450c4c06d5a..1170fb4c074c495b1a8afb4c5d50a3904b11e0f7 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -887,6 +887,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -888,6 +888,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.pop();
}
@@ -366,7 +366,7 @@ index 5090c9780ada3c8a7fd070bc6aec3ff1af034d73..a4e6225add32df93b0d6009d98ee3229
this.entityTickList
.forEach(
entity -> {
@@ -1404,12 +1405,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1405,12 +1406,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickNonPassenger");
entity.commonTick();
@@ -383,7 +383,7 @@ index 5090c9780ada3c8a7fd070bc6aec3ff1af034d73..a4e6225add32df93b0d6009d98ee3229
}
// Paper start - log detailed entity tick information
} finally {
@@ -1420,7 +1424,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1421,7 +1425,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - log detailed entity tick information
}
@@ -392,7 +392,7 @@ index 5090c9780ada3c8a7fd070bc6aec3ff1af034d73..a4e6225add32df93b0d6009d98ee3229
if (entity.isRemoved() || entity.getVehicle() != vehicle) {
entity.stopRiding();
} else if (entity instanceof Player || this.entityTickList.contains(entity)) {
@@ -1428,12 +1432,21 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1429,12 +1433,21 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickPassenger");
entity.commonTick();
@@ -463,7 +463,7 @@ index f74837dc0be560a1601132594587bcfc22dcf778..c5a212e73c2efeb1f440778cb3f1fa6e
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index bb61d4e4ecfe5bab6269fa3d4303dab95719d7bb..3d219969a55896b1495c54c0a12a25b49ba093f0 100644
index 71ce072c2c9a6349d895054184321cb336ae567b..62d534132f6a2d69df28e244fa296f24a007181f 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -388,6 +388,15 @@ public abstract class Entity
@@ -822,7 +822,7 @@ index f46cca0467bb4da5511bc953ce5b43fa606bf978..445c5cafad71028171c1f26193966177
+
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index d327301693c70affc866d355a08efe935e3656b4..efcf399ced67201525ea6e6b3bd46b93b7e1823d 100644
index a828576457e0bb932750dba507bdef7e8903e144..2f5ec35bf7f104e259f85d15b17c8064daa05a5e 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -154,6 +154,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -2321,7 +2321,7 @@ index 0000000000000000000000000000000000000000..505b5e39d0b4cf2a56c1c720f0c9ab01
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index bc2bacabb588bd6a644ac5226720c7451ac0e475..e4d38cfdac2b926afae3b1b96679b6ce45ea256a 100644
index 1170fb4c074c495b1a8afb4c5d50a3904b11e0f7..3560df07024e4423835bb724318fcea2adfd4cfe 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -238,6 +238,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2332,7 +2332,7 @@ index bc2bacabb588bd6a644ac5226720c7451ac0e475..e4d38cfdac2b926afae3b1b96679b6ce
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
@@ -3040,6 +3041,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -3041,6 +3042,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return this.debugSynchronizers;
}
@@ -83,10 +83,10 @@ index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5
ProfilerFiller profiler = Profiler.get();
this.server.spark.executeMainThreadTasks(); // Paper - spark
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index e197ac0ee5e49d7768a9de889c972b3d590f7951..85ab77c3d1d5420df648fd173eaf5ad4f4775a80 100644
index 3560df07024e4423835bb724318fcea2adfd4cfe..377c4373785ec5c14a7d7ce9e710c02943546fbe 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1470,6 +1470,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1471,6 +1471,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)));
}
@@ -45,11 +45,11 @@
+ // Paper start - per-world time
+ ServerClockManager clockManager = source.getLevel().clockManager();
+ long currentTotalTicks = clockManager.getInstance(clock).totalTicks();
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, totalTicks - currentTotalTicks);
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, clock, totalTicks - currentTotalTicks);
+ final long newTotalTicks;
+ if (event.callEvent()) {
+ newTotalTicks = currentTotalTicks + event.getSkipAmount();
+ clockManager.setTotalTicks(clock, newTotalTicks);
+ final boolean cancelled = !event.callEvent();
+ if (!cancelled) {
+ newTotalTicks = Math.max(currentTotalTicks + event.getSkipAmount(), 0L);
+ } else {
+ newTotalTicks = totalTicks; // just pass it through
+ }
@@ -60,7 +60,7 @@
- clockManager.setTotalTicks(clock, totalTicks);
- source.sendSuccess(() -> Component.translatable("commands.time.set.absolute", clock.getRegisteredName(), totalTicks), true);
- return totalTicks;
+ clockManager.setTotalTicks(clock, newTotalTicks);
+ if (!cancelled) clockManager.setTotalTicks(clock, newTotalTicks);
+ source.sendSuccess(() -> Component.translatable("commands.time.set.absolute", clock.getRegisteredName(), newTotalTicks), true);
+ return wrapTime(newTotalTicks);
+ // Paper end - per-world time
@@ -71,7 +71,7 @@
- clockManager.addTicks(clock, time);
+ // Paper start - per-world time
+ ServerClockManager clockManager = source.getLevel().clockManager();
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, time);
+ org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, clock, time);
+ if (event.callEvent()) {
+ clockManager.addTicks(clock, event.getSkipAmount());
+ }
@@ -99,7 +99,7 @@
+ if (timerMove == null) return ServerClockManager.MoveResult.NO_TIME_MARKER_FOUND;
+
+ // We consider it an attempted move even if the marker occurs at this point in time for event calling purposes.
+ final org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, timerMove.targetTime() - timerMove.currentTime());
+ final org.bukkit.event.world.ClockTimeSkipEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(source, clock, timerMove.targetTime() - timerMove.currentTime());
+ if (!event.callEvent() || event.getSkipAmount() == 0) return ServerClockManager.MoveResult.NOT_MOVED;
+
+ clockManager.setTotalTicks(clock, timerMove.currentTime() + event.getSkipAmount());
@@ -276,7 +276,7 @@
}
@Override
@@ -373,11 +_,25 @@
@@ -373,11 +_,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();
@@ -286,8 +286,9 @@
+ // Paper start - per-world time
+ ServerClockManager.TimerMove timerMove = this.clockManager().resolveTimerMove(defaultClock.get(), ClockTimeMarkers.WAKE_UP_FROM_SLEEP);
+ if (timerMove != null) {
+ event = new org.bukkit.event.world.TimeSkipEvent(
+ this.getWorld(),
+ event = org.bukkit.craftbukkit.event.CraftEventFactory.createTimeSkipEvent(
+ this,
+ defaultClock.get(),
+ org.bukkit.event.world.ClockTimeSkipEvent.SkipReason.NIGHT_SKIP,
+ timerMove.targetTime() - timerMove.currentTime()
+ );
@@ -0,0 +1,40 @@
--- a/net/minecraft/world/timeline/Timeline.java
+++ b/net/minecraft/world/timeline/Timeline.java
@@ -142,6 +_,23 @@
}
}
+ // Paper start - clock API - value equality for registry builder round-trips
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (!(o instanceof final Timeline other)) return false;
+ return this.clock.equals(other.clock)
+ && this.periodTicks.equals(other.periodTicks)
+ && this.tracks.equals(other.tracks)
+ && this.timeMarkers.equals(other.timeMarkers);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(this.clock, this.periodTicks, this.tracks, this.timeMarkers);
+ }
+ // Paper end - clock API - value equality for registry builder round-trips
+
public Set<EnvironmentAttribute<?>> attributes() {
return this.tracks.keySet();
}
@@ -181,6 +_,13 @@
this.tracks.put(attribute, new AttributeTrack<>(modifier, argumentTrack.build()));
return this;
}
+
+ // Paper start - clock API
+ public Timeline.Builder addTracks(final Map<EnvironmentAttribute<?>, AttributeTrack<?, ?>> tracks) {
+ this.tracks.putAll(tracks);
+ return this;
+ }
+ // Paper end - clock API
public <Value> Timeline.Builder addTrack(final EnvironmentAttribute<Value> attribute, final Consumer<KeyframeTrack.Builder<Value>> builder) {
return this.addModifierTrack(attribute, AttributeModifier.override(), builder);
@@ -25,14 +25,18 @@ import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperPigVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperSoundEventRegistryEntry;
import io.papermc.paper.registry.data.PaperSulfurCubeArchetypeRegistryEntry;
import io.papermc.paper.registry.data.PaperTimelineRegistryEntry;
import io.papermc.paper.registry.data.PaperTrimMaterialRegistryEntry;
import io.papermc.paper.registry.data.PaperTrimPatternRegistryEntry;
import io.papermc.paper.registry.data.PaperWolfVariantRegistryEntry;
import io.papermc.paper.registry.data.PaperWorldClockRegistryEntry;
import io.papermc.paper.registry.data.PaperZombieNautilusVariantRegistryEntry;
import io.papermc.paper.registry.data.dialog.PaperDialogRegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntryMeta;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.world.Timeline;
import io.papermc.paper.world.WorldClock;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -60,6 +64,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;
@@ -159,6 +165,8 @@ public final class PaperRegistries {
start(Registries.ZOMBIE_NAUTILUS_VARIANT, RegistryKey.ZOMBIE_NAUTILUS_VARIANT).craft(ZombieNautilus.Variant.class, CraftZombieNautilus.CraftVariant::new).writable(PaperZombieNautilusVariantRegistryEntry.PaperBuilder::new),
start(Registries.SULFUR_CUBE_ARCHETYPE, RegistryKey.SULFUR_CUBE_ARCHETYPE).craft(SulfurCube.Archetype.class, CraftSulfurCube.CraftArchetype::new).writable(PaperSulfurCubeArchetypeRegistryEntry.PaperBuilder::new),
start(Registries.DIALOG, RegistryKey.DIALOG).craft(Dialog.class, PaperDialog::new, true).writable(PaperDialogRegistryEntry.PaperBuilder::new),
start(Registries.WORLD_CLOCK, RegistryKey.WORLD_CLOCK).craft(WorldClock.class, CraftWorldClock::new).writable(PaperWorldClockRegistryEntry.PaperBuilder::new).delayed(),
start(Registries.TIMELINE, RegistryKey.TIMELINE).craft(Timeline.class, CraftTimeline::new).writable(PaperTimelineRegistryEntry.PaperBuilder::new).delayed(),
// api-only
start(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE).apiOnly(PaperSimpleRegistry::entityType),
@@ -0,0 +1,103 @@
package io.papermc.paper.registry.data;
import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.world.WorldClock;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.OptionalInt;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.timeline.Timeline;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
import static io.papermc.paper.registry.data.util.Checks.asArgument;
import static io.papermc.paper.registry.data.util.Checks.asConfigured;
public class PaperTimelineRegistryEntry implements TimelineRegistryEntry {
protected final Conversions conversions;
protected @Nullable Timeline internal;
protected @Nullable Holder<net.minecraft.world.clock.WorldClock> clock;
protected @Nullable Integer periodTicks;
protected final Map<Key, TimeMarker> timeMarkers = new LinkedHashMap<>();
public PaperTimelineRegistryEntry(final Conversions conversions, final @Nullable Timeline internal) {
this.conversions = conversions;
this.internal = internal;
if (internal == null) {
return;
}
this.clock = internal.clock();
this.periodTicks = internal.periodTicks().orElse(null);
internal.registerTimeMarkers((key, marker) -> this.timeMarkers.put(PaperAdventure.asAdventure(key.identifier()), TimeMarker.of(marker.ticks(), marker.showInCommands())));
}
@Override
public TypedKey<WorldClock> clock() {
return PaperRegistries.fromNms(asConfigured(this.clock, "clock").unwrapKey().orElseThrow());
}
@Override
public OptionalInt periodTicks() {
return this.periodTicks != null ? OptionalInt.of(this.periodTicks) : OptionalInt.empty();
}
@Override
public Map<Key, TimeMarker> timeMarkers() {
return Map.copyOf(this.timeMarkers);
}
public static final class PaperBuilder extends PaperTimelineRegistryEntry implements TimelineRegistryEntry.Builder, PaperRegistryBuilder<Timeline, io.papermc.paper.world.Timeline> {
public PaperBuilder(final Conversions conversions, final @Nullable Timeline internal) {
super(conversions, internal);
}
@Override
public Builder clock(final TypedKey<WorldClock> clock) {
this.clock = this.conversions.getReferenceHolder(PaperRegistries.toNms(asArgument(clock, "clock")));
return this;
}
@Override
public Builder periodTicks(final @Range(from = 1, to = Integer.MAX_VALUE) int periodTicks) {
Preconditions.checkArgument(periodTicks >= 1, "periodTicks must be at least 1, was %s", periodTicks);
this.periodTicks = periodTicks;
return this;
}
@Override
public Builder clearPeriodTicks() {
this.periodTicks = null;
return this;
}
@Override
public Builder addTimeMarker(final Key key, final @Range(from = 0, to = Integer.MAX_VALUE) int ticks, final boolean showInCommands) {
Preconditions.checkArgument(ticks >= 0, "ticks must be at least 0, was %s", ticks);
this.timeMarkers.put(asArgument(key, "key"), TimeMarker.of(ticks, showInCommands));
return this;
}
@Override
public Timeline build() {
final Timeline.Builder builder = Timeline.builder(asConfigured(this.clock, "clock"));
if (this.periodTicks != null) {
builder.setPeriodTicks(this.periodTicks);
}
if (this.internal != null) {
builder.addTracks(this.internal.tracks);
}
this.timeMarkers.forEach((key, marker) -> builder.addTimeMarker(ResourceKey.create(net.minecraft.world.clock.ClockTimeMarkers.ROOT_ID, PaperAdventure.asVanilla(key)), marker.ticks(), marker.showInCommands()));
return builder.build();
}
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -143,7 +143,6 @@ import org.bukkit.event.weather.ThunderChangeEvent;
import org.bukkit.event.weather.WeatherChangeEvent;
import org.bukkit.event.world.ClockTimeSkipEvent;
import org.bukkit.event.world.SpawnChangeEvent;
import org.bukkit.event.world.TimeSkipEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
@@ -823,19 +822,63 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void setFullTime(long time) {
if (this.world.dimensionType().defaultClock().isEmpty()) {
throw new IllegalArgumentException("Cannot set time in world without world clock");
}
final Holder<net.minecraft.world.clock.WorldClock> clock = this.world.dimensionType().defaultClock()
.orElseThrow(() -> new IllegalArgumentException("Cannot set time in world without world clock"));
this.setFullTime(clock, time);
}
final long currentClockTime = this.world.getDefaultClockTime();
final ClockTimeSkipEvent event = new TimeSkipEvent(this, ClockTimeSkipEvent.SkipReason.CUSTOM, time - currentClockTime);
this.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
@Override
public io.papermc.paper.world.@Nullable WorldClock getDefaultClock() {
return this.world.dimensionType().defaultClock().map(CraftWorldClock::minecraftHolderToBukkit).orElse(null);
}
@Override
public long getFullTime(final io.papermc.paper.world.WorldClock clock) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
return this.world.clockManager().getInstance(CraftWorldClock.bukkitToMinecraftHolder(clock)).totalTicks();
}
@Override
public void setFullTime(final io.papermc.paper.world.WorldClock clock, final long time) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
this.setFullTime(CraftWorldClock.bukkitToMinecraftHolder(clock), time);
}
private void setFullTime(final Holder<net.minecraft.world.clock.WorldClock> clock, final long time) {
Preconditions.checkArgument(time >= 0, "time cannot be negative, was %s", time);
final long currentClockTime = this.world.clockManager().getInstance(clock).totalTicks();
final ClockTimeSkipEvent event = CraftEventFactory.createTimeSkipEvent(this.world, clock, ClockTimeSkipEvent.SkipReason.CUSTOM, time - currentClockTime);
if (!event.callEvent()) {
return;
}
// Updates the clock for all players
this.world.clockManager().setTotalTicks(this.world.dimensionType().defaultClock().get(), currentClockTime + event.getSkipAmount());
this.world.clockManager().setTotalTicks(clock, Math.max(currentClockTime + event.getSkipAmount(), 0L));
}
@Override
public boolean isClockPaused(final io.papermc.paper.world.WorldClock clock) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
return this.world.clockManager().getInstance(CraftWorldClock.bukkitToMinecraftHolder(clock)).isPaused();
}
@Override
public void setClockPaused(final io.papermc.paper.world.WorldClock clock, final boolean paused) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
this.world.clockManager().setPaused(CraftWorldClock.bukkitToMinecraftHolder(clock), paused);
}
@Override
public float getClockRate(final io.papermc.paper.world.WorldClock clock) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
return this.world.clockManager().getInstance(CraftWorldClock.bukkitToMinecraftHolder(clock)).rate();
}
@Override
public void setClockRate(final io.papermc.paper.world.WorldClock clock, final float rate) {
Preconditions.checkArgument(clock != null, "clock cannot be null");
Preconditions.checkArgument(Float.isFinite(rate) && rate > 0.0F, "rate must be positive and finite, was %s", rate);
this.world.clockManager().setRate(CraftWorldClock.bukkitToMinecraftHolder(clock), rate);
}
// 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);
}
}
@@ -30,6 +30,7 @@ import net.minecraft.Optionull;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
@@ -2469,11 +2470,16 @@ public class CraftEventFactory {
return new ItemTransportingEntitySortEvent(mob.getBukkitEntity(), CraftItemStack.asBukkitCopy(mob.getMainHandItem()), new CraftInventory(container));
}
public static ClockTimeSkipEvent createTimeSkipEvent(final CommandSourceStack source, final long skipAmount) {
public static ClockTimeSkipEvent createTimeSkipEvent(final CommandSourceStack source, final Holder<net.minecraft.world.clock.WorldClock> clock, final long skipAmount) {
return createTimeSkipEvent(source.getLevel(), clock, ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
}
public static ClockTimeSkipEvent createTimeSkipEvent(final ServerLevel level, final Holder<net.minecraft.world.clock.WorldClock> clock, final ClockTimeSkipEvent.SkipReason reason, final long skipAmount) {
final io.papermc.paper.world.WorldClock bukkitClock = org.bukkit.craftbukkit.CraftWorldClock.minecraftHolderToBukkit(clock);
if (io.papermc.paper.configuration.GlobalConfiguration.get().time.affectsAllWorlds) {
return new ClockTimeSkipEvent(ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
return new ClockTimeSkipEvent(bukkitClock, reason, skipAmount);
}
return new TimeSkipEvent(source.getLevel().getWorld(), ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount);
return new TimeSkipEvent(level.getWorld(), bukkitClock, reason, skipAmount);
}
public static int callEntityIgniteEvent(Entity entity, int fuseTime) {
@@ -170,6 +170,8 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
register(Registries.GAME_RULE, GameRule.class, GameRules.class, CraftGameRule.class, net.minecraft.world.level.gamerules.GameRule.class);
register(Registries.POINT_OF_INTEREST_TYPE, PoiType.class, PoiTypes.class, PaperPoiType.class, net.minecraft.world.entity.ai.village.poi.PoiType.class);
register(Registries.SULFUR_CUBE_ARCHETYPE, SulfurCube.Archetype.class, CraftSulfurCube.CraftArchetype.class, SulfurCubeArchetype.class);
register(Registries.WORLD_CLOCK, io.papermc.paper.world.WorldClock.class, org.bukkit.craftbukkit.CraftWorldClock.class, net.minecraft.world.clock.WorldClock.class);
register(Registries.TIMELINE, io.papermc.paper.world.Timeline.class, org.bukkit.craftbukkit.CraftTimeline.class, net.minecraft.world.timeline.Timeline.class);
}
private static <M, B extends Keyed> void register(ResourceKey<? extends Registry<? extends M>> registryKey, Class<B> api, Class<? extends B> impl, Class<M> internal) {