mirror of
https://github.com/PaperMC/Paper.git
synced 2026-11-04 08:52:01 -05:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f1fad8687 | ||
|
|
431b4245ea | ||
|
|
8f5f29305d | ||
|
|
4e8df4a990 | ||
|
|
3612df6d9c | ||
|
|
e3ba7b1dc8 | ||
|
|
05c2798ec0 | ||
|
|
02378ab582 | ||
|
|
67912d0827 | ||
|
|
e8a1f8fd68 | ||
|
|
1b9bf5c8e9 | ||
|
|
325f9071f0 | ||
|
|
d86d427405 | ||
|
|
2f1ce01e87 | ||
|
|
0d9bf08a23 | ||
|
|
e65c0765c6 | ||
|
|
ac9a1b2c72 | ||
|
|
0db5c93a3d | ||
|
|
2d602d68c1 | ||
|
|
2766dd30b1 | ||
|
|
03af8b4291 | ||
|
|
45fac22871 |
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package io.papermc.paper.command.brigadier;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.brigadier.RedirectModifier;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import net.kyori.adventure.pointer.Pointered;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.bukkit.GameRules;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -81,4 +87,123 @@ public interface CommandSourceStack {
|
||||
* @see com.mojang.brigadier.builder.ArgumentBuilder#fork(CommandNode, RedirectModifier)
|
||||
*/
|
||||
CommandSourceStack withExecutor(Entity executor);
|
||||
|
||||
/**
|
||||
* Sends a system message to the {@link #getExecutor()} if it is a {@link Player},
|
||||
* otherwise sends a system message to the {@link #getSender()}.
|
||||
*
|
||||
* @param message the message to send
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
void sendReply(ComponentLike message);
|
||||
|
||||
/**
|
||||
* Sends a system message with the MiniMessage format to the {@link #getExecutor()}
|
||||
* if it is a {@link Player}, otherwise sends it to the {@link #getSender()}.
|
||||
*
|
||||
* <p>See <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a> and
|
||||
* <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements/">MiniMessage Placeholders docs</a>
|
||||
* for more information on the format.</p>
|
||||
*
|
||||
* <p>This method provides the target of the system message as a {@link net.kyori.adventure.pointer.Pointered}
|
||||
* to the MiniMessage {@link MiniMessage#deserialize(String, Pointered, TagResolver...)} method.</p>
|
||||
*
|
||||
* @param message the MiniMessage message to send
|
||||
* @param resolvers resolvers to use
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
default void sendRichReply(final String message, final TagResolver... resolvers) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.sendReply(MiniMessage.miniMessage().deserialize(
|
||||
message,
|
||||
this.getExecutor() instanceof final Player player ? player : this.getSender(),
|
||||
resolvers
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system message to the {@link #getSender()}, admins, and console indicating successful command execution
|
||||
* according to vanilla semantics.
|
||||
*
|
||||
* <p>This currently includes checking for environments with suppressed output,
|
||||
* {@link GameRules#SEND_COMMAND_FEEDBACK}, and {@link GameRules#LOG_ADMIN_COMMANDS}.</p>
|
||||
*
|
||||
* @param message the message to send
|
||||
* @param allowInformingAdmins whether admins and console may be informed of this success
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
void sendSuccess(ComponentLike message, boolean allowInformingAdmins);
|
||||
|
||||
/**
|
||||
* Sends a system message to the {@link #getSender()}, admins, and console indicating successful command execution
|
||||
* according to vanilla semantics. This method informs admins and the console of this success.
|
||||
*
|
||||
* <p>This currently includes checking for environments with suppressed output,
|
||||
* {@link GameRules#SEND_COMMAND_FEEDBACK}, and {@link GameRules#LOG_ADMIN_COMMANDS}.</p>
|
||||
*
|
||||
* @param message the message to send
|
||||
* @see #sendSuccess(ComponentLike, boolean) to disable if admins and console should be informed
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
default void sendSuccess(final ComponentLike message) {
|
||||
this.sendSuccess(message, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system message with the MiniMessage format to the {@link #getSender()}, admins, and console indicating
|
||||
* successful command execution according to vanilla semantics. This method informs admins and the console of
|
||||
* this success.
|
||||
*
|
||||
* <p>This currently includes checking for environments with suppressed output,
|
||||
* {@link GameRules#SEND_COMMAND_FEEDBACK}, and {@link GameRules#LOG_ADMIN_COMMANDS}.</p>
|
||||
*
|
||||
* <p>See <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a> and
|
||||
* <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements/">MiniMessage Placeholders docs</a>
|
||||
* for more information on the format.</p>
|
||||
*
|
||||
* <p>This method provides the {@link CommandSender} of this command as a {@link net.kyori.adventure.pointer.Pointered}
|
||||
* to the MiniMessage {@link MiniMessage#deserialize(String, Pointered, TagResolver...)} method.</p>
|
||||
*
|
||||
* @param message the MiniMessage message to send
|
||||
* @param resolvers resolvers to use
|
||||
* @see #sendSuccess(ComponentLike, boolean) to disable if admins and console should be informed
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
default void sendRichSuccess(final String message, final TagResolver... resolvers) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.sendSuccess(MiniMessage.miniMessage().deserialize(message, this.getSender(), resolvers), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system message indicating a failed command execution to the {@link #getSender()}.
|
||||
* Does not apply red styling to the message as vanilla does to allow for custom failure message styling.
|
||||
*
|
||||
* <p>Respects vanilla semantics for accepting failure output and suppressed output environments.</p>
|
||||
*
|
||||
* @param message the message to send
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
void sendFailure(ComponentLike message);
|
||||
|
||||
/**
|
||||
* Sends a system message with the MiniMessage format indicating a failed command execution to the{@link #getSender()}.
|
||||
* Does not apply red styling to the message as vanilla does to allow for custom failure message styling.
|
||||
*
|
||||
* <p>Respects vanilla semantics for accepting failure output and suppressed output environments.</p>
|
||||
*
|
||||
* <p>See <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a> and
|
||||
* <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements/">MiniMessage Placeholders docs</a>
|
||||
* for more information on the format.</p>
|
||||
*
|
||||
* <p>This method provides the {@link CommandSender} of this command as a {@link net.kyori.adventure.pointer.Pointered}
|
||||
* to the MiniMessage {@link MiniMessage#deserialize(String, Pointered, TagResolver...)} method.</p>
|
||||
*
|
||||
* @param message the MiniMessage message to send
|
||||
* @param resolvers resolvers to use
|
||||
* @throws IllegalArgumentException if the message is null
|
||||
*/
|
||||
default void sendRichFailure(final String message, final TagResolver... resolvers) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.sendFailure(MiniMessage.miniMessage().deserialize(message, this.getSender(), resolvers));
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package io.papermc.paper.event.entity;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.entity.EntityEvent;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
/**
|
||||
* Called when an entity is launched upward by a Potent Sulfur geyser.
|
||||
*/
|
||||
@NullMarked
|
||||
public class EntityPotentSulfurLaunchEvent extends EntityEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
private final Block sulfurBlock;
|
||||
private final Location sourceLocation;
|
||||
|
||||
private final Vector launchVelocity;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public EntityPotentSulfurLaunchEvent(
|
||||
final Entity entity,
|
||||
final Block sulfurBlock,
|
||||
final Location sourceLocation,
|
||||
final Vector launchVelocity
|
||||
) {
|
||||
super(entity);
|
||||
this.sulfurBlock = sulfurBlock;
|
||||
this.sourceLocation = sourceLocation;
|
||||
this.launchVelocity = launchVelocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Potent Sulfur block.
|
||||
*/
|
||||
public Block getSulfurBlock() {
|
||||
return this.sulfurBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location where the geyser exits the water column.
|
||||
*/
|
||||
public Location getSourceLocation() {
|
||||
return this.sourceLocation.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the velocity that will be added to the entity.
|
||||
* <br>
|
||||
* This value is read-only, changes made to it <b>will not</b> have any
|
||||
* effect on the final launch velocity.
|
||||
*/
|
||||
public Vector getLaunchVelocity() {
|
||||
return this.launchVelocity.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -961,8 +961,7 @@ public final class Bukkit {
|
||||
* Create a new explorer map targeting the closest nearby structure of a
|
||||
* given {@link StructureType}.
|
||||
* <br>
|
||||
* This method uses implementation default values for radius and
|
||||
* findUnexplored (usually 100, true).
|
||||
* This method uses a radius of 100 and only searches for unexplored structures.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
@@ -986,8 +985,7 @@ public final class Bukkit {
|
||||
* Create a new explorer map targeting the closest nearby structure of a
|
||||
* given {@link org.bukkit.generator.structure.StructureType}.
|
||||
* <br>
|
||||
* This method uses implementation default values for radius and
|
||||
* findUnexplored (usually 100, true).
|
||||
* This method uses a radius of 100 and only searches for unexplored structures.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
@@ -1021,6 +1019,45 @@ public final class Bukkit {
|
||||
public static @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) {
|
||||
return server.createExplorerMap(world, location, structureType, mapIcon, radius, findUnexplored);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new explorer map targeting the closest nearby specific
|
||||
* {@link org.bukkit.generator.structure.Structure}.
|
||||
* <br>
|
||||
* This method uses a radius of 100 and only searches for unexplored structures.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
* @param structure the specific structure to find
|
||||
* @param mapIcon the map icon to use on the map
|
||||
* @return a newly created item stack or null if it can't find a location
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* org.bukkit.generator.structure.Structure, int, boolean)
|
||||
*/
|
||||
public static @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.Structure structure, @NotNull org.bukkit.map.MapCursor.Type mapIcon) {
|
||||
return server.createExplorerMap(world, location, structure, mapIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new explorer map targeting the closest nearby specific
|
||||
* {@link org.bukkit.generator.structure.Structure}.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
* @param structure the specific structure to find
|
||||
* @param mapIcon the map icon to use on the map
|
||||
* @param radius radius to search, see World#locateNearestStructure for more
|
||||
* information
|
||||
* @param findUnexplored whether to find unexplored structures
|
||||
* @return the newly created item stack or null if it can't find a location
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* org.bukkit.generator.structure.Structure, int, boolean)
|
||||
*/
|
||||
public static @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.Structure structure, @NotNull org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) {
|
||||
return server.createExplorerMap(world, location, structure, mapIcon, radius, findUnexplored);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,10 +6,12 @@ import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import net.kyori.adventure.util.ARGBLike;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.configuration.serialization.SerializableAs;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
/**
|
||||
* A container for a color palette. This class is immutable; the set methods
|
||||
@@ -17,7 +19,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* but subject to change.
|
||||
*/
|
||||
@SerializableAs("Color")
|
||||
public final class Color implements ConfigurationSerializable {
|
||||
public final class Color implements ConfigurationSerializable, ARGBLike {
|
||||
private static final int BIT_MASK = 0xff;
|
||||
private static final int DEFAULT_ALPHA = 255;
|
||||
|
||||
@@ -122,7 +124,12 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
|
||||
*/
|
||||
@NotNull
|
||||
public static Color fromARGB(int alpha, int red, int green, int blue) throws IllegalArgumentException {
|
||||
public static Color fromARGB(
|
||||
final @Range(from = 0, to = 255) int alpha,
|
||||
final @Range(from = 0, to = 255) int red,
|
||||
final @Range(from = 0, to = 255) int green,
|
||||
final @Range(from = 0, to = 255) int blue
|
||||
) throws IllegalArgumentException {
|
||||
return new Color(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
@@ -136,7 +143,11 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
|
||||
*/
|
||||
@NotNull
|
||||
public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException {
|
||||
public static Color fromRGB(
|
||||
final @Range(from = 0, to = 255) int red,
|
||||
final @Range(from = 0, to = 255) int green,
|
||||
final @Range(from = 0, to = 255) int blue
|
||||
) throws IllegalArgumentException {
|
||||
return new Color(DEFAULT_ALPHA, red, green, blue);
|
||||
}
|
||||
|
||||
@@ -150,7 +161,11 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @throws IllegalArgumentException if any value is strictly {@literal >255 or <0}
|
||||
*/
|
||||
@NotNull
|
||||
public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException {
|
||||
public static Color fromBGR(
|
||||
final @Range(from = 0, to = 255) int blue,
|
||||
final @Range(from = 0, to = 255) int green,
|
||||
final @Range(from = 0, to = 255) int red
|
||||
) throws IllegalArgumentException {
|
||||
return new Color(DEFAULT_ALPHA, red, green, blue);
|
||||
}
|
||||
|
||||
@@ -164,7 +179,7 @@ public final class Color implements ConfigurationSerializable {
|
||||
* bits
|
||||
*/
|
||||
@NotNull
|
||||
public static Color fromRGB(int rgb) throws IllegalArgumentException {
|
||||
public static Color fromRGB(final int rgb) throws IllegalArgumentException {
|
||||
Preconditions.checkArgument((rgb >> 24) == 0, "Extraneous data in: %s", rgb);
|
||||
return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb & BIT_MASK);
|
||||
}
|
||||
@@ -177,7 +192,7 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @return a new color object for specified values
|
||||
*/
|
||||
@NotNull
|
||||
public static Color fromARGB(int argb) {
|
||||
public static Color fromARGB(final int argb) {
|
||||
return fromARGB(argb >> 24 & BIT_MASK, argb >> 16 & BIT_MASK, argb >> 8 & BIT_MASK, argb & BIT_MASK);
|
||||
}
|
||||
|
||||
@@ -191,16 +206,16 @@ public final class Color implements ConfigurationSerializable {
|
||||
* bits
|
||||
*/
|
||||
@NotNull
|
||||
public static Color fromBGR(int bgr) throws IllegalArgumentException {
|
||||
public static Color fromBGR(final int bgr) throws IllegalArgumentException {
|
||||
Preconditions.checkArgument((bgr >> 24) == 0, "Extrenuous data in: %s", bgr);
|
||||
return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr & BIT_MASK);
|
||||
}
|
||||
|
||||
private Color(int red, int green, int blue) {
|
||||
private Color(final int red, final int green, final int blue) {
|
||||
this(DEFAULT_ALPHA, red, green, blue);
|
||||
}
|
||||
|
||||
private Color(int alpha, int red, int green, int blue) {
|
||||
private Color(final int alpha, final int red, final int green, final int blue) {
|
||||
Preconditions.checkArgument(alpha >= 0 && alpha <= 0xff, "Alpha[%s] is not between 0-255", alpha);
|
||||
Preconditions.checkArgument(red >= 0 && red <= 0xff, "Red[%s] is not between 0-255", red);
|
||||
Preconditions.checkArgument(green >= 0 && green <= 0xff, "Green[%s] is not between 0-255", green);
|
||||
@@ -213,83 +228,123 @@ public final class Color implements ConfigurationSerializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alpha component
|
||||
* Gets the alpha component.
|
||||
*
|
||||
* @return alpha component, from 0 to 255
|
||||
*/
|
||||
public int getAlpha() {
|
||||
public @Range(from = 0, to = 255) int getAlpha() {
|
||||
return BIT_MASK & alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with specified component
|
||||
* Gets the alpha component.
|
||||
*
|
||||
* @return alpha component, from 0 to 255
|
||||
*/
|
||||
@Override
|
||||
public @Range(from = 0, to = 255) int alpha() {
|
||||
return this.getAlpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with a specified component.
|
||||
*
|
||||
* @param alpha the alpha component, from 0 to 255
|
||||
* @return a new color object with the alpha component
|
||||
*/
|
||||
@NotNull
|
||||
public Color setAlpha(int alpha) {
|
||||
return fromARGB(alpha, getRed(), getGreen(), getBlue());
|
||||
public Color setAlpha(final @Range(from = 0, to = 255) int alpha) {
|
||||
return fromARGB(alpha, this.getRed(), this.getGreen(), this.getBlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the red component
|
||||
* Gets the red component.
|
||||
*
|
||||
* @return red component, from 0 to 255
|
||||
*/
|
||||
public int getRed() {
|
||||
return BIT_MASK & red;
|
||||
public @Range(from = 0, to = 255) int getRed() {
|
||||
return BIT_MASK & this.red;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with specified component
|
||||
* Gets the red component.
|
||||
*
|
||||
* @return red component, from 0 to 255
|
||||
*/
|
||||
@Override
|
||||
public @Range(from = 0, to = 255) int red() {
|
||||
return this.getRed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with a specified component.
|
||||
*
|
||||
* @param red the red component, from 0 to 255
|
||||
* @return a new color object with the red component
|
||||
*/
|
||||
@NotNull
|
||||
public Color setRed(int red) {
|
||||
return fromARGB(getAlpha(), red, getGreen(), getBlue());
|
||||
public Color setRed(final @Range(from = 0, to = 255) int red) {
|
||||
return fromARGB(this.getAlpha(), red, this.getGreen(), this.getBlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the green component
|
||||
* Gets the green component.
|
||||
*
|
||||
* @return green component, from 0 to 255
|
||||
*/
|
||||
public int getGreen() {
|
||||
return BIT_MASK & green;
|
||||
public @Range(from = 0, to = 255) int getGreen() {
|
||||
return BIT_MASK & this.green;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with specified component
|
||||
* Gets the green component.
|
||||
*
|
||||
* @return green component, from 0 to 255
|
||||
*/
|
||||
@Override
|
||||
public @Range(from = 0, to = 255) int green() {
|
||||
return this.getGreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with a specified component.
|
||||
*
|
||||
* @param green the green component, from 0 to 255
|
||||
* @return a new color object with the green component
|
||||
*/
|
||||
@NotNull
|
||||
public Color setGreen(int green) {
|
||||
return fromARGB(getAlpha(), getRed(), green, getBlue());
|
||||
public Color setGreen(final @Range(from = 0, to = 255) int green) {
|
||||
return fromARGB(this.getAlpha(), this.getRed(), green, this.getBlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blue component
|
||||
* Gets the blue component.
|
||||
*
|
||||
* @return blue component, from 0 to 255
|
||||
*/
|
||||
public int getBlue() {
|
||||
return BIT_MASK & blue;
|
||||
public @Range(from = 0, to = 255) int getBlue() {
|
||||
return BIT_MASK & this.blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with specified component
|
||||
* Gets the blue component.
|
||||
*
|
||||
* @return blue component, from 0 to 255
|
||||
*/
|
||||
@Override
|
||||
public @Range(from = 0, to = 255) int blue() {
|
||||
return this.getBlue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Color object with a specified component.
|
||||
*
|
||||
* @param blue the blue component, from 0 to 255
|
||||
* @return a new color object with the blue component
|
||||
*/
|
||||
@NotNull
|
||||
public Color setBlue(int blue) {
|
||||
return fromARGB(getAlpha(), getRed(), getGreen(), blue);
|
||||
public Color setBlue(final @Range(from = 0, to = 255) int blue) {
|
||||
return fromARGB(this.getAlpha(), this.getRed(), this.getGreen(), blue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +353,7 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @return An integer representation of this color, as 0xRRGGBB
|
||||
*/
|
||||
public int asRGB() {
|
||||
return getRed() << 16 | getGreen() << 8 | getBlue();
|
||||
return this.getRed() << 16 | this.getGreen() << 8 | this.getBlue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +362,7 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @return An integer representation of this color, as 0xAARRGGBB
|
||||
*/
|
||||
public int asARGB() {
|
||||
return getAlpha() << 24 | getRed() << 16 | getGreen() << 8 | getBlue();
|
||||
return this.getAlpha() << 24 | this.getRed() << 16 | this.getGreen() << 8 | this.getBlue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,33 +371,33 @@ public final class Color implements ConfigurationSerializable {
|
||||
* @return An integer representation of this color, as 0xBBGGRR
|
||||
*/
|
||||
public int asBGR() {
|
||||
return getBlue() << 16 | getGreen() << 8 | getRed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new color with its RGB components changed as if it was dyed
|
||||
* with the colors passed in, replicating vanilla workbench dyeing
|
||||
*
|
||||
* @param colors The DyeColors to dye with
|
||||
* @return A new color with the changed rgb components
|
||||
*/
|
||||
// TODO: Javadoc what this method does, not what it mimics. API != Implementation
|
||||
@NotNull
|
||||
public Color mixDyes(@NotNull DyeColor... colors) {
|
||||
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "DyeColor cannot be null or contain null values");
|
||||
|
||||
Color[] toPass = new Color[colors.length];
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
toPass[i] = colors[i].getColor();
|
||||
}
|
||||
|
||||
return mixColors(toPass);
|
||||
return this.getBlue() << 16 | this.getGreen() << 8 | this.getRed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new color with its RGB components changed as if it was dyed
|
||||
* with the colors passed in, replicating vanilla workbench dyeing.
|
||||
*
|
||||
* @param colors The DyeColors to dye with
|
||||
* @return A new color with the changed rgb components
|
||||
*/
|
||||
// TODO: Javadoc what this method does, not what it mimics. API != Implementation
|
||||
@NotNull
|
||||
public Color mixDyes(final @NotNull DyeColor... colors) {
|
||||
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "DyeColor cannot be null or contain null values");
|
||||
|
||||
final Color[] toPass = new Color[colors.length];
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
toPass[i] = colors[i].getColor();
|
||||
}
|
||||
|
||||
return this.mixColors(toPass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new color with its RGB components changed as if it was dyed
|
||||
* with the colors passed in, replicating vanilla workbench dyeing.
|
||||
* <br>
|
||||
* <b>Note that this method does not currently take into account alpha
|
||||
* components.</b>
|
||||
*
|
||||
@@ -351,7 +406,7 @@ public final class Color implements ConfigurationSerializable {
|
||||
*/
|
||||
// TODO: Javadoc what this method does, not what it mimics. API != Implementation
|
||||
@NotNull
|
||||
public Color mixColors(@NotNull Color... colors) {
|
||||
public Color mixColors(final @NotNull Color... colors) {
|
||||
Preconditions.checkArgument(colors != null && Arrays.stream(colors).allMatch(Objects::nonNull), "Colors cannot be null");
|
||||
|
||||
int totalRed = this.getRed();
|
||||
@@ -365,44 +420,43 @@ public final class Color implements ConfigurationSerializable {
|
||||
totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue());
|
||||
}
|
||||
|
||||
float averageRed = totalRed / (colors.length + 1);
|
||||
float averageGreen = totalGreen / (colors.length + 1);
|
||||
float averageBlue = totalBlue / (colors.length + 1);
|
||||
float averageMax = totalMax / (colors.length + 1);
|
||||
final float averageRed = totalRed / (colors.length + 1);
|
||||
final float averageGreen = totalGreen / (colors.length + 1);
|
||||
final float averageBlue = totalBlue / (colors.length + 1);
|
||||
final float averageMax = totalMax / (colors.length + 1);
|
||||
|
||||
float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
|
||||
float gainFactor = averageMax / maximumOfAverages;
|
||||
final float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
|
||||
final float gainFactor = averageMax / maximumOfAverages;
|
||||
|
||||
return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Color)) {
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof Color that)) {
|
||||
return false;
|
||||
}
|
||||
final Color that = (Color) o;
|
||||
return this.alpha == that.alpha && this.blue == that.blue && this.green == that.green && this.red == that.red;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return asARGB() ^ Color.class.hashCode();
|
||||
return this.asARGB() ^ Color.class.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Object> serialize() {
|
||||
return ImmutableMap.of(
|
||||
"ALPHA", getAlpha(),
|
||||
"RED", getRed(),
|
||||
"BLUE", getBlue(),
|
||||
"GREEN", getGreen()
|
||||
"ALPHA", this.getAlpha(),
|
||||
"RED", this.getRed(),
|
||||
"BLUE", this.getBlue(),
|
||||
"GREEN", this.getGreen()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Color deserialize(@NotNull Map<String, Object> map) {
|
||||
public static Color deserialize(final @NotNull Map<String, Object> map) {
|
||||
return fromARGB(
|
||||
asInt("ALPHA", map, DEFAULT_ALPHA),
|
||||
asInt("RED", map),
|
||||
@@ -411,23 +465,23 @@ public final class Color implements ConfigurationSerializable {
|
||||
);
|
||||
}
|
||||
|
||||
private static int asInt(@NotNull String string, @NotNull Map<String, Object> map) {
|
||||
private static int asInt(final @NotNull String string, final @NotNull Map<String, Object> map) {
|
||||
return asInt(string, map, null);
|
||||
}
|
||||
|
||||
private static int asInt(@NotNull String string, @NotNull Map<String, Object> map, @Nullable Object defaultValue) {
|
||||
Object value = map.getOrDefault(string, defaultValue);
|
||||
private static int asInt(final @NotNull String string, final @NotNull Map<String, Object> map, final @Nullable Object defaultValue) {
|
||||
final Object value = map.getOrDefault(string, defaultValue);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException(string + " not in map " + map);
|
||||
}
|
||||
if (!(value instanceof Number)) {
|
||||
if (!(value instanceof Number number)) {
|
||||
throw new IllegalArgumentException(string + '(' + value + ") is not a number");
|
||||
}
|
||||
return ((Number) value).intValue();
|
||||
return number.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Color:[argb0x" + Integer.toHexString(asARGB()).toUpperCase(Locale.ROOT) + "]";
|
||||
return "Color:[argb0x" + Integer.toHexString(this.asARGB()).toUpperCase(Locale.ROOT) + "]";
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,8 @@ import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.scoreboard.Criteria;
|
||||
import org.bukkit.scoreboard.ScoreboardManager;
|
||||
import org.bukkit.generator.structure.Structure;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.structure.StructureManager;
|
||||
import org.bukkit.util.CachedServerIcon;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -872,8 +874,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
* Create a new explorer map targeting the closest nearby structure of a
|
||||
* given {@link StructureType}.
|
||||
* <br>
|
||||
* This method uses implementation default values for radius and
|
||||
* findUnexplored (usually 100, true).
|
||||
* This method uses a radius of 100 and only searches for unexplored structures.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
@@ -882,9 +883,9 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* org.bukkit.StructureType, int, boolean)
|
||||
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, org.bukkit.map.MapCursor.Type)}
|
||||
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, MapCursor.Type)}
|
||||
*/
|
||||
@Deprecated // Paper
|
||||
@Deprecated
|
||||
@NotNull
|
||||
default ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType) {
|
||||
return this.createExplorerMap(world, location, structureType, 100, true);
|
||||
@@ -904,18 +905,16 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* org.bukkit.StructureType, int, boolean)
|
||||
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, org.bukkit.map.MapCursor.Type, int, boolean)}
|
||||
* @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, MapCursor.Type, int, boolean)}
|
||||
*/
|
||||
@Deprecated // Paper
|
||||
@NotNull
|
||||
public ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored);
|
||||
// Paper start
|
||||
@Deprecated
|
||||
@NotNull ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored);
|
||||
|
||||
/**
|
||||
* Create a new explorer map targeting the closest nearby structure of a
|
||||
* given {@link org.bukkit.generator.structure.StructureType}.
|
||||
* given {@link StructureType}.
|
||||
* <br>
|
||||
* This method uses implementation default values for radius and
|
||||
* findUnexplored (usually 100, true).
|
||||
* This method uses a radius of 100 and only searches for unexplored structures.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
@@ -924,15 +923,15 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
* @return a newly created item stack or null if it can't find a location
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* org.bukkit.generator.structure.StructureType, int, boolean)
|
||||
* StructureType, int, boolean)
|
||||
*/
|
||||
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon) {
|
||||
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull MapCursor.Type mapIcon) {
|
||||
return this.createExplorerMap(world, location, structureType, mapIcon, 100, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new explorer map targeting the closest nearby structure of a
|
||||
* given {@link org.bukkit.generator.structure.StructureType}.
|
||||
* given {@link StructureType}.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
@@ -944,10 +943,46 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
* @return the newly created item stack or null if it can't find a location
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* org.bukkit.generator.structure.StructureType, int, boolean)
|
||||
* StructureType, int, boolean)
|
||||
*/
|
||||
@Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored);
|
||||
// Paper end
|
||||
@Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull org.bukkit.generator.structure.StructureType structureType, @NotNull MapCursor.Type mapIcon, int radius, boolean findUnexplored);
|
||||
|
||||
/**
|
||||
* Create a new explorer map targeting the closest nearby specific
|
||||
* {@link Structure}.
|
||||
* <br>
|
||||
* This method uses a radius of 100 and only searches for unexplored structures.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
* @param structure the specific structure to find
|
||||
* @param mapIcon the map icon to use on the map
|
||||
* @return a newly created item stack or null if it can't find a location
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* Structure, int, boolean)
|
||||
*/
|
||||
default @Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull Structure structure, @NotNull MapCursor.Type mapIcon) {
|
||||
return this.createExplorerMap(world, location, structure, mapIcon, 100, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new explorer map targeting the closest nearby specific
|
||||
* {@link Structure}.
|
||||
*
|
||||
* @param world the world the map will belong to
|
||||
* @param location the origin location to find the nearest structure
|
||||
* @param structure the specific structure to find
|
||||
* @param mapIcon the map icon to use on the map
|
||||
* @param radius radius to search, see World#locateNearestStructure for more
|
||||
* information
|
||||
* @param findUnexplored whether to find unexplored structures
|
||||
* @return the newly created item stack or null if it can't find a location
|
||||
*
|
||||
* @see World#locateNearestStructure(org.bukkit.Location,
|
||||
* Structure, int, boolean)
|
||||
*/
|
||||
@Nullable ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull Structure structure, @NotNull MapCursor.Type mapIcon, int radius, boolean findUnexplored);
|
||||
|
||||
/**
|
||||
* Reloads the server, refreshing settings and plugin information.
|
||||
|
||||
@@ -110,7 +110,15 @@ public enum TreeType {
|
||||
*/
|
||||
PALE_OAK_CREAKING,
|
||||
/**
|
||||
* Poplar tree
|
||||
* Poplar tree with red leaves
|
||||
*/
|
||||
POPLAR,
|
||||
RED_POPLAR,
|
||||
/**
|
||||
* Poplar tree with orange leaves
|
||||
*/
|
||||
ORANGE_POPLAR,
|
||||
/**
|
||||
* Poplar tree with yellow leaves
|
||||
*/
|
||||
YELLOW_POPLAR,
|
||||
}
|
||||
@@ -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.entity;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -1401,25 +1402,39 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
|
||||
void setHurtDirection(float hurtDirection);
|
||||
// Paper end - hurt direction API
|
||||
|
||||
// Paper start - swing hand API
|
||||
/**
|
||||
* Makes this entity swing their hand.
|
||||
* Makes this entity swing their hand, using the {@link io.papermc.paper.datacomponent.DataComponentTypes#ATTACK_ANIMATION}
|
||||
* component on the item held in that hand to determine the animation type and duration.
|
||||
* If the held item does not have the component, the default {@link io.papermc.paper.datacomponent.item.SwingAnimation.Animation#WHACK}
|
||||
* animation with a duration of {@code 6} ticks is used.
|
||||
*
|
||||
* <p>This method does nothing if this entity does not
|
||||
* have an animation for swinging their hand.
|
||||
*
|
||||
* @param hand hand to be swung, either {@link org.bukkit.inventory.EquipmentSlot#HAND} or {@link org.bukkit.inventory.EquipmentSlot#OFF_HAND}
|
||||
* @throws IllegalArgumentException if invalid hand is passed
|
||||
* @throws IllegalArgumentException if an invalid hand is passed
|
||||
*/
|
||||
default void swingHand(@NotNull org.bukkit.inventory.EquipmentSlot hand) {
|
||||
com.google.common.base.Preconditions.checkArgument(hand != null && hand.isHand(), String.format("Expected a valid hand, got \"%s\" instead!", hand));
|
||||
Preconditions.checkArgument(hand != null && hand.isHand(), "Expected a valid hand, got \"%s\" instead!", hand);
|
||||
if (hand == org.bukkit.inventory.EquipmentSlot.HAND) {
|
||||
this.swingMainHand();
|
||||
} else {
|
||||
this.swingOffHand();
|
||||
}
|
||||
}
|
||||
// Paper end - swing hand API
|
||||
|
||||
/**
|
||||
* Makes this entity swing their hand using the provided {@link io.papermc.paper.datacomponent.item.SwingAnimation},
|
||||
* instead of the swing animation component the held item may have.
|
||||
*
|
||||
* <p>This method does nothing if this entity does not
|
||||
* have an animation for swinging their hand.
|
||||
*
|
||||
* @param hand hand to be swung, either {@link org.bukkit.inventory.EquipmentSlot#HAND} or {@link org.bukkit.inventory.EquipmentSlot#OFF_HAND}
|
||||
* @param swingAnimation the swing animation to use
|
||||
* @throws IllegalArgumentException if an invalid hand is passed
|
||||
*/
|
||||
void swingHand(@NotNull org.bukkit.inventory.EquipmentSlot hand, @NotNull io.papermc.paper.datacomponent.item.SwingAnimation swingAnimation);
|
||||
|
||||
// Paper start - knockback API
|
||||
/**
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -110,7 +110,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":paper-api"))
|
||||
implementation(platform("ca.spottedleaf.leafpile:bom:1.3.0"))
|
||||
implementation(platform("ca.spottedleaf.leafpile:bom:1.3.2"))
|
||||
implementation("ca.spottedleaf.leafpile:common")
|
||||
implementation("ca.spottedleaf.leafpile:concurrentutil")
|
||||
implementation("ca.spottedleaf.leafpile:converter")
|
||||
@@ -285,10 +285,10 @@ fun TaskContainer.registerRunTask(
|
||||
.asFile
|
||||
javaLauncher.set(project.javaToolchains.launcherFor {
|
||||
languageVersion.set(JavaLanguageVersion.of(25))
|
||||
// TODO - JB runtime 25 has issues with spark rn
|
||||
// vendor.set(JvmVendorSpec.JETBRAINS)
|
||||
@Suppress("UnstableApiUsage")
|
||||
vendor.set(JvmVendorSpec.JETBRAINS)
|
||||
})
|
||||
jvmArgs(/*"-XX:+AllowEnhancedClassRedefinition", */"--enable-native-access=ALL-UNNAMED")
|
||||
jvmArgs("-XX:+AllowEnhancedClassRedefinition", "--enable-native-access=ALL-UNNAMED")
|
||||
|
||||
if (rootProject.childProjects["test-plugin"] != null) {
|
||||
val testPluginJar = rootProject.project(":test-plugin").tasks.jar.flatMap { it.archiveFile }
|
||||
|
||||
@@ -34086,10 +34086,10 @@ index 06103ffdf599af91bc1d3baf89772a1acc93a21a..fc1f82d313acb8bf9bd03b1f09c68035
|
||||
return structureTemplate.save(new CompoundTag());
|
||||
}
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index d40863262dd7cf0e7c5667fd836e80d4f47e25aa..c4adc7266f41057bc93e8c34929c079c9c468d0b 100644
|
||||
index a9ee3ae3217ce7182cd8ab11ea455bbedca1c363..7bc5221da2707b53f0d710f80ea4bfa838195ad4 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -293,6 +293,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -294,6 +294,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
private final ServerClockManager clockManager;
|
||||
|
||||
public static <S extends MinecraftServer> S spin(final Function<Thread, S> factory) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
+2
-2
@@ -2321,7 +2321,7 @@ index 0000000000000000000000000000000000000000..505b5e39d0b4cf2a56c1c720f0c9ab01
|
||||
+ }
|
||||
+}
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index 7fa69d76bf2c802b538a21af40898b8abb79629e..d9daa3ea8c4dc21bbe0d0ea9f0ece3f6c6777ba4 100644
|
||||
index 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 7fa69d76bf2c802b538a21af40898b8abb79629e..d9daa3ea8c4dc21bbe0d0ea9f0ece3f6
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02752b35f1 100644
|
||||
index 07ba0aeefec186d7befe60c86dc5180e3131de51..482b428cab996c19664b57c341c55db5a66a6387 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -929,7 +929,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -930,7 +930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force, final boolean close) {
|
||||
// Paper end - add close param
|
||||
@@ -17,7 +17,7 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
|
||||
boolean result = false;
|
||||
|
||||
for (ServerLevel level : this.getAllLevels()) {
|
||||
@@ -941,6 +941,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -942,6 +942,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
result = true;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
|
||||
GameProfile singleplayerProfile = this.getSingleplayerProfile();
|
||||
this.storageSource.saveDataTag(this.worldData, singleplayerProfile == null ? null : singleplayerProfile.id());
|
||||
if (flush) {
|
||||
@@ -948,14 +955,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -949,14 +956,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
} else {
|
||||
this.savedDataStorage.scheduleSave();
|
||||
}
|
||||
@@ -48,7 +48,7 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
|
||||
boolean result = this.saveAllChunks(silent, flush, force);
|
||||
this.warnOnLowDiskSpace();
|
||||
return result;
|
||||
@@ -1600,9 +1606,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1602,9 +1608,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
|
||||
this.ticksUntilAutosave--;
|
||||
@@ -83,10 +83,10 @@ index 255e01251005a547088aba437ca27ce06fc996e5..487fcb8546ad24125c6573fb84f22a02
|
||||
ProfilerFiller profiler = Profiler.get();
|
||||
this.server.spark.executeMainThreadTasks(); // Paper - spark
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index e4d38cfdac2b926afae3b1b96679b6ce45ea256a..ca793f3f7300148cd55d2e873af5b43d9f4c708f 100644
|
||||
index 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)));
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ index ba372f75dec3ee8b89c48df2cc646f071a47d25a..706f529a7d1064aad128b1ee8c82f631
|
||||
|
||||
@Override
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 487fcb8546ad24125c6573fb84f22a02752b35f1..b675e9322234761a69e71e9e8b9d41364caf34b6 100644
|
||||
index 482b428cab996c19664b57c341c55db5a66a6387..bd5efcf1dee77c204bf29ef4693ac54146e8d901 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1741,32 +1741,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1743,32 +1743,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
return new ServerStatus.Players(maxPlayers, players.size(), sample);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ index 487fcb8546ad24125c6573fb84f22a02752b35f1..b675e9322234761a69e71e9e8b9d4136
|
||||
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
|
||||
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index 2cec345fdabc8e01e378e66c35093dec1fcb7c2c..0ae23c90594d85e94d17d8114a21fc62b79219d3 100644
|
||||
index 62d534132f6a2d69df28e244fa296f24a007181f..5cc94652112c67ca4e7ae6e8b324e9c9f8908d7d 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -5379,6 +5379,11 @@ public abstract class Entity
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Fri, 15 Feb 2019 01:08:19 -0500
|
||||
Subject: [PATCH] Allow Saving of Oversized Chunks
|
||||
|
||||
The Minecraft World Region File format has a hard cap of 1MB per chunk.
|
||||
This is due to the fact that the header of the file format only allocates
|
||||
a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector.
|
||||
|
||||
This limit can be reached fairly easily with books, resulting in the chunk being unable
|
||||
to save to the world. Worse off, is that nothing printed when this occured, and silently
|
||||
performed a chunk rollback on next load.
|
||||
|
||||
This leads to security risk with duplication and is being actively exploited.
|
||||
|
||||
This patch catches the too large scenario, falls back and moves any large Entity
|
||||
or Tile Entity into a new compound, and this compound is saved into a different file.
|
||||
|
||||
On Chunk Load, we check for oversized status, and if so, we load the extra file and
|
||||
merge the Entities and Tile Entities from the oversized chunk back into the level to
|
||||
then be loaded as normal.
|
||||
|
||||
Once a chunk is returned back to normal size, the oversized flag will clear, and no
|
||||
extra data file will exist.
|
||||
|
||||
This fix maintains compatability with all existing Anvil Region Format tools as it
|
||||
does not alter the save format. They will just not know about the extra entities.
|
||||
|
||||
This fix also maintains compatability if someone switches server jars to one without
|
||||
this fix, as the data will remain in the oversized file. Once the server returns
|
||||
to a jar with this fix, the data will be restored.
|
||||
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
index 7767e5420e3d2e569008747523d70147ea8d97fb..eddf7c3cef3b3209b47de97d93862937e9d2c82b 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
@@ -68,6 +68,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
this.info = info;
|
||||
this.path = path;
|
||||
this.version = version;
|
||||
+ this.initOversizedState(); // Paper
|
||||
if (!Files.isDirectory(externalFileDir)) {
|
||||
throw new IllegalArgumentException("Expected directory, got " + externalFileDir.toAbsolutePath());
|
||||
}
|
||||
@@ -463,4 +464,75 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
private interface CommitOp {
|
||||
void run() throws IOException;
|
||||
}
|
||||
+
|
||||
+ // Paper start
|
||||
+ private final byte[] oversized = new byte[1024];
|
||||
+ private int oversizedCount;
|
||||
+
|
||||
+ private synchronized void initOversizedState() throws IOException {
|
||||
+ Path metaFile = getOversizedMetaFile();
|
||||
+ if (Files.exists(metaFile)) {
|
||||
+ final byte[] read = Files.readAllBytes(metaFile);
|
||||
+ System.arraycopy(read, 0, oversized, 0, oversized.length);
|
||||
+ for (byte temp : oversized) {
|
||||
+ oversizedCount += temp;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static int getChunkIndex(int x, int z) {
|
||||
+ return (x & 31) + (z & 31) * 32;
|
||||
+ }
|
||||
+
|
||||
+ synchronized boolean isOversized(int x, int z) {
|
||||
+ return this.oversized[getChunkIndex(x, z)] == 1;
|
||||
+ }
|
||||
+
|
||||
+ synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
|
||||
+ final int offset = getChunkIndex(x, z);
|
||||
+ boolean previous = this.oversized[offset] == 1;
|
||||
+ this.oversized[offset] = (byte) (oversized ? 1 : 0);
|
||||
+ if (!previous && oversized) {
|
||||
+ oversizedCount++;
|
||||
+ } else if (!oversized && previous) {
|
||||
+ oversizedCount--;
|
||||
+ }
|
||||
+ if (previous && !oversized) {
|
||||
+ Path oversizedFile = getOversizedFile(x, z);
|
||||
+ if (Files.exists(oversizedFile)) {
|
||||
+ Files.delete(oversizedFile);
|
||||
+ }
|
||||
+ }
|
||||
+ if (oversizedCount > 0) {
|
||||
+ if (previous != oversized) {
|
||||
+ writeOversizedMeta();
|
||||
+ }
|
||||
+ } else if (previous) {
|
||||
+ Path oversizedMetaFile = getOversizedMetaFile();
|
||||
+ if (Files.exists(oversizedMetaFile)) {
|
||||
+ Files.delete(oversizedMetaFile);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void writeOversizedMeta() throws IOException {
|
||||
+ Files.write(this.getOversizedMetaFile(), this.oversized);
|
||||
+ }
|
||||
+
|
||||
+ private Path getOversizedMetaFile() {
|
||||
+ return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + ".oversized.nbt");
|
||||
+ }
|
||||
+
|
||||
+ private Path getOversizedFile(int x, int z) {
|
||||
+ return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
|
||||
+ }
|
||||
+
|
||||
+ synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException {
|
||||
+ Path file = getOversizedFile(x, z);
|
||||
+ try (DataInputStream out = new DataInputStream(new java.util.zip.InflaterInputStream(Files.newInputStream(file)))) { // Paper - Optimize NBT reading/writing; FastNBTParser buffers internally
|
||||
+ return ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead((java.io.DataInput) out); // Paper - Optimize NBT reading/writing
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+ // Paper end
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
index a78e27fa5733bdb21e06e89025fd23551b3912e7..939d9a660ca9c878d2c087bad07f64dab3c06fef 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
@@ -19,6 +19,7 @@ import net.minecraft.world.level.ChunkPos;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
|
||||
+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
|
||||
public static final String ANVIL_EXTENSION = ".mca";
|
||||
private static final int MAX_CACHE_SIZE = 256;
|
||||
private final Long2ObjectLinkedOpenHashMap<Optional<RegionFile>> regionCache = new Long2ObjectLinkedOpenHashMap<>();
|
||||
@@ -229,11 +230,54 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
return Objects.requireNonNull(this.getRegionFile(pos, true));
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ private static void printOversizedLog(String msg, Path file, int x, int z) {
|
||||
+ LOGGER.error("{} ({} - {},{}) Go clean it up to remove this message. /minecraft:tp {} 128 {} - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.", msg, file.toString().replaceAll(".+[\\\\/]", ""), x, z, x << 4, z << 4);
|
||||
+ }
|
||||
+
|
||||
+ private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
|
||||
+ synchronized (regionfile) {
|
||||
+ try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
|
||||
+ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x(), chunkCoordinate.z());
|
||||
+ CompoundTag chunk = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(datainputstream); // Paper - Optimize NBT reading/writing
|
||||
+ if (oversizedData == null) {
|
||||
+ return chunk;
|
||||
+ }
|
||||
+ CompoundTag oversizedLevel = oversizedData.getCompoundOrEmpty("Level");
|
||||
+
|
||||
+ mergeChunkList(chunk.getCompoundOrEmpty("Level"), oversizedLevel, "Entities", "Entities");
|
||||
+ mergeChunkList(chunk.getCompoundOrEmpty("Level"), oversizedLevel, "TileEntities", "TileEntities");
|
||||
+
|
||||
+ return chunk;
|
||||
+ } catch (Throwable t) {
|
||||
+ t.printStackTrace();
|
||||
+ throw t;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key, String oversizedKey) {
|
||||
+ net.minecraft.nbt.ListTag levelList = level.getListOrEmpty(key);
|
||||
+ net.minecraft.nbt.ListTag oversizedList = oversizedLevel.getListOrEmpty(oversizedKey);
|
||||
+
|
||||
+ if (!oversizedList.isEmpty()) {
|
||||
+ levelList.addAll(oversizedList);
|
||||
+ level.put(key, levelList);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
public @Nullable CompoundTag read(final ChunkPos pos) throws IOException {
|
||||
RegionFile region = this.getRegionFile(pos, false);
|
||||
if (region == null) {
|
||||
return null;
|
||||
}
|
||||
+ // Paper start
|
||||
+ if (region.isOversized(pos.x(), pos.z())) {
|
||||
+ printOversizedLog("Loading Oversized Chunk!", region.getPath(), pos.x(), pos.z());
|
||||
+ return readOversizedChunk(region, pos);
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
try (DataInputStream regionChunkInputStream = region.getChunkDataInputStream(pos)) {
|
||||
return regionChunkInputStream == null ? null : ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(regionChunkInputStream); // Paper - Optimize NBT reading/writing
|
||||
@@ -265,6 +309,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
} else {
|
||||
try (DataOutputStream output = region.getChunkDataOutputStream(pos)) {
|
||||
NbtIo.write(value, output);
|
||||
+ region.setOversized(pos.x(), pos.z(), false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+5
-6
@@ -8,7 +8,7 @@ This ensures at least a valid version of the chunk exists
|
||||
on disk, even if outdated
|
||||
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
index b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116da07c32c0 100644
|
||||
index 1077ff25ca0da7fb6f89dd2e16174f00b4986283..bd4b488cdf7a386bc458fad2ae659b77e60962ad 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
@@ -24,6 +24,7 @@ import org.slf4j.Logger;
|
||||
@@ -19,7 +19,7 @@ index b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116d
|
||||
private static final int SECTOR_BYTES = 4096;
|
||||
@VisibleForTesting
|
||||
protected static final int SECTOR_INTS = 1024;
|
||||
@@ -451,6 +452,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
@@ -903,6 +904,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ index b6c6bf9b20fee63b7e2a37d949bb1e55cef40de6..3ba442ac6b823e1c6a69398abc4f116d
|
||||
public void close() throws IOException {
|
||||
ByteBuffer result = ByteBuffer.wrap(this.buf, 0, this.count);
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b93011713c 100644
|
||||
index 9f8b1d4235d2f268a523c028006114b61d8d67ef..7e815fee19ab2c283bf1fdc3d6a530d4101de3de 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
@@ -95,11 +95,24 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
@@ -73,7 +73,7 @@ index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b9
|
||||
|
||||
return writeData;
|
||||
}
|
||||
@@ -307,9 +320,18 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
@@ -330,8 +343,17 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
if (value == null) {
|
||||
region.clear(pos);
|
||||
} else {
|
||||
@@ -82,7 +82,6 @@ index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b9
|
||||
+ DataOutputStream output = region.getChunkDataOutputStream(pos);
|
||||
+ try { // Paper - Only write if successful
|
||||
NbtIo.write(value, output);
|
||||
region.setOversized(pos.x(), pos.z(), false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
|
||||
+ // Paper start - don't write garbage data to disk if writing serialization fails
|
||||
+ output.close();
|
||||
+ } catch (final RegionFileSizeException ex) {
|
||||
@@ -93,7 +92,7 @@ index c4f27907774f694c6bf2129ba14e5155c838a19b..a2dd62ced02de213ffe1244b893069b9
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,4 +379,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
@@ -379,4 +401,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
public RegionStorageInfo info() {
|
||||
return this.info;
|
||||
}
|
||||
File renamed without changes.
+1
-1
@@ -14,7 +14,7 @@ timestamp so that fs watchers can detect when RegionFiles are
|
||||
modified.
|
||||
|
||||
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
|
||||
index 6dd6b4fb79870da6d3bf54dc813ae685237d1f0e..09205efff3a1a2042e67600510af270df9e46c67 100644
|
||||
index 48353259f9a17937f71951706e0172b5b4957d56..e9dc38d934ede23f17e18893ee207d9fbbd26449 100644
|
||||
--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
|
||||
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
|
||||
@@ -1309,6 +1309,14 @@ public final class MoonriseRegionFileIO {
|
||||
+1
-1
@@ -5,7 +5,7 @@ Subject: [PATCH] Remove debug-like logging
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 951f5ab5a05dcedf323845f57fd061645466e563..19c7dffe5f9c45cd99cb9b14cae5ac908cae0afc 100644
|
||||
index 2b5377da8fc3d8582c47809a5e4815d7b0293780..d920a4905d044925c4c4ab980fc238c9c489d5ac 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -1761,7 +1761,7 @@ public class ServerGamePacketListenerImpl
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
|
||||
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 19c7dffe5f9c45cd99cb9b14cae5ac908cae0afc..1c92e33104cc131269be15333eeee0ca5f0c92b1 100644
|
||||
index d920a4905d044925c4c4ab980fc238c9c489d5ac..60decc2534c5bc52b1a22e23a416a7660f724cb0 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -647,6 +647,7 @@ public class ServerGamePacketListenerImpl
|
||||
-845
@@ -1,845 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 2 Feb 2020 02:25:10 -0800
|
||||
Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt
|
||||
|
||||
Instead of trying to relocate the chunk, which is seems to never
|
||||
be the correct choice, so we end up duplicating or swapping chunks,
|
||||
we instead drop the current regionfile header and recalculate -
|
||||
hoping that at least then we don't swap chunks, and maybe recover
|
||||
them all.
|
||||
|
||||
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
|
||||
index 09205efff3a1a2042e67600510af270df9e46c67..775d93d35855e394e5eccf0ad80eca43b1ed9562 100644
|
||||
--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
|
||||
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
|
||||
@@ -1547,7 +1547,7 @@ public final class MoonriseRegionFileIO {
|
||||
|
||||
public abstract void finishWrite(final int chunkX, final int chunkZ, final WriteData writeData) throws IOException;
|
||||
|
||||
- public static record ReadData(ReadResult result, DataInputStream input, CompoundTag syncRead) {
|
||||
+ public static record ReadData(ReadResult result, DataInputStream input, CompoundTag syncRead, int recalculateCount) { // Paper - Attempt to recalculate regionfile header if it is corrupt
|
||||
public static enum ReadResult {
|
||||
NO_DATA,
|
||||
HAS_DATA,
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
||||
index a6f9435a8e2a6c8cc05bd3873cb6fc0ec98973b8..d44fd9eea0ccb09c894d6431ede8a4c2aae85c5b 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
||||
@@ -9,6 +9,27 @@ import java.util.BitSet;
|
||||
public class RegionBitmap {
|
||||
private final BitSet used = new BitSet();
|
||||
|
||||
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
|
||||
+ public final void copyFrom(RegionBitmap other) {
|
||||
+ BitSet thisBitset = this.used;
|
||||
+ BitSet otherBitset = other.used;
|
||||
+
|
||||
+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) {
|
||||
+ thisBitset.set(i, otherBitset.get(i));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public final boolean tryAllocate(int from, int length) {
|
||||
+ BitSet bitset = this.used;
|
||||
+ int firstSet = bitset.nextSetBit(from);
|
||||
+ if (firstSet > 0 && firstSet < (from + length)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ bitset.set(from, from + length);
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
|
||||
+
|
||||
public void force(final int position, final int size) {
|
||||
this.used.set(position, position + size);
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
index d1ea7f442e258b06f5c879de7d57fd16caf09178..5e64762059846f14d4e8ddfec1d1640c199cece2 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
@@ -46,6 +46,363 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
@VisibleForTesting
|
||||
protected final RegionBitmap usedSectors = new RegionBitmap();
|
||||
|
||||
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
|
||||
+ private static long roundToSectors(long bytes) {
|
||||
+ long sectors = bytes >>> 12; // 4096 = 2^12
|
||||
+ long remainingBytes = bytes & 4095;
|
||||
+ long sign = -remainingBytes; // sign is 1 if nonzero
|
||||
+ return sectors + (sign >>> 63);
|
||||
+ }
|
||||
+
|
||||
+ private static final net.minecraft.nbt.CompoundTag OVERSIZED_COMPOUND = new net.minecraft.nbt.CompoundTag();
|
||||
+
|
||||
+ private net.minecraft.nbt.@Nullable CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
|
||||
+ try {
|
||||
+ if (chunkDataLength < 0) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ long offset = sector * 4096L + 4L; // offset for chunk data
|
||||
+
|
||||
+ if ((offset + chunkDataLength) > fileLength) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength);
|
||||
+ if (chunkDataLength != this.file.read(chunkData, offset)) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ ((java.nio.Buffer)chunkData).flip();
|
||||
+
|
||||
+ byte compressionType = chunkData.get();
|
||||
+ if (compressionType < 0) { // compressionType & 128 != 0
|
||||
+ // oversized chunk
|
||||
+ return OVERSIZED_COMPOUND;
|
||||
+ }
|
||||
+
|
||||
+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType);
|
||||
+ if (compression == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
|
||||
+
|
||||
+ return ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(new DataInputStream(input)); // Paper - Optimize NBT reading/writing
|
||||
+ } catch (Exception ex) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private int getLength(long sector) throws IOException {
|
||||
+ ByteBuffer length = ByteBuffer.allocate(4);
|
||||
+ if (4 != this.file.read(length, sector * 4096L)) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ return length.getInt(0);
|
||||
+ }
|
||||
+
|
||||
+ private void backupRegionFile() {
|
||||
+ Path backup = this.path.getParent().resolve(this.path.getFileName() + "." + new java.util.Random().nextLong() + ".backup");
|
||||
+ this.backupRegionFile(backup);
|
||||
+ }
|
||||
+
|
||||
+ private void backupRegionFile(Path to) {
|
||||
+ try {
|
||||
+ this.file.force(true);
|
||||
+ LOGGER.warn("Backing up regionfile \"{}\" to {}", this.path.toAbsolutePath(), to.toAbsolutePath());
|
||||
+ Files.copy(this.path, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
|
||||
+ LOGGER.warn("Backed up the regionfile to {}", to.toAbsolutePath());
|
||||
+ } catch (IOException ex) {
|
||||
+ LOGGER.error("Failed to backup to {}", to.toAbsolutePath(), ex);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) {
|
||||
+ return (first.x() & ~31) == (second.x() & ~31) && (first.z() & ~31) == (second.z() & ~31);
|
||||
+ }
|
||||
+
|
||||
+ // note: only call for CHUNK regionfiles
|
||||
+ private final java.util.concurrent.atomic.AtomicInteger recalculateCount = new java.util.concurrent.atomic.AtomicInteger();
|
||||
+
|
||||
+ public int getRecalculateCount() {
|
||||
+ return this.recalculateCount.get();
|
||||
+ }
|
||||
+
|
||||
+ boolean recalculateHeader() throws IOException {
|
||||
+ if (!this.canRecalcHeader) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.path);
|
||||
+ if (ourLowerLeftPosition == null) {
|
||||
+ LOGGER.error("Unable to get chunk location of regionfile {}, cannot recover header", this.path.toAbsolutePath());
|
||||
+ return false;
|
||||
+ }
|
||||
+ synchronized (this) {
|
||||
+ this.recalculateCount.getAndIncrement();
|
||||
+
|
||||
+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile {}", this.path.toAbsolutePath(), new Throwable());
|
||||
+
|
||||
+ // try to backup file so maybe it could be sent to us for further investigation
|
||||
+
|
||||
+ this.backupRegionFile();
|
||||
+ net.minecraft.nbt.CompoundTag[] compounds = new net.minecraft.nbt.CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
|
||||
+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
|
||||
+ int[] sectorOffsets = new int[32 * 32]; // in sectors
|
||||
+ boolean[] hasAikarOversized = new boolean[32 * 32];
|
||||
+
|
||||
+ long fileLength = this.file.size();
|
||||
+ long totalSectors = roundToSectors(fileLength);
|
||||
+
|
||||
+ // search the regionfile from start to finish for the most up-to-date chunk data
|
||||
+
|
||||
+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
|
||||
+ int chunkDataLength = this.getLength(i);
|
||||
+ net.minecraft.nbt.CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
|
||||
+ if (compound == null || compound == OVERSIZED_COMPOUND) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ ChunkPos chunkPos = SerializableChunkData.getChunkCoordinate(compound);
|
||||
+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) {
|
||||
+ LOGGER.error("Ignoring absolute chunk {} in regionfile as it is not contained in the bounds of the regionfile '{}'. It should be in regionfile ({},{})", chunkPos, this.path.toAbsolutePath(), chunkPos.x() >> 5, chunkPos.z() >> 5);
|
||||
+ continue;
|
||||
+ }
|
||||
+ int location = (chunkPos.x() & 31) | ((chunkPos.z() & 31) << 5);
|
||||
+
|
||||
+ net.minecraft.nbt.CompoundTag otherCompound = compounds[location];
|
||||
+
|
||||
+ if (otherCompound != null && SerializableChunkData.getLastWorldSaveTime(otherCompound) > SerializableChunkData.getLastWorldSaveTime(compound)) {
|
||||
+ continue; // don't overwrite newer data.
|
||||
+ }
|
||||
+
|
||||
+ // aikar oversized?
|
||||
+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x(), chunkPos.z());
|
||||
+ boolean isAikarOversized = false;
|
||||
+ if (Files.exists(aikarOversizedFile)) {
|
||||
+ try {
|
||||
+ net.minecraft.nbt.CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x(), chunkPos.z());
|
||||
+ if (SerializableChunkData.getLastWorldSaveTime(compound) == SerializableChunkData.getLastWorldSaveTime(aikarOversizedCompound)) {
|
||||
+ // best we got for an id. hope it's good enough
|
||||
+ isAikarOversized = true;
|
||||
+ }
|
||||
+ } catch (Exception ex) {
|
||||
+ LOGGER.error("Failed to read aikar oversized data for absolute chunk ({},{}) in regionfile {}, oversized data for this chunk will be lost", chunkPos.x(), chunkPos.z(), this.path.toAbsolutePath(), ex);
|
||||
+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ hasAikarOversized[location] = isAikarOversized;
|
||||
+ compounds[location] = compound;
|
||||
+ rawLengths[location] = chunkDataLength + 4;
|
||||
+ sectorOffsets[location] = (int)i;
|
||||
+
|
||||
+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]);
|
||||
+ i += chunkSectorLength;
|
||||
+ --i; // gets incremented next iteration
|
||||
+ }
|
||||
+
|
||||
+ // forge style oversized data is already handled by the local search, and aikar data we just hope
|
||||
+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding
|
||||
+ // local data compound
|
||||
+
|
||||
+ java.nio.file.Path containingFolder = this.externalFileDir;
|
||||
+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new);
|
||||
+ boolean[] oversized = new boolean[32 * 32];
|
||||
+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32];
|
||||
+
|
||||
+ if (regionFiles != null) {
|
||||
+ int lowerXBound = ourLowerLeftPosition.x(); // inclusive
|
||||
+ int lowerZBound = ourLowerLeftPosition.z(); // inclusive
|
||||
+ int upperXBound = lowerXBound + 32 - 1; // inclusive
|
||||
+ int upperZBound = lowerZBound + 32 - 1; // inclusive
|
||||
+
|
||||
+ // read mojang oversized data
|
||||
+ for (Path regionFile : regionFiles) {
|
||||
+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile);
|
||||
+ if (oversizedCoords == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if ((oversizedCoords.x() < lowerXBound || oversizedCoords.x() > upperXBound) || (oversizedCoords.z() < lowerZBound || oversizedCoords.z() > upperZBound)) {
|
||||
+ continue; // not in our regionfile
|
||||
+ }
|
||||
+
|
||||
+ // ensure oversized data is valid & is newer than data in the regionfile
|
||||
+
|
||||
+ int location = (oversizedCoords.x() & 31) | ((oversizedCoords.z() & 31) << 5);
|
||||
+
|
||||
+ byte[] chunkData;
|
||||
+ try {
|
||||
+ chunkData = Files.readAllBytes(regionFile);
|
||||
+ } catch (Exception ex) {
|
||||
+ LOGGER.error("Failed to read oversized chunk data in file {}, data will be lost", regionFile.toAbsolutePath(), ex);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ net.minecraft.nbt.CompoundTag compound = null;
|
||||
+
|
||||
+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
|
||||
+ RegionFileVersion compression = null;
|
||||
+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
|
||||
+ try {
|
||||
+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java
|
||||
+ compound = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead((java.io.DataInput)in); // Paper - Optimize NBT reading/writing
|
||||
+ compression = compressionType;
|
||||
+ break; // reaches here iff readNBT does not throw
|
||||
+ } catch (Exception ex) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (compound == null) {
|
||||
+ LOGGER.error("Failed to read oversized chunk data in file {}, it's corrupt. Its data will be lost", regionFile.toAbsolutePath());
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (!SerializableChunkData.getChunkCoordinate(compound).equals(oversizedCoords)) {
|
||||
+ LOGGER.error("Can't use oversized chunk stored in {}, got absolute chunkpos: {}, expected {}", regionFile.toAbsolutePath(), net.minecraft.world.level.chunk.storage.SerializableChunkData.getChunkCoordinate(compound), oversizedCoords);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (compounds[location] == null || SerializableChunkData.getLastWorldSaveTime(compound) > SerializableChunkData.getLastWorldSaveTime(compounds[location])) {
|
||||
+ oversized[location] = true;
|
||||
+ oversizedCompressionTypes[location] = compression;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // now we need to calculate a new offset header
|
||||
+
|
||||
+ int[] calculatedOffsets = new int[32 * 32];
|
||||
+ RegionBitmap newSectorAllocations = new RegionBitmap();
|
||||
+ newSectorAllocations.force(0, 2); // make space for header
|
||||
+
|
||||
+ // allocate sectors for normal chunks
|
||||
+
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+
|
||||
+ if (oversized[location]) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ int rawLength = rawLengths[location]; // bytes
|
||||
+ int sectorOffset = sectorOffsets[location]; // sectors
|
||||
+ int sectorLength = (int)roundToSectors(rawLength);
|
||||
+
|
||||
+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) {
|
||||
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
|
||||
+ } else {
|
||||
+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at ({},{}) in regionfile {}, chunk will be regenerated", chunkX, chunkZ, this.path.toAbsolutePath());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // allocate sectors for oversized chunks
|
||||
+
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+
|
||||
+ if (!oversized[location]) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ int sectorOffset = newSectorAllocations.allocate(1);
|
||||
+ int sectorLength = 1;
|
||||
+
|
||||
+ try {
|
||||
+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096);
|
||||
+ // only allocate in the new offsets if the write succeeds
|
||||
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
|
||||
+ } catch (IOException ex) {
|
||||
+ newSectorAllocations.free(sectorOffset, sectorLength);
|
||||
+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at ({},{}) in regionfile {} will be regenerated", chunkX, chunkZ, this.path.toAbsolutePath());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // rewrite aikar oversized data
|
||||
+
|
||||
+ this.oversizedCount = 0;
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0;
|
||||
+
|
||||
+ this.oversizedCount += isAikarOversized;
|
||||
+ this.oversized[location] = (byte)isAikarOversized;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (this.oversizedCount > 0) {
|
||||
+ try {
|
||||
+ this.writeOversizedMeta();
|
||||
+ } catch (Exception ex) {
|
||||
+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile {}", this.path.toAbsolutePath(), ex);
|
||||
+ Files.deleteIfExists(this.getOversizedMetaFile());
|
||||
+ }
|
||||
+ } else {
|
||||
+ Files.deleteIfExists(this.getOversizedMetaFile());
|
||||
+ }
|
||||
+
|
||||
+ this.usedSectors.copyFrom(newSectorAllocations);
|
||||
+
|
||||
+ // before we overwrite the old sectors, print a summary of the chunks that got changed.
|
||||
+
|
||||
+ LOGGER.info("Starting summary of changes for regionfile {}", this.path.toAbsolutePath());
|
||||
+
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+
|
||||
+ int oldOffset = this.offsets.get(location);
|
||||
+ int newOffset = calculatedOffsets[location];
|
||||
+
|
||||
+ if (oldOffset == newOffset) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ this.offsets.put(location, newOffset); // overwrite incorrect offset
|
||||
+
|
||||
+ if (oldOffset == 0) {
|
||||
+ // found lost data
|
||||
+ LOGGER.info("Found missing data for local chunk ({},{}) in regionfile {}", chunkX, chunkZ, this.path.toAbsolutePath());
|
||||
+ } else if (newOffset == 0) {
|
||||
+ LOGGER.warn("Data for local chunk ({},{}) could not be recovered in regionfile {}, it will be regenerated", chunkX, chunkZ, this.path.toAbsolutePath());
|
||||
+ } else {
|
||||
+ LOGGER.info("Local chunk ({},{}) changed to point to newer data or correct chunk in regionfile {}", chunkX, chunkZ, this.path.toAbsolutePath());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ LOGGER.info("End of change summary for regionfile {}", this.path.toAbsolutePath());
|
||||
+
|
||||
+ // simply destroy the timestamp header, it's not used
|
||||
+
|
||||
+ for (int i = 0; i < 32 * 32; ++i) {
|
||||
+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? RegionFile.getTimestamp() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this
|
||||
+ }
|
||||
+
|
||||
+ // write new header
|
||||
+ try {
|
||||
+ this.flush();
|
||||
+ this.file.force(true); // try to ensure it goes through...
|
||||
+ LOGGER.info("Successfully wrote new header to disk for regionfile {}", this.path.toAbsolutePath());
|
||||
+ } catch (IOException ex) {
|
||||
+ LOGGER.error("Failed to write new header to disk for regionfile {}", this.path.toAbsolutePath(), ex);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ final boolean canRecalcHeader; // final forces compile fail on new constructor
|
||||
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
|
||||
+
|
||||
// Paper start - rewrite chunk system
|
||||
@Override
|
||||
public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final net.minecraft.nbt.CompoundTag data, final ChunkPos pos) throws IOException {
|
||||
@@ -75,6 +432,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
}
|
||||
|
||||
this.externalFileDir = externalFileDir;
|
||||
+ this.canRecalcHeader = info.dfuType()[0] == net.minecraft.util.datafix.DataFixTypes.CHUNK; // Paper - add can recalc flag
|
||||
this.offsets = this.header.asIntBuffer();
|
||||
this.offsets.limit(1024);
|
||||
this.header.position(4096);
|
||||
@@ -95,11 +453,13 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
|
||||
long size = Files.size(path);
|
||||
|
||||
- for (int i = 0; i < 1024; i++) {
|
||||
+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
|
||||
+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption
|
||||
+ for (int i = 0; i < 1024; i++) { final int headerLocation = i; // Paper - diff on change, we expect this to be the header location
|
||||
int offset = this.offsets.get(i);
|
||||
if (offset != 0) {
|
||||
- int sectorNumber = getSectorNumber(offset);
|
||||
- int numSectors = getNumSectors(offset);
|
||||
+ int sectorNumber = getSectorNumber(offset); // Paper - diff on change, we expect this to be offset in file in sectors
|
||||
+ int numSectors = getNumSectors(offset); // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
|
||||
// Spigot start
|
||||
if (numSectors == 255) {
|
||||
// We're maxed out, so we need to read the proper length from the section
|
||||
@@ -110,18 +470,62 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
// Spigot end
|
||||
if (sectorNumber < 2) {
|
||||
LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", path, i, sectorNumber);
|
||||
- this.offsets.put(i, 0);
|
||||
+ //this.offsets.put(i, 0); // Paper - we catch this, but need it in the header for the summary change
|
||||
} else if (numSectors == 0) {
|
||||
LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, i);
|
||||
- this.offsets.put(i, 0);
|
||||
+ //this.offsets.put(i, 0); // Paper - we catch this, but need it in the header for the summary change
|
||||
} else if (sectorNumber * 4096L > size) {
|
||||
LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", path, i, sectorNumber);
|
||||
- this.offsets.put(i, 0);
|
||||
+ //this.offsets.put(i, 0); // Paper - we catch this, but need it in the header for the summary change
|
||||
} else {
|
||||
- this.usedSectors.force(sectorNumber, numSectors);
|
||||
+ //this.usedSectors.force(sectorNumber, numSectors); // Paper - move this down so we can check if it fails to allocate
|
||||
+ }
|
||||
+ // Paper start - recalculate header on header corruption
|
||||
+ if (sectorNumber < 2 || numSectors <= 0 || ((long)sectorNumber * 4096L) > size) {
|
||||
+ if (this.canRecalcHeader) {
|
||||
+ LOGGER.error("Detected invalid header for regionfile {}! Recalculating header...", this.path.toAbsolutePath());
|
||||
+ needsHeaderRecalc = true;
|
||||
+ break;
|
||||
+ } else {
|
||||
+ // location = chunkX | (chunkZ << 5);
|
||||
+ LOGGER.error("Detected invalid header for regionfile {}! Cannot recalculate, removing local chunk ({},{}) from header",
|
||||
+ this.path.toAbsolutePath(), headerLocation & 31, headerLocation >>> 5);
|
||||
+ if (!hasBackedUp) {
|
||||
+ hasBackedUp = true;
|
||||
+ this.backupRegionFile();
|
||||
+ }
|
||||
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
|
||||
+ this.offsets.put(headerLocation, 0); // delete the entry from header
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+ boolean failedToAllocate = !this.usedSectors.tryAllocate(sectorNumber, numSectors);
|
||||
+ if (failedToAllocate) {
|
||||
+ LOGGER.error("Overlapping allocation by local chunk ({},{}) in regionfile {}", headerLocation & 31, headerLocation >>> 5, this.path.toAbsolutePath());
|
||||
}
|
||||
+ if (failedToAllocate & !this.canRecalcHeader) {
|
||||
+ // location = chunkX | (chunkZ << 5);
|
||||
+ LOGGER.error("Detected invalid header for regionfile {}! Cannot recalculate, removing local chunk ({},{}) from header",
|
||||
+ this.path.toAbsolutePath(), headerLocation & 31, headerLocation >>> 5);
|
||||
+ if (!hasBackedUp) {
|
||||
+ hasBackedUp = true;
|
||||
+ this.backupRegionFile();
|
||||
+ }
|
||||
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
|
||||
+ this.offsets.put(headerLocation, 0); // delete the entry from header
|
||||
+ continue;
|
||||
+ }
|
||||
+ needsHeaderRecalc |= failedToAllocate;
|
||||
+ // Paper end - recalculate header on header corruption
|
||||
}
|
||||
}
|
||||
+ // Paper start - recalculate header on header corruption
|
||||
+ // we move the recalc here so comparison to old header is correct when logging to console
|
||||
+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues
|
||||
+ LOGGER.error("Recalculating regionfile {}, header gave erroneous offsets & locations", this.path.toAbsolutePath());
|
||||
+ this.recalculateHeader();
|
||||
+ }
|
||||
+ // Paper end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +534,34 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
}
|
||||
|
||||
private Path getExternalChunkPath(final ChunkPos pos) {
|
||||
- String externalFileName = "c." + pos.x() + "." + pos.z() + ".mcc";
|
||||
+ String externalFileName = "c." + pos.x() + "." + pos.z() + ".mcc"; // Paper - diff on change
|
||||
return this.externalFileDir.resolve(externalFileName);
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ private static @Nullable ChunkPos getOversizedChunkPair(Path file) {
|
||||
+ String fileName = file.getFileName().toString();
|
||||
+
|
||||
+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ String[] split = fileName.split("\\.");
|
||||
+
|
||||
+ if (split.length != 4) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ try {
|
||||
+ int x = Integer.parseInt(split[1]);
|
||||
+ int z = Integer.parseInt(split[2]);
|
||||
+
|
||||
+ return new ChunkPos(x, z);
|
||||
+ } catch (NumberFormatException ex) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
public synchronized @Nullable DataInputStream getChunkDataInputStream(final ChunkPos pos) throws IOException {
|
||||
int offset = this.getOffset(pos);
|
||||
if (offset == 0) {
|
||||
@@ -155,6 +583,11 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
buffer.flip();
|
||||
if (buffer.remaining() < 5) {
|
||||
LOGGER.error("Chunk {} header is truncated: expected {} but read {}", pos, sectorsLength, buffer.remaining());
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,6 +595,11 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
byte versionId = buffer.get();
|
||||
if (length == 0) {
|
||||
LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -169,18 +607,45 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
if (isExternalStreamChunk(versionId)) {
|
||||
if (streamLength != 0) {
|
||||
LOGGER.warn("Chunk has both internal and external streams");
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
}
|
||||
|
||||
- return this.createExternalChunkInputStream(pos, getExternalChunkVersion(versionId));
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ final DataInputStream ret = this.createExternalChunkInputStream(pos, getExternalChunkVersion(versionId));
|
||||
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ return ret;
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
} else if (streamLength > buffer.remaining()) {
|
||||
LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", pos, streamLength, buffer.remaining());
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
} else if (streamLength < 0) {
|
||||
LOGGER.error("Declared size {} of chunk {} is negative", length, pos);
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
} else {
|
||||
JvmProfiler.INSTANCE.onRegionFileRead(this.info, pos, this.version, streamLength);
|
||||
- return this.createChunkInputStream(pos, versionId, createStream(buffer, streamLength));
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ final DataInputStream ret = this.createChunkInputStream(pos, versionId, createStream(buffer, streamLength));
|
||||
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ return ret;
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,9 +822,14 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
|
||||
}
|
||||
|
||||
private ByteBuffer createExternalStub() {
|
||||
+ // Paper start - add version param
|
||||
+ return this.createExternalStub(this.version);
|
||||
+ }
|
||||
+ private ByteBuffer createExternalStub(RegionFileVersion version) {
|
||||
+ // Paper end - add version param
|
||||
ByteBuffer stub = ByteBuffer.allocate(5);
|
||||
stub.putInt(1);
|
||||
- stub.put((byte)(this.version.getId() | EXTERNAL_STREAM_FLAG));
|
||||
+ stub.put((byte)(version.getId() | EXTERNAL_STREAM_FLAG)); // Paper - add version param
|
||||
stub.flip();
|
||||
return stub;
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
index 4d3abebdb8b5f952837acb3093ff6e04c44b08e4..6db9314f12dd69ab82d689ab77b7cc6bd53af102 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
||||
@@ -27,6 +27,32 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
private final Path folder;
|
||||
private final boolean sync;
|
||||
|
||||
+ // Paper start - recalculate region file headers
|
||||
+ private final boolean isChunkData;
|
||||
+
|
||||
+ @Nullable
|
||||
+ public static ChunkPos getRegionFileCoordinates(Path file) {
|
||||
+ String fileName = file.getFileName().toString();
|
||||
+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ String[] split = fileName.split("\\.");
|
||||
+
|
||||
+ if (split.length != 4) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ try {
|
||||
+ int x = Integer.parseInt(split[1]);
|
||||
+ int z = Integer.parseInt(split[2]);
|
||||
+
|
||||
+ return new ChunkPos(x << 5, z << 5);
|
||||
+ } catch (NumberFormatException ex) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
// Paper start - rewrite chunk system
|
||||
private static final int REGION_SHIFT = 5;
|
||||
private static final int MAX_NON_EXISTING_CACHE = 1024 * 4;
|
||||
@@ -144,12 +170,12 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
|
||||
if (input == null) {
|
||||
return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData(
|
||||
- ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.NO_DATA, null, null
|
||||
+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.NO_DATA, null, null, regionFile == null ? 0 : regionFile.getRecalculateCount() // Paper - Attempt to recalculate regionfile header if it is corrupt
|
||||
);
|
||||
}
|
||||
|
||||
final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData ret = new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData(
|
||||
- ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.HAS_DATA, input, null
|
||||
+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.HAS_DATA, input, null, regionFile.getRecalculateCount() // Paper - Attempt to recalculate regionfile header if it is corrupt
|
||||
);
|
||||
|
||||
if (!(input instanceof ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker)) {
|
||||
@@ -165,7 +191,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
}
|
||||
|
||||
return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData(
|
||||
- ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.SYNC_READ, null, syncRead
|
||||
+ ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData.ReadResult.SYNC_READ, null, syncRead, regionFile.getRecalculateCount() // Paper - Attempt to recalculate regionfile header if it is corrupt
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,7 +201,32 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
final int chunkX, final int chunkZ, final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData readData
|
||||
) throws IOException {
|
||||
try {
|
||||
- return ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(readData.input()); // Paper - Optimize NBT reading/writing
|
||||
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
|
||||
+ final CompoundTag ret = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(readData.input()); // Paper - Optimize NBT reading/writing
|
||||
+ if (!this.isChunkData) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
|
||||
+ final ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(ret);
|
||||
+ final RegionFile regionFile = this.getRegionFile(pos, true);
|
||||
+
|
||||
+ if (regionFile.getRecalculateCount() != readData.recalculateCount()) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ if (!headerChunkPos.equals(pos)) {
|
||||
+ LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + headerChunkPos + " instead! Attempting regionfile recalculation " + regionFile.getPath().toAbsolutePath());
|
||||
+ if (regionFile.recalculateHeader()) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ LOGGER.error(com.mojang.logging.LogUtils.FATAL_MARKER, "Can't recalculate regionfile header?");
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
|
||||
} finally {
|
||||
readData.input().close();
|
||||
}
|
||||
@@ -186,6 +237,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
this.folder = folder;
|
||||
this.sync = sync;
|
||||
this.info = info;
|
||||
+ this.isChunkData = info.dfuType()[0] == net.minecraft.util.datafix.DataFixTypes.CHUNK; // Paper - recalculate region file headers
|
||||
}
|
||||
|
||||
private @Nullable RegionFile getRegionFile(final ChunkPos pos, final boolean create) throws IOException {
|
||||
@@ -293,7 +345,25 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
|
||||
// Paper end
|
||||
|
||||
try (DataInputStream regionChunkInputStream = region.getChunkDataInputStream(pos)) {
|
||||
- return regionChunkInputStream == null ? null : ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(regionChunkInputStream); // Paper - Optimize NBT reading/writing
|
||||
+ // Paper start - recover from corrupt regionfile header
|
||||
+ if (regionChunkInputStream == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ final CompoundTag serialisedChunkData = ca.spottedleaf.moonrise.patches.nbt_rw.FastNBTParser.nbtIoRead(regionChunkInputStream); // Paper - Optimize NBT reading/writing
|
||||
+ if (this.isChunkData) {
|
||||
+ ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(serialisedChunkData);
|
||||
+ if (!headerChunkPos.equals(pos)) {
|
||||
+ LOGGER.error("Attempting to read chunk data at {} but got chunk data for {} instead! Attempting regionfile recalculation for regionfile {}", pos, headerChunkPos, region.getPath().toAbsolutePath());
|
||||
+ if (region.recalculateHeader()) {
|
||||
+ return this.read(pos);
|
||||
+ }
|
||||
+ LOGGER.error("Can't recalculate regionfile header, regenerating chunk {} for {}", pos, region.getPath().toAbsolutePath());
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+ return serialisedChunkData;
|
||||
+ // Paper end - recover from corrupt regionfile header
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
index ea229f63f81c5638ded74c28e1e13d8948f2e5e0..d991fccfc3d2cc51611f802a6d40429024e21d9a 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
@@ -21,7 +21,7 @@ import org.slf4j.Logger;
|
||||
|
||||
public class RegionFileVersion {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
|
||||
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
|
||||
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
|
||||
public static final RegionFileVersion VERSION_GZIP = register(
|
||||
new RegionFileVersion(1, "gzip", in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out))) // Paper - Enable for Configurable region compression format
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java b/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
|
||||
index 590bd1ca60956e5c5be58481f97401e55f9e1aeb..4e0c9fa1eecfa9f21285b7911e25830bbcbc8d86 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
|
||||
@@ -3,7 +3,12 @@ package net.minecraft.world.level.chunk.storage;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
-public record RegionStorageInfo(String level, ResourceKey<Level> dimension, String type) {
|
||||
+// Paper start - crazy hack to indicate what type the region file is
|
||||
+public record RegionStorageInfo(String level, ResourceKey<Level> dimension, String type, net.minecraft.util.datafix.DataFixTypes[] dfuType) {
|
||||
+ public RegionStorageInfo(String level, ResourceKey<Level> dimension, String type) {
|
||||
+ this(level, dimension, type, new net.minecraft.util.datafix.DataFixTypes[1]);
|
||||
+ }
|
||||
+ // Paper end - crazy hack to indicate what type the region file is
|
||||
public RegionStorageInfo withTypeSuffix(final String suffix) {
|
||||
return new RegionStorageInfo(this.level, this.dimension, this.type + suffix);
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
|
||||
index 84a92dc868e61969460dddb156af332e6384bcd1..3fef98e8c3c9908c6f05ded33596cbcb18996c3d 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
|
||||
@@ -119,6 +119,18 @@ public record SerializableChunkData(
|
||||
}
|
||||
}
|
||||
// Paper end - guard against serializing mismatching coordinates
|
||||
+ // Paper start - Attempt to recalculate regionfile header if it is corrupt
|
||||
+ // TODO: Check on update
|
||||
+ public static long getLastWorldSaveTime(final CompoundTag chunkData) {
|
||||
+ final int dataVersion = NbtUtils.getDataVersion(chunkData);
|
||||
+ if (dataVersion < 2842) { // Level tag is removed after this version
|
||||
+ final CompoundTag levelData = chunkData.getCompoundOrEmpty("Level");
|
||||
+ return levelData.getLongOr("LastUpdate", 0L);
|
||||
+ } else {
|
||||
+ return chunkData.getLongOr("LastUpdate", 0L);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - Attempt to recalculate regionfile header if it is corrupt
|
||||
|
||||
// Paper start - Do not let the server load chunks from newer versions
|
||||
private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().dataVersion().version();
|
||||
@@ -528,7 +540,7 @@ public record SerializableChunkData(
|
||||
tag.putInt("xPos", this.chunkPos.x());
|
||||
tag.putInt("yPos", this.minSectionY);
|
||||
tag.putInt("zPos", this.chunkPos.z());
|
||||
- tag.putLong("LastUpdate", this.lastUpdateTime);
|
||||
+ tag.putLong("LastUpdate", this.lastUpdateTime); // Paper - Diff on change
|
||||
tag.putLong("InhabitedTime", this.inhabitedTime);
|
||||
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
|
||||
tag.storeNullable("blending_data", BlendingData.Packed.CODEC, this.blendingData);
|
||||
diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
|
||||
index cdbe291d2053239e3b8dd0042dfb475306a6b6e5..12ac13394fc38f089be0ad7731eb4ea9487e1d17 100644
|
||||
--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
|
||||
+++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
|
||||
@@ -34,6 +34,7 @@ public class SimpleRegionStorage implements ca.spottedleaf.moonrise.patches.chun
|
||||
public SimpleRegionStorage(
|
||||
final RegionStorageInfo info, final Path folder, final DataFixer fixerUpper, final boolean syncWrites, final DataFixTypes dataFixType
|
||||
) {
|
||||
+ info.dfuType()[0] = dataFixType; // Paper - crazy hack to indicate what type the region file is
|
||||
this.fixerUpper = fixerUpper;
|
||||
this.dataFixType = dataFixType;
|
||||
this.storage = new IOWorker(info, folder, syncWrites).storage; // Paper - rewrite chunk system
|
||||
+2
-2
@@ -42,7 +42,7 @@ index 0a60283625536515bfcf6c2a09ca0f4771d6b142..42500899812fe3ef18c09f68a85012b7
|
||||
protected ChunkGenerator generator() {
|
||||
return this.worldGenContext.generator();
|
||||
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
|
||||
index 2dc8b94c434e841b319ef5b77bd725fd66f3c10b..a5540612a4052d9e9d8b1b0ac3c1591777f26caa 100644
|
||||
index 8f2c37a3a534b3b0d4f3ffd2a0d7013a9b454244..f1ca359abcc422e791c81e036b4777643bce53b3 100644
|
||||
--- a/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -547,7 +547,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
||||
@@ -75,7 +75,7 @@ index 2dc8b94c434e841b319ef5b77bd725fd66f3c10b..a5540612a4052d9e9d8b1b0ac3c15917
|
||||
profiler.popPush("tickSpawningChunks");
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||
index 2cf8854636df72c1dccbb73dfe8f642dfdf1ea37..99403edc1eda1e238b66028b7d236e5fceb235a1 100644
|
||||
index 60db677d06207b488aad0363af55012b2d76fe8b..eeeb4075a6bbc4e310c2ad685f4efeade702ee3f 100644
|
||||
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -422,6 +422,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
+2
-2
@@ -37,7 +37,7 @@ index 42500899812fe3ef18c09f68a85012b705c1010d..fdcbf132e2045bc6828028556ca4c354
|
||||
// Paper end - Optional per player mob spawns
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
|
||||
index a5540612a4052d9e9d8b1b0ac3c1591777f26caa..c4cfb53e145d69666ffc27ef4ee0c50b54eb9143 100644
|
||||
index f1ca359abcc422e791c81e036b4777643bce53b3..ef53d4d17222a882dd3d33575096c864a0a00a06 100644
|
||||
--- a/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -552,7 +552,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
|
||||
@@ -60,7 +60,7 @@ index a5540612a4052d9e9d8b1b0ac3c1591777f26caa..c4cfb53e145d69666ffc27ef4ee0c50b
|
||||
spawnCookie = NaturalSpawner.createState(chunkCount, this.level, this::getFullChunk, null, true);
|
||||
} else {
|
||||
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||
index 99403edc1eda1e238b66028b7d236e5fceb235a1..23b3a82ab3e1c6abc85000d9285470cb12300a6b 100644
|
||||
index eeeb4075a6bbc4e310c2ad685f4efeade702ee3f..1137be30f125cb79f05f3e5c104cdf8cca979ff1 100644
|
||||
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||
@@ -426,6 +426,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||
+1
-1
@@ -5,7 +5,7 @@ Subject: [PATCH] DataConverter, Moonrise & co fixes
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 1c92e33104cc131269be15333eeee0ca5f0c92b1..6dc9e669a37ddc0a17099bcc4839b42fe237cf99 100644
|
||||
index 60decc2534c5bc52b1a22e23a416a7660f724cb0..8178922f5ecc7f6709335c7fbeb6dca505b044bb 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -1857,9 +1857,14 @@ public class ServerGamePacketListenerImpl
|
||||
File renamed without changes.
File renamed without changes.
+2
-2
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
|
||||
+ }
|
||||
+}
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index b675e9322234761a69e71e9e8b9d41364caf34b6..c3bf41af0273e902f881cc070fcef6dd64abb063 100644
|
||||
index bd5efcf1dee77c204bf29ef4693ac54146e8d901..7517b872794f3bbe556f3abbeb294f426c8da899 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1797,6 +1797,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1799,6 +1799,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
level.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
|
||||
level.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
|
||||
level.updateLagCompensationTick(); // Paper - lag compensation
|
||||
+1
-1
@@ -5,7 +5,7 @@ Subject: [PATCH] Pre-load data off-main during startup
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
|
||||
index c22cc4387e7cc5da7d38ee14ed7f187b5d9507f4..aa7f2ba4d931f98ce15c52dc2b99f07504149582 100644
|
||||
index b515d8be0e2f0639f7fc843d6ce074b0a583c673..984cf7143e3809507c557e0e70ab271b753db235 100644
|
||||
--- a/net/minecraft/server/Main.java
|
||||
+++ b/net/minecraft/server/Main.java
|
||||
@@ -102,7 +102,10 @@ public class Main {
|
||||
File renamed without changes.
File renamed without changes.
@@ -140,7 +140,7 @@
|
||||
|
||||
WorldStem worldStem;
|
||||
try {
|
||||
@@ -169,17 +_,31 @@
|
||||
@@ -169,17 +_,40 @@
|
||||
executor -> WorldLoader.load(
|
||||
worldLoadConfig,
|
||||
context -> {
|
||||
@@ -161,8 +161,16 @@
|
||||
+ }
|
||||
+ // Paper end - migrate startup world
|
||||
LevelDataAndDimensions worldData = LevelStorageSource.getLevelDataAndDimensions(
|
||||
- access, levelDataTag, context.dataConfiguration(), datapackDimensions, context.datapackWorldRegistries()
|
||||
+ access, levelDataTag, context.dataConfiguration(), datapackDimensions, context.datapackWorldRegistries(), net.minecraft.world.level.Level.OVERWORLD // Paper
|
||||
access, levelDataTag, context.dataConfiguration(), datapackDimensions, context.datapackWorldRegistries()
|
||||
+ // Paper start - pass dimension and regenerate missing world gen settings
|
||||
+ , net.minecraft.world.level.Level.OVERWORLD
|
||||
+ , () -> {
|
||||
+ final DedicatedServerProperties properties = settings.getProperties();
|
||||
+ return new WorldGenSettings(
|
||||
+ properties.worldOptions, properties.createDimensions(context.datapackWorldRegistries())
|
||||
+ );
|
||||
+ }
|
||||
+ // Paper end - pass dimension and regenerate missing world gen settings
|
||||
);
|
||||
return new WorldLoader.DataLoadOutput<>(
|
||||
worldData.worldDataAndGenSettings(), worldData.dimensions().dimensionsRegistryAccess()
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
private @Nullable ServerStatus status;
|
||||
private ServerStatus.@Nullable Favicon statusIcon;
|
||||
private final RandomSource random = RandomSource.create();
|
||||
@@ -234,13 +_,14 @@
|
||||
@@ -234,13 +_,15 @@
|
||||
private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
|
||||
private PlayerList playerList;
|
||||
private volatile boolean running = true;
|
||||
+ public volatile boolean hasStarted = false; // Paper
|
||||
+ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
|
||||
private boolean stopped;
|
||||
private int tickCount;
|
||||
@@ -758,7 +759,7 @@
|
||||
protected void runServer() {
|
||||
try {
|
||||
if (!this.initServer()) {
|
||||
@@ -710,30 +_,73 @@
|
||||
@@ -710,30 +_,74 @@
|
||||
}
|
||||
|
||||
this.nextTickTimeNanos = Util.getNanos();
|
||||
@@ -778,6 +779,7 @@
|
||||
+ // tasks are default scheduled at -1 + delay, and first tick will tick at 1
|
||||
+ final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Improve startup message
|
||||
+ LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Improve startup message
|
||||
+ this.hasStarted = true;
|
||||
+ org.spigotmc.WatchdogThread.tick();
|
||||
+ // Paper end
|
||||
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
--- a/net/minecraft/server/commands/RandomCommand.java
|
||||
+++ b/net/minecraft/server/commands/RandomCommand.java
|
||||
@@ -42,7 +_,7 @@
|
||||
.then(drawRandomValueTree("roll", true))
|
||||
.then(
|
||||
Commands.literal("reset")
|
||||
- .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
|
||||
+ .requires(stack -> stack.getBukkitSender().hasPermission("minecraft.command.random.reset")) // Paper - specific permissions for sub command
|
||||
.then(
|
||||
Commands.literal("*")
|
||||
.executes(c -> resetAllSequences(c.getSource()))
|
||||
@@ -122,7 +_,7 @@
|
||||
.then(
|
||||
Commands.argument("sequence", IdentifierArgument.id())
|
||||
.suggests(RandomCommand::suggestRandomSequence)
|
||||
- .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
|
||||
+ .requires(stack -> stack.getBukkitSender().hasPermission("minecraft.command.random.sequence." + (announce ? "roll" : "value"))) // Paper - specific permissions for sub command
|
||||
.executes(
|
||||
c -> randomSample(c.getSource(), RangeArgument.Ints.getRange(c, "range"), IdentifierArgument.getId(c, "sequence"), announce)
|
||||
)
|
||||
@@ -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()
|
||||
+ );
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@
|
||||
+ this.inventory.getItems().stream().filter(item -> !item.isEmpty()).forEach(stack -> this.spawnAtLocation(level, stack)); // Paper - delay removal post event - taken from removeAllItems
|
||||
+ this.postDeathEventTasks.add(this.inventory::clearContent); // Paper - only clear after death event
|
||||
ItemStack itemStack = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
||||
if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
||||
- if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
||||
+ if (!itemStack.isEmpty() && !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) && !this.droppedEquipmentSlots.contains(EquipmentSlot.MAINHAND)) { // Paper
|
||||
this.spawnAtLocation(level, itemStack);
|
||||
- this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
||||
+ this.postDeathEventTasks.add(() -> this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY)); // Paper
|
||||
|
||||
@@ -29,3 +29,20 @@
|
||||
return this.container.stillValid(player);
|
||||
}
|
||||
|
||||
@@ -107,7 +_,15 @@
|
||||
if (this.player instanceof ServerPlayer serverPlayer) {
|
||||
ServerLevel level = serverPlayer.level();
|
||||
CraftingInput craftInput = this.container.asCraftInput();
|
||||
- ItemStack result = CrafterBlock.getPotentialResults(level, craftInput).map(recipe -> recipe.value().assemble(craftInput)).orElse(ItemStack.EMPTY);
|
||||
+ // Paper start - Fire PrepareItemCraftEvent for Crafter preview
|
||||
+ final java.util.Optional<net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe>> potentialResults = CrafterBlock.getPotentialResults(level, craftInput);
|
||||
+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(
|
||||
+ this.container,
|
||||
+ this.resultContainer,
|
||||
+ potentialResults.map(recipe -> recipe.value().assemble(craftInput)).orElse(ItemStack.EMPTY),
|
||||
+ this.getBukkitView(),
|
||||
+ potentialResults.map(net.minecraft.world.item.crafting.RecipeHolder::value).orElse(null) instanceof net.minecraft.world.item.crafting.RepairItemRecipe);
|
||||
+ // Paper end - Fire PrepareItemCraftEvent for Crafter preview
|
||||
this.resultContainer.setItem(0, result);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class BucketItem extends Item implements DispensibleContainerItem {
|
||||
+ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
|
||||
+ static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
|
||||
protected final Fluid content;
|
||||
|
||||
public BucketItem(final Fluid content, final Item.Properties properties) {
|
||||
@@ -65,7 +65,7 @@
|
||||
+ return this.emptyContents(user, level, pos, hitResult, null, null, null, InteractionHand.MAIN_HAND);
|
||||
+ }
|
||||
+
|
||||
+ public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult, final Direction direction, final BlockPos clicked, final ItemStack itemStack, final InteractionHand hand) {
|
||||
+ public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult, final @Nullable Direction direction, final @Nullable BlockPos clicked, final @Nullable ItemStack itemStack, final InteractionHand hand) {
|
||||
+ // CraftBukkit end
|
||||
if (!(this.content instanceof FlowingFluid flowingFluid)) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/world/item/MobBucketItem.java
|
||||
+++ b/net/minecraft/world/item/MobBucketItem.java
|
||||
@@ -52,18 +_,18 @@
|
||||
@@ -52,18 +_,28 @@
|
||||
}
|
||||
|
||||
if (mob != null) {
|
||||
@@ -14,6 +14,16 @@
|
||||
- public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult) {
|
||||
+ public boolean emptyContents(final @Nullable LivingEntity user, final Level level, final BlockPos pos, final @Nullable BlockHitResult hitResult, final net.minecraft.core.Direction direction, final BlockPos clicked, final ItemStack itemStack, final net.minecraft.world.InteractionHand hand) { // Paper
|
||||
if (this.content == Fluids.EMPTY) {
|
||||
+ // Paper start - call PlayerBucketEmptyEvent for mobs in buckets without fluids
|
||||
+ if (user instanceof net.minecraft.world.entity.player.Player player) {
|
||||
+ org.bukkit.event.player.PlayerBucketEmptyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent(level, player, pos, clicked, direction, itemStack, hand);
|
||||
+ if (event.isCancelled()) {
|
||||
+ player.containerMenu.sendAllDataToRemote(); // SPIGOT-4541
|
||||
+ return false;
|
||||
+ }
|
||||
+ BucketItem.itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(org.bukkit.inventory.ItemStack.of(org.bukkit.Material.BUCKET)) ? null : org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.playEmptySound(user, level, pos);
|
||||
return true;
|
||||
} else {
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
--- a/net/minecraft/world/level/block/entity/PotentSulfurBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/PotentSulfurBlockEntity.java
|
||||
@@ -127,7 +_,19 @@
|
||||
&& !entityToBeLaunched.isPassenger()
|
||||
&& !entityToBeLaunched.is(EntityTypeTags.NOT_AFFECTED_BY_GEYSERS)
|
||||
&& entityVelocity.y < 0.3F + waterBlocks * 0.1) {
|
||||
- entityToBeLaunched.addDeltaMovement(new Vec3(0.0, 0.2F, 0.0));
|
||||
+ // Paper start - add EntityPotentSulfurLaunchEvent
|
||||
+ final Vec3 launchVelocity = new Vec3(0.0, GEYSER_LAUNCH_FORCE, 0.0);
|
||||
+ if (io.papermc.paper.event.entity.EntityPotentSulfurLaunchEvent.getHandlerList().hasListeners()) {
|
||||
+ io.papermc.paper.event.entity.EntityPotentSulfurLaunchEvent event = new io.papermc.paper.event.entity.EntityPotentSulfurLaunchEvent(
|
||||
+ entityToBeLaunched.getBukkitEntity(),
|
||||
+ org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
|
||||
+ org.bukkit.craftbukkit.util.CraftLocation.toBukkit(sourceBlock, level.getWorld()),
|
||||
+ org.bukkit.craftbukkit.util.CraftVector.toBukkit(launchVelocity)
|
||||
+ );
|
||||
+ event.callEvent();
|
||||
+ }
|
||||
+ entityToBeLaunched.addDeltaMovement(launchVelocity);
|
||||
+ // Paper end - add EntityPotentSulfurLaunchEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -16,7 +16,7 @@
|
||||
Feature feature = featureHolder.value();
|
||||
removeSapling(level, pos);
|
||||
if (feature.place(level, generator, random, pos)) {
|
||||
@@ -229,4 +_,60 @@
|
||||
@@ -229,4 +_,64 @@
|
||||
|
||||
private record TwoByTwoSaplingPos(int offsetX, int offsetZ, List<Pair<BlockState, BlockPos>> groundLevelSurroundingBlocks) {
|
||||
}
|
||||
@@ -69,8 +69,12 @@
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK;
|
||||
+ } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING;
|
||||
+ } else if (feature.is(TreeFeatures.RED_POPLAR) || feature.is(TreeFeatures.ORANGE_POPLAR) || feature.is(TreeFeatures.YELLOW_POPLAR)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.POPLAR;
|
||||
+ } else if (feature.is(TreeFeatures.RED_POPLAR)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_POPLAR;
|
||||
+ } else if (feature.is(TreeFeatures.ORANGE_POPLAR)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ORANGE_POPLAR;
|
||||
+ } else if (feature.is(TreeFeatures.YELLOW_POPLAR)) {
|
||||
+ net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.YELLOW_POPLAR;
|
||||
+ } else {
|
||||
+ throw new IllegalArgumentException("Unknown tree generator " + feature);
|
||||
+ }
|
||||
|
||||
+4
-4
@@ -8,8 +8,8 @@
|
||||
+ if (numSectors == 255) {
|
||||
+ // We're maxed out, so we need to read the proper length from the section
|
||||
+ ByteBuffer realLen = ByteBuffer.allocate(4);
|
||||
+ this.file.read(realLen, sectorNumber * 4096);
|
||||
+ numSectors = (realLen.getInt(0) + 4) / 4096 + 1;
|
||||
+ this.file.read(realLen, sectorNumber * 4096L);
|
||||
+ numSectors = sizeToSectors(realLen.getInt(0) + 4);
|
||||
+ }
|
||||
+ // Spigot end
|
||||
if (sectorNumber < 2) {
|
||||
@@ -22,8 +22,8 @@
|
||||
+ // Spigot start
|
||||
+ if (numSectors == 255) {
|
||||
+ ByteBuffer realLen = ByteBuffer.allocate(4);
|
||||
+ this.file.read(realLen, sectorNumber * 4096);
|
||||
+ numSectors = (realLen.getInt(0) + 4) / 4096 + 1;
|
||||
+ this.file.read(realLen, sectorNumber * 4096L);
|
||||
+ numSectors = sizeToSectors(realLen.getInt(0) + 4);
|
||||
+ }
|
||||
+ // Spigot end
|
||||
int sectorsLength = numSectors * 4096;
|
||||
|
||||
+41
-2
@@ -1,11 +1,32 @@
|
||||
--- a/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
|
||||
@@ -145,13 +_,14 @@
|
||||
@@ -145,21 +_,48 @@
|
||||
final WorldDataConfiguration dataConfiguration,
|
||||
final Registry<LevelStem> datapackDimensions,
|
||||
final HolderLookup.Provider registryAccess
|
||||
- ) {
|
||||
+ , final ResourceKey<Level> dimension // Paper - pass dimension
|
||||
) {
|
||||
+ ) {
|
||||
+ // Paper start - Regenerate missing overworld world gen settings
|
||||
+ return getLevelDataAndDimensions(worldAccess, levelDataTag, dataConfiguration, datapackDimensions, registryAccess, dimension, () -> {
|
||||
+ // Preserve dimension settings if we got them from the datapack
|
||||
+ WorldDimensions defaultDimensions = datapackDimensions.containsKey(LevelStem.OVERWORLD)
|
||||
+ ? new WorldDimensions(datapackDimensions)
|
||||
+ : net.minecraft.world.level.levelgen.presets.WorldPresets.createNormalWorldDimensions(registryAccess);
|
||||
+ return new WorldGenSettings(WorldOptions.defaultWithRandomSeed(), defaultDimensions);
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ public static LevelDataAndDimensions getLevelDataAndDimensions(
|
||||
+ final LevelStorageSource.LevelStorageAccess worldAccess,
|
||||
+ final Dynamic<?> levelDataTag,
|
||||
+ final WorldDataConfiguration dataConfiguration,
|
||||
+ final Registry<LevelStem> datapackDimensions,
|
||||
+ final HolderLookup.Provider registryAccess,
|
||||
+ final ResourceKey<Level> dimension,
|
||||
+ final java.util.function.Supplier<WorldGenSettings> worldGenSettingsFactory
|
||||
+ ) {
|
||||
+ // Paper end - Regenerate missing overworld world gen settings
|
||||
if (DataFixers.getFileFixer().requiresFileFixing(NbtUtils.getDataVersion(levelDataTag))) {
|
||||
throw new IllegalStateException("Cannot get level data without file fixing first");
|
||||
}
|
||||
@@ -16,6 +37,24 @@
|
||||
.mapOrElse(
|
||||
Function.identity(),
|
||||
error -> {
|
||||
- LOGGER.error(
|
||||
- "Unable to read or access the world gen settings file! Falling back to the default settings with a random world seed. {}",
|
||||
- error.message()
|
||||
+ // Paper start - Regenerate missing overworld world gen settings
|
||||
+ Path dataLocation = WorldGenSettings.TYPE.id().withSuffix(".dat").resolveAgainst(worldAccess.getDimensionPath(dimension).resolve(LevelResource.DATA.id()));
|
||||
+ if (!Files.notExists(dataLocation, java.nio.file.LinkOption.NOFOLLOW_LINKS)) {
|
||||
+ throw new IllegalStateException("Unable to read or access the world gen settings file " + dataLocation + ". " + error.message());
|
||||
+ }
|
||||
+ LOGGER.warn(
|
||||
+ "World gen settings file is missing! Regenerating settings from server.properties: {}",
|
||||
+ dataLocation
|
||||
);
|
||||
- return new WorldGenSettings(WorldOptions.defaultWithRandomSeed(), new WorldDimensions(datapackDimensions));
|
||||
+ return worldGenSettingsFactory.get();
|
||||
+ // Paper end - Regenerate missing overworld world gen settings
|
||||
}
|
||||
);
|
||||
LevelSettings settings = LevelSettings.parse(dataTag, dataConfiguration);
|
||||
@@ -170,9 +_,9 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
--- a/net/minecraft/world/scores/Scoreboard.java
|
||||
+++ b/net/minecraft/world/scores/Scoreboard.java
|
||||
@@ -267,6 +_,7 @@
|
||||
}
|
||||
|
||||
this.onTeamRemoved(team);
|
||||
+ team.getPlayers().clear(); // Paper - Clear scoreboard team entries on unregister
|
||||
}
|
||||
|
||||
public boolean addPlayerToTeam(final String player, final PlayerTeam team) {
|
||||
@@ -373,7 +_,7 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
+20
@@ -2,6 +2,8 @@ package io.papermc.paper.command.brigadier;
|
||||
|
||||
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.adventure.PaperAdventure;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
@@ -49,6 +51,24 @@ public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBriga
|
||||
return this.getHandle().withEntity(((CraftEntity) executor).getHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendReply(final ComponentLike message) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.getHandle().sendSystemMessage(PaperAdventure.asVanilla(message.asComponent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendSuccess(final ComponentLike message, final boolean allowInformingAdmins) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.getHandle().sendSuccess(() -> PaperAdventure.asVanilla(message.asComponent()), allowInformingAdmins);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendFailure(final ComponentLike message) {
|
||||
Preconditions.checkArgument(message != null, "message cannot be null.");
|
||||
this.getHandle().sendFailure(PaperAdventure.asVanilla(message.asComponent()), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Player getPlayerOrThrow() throws CommandSyntaxException {
|
||||
return this.getHandle().getPlayerOrException().getBukkitEntity();
|
||||
|
||||
@@ -12,10 +12,11 @@ import org.jspecify.annotations.NullMarked;
|
||||
@NullMarked
|
||||
public interface PluginMessageBridgeImpl {
|
||||
|
||||
boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit
|
||||
boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null;
|
||||
int CHANNEL_LIMIT = Integer.getInteger("paper.pluginMessageChannelLimit", 128);
|
||||
|
||||
default boolean addChannel(String channel) {
|
||||
Preconditions.checkState(DISABLE_CHANNEL_LIMIT || this.channels().size() < 128, "Cannot register channel. Too many channels registered!"); // Paper - flag to disable channel limit
|
||||
Preconditions.checkState(DISABLE_CHANNEL_LIMIT || this.channels().size() < CHANNEL_LIMIT, "Cannot register channel. Too many channels registered!");
|
||||
channel = StandardMessenger.validateAndCorrectChannel(channel);
|
||||
if (channels().add(channel)) {
|
||||
if (this instanceof CraftPlayer player) {
|
||||
@@ -37,7 +38,7 @@ public interface PluginMessageBridgeImpl {
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
Set<String> channels();
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
+103
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -233,6 +233,7 @@ public final class FoliaAsyncScheduler implements AsyncScheduler {
|
||||
this.delay = FoliaAsyncScheduler.this.timerThread.schedule(this, delay, TimeUnit.NANOSECONDS);
|
||||
} else {
|
||||
// cancelled repeating task
|
||||
this.state = STATE_CANCELLED;
|
||||
removeFromTasks = true;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -174,13 +174,18 @@ public final class FoliaEntityScheduler implements EntityScheduler {
|
||||
this.plugin.getLogger().log(Level.WARNING, "Entity task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable);
|
||||
} finally {
|
||||
boolean reschedule = false;
|
||||
if (!repeating && !retired) {
|
||||
if (!repeating && !retired) {
|
||||
this.setStateVolatile(STATE_FINISHED);
|
||||
} else if (retired || !this.plugin.isEnabled()) {
|
||||
this.setStateVolatile(STATE_CANCELLED);
|
||||
} else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) {
|
||||
reschedule = true;
|
||||
} // else: cancelled repeating task
|
||||
this.setStateVolatile(STATE_CANCELLED);
|
||||
} else {
|
||||
final int previousState = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE);
|
||||
if (previousState == STATE_EXECUTING) {
|
||||
reschedule = true;
|
||||
} else if (previousState == STATE_EXECUTING_CANCELLED) {
|
||||
this.setStateVolatile(STATE_CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!reschedule) {
|
||||
this.run = null;
|
||||
|
||||
+8
-3
@@ -182,9 +182,14 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
|
||||
boolean reschedule = false;
|
||||
if (!repeating) {
|
||||
this.setStateVolatile(STATE_FINISHED);
|
||||
} else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) {
|
||||
reschedule = true;
|
||||
} // else: cancelled repeating task
|
||||
} else {
|
||||
final int previousState = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE);
|
||||
if (previousState == STATE_EXECUTING) {
|
||||
reschedule = true;
|
||||
} else if (previousState == STATE_EXECUTING_CANCELLED) {
|
||||
this.setStateVolatile(STATE_CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!reschedule) {
|
||||
this.run = null;
|
||||
|
||||
@@ -144,7 +144,9 @@ public record PaperWorldLoader(MinecraftServer server, String levelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WorldGenSettings worldGenSettings = !hasWorldData
|
||||
final boolean isOverworldLevel = loading.info().dimensionKey() == Level.OVERWORLD;
|
||||
|
||||
final WorldGenSettings worldGenSettings = isOverworldLevel || !hasWorldData
|
||||
? this.server.getWorldGenSettings()
|
||||
: loadWorldGenSettings(
|
||||
this.server.storageSource,
|
||||
@@ -153,7 +155,7 @@ public record PaperWorldLoader(MinecraftServer server, String levelId) {
|
||||
);
|
||||
final var worldDataAndGenSettings = new LevelDataAndDimensions.WorldDataAndGenSettings(this.server.getWorldData(), worldGenSettings);
|
||||
|
||||
if (loading.info().dimensionKey() == Level.OVERWORLD) {
|
||||
if (isOverworldLevel) {
|
||||
final var primaryLevelData = ((PrimaryLevelData) this.server.getWorldData());
|
||||
primaryLevelData.checkName(loading.data().bukkitName());
|
||||
primaryLevelData.setModdedInfo(this.server.getServerModName(), this.server.getModdedStatus().shouldReportAsModified());
|
||||
|
||||
@@ -239,9 +239,15 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
|
||||
case PALE_OAK_CREAKING:
|
||||
gen = TreeFeatures.PALE_OAK_CREAKING;
|
||||
break;
|
||||
case POPLAR:
|
||||
case RED_POPLAR:
|
||||
gen = TreeFeatures.RED_POPLAR;
|
||||
break;
|
||||
case ORANGE_POPLAR:
|
||||
gen = TreeFeatures.ORANGE_POPLAR;
|
||||
break;
|
||||
case YELLOW_POPLAR:
|
||||
gen = TreeFeatures.YELLOW_POPLAR;
|
||||
break;
|
||||
case TREE:
|
||||
default:
|
||||
gen = TreeFeatures.OAK;
|
||||
|
||||
@@ -216,6 +216,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.Merchant;
|
||||
import org.bukkit.inventory.Recipe;
|
||||
import org.bukkit.loot.LootTable;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapPalette;
|
||||
import org.bukkit.map.MapView;
|
||||
import org.bukkit.packs.ResourcePack;
|
||||
@@ -233,6 +234,7 @@ import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.scheduler.BukkitWorker;
|
||||
import org.bukkit.scoreboard.Criteria;
|
||||
import org.bukkit.structure.StructureManager;
|
||||
import org.bukkit.util.StructureSearchResult;
|
||||
import org.bukkit.util.permissions.DefaultPermissions;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -1847,7 +1849,6 @@ public final class CraftServer implements Server {
|
||||
return CraftItemStack.asBukkitCopy(stack);
|
||||
}
|
||||
|
||||
// Paper start - copied from above (uses un-deprecated StructureType type)
|
||||
@Override
|
||||
public ItemStack createExplorerMap(World world, Location location, org.bukkit.generator.structure.StructureType structureType, org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) {
|
||||
Preconditions.checkArgument(world != null, "World cannot be null");
|
||||
@@ -1855,23 +1856,43 @@ public final class CraftServer implements Server {
|
||||
Preconditions.checkArgument(structureType != null, "StructureType cannot be null");
|
||||
Preconditions.checkArgument(mapIcon != null, "mapIcon cannot be null");
|
||||
|
||||
ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
final org.bukkit.util.StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structureType, radius, findUnexplored);
|
||||
final ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
final StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structureType, radius, findUnexplored);
|
||||
if (structureSearchResult == null) {
|
||||
return null;
|
||||
}
|
||||
Location structureLocation = structureSearchResult.getLocation();
|
||||
BlockPos structurePos = new BlockPos(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ());
|
||||
|
||||
return this.createExplorerMap(level, structureSearchResult, mapIcon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createExplorerMap(World world, Location location, org.bukkit.generator.structure.Structure structure, org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) {
|
||||
Preconditions.checkArgument(world != null, "World cannot be null");
|
||||
Preconditions.checkArgument(location != null, "Location cannot be null");
|
||||
Preconditions.checkArgument(structure != null, "Structure cannot be null");
|
||||
Preconditions.checkArgument(mapIcon != null, "mapIcon cannot be null");
|
||||
|
||||
final ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
final StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structure, radius, findUnexplored);
|
||||
if (structureSearchResult == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.createExplorerMap(level, structureSearchResult, mapIcon);
|
||||
}
|
||||
|
||||
private ItemStack createExplorerMap(final ServerLevel level, final StructureSearchResult structureSearchResult, final MapCursor.Type mapIcon) {
|
||||
final Location structureLocation = structureSearchResult.getLocation();
|
||||
final BlockPos structurePos = CraftLocation.toBlockPos(structureLocation);
|
||||
|
||||
// Create map with trackingPosition = true, unlimitedTracking = true
|
||||
net.minecraft.world.item.ItemStack stack = MapItem.create(level, structurePos.getX(), structurePos.getZ(), MapView.Scale.NORMAL.getValue(), true, true);
|
||||
final net.minecraft.world.item.ItemStack stack = MapItem.create(level, structurePos.getX(), structurePos.getZ(), MapView.Scale.NORMAL.getValue(), true, true);
|
||||
MapItem.renderBiomePreviewMap(level, stack);
|
||||
// "+" map ID taken from VillagerTrades$TreasureMapForEmeralds
|
||||
MapItemSavedData.addTargetDecoration(stack, structurePos, "+", CraftMapCursor.CraftType.bukkitToMinecraftHolder(mapIcon));
|
||||
|
||||
return CraftItemStack.asBukkitCopy(stack);
|
||||
}
|
||||
// Paper end
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -820,15 +820,26 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
|
||||
@Override
|
||||
public void swingMainHand() {
|
||||
Preconditions.checkState(!this.getHandle().generation, "Cannot swing hand during world generation");
|
||||
|
||||
this.getHandle().swing(InteractionHand.MAIN_HAND, SwingAnimation.DEFAULT, true);
|
||||
this.getHandle().swing(InteractionHand.MAIN_HAND, this.getSwingAnimation(InteractionHand.MAIN_HAND), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swingOffHand() {
|
||||
Preconditions.checkState(!this.getHandle().generation, "Cannot swing hand during world generation");
|
||||
this.getHandle().swing(InteractionHand.OFF_HAND, this.getSwingAnimation(InteractionHand.OFF_HAND), true);
|
||||
}
|
||||
|
||||
this.getHandle().swing(InteractionHand.OFF_HAND, SwingAnimation.DEFAULT, true);
|
||||
@Override
|
||||
public void swingHand(org.bukkit.inventory.EquipmentSlot hand, io.papermc.paper.datacomponent.item.SwingAnimation swingAnimation) {
|
||||
Preconditions.checkState(!this.getHandle().generation, "Cannot swing hand during world generation");
|
||||
this.getHandle().swing(CraftEquipmentSlot.getHand(hand), ((io.papermc.paper.datacomponent.item.PaperSwingAnimation) swingAnimation).getHandle(), true);
|
||||
}
|
||||
|
||||
private net.minecraft.world.item.component.SwingAnimation getSwingAnimation(InteractionHand hand) {
|
||||
final net.minecraft.world.item.component.SwingAnimation animation = this.getHandle()
|
||||
.getItemInHand(hand)
|
||||
.get(net.minecraft.core.component.DataComponents.ATTACK_ANIMATION);
|
||||
return animation != null ? animation : SwingAnimation.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class CraftMetaArmorStand extends CraftMetaItem implements com.destroysto
|
||||
java.io.ByteArrayInputStream buf = new java.io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(entityTag));
|
||||
try {
|
||||
this.entityTag = net.minecraft.nbt.NbtIo.readCompressed(buf, net.minecraft.nbt.NbtAccounter.unlimitedHeap());
|
||||
} catch (java.io.IOException ex) {
|
||||
} catch (java.io.IOException | net.minecraft.nbt.ReportedNbtException | net.minecraft.nbt.NbtException ex) {
|
||||
java.util.logging.Logger.getLogger(CraftMetaItem.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -57,8 +57,10 @@ import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtAccounter;
|
||||
import net.minecraft.nbt.NbtException;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.ReportedNbtException;
|
||||
import net.minecraft.nbt.SnbtPrinterTagVisitor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
@@ -754,7 +756,7 @@ public class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDat
|
||||
try {
|
||||
CompoundTag internalTag = NbtIo.readCompressed(buf, NbtAccounter.unlimitedHeap());
|
||||
this.deserializeInternal(internalTag, map);
|
||||
} catch (IOException ex) {
|
||||
} catch (IOException | ReportedNbtException | NbtException ex) {
|
||||
LOGGER.error("Failed to read internal tag from object", ex);
|
||||
}
|
||||
}
|
||||
@@ -783,7 +785,7 @@ public class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDat
|
||||
this.removedTags.add(key);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
} catch (IOException | ReportedNbtException | NbtException ex) {
|
||||
LOGGER.error("Failed to read unhandled tag for item", ex);
|
||||
}
|
||||
}
|
||||
@@ -813,7 +815,7 @@ public class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDat
|
||||
ByteArrayInputStream buf = new ByteArrayInputStream(Base64.getDecoder().decode(custom));
|
||||
try {
|
||||
this.customTag = NbtIo.readCompressed(buf, NbtAccounter.unlimitedHeap());
|
||||
} catch (IOException ex) {
|
||||
} catch (IOException | ReportedNbtException | NbtException ex) {
|
||||
LOGGER.error("Failed to read custom tag for item", ex);
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -154,6 +154,8 @@ public class CraftPersistentDataContainer extends io.papermc.paper.persistence.P
|
||||
try (final java.io.DataInputStream dataInput = new java.io.DataInputStream(new java.io.ByteArrayInputStream(bytes))) {
|
||||
final net.minecraft.nbt.CompoundTag compound = net.minecraft.nbt.NbtIo.read(dataInput);
|
||||
this.putAll(compound);
|
||||
} catch (final net.minecraft.nbt.ReportedNbtException | net.minecraft.nbt.NbtException ex) {
|
||||
throw new java.io.IOException("Invalid NBT data", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package org.bukkit.craftbukkit.util;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import io.papermc.paper.log.LoggerShutdown;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
public class ServerShutdownThread extends Thread {
|
||||
public class ServerShutdownThread extends TickThread {
|
||||
private final MinecraftServer server;
|
||||
|
||||
public ServerShutdownThread(MinecraftServer server) {
|
||||
super("Server Shutdown Thread");
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@@ -15,6 +17,14 @@ public class ServerShutdownThread extends Thread {
|
||||
try {
|
||||
// Paper start - try to shutdown on main
|
||||
server.halt(false, false);
|
||||
|
||||
// If the server hasn't finished starting up yet, give it as much time as it needs to start up and close on its own. This is
|
||||
// assuming that as long as the server thread is running, it isn't deadlocked and will eventually either successfully init or fail and stop running.
|
||||
while (!server.hasStopped() && !server.hasStarted && server.getRunningThread().isAlive()) {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
|
||||
// Give the server at least a second to realize that it needs to stop, if it takes longer that means it could be stuck.
|
||||
for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.papermc.testplugin.brigtests;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.BoolArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.command.brigadier.Commands;
|
||||
@@ -19,6 +21,8 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.defaults.BukkitCommand;
|
||||
@@ -45,7 +49,7 @@ public final class Registration {
|
||||
.then(
|
||||
Commands.argument("name", ArgumentTypes.resource(RegistryKey.ENCHANTMENT))
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("name", Enchantment.class).toString());
|
||||
ctx.getSource().sendSuccess(Component.translatable(ctx.getArgument("name", Enchantment.class)), false);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build()
|
||||
@@ -55,7 +59,7 @@ public final class Registration {
|
||||
Commands.argument("key", ArgumentTypes.resourceKey(RegistryKey.ENCHANTMENT))
|
||||
.executes(ctx -> {
|
||||
final TypedKey<Enchantment> key = RegistryArgumentExtractor.getTypedKey(ctx, RegistryKey.ENCHANTMENT, "key");
|
||||
ctx.getSource().getSender().sendPlainMessage(key.toString());
|
||||
ctx.getSource().sendSuccess(Component.text(key.toString()), false);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build()
|
||||
@@ -65,7 +69,7 @@ public final class Registration {
|
||||
Commands.argument("pos", ArgumentTypes.finePosition(false))
|
||||
.executes(ctx -> {
|
||||
final FinePositionResolver position = ctx.getArgument("pos", FinePositionResolver.class);
|
||||
ctx.getSource().getSender().sendPlainMessage("Position: " + position.resolve(ctx.getSource()));
|
||||
ctx.getSource().sendRichSuccess("Position: <red>" + position.resolve(ctx.getSource()));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build()
|
||||
@@ -73,7 +77,7 @@ public final class Registration {
|
||||
// ensure plugin commands override
|
||||
commands.register(Commands.literal("tag")
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getSender().sendPlainMessage("overriden command");
|
||||
ctx.getSource().sendRichSuccess("overriden command");
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
.build(),
|
||||
@@ -88,7 +92,7 @@ public final class Registration {
|
||||
.then(Commands.literal("sub_command")
|
||||
.requires(source -> source.getSender().hasPermission("testplugin.test"))
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getSender().sendPlainMessage("root_command sub_command");
|
||||
ctx.getSource().sendRichSuccess("<red>root_command <gold>sub_command");
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})).build(),
|
||||
null,
|
||||
@@ -126,7 +130,7 @@ public final class Registration {
|
||||
return true;
|
||||
})
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getExecutor().sendMessage("You are in lava!");
|
||||
ctx.getSource().sendRichReply("You are in lava!");
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})).build(),
|
||||
null,
|
||||
@@ -165,14 +169,14 @@ public final class Registration {
|
||||
.then(Commands.literal("item")
|
||||
.then(Commands.argument("mat", MaterialArgumentType.item())
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("mat", Material.class).name());
|
||||
ctx.getSource().sendSuccess(Component.translatable(ctx.getArgument("mat", Material.class)), false);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
)
|
||||
).then(Commands.literal("block")
|
||||
.then(Commands.argument("mat", MaterialArgumentType.block())
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("mat", Material.class).name());
|
||||
ctx.getSource().sendSuccess(Component.translatable(ctx.getArgument("mat", Material.class)), false);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
)
|
||||
@@ -181,6 +185,21 @@ public final class Registration {
|
||||
null,
|
||||
Collections.emptyList()
|
||||
);
|
||||
|
||||
commands.register(Commands.literal("send_success")
|
||||
.then(Commands.argument("allow_inform_admins", BoolArgumentType.bool())
|
||||
.then(Commands.argument("msg", StringArgumentType.greedyString())
|
||||
.executes(ctx -> {
|
||||
ctx.getSource().sendSuccess(
|
||||
MiniMessage.miniMessage().deserialize(StringArgumentType.getString(ctx, "msg")),
|
||||
BoolArgumentType.getBool(ctx, "allow_inform_admins")
|
||||
);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})))
|
||||
.build(),
|
||||
null,
|
||||
Collections.emptyList()
|
||||
);
|
||||
});
|
||||
|
||||
lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
|
||||
@@ -188,8 +207,8 @@ public final class Registration {
|
||||
commands.register(Commands.literal("heya")
|
||||
.then(Commands.argument("range", ArgumentTypes.doubleRange())
|
||||
.executes((ct) -> {
|
||||
ct.getSource().getSender().sendPlainMessage(ct.getArgument("range", DoubleRangeProvider.class).range().toString());
|
||||
return 1;
|
||||
ct.getSource().sendSuccess(Component.text(ct.getArgument("range", DoubleRangeProvider.class).range().toString()), false);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build(),
|
||||
null,
|
||||
|
||||
Reference in new issue
Block a user