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
@@ -1,4 +1,4 @@
|
||||
name: Release 26.4
|
||||
name: Release 26.3
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
cancel-in-progress: false
|
||||
group: release-26.4
|
||||
group: release-26.3
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -1,4 +1,4 @@
|
||||
Paper [](https://papermc.io/downloads/paper)
|
||||
[](https://github.com/PaperMC/Paper/actions)
|
||||
[](https://discord.gg/papermc)
|
||||
@@ -39,7 +39,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:26.4.build.+")
|
||||
compileOnly("io.papermc.paper:paper-api:26.3.build.+")
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -59,7 +59,7 @@ java {
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>[26.4.build,)</version>
|
||||
<version>[26.3.build,)</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
+2
-2
@@ -630,15 +630,14 @@ public net.minecraft.world.level.dimension.end.EnderDragonFight spawnNewGateway(
|
||||
public net.minecraft.world.level.entity.PersistentEntitySectionManager ensureChunkQueuedForLoad(J)V
|
||||
public net.minecraft.world.level.entity.PersistentEntitySectionManager permanentStorage
|
||||
public net.minecraft.world.level.gamerules.GameRules rules
|
||||
public net.minecraft.world.level.levelgen.material.MaterialRuleContext context
|
||||
public net.minecraft.world.level.levelgen.structure.StructurePiece SHAPE_CHECK_BLOCKS
|
||||
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement exclusionZone
|
||||
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement frequency
|
||||
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement frequencyReductionMethod
|
||||
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement locateOffset
|
||||
public net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement salt
|
||||
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate <init>()V
|
||||
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate entityInfoList
|
||||
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate fillFromWorld(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Vec3i;ZLjava/util/List;)V
|
||||
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate palettes
|
||||
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager resourceManagerSource
|
||||
public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager structureRepository
|
||||
@@ -655,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
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
group=io.papermc.paper
|
||||
mcVersion=26.4-snapshot-5
|
||||
mcVersion=26.3
|
||||
# This is the current API version for use in (paper-)plugin.yml files
|
||||
# During snapshot cycles this should be the anticipated version of the release target
|
||||
apiVersion=26.4
|
||||
channel=ALPHA
|
||||
apiVersion=26.3
|
||||
channel=STABLE
|
||||
|
||||
# Set to true while updating Minecraft version
|
||||
updatingMinecraft=true
|
||||
updatingMinecraft=false
|
||||
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.caching=true
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.destroystokyo.paper.block;
|
||||
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents the sounds that a {@link Block} makes in certain situations
|
||||
* <p>
|
||||
* The sound group includes break, step, place, hit, and fall sounds.
|
||||
* @deprecated use {@link org.bukkit.SoundGroup}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
public interface BlockSoundGroup {
|
||||
/**
|
||||
* Gets the sound that plays when breaking this block
|
||||
*
|
||||
* @return The break sound
|
||||
* @deprecated use {@link org.bukkit.SoundGroup#getBreakSound()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
Sound getBreakSound();
|
||||
|
||||
/**
|
||||
* Gets the sound that plays when stepping on this block
|
||||
*
|
||||
* @return The step sound
|
||||
* @deprecated use {@link org.bukkit.SoundGroup#getStepSound()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
Sound getStepSound();
|
||||
|
||||
/**
|
||||
* Gets the sound that plays when placing this block
|
||||
*
|
||||
* @return The place sound
|
||||
* @deprecated use {@link org.bukkit.SoundGroup#getPlaceSound()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
Sound getPlaceSound();
|
||||
|
||||
/**
|
||||
* Gets the sound that plays when hitting this block
|
||||
*
|
||||
* @return The hit sound
|
||||
* @deprecated use {@link org.bukkit.SoundGroup#getHitSound()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
Sound getHitSound();
|
||||
|
||||
/**
|
||||
* Gets the sound that plays when this block falls
|
||||
*
|
||||
* @return The fall sound
|
||||
* @deprecated use {@link org.bukkit.SoundGroup#getFallSound()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
Sound getFallSound();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Holds the properties for this item for when it is consumed.
|
||||
@@ -31,7 +30,7 @@ public interface Consumable extends BuildableDataComponent<Consumable, Consumabl
|
||||
ItemUseAnimation animation();
|
||||
|
||||
@Contract(pure = true)
|
||||
@Nullable Key sound();
|
||||
Key sound();
|
||||
|
||||
@Contract(pure = true)
|
||||
boolean hasConsumeParticles();
|
||||
@@ -66,11 +65,11 @@ public interface Consumable extends BuildableDataComponent<Consumable, Consumabl
|
||||
/**
|
||||
* Sets the sound played when consuming the item.
|
||||
*
|
||||
* @param sound the {@link Key} representing the sound to be used, or {@code null} for no sound
|
||||
* @param sound the {@link Key} representing the sound to be used
|
||||
* @return the builder for chaining
|
||||
*/
|
||||
@Contract(value = "_ -> this", mutates = "this")
|
||||
Builder sound(@Nullable Key sound);
|
||||
Builder sound(Key sound);
|
||||
|
||||
/**
|
||||
* Sets whether consuming the item results in particle effects.
|
||||
|
||||
@@ -39,10 +39,10 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
|
||||
/**
|
||||
* Gets the equip sound key.
|
||||
*
|
||||
* @return the equip sound key, or {@code null} if none
|
||||
* @return the equip sound key
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
@Nullable Key equipSound();
|
||||
Key equipSound();
|
||||
|
||||
/**
|
||||
* Gets the asset id if present.
|
||||
@@ -112,10 +112,10 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
|
||||
/**
|
||||
* Returns the sound that is played when shearing this equipment off an entity.
|
||||
*
|
||||
* @return shear sound, or {@code null} if none
|
||||
* @return shear sound
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
@Nullable Key shearSound();
|
||||
Key shearSound();
|
||||
|
||||
/**
|
||||
* Builder for {@link Equippable}.
|
||||
@@ -126,11 +126,11 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
|
||||
/**
|
||||
* Sets the equip sound key for this item.
|
||||
*
|
||||
* @param sound the equip sound key, or {@code null} for no sound
|
||||
* @param sound the equip sound key
|
||||
* @return the builder for chaining
|
||||
*/
|
||||
@Contract(value = "_ -> this", mutates = "this")
|
||||
Builder equipSound(@Nullable Key sound);
|
||||
Builder equipSound(Key sound);
|
||||
|
||||
/**
|
||||
* Sets the asset id for this item.
|
||||
@@ -207,10 +207,10 @@ public interface Equippable extends BuildableDataComponent<Equippable, Equippabl
|
||||
/**
|
||||
* Sets the sound that is played when shearing this equipment off an entity.
|
||||
*
|
||||
* @param shearSound the shear sound key, or {@code null} for no sound
|
||||
* @param shearSound the shear sound key
|
||||
* @return the builder for chaining
|
||||
*/
|
||||
@Contract(value = "_ -> this", mutates = "this")
|
||||
Builder shearSound(@Nullable Key shearSound);
|
||||
Builder shearSound(Key shearSound);
|
||||
}
|
||||
}
|
||||
+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");
|
||||
|
||||
|
||||
/* ******************* *
|
||||
|
||||
+6
-6
@@ -182,16 +182,16 @@ public interface SulfurCubeArchetypeRegistryEntry {
|
||||
|
||||
// todo The sounds should take a RegistryHolder but need more thoughts on the create method
|
||||
/**
|
||||
* {@return the sound played once the sulfur cube is knocked, or {@code null} if none}
|
||||
* {@return the sound played once the sulfur cube is knocked}
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Nullable TypedKey<Sound> hitSound();
|
||||
TypedKey<Sound> hitSound();
|
||||
|
||||
/**
|
||||
* {@return the sound played once the sulfur cube is pushed, or {@code null} if none}
|
||||
* {@return the sound played once the sulfur cube is pushed}
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@Nullable TypedKey<Sound> pushSound();
|
||||
TypedKey<Sound> pushSound();
|
||||
|
||||
/**
|
||||
* {@return the smallest impulse required to play the push sound}
|
||||
@@ -214,8 +214,8 @@ public interface SulfurCubeArchetypeRegistryEntry {
|
||||
*/
|
||||
@Contract(value = "_, _, _, _ -> new", pure = true)
|
||||
@ApiStatus.Experimental
|
||||
static SoundSettings of(final @Nullable TypedKey<Sound> hitSound, final @Nullable TypedKey<Sound> pushSound, final float pushSoundImpulseThreshold, final float pushSoundCooldown) {
|
||||
record Impl(@Nullable TypedKey<Sound> hitSound, @Nullable TypedKey<Sound> pushSound, float pushSoundImpulseThreshold, float pushSoundCooldown) implements SoundSettings {
|
||||
static SoundSettings of(final TypedKey<Sound> hitSound, final TypedKey<Sound> pushSound, final float pushSoundImpulseThreshold, final float pushSoundCooldown) {
|
||||
record Impl(TypedKey<Sound> hitSound, TypedKey<Sound> pushSound, float pushSoundImpulseThreshold, float pushSoundCooldown) implements SoundSettings {
|
||||
}
|
||||
|
||||
return new Impl(hitSound, pushSound, pushSoundImpulseThreshold, pushSoundCooldown);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,112 +1,70 @@
|
||||
package org.bukkit;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a group of sounds for blocks that are played when various actions
|
||||
* happen (ie stepping, breaking, hitting, etc.).
|
||||
* happen (ie stepping, breaking, hitting, etc).
|
||||
*/
|
||||
@NullMarked
|
||||
public interface SoundGroup {
|
||||
|
||||
/**
|
||||
* Get the volume these sounds are played at.
|
||||
* <p>
|
||||
*
|
||||
* Note that this volume does not always represent the actual volume
|
||||
* received by the client.
|
||||
*
|
||||
* @return volume
|
||||
*/
|
||||
float getVolume();
|
||||
public float getVolume();
|
||||
|
||||
/**
|
||||
* Gets the pitch these sounds are played at.
|
||||
* <p>
|
||||
*
|
||||
* Note that this pitch does not always represent the actual pitch received
|
||||
* by the client.
|
||||
*
|
||||
* @return pitch
|
||||
*/
|
||||
float getPitch();
|
||||
public float getPitch();
|
||||
|
||||
/**
|
||||
* Gets the corresponding breaking sound for this group.
|
||||
*
|
||||
* @return the break sound, or {@link Sound#BLOCK_STONE_BREAK} if this group has none
|
||||
* @deprecated sounds may now be nullable, use {@link #breakSound()} instead.
|
||||
* @return the break sound
|
||||
*/
|
||||
@Deprecated(since = "26.4")
|
||||
Sound getBreakSound();
|
||||
@NotNull
|
||||
public Sound getBreakSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding step sound for this group.
|
||||
*
|
||||
* @return the step sound, or {@link Sound#BLOCK_STONE_STEP} if this group has none
|
||||
* @deprecated sounds may now be nullable, use {@link #stepSound()} instead.
|
||||
* @return the step sound
|
||||
*/
|
||||
@Deprecated(since = "26.4")
|
||||
Sound getStepSound();
|
||||
@NotNull
|
||||
public Sound getStepSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding place sound for this group.
|
||||
*
|
||||
* @return the place sound, or {@link Sound#BLOCK_STONE_PLACE} if this group has none
|
||||
* @deprecated sounds may now be nullable, use {@link #placeSound()} instead.
|
||||
* @return the place sound
|
||||
*/
|
||||
@Deprecated(since = "26.4")
|
||||
Sound getPlaceSound();
|
||||
@NotNull
|
||||
public Sound getPlaceSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding hit sound for this group.
|
||||
*
|
||||
* @return the hit sound, or {@link Sound#BLOCK_STONE_HIT} if this group has none
|
||||
* @deprecated sounds may now be nullable, use {@link #hitSound()} instead.
|
||||
* @return the hit sound
|
||||
*/
|
||||
@Deprecated(since = "26.4")
|
||||
Sound getHitSound();
|
||||
@NotNull
|
||||
public Sound getHitSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding fall sound for this group.
|
||||
*
|
||||
* @return the fall sound, or {@link Sound#BLOCK_STONE_FALL} if this group has none
|
||||
* @deprecated sounds may now be nullable, use {@link #fallSound()} instead.
|
||||
* @return the fall sound
|
||||
*/
|
||||
@Deprecated(since = "26.4")
|
||||
Sound getFallSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding breaking sound for this group.
|
||||
*
|
||||
* @return the break sound, or {@code null} if this group has none
|
||||
*/
|
||||
@Nullable Sound breakSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding step sound for this group.
|
||||
*
|
||||
* @return the step sound, or {@code null} if this group has none
|
||||
*/
|
||||
@Nullable Sound stepSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding place sound for this group.
|
||||
*
|
||||
* @return the place sound, or {@code null} if this group has none
|
||||
*/
|
||||
@Nullable Sound placeSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding hit sound for this group.
|
||||
*
|
||||
* @return the hit sound, or {@code null} if this group has none
|
||||
*/
|
||||
@Nullable Sound hitSound();
|
||||
|
||||
/**
|
||||
* Gets the corresponding fall sound for this group.
|
||||
*
|
||||
* @return the fall sound, or {@code null} if this group has none
|
||||
*/
|
||||
@Nullable Sound fallSound();
|
||||
@NotNull
|
||||
public Sound getFallSound();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -778,6 +778,19 @@ public interface Block extends Metadatable, Translatable, net.kyori.adventure.tr
|
||||
*/
|
||||
boolean canPlace(@NotNull BlockData data);
|
||||
|
||||
// Paper start
|
||||
/**
|
||||
* Gets the {@link com.destroystokyo.paper.block.BlockSoundGroup} for this block.
|
||||
* <p>
|
||||
* This object contains the block, step, place, hit, and fall sounds.
|
||||
*
|
||||
* @return the sound group for this block
|
||||
* @deprecated use {@link #getBlockSoundGroup()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true, since = "1.18.2")
|
||||
com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup();
|
||||
|
||||
/**
|
||||
* Gets the {@link org.bukkit.SoundGroup} for this block.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -35,9 +35,9 @@ public interface EquippableComponent extends ConfigurationSerializable {
|
||||
/**
|
||||
* Gets the sound to play when the item is equipped.
|
||||
*
|
||||
* @return the sound, or null if none
|
||||
* @return the sound
|
||||
*/
|
||||
@Nullable
|
||||
@NotNull // Paper
|
||||
Sound getEquipSound();
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -14,7 +14,7 @@ plugins {
|
||||
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
|
||||
|
||||
dependencies {
|
||||
mache("io.papermc:mache:26.4-snapshot-5+build.2")
|
||||
mache("io.papermc:mache:26.3+build.1")
|
||||
paperclip("io.papermc:paperclip:3.0.4")
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ paperweight {
|
||||
gitFilePatches = false
|
||||
|
||||
updatingMinecraft {
|
||||
oldPaperCommit = "8c0b8396d676f6abef3df8ab992ac726f6e01c9c"
|
||||
//oldPaperCommit = "e5fe71723e2ffde7cc9fafc085ac3bb73e63175e"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
File renamed without changes.
+2
-2
@@ -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) {
|
||||
+910
-76
File diff suppressed because it is too large.
Load diff
+7
-7
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+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;
|
||||
}
|
||||
|
||||
+7
-7
@@ -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)));
|
||||
}
|
||||
File renamed without changes.
+3
-3
@@ -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
|
||||
File renamed without changes.
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
|
||||
+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.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
-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
|
||||
+9
-9
@@ -1,25 +1,25 @@
|
||||
--- a/com/mojang/authlib/services/MinecraftServicesDiscoveryService.java
|
||||
+++ b/com/mojang/authlib/services/MinecraftServicesDiscoveryService.java
|
||||
@@ -40,7 +_,7 @@
|
||||
@@ -37,7 +_,7 @@
|
||||
private final boolean servicesKeySetEnabled;
|
||||
private final Supplier<DiscoveryResponse> discoverySupplier;
|
||||
private final String clientVersion;
|
||||
|
||||
- private MinecraftServicesDiscoveryService(
|
||||
+ protected MinecraftServicesDiscoveryService( // Paper - private->protected
|
||||
final Proxy proxy,
|
||||
final boolean servicesKeySetEnabled,
|
||||
final Supplier<DiscoveryResponse> discoverySupplier,
|
||||
@@ -75,7 +_,7 @@
|
||||
final Supplier<DiscoveryResponse> discoverySupplier
|
||||
@@ -68,7 +_,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
- private static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment, final String clientVersion) {
|
||||
+ protected static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment, final String clientVersion) { // Paper - private->protected
|
||||
final MinecraftClient client = MinecraftClient.unauthenticated(proxy, clientVersion);
|
||||
- private static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment) {
|
||||
+ protected static Supplier<DiscoveryResponse> createDiscoverySupplier(final Proxy proxy, final Environment environment) { // Paper - private->protected
|
||||
final MinecraftClient client = MinecraftClient.unauthenticated(proxy);
|
||||
return RetryableFetch.fetch(
|
||||
() -> HttpDiscoveryService.constantURL(environment.discoveryUrl()),
|
||||
@@ -95,7 +_,7 @@
|
||||
return clientVersion;
|
||||
@@ -84,7 +_,7 @@
|
||||
return new MinecraftServicesDiscoveryService(proxy, false, DiscoveryResponse::offline);
|
||||
}
|
||||
|
||||
- private static Environment determineEnvironment() {
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
--- a/com/mojang/authlib/services/MinecraftServicesProfileRepository.java
|
||||
+++ b/com/mojang/authlib/services/MinecraftServicesProfileRepository.java
|
||||
@@ -44,6 +_,8 @@
|
||||
@@ -42,6 +_,8 @@
|
||||
.filter(name -> !Strings.isNullOrEmpty(name))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
for (final List<String> request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) {
|
||||
final List<String> normalizedRequest = request.stream().map(MinecraftServicesProfileRepository::normalizeName).toList();
|
||||
|
||||
@@ -75,6 +_,13 @@
|
||||
@@ -73,6 +_,13 @@
|
||||
callback.onProfileLookupFailed(name, new ProfileNotFoundException("Server did not find the requested profile"));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
try {
|
||||
Thread.sleep(DELAY_BETWEEN_PAGES);
|
||||
} catch (final InterruptedException ignored) {
|
||||
@@ -104,7 +_,15 @@
|
||||
@@ -102,7 +_,15 @@
|
||||
try {
|
||||
return Optional.ofNullable(client.get(HttpDiscoveryService.constantURL(discoveryService.getUrl(Service.PROFILES, "getByName").replace("{name}", normalizeName(name))), NameAndId.class));
|
||||
} catch (final MinecraftClientException e) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
this.nodes.remove(node.holder().id());
|
||||
}
|
||||
|
||||
@@ -47,7 +_,7 @@
|
||||
@@ -46,7 +_,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,8 @@
|
||||
|
||||
@Override
|
||||
public Collection<String> getOnlinePlayerNames() {
|
||||
- return List.of(this.server.getPlayerNames());
|
||||
+ return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().name()).toList() : List.of(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players
|
||||
- return Lists.newArrayList(this.server.getPlayerNames());
|
||||
+ return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().name()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/commands/Commands.java
|
||||
+++ b/net/minecraft/commands/Commands.java
|
||||
@@ -160,6 +_,7 @@
|
||||
@@ -159,6 +_,7 @@
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class Commands {
|
||||
@@ -8,7 +8,7 @@
|
||||
public static final String COMMAND_PREFIX = "/";
|
||||
private static final ThreadLocal<@Nullable ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal<>();
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -184,6 +_,7 @@
|
||||
@@ -183,6 +_,7 @@
|
||||
|
||||
@Override
|
||||
public boolean isRestricted(final CommandNode<CommandSourceStack> node) {
|
||||
@@ -16,7 +16,7 @@
|
||||
Predicate<CommandSourceStack> requirement = node.getRequirement();
|
||||
return !requirement.test(this.noPermissionSource);
|
||||
}
|
||||
@@ -191,6 +_,11 @@
|
||||
@@ -190,6 +_,11 @@
|
||||
private final CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();
|
||||
|
||||
public Commands(final Commands.CommandSelection commandSelection, final CommandBuildContext context) {
|
||||
@@ -28,7 +28,7 @@
|
||||
AdvancementCommands.register(this.dispatcher);
|
||||
AttributeCommand.register(this.dispatcher, context);
|
||||
ExecuteCommand.register(this.dispatcher, context);
|
||||
@@ -305,6 +_,42 @@
|
||||
@@ -304,6 +_,42 @@
|
||||
UnpublishCommand.register(this.dispatcher);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
|
||||
}
|
||||
|
||||
@@ -324,6 +_,13 @@
|
||||
@@ -323,6 +_,13 @@
|
||||
}
|
||||
|
||||
public void performCommand(final ParseResults<CommandSourceStack> command, final String commandString) {
|
||||
@@ -85,7 +85,7 @@
|
||||
CommandSourceStack sender = command.getContext().getSource();
|
||||
Profiler.get().push(() -> "/" + commandString);
|
||||
ContextChain<CommandSourceStack> commandChain = finishParsing(command, commandString, sender);
|
||||
@@ -337,10 +_,12 @@
|
||||
@@ -336,10 +_,12 @@
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -101,7 +101,7 @@
|
||||
StackTraceElement[] stackTrace = e.getStackTrace();
|
||||
|
||||
for (int i = 0; i < Math.min(stackTrace.length, 3); i++) {
|
||||
@@ -371,12 +_,16 @@
|
||||
@@ -370,12 +_,16 @@
|
||||
return ContextChain.tryFlatten(command.getContext().build(commandString))
|
||||
.orElseThrow(() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(command.getReader()));
|
||||
} catch (CommandSyntaxException e) {
|
||||
@@ -120,7 +120,7 @@
|
||||
if (cursor > 10) {
|
||||
context.append(CommonComponents.ELLIPSIS);
|
||||
}
|
||||
@@ -388,7 +_,17 @@
|
||||
@@ -387,7 +_,17 @@
|
||||
}
|
||||
|
||||
context.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
|
||||
@@ -139,7 +139,7 @@
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -416,10 +_,65 @@
|
||||
@@ -415,10 +_,65 @@
|
||||
}
|
||||
|
||||
public void sendCommands(final ServerPlayer player) {
|
||||
@@ -207,7 +207,7 @@
|
||||
player.connection.send(new ClientboundCommandsPacket(root, COMMAND_NODE_INSPECTOR));
|
||||
}
|
||||
|
||||
@@ -427,8 +_,49 @@
|
||||
@@ -426,8 +_,49 @@
|
||||
final CommandNode<S> source, final CommandNode<S> target, final S commandFilter, final Map<CommandNode<S>, CommandNode<S>> converted
|
||||
) {
|
||||
for (CommandNode<S> child : source.getChildren()) {
|
||||
|
||||
+3
-3
@@ -4,8 +4,8 @@
|
||||
private final StringReader reader;
|
||||
private final boolean forTesting;
|
||||
private final boolean allowNbt;
|
||||
- private final Map<Property<?>, Comparable<?>> properties = new HashMap<>();
|
||||
+ private final Map<Property<?>, Comparable<?>> properties = new java.util.LinkedHashMap<>(); // CraftBukkit - stable
|
||||
private final Map<String, String> vagueProperties = new HashMap<>();
|
||||
- private final Map<Property<?>, Comparable<?>> properties = Maps.newHashMap();
|
||||
+ private final Map<Property<?>, Comparable<?>> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable
|
||||
private final Map<String, String> vagueProperties = Maps.newHashMap();
|
||||
private Identifier id = Identifier.withDefaultNamespace("");
|
||||
private @Nullable StateDefinition<Block, BlockState> definition;
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/core/BlockPos.java
|
||||
+++ b/net/minecraft/core/BlockPos.java
|
||||
@@ -139,67 +_,84 @@
|
||||
@@ -141,67 +_,84 @@
|
||||
|
||||
@Override
|
||||
public BlockPos above() {
|
||||
@@ -98,7 +98,7 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -698,9 +_,9 @@
|
||||
@@ -667,9 +_,9 @@
|
||||
}
|
||||
|
||||
public BlockPos.MutableBlockPos set(final int x, final int y, final int z) {
|
||||
@@ -111,7 +111,7 @@
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -758,19 +_,19 @@
|
||||
@@ -727,19 +_,19 @@
|
||||
|
||||
@Override
|
||||
public BlockPos.MutableBlockPos setX(final int x) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
--- a/net/minecraft/core/Holder.java
|
||||
+++ b/net/minecraft/core/Holder.java
|
||||
@@ -252,7 +_,7 @@
|
||||
}
|
||||
|
||||
void bindTags(final Collection<TagKey<T>> tags) {
|
||||
- this.tags = Set.copyOf(tags);
|
||||
+ this.tags = it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper - use reference set because TagKey are interned
|
||||
}
|
||||
|
||||
public void bindComponents(final DataComponentMap components) {
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/core/MappedRegistry.java
|
||||
+++ b/net/minecraft/core/MappedRegistry.java
|
||||
@@ -34,17 +_,25 @@
|
||||
@@ -33,17 +_,25 @@
|
||||
public class MappedRegistry<T> implements WritableRegistry<T> {
|
||||
private final ResourceKey<? extends Registry<T>> key;
|
||||
private final ObjectList<Holder.Reference<T>> byId = new ObjectArrayList<>(256);
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
@Override
|
||||
public Stream<HolderSet.Named<T>> listTags() {
|
||||
@@ -118,6 +_,7 @@
|
||||
@@ -117,6 +_,7 @@
|
||||
this.toId.put(value, newId);
|
||||
this.registrationInfos.put(key, registrationInfo);
|
||||
this.registryLifecycle = this.registryLifecycle.add(registrationInfo.lifecycle());
|
||||
@@ -39,7 +39,7 @@
|
||||
return holder;
|
||||
}
|
||||
|
||||
@@ -279,6 +_,7 @@
|
||||
@@ -278,6 +_,7 @@
|
||||
}
|
||||
|
||||
this.frozen = true;
|
||||
@@ -47,7 +47,7 @@
|
||||
this.byValue.forEach((value, holder) -> holder.bindValue((T)value));
|
||||
List<Identifier> unboundEntries = this.byKey
|
||||
.entrySet()
|
||||
@@ -518,4 +_,13 @@
|
||||
@@ -517,4 +_,13 @@
|
||||
|
||||
Stream<HolderSet.Named<T>> getTags();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/core/Vec3i.java
|
||||
+++ b/net/minecraft/core/Vec3i.java
|
||||
@@ -20,9 +_,9 @@
|
||||
@@ -22,9 +_,9 @@
|
||||
ByteBufCodecs.VAR_INT, Vec3i::getX, ByteBufCodecs.VAR_INT, Vec3i::getY, ByteBufCodecs.VAR_INT, Vec3i::getZ, Vec3i::new
|
||||
);
|
||||
public static final Vec3i ZERO = new Vec3i(0, 0, 0);
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
public static Codec<Vec3i> offsetCodec(final int maxOffsetPerAxis) {
|
||||
return CODEC.validate(
|
||||
@@ -39,12 +_,12 @@
|
||||
@@ -41,12 +_,12 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -28,7 +28,7 @@
|
||||
return (this.getY() + this.getZ() * 31) * 31 + this.getX();
|
||||
}
|
||||
|
||||
@@ -57,15 +_,15 @@
|
||||
@@ -59,15 +_,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
return this.z;
|
||||
}
|
||||
|
||||
@@ -249,4 +_,11 @@
|
||||
@@ -251,4 +_,11 @@
|
||||
public String toShortString() {
|
||||
return this.getX() + ", " + this.getY() + ", " + this.getZ();
|
||||
}
|
||||
|
||||
+2
-2
@@ -236,8 +236,8 @@
|
||||
hand,
|
||||
itemInHand,
|
||||
Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3),
|
||||
- SoundEvents.BUCKET_EMPTY_POWDER_SNOW.value()
|
||||
+ SoundEvents.BUCKET_EMPTY_POWDER_SNOW.value(), hitDirection // Paper - add hitDirection
|
||||
- SoundEvents.BUCKET_EMPTY_POWDER_SNOW
|
||||
+ SoundEvents.BUCKET_EMPTY_POWDER_SNOW, hitDirection // Paper - add hitDirection
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/core/component/BlockTransformer.java
|
||||
+++ b/net/minecraft/core/component/BlockTransformer.java
|
||||
@@ -64,6 +_,14 @@
|
||||
@@ -68,6 +_,14 @@
|
||||
BlockState updatedShape = transformData.updateFromNeighbors ? Block.updateFromNeighbourShapes(newBlockState, level, pos) : newBlockState;
|
||||
Player player = context.getPlayer();
|
||||
ItemStack itemInHand = context.getItemInHand();
|
||||
|
||||
+20
-23
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
|
||||
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
|
||||
@@ -83,13 +_,39 @@
|
||||
@@ -80,12 +_,38 @@
|
||||
Direction direction = source.state().getValue(DispenserBlock.FACING);
|
||||
BlockPos pos = source.pos().relative(direction);
|
||||
ServerLevel serverLevel = source.level();
|
||||
@@ -36,17 +36,17 @@
|
||||
);
|
||||
ArmorStand armorStand = EntityTypes.ARMOR_STAND.spawn(serverLevel, postSpawnConfig, pos, EntitySpawnReason.DISPENSER, false, false);
|
||||
if (armorStand != null) {
|
||||
ArmorStandItem.playSound(serverLevel, armorStand);
|
||||
- dispensed.shrink(1);
|
||||
+ if (shrink) dispensed.shrink(1); // Paper
|
||||
}
|
||||
|
||||
return dispensed;
|
||||
@@ -107,9 +_,34 @@
|
||||
@@ -103,8 +_,33 @@
|
||||
.getEntitiesOfClass(AbstractChestedHorse.class, new AABB(pos), entity -> entity.isAlive() && !entity.hasChest())) {
|
||||
if (abstractChestedHorse.isTamed()) {
|
||||
SlotAccess slot = abstractChestedHorse.getSlot(AbstractHorse.CHEST_SLOT_OFFSET);
|
||||
- if (slot != null && slot.set(dispensed)) {
|
||||
- dispensed.shrink(1);
|
||||
+ // CraftBukkit start
|
||||
+ if (slot != null/* && slot.set(dispensed)*/) {
|
||||
+ ItemStack singleCopy = dispensed.copyWithCount(1);
|
||||
@@ -72,14 +72,12 @@
|
||||
+ }
|
||||
+ slot.set(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
|
||||
+ // CraftBukkit end
|
||||
abstractChestedHorse.playChestEquipsSound();
|
||||
- dispensed.shrink(1);
|
||||
+
|
||||
+ if (shrink) dispensed.shrink(1); // Paper - actually handle here
|
||||
this.setSuccess(true);
|
||||
return dispensed;
|
||||
}
|
||||
@@ -150,8 +_,45 @@
|
||||
@@ -145,8 +_,45 @@
|
||||
DispensibleContainerItem bucket = (DispensibleContainerItem)dispensed.getItem();
|
||||
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
|
||||
Level level = source.level();
|
||||
@@ -90,7 +88,7 @@
|
||||
+ /* Taken from SolidBucketItem#emptyContents */
|
||||
+ boolean willEmptyContentsSolidBucketItem = bucket instanceof net.minecraft.world.item.SolidBucketItem && level.isInWorldBounds(target) && state.isAir();
|
||||
+ /* Taken from BucketItem#emptyContents */
|
||||
+ boolean willEmptyBucketItem = bucket instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.getContent() instanceof net.minecraft.world.level.material.FlowingFluid && (state.isAir() || state.canBeReplaced(bucketItem.getContent()) || (state.getBlock() instanceof net.minecraft.world.level.block.LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, target, state, bucketItem.getContent().defaultFluidState())));
|
||||
+ boolean willEmptyBucketItem = bucket instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.getContent() instanceof net.minecraft.world.level.material.FlowingFluid && (state.isAir() || state.canBeReplaced(bucketItem.getContent()) || (state.getBlock() instanceof net.minecraft.world.level.block.LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, target, state, bucketItem.getContent())));
|
||||
+ if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) {
|
||||
+ // Paper end - correctly check if the bucket place will succeed
|
||||
+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, source.pos());
|
||||
@@ -126,7 +124,7 @@
|
||||
return this.consumeWithRemainder(source, dispensed, new ItemStack(Items.BUCKET));
|
||||
} else {
|
||||
return this.defaultDispenseItemBehavior.dispense(source, dispensed);
|
||||
@@ -175,7 +_,7 @@
|
||||
@@ -170,13 +_,20 @@
|
||||
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
|
||||
BlockState blockState = level.getBlockState(target);
|
||||
if (blockState.getBlock() instanceof BucketPickup bucket) {
|
||||
@@ -135,8 +133,7 @@
|
||||
if (pickup.isEmpty()) {
|
||||
return super.execute(source, dispensed);
|
||||
}
|
||||
@@ -183,6 +_,13 @@
|
||||
bucket.getPickupSound().ifPresent(soundEvent -> level.playSound(null, target, soundEvent, SoundSource.BLOCKS));
|
||||
|
||||
level.gameEvent(null, GameEvent.FLUID_PICKUP, target);
|
||||
Item targetType = pickup.getItem();
|
||||
+ // Paper start - Call BlockDispenseEvent
|
||||
@@ -149,7 +146,7 @@
|
||||
return this.consumeWithRemainder(source, dispensed, new ItemStack(targetType));
|
||||
} else {
|
||||
return super.execute(source, dispensed);
|
||||
@@ -196,11 +_,54 @@
|
||||
@@ -190,11 +_,54 @@
|
||||
this.setSuccess(true);
|
||||
Level level = source.level();
|
||||
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
|
||||
@@ -204,7 +201,7 @@
|
||||
|
||||
return dispensed;
|
||||
}
|
||||
@@ -215,15 +_,41 @@
|
||||
@@ -209,15 +_,41 @@
|
||||
}
|
||||
|
||||
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
|
||||
@@ -253,7 +250,7 @@
|
||||
this.setSuccess(true);
|
||||
return dispensed;
|
||||
}
|
||||
@@ -236,6 +_,13 @@
|
||||
@@ -230,6 +_,13 @@
|
||||
Level level = source.level();
|
||||
Direction direction = source.state().getValue(DispenserBlock.FACING);
|
||||
BlockPos target = source.pos().relative(direction);
|
||||
@@ -267,7 +264,7 @@
|
||||
if (level.isEmptyBlock(target) && WitherSkullBlock.canSpawnMob(level, target, dispensed)) {
|
||||
level.setBlockAndUpdate(
|
||||
target, Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(direction))
|
||||
@@ -248,7 +_,7 @@
|
||||
@@ -242,7 +_,7 @@
|
||||
dispensed.shrink(1);
|
||||
this.setSuccess(true);
|
||||
} else {
|
||||
@@ -276,7 +273,7 @@
|
||||
}
|
||||
|
||||
return dispensed;
|
||||
@@ -261,6 +_,13 @@
|
||||
@@ -255,6 +_,13 @@
|
||||
Level level = source.level();
|
||||
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
|
||||
CarvedPumpkinBlock pumpkinBlock = (CarvedPumpkinBlock)Blocks.CARVED_PUMPKIN;
|
||||
@@ -290,7 +287,7 @@
|
||||
if (level.isEmptyBlock(target) && pumpkinBlock.canSpawnGolem(level, target)) {
|
||||
if (!level.isClientSide()) {
|
||||
level.setBlockAndUpdate(target, pumpkinBlock.defaultBlockState());
|
||||
@@ -270,7 +_,7 @@
|
||||
@@ -264,7 +_,7 @@
|
||||
dispensed.shrink(1);
|
||||
this.setSuccess(true);
|
||||
} else {
|
||||
@@ -299,7 +296,7 @@
|
||||
}
|
||||
|
||||
return dispensed;
|
||||
@@ -296,6 +_,12 @@
|
||||
@@ -287,6 +_,12 @@
|
||||
ServerLevel level = source.level();
|
||||
BlockPos target = source.pos().relative(source.state().getValue(DispenserBlock.FACING));
|
||||
BlockState state = level.getBlockState(target);
|
||||
@@ -309,10 +306,10 @@
|
||||
+ return result;
|
||||
+ }
|
||||
+ // Paper end - Call BlockDispenseEvent
|
||||
if (state.is(BlockTags.BEEHIVES, s -> s.getBlock() instanceof BeehiveBlock) && state.getValueOrElse(BeehiveBlock.HONEY_LEVEL, 0) >= 5) {
|
||||
if (state.is(BlockTags.BEEHIVES, s -> s.hasProperty(BeehiveBlock.HONEY_LEVEL) && s.getBlock() instanceof BeehiveBlock)
|
||||
&& state.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) {
|
||||
((BeehiveBlock)state.getBlock())
|
||||
.releaseBeesAndResetHoneyLevel(level, state, target, null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED);
|
||||
@@ -320,6 +_,13 @@
|
||||
@@ -312,6 +_,13 @@
|
||||
this.setSuccess(true);
|
||||
if (blockState.is(Blocks.RESPAWN_ANCHOR)) {
|
||||
if (blockState.getValue(RespawnAnchorBlock.CHARGE) != 4) {
|
||||
@@ -326,7 +323,7 @@
|
||||
RespawnAnchorBlock.charge(null, level, pos, blockState);
|
||||
dispensed.shrink(1);
|
||||
} else {
|
||||
@@ -344,6 +_,28 @@
|
||||
@@ -336,6 +_,28 @@
|
||||
return dispensed;
|
||||
}
|
||||
|
||||
@@ -355,7 +352,7 @@
|
||||
for (Armadillo armadillo : armadillos) {
|
||||
if (armadillo.brushOffScute(null, dispensed)) {
|
||||
dispensed.hurtAndBreak(16, level, null, item -> {});
|
||||
@@ -363,6 +_,13 @@
|
||||
@@ -355,6 +_,13 @@
|
||||
BlockState blockState = level.getBlockState(pos);
|
||||
Optional<BlockState> maybeWaxed = HoneycombItem.getWaxed(blockState);
|
||||
if (maybeWaxed.isPresent()) {
|
||||
@@ -369,7 +366,7 @@
|
||||
level.setBlockAndUpdate(pos, maybeWaxed.get());
|
||||
level.levelEvent(LevelEvent.PARTICLES_WAX_ON, pos, 0);
|
||||
level.playSound(null, pos, SoundEvents.HONEYCOMB_WAX_ON, SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||
@@ -393,6 +_,12 @@
|
||||
@@ -385,6 +_,12 @@
|
||||
return this.defaultDispenseItemBehavior.dispense(source, dispensed);
|
||||
}
|
||||
|
||||
|
||||
+19
-33
@@ -1,7 +1,7 @@
|
||||
--- a/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
|
||||
+++ b/net/minecraft/core/dispenser/FlintAndSteelDispenseItemBehavior.java
|
||||
@@ -24,8 +_,15 @@
|
||||
ServerLevel level = source.level();
|
||||
@@ -24,16 +_,27 @@
|
||||
this.setSuccess(true);
|
||||
Direction facing = source.state().getValue(DispenserBlock.FACING);
|
||||
BlockPos targetPos = source.pos().relative(facing);
|
||||
+ // Paper start - Call BlockDispenseEvent
|
||||
@@ -12,34 +12,20 @@
|
||||
+ }
|
||||
+ // Paper end - Call BlockDispenseEvent
|
||||
BlockState target = level.getBlockState(targetPos);
|
||||
- boolean ignited = tryIgnite(dispensed, level, targetPos, facing, target);
|
||||
+ boolean ignited = tryIgnite(dispensed, level, targetPos, facing, target, source.pos()); // CraftBukkit
|
||||
this.setSuccess(ignited);
|
||||
if (this.isSuccess()) {
|
||||
FlintAndSteelItem.playSound(level, targetPos);
|
||||
@@ -36,19 +_,23 @@
|
||||
}
|
||||
|
||||
private static boolean tryIgnite(
|
||||
- final ItemStack dispensed, final ServerLevel level, final BlockPos targetPos, final Direction facing, final BlockState target
|
||||
+ final ItemStack dispensed, final ServerLevel level, final BlockPos targetPos, final Direction facing, final BlockState target, final BlockPos sourcePos // CraftBukkit
|
||||
) {
|
||||
if (tryIgniteExplosiveEntities(level, targetPos)) {
|
||||
return true;
|
||||
} else if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) {
|
||||
+ // CraftBukkit start - Ignition by dispensing flint and steel
|
||||
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, targetPos, sourcePos).isCancelled()) {
|
||||
level.setBlockAndUpdate(targetPos, BaseFireBlock.getState(level, targetPos));
|
||||
level.gameEvent(null, GameEvent.BLOCK_PLACE, targetPos);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
return true;
|
||||
} else if (CampfireBlock.canLight(target) || CandleBlock.canLight(target) || CandleCakeBlock.canLight(target)) {
|
||||
level.setBlockAndUpdate(targetPos, target.setValue(BlockStateProperties.LIT, true));
|
||||
level.gameEvent(null, GameEvent.BLOCK_CHANGE, targetPos);
|
||||
return true;
|
||||
- } else if (target.getBlock() instanceof TntBlock && TntBlock.prime(level, targetPos, null, dispensed)) {
|
||||
+ } else if (target.getBlock() instanceof TntBlock && TntBlock.prime(level, targetPos, null, dispensed, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, sourcePos))) { // CraftBukkit - TNTPrimeEvent
|
||||
level.removeBlock(targetPos, false);
|
||||
return true;
|
||||
} else {
|
||||
if (!tryIgniteExplosiveEntities(level, targetPos)) {
|
||||
if (BaseFireBlock.canBePlacedAt(level, targetPos, facing)) {
|
||||
+ // CraftBukkit start - Ignition by dispensing flint and steel
|
||||
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, targetPos, source.pos()).isCancelled()) {
|
||||
level.setBlockAndUpdate(targetPos, BaseFireBlock.getState(level, targetPos));
|
||||
level.gameEvent(null, GameEvent.BLOCK_PLACE, targetPos);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
} else if (CampfireBlock.canLight(target) || CandleBlock.canLight(target) || CandleCakeBlock.canLight(target)) {
|
||||
level.setBlockAndUpdate(targetPos, target.setValue(BlockStateProperties.LIT, true));
|
||||
level.gameEvent(null, GameEvent.BLOCK_CHANGE, targetPos);
|
||||
} else if (target.getBlock() instanceof TntBlock) {
|
||||
- if (TntBlock.prime(level, targetPos, null, dispensed)) {
|
||||
+ if (TntBlock.prime(level, targetPos, null, dispensed, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, targetPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, source.pos()))) { // CraftBukkit - TNTPrimeEvent
|
||||
level.removeBlock(targetPos, false);
|
||||
} else {
|
||||
this.setSuccess(false);
|
||||
+49
-7
@@ -1,23 +1,65 @@
|
||||
--- a/net/minecraft/core/registries/BuiltInRegistries.java
|
||||
+++ b/net/minecraft/core/registries/BuiltInRegistries.java
|
||||
@@ -373,9 +_,19 @@
|
||||
public static final Registry<ContextKeySet> CONTEXT_KEY_SET = BUILDER.registerSimple(Registries.CONTEXT_KEY_SET, LootContextParamSets::bootstrap);
|
||||
public static final Registry<Consumer<GameTestHelper>> TEST_FUNCTION = BUILDER.registerSimple(Registries.TEST_FUNCTION, BuiltinTestFunctions::bootstrap);
|
||||
public static final Registry<? extends Registry<?>> REGISTRY = BUILDER.build();
|
||||
@@ -367,6 +_,11 @@
|
||||
public static final Registry<ContextKeySet> CONTEXT_KEY_SET = registerSimple(Registries.CONTEXT_KEY_SET, LootContextParamSets::bootstrap);
|
||||
public static final Registry<Consumer<GameTestHelper>> TEST_FUNCTION = registerSimple(Registries.TEST_FUNCTION, BuiltinTestFunctions::bootstrap);
|
||||
public static final Registry<? extends Registry<?>> REGISTRY = WRITABLE_REGISTRY;
|
||||
+ // Paper start - add built-in registry conversions
|
||||
+ public static final io.papermc.paper.registry.data.util.Conversions STATIC_ACCESS_CONVERSIONS = new io.papermc.paper.registry.data.util.Conversions(
|
||||
+ new net.minecraft.resources.RegistryOps.HolderLookupAdapter(net.minecraft.server.RegistryLayer.STATIC_ACCESS)
|
||||
+ );
|
||||
+ // Paper end - add built-in registry conversions
|
||||
|
||||
private static <T> Registry<T> registerSimple(final ResourceKey<? extends Registry<T>> name, final BuiltInRegistries.RegistryBootstrap<T> loader) {
|
||||
return internalRegister(name, new MappedRegistry<>(name, Lifecycle.stable(), false), loader);
|
||||
@@ -394,6 +_,7 @@
|
||||
final ResourceKey<? extends Registry<T>> name, final R registry, final BuiltInRegistries.RegistryBootstrap<T> loader
|
||||
) {
|
||||
Bootstrap.checkBootstrapCalled(() -> "registry " + name.identifier());
|
||||
+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry); // Paper - initialize API registry
|
||||
Identifier key = name.identifier();
|
||||
LOADERS.put(key, () -> loader.run(registry));
|
||||
WRITABLE_REGISTRY.register((ResourceKey)name, registry, RegistrationInfo.BUILT_IN);
|
||||
@@ -401,16 +_,34 @@
|
||||
}
|
||||
|
||||
public static void bootStrap() {
|
||||
- BUILDER.bootStrap();
|
||||
+ // Paper start
|
||||
+ bootStrap(() -> {});
|
||||
+ }
|
||||
+ public static void bootStrap(Runnable runnable) {
|
||||
+ BUILDER.bootStrap(runnable);
|
||||
+ // Paper end
|
||||
+ REGISTRY.freeze(); // Paper - freeze main registry early
|
||||
createContents();
|
||||
+ runnable.run(); // Paper
|
||||
freeze();
|
||||
validate(REGISTRY);
|
||||
}
|
||||
|
||||
public static <T> HolderGetter<T> acquireBootstrapRegistrationLookup(final Registry<T> registry) {
|
||||
private static void createContents() {
|
||||
+ // Paper start - class-load org.bukkit.Registry
|
||||
+ // we have to class-load Registry here to create all the CraftRegistry instances
|
||||
+ // that will be created when Registry is class-loaded before RegistryAccess#getRegistry
|
||||
+ // would try to create them in lockReferenceHolder
|
||||
+ try {
|
||||
+ Class.forName(org.bukkit.Registry.class.getName());
|
||||
+ } catch (final ClassNotFoundException ex) {
|
||||
+ throw new RuntimeException(ex);
|
||||
+ }
|
||||
+ // Paper end - class-load org.bukkit.Registry
|
||||
LOADERS.forEach((key, value) -> {
|
||||
if (value.get() == null) {
|
||||
LOGGER.error("Unable to bootstrap registry '{}'", key);
|
||||
}
|
||||
+ io.papermc.paper.registry.PaperRegistryAccess.instance().lockReferenceHolders(ResourceKey.createRegistryKey(key)); // Paper - lock reference holder creation
|
||||
});
|
||||
}
|
||||
|
||||
@@ -419,6 +_,7 @@
|
||||
|
||||
for (Registry<?> registry : REGISTRY) {
|
||||
bindBootstrappedTagsToEmpty(registry);
|
||||
+ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), STATIC_ACCESS_CONVERSIONS); // Paper
|
||||
registry.freeze();
|
||||
}
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
--- a/net/minecraft/core/registries/BuiltinRegistryBuilder.java
|
||||
+++ b/net/minecraft/core/registries/BuiltinRegistryBuilder.java
|
||||
@@ -53,6 +_,7 @@
|
||||
final ResourceKey<? extends Registry<T>> name, final R registry, final BuiltinRegistryBuilder.RegistryBootstrap<T> loader
|
||||
) {
|
||||
Bootstrap.checkBootstrapCalled(() -> "registry " + name.identifier());
|
||||
+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry); // Paper - initialize API registry
|
||||
Identifier key = name.identifier();
|
||||
this.loaders.put(key, () -> loader.run(registry));
|
||||
this.registry.register((ResourceKey)name, registry, RegistrationInfo.BUILT_IN);
|
||||
@@ -60,16 +_,34 @@
|
||||
}
|
||||
|
||||
public void bootStrap() {
|
||||
+ // Paper start
|
||||
+ this.bootStrap(() -> {});
|
||||
+ }
|
||||
+ public void bootStrap(Runnable runnable) {
|
||||
+ // Paper end
|
||||
+ this.registry.freeze(); // Paper - freeze main registry early
|
||||
this.createContents();
|
||||
+ runnable.run(); // Paper
|
||||
this.freeze();
|
||||
validate(this.registry);
|
||||
}
|
||||
|
||||
private void createContents() {
|
||||
+ // Paper start - class-load org.bukkit.Registry
|
||||
+ // we have to class-load Registry here to create all the CraftRegistry instances
|
||||
+ // that will be created when Registry is class-loaded before RegistryAccess#getRegistry
|
||||
+ // would try to create them in lockReferenceHolder
|
||||
+ try {
|
||||
+ Class.forName(org.bukkit.Registry.class.getName());
|
||||
+ } catch (final ClassNotFoundException ex) {
|
||||
+ throw new RuntimeException(ex);
|
||||
+ }
|
||||
+ // Paper end - class-load org.bukkit.Registry
|
||||
this.loaders.forEach((key, value) -> {
|
||||
if (value.get() == null) {
|
||||
LOGGER.error("Unable to bootstrap registry '{}'", key);
|
||||
}
|
||||
+ io.papermc.paper.registry.PaperRegistryAccess.instance().lockReferenceHolders(ResourceKey.createRegistryKey(key)); // Paper - lock reference holder creation
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,6 +_,7 @@
|
||||
|
||||
for (Registry<?> registry : this.registry) {
|
||||
bindBootstrappedTagsToEmpty(registry);
|
||||
+ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), BuiltInRegistries.STATIC_ACCESS_CONVERSIONS); // Paper
|
||||
registry.freeze();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/gametest/framework/GameTestInfo.java
|
||||
+++ b/net/minecraft/gametest/framework/GameTestInfo.java
|
||||
@@ -247,7 +_,7 @@
|
||||
@@ -246,7 +_,7 @@
|
||||
this.finish();
|
||||
AABB bounds = this.getStructureBounds();
|
||||
List<Entity> entities = this.getLevel().getEntitiesOfClass(Entity.class, bounds.inflate(1.0), mob -> !(mob instanceof Player));
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
--- a/net/minecraft/nbt/CompoundTag.java
|
||||
+++ b/net/minecraft/nbt/CompoundTag.java
|
||||
@@ -50,7 +_,7 @@
|
||||
@@ -51,7 +_,7 @@
|
||||
|
||||
private static CompoundTag loadCompound(final DataInput input, final NbtAccounter accounter) throws IOException {
|
||||
accounter.accountBytes(48L);
|
||||
- Map<String, Tag> values = new HashMap<>();
|
||||
- Map<String, Tag> values = Maps.newHashMap();
|
||||
+ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<String, Tag> values = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag
|
||||
|
||||
byte tagType;
|
||||
while ((tagType = input.readByte()) != 0) {
|
||||
@@ -164,7 +_,7 @@
|
||||
@@ -165,7 +_,7 @@
|
||||
}
|
||||
|
||||
public CompoundTag() {
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -393,9 +_,16 @@
|
||||
@@ -394,9 +_,16 @@
|
||||
|
||||
@Override
|
||||
public CompoundTag copy() {
|
||||
@@ -38,7 +38,7 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -516,22 +_,47 @@
|
||||
@@ -517,22 +_,47 @@
|
||||
this.merge((CompoundTag)codec.encoder().encodeStart(ops, value).getOrThrow());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/nbt/SnbtGrammar.java
|
||||
+++ b/net/minecraft/nbt/SnbtGrammar.java
|
||||
@@ -607,7 +_,7 @@
|
||||
@@ -619,7 +_,7 @@
|
||||
mapEntries, Term.repeatedWithTrailingSeparator(mapEntryRule, mapEntries, StringReaderTerms.character(',')), scope -> scope.getOrThrow(mapEntries)
|
||||
);
|
||||
Atom<T> mapLiteral = Atom.of("map_literal");
|
||||
@@ -9,7 +9,7 @@
|
||||
List<Entry<String, T>> entries = scope.getOrThrow(mapEntries);
|
||||
if (entries.isEmpty()) {
|
||||
return emptyMapValue;
|
||||
@@ -648,9 +_,11 @@
|
||||
@@ -660,9 +_,11 @@
|
||||
listLiteral,
|
||||
Term.sequence(
|
||||
StringReaderTerms.character('['),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
--- a/net/minecraft/network/Connection.java
|
||||
+++ b/net/minecraft/network/Connection.java
|
||||
@@ -67,6 +_,11 @@
|
||||
private final Queue<Consumer<Connection>> pendingActions = new ConcurrentLinkedQueue<>();
|
||||
private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
|
||||
public Channel channel;
|
||||
public SocketAddress address;
|
||||
+ // Spigot start
|
||||
@@ -112,15 +112,20 @@
|
||||
this.disconnect(Component.translatable("disconnect.timeout"));
|
||||
} else {
|
||||
Component reason = Component.translatable("disconnect.genericReason", "Internal Exception: " + cause);
|
||||
@@ -120,6 +_,7 @@
|
||||
@@ -120,9 +_,11 @@
|
||||
details = new DisconnectionDetails(reason);
|
||||
}
|
||||
|
||||
+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason
|
||||
if (isFirstFault) {
|
||||
LOGGER.debug("Failed to sent packet", cause);
|
||||
Packet<?> disconnectPacket = null;
|
||||
@@ -145,6 +_,7 @@
|
||||
- if (this.getSending() == PacketFlow.CLIENTBOUND) {
|
||||
+ boolean doesDisconnectExist = this.packetListener.protocol() != ConnectionProtocol.STATUS && this.packetListener.protocol() != ConnectionProtocol.HANDSHAKING; // Paper
|
||||
+ if (this.getSending() == PacketFlow.CLIENTBOUND && doesDisconnectExist) { // Paper
|
||||
Packet<?> packet = this.sendLoginDisconnect
|
||||
? new ClientboundLoginDisconnectPacket(reason)
|
||||
: new ClientboundDisconnectPacket(reason);
|
||||
@@ -139,6 +_,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +133,7 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,11 +_,60 @@
|
||||
@@ -148,11 +_,60 @@
|
||||
if (packetListener == null) {
|
||||
throw new IllegalStateException("Received a packet before the packet listener was initialized");
|
||||
}
|
||||
@@ -190,7 +195,7 @@
|
||||
} catch (RejectedExecutionException ignored) {
|
||||
this.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
|
||||
} catch (ClassCastException exception) {
|
||||
@@ -298,6 +_,47 @@
|
||||
@@ -295,6 +_,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +243,7 @@
|
||||
private void sendPacket(final Packet<?> packet, final @Nullable ChannelFutureListener listener, final boolean flush) {
|
||||
this.sentPackets++;
|
||||
if (this.channel.eventLoop().inEventLoop()) {
|
||||
@@ -345,10 +_,30 @@
|
||||
@@ -342,10 +_,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +274,7 @@
|
||||
}
|
||||
|
||||
if (!this.isConnected() && !this.disconnectionHandled) {
|
||||
@@ -356,7 +_,7 @@
|
||||
@@ -353,7 +_,7 @@
|
||||
}
|
||||
|
||||
if (this.channel != null) {
|
||||
@@ -278,7 +283,7 @@
|
||||
}
|
||||
|
||||
if (this.tickCount++ % 20 == 0) {
|
||||
@@ -392,6 +_,7 @@
|
||||
@@ -389,6 +_,7 @@
|
||||
}
|
||||
|
||||
public void disconnect(final DisconnectionDetails details) {
|
||||
@@ -286,7 +291,7 @@
|
||||
if (this.channel == null) {
|
||||
this.delayedDisconnect = details;
|
||||
}
|
||||
@@ -552,10 +_,19 @@
|
||||
@@ -541,6 +_,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,13 +305,16 @@
|
||||
+
|
||||
public void setupCompression(final int threshold, final boolean validateDecompressed) {
|
||||
if (threshold >= 0) {
|
||||
this.setupInboundCompression(threshold, validateDecompressed);
|
||||
this.setupOutboundCompression(threshold);
|
||||
if (this.channel.pipeline().get(HandlerNames.DECOMPRESS) instanceof CompressionDecoder compressionDecoder) {
|
||||
@@ -554,6 +_,7 @@
|
||||
} else {
|
||||
this.channel.pipeline().addAfter(HandlerNames.PREPENDER, HandlerNames.COMPRESS, new CompressionEncoder(threshold));
|
||||
}
|
||||
+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
|
||||
} else {
|
||||
if (this.channel.pipeline().get(HandlerNames.DECOMPRESS) instanceof CompressionDecoder) {
|
||||
this.channel.pipeline().remove(HandlerNames.DECOMPRESS);
|
||||
@@ -564,6 +_,7 @@
|
||||
@@ -562,6 +_,7 @@
|
||||
if (this.channel.pipeline().get(HandlerNames.COMPRESS) instanceof CompressionEncoder) {
|
||||
this.channel.pipeline().remove(HandlerNames.COMPRESS);
|
||||
}
|
||||
@@ -314,7 +322,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,6 +_,26 @@
|
||||
@@ -579,6 +_,26 @@
|
||||
);
|
||||
disconnectListener.onDisconnect(details);
|
||||
}
|
||||
@@ -332,7 +340,7 @@
|
||||
+ case WAITING_FOR_DUPE_DISCONNECT:
|
||||
+ case PROTOCOL_SWITCHING:
|
||||
+ case ACCEPTED:
|
||||
+ final com.mojang.authlib.GameProfile profile = loginListener.resolvedUserProfile; /* Should be non-null at this stage */
|
||||
+ final com.mojang.authlib.GameProfile profile = loginListener.authenticatedProfile; /* Should be non-null at this stage */
|
||||
+ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.id(), profile.name(),
|
||||
+ this.address, false).callEvent();
|
||||
+ }
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
return Thread.currentThread() == this.runningThread;
|
||||
}
|
||||
|
||||
public <T extends PacketListener & PacketProcessor.Handler> void scheduleIfPossible(final T listener, final Packet<? super T> packet) {
|
||||
public <T extends PacketListener> void scheduleIfPossible(final T listener, final Packet<T> packet) {
|
||||
if (this.closed) {
|
||||
- throw new RejectedExecutionException("Server already shutting down");
|
||||
- }
|
||||
@@ -47,8 +47,8 @@
|
||||
}
|
||||
|
||||
public void processQueuedPackets() {
|
||||
@@ -48,8 +_,28 @@
|
||||
void onPacketError(Packet<?> packet, Exception cause) throws ReportedException;
|
||||
@@ -44,8 +_,28 @@
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
+ // Paper start - detailed watchdog information
|
||||
@@ -69,14 +69,14 @@
|
||||
+ }
|
||||
+ // Paper end - detailed watchdog information
|
||||
+
|
||||
private record ListenerAndPacket<T extends PacketListener & PacketProcessor.Handler>(T listener, Packet<? super T> packet) {
|
||||
private record ListenerAndPacket<T extends PacketListener>(T listener, Packet<T> packet) {
|
||||
public void handle() {
|
||||
+ packetProcessing.push(this.listener); // Paper - detailed watchdog information
|
||||
+ try { // Paper - detailed watchdog information
|
||||
if (this.listener.shouldHandleMessage(this.packet)) {
|
||||
try {
|
||||
this.packet.handle(this.listener);
|
||||
@@ -63,6 +_,12 @@
|
||||
@@ -59,6 +_,12 @@
|
||||
} else {
|
||||
PacketProcessor.LOGGER.debug("Ignoring packet due to disconnection: {}", this.packet);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/network/chat/Component.java
|
||||
+++ b/net/minecraft/network/chat/Component.java
|
||||
@@ -26,7 +_,19 @@
|
||||
@@ -27,7 +_,19 @@
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/network/codec/ByteBufCodecs.java
|
||||
+++ b/net/minecraft/network/codec/ByteBufCodecs.java
|
||||
@@ -378,7 +_,13 @@
|
||||
@@ -377,7 +_,13 @@
|
||||
return new StreamCodec<ByteBuf, Tag>() {
|
||||
@Override
|
||||
public Tag decode(final ByteBuf input) {
|
||||
@@ -15,7 +15,7 @@
|
||||
if (result == null) {
|
||||
throw new DecoderException("Expected non-null compound tag");
|
||||
} else {
|
||||
@@ -425,7 +_,7 @@
|
||||
@@ -424,7 +_,7 @@
|
||||
|
||||
@Override
|
||||
public void encode(final B output, final V value) {
|
||||
@@ -24,7 +24,7 @@
|
||||
original.encode(output, payload);
|
||||
}
|
||||
};
|
||||
@@ -455,13 +_,55 @@
|
||||
@@ -454,13 +_,55 @@
|
||||
|
||||
@Override
|
||||
public void encode(final RegistryFriendlyByteBuf output, final T value) {
|
||||
@@ -81,7 +81,7 @@
|
||||
static <B extends ByteBuf, V> StreamCodec<B, Optional<V>> optional(final StreamCodec<? super B, V> original) {
|
||||
return new StreamCodec<B, Optional<V>>() {
|
||||
@Override
|
||||
@@ -682,7 +_,7 @@
|
||||
@@ -681,7 +_,7 @@
|
||||
}
|
||||
|
||||
static <V> StreamCodec.CodecOperation<RegistryFriendlyByteBuf, V, V> registryFriendlyLengthPrefixed(final int maxSize) {
|
||||
|
||||
+3
-7
@@ -1,14 +1,10 @@
|
||||
--- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
|
||||
+++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
|
||||
@@ -14,10 +_,8 @@
|
||||
public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ServerCommonPacketListener> {
|
||||
@@ -14,7 +_,7 @@
|
||||
private static final int MAX_PAYLOAD_SIZE = 32767;
|
||||
public static final StreamCodec<FriendlyByteBuf, ServerboundCustomPayloadPacket> STREAM_CODEC = CustomPacketPayload.<FriendlyByteBuf>codec(
|
||||
- id -> DiscardedPayload.codec(id, 32767), Util.make(new ArrayList<>(), types -> {
|
||||
- types.add(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC));
|
||||
- types.add(new CustomPacketPayload.TypeAndCodec<>(ModListPayload.TYPE, ModListPayload.STREAM_CODEC));
|
||||
- })
|
||||
+ id -> DiscardedPayload.codec(id, 32767),
|
||||
id -> DiscardedPayload.codec(id, 32767),
|
||||
- Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)), types -> {})
|
||||
+ java.util.Collections.emptyList() // CraftBukkit - treat all packets the same
|
||||
)
|
||||
.map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload);
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java
|
||||
+++ b/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java
|
||||
@@ -40,6 +_,17 @@
|
||||
@@ -42,6 +_,17 @@
|
||||
this.actions = EnumSet.of(action);
|
||||
this.entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry(player));
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(final Collection<ServerPlayer> players) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.of(
|
||||
@@ -54,6 +_,46 @@
|
||||
@@ -56,6 +_,46 @@
|
||||
);
|
||||
return new ClientboundPlayerInfoUpdatePacket(actions, players);
|
||||
}
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
private static StreamCodec<RegistryFriendlyByteBuf, ClientboundPlayerInfoUpdatePacket.Entry> entryCodec(
|
||||
final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions
|
||||
@@ -125,7 +_,15 @@
|
||||
@@ -127,7 +_,15 @@
|
||||
INITIALIZE_CHAT(
|
||||
1,
|
||||
(entry, input) -> entry.chatSession = input.readNullable(RemoteChatSession.Data.STREAM_CODEC),
|
||||
@@ -82,7 +82,7 @@
|
||||
),
|
||||
UPDATE_GAME_MODE(
|
||||
2, (entry, input) -> entry.gameMode = GameType.STREAM_CODEC.decode(input), (output, entry) -> GameType.STREAM_CODEC.encode(output, entry.gameMode)
|
||||
@@ -174,10 +_,15 @@
|
||||
@@ -177,10 +_,15 @@
|
||||
RemoteChatSession.@Nullable Data chatSession
|
||||
) {
|
||||
private Entry(final ServerPlayer player) {
|
||||
@@ -99,7 +99,7 @@
|
||||
player.connection.latency(),
|
||||
player.gameMode(),
|
||||
player.getTabListDisplayName(),
|
||||
@@ -186,6 +_,11 @@
|
||||
@@ -189,6 +_,11 @@
|
||||
Optionull.map(player.getChatSession(), RemoteChatSession::asData)
|
||||
);
|
||||
}
|
||||
|
||||
+9
-9
@@ -1,11 +1,11 @@
|
||||
--- a/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
|
||||
+++ b/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
|
||||
@@ -11,7 +_,7 @@
|
||||
public static final StreamCodec<FriendlyByteBuf, ClientIntentionPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT,
|
||||
ClientIntentionPacket::protocolVersion,
|
||||
- ByteBufCodecs.stringUtf8(1024),
|
||||
+ ByteBufCodecs.stringUtf8(Short.MAX_VALUE), // Spigot - increase max hostName length
|
||||
ClientIntentionPacket::hostName,
|
||||
ByteBufCodecs.UNSIGNED_SHORT,
|
||||
ClientIntentionPacket::port,
|
||||
@@ -16,7 +_,7 @@
|
||||
}
|
||||
|
||||
private ClientIntentionPacket(final FriendlyByteBuf input) {
|
||||
- this(input.readVarInt(), input.readUtf(255), input.readUnsignedShort(), ClientIntent.byId(input.readVarInt()));
|
||||
+ this(input.readVarInt(), input.readUtf(Short.MAX_VALUE), input.readUnsignedShort(), ClientIntent.byId(input.readVarInt())); // Spigot - increase max hostName length
|
||||
}
|
||||
|
||||
private void write(final FriendlyByteBuf output) {
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/resources/RegistryDataLoader.java
|
||||
+++ b/net/minecraft/resources/RegistryDataLoader.java
|
||||
@@ -281,11 +_,21 @@
|
||||
@@ -278,11 +_,21 @@
|
||||
final Map<ResourceKey<? extends Registry<?>>, HolderGetter<?>> result = new HashMap<>();
|
||||
contextRegistries.forEach(e -> result.put(e.key(), e));
|
||||
newRegistriesAndLoaders.forEach(e -> result.put(e.registryKey(), e.concurrentRegistrationGetter));
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
--- a/net/minecraft/server/Bootstrap.java
|
||||
+++ b/net/minecraft/server/Bootstrap.java
|
||||
@@ -47,6 +_,7 @@
|
||||
@@ -44,6 +_,7 @@
|
||||
if (!isBootstrapped) {
|
||||
isBootstrapped = true;
|
||||
Instant start = Instant.now();
|
||||
SystemReport.setVersionDetails(SharedConstants.getCurrentVersion().name(), SharedConstants.getCurrentVersion().id(), "MC_DEBUG_");
|
||||
+ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
|
||||
if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
|
||||
throw new IllegalStateException("Unable to load registries");
|
||||
}
|
||||
@@ -59,11 +_,81 @@
|
||||
@@ -56,11 +_,81 @@
|
||||
EntitySelectorOptions.bootStrap();
|
||||
DispenseItemBehavior.bootStrap();
|
||||
CauldronInteractions.bootStrap();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
OptionParser parser = new OptionParser();
|
||||
OptionSpec<Void> nogui = parser.accepts("nogui");
|
||||
OptionSpec<Void> initSettings = parser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
|
||||
@@ -92,43 +_,99 @@
|
||||
@@ -92,41 +_,97 @@
|
||||
parser.printHelpOn(System.err);
|
||||
return;
|
||||
}
|
||||
@@ -85,6 +85,7 @@
|
||||
}
|
||||
|
||||
- File universePath = new File(options.valueOf(universe));
|
||||
- Services services = Services.create(MinecraftServicesDiscoveryService.create(Proxy.NO_PROXY), universePath);
|
||||
+ // Paper start - Detect headless JRE
|
||||
+ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
|
||||
+ if (awtException != null) {
|
||||
@@ -109,10 +110,7 @@
|
||||
+ universePath = new File(bukkitConfiguration.getString("settings.world-container", "."));
|
||||
+ }
|
||||
+ // Paper end - fix SPIGOT-5824
|
||||
Services services = Services.create(
|
||||
- MinecraftServicesDiscoveryService.create(Proxy.NO_PROXY, SharedConstants.getCurrentVersion().id()), universePath
|
||||
+ com.destroystokyo.paper.profile.PaperServicesDiscoveryService.create(Proxy.NO_PROXY, SharedConstants.getCurrentVersion().id()), universePath, userCacheFile, options // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container
|
||||
);
|
||||
+ Services services = Services.create(com.destroystokyo.paper.profile.PaperServicesDiscoveryService.create(Proxy.NO_PROXY), universePath, userCacheFile, options); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container
|
||||
NotificationManager notificationManager = new NotificationManager();
|
||||
ManagementServer jsonRpcServer = JsonRpc.create(settings, notificationManager);
|
||||
- String levelName = Optional.ofNullable(options.valueOf(worldName)).orElse(settings.getProperties().levelName);
|
||||
@@ -120,7 +118,7 @@
|
||||
LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(universePath.toPath());
|
||||
LevelStorageSource.LevelStorageAccess access = levelStorageSource.validateAndCreateAccess(levelName);
|
||||
Dynamic<?> levelDataTag;
|
||||
@@ -156,13 +_,20 @@
|
||||
@@ -154,13 +_,20 @@
|
||||
} else {
|
||||
levelDataTag = null;
|
||||
}
|
||||
@@ -142,7 +140,7 @@
|
||||
|
||||
WorldStem worldStem;
|
||||
try {
|
||||
@@ -171,17 +_,31 @@
|
||||
@@ -169,17 +_,40 @@
|
||||
executor -> WorldLoader.load(
|
||||
worldLoadConfig,
|
||||
context -> {
|
||||
@@ -163,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()
|
||||
@@ -176,7 +182,7 @@
|
||||
}
|
||||
},
|
||||
WorldStem::new,
|
||||
@@ -197,6 +_,7 @@
|
||||
@@ -195,6 +_,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,7 +190,7 @@
|
||||
RegistryAccess.Frozen registryHolder = worldStem.registries().compositeAccess();
|
||||
WorldData data = worldStem.worldDataAndGenSettings().data();
|
||||
boolean recreateRegionFilesValue = options.has(recreateRegionFiles);
|
||||
@@ -205,9 +_,12 @@
|
||||
@@ -203,9 +_,12 @@
|
||||
}
|
||||
|
||||
access.saveDataTag(data);
|
||||
@@ -197,7 +203,7 @@
|
||||
thread,
|
||||
access,
|
||||
packRepository,
|
||||
@@ -220,17 +_,35 @@
|
||||
@@ -218,17 +_,35 @@
|
||||
notificationManager
|
||||
);
|
||||
notificationManager.setServer(server);
|
||||
@@ -235,7 +241,7 @@
|
||||
Thread shutdownThread = new Thread("Server Shutdown Thread") {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -239,12 +_,13 @@
|
||||
@@ -237,12 +_,13 @@
|
||||
};
|
||||
shutdownThread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||
@@ -250,7 +256,7 @@
|
||||
final DedicatedServerSettings settings,
|
||||
final WorldLoader.DataLoadContext context,
|
||||
final Registry<LevelStem> datapackDimensions,
|
||||
@@ -315,7 +_,11 @@
|
||||
@@ -313,7 +_,11 @@
|
||||
final RegistryAccess registryAccess,
|
||||
final boolean recreateRegionFiles
|
||||
) {
|
||||
@@ -263,7 +269,7 @@
|
||||
|
||||
try (WorldUpgrader upgrader = new WorldUpgrader(storageSource, fixerUpper, registryAccess, eraseCache, recreateRegionFiles)) {
|
||||
Component lastStatus = null;
|
||||
@@ -343,5 +_,6 @@
|
||||
@@ -341,5 +_,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -189,11 +_,13 @@
|
||||
@@ -188,11 +_,13 @@
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements CommandSource, ServerInfo, ChunkIOErrorReporter {
|
||||
@@ -15,16 +15,16 @@
|
||||
private static final int OVERLOADED_TICKS_THRESHOLD = 20;
|
||||
private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
|
||||
private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
|
||||
@@ -216,7 +_,7 @@
|
||||
@@ -215,7 +_,7 @@
|
||||
protected final PlayerDataStorage playerDataStorage;
|
||||
private final SavedDataStorage savedDataStorage;
|
||||
private final List<Runnable> tickables = new ArrayList<>();
|
||||
private final List<Runnable> tickables = Lists.newArrayList();
|
||||
- private final GameRules gameRules;
|
||||
+ // Paper - per-level GameRules
|
||||
private MetricsRecorder metricsRecorder = InactiveMetricsRecorder.INSTANCE;
|
||||
private Consumer<ProfileResults> onMetricsRecordingStopped = results -> this.stopRecordingMetrics();
|
||||
private Consumer<Path> onMetricsRecordingFinished = ignored -> {};
|
||||
@@ -224,7 +_,7 @@
|
||||
@@ -223,7 +_,7 @@
|
||||
private MinecraftServer.@Nullable TimeProfiler debugCommandProfiler;
|
||||
private boolean debugCommandProfilerDelayStart;
|
||||
private ServerConnectionListener connection;
|
||||
@@ -33,10 +33,11 @@
|
||||
private @Nullable ServerStatus status;
|
||||
private ServerStatus.@Nullable Favicon statusIcon;
|
||||
private final RandomSource random = RandomSource.create();
|
||||
@@ -235,13 +_,14 @@
|
||||
private Map<ResourceKey<Level>, ServerLevel> levels = new LinkedHashMap<>();
|
||||
@@ -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;
|
||||
@@ -49,7 +50,7 @@
|
||||
private int playerIdleTimeout;
|
||||
private final long[] tickTimesNanos = new long[100];
|
||||
private long aggregatedTickTimesNanos = 0L;
|
||||
@@ -269,7 +_,7 @@
|
||||
@@ -268,7 +_,7 @@
|
||||
private @Nullable CommandStorage commandStorage;
|
||||
private final CustomBossEvents customBossEvents;
|
||||
private final RandomSequences randomSequences;
|
||||
@@ -58,7 +59,7 @@
|
||||
private final ServerFunctionManager functionManager;
|
||||
private boolean enforceWhitelist;
|
||||
private boolean usingWhitelist;
|
||||
@@ -287,13 +_,14 @@
|
||||
@@ -286,13 +_,14 @@
|
||||
private final SuppressedExceptionCollector suppressedExceptions = new SuppressedExceptionCollector();
|
||||
private final DiscontinuousFrame tickFrame;
|
||||
private final PacketProcessor packetProcessor;
|
||||
@@ -75,7 +76,7 @@
|
||||
if (Runtime.getRuntime().availableProcessors() > 4) {
|
||||
thread.setPriority(8);
|
||||
}
|
||||
@@ -304,7 +_,124 @@
|
||||
@@ -303,7 +_,124 @@
|
||||
return server;
|
||||
}
|
||||
|
||||
@@ -200,7 +201,7 @@
|
||||
final Thread serverThread,
|
||||
final LevelStorageSource.LevelStorageAccess storageSource,
|
||||
final PackRepository packRepository,
|
||||
@@ -318,28 +_,28 @@
|
||||
@@ -317,28 +_,28 @@
|
||||
final NotificationManager notificationManager
|
||||
) {
|
||||
super("Server", propagatesCrashes);
|
||||
@@ -237,7 +238,7 @@
|
||||
this.fixerUpper = fixerUpper;
|
||||
this.functionManager = new ServerFunctionManager(this, this.resources.managers.getFunctionLibrary());
|
||||
HolderGetter<Block> blockLookup = this.registries.compositeAccess().lookupOrThrow(Registries.BLOCK).filterFeatures(this.worldData.enabledFeatures());
|
||||
@@ -354,7 +_,14 @@
|
||||
@@ -353,7 +_,14 @@
|
||||
this.clockManager = this.getDataStorage().computeIfAbsent(ServerClockManager.TYPE);
|
||||
this.clockManager.init(this);
|
||||
this.customBossEvents = this.savedDataStorage.computeIfAbsent(CustomBossEvents.TYPE);
|
||||
@@ -253,7 +254,7 @@
|
||||
}
|
||||
|
||||
protected abstract boolean initServer() throws IOException;
|
||||
@@ -387,15 +_,15 @@
|
||||
@@ -386,15 +_,15 @@
|
||||
};
|
||||
}
|
||||
|
||||
@@ -274,7 +275,7 @@
|
||||
if (profiledDuration != null) {
|
||||
profiledDuration.finish(true);
|
||||
}
|
||||
@@ -409,31 +_,146 @@
|
||||
@@ -408,33 +_,150 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,12 +324,14 @@
|
||||
+ final ResourceKey<Level> dimensionKey = loading.info().dimensionKey();
|
||||
+ final SavedDataStorage savedDataStorage = new SavedDataStorage(this.storageSource.getDimensionPath(dimensionKey).resolve(LevelResource.DATA.id()), this.getFixerUpper(), this.registryAccess());
|
||||
+ savedDataStorage.set(WorldGenSettings.TYPE, new WorldGenSettings(worldDataAndGenSettings.genSettings().options(), worldDataAndGenSettings.genSettings().dimensions()));
|
||||
long seed = worldOptions.seed();
|
||||
long biomeZoomSeed = BiomeManager.obfuscateSeed(seed);
|
||||
List<CustomSpawner> overworldCustomSpawners = ImmutableList.of(
|
||||
- new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(this.savedDataStorage)
|
||||
- );
|
||||
- LevelStem overworldData = dimensions.getValue(LevelStem.OVERWORLD);
|
||||
- ServerLevel overworld = new ServerLevel(
|
||||
- this, this.executor, this.storageSource, levelData, Level.OVERWORLD, overworldData, isDebug, overworldCustomSpawners, true
|
||||
- this, this.executor, this.storageSource, levelData, Level.OVERWORLD, overworldData, isDebug, biomeZoomSeed, overworldCustomSpawners, true
|
||||
- );
|
||||
- this.levels.put(Level.OVERWORLD, overworld);
|
||||
+ new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(savedDataStorage) // Paper - save to world data
|
||||
@@ -359,6 +362,7 @@
|
||||
+ dimensionKey,
|
||||
+ levelStem,
|
||||
+ worldDataAndGenSettings.data().isDebugWorld(),
|
||||
+ biomeZoomSeed,
|
||||
+ overworldCustomSpawners,
|
||||
+ true,
|
||||
+ loading.info().stemKey(),
|
||||
@@ -389,6 +393,7 @@
|
||||
+ dimensionKey,
|
||||
+ levelStem,
|
||||
+ this.worldData.isDebugWorld(),
|
||||
+ biomeZoomSeed,
|
||||
+ spawners,
|
||||
+ true,
|
||||
+ loading.info().stemKey(),
|
||||
@@ -435,7 +440,7 @@
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
CrashReport report = CrashReport.forThrowable(t, "Exception initializing level");
|
||||
@@ -450,25 +_,7 @@
|
||||
@@ -451,25 +_,7 @@
|
||||
}
|
||||
|
||||
GlobalPos focusPos = this.selectLevelLoadFocusPos();
|
||||
@@ -448,7 +453,7 @@
|
||||
- ResourceKey<Level> dimension = ResourceKey.create(Registries.DIMENSION, name.identifier());
|
||||
- DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, levelData);
|
||||
- level = new ServerLevel(
|
||||
- this, this.executor, this.storageSource, derivedLevelData, dimension, entry.getValue(), isDebug, ImmutableList.of(), false
|
||||
- this, this.executor, this.storageSource, derivedLevelData, dimension, entry.getValue(), isDebug, biomeZoomSeed, ImmutableList.of(), false
|
||||
- );
|
||||
- this.levels.put(dimension, level);
|
||||
- } else {
|
||||
@@ -462,7 +467,7 @@
|
||||
}
|
||||
|
||||
private static void setInitialSpawn(
|
||||
@@ -484,6 +_,28 @@
|
||||
@@ -485,6 +_,28 @@
|
||||
levelData.setSpawn(LevelData.RespawnData.of(level.dimension(), BlockPos.ZERO.above(80), 0.0F, 0.0F));
|
||||
} else {
|
||||
ServerChunkCache chunkSource = level.getChunkSource();
|
||||
@@ -491,7 +496,7 @@
|
||||
ChunkPos spawnChunk = chunkSource.getGenerator().getOrigin(chunkSource.randomState());
|
||||
levelLoadListener.start(LevelLoadListener.Stage.PREPARE_GLOBAL_SPAWN, 0);
|
||||
levelLoadListener.updateFocus(level.dimension(), spawnChunk);
|
||||
@@ -529,19 +_,22 @@
|
||||
@@ -530,19 +_,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +523,7 @@
|
||||
chunkLoadCounter.track(level, () -> {
|
||||
TicketStorage savedTickets = level.getDataStorage().get(TicketStorage.TYPE);
|
||||
if (savedTickets != null) {
|
||||
@@ -550,17 +_,19 @@
|
||||
@@ -551,17 +_,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
@@ -544,7 +549,7 @@
|
||||
}
|
||||
|
||||
protected GlobalPos selectLevelLoadFocusPos() {
|
||||
@@ -622,19 +_,49 @@
|
||||
@@ -623,19 +_,49 @@
|
||||
this.stopServer();
|
||||
}
|
||||
|
||||
@@ -595,7 +600,7 @@
|
||||
}
|
||||
|
||||
LOGGER.info("Saving worlds");
|
||||
@@ -677,6 +_,20 @@
|
||||
@@ -678,6 +_,20 @@
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), e);
|
||||
}
|
||||
@@ -616,7 +621,7 @@
|
||||
}
|
||||
|
||||
public String getLocalIp() {
|
||||
@@ -692,6 +_,14 @@
|
||||
@@ -693,6 +_,14 @@
|
||||
}
|
||||
|
||||
public void halt(final boolean wait) {
|
||||
@@ -631,7 +636,7 @@
|
||||
this.running = false;
|
||||
if (wait) {
|
||||
try {
|
||||
@@ -702,6 +_,122 @@
|
||||
@@ -703,6 +_,122 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,7 +759,7 @@
|
||||
protected void runServer() {
|
||||
try {
|
||||
if (!this.initServer()) {
|
||||
@@ -709,30 +_,73 @@
|
||||
@@ -710,30 +_,74 @@
|
||||
}
|
||||
|
||||
this.nextTickTimeNanos = Util.getNanos();
|
||||
@@ -774,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
|
||||
@@ -842,7 +848,7 @@
|
||||
|
||||
boolean sprinting = thisTickNanos == 0L;
|
||||
if (this.debugCommandProfilerDelayStart) {
|
||||
@@ -740,7 +_,7 @@
|
||||
@@ -741,7 +_,7 @@
|
||||
this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
|
||||
}
|
||||
|
||||
@@ -851,7 +857,7 @@
|
||||
|
||||
try (Profiler.Scope ignored = Profiler.use(this.createProfiler())) {
|
||||
this.processPacketsAndTick(sprinting);
|
||||
@@ -749,7 +_,7 @@
|
||||
@@ -750,7 +_,7 @@
|
||||
this.mayHaveDelayedTasks = true;
|
||||
this.delayedTasksMaxNextTickTimeNanos = Math.max(Util.getNanos() + thisTickNanos, this.nextTickTimeNanos);
|
||||
this.startMeasuringTaskExecutionTime();
|
||||
@@ -860,7 +866,7 @@
|
||||
this.finishMeasuringTaskExecutionTime();
|
||||
if (sprinting) {
|
||||
this.tickRateManager.endTickWork();
|
||||
@@ -783,7 +_,7 @@
|
||||
@@ -784,7 +_,7 @@
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Exception stopping the server", t);
|
||||
} finally {
|
||||
@@ -869,7 +875,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -835,7 +_,14 @@
|
||||
@@ -836,7 +_,14 @@
|
||||
}
|
||||
|
||||
private boolean haveTime() {
|
||||
@@ -885,7 +891,7 @@
|
||||
}
|
||||
|
||||
public NotificationManager notificationManager() {
|
||||
@@ -843,11 +_,11 @@
|
||||
@@ -844,11 +_,11 @@
|
||||
}
|
||||
|
||||
protected void waitUntilNextTick() {
|
||||
@@ -899,7 +905,7 @@
|
||||
} finally {
|
||||
this.waitingForNextTick = false;
|
||||
}
|
||||
@@ -865,18 +_,38 @@
|
||||
@@ -866,18 +_,38 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -942,7 +948,7 @@
|
||||
this.mayHaveDelayedTasks = mayHaveMoreTasks;
|
||||
return mayHaveMoreTasks;
|
||||
}
|
||||
@@ -886,15 +_,16 @@
|
||||
@@ -887,15 +_,16 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -961,7 +967,7 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -946,26 +_,44 @@
|
||||
@@ -947,26 +_,44 @@
|
||||
}
|
||||
|
||||
protected void tickServer(final BooleanSupplier haveTime) {
|
||||
@@ -1007,7 +1013,7 @@
|
||||
this.tickCount++;
|
||||
this.tickRateManager.tick();
|
||||
this.tickChildren(haveTime);
|
||||
@@ -975,11 +_,18 @@
|
||||
@@ -976,11 +_,18 @@
|
||||
}
|
||||
|
||||
this.ticksUntilAutosave--;
|
||||
@@ -1027,7 +1033,7 @@
|
||||
profiler.push("tallying");
|
||||
long tickTime = Util.getNanos() - nano;
|
||||
int tickIndex = this.tickCount % 100;
|
||||
@@ -995,16 +_,16 @@
|
||||
@@ -996,16 +_,16 @@
|
||||
ProfilerFiller profiler = Profiler.get();
|
||||
profiler.push("tick");
|
||||
this.tickFrame.start();
|
||||
@@ -1048,7 +1054,7 @@
|
||||
LOGGER.debug("Autosave started");
|
||||
ProfilerFiller profiler = Profiler.get();
|
||||
profiler.push("save");
|
||||
@@ -1046,7 +_,7 @@
|
||||
@@ -1047,7 +_,7 @@
|
||||
private ServerStatus buildServerStatus() {
|
||||
ServerStatus.Players players = this.buildPlayerStatus();
|
||||
return new ServerStatus(
|
||||
@@ -1180,14 +1186,14 @@
|
||||
+ // CraftBukkit start
|
||||
+ public void addLevel(ServerLevel level) {
|
||||
+ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
|
||||
+ Map<ResourceKey<Level>, ServerLevel> newLevels = new LinkedHashMap<>(oldLevels);
|
||||
+ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
|
||||
+ newLevels.put(level.dimension(), level);
|
||||
+ this.levels = Collections.unmodifiableMap(newLevels);
|
||||
+ }
|
||||
+
|
||||
+ public void removeLevel(ServerLevel level) {
|
||||
+ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
|
||||
+ Map<ResourceKey<Level>, ServerLevel> newLevels = new LinkedHashMap<>(oldLevels);
|
||||
+ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
|
||||
+ newLevels.remove(level.dimension());
|
||||
+ this.levels = Collections.unmodifiableMap(newLevels);
|
||||
+ }
|
||||
@@ -1205,7 +1211,7 @@
|
||||
}
|
||||
|
||||
public ServerClockManager clockManager() {
|
||||
@@ -1251,7 +_,12 @@
|
||||
@@ -1243,7 +_,12 @@
|
||||
|
||||
@Override
|
||||
public void sendSystemMessage(final Component message) {
|
||||
@@ -1219,7 +1225,7 @@
|
||||
}
|
||||
|
||||
public KeyPair getKeyPair() {
|
||||
@@ -1288,11 +_,17 @@
|
||||
@@ -1280,11 +_,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1242,7 +1248,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1308,6 +_,11 @@
|
||||
@@ -1300,6 +_,11 @@
|
||||
|
||||
public void setDifficultyLocked(final boolean locked) {
|
||||
this.worldData.setDifficultyLocked(locked);
|
||||
@@ -1254,7 +1260,7 @@
|
||||
this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
|
||||
}
|
||||
|
||||
@@ -1368,10 +_,20 @@
|
||||
@@ -1360,10 +_,20 @@
|
||||
|
||||
@Override
|
||||
public String getMotd() {
|
||||
@@ -1276,7 +1282,7 @@
|
||||
this.motd = motd;
|
||||
}
|
||||
|
||||
@@ -1401,16 +_,20 @@
|
||||
@@ -1393,16 +_,20 @@
|
||||
int count = 0;
|
||||
|
||||
for (ServerPlayer player : this.getPlayerList().getPlayers()) {
|
||||
@@ -1300,7 +1306,7 @@
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
@@ -1481,7 +_,7 @@
|
||||
@@ -1469,7 +_,7 @@
|
||||
@Override
|
||||
public void executeIfPossible(final Runnable command) {
|
||||
if (this.isStopped()) {
|
||||
@@ -1309,7 +1315,7 @@
|
||||
}
|
||||
|
||||
super.executeIfPossible(command);
|
||||
@@ -1520,7 +_,16 @@
|
||||
@@ -1504,7 +_,16 @@
|
||||
return this.functionManager;
|
||||
}
|
||||
|
||||
@@ -1326,7 +1332,7 @@
|
||||
CompletableFuture<Void> result = CompletableFuture.<ImmutableList>supplyAsync(
|
||||
() -> packsToEnable.stream()
|
||||
.map(this.packRepository::getPack)
|
||||
@@ -1532,7 +_,7 @@
|
||||
@@ -1516,7 +_,7 @@
|
||||
.thenCompose(
|
||||
packsToLoad -> {
|
||||
CloseableResourceManager resources = new MultiPackResourceManager(PackType.SERVER_DATA, packsToLoad);
|
||||
@@ -1335,7 +1341,7 @@
|
||||
return ReloadableServerResources.loadResources(
|
||||
resources,
|
||||
this.registries,
|
||||
@@ -1552,17 +_,35 @@
|
||||
@@ -1536,17 +_,35 @@
|
||||
}
|
||||
)
|
||||
.thenAcceptAsync(newResources -> {
|
||||
@@ -1373,7 +1379,7 @@
|
||||
}, this);
|
||||
if (this.isSameThread()) {
|
||||
this.managedBlock(result::isDone);
|
||||
@@ -1577,7 +_,7 @@
|
||||
@@ -1561,7 +_,7 @@
|
||||
DataPackConfig dataPackConfig = initialDataConfig.dataPacks();
|
||||
FeatureFlagSet forcedFeatures = initMode ? FeatureFlagSet.of() : initialDataConfig.enabledFeatures();
|
||||
FeatureFlagSet allowedFeatures = initMode ? FeatureFlags.REGISTRY.allFlags() : initialDataConfig.enabledFeatures();
|
||||
@@ -1382,7 +1388,7 @@
|
||||
if (safeMode) {
|
||||
return configureRepositoryWithSelection(packRepository, List.of("vanilla"), forcedFeatures, false);
|
||||
}
|
||||
@@ -1632,7 +_,7 @@
|
||||
@@ -1616,7 +_,7 @@
|
||||
private static WorldDataConfiguration configureRepositoryWithSelection(
|
||||
final PackRepository packRepository, final Collection<String> selected, final FeatureFlagSet forcedFeatures, final boolean disableInactive
|
||||
) {
|
||||
@@ -1391,7 +1397,7 @@
|
||||
enableForcedFeaturePacks(packRepository, forcedFeatures);
|
||||
DataPackConfig packConfig = getSelectedPacks(packRepository, disableInactive);
|
||||
FeatureFlagSet packRequestedFeatures = packRepository.getRequestedFeatureFlags().join(forcedFeatures);
|
||||
@@ -1664,7 +_,7 @@
|
||||
@@ -1648,7 +_,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1400,10 +1406,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1681,8 +_,8 @@
|
||||
@@ -1665,8 +_,8 @@
|
||||
UserWhiteList whiteList = playerList.getWhiteList();
|
||||
|
||||
for (ServerPlayer player : List.copyOf(playerList.getPlayers())) {
|
||||
for (ServerPlayer player : Lists.newArrayList(playerList.getPlayers())) {
|
||||
- if (!whiteList.isWhiteListed(player.nameAndId())) {
|
||||
- player.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted"));
|
||||
+ if (!whiteList.isWhiteListed(player.nameAndId()) && !this.getPlayerList().isOp(player.nameAndId())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
|
||||
@@ -1411,7 +1417,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1704,12 +_,12 @@
|
||||
@@ -1688,12 +_,12 @@
|
||||
}
|
||||
|
||||
public ServerLevel findRespawnDimension() {
|
||||
@@ -1426,7 +1432,7 @@
|
||||
public void setRespawnData(final LevelData.RespawnData respawnData) {
|
||||
ServerLevelData levelData = this.worldData.overworldData();
|
||||
LevelData.RespawnData oldRespawnData = levelData.getRespawnData();
|
||||
@@ -1777,17 +_,20 @@
|
||||
@@ -1761,17 +_,20 @@
|
||||
return this.randomSequences;
|
||||
}
|
||||
|
||||
@@ -1452,16 +1458,16 @@
|
||||
}
|
||||
|
||||
public boolean isEnforceWhitelist() {
|
||||
@@ -1870,7 +_,7 @@
|
||||
@@ -1854,7 +_,7 @@
|
||||
private void dumpGameRules(final Path path) throws IOException {
|
||||
try (Writer output = Files.newBufferedWriter(path)) {
|
||||
final List<String> entries = new ArrayList<>();
|
||||
final List<String> entries = Lists.newArrayList();
|
||||
- final GameRules gameRules = this.getGameRules();
|
||||
+ final GameRules gameRules = this.overworld().getGameRules(); // Paper - per-level GameRules
|
||||
gameRules.visitGameRuleTypes(new GameRuleTypeVisitor() {
|
||||
@Override
|
||||
public <T> void visit(final GameRule<T> gameRule) {
|
||||
@@ -1927,6 +_,17 @@
|
||||
@@ -1911,6 +_,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1479,7 +1485,7 @@
|
||||
private ProfilerFiller createProfiler() {
|
||||
if (this.willStartRecordingMetrics) {
|
||||
this.metricsRecorder = ActiveMetricsRecorder.createStarted(
|
||||
@@ -2054,16 +_,22 @@
|
||||
@@ -2038,16 +_,22 @@
|
||||
}
|
||||
|
||||
public void logChatMessage(final Component message, final ChatType.Bound chatType, final @Nullable String tag) {
|
||||
@@ -1506,7 +1512,7 @@
|
||||
}
|
||||
|
||||
public boolean logIPs() {
|
||||
@@ -2074,8 +_,9 @@
|
||||
@@ -2058,8 +_,9 @@
|
||||
LOGGER.debug("Received custom click action {} with payload {}", id, payload.orElse(null));
|
||||
}
|
||||
|
||||
@@ -1517,7 +1523,7 @@
|
||||
}
|
||||
|
||||
public boolean setAutoSave(final boolean enable) {
|
||||
@@ -2101,12 +_,14 @@
|
||||
@@ -2085,12 +_,14 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1535,7 +1541,7 @@
|
||||
player.connection.send(new ClientboundEntityEventPacket(player, event));
|
||||
}
|
||||
} else if (rule == GameRules.LIMITED_CRAFTING || rule == GameRules.IMMEDIATE_RESPAWN) {
|
||||
@@ -2114,20 +_,21 @@
|
||||
@@ -2098,20 +_,21 @@
|
||||
? ClientboundGameEventPacket.LIMITED_CRAFTING
|
||||
: ClientboundGameEventPacket.IMMEDIATE_RESPAWN;
|
||||
ClientboundGameEventPacket packet = new ClientboundGameEventPacket(eventType, (Boolean)value ? 1.0F : 0.0F);
|
||||
@@ -1562,7 +1568,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2140,12 +_,14 @@
|
||||
@@ -2124,12 +_,14 @@
|
||||
return this.savedDataStorage;
|
||||
}
|
||||
|
||||
@@ -1579,7 +1585,7 @@
|
||||
}
|
||||
|
||||
public boolean acceptsTransfers() {
|
||||
@@ -2309,4 +_,53 @@
|
||||
@@ -2288,4 +_,53 @@
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 370 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user