Compare commits

...
Author SHA1 Message Date
Owen1212055 6409d5ce9e Update 2026-06-26 20:54:44 +02:00
Owen1212055 2de25adfc9 Merge remote-tracking branch 'origin/main' into feat/block-capturing
# Conflicts:
#	paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
#	paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
#	paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
#	paper-server/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java
2026-04-25 17:46:29 -04:00
Miles 182b2f8a2a Add Point Of Interest API (#12117) 2026-04-25 16:27:55 -04:00
Owen a8c05a35c2 Add an option to WorldCreator to avoid spawn location computation on world creation (#13407) 2026-04-25 15:31:47 -04:00
Pedro 945aea9961 Fix Damager for HangingBreakByEntityEvent and minor improvements (#12615) 2026-04-25 14:52:57 -04:00
Pedro 4aef3f0a67 Remove already fixed vanilla bug MC-244739 (#13787) 2026-04-25 14:26:37 -04:00
Pedro 60073714ba Update MOJIRA reference in DeOp command response for multiple targets [ci skip] (#13812) 2026-04-25 14:20:54 -04:00
Rothes 4f184db3c9 Fix memory leak on constantly damage (#13455) 2026-04-25 14:19:52 -04:00
Illia Bondar 57d84f68a3 Prevent NPE on ThrownPotion#splash (#13683) 2026-04-25 14:16:47 -04:00
Warrior 49daadd25d Fix insomniaStartTicks option being ignored for phantom spawning (#13609) 2026-04-25 13:57:03 -04:00
Pedro b2b1e177cb Improvement to Vehicle damage events (#13572) 2026-04-25 13:46:17 -04:00
PedroandLulu13022002 90aee8e007 Update world methods to take in account environment attributes (#13543)
Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
2026-04-25 19:20:49 +02:00
Pedro d74f6bc77a Support for Kill damageable entities (#13667) 2026-04-25 13:12:18 -04:00
Kezz dd203a2716 chore: Don't log errors for accepting the EULA via the system property (#13681) 2026-04-25 13:02:42 -04:00
David Lüdtke dcf5577aae Fix PlayerProfile applySkinToPlayerHeadContents (#13692) 2026-04-25 12:53:39 -04:00
Harold c39822ae08 reset pending keepalive state on listener handoff during reconfiguration (#13712) 2026-04-25 12:50:10 -04:00
David 4af86ff3e0 Handle null ServerLevel in fromGlobalPos (#13703) 2026-04-25 12:41:23 -04:00
ac15CR 1a8417b12b [ci skip] Add Javadocs for PotionEffect withX methods (#13723) 2026-04-25 12:39:33 -04:00
personalized-advertising 65461225ef Update health after setPlayerProfile() (#13717) 2026-04-25 12:32:53 -04:00
PedroandLulu13022002 7b31cc2b6b Make leaveMessage in PlayerKickEvent nullable (#13804)
Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
2026-04-25 12:30:26 -04:00
sovdeeandLulu13022002 aa6f09b098 Prevent Happy Ghast from playing mount/dismount noises when silenced (#13807)
Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
2026-04-25 12:01:24 -04:00
Nassim Jahnke 944ce44a4a Fix CraftWorld identifier passing 2026-04-25 11:59:27 +02:00
Pedro 6fe7eb5aae Load World Key when its instance is created (#13818) 2026-04-25 09:58:13 +02:00
Spottedleaf 948acae8c2 Raise existing level.dat data size quota
It appears that custom data may push it to be higher than
what the default quota allows for.

See https://mojira.dev/MC-307144

Fixes https://github.com/PaperMC/Paper/issues/13820
2026-04-24 19:25:14 -07:00
Nassim Jahnke ef4e85d6db Replace remaining relevant getWorld(String) use 2026-04-24 19:16:43 +02:00
Shane Freeder 3611a815e6 Fix String#toLower/UpperCase usages 2026-04-23 16:37:29 +01:00
Jason Penilla 1d14197790 fix setSpawnLocation changing respawn dimension
it was carrying some leftover intermediary shape from before PaperLevelOverrides design was finalized - revert to original shape for correct behavior

also revert ordering change in setRespawnData
2026-04-21 09:49:28 -07:00
Spottedleaf 6bac3c95bf Delay open/close callbacks for chests
The logic for chests may invoke block updates, which may not
be safe to perform. For example, inventory closing due to
player logout (which restricts chunk loading due to being
inside an entity status callback) or during shutdown (which has
chunk loading restrictions due to the chunk system being halted).

This also fixes the recheckOpeners function not firing
the redstone event if the open count changes.
2026-04-20 03:05:27 -07:00
Spottedleaf 9b1dd57135 Do not empty beehives from worldgen threads
The level is not set here, so emptying would cause a crash
due to NPE, and of course the logic is not safe to run from
the worldgen thread even if the level was set.
2026-04-20 02:48:53 -07:00
Spottedleaf 4be1a310c2 Add EntityScheduler#scheduleOrExecute
This is useful on Folia where we may want to try to avoid
scheduling a delayed task.
2026-04-20 02:15:28 -07:00
Nassim Jahnke 66d3bbed38 Update frosted ice patch for 26.1 2026-04-19 18:41:44 +02:00
Jonathan d0be5e20fd Call BlockFadeEvent for FrostedIce (#13764) 2026-04-19 16:14:54 +02:00
Asish Kumar dd0810c4af Skip inactive AI ticks for unaware mobs (#13781) 2026-04-19 16:14:21 +02:00
Pedro 04ee8eac9b Remove Paper config unsupported-settings.compression-format (#13800) 2026-04-19 16:08:48 +02:00
Warrior c58cba39fb Port blockstate validation patch (#13786) 2026-04-19 16:08:32 +02:00
Lulu13022002 4731020c9d only grow water plant on solid blocks, prevent item removal when event is cancelled 2026-02-15 20:04:50 +01:00
Lulu13022002 dbd8747173 allow to bonemeal bamboo and moss block 2026-02-14 20:02:59 +01:00
Lulu13022002 d4895653fa bunch of bonemeal fixes
Track bonemeal usage for StructureGrowEvent and propagate context properly between the two events.
Call the StructureGrowEvent for fungus, only call the BlockFertilizeEvent if the placement is sucessful.
2026-02-13 20:31:06 +01:00
Lulu13022002 4397b319a7 fix relative pos for placed block on water 2026-02-12 22:28:19 +01:00
Lulu13022002 298a8bfd32 remove CapturedBlockState and capturedFlags field 2026-02-12 21:06:58 +01:00
Lulu13022002 bcf5a3fffe allow to bonemeal the ocean floor 2026-02-11 22:20:10 +01:00
Lulu13022002 84a73d8984 format 2026-02-11 21:17:17 +01:00
Owen1212055 f46fbbe20b Fix block against logic
TODO: Lillypad logic looks odd
2026-02-06 17:40:37 -05:00
Owen1212055 ad45c84240 Bye treetype field 2026-02-06 14:48:11 -05:00
Owen1212055 e4114b0e0e Adjust pre cancel 2026-02-06 14:38:26 -05:00
Owen1212055 d56b548552 Fix some niche issues 2026-02-06 14:21:55 -05:00
Owen1212055 8e32dcbfe7 Fix some layering issues, simplify a bit 2026-02-06 12:32:57 -05:00
Owen1212055 6968eb065e Small name cleanup 2026-02-06 10:31:01 -05:00
Owen1212055 d55864cc09 Fix block entity placement 2026-02-06 10:08:51 -05:00
Owen1212055 c77b8418bf Better bonemeal support 2026-02-06 10:04:38 -05:00
Owen1212055 f4074a073e Fix build 2026-02-05 17:05:08 -05:00
Owen1212055 05eb2e4543 Block Capturing System 2026-02-05 16:52:05 -05:00
98 changed files with 4709 additions and 415 deletions

No files matched your search

@@ -0,0 +1,181 @@
package io.papermc.paper.registry.keys;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.annotation.GeneratedClass;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla keys for {@link RegistryKey#POINT_OF_INTEREST_TYPE}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedClass
public final class PoiTypeKeys {
/**
* {@code minecraft:armorer}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> ARMORER = create(key("armorer"));
/**
* {@code minecraft:bee_nest}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> BEE_NEST = create(key("bee_nest"));
/**
* {@code minecraft:beehive}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> BEEHIVE = create(key("beehive"));
/**
* {@code minecraft:butcher}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> BUTCHER = create(key("butcher"));
/**
* {@code minecraft:cartographer}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> CARTOGRAPHER = create(key("cartographer"));
/**
* {@code minecraft:cleric}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> CLERIC = create(key("cleric"));
/**
* {@code minecraft:farmer}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> FARMER = create(key("farmer"));
/**
* {@code minecraft:fisherman}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> FISHERMAN = create(key("fisherman"));
/**
* {@code minecraft:fletcher}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> FLETCHER = create(key("fletcher"));
/**
* {@code minecraft:home}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> HOME = create(key("home"));
/**
* {@code minecraft:leatherworker}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LEATHERWORKER = create(key("leatherworker"));
/**
* {@code minecraft:librarian}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LIBRARIAN = create(key("librarian"));
/**
* {@code minecraft:lightning_rod}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LIGHTNING_ROD = create(key("lightning_rod"));
/**
* {@code minecraft:lodestone}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> LODESTONE = create(key("lodestone"));
/**
* {@code minecraft:mason}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> MASON = create(key("mason"));
/**
* {@code minecraft:meeting}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> MEETING = create(key("meeting"));
/**
* {@code minecraft:nether_portal}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> NETHER_PORTAL = create(key("nether_portal"));
/**
* {@code minecraft:shepherd}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> SHEPHERD = create(key("shepherd"));
/**
* {@code minecraft:test_instance}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> TEST_INSTANCE = create(key("test_instance"));
/**
* {@code minecraft:toolsmith}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> TOOLSMITH = create(key("toolsmith"));
/**
* {@code minecraft:weaponsmith}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<PoiType> WEAPONSMITH = create(key("weaponsmith"));
private PoiTypeKeys() {
}
private static TypedKey<PoiType> create(final Key key) {
return TypedKey.create(RegistryKey.POINT_OF_INTEREST_TYPE, key);
}
}
@@ -0,0 +1,61 @@
package io.papermc.paper.registry.keys.tags;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.annotation.GeneratedClass;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.tag.TagKey;
import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;
/**
* Vanilla tag keys for {@link RegistryKey#POINT_OF_INTEREST_TYPE}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@NullMarked
@GeneratedClass
public final class PoiTypeTagKeys {
/**
* {@code #minecraft:acquirable_job_site}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<PoiType> ACQUIRABLE_JOB_SITE = create(key("acquirable_job_site"));
/**
* {@code #minecraft:bee_home}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<PoiType> BEE_HOME = create(key("bee_home"));
/**
* {@code #minecraft:village}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TagKey<PoiType> VILLAGE = create(key("village"));
private PoiTypeTagKeys() {
}
/**
* Creates a tag key for {@link PoiType} in the registry {@code minecraft:point_of_interest_type}.
*
* @param key the tag key's key
* @return a new tag key
*/
public static TagKey<PoiType> create(final Key key) {
return TagKey.create(RegistryKey.POINT_OF_INTEREST_TYPE, key);
}
}
@@ -75,7 +75,7 @@ public final class NamespacedTag implements com.destroystokyo.paper.Namespaced {
Preconditions.checkArgument(key != null, "Key cannot be null");
this.namespace = plugin.getName().toLowerCase(Locale.ROOT);
this.key = key.toLowerCase().toLowerCase(Locale.ROOT);
this.key = key.toLowerCase(Locale.ROOT);
// Check validity after normalization
Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", this.namespace);
@@ -257,16 +257,16 @@ public interface PlayerProfile extends org.bukkit.profile.PlayerProfile, PlayerH
@Override
default void applySkinToPlayerHeadContents(final PlayerHeadObjectContents.Builder builder) {
if (this.getProperties().isEmpty() && (this.getName() != null) != (this.getId() != null)) {
if (this.getProperties().isEmpty()) {
if (this.getId() != null) {
builder.id(this.getId());
} else {
} else if (this.getName() != null) {
builder.name(this.getName());
}
return;
} else {
builder.id(this.getId())
.name(this.getName())
.profileProperties(this.getProperties().stream().map(prop -> property(prop.getName(), prop.getValue(), prop.getSignature())).toList());
}
builder.id(this.getId())
.name(this.getName())
.profileProperties(this.getProperties().stream().map(prop -> property(prop.getName(), prop.getValue(), prop.getSignature())).toList());
}
}
@@ -3,6 +3,7 @@ package io.papermc.paper;
import com.destroystokyo.paper.SkinParts;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.datacomponent.item.ResolvableProfile;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.world.damagesource.CombatEntry;
import io.papermc.paper.world.damagesource.FallLocationType;
import java.util.Set;
@@ -52,6 +53,14 @@ public interface InternalAPIBridge {
*/
DamageEffect getDamageEffect(String key);
/**
* Creates an occupancy instance for the passed enum entry name.
*
* @param enumNameEntry the enum entry
* @return the occupancy
*/
PoiType.Occupancy createOccupancy(String enumNameEntry);
/**
* Constructs the legacy custom biome instance for the biome enum.
*
@@ -0,0 +1,30 @@
package io.papermc.paper.entity.poi;
import java.util.function.Predicate;
import org.bukkit.Location;
import org.bukkit.World;
import org.jspecify.annotations.NullMarked;
/**
* Holds the result of searching for a point of interest.
*
* @see World#locateAllPoiInRange(Location, Predicate, int)
* @see World#locateAllPoiInRange(Location, Predicate, int, PoiType.Occupancy)
*/
@NullMarked
public interface PoiSearchResult {
/**
* Returns the {@link PoiType}.
*
* @return the {@link PoiType}
*/
PoiType poiType();
/**
* Return the location of the {@link PoiType}.
*
* @return the location the {@link PoiType} was found at
*/
Location location();
}
@@ -0,0 +1,66 @@
package io.papermc.paper.entity.poi;
import io.papermc.paper.InternalAPIBridge;
import org.bukkit.Keyed;
import org.bukkit.block.data.BlockData;
import org.jspecify.annotations.NullMarked;
/**
* Represents a point of interest (POI).
* <p>
* Many PoiTypes act as a marker vs storing occupants examples of this are most
* explicitly {@link PoiTypes#NETHER_PORTAL}. This state can be checked using the method
* {@link #hasOccupants()} which will return {@code true} if the PoiType can have occupants.
*
* @see PoiTypes
*/
@NullMarked
public interface PoiType extends Keyed {
/**
* Determines whether the provided BlockState is relevant to this
* point of interest.
*
* @param data the BlockData to check
* @return {@code true} if the BlockState is relevant, otherwise {@code false}
*/
boolean is(BlockData data);
/**
* Determines whether this PoiType can actually hold occupants.
*
* @return {@code true} if this PoiType can hold occupants
*/
boolean hasOccupants();
/**
* Determines the type of occupancy the point of interest has.
* <p>
* A PoiType occupancy is defined by how much "space" a point of interest
* has.
* <p>
* With context to a Villager workstation if a villager is currently using
* a workstation as a source of its profession the occupancy would be
* {@link Occupancy#IS_OCCUPIED}, however, if no villager was attached to
* a workstation its occupancy would be {@link Occupancy#HAS_SPACE}.
*/
interface Occupancy {
/**
* The poi has space.
*/
Occupancy HAS_SPACE = occupancy("HAS_SPACE");
/**
* The poi is occupied.
*/
Occupancy IS_OCCUPIED = occupancy("IS_OCCUPIED");
/**
* The poi is either occupied or has space.
*/
Occupancy ANY = occupancy("ANY");
private static Occupancy occupancy(final String enumEntryName) {
return InternalAPIBridge.get().createOccupancy(enumEntryName);
}
}
}
@@ -0,0 +1,64 @@
package io.papermc.paper.entity.poi;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import org.bukkit.Registry;
import org.jspecify.annotations.NullMarked;
/**
* All the built-in point of interest types.
*/
@NullMarked
public final class PoiTypes {
// Start generate - PoiTypes
public static final PoiType ARMORER = get("armorer");
public static final PoiType BEE_NEST = get("bee_nest");
public static final PoiType BEEHIVE = get("beehive");
public static final PoiType BUTCHER = get("butcher");
public static final PoiType CARTOGRAPHER = get("cartographer");
public static final PoiType CLERIC = get("cleric");
public static final PoiType FARMER = get("farmer");
public static final PoiType FISHERMAN = get("fisherman");
public static final PoiType FLETCHER = get("fletcher");
public static final PoiType HOME = get("home");
public static final PoiType LEATHERWORKER = get("leatherworker");
public static final PoiType LIBRARIAN = get("librarian");
public static final PoiType LIGHTNING_ROD = get("lightning_rod");
public static final PoiType LODESTONE = get("lodestone");
public static final PoiType MASON = get("mason");
public static final PoiType MEETING = get("meeting");
public static final PoiType NETHER_PORTAL = get("nether_portal");
public static final PoiType SHEPHERD = get("shepherd");
public static final PoiType TEST_INSTANCE = get("test_instance");
public static final PoiType TOOLSMITH = get("toolsmith");
public static final PoiType WEAPONSMITH = get("weaponsmith");
// End generate - PoiTypes
private static PoiType get(@KeyPattern.Value final String key) {
return Registry.POINT_OF_INTEREST_TYPE.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key));
}
private PoiTypes() {
}
}
@@ -2,6 +2,7 @@ package io.papermc.paper.registry;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.registry.tag.TagKey;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
@@ -92,6 +93,11 @@ public sealed interface RegistryKey<T> extends Keyed permits RegistryKeyImpl {
* @see io.papermc.paper.registry.keys.VillagerProfessionKeys
*/
RegistryKey<Villager.Profession> VILLAGER_PROFESSION = create("villager_profession");
/**
* Built-in registry for poi types.
* @see io.papermc.paper.registry.keys.PoiTypeKeys
*/
RegistryKey<PoiType> POINT_OF_INTEREST_TYPE = create("point_of_interest_type");
/**
* Built-in registry for villager types.
* @see io.papermc.paper.registry.keys.VillagerTypeKeys
@@ -859,9 +859,13 @@ public final class Bukkit {
/**
* Gets the world with the given name.
*
* <p>This method is considered obsolete and is a candidate for future deprecation.
* Prefer using {@link #getWorld(NamespacedKey)}.</p>
*
* @param name the name of the world to retrieve
* @return a world with the given name, or null if none exists
*/
@ApiStatus.Obsolete
@Nullable
public static World getWorld(@NotNull String name) {
return server.getWorld(name);
@@ -877,7 +881,7 @@ public final class Bukkit {
public static World getWorld(@NotNull UUID uid) {
return server.getWorld(uid);
}
// Paper start
/**
* Gets the world from the given NamespacedKey
*
@@ -899,7 +903,6 @@ public final class Bukkit {
public static World getWorld(@NotNull net.kyori.adventure.key.Key worldKey) {
return server.getWorld(worldKey);
}
// Paper end
/**
* Create a new virtual {@link WorldBorder}.
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Predicate;
import io.papermc.paper.world.MoonPhase;
import org.bukkit.block.Biome;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
@@ -526,12 +527,12 @@ public interface RegionAccessor extends Keyed, io.papermc.paper.world.flag.Featu
@NotNull
public <T extends Entity> T addEntity(@NotNull T entity);
// Paper start
/**
* @return the current moon phase at the current time in the world
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
@NotNull
io.papermc.paper.world.MoonPhase getMoonPhase();
MoonPhase getMoonPhase();
/**
* Get the world's key
@@ -559,5 +560,4 @@ public interface RegionAccessor extends Keyed, io.papermc.paper.world.flag.Featu
* @return collides or not
*/
boolean hasCollisionsIn(@NotNull org.bukkit.util.BoundingBox boundingBox);
// Paper end
}
@@ -5,6 +5,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
@@ -280,6 +281,12 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
* @see Villager.Profession
*/
Registry<Villager.Profession> VILLAGER_PROFESSION = registryFor(RegistryKey.VILLAGER_PROFESSION);
/**
* Point of interest types.
*
* @see PoiType
*/
Registry<PoiType> POINT_OF_INTEREST_TYPE = registryFor(RegistryKey.POINT_OF_INTEREST_TYPE);
/**
* Villager type.
*
+74 -4
View File
@@ -1,5 +1,7 @@
package org.bukkit;
import io.papermc.paper.entity.poi.PoiSearchResult;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import java.io.File;
import java.nio.file.Path;
@@ -43,6 +45,7 @@ import org.bukkit.util.BoundingBox;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.StructureSearchResult;
import org.bukkit.util.Vector;
import org.checkerframework.checker.index.qual.Positive;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -2769,10 +2772,11 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
/**
* Gets if this world is natural.
* <p>
* When false, the moon is not visible and eyeblossoms do not open/close
* When true, eyeblossoms cycle open/close, nether portals can spawn
* zombified piglins and creaking heart works
*
* @return true if world is natural
* @deprecated replaced by the gameplay/eyeblossom_open and gameplay/creaking_active environment attributes
* @deprecated replaced by the gameplay/nether_portal_spawns_piglin, gameplay/eyeblossom_open and gameplay/creaking_active environmental attributes
*/
@Deprecated(since = "1.21.11")
public boolean isNatural();
@@ -2784,7 +2788,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* not always be the case.
*
* @return true if beds work in this world
* @deprecated Due to 1.21.11 beds changes, a boolean no longer
* @deprecated due to 1.21.11 beds changes, a boolean no longer
* represents if they work. There is no replacement API yet
*/
@ApiStatus.Obsolete(since = "1.21.11")
@@ -2809,6 +2813,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* transforming to zombified piglins.
*
* @return true if piglins will not transform to zombified piglins
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
public boolean isPiglinSafe();
@@ -2816,6 +2821,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* Gets if this world allows players to charge and use respawn anchors.
*
* @return true if players can charge and use respawn anchors
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
public boolean isRespawnAnchorWorks();
@@ -2824,6 +2830,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* raid.
*
* @return true if raids will be triggered
* @apiNote the returned value may be inaccurate in custom biome using environmental attribute override
*/
public boolean hasRaids();
@@ -2837,7 +2844,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
* </ul>
*
* @return true if this world has the above mechanics
* @deprecated as of 1.21.11, ultra warm is replaced by the WATER_EVAPORATES, FAST_LAVA, and DEFAULT_DRIPSTONE_PARTICLE environment attributes.
* @deprecated replaced by the gameplay/water_evaporates and gameplay/fast_lava environmental attributes
*/
@Deprecated(since = "1.21.11")
public boolean isUltraWarm();
@@ -4295,6 +4302,69 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
return java.util.Optional.ofNullable(this.locateNearestBiome(origin, radius, step, step, biome)).map(BiomeSearchResult::getLocation).orElse(null);
}
/**
* Finds the nearest point of interest closest to the given location
* without any occupancy restriction.
*
* @param origin where to start looking for a new point of interest at
* @param poiType the poi type to find
* @param radius the radius
* @return a location at the nearest PoiType or {@code null} if no poi was found
*/
@Nullable
default Location locateNearestPoi(@NotNull Location origin, @NotNull PoiType poiType, @Positive int radius) {
return this.locateNearestPoi(origin, poiType, radius, PoiType.Occupancy.ANY);
}
/**
* Finds the nearest point of interest closest to the given location.
* <p>
* {@link PoiType} that return {@code false} for {@link PoiType#hasOccupants()}
* may not behave as expected for given occupancies other than
* {@link PoiType.Occupancy#ANY}.
*
* @param origin where to start looking for a new point of interest at
* @param poiType the poi type to find
* @param radius the radius
* @param occupancy the current required occupancy of the point of interest
* @return a location at the nearest PoiType or {@code null} if no poi was found
*/
@Nullable
Location locateNearestPoi(@NotNull Location origin, @NotNull PoiType poiType, @Positive int radius, @NotNull PoiType.Occupancy occupancy);
/**
* Finds all valid {@link PoiType} in the provided radius and returns them
* in a list format without any occupancy restriction.
*
* @param origin the center point of the radius
* @param poiTypePredicate the predicate to test whether a PoiType
* can be collected into the result
* @param radius the radius
* @return a list of search results containing all found Poi's in the range
*/
@NotNull
default List<PoiSearchResult> locateAllPoiInRange(@NotNull Location origin, @NotNull Predicate<PoiType> poiTypePredicate, @Positive int radius) {
return this.locateAllPoiInRange(origin, poiTypePredicate, radius, PoiType.Occupancy.ANY);
}
/**
* Finds all valid {@link PoiType} in the provided radius and returns them
* in a list format.
* <p>
* {@link PoiType} that return false for {@link PoiType#hasOccupants()}
* may not behave as expected for given occupancies other than
* {@link PoiType.Occupancy#ANY}.
*
* @param origin the center point of the radius
* @param poiTypePredicate the predicate to test whether a PoiType
* can be collected into the result
* @param radius the radius
* @param occupancy the current required occupancy of the point of interest
* @return a list of search results containing all found Poi's in the range
*/
@NotNull
List<PoiSearchResult> locateAllPoiInRange(@NotNull Location origin, @NotNull Predicate<PoiType> poiTypePredicate, @Positive int radius, @NotNull PoiType.Occupancy occupancy);
/**
* Gets the coordinate scaling of this world.
*
@@ -2,6 +2,7 @@ package org.bukkit;
import com.google.common.base.Preconditions;
import java.util.Random;
import io.papermc.paper.math.Position;
import org.bukkit.command.CommandSender;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
@@ -26,6 +27,10 @@ public class WorldCreator {
private boolean hardcore = false;
private boolean bonusChest = false;
private @Nullable Position spawnPositionOverride;
private @Nullable Float spawnYawOverride;
private @Nullable Float spawnPitchOverride;
/**
* Creates an empty WorldCreationOptions for the given world name.
*
@@ -261,6 +266,80 @@ public class WorldCreator {
return this;
}
/**
* Sets the forced spawn position for the world created by this {@link WorldCreator}.
* <p>
* This overrides vanilla and custom generator behavior without loading any chunks.
* When a forced spawn is specified, the bonus chest will not be generated.
*
* @param position the spawn position
* @param yaw the yaw rotation at spawn
* @param pitch the pitch rotation at spawn
* @return this object, for chaining
*/
@NotNull
public WorldCreator forcedSpawnPosition(@NotNull Position position, float yaw, float pitch) {
this.spawnPositionOverride = position; // If you set this to null, it wont do anything!
this.spawnYawOverride = yaw;
this.spawnPitchOverride = pitch;
return this;
}
/**
* Clears any previously forced spawn position.
* <p>
* After calling this, vanilla spawn selection behavior is used.
*
* @return this object, for chaining
*/
@NotNull
public WorldCreator clearForcedSpawnPosition() {
this.spawnPositionOverride = null;
this.spawnYawOverride = null;
this.spawnPitchOverride = null;
return this;
}
/**
* Gets the forced spawn position that will be applied when this world is created.
* <p>
* If this returns {@code null}, vanilla or custom generator behavior will be used
* to determine the spawn position.
*
* @return the forced spawn position, or {@code null} for the vanilla behavior
*/
public @Nullable Position forcedSpawnPosition() {
return this.spawnPositionOverride;
}
/**
* Gets the forced spawn yaw that will be applied when this world is created.
* <p>
* If this returns {@code null}, the spawn yaw will be determined by vanilla behavior
* or the world generator.
* <p>
* This value is only meaningful if a forced spawn position is present.
*
* @return the forced spawn yaw, or {@code null} for the vanilla behavior
*/
public @Nullable Float forcedSpawnYaw() {
return this.spawnYawOverride;
}
/**
* Gets the forced spawn pitch that will be applied when this world is created.
* <p>
* If this returns {@code null}, the spawn pitch will be determined by vanilla behavior
* or the world generator.
* <p>
* This value is only meaningful if a forced spawn position is present.
*
* @return the forced spawn pitch, or {@code null} for the vanilla behavior
*/
public @Nullable Float forcedSpawnPitch() {
return this.spawnPitchOverride;
}
/**
* Gets the generator that will be used to create or load the world.
* <p>
@@ -2,12 +2,15 @@ package org.bukkit.entity;
import org.bukkit.attribute.Attribute;
import org.bukkit.damage.DamageSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.bukkit.damage.DamageType;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* Represents an {@link Entity} that has health and can take damage.
*/
@NullMarked
public interface Damageable extends Entity {
/**
* Deals the given amount of damage to this entity.
@@ -32,7 +35,24 @@ public interface Damageable extends Entity {
* @param amount amount of damage to deal
* @param damageSource source to which the damage should be attributed
*/
void damage(double amount, @NotNull DamageSource damageSource);
void damage(double amount, DamageSource damageSource);
/**
* Sets the entity's health to 0 and kill the entity with a generic DamageSource.
*
* @throws IllegalStateException if is used in world generation
*/
default void kill() {
this.kill(DamageSource.builder(DamageType.GENERIC_KILL).build());
}
/**
* Sets the entity's health to 0 and kill the entity with the specified DamageSource.
*
* @param damageSource the DamageSource to use for the kill
* @throws IllegalStateException if is used in world generation
*/
void kill(DamageSource damageSource);
/**
* Gets the entity's health from 0 to {@link #getMaxHealth()}, where 0 is dead.
@@ -57,7 +77,7 @@ public interface Damageable extends Entity {
* @param amount heal amount
*/
default void heal(final double amount) {
this.heal(amount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.CUSTOM);
this.heal(amount, EntityRegainHealthEvent.RegainReason.CUSTOM);
}
/**
@@ -66,7 +86,7 @@ public interface Damageable extends Entity {
* @param amount heal amount
* @param reason heal reason
*/
void heal(double amount, @NotNull org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason);
void heal(double amount, EntityRegainHealthEvent.RegainReason reason);
/**
* Gets the entity's absorption amount.
@@ -10,8 +10,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when a block is ignited. If you want to catch when a Player places
* fire, you need to use {@link BlockPlaceEvent}.
* Called when a block is ignited.
* <p>
* If this event is cancelled, the block will not be ignited.
*/
@@ -1,27 +1,30 @@
package org.bukkit.event.hanging;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Hanging;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
/**
* Triggered when a hanging entity is removed by an entity
*/
@NullMarked
public class HangingBreakByEntityEvent extends HangingBreakEvent {
private final Entity remover;
private final DamageSource damageSource;
@ApiStatus.Internal
public HangingBreakByEntityEvent(@NotNull final Hanging hanging, @NotNull final Entity remover) {
this(hanging, remover, HangingBreakEvent.RemoveCause.ENTITY);
public HangingBreakByEntityEvent(final Hanging hanging, final Entity remover, final DamageSource damageSource) {
this(hanging, remover, damageSource, HangingBreakEvent.RemoveCause.ENTITY);
}
@ApiStatus.Internal
public HangingBreakByEntityEvent(@NotNull final Hanging hanging, @NotNull final Entity remover, @NotNull final HangingBreakEvent.RemoveCause cause) {
public HangingBreakByEntityEvent(final Hanging hanging, final Entity remover, final DamageSource damageSource, final HangingBreakEvent.RemoveCause cause) {
super(hanging, cause);
this.remover = remover;
this.damageSource = damageSource;
}
/**
@@ -29,8 +32,16 @@ public class HangingBreakByEntityEvent extends HangingBreakEvent {
*
* @return the entity that removed the hanging entity
*/
@NotNull
public Entity getRemover() {
return this.remover;
}
/**
* Gets the {@link DamageSource} that caused the hanging entity to be removed.
*
* @return the damage source
*/
public DamageSource getDamageSource() {
return this.damageSource;
}
}
@@ -6,24 +6,26 @@ import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* Called when a player gets kicked from the server
*/
@NullMarked
public class PlayerKickEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private Component kickReason;
private Component leaveMessage;
private @Nullable Component leaveMessage;
private final Cause cause;
private boolean cancelled;
@ApiStatus.Internal
@Deprecated(forRemoval = true)
public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final String kickReason, @NotNull final String leaveMessage) {
public PlayerKickEvent(final Player playerKicked, final String kickReason, final String leaveMessage) {
super(playerKicked);
this.kickReason = LegacyComponentSerializer.legacySection().deserialize(kickReason);
this.leaveMessage = LegacyComponentSerializer.legacySection().deserialize(leaveMessage);
@@ -32,7 +34,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
@ApiStatus.Internal
@Deprecated(forRemoval = true)
public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final Component kickReason, @NotNull final Component leaveMessage) {
public PlayerKickEvent(final Player playerKicked, final Component kickReason, final Component leaveMessage) {
super(playerKicked);
this.kickReason = kickReason;
this.leaveMessage = leaveMessage;
@@ -40,7 +42,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
}
@ApiStatus.Internal
public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final Component kickReason, @NotNull final Component leaveMessage, @NotNull final Cause cause) {
public PlayerKickEvent(final Player playerKicked, final Component kickReason, final Component leaveMessage, final Cause cause) {
super(playerKicked);
this.kickReason = kickReason;
this.leaveMessage = leaveMessage;
@@ -52,7 +54,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
*
* @return string kick reason
*/
public @NotNull Component reason() {
public Component reason() {
return this.kickReason;
}
@@ -61,7 +63,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
*
* @param kickReason kick reason
*/
public void reason(@NotNull Component kickReason) {
public void reason(Component kickReason) {
this.kickReason = kickReason;
}
@@ -71,7 +73,6 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
* @return string kick reason
* @deprecated in favour of {@link #reason()}
*/
@NotNull
@Deprecated
public String getReason() {
return LegacyComponentSerializer.legacySection().serialize(this.kickReason);
@@ -81,10 +82,10 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
* Sets the reason why the player is getting kicked
*
* @param kickReason kick reason
* @deprecated in favour of {@link #reason(net.kyori.adventure.text.Component)}
* @deprecated in favour of {@link #reason(Component)}
*/
@Deprecated
public void setReason(@NotNull String kickReason) {
public void setReason(String kickReason) {
this.kickReason = LegacyComponentSerializer.legacySection().deserialize(kickReason);
}
@@ -93,16 +94,16 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
*
* @return string kick reason
*/
public @NotNull Component leaveMessage() {
public @Nullable Component leaveMessage() {
return this.leaveMessage;
}
/**
* Sets the leave message send to all online players
*
* @param leaveMessage leave message
* @param leaveMessage leave message. If {@code null}, no message will be sent
*/
public void leaveMessage(@NotNull Component leaveMessage) {
public void leaveMessage(@Nullable Component leaveMessage) {
this.leaveMessage = leaveMessage;
}
@@ -112,28 +113,26 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
* @return string kick reason
* @deprecated in favour of {@link #leaveMessage()}
*/
@NotNull
@Deprecated
public String getLeaveMessage() {
return LegacyComponentSerializer.legacySection().serialize(this.leaveMessage);
public @Nullable String getLeaveMessage() {
return LegacyComponentSerializer.legacySection().serializeOrNull(this.leaveMessage);
}
/**
* Sets the leave message send to all online players
*
* @param leaveMessage leave message
* @deprecated in favour of {@link #leaveMessage(net.kyori.adventure.text.Component)}
* @param leaveMessage leave message. If {@code null}, no message will be sent
* @deprecated in favour of {@link #leaveMessage(Component)}
*/
@Deprecated
public void setLeaveMessage(@NotNull String leaveMessage) {
this.leaveMessage = LegacyComponentSerializer.legacySection().deserialize(leaveMessage);
public void setLeaveMessage(@Nullable String leaveMessage) {
this.leaveMessage = LegacyComponentSerializer.legacySection().deserializeOrNull(leaveMessage);
}
/**
* Gets the cause of this kick
*/
@NotNull
public org.bukkit.event.player.PlayerKickEvent.Cause getCause() {
public PlayerKickEvent.Cause getCause() {
return this.cause;
}
@@ -147,13 +146,11 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
@@ -1,5 +1,6 @@
package org.bukkit.event.vehicle;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Cancellable;
@@ -15,18 +16,30 @@ public class VehicleDamageEvent extends VehicleEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final DamageSource damageSource;
private final Entity attacker;
private double damage;
private boolean cancelled;
@ApiStatus.Internal
public VehicleDamageEvent(@NotNull final Vehicle vehicle, @Nullable final Entity attacker, final double damage) {
public VehicleDamageEvent(final @NotNull Vehicle vehicle, final @NotNull DamageSource damageSource, final @Nullable Entity attacker, final double damage) {
super(vehicle);
this.damageSource = damageSource;
this.attacker = attacker;
this.damage = damage;
}
/**
* Gets the DamageSource that caused the damage.
*
* @return the DamageSource that caused the damage
*/
@NotNull
public DamageSource getDamageSource() {
return this.damageSource;
}
/**
* Gets the Entity that is attacking the vehicle
*
@@ -1,5 +1,6 @@
package org.bukkit.event.vehicle;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Cancellable;
@@ -17,15 +18,27 @@ public class VehicleDestroyEvent extends VehicleEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final DamageSource damageSource;
private final Entity attacker;
private boolean cancelled;
@ApiStatus.Internal
public VehicleDestroyEvent(@NotNull final Vehicle vehicle, @Nullable final Entity attacker) {
public VehicleDestroyEvent(final @NotNull Vehicle vehicle, final @NotNull DamageSource damageSource, final @Nullable Entity attacker) {
super(vehicle);
this.damageSource = damageSource;
this.attacker = attacker;
}
/**
* Gets the DamageSource that has destroyed the vehicle.
*
* @return the DamageSource that has destroyed the vehicle
*/
@NotNull
public DamageSource getDamageSource() {
return this.damageSource;
}
/**
* Gets the Entity that has destroyed the vehicle, potentially null
*
@@ -133,26 +133,74 @@ public class PotionEffect implements ConfigurationSerializable {
}
// Paper start
/**
* Returns a new potion effect with the provided type and this effect's
* duration, amplifier, ambient, particles, and icon.
*
* @param type effect type
* @return a new potion effect with the provided type
*/
@NotNull
public PotionEffect withType(@NotNull PotionEffectType type) {
return new PotionEffect(type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided duration and this effect's
* type, amplifier, ambient, particles, and icon.
*
* @param duration measured in ticks, see {@link
* PotionEffect#getDuration()}
* @return a new potion effect with the provided duration
*/
@NotNull
public PotionEffect withDuration(int duration) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided amplifier and this effect's
* type, duration, ambient, particles, and icon.
*
* @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()}
* @return a new potion effect with the provided amplifier
*/
@NotNull
public PotionEffect withAmplifier(int amplifier) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided ambient and this effect's
* type, duration, amplifier, particles, and icon.
*
* @param ambient the ambient status, see {@link PotionEffect#isAmbient()}
* @return a new potion effect with the provided ambient
*/
@NotNull
public PotionEffect withAmbient(boolean ambient) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided particles and this effect's
* type, duration, amplifier, ambient, and icon.
*
* @param particles the particle status, see {@link PotionEffect#hasParticles()}
* @return a new potion effect with the provided particles
*/
@NotNull
public PotionEffect withParticles(boolean particles) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
}
/**
* Returns a new potion effect with the provided icon and this effect's
* type, duration, amplifier, ambient, and particles.
*
* @param icon the icon status, see {@link PotionEffect#hasIcon()}
* @return a new potion effect with the provided icon
*/
@NotNull
public PotionEffect withIcon(boolean icon) {
return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
@@ -27,6 +27,7 @@ import io.papermc.paper.datacomponent.item.SwingAnimation;
import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.item.MapPostProcessing;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.world.WeatheringCopperState;
import io.papermc.typewriter.preset.EnumCloneRewriter;
import io.papermc.typewriter.preset.model.EnumValue;
@@ -231,6 +232,7 @@ public final class Rewriters {
.register("PigSoundVariant", Pig.SoundVariant.class, new RegistryFieldRewriter<>(Registries.PIG_SOUND_VARIANT, "getSoundVariant"))
.register("ZombieNautilusVariant", ZombieNautilus.Variant.class, new RegistryFieldRewriter<>(Registries.ZOMBIE_NAUTILUS_VARIANT, "getVariant"))
.register("Dialog", Dialog.class, new RegistryFieldRewriter<>(Registries.DIALOG, "getDialog"))
.register("PoiTypes", PoiTypes.class, new RegistryFieldRewriter<>(Registries.POINT_OF_INTEREST_TYPE, "get"))
.register("MemoryKey", MemoryKey.class, new MemoryKeyRewriter())
// .register("ItemType", org.bukkit.inventory.ItemType.class, new io.papermc.generator.rewriter.types.simple.ItemTypeRewriter()) - disable for now, lynx want the generic type
.register("BlockType", BlockType.class, new BlockTypeRewriter())
@@ -4,6 +4,7 @@ import io.papermc.generator.utils.ClassHelper;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.registry.data.BannerPatternRegistryEntry;
import io.papermc.paper.registry.data.CatTypeRegistryEntry;
import io.papermc.paper.registry.data.ChickenVariantRegistryEntry;
@@ -175,7 +176,8 @@ public final class RegistryEntries {
entry(Registries.FLUID, Fluids.class, Fluid.class),
entry(Registries.SOUND_EVENT, SoundEvents.class, Sound.class).allowDirect().apiRegistryField("SOUNDS").apiRegistryBuilder(SoundEventRegistryEntry.Builder.class, "PaperSoundEventRegistryEntry.PaperBuilder", RegistryEntry.RegistryModificationApiSupport.NONE),
entry(Registries.DATA_COMPONENT_TYPE, DataComponents.class, DataComponentType.class, "Paper").preload(DataComponentTypes.class).apiAccessName("of"),
entry(Registries.GAME_RULE, GameRules.class, GameRule.class).genericArgCount(1)/*.preload(org.bukkit.GameRules.class)*/ // only preload once the old names are removed
entry(Registries.GAME_RULE, GameRules.class, GameRule.class).genericArgCount(1)/*.preload(org.bukkit.GameRules.class)*/, // only preload once the old names are removed
entry(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.class, io.papermc.paper.entity.poi.PoiType.class, "Paper").preload(io.papermc.paper.entity.poi.PoiTypes.class)
);
public static final List<RegistryEntry<?>> DATA_DRIVEN = List.of(
@@ -23838,7 +23838,7 @@ index 3c1490ac7c259da04031db2f170e0c0a5f512191..470d7c770ae9d045b97e2df145cfe3cf
}
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf164195b730e22 100644
index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda79e93f6f4 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -191,7 +191,7 @@ import net.minecraft.world.scores.ScoreboardSaveData;
@@ -23943,7 +23943,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
public MinecraftServer(
// CraftBukkit start
@@ -843,7 +929,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -853,7 +939,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
level.setSpawnSettings(level.isSpawningMonsters()); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
this.updateEffectiveRespawnData();
this.forceTicks = false; // CraftBukkit
@@ -23952,7 +23952,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
new org.bukkit.event.world.WorldLoadEvent(level.getWorld()).callEvent(); // Paper - call WorldLoadEvent
}
@@ -866,6 +952,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -876,6 +962,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public abstract boolean shouldRconBroadcast();
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force) {
@@ -23964,7 +23964,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
this.scoreboard.storeToSaveDataIfDirty(this.getDataStorage().computeIfAbsent(ScoreboardSaveData.TYPE));
boolean result = false;
@@ -874,7 +965,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -884,7 +975,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
LOGGER.info("Saving chunks for level '{}'/{}", level, level.dimension().identifier());
}
@@ -23973,7 +23973,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
result = true;
}
@@ -971,7 +1062,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -981,7 +1072,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -23982,7 +23982,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
for (ServerLevel levelx : this.getAllLevels()) {
@@ -982,17 +1073,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -992,17 +1083,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.waitUntilNextTick();
}
@@ -24007,7 +24007,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
this.isSaving = false;
this.savedDataStorage.close();
@@ -1013,6 +1101,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1023,6 +1111,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.services().nameToIdCache().save(false); // Paper - Perf: Async GameProfileCache saving
}
// Spigot end
@@ -24022,7 +24022,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
// Paper start - Improved watchdog support - move final shutdown items here
Util.shutdownExecutors();
this.onServerExit();
@@ -1107,16 +1203,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1117,16 +1213,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// execute small amounts of other tasks just in case the number of tasks we are
// draining is large - chunk system and packet processing may be latency sensitive
@@ -24057,7 +24057,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
profiler.pop(); // moonrise:run_all_chunk
profiler.pop(); // moonrise:run_all_tasks
@@ -1417,6 +1528,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1427,6 +1538,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private boolean pollTaskInternal() {
if (super.pollTask()) {
@@ -24065,7 +24065,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
return true;
} else {
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
@@ -1558,6 +1670,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1568,6 +1680,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper - improve tick loop - moved into runAllTasksAtTickStart
this.runAllTasksAtTickStart(); // Paper - improve tick loop
this.tickServer(sprinting ? () -> false : this::haveTime);
@@ -24079,7 +24079,7 @@ index ceb6d955ef275802e549fb9d3e653cc3dda92dad..b4bd3b788a86458ece580693baf16419
this.tickFrame.end();
this.recordEndOfTick(); // Paper - improve tick loop
profiler.pop();
@@ -2600,6 +2719,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2610,6 +2729,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -27004,7 +27004,7 @@ index de75dda2e6f17f0ae6e7f6a5620fd3eac9d389e5..6774d12d04fa848624fb8bf7a3c2b8bc
List<Entity> passengers = this.entity.getPassengers();
if (!passengers.equals(this.lastPassengers)) {
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f4597103e7a111 100644
index 5dc8f5d3738bb3b97e0a0d98fcec4fda44d741a3..0b7fb9e17a43d2eddd96a55e422626c3bb953056 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -182,7 +182,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
@@ -27643,7 +27643,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
return false;
} else {
this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
@@ -2103,7 +2414,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2102,7 +2413,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
}
@@ -27652,7 +27652,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
output.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
output.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
output.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
@@ -2121,13 +2432,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2120,13 +2431,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Path chunks = rootDir.resolve("chunks.csv");
try (Writer output = Files.newBufferedWriter(chunks)) {
@@ -27668,7 +27668,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
Path entities = rootDir.resolve("entities.csv");
@@ -2222,8 +2533,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2221,8 +2532,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
Locale.ROOT,
"players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s",
this.players.size(),
@@ -27679,7 +27679,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
this.blockEntityTickers.size(),
getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
this.getBlockTicks().count(),
@@ -2256,15 +2567,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2255,15 +2566,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public LevelEntityGetter<Entity> getEntities() {
org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -27708,7 +27708,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
public void startTickingChunk(final LevelChunk levelChunk) {
@@ -2281,8 +2602,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2280,8 +2601,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public void waitForEntities(final ChunkPos centerChunk, final int radius) {
List<ChunkPos> chunks = ChunkPos.rangeClosed(centerChunk, radius).toList();
@@ -27719,7 +27719,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
for (ChunkPos chunk : chunks) {
if (!this.areEntitiesLoaded(chunk.pack())) {
@@ -2303,28 +2624,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2302,28 +2623,38 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public void close() throws IOException {
super.close();
@@ -27764,7 +27764,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
public boolean anyPlayerCloseEnoughForSpawning(final BlockPos pos) {
@@ -2341,7 +2672,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2340,7 +2671,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
public boolean canSpawnEntitiesInChunk(final ChunkPos pos) {
@@ -27776,7 +27776,7 @@ index bc545cb3fc519aa1f5136e420264fea2239fc4c1..3cd2fcc38f3989bb5124ba5bc2f45971
}
@Override
@@ -2389,7 +2723,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2388,7 +2722,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public CrashReportCategory fillReportDetails(final CrashReport report) {
CrashReportCategory category = super.fillReportDetails(report);
WeatherData weatherData = this.getWeatherData();
@@ -28893,7 +28893,7 @@ index 1ceb7e4a3abd4d9de5133d182d3267d2164918f6..eb2fa32cff6824c14f865c8731df7d08
+ // Paper end - block counting
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e3980bced 100644
index 29896cb17fcf8ab967a937e9b0e102a8354b6889..e087d5596979044fe7fbcf7f2cccdae4e81a3d3a 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -167,7 +167,7 @@ public abstract class Entity
@@ -29366,7 +29366,7 @@ index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e
}
public InteractionResult interact(final Player player, final InteractionHand hand, final Vec3 location) {
@@ -4364,15 +4601,17 @@ public abstract class Entity
@@ -4363,15 +4600,17 @@ public abstract class Entity
}
public Iterable<Entity> getIndirectPassengers() {
@@ -29392,7 +29392,7 @@ index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e
}
public int countPlayerPassengers() {
@@ -4683,6 +4922,15 @@ public abstract class Entity
@@ -4682,6 +4921,15 @@ public abstract class Entity
}
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -29408,7 +29408,7 @@ index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e
if (!checkPosition(this, x, y, z)) {
return;
}
@@ -4832,6 +5080,12 @@ public abstract class Entity
@@ -4831,6 +5079,12 @@ public abstract class Entity
@Override
public final void setRemoved(final Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.@Nullable Cause cause) { // CraftBukkit - add Bukkit remove cause
@@ -29421,7 +29421,7 @@ index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e
org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit
final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
if (this.removalReason == null) {
@@ -4842,7 +5096,7 @@ public abstract class Entity
@@ -4841,7 +5095,7 @@ public abstract class Entity
this.stopRiding();
}
@@ -29430,7 +29430,7 @@ index 3d7bc43e3e367a2328601ac6b74f99361b4be57e..f7b4da570ee28b7128b4d2cd1c67b89e
this.levelCallback.onRemove(reason);
this.onRemoval(reason);
// Paper start - Folia schedulers
@@ -4876,7 +5130,7 @@ public abstract class Entity
@@ -4875,7 +5129,7 @@ public abstract class Entity
public boolean shouldBeSaved() {
return (this.removalReason == null || this.removalReason.shouldSave())
&& !this.isPassenger()
@@ -30053,7 +30053,7 @@ index e07a7bda45146686a6ac2d20507df9fc8630514f..f84e651e2a34e76a0a71993332e4be1b
// Paper start - Affects Spawning API
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 81c3f9f04040b6cfa014ce55e517bd9b374ec252..0958fe0438c3e934b885acf78a0f8003ef8bb694 100644
index 30183d8752aa397a5c1fc554da49f595be286534..5566f449fb79b04822e533df4fd6de42116ffd14 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -87,6 +87,7 @@ import net.minecraft.world.level.storage.LevelData;
@@ -33593,7 +33593,7 @@ index cc5d366f7b485f98901572b1aaf57d8b650d521f..7adb4be57bd9fdc9980b5b7e91e9d9ca
private final Long2ObjectLinkedOpenHashMap<CompletableFuture<BitSet>> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>();
private static final int REGION_CACHE_SIZE = 1024;
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index 728ec122b7af090427cc7511a168336d539835a1..5149635679ec03d881e63effbd79fc193d7e52c8 100644
index 9409cc5c8dbd12ce6bdf34843da70170ebce41bd..c9acbdce97cb605a45124bb9d3944070396adc03 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -23,7 +23,7 @@ import net.minecraft.world.level.ChunkPos;
@@ -33625,7 +33625,7 @@ index 728ec122b7af090427cc7511a168336d539835a1..5149635679ec03d881e63effbd79fc19
+ // Paper end - rewrite chunk system
+
public RegionFile(final RegionStorageInfo info, final Path path, final Path externalFileDir, final boolean sync) throws IOException {
this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
this(info, path, externalFileDir, RegionFileVersion.getSelected(), sync);
}
@@ -201,6 +216,15 @@ public class RegionFile implements AutoCloseable {
}
@@ -33491,7 +33491,7 @@ index 109d7b563ee799d998a5773c5a36e61ca3607d46..747755082639436731989d3ee4b3c30a
return structureTemplate.save(new CompoundTag());
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 1b064f00c33712c3d2d85ed8520f1eda02abfbf9..65a971379f35c4b1b519b93787f690d891e29523 100644
index e11862c7e0617acfb36212cfe349bdade3a5164b..7ae2d491a3b2f379df99ccc649b8bb8cbf2304b9 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -299,6 +299,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -462,7 +462,7 @@ index 0df8332933203a904bd9ef9efb3c9bce21e65441..1a502cbd8acea9420fa6dd8d716018b5
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index f7b4da570ee28b7128b4d2cd1c67b89e3980bced..93d3fb313d538cc1067b1db010458169a1320122 100644
index e087d5596979044fe7fbcf7f2cccdae4e81a3d3a..fea6c3b48c4eb162fbdc099fa775bc3161a4dd4e 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -382,6 +382,15 @@ public abstract class Entity
@@ -540,10 +540,10 @@ index c4da41f3b5ba6d8492d3776654b1ce62a5015895..6a0ebbdfcc0bc07915574c1cf87463e8
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
index c7c53ef16e104ebc48aeb8719783fb4c45d39fc2..90eaa775dc134041647618c3b965334a9bb9fd3b 100644
index c7c53ef16e104ebc48aeb8719783fb4c45d39fc2..5bbb5cbb51953399c7bce547cd6968eb3c6bd130 100644
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -211,6 +211,19 @@ public abstract class Mob extends LivingEntity implements Targeting, EquipmentUs
@@ -211,6 +211,20 @@ public abstract class Mob extends LivingEntity implements Targeting, EquipmentUs
return this.lookControl;
}
@@ -551,6 +551,7 @@ index c7c53ef16e104ebc48aeb8719783fb4c45d39fc2..90eaa775dc134041647618c3b965334a
+ @Override
+ public void inactiveTick() {
+ super.inactiveTick();
+ if (!this.aware) return; // Paper - Do not tick AI for inactive unaware mobs
+ if (this.goalSelector.inactiveTick()) {
+ this.goalSelector.tick();
+ }
@@ -673,7 +674,7 @@ index c482c3348267ca2b88109e34291aa860e0cde916..b11b59d80e37d272770ab51a63028fdd
public void tick() {
if (this.getItem().isEmpty()) {
diff --git a/net/minecraft/world/entity/npc/villager/Villager.java b/net/minecraft/world/entity/npc/villager/Villager.java
index a80e6a23cf29cb44943339101f15565dbc19af1f..f481fd661440f77c98940cbde4a8b95818e4a22c 100644
index a80e6a23cf29cb44943339101f15565dbc19af1f..26108ac0543c7b4195f149c07d8348b5b15b8bdb 100644
--- a/net/minecraft/world/entity/npc/villager/Villager.java
+++ b/net/minecraft/world/entity/npc/villager/Villager.java
@@ -244,11 +244,35 @@ public class Villager extends AbstractVillager implements VillagerDataHolder, Re
@@ -687,7 +688,7 @@ index a80e6a23cf29cb44943339101f15565dbc19af1f..f481fd661440f77c98940cbde4a8b958
+ if (this.getUnhappyCounter() > 0) {
+ this.setUnhappyCounter(this.getUnhappyCounter() - 1);
+ }
+ if (this.isEffectiveAi()) {
+ if (this.aware && this.isEffectiveAi()) { // Paper - Do not tick AI for inactive unaware mobs
+ if (this.level().spigotConfig.tickInactiveVillagers) {
+ this.customServerAiStep(this.level().getMinecraftWorld());
+ } else {
@@ -9,7 +9,7 @@ creating too large of a packet to sed.
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
index 2bfc4cb47fc51345fc733b6ab3b19ace4f8bc171..dad2b89355b05900251955d015451fe195bfdb49 100644
index 6ecda939755534ce0f35680be16a04c0a0e0a312..488d5654f5414c9950f19e86a791507366366e01 100644
--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
@@ -32,6 +32,14 @@ public class ClientboundLevelChunkPacketData {
@@ -2326,7 +2326,7 @@ index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 6d847ff3646b27fe9bc75e792d813a7f611ab3e5..cfc30654917703a9278143a2c36d37eb719ee061 100644
index 26c05173e7fb2bf055afda1717af0dd2e997118d..ffe3ae41dd3d8c4141f81edf0b92001db957ebac 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -232,6 +232,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2337,7 +2337,7 @@ index 6d847ff3646b27fe9bc75e792d813a7f611ab3e5..cfc30654917703a9278143a2c36d37eb
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
@@ -2766,6 +2767,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -2765,6 +2766,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return this.debugSynchronizers;
}
@@ -55,7 +55,7 @@ index a6f9435a8e2a6c8cc05bd3873cb6fc0ec98973b8..d44fd9eea0ccb09c894d6431ede8a4c2
this.used.set(position, position + size);
}
diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
index fc712f9dbc893a6dee8ccecc54d0af07659dee62..b4a59b855b45018853e8122a5829a48ebd220a5f 100644
index 5e77906d77da82b23fbd6e8d16fbd0847c83f4da..e077797cefffcb9db627cab12c7d88245e5baa69 100644
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -47,6 +47,363 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
@@ -760,7 +760,7 @@ index 5fa64ff30b8252f139ca7f77c14352c384c8c0e5..eae4963b6d18c5f92871477b93f81c2c
return var4;
diff --git a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
index 97d3da90c270a06d85e70d261658f71d97ae4e3c..11b65c688144e7ef5b7cba603949843a11ccd084 100644
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;
@@ -771,7 +771,7 @@ index 97d3da90c270a06d85e70d261658f71d97ae4e3c..11b65c688144e7ef5b7cba603949843a
+ 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, null, in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out)))
new RegionFileVersion(1, "gzip", in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out))) // Paper - Enable for Configurable region compression format
diff --git a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java b/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
index 590bd1ca60956e5c5be58481f97401e55f9e1aeb..4e0c9fa1eecfa9f21285b7911e25830bbcbc8d86 100644
--- a/net/minecraft/world/level/chunk/storage/RegionStorageInfo.java
@@ -791,7 +791,7 @@ index 590bd1ca60956e5c5be58481f97401e55f9e1aeb..4e0c9fa1eecfa9f21285b7911e25830b
return new RegionStorageInfo(this.level, this.dimension, this.type + suffix);
}
diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
index 22a83f312d357176a5489e5b6b71c3ef3e737df7..74158a5d7ea8058c99ab215265987746aa043fc7 100644
index 1e0afa9590de7fe492cf29aded96be3db498c51e..df0bb06176ad5cce3ed8785d22d68b2dcbf5a8ca 100644
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
@@ -120,6 +120,18 @@ public record SerializableChunkData(
@@ -823,7 +823,7 @@ index 22a83f312d357176a5489e5b6b71c3ef3e737df7..74158a5d7ea8058c99ab215265987746
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 884fa9545a93008fa92863c845aef8f18d88e25a..15da3686bb1b5fddd388a88a7b300aa221c83f58 100644
index 6e55cb304fd9a5a01bbe7666dddb882e684cdcf1..6d954064f409ddd2cb7cfd0d0c428ac186f5ca4d 100644
--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
@@ -34,6 +34,7 @@ public class SimpleRegionStorage implements ca.spottedleaf.moonrise.patches.chun
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba12bb86bad 100644
index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006ac7ab27c7 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -958,7 +958,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -968,7 +968,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 def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba1
boolean result = false;
for (ServerLevel level : this.getAllLevels()) {
@@ -970,13 +970,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -980,13 +980,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
result = true;
}
@@ -32,7 +32,7 @@ index def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba1
if (flush) {
for (ServerLevel level : this.getAllLevels()) {
@@ -990,11 +984,25 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1000,11 +994,25 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return result;
}
@@ -59,7 +59,7 @@ index def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba1
boolean result = this.saveAllChunks(silent, flush, force);
this.warnOnLowDiskSpace();
var5 = result;
@@ -1641,9 +1649,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1651,9 +1659,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
this.ticksUntilAutosave--;
@@ -94,7 +94,7 @@ index def29f67aa4d901fd61cb546f65b763b3da09131..a3edf217dd2ffce19dc1111402975ba1
ProfilerFiller profiler = Profiler.get();
this.server.spark.executeMainThreadTasks(); // Paper - spark
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index cfc30654917703a9278143a2c36d37eb719ee061..78021db10860396fef4eb2e01926fbad123916fa 100644
index ffe3ae41dd3d8c4141f81edf0b92001db957ebac..98461e8468a7eeaef2544ee907ff64de1fd42a49 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1441,6 +1441,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -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 05a700ffc844b41f4fa129226f78dc6c52520f32..1d93a864c5fc040b5ec7c48b4db0bd54cd6255ff 100644
index 0b86c393b041607278c1b712a62ec9ae3640f4de..80162ffef5d54d00663dcb58a087884ec8a3eaaa 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -634,6 +634,7 @@ public class ServerGamePacketListenerImpl
@@ -13,10 +13,10 @@ average over the last 5 seconds of keepalive transactions.
diff --git a/io/papermc/paper/util/KeepAlive.java b/io/papermc/paper/util/KeepAlive.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a2520f554c2ee74faf86d7c93baccf0f391a6b3
index 0000000000000000000000000000000000000000..66d6af2833d76af28fbeb2ce70c194a559572460
--- /dev/null
+++ b/io/papermc/paper/util/KeepAlive.java
@@ -0,0 +1,67 @@
@@ -0,0 +1,84 @@
+package io.papermc.paper.util;
+
+public class KeepAlive {
@@ -34,6 +34,14 @@ index 0000000000000000000000000000000000000000..4a2520f554c2ee74faf86d7c93baccf0
+ public final PingCalculator pingCalculator1m = new PingCalculator(java.util.concurrent.TimeUnit.MINUTES.toNanos(1L));
+ public final PingCalculator pingCalculator5s = new PingCalculator(java.util.concurrent.TimeUnit.SECONDS.toNanos(5L));
+
+ public KeepAlive copyForListenerHandoff() {
+ KeepAlive copy = new KeepAlive();
+ copy.lastKeepAliveTx = this.lastKeepAliveTx;
+ copy.pingCalculator1m.copyFrom(this.pingCalculator1m);
+ copy.pingCalculator5s.copyFrom(this.pingCalculator5s);
+ return copy;
+ }
+
+ public static final class PingCalculator {
+
+ private final long intervalNS;
@@ -47,6 +55,15 @@ index 0000000000000000000000000000000000000000..4a2520f554c2ee74faf86d7c93baccf0
+ this.intervalNS = intervalNS;
+ }
+
+ private void copyFrom(PingCalculator other) {
+ for (KeepAliveResponse response : other.responses) {
+ this.responses.add(response);
+ }
+ this.timeSumNS = other.timeSumNS;
+ this.timeSumCount = other.timeSumCount;
+ this.lastAverageNS = other.lastAverageNS;
+ }
+
+ public void update(KeepAliveResponse response) {
+ long currTime = response.txTimeNS;
+
@@ -100,7 +117,7 @@ index d87a00ee3cd04ce25dbe46ae6b386b7f0799102c..ace440c2b82a6368aa27a85ff433ba2c
}
}
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index f54d99c59bbca7fb5e0615f8fee9c3f6fbffa18a..d9b6f3826e2126d339e926afed782f6d0e890508 100644
index 079ab378920c0e52ef4e42ef20b37bd389f40870..dc9020f210c21a626c948d013cc6c972cbc093f3 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -39,12 +39,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -223,11 +240,19 @@ index f54d99c59bbca7fb5e0615f8fee9c3f6fbffa18a..d9b6f3826e2126d339e926afed782f6d
}
}
@@ -424,6 +457,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@@ -424,6 +457,14 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
}
protected CommonListenerCookie createCookie(final ClientInformation clientInformation) {
- return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand, this.pluginMessagerChannels); // Paper
+ return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand, this.pluginMessagerChannels, this.keepAlive); // Paper // Paper
+ return new CommonListenerCookie(
+ this.playerProfile(),
+ this.latency,
+ clientInformation,
+ this.transferred,
+ this.playerBrand,
+ this.pluginMessagerChannels,
+ this.keepAlive.copyForListenerHandoff()
+ ); // Paper - listener handoff should reset pending keepalive expectations
}
}
@@ -20,10 +20,10 @@ index 2bc436cdf5180a7943c45fabb9fbbedae6f7db56..f312a7f5b1b2a777ab36b94ce7cbf387
@Override
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index a3edf217dd2ffce19dc1111402975ba12bb86bad..2b6717833b56c3e33f3ac656a376dd5c841dc1bc 100644
index 8c4b40362f77cdea6e57315a6639006ac7ab27c7..a5217deb236ee922d020a7a6a03141a5f67acd77 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1784,32 +1784,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1794,32 +1794,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -66,10 +66,10 @@ index a3edf217dd2ffce19dc1111402975ba12bb86bad..2b6717833b56c3e33f3ac656a376dd5c
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 93d3fb313d538cc1067b1db010458169a1320122..9cd46a8619e575a865f4d00ce71d249c9b028b2f 100644
index fea6c3b48c4eb162fbdc099fa775bc3161a4dd4e..82f6582a92f17bdeb3218c4c1fcac86981fd9c37 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5147,6 +5147,11 @@ public abstract class Entity
@@ -5146,6 +5146,11 @@ public abstract class Entity
this.getBukkitEntity().taskScheduler.retire();
}
// Paper end - Folia schedulers
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
+ }
+}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 2b6717833b56c3e33f3ac656a376dd5c841dc1bc..2142757965b7345a011a92de08038473fa8ae320 100644
index a5217deb236ee922d020a7a6a03141a5f67acd77..4fdf7354bc01789cf7480b14c2fc8fe75b748563 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1836,6 +1836,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1846,6 +1846,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
@@ -0,0 +1,119 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 20 Apr 2026 02:56:22 -0700
Subject: [PATCH] Delay open/close callbacks for chests
The logic for chests may invoke block updates, which may not
be safe to perform. For example, inventory closing due to
player logout (which restricts chunk loading due to being
inside an entity status callback) or during shutdown (which has
chunk loading restrictions due to the chunk system being halted).
diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
index 9eb6cb86ff70f04863cae3def1006f82bcb4fa6b..da5fdf77dd26d97c8b92bac9a6af8d922c31eb9c 100644
--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
@@ -66,6 +66,13 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
Objects.requireNonNull(BarrelBlockEntity.this);
}
+ // Paper start - delay open/close callbacks
+ @Override
+ public boolean delayCallbacks() {
+ return true;
+ }
+ // Paper end - delay open/close callbacks
+
@Override
protected void onOpen(final Level level, final BlockPos pos, final BlockState state) {
BarrelBlockEntity.this.playSound(state, SoundEvents.BARREL_OPEN);
diff --git a/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
index 81a293cc13653a9a0336cb25d75273969fee4d16..8ebcce4c4971a11d367fb39e9c7fd280f63594e3 100644
--- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
@@ -35,6 +35,13 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
Objects.requireNonNull(ChestBlockEntity.this);
}
+ // Paper start - delay open/close callbacks
+ @Override
+ public boolean delayCallbacks() {
+ return true;
+ }
+ // Paper end - delay open/close callbacks
+
@Override
protected void onOpen(final Level level, final BlockPos pos, final BlockState blockState) {
if (blockState.getBlock() instanceof ChestBlock chestBlock) {
diff --git a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
index 9af046cbe2c5024040057118a0e9ecd539815781..aa8b384b05738139fa3492b3c09084cda09448ce 100644
--- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
@@ -37,11 +37,23 @@ public abstract class ContainerOpenersCounter {
protected abstract void openerCountChanged(final Level level, final BlockPos pos, final BlockState blockState, int previous, int current);
+ // Paper start - delay open/close callbacks
+ public boolean delayCallbacks() {
+ return false;
+ }
+ // Paper end - delay open/close callbacks
+
public abstract boolean isOwnContainer(final Player player);
public void incrementOpeners(
final LivingEntity entity, final Level level, final BlockPos pos, final BlockState blockState, final double maxInteractionRange
) {
+ // Paper start - delay open/close callbacks
+ if (this.delayCallbacks()) {
+ scheduleRecheck(level, pos, blockState, 1);
+ return;
+ }
+ // Paper end - delay open/close callbacks
int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
int previous = this.openCount++;
@@ -66,6 +78,12 @@ public abstract class ContainerOpenersCounter {
}
public void decrementOpeners(final LivingEntity entity, final Level level, final BlockPos pos, final BlockState blockState) {
+ // Paper start - delay open/close callbacks
+ if (this.delayCallbacks()) {
+ scheduleRecheck(level, pos, blockState, 1);
+ return;
+ }
+ // Paper end - delay open/close callbacks
int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative
int previous = this.openCount--;
@@ -151,6 +169,11 @@ public abstract class ContainerOpenersCounter {
}
private static void scheduleRecheck(final Level level, final BlockPos blockPos, final BlockState blockState) {
- level.scheduleTick(blockPos, blockState.getBlock(), 5);
+ // Paper start - delay open/close callbacks
+ scheduleRecheck(level, blockPos, blockState, 5);
+ }
+ private static void scheduleRecheck(final Level level, final BlockPos blockPos, final BlockState blockState, final int delay) {
+ level.scheduleTick(blockPos, blockState.getBlock(), delay);
+ // Paper end - delay open/close callbacks
}
}
diff --git a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
index eeac644f60760570244380d3c454fd33e37b5a7f..751625124a87de63d933d9c7809e46361e3136a5 100644
--- a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
@@ -18,6 +18,13 @@ public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity
Objects.requireNonNull(EnderChestBlockEntity.this);
}
+ // Paper start - delay open/close callbacks
+ @Override
+ public boolean delayCallbacks() {
+ return true;
+ }
+ // Paper end - delay open/close callbacks
+
@Override
protected void onOpen(final Level level, final BlockPos pos, final BlockState blockState) {
level.playSound(
File diff suppressed because it is too large. Load diff
@@ -14,9 +14,9 @@
+ // Paper start - Validate Identifier
+ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short)
+ final String id = namespace + ":" + path;
+ if (id.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(id) > 2 * Short.MAX_VALUE + 1) {
+ throw new IdentifierException("Identifier too long: " + id);
+ final int length = namespace.length() + path.length() + 1;
+ if (length > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(length) > 2 * Short.MAX_VALUE + 1) {
+ throw new IdentifierException("Identifier too long: " + length);
+ }
+ // Paper end - Validate Identifier
this.namespace = namespace;
@@ -12,7 +12,7 @@
OptionParser parser = new OptionParser();
OptionSpec<Void> nogui = parser.accepts("nogui");
OptionSpec<Void> initSettings = parser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
@@ -89,39 +_,96 @@
@@ -89,39 +_,95 @@
parser.printHelpOn(System.err);
return;
}
@@ -72,15 +72,14 @@
}
- if (!eula.hasAgreedToEULA()) {
+ // Spigot start
+ // Paper start - eula system property
+ boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree");
+ if (eulaAgreed) {
+ LOGGER.error("You have used the Spigot command line EULA agreement flag.");
+ LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
+ LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
+ }
+ if (!eula.hasAgreedToEULA() && !eulaAgreed) {
+ // Spigot end
+ LOGGER.warn("You have used the Paper command line EULA agreement flag.");
+ LOGGER.warn("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
+ LOGGER.warn("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
+ } else if (!eula.hasAgreedToEULA()) {
+ // Paper end - eula system property
LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
return;
}
@@ -300,7 +300,7 @@
if (profiledDuration != null) {
profiledDuration.finish(true);
}
@@ -422,33 +_,140 @@
@@ -422,33 +_,150 @@
}
}
@@ -430,9 +430,9 @@
+ );
+ }
+ this.addLevel(serverLevel);
+ this.initWorld(serverLevel);
+ this.initWorld(serverLevel, null);
+ }
+ public void initWorld(ServerLevel overworld) {
+ public void initWorld(ServerLevel overworld, org.bukkit.@Nullable WorldCreator worldCreator) {
+ final net.minecraft.world.level.storage.ServerLevelData levelData = overworld.serverLevelData;
+ final WorldOptions worldOptions = overworld.worldGenSettings.options();
+ final boolean isDebug = this.worldData.isDebugWorld();
@@ -447,7 +447,17 @@
if (!levelData.isInitialized()) {
try {
- setInitialSpawn(overworld, levelData, worldOptions.generateBonusChest(), isDebug, this.levelLoadListener);
+ setInitialSpawn(overworld, levelData, worldOptions.generateBonusChest(), isDebug, overworld.levelLoadListener); // Paper - per world level load listener
+ // Paper start - Allow direct setting spawn location
+ if (worldCreator != null && worldCreator.forcedSpawnPosition() != null) {
+ levelData.setSpawn(LevelData.RespawnData.of(overworld.dimension(),
+ io.papermc.paper.util.MCUtil.toBlockPos(worldCreator.forcedSpawnPosition()),
+ Objects.requireNonNullElse(worldCreator.forcedSpawnYaw(), LevelData.RespawnData.DEFAULT.yaw()),
+ Objects.requireNonNullElse(worldCreator.forcedSpawnPitch(), LevelData.RespawnData.DEFAULT.pitch())
+ ));
+ } else {
+ setInitialSpawn(overworld, levelData, worldOptions.generateBonusChest(), isDebug, overworld.levelLoadListener); // Paper - per world level load listener
+ }
+ // Paper end - Allow direct setting spawn location
levelData.setInitialized(true);
if (isDebug) {
- this.setupDebugLevel(this.worldData);
@@ -493,7 +503,7 @@
+
+ if (spawn != null) {
+ if (spawn.getWorld() != level.getWorld()) {
+ throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + io.papermc.paper.util.MCUtil.getLevelName(level) + ")");
+ throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + (spawn.getWorld() != null ? spawn.getWorld().key() : null) + ")");
+ } else {
+ levelData.setSpawn(
+ net.minecraft.world.level.storage.LevelData.RespawnData.of(
@@ -5,7 +5,7 @@
list.deop(player);
count++;
- source.sendSuccess(() -> Component.translatable("commands.deop.success", players.iterator().next().name()), true);
+ source.sendSuccess(() -> Component.translatable("commands.deop.success", player.name()), true); // Paper - fixes MC-253721
+ source.sendSuccess(() -> Component.translatable("commands.deop.success", player.name()), true); // Paper - fixes MC-307513
}
}
@@ -987,16 +987,15 @@
this.getServer().getDataStorage().set(MapItemSavedData.type(id), data);
}
@@ -1492,7 +_,21 @@
@@ -1492,7 +_,20 @@
@Override
public void setRespawnData(final LevelData.RespawnData respawnData) {
- this.getServer().setRespawnData(respawnData);
+ // Paper start
+ final LevelData.RespawnData previousRespawnData = this.serverLevelData.getRespawnData();
+ this.serverLevelData.setSpawn(respawnData);
+ if (!previousRespawnData.positionEquals(respawnData)) {
+ if (!this.serverLevelData.getRespawnData().positionEquals(respawnData)) {
+ org.bukkit.Location previousLocation = this.getWorld().getSpawnLocation();
+ this.serverLevelData.setSpawn(respawnData);
+ this.server.getPlayerList().broadcastAll(new net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket(respawnData), this.dimension());
+ this.server.updateEffectiveRespawnData();
+ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), previousLocation).callEvent();
@@ -243,7 +243,7 @@
+ if (!this.cserver.isPrimaryThread()) {
+ org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() {
+ @Override
+ protected Object evaluate() {
+ protected @Nullable Object evaluate() {
+ ServerCommonPacketListenerImpl.this.disconnect(details);
+ return null;
+ }
@@ -285,7 +285,7 @@
+ }
+
+ reason = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason());
+ leaveMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.leaveMessage());
+ leaveMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.leaveMessage());
+ serverGamePacketListener.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason
+ // Log kick to console *after* event was processed.
+ switch (cause) {
@@ -1,6 +1,14 @@
--- a/net/minecraft/world/damagesource/CombatTracker.java
+++ b/net/minecraft/world/damagesource/CombatTracker.java
@@ -29,15 +_,24 @@
@@ -22,22 +_,40 @@
private static final Style INTENTIONAL_GAME_DESIGN_STYLE = Style.EMPTY
.withClickEvent(new ClickEvent.OpenUrl(CommonLinks.INTENTIONAL_GAME_DESIGN_BUG))
.withHoverEvent(new HoverEvent.ShowText(Component.literal("MCPE-28723")));
- public final List<CombatEntry> entries = Lists.newArrayList();
+ public final List<CombatEntry> entries = new net.minecraft.util.ArrayListDeque<>(); // Paper - Fix mem leak (MC-301114); Use ArrayListDeque
public final LivingEntity mob;
private int lastDamageTime;
private int combatStartTime;
private int combatEndTime;
public boolean inCombat;
public boolean takingDamage;
@@ -22,6 +30,15 @@
+ public void recordDamageAndCheckCombatState(final CombatEntry entry) {
+ final DamageSource source = entry.source();
+ // Paper end - Combat tracker API
+ // Paper start - Fix mem leak (MC-301114)
+ final io.papermc.paper.configuration.type.number.IntOr.Disabled maxTrackingCombatEntries;
+ if (!this.entries.isEmpty() && (maxTrackingCombatEntries = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxTrackingCombatEntries).enabled()) {
+ final int maxEntries = maxTrackingCombatEntries.intValue();
+ while (this.entries.size() > maxEntries) {
+ this.entries.removeFirst();
+ }
+ }
+ // Paper end - Fix mem leak (MC-301114)
this.entries.add(entry);
this.lastDamageTime = this.mob.tickCount;
this.takingDamage = true;
@@ -1355,7 +1355,7 @@
this.setTicksFrozen(0);
}
@@ -2762,11 +_,43 @@
@@ -2762,11 +_,42 @@
public void thunderHit(final ServerLevel level, final LightningBolt lightningBolt) {
this.setRemainingFireTicks(this.remainingFireTicks + 1);
@@ -1383,11 +1383,10 @@
+ }
+
+ // CraftBukkit start
+ final DamageSource damageSource = this.damageSources().lightningBolt().eventEntityDamager(lightningBolt);
+ if (thisBukkitEntity instanceof org.bukkit.entity.Hanging) {
+ org.bukkit.event.hanging.HangingBreakByEntityEvent hangingEvent = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) thisBukkitEntity, stormBukkitEntity);
+ pluginManager.callEvent(hangingEvent);
+
+ if (hangingEvent.isCancelled()) {
+ org.bukkit.event.hanging.HangingBreakByEntityEvent hangingEvent = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) thisBukkitEntity, stormBukkitEntity, new org.bukkit.craftbukkit.damage.CraftDamageSource(damageSource));
+ if (!hangingEvent.callEvent()) {
+ return;
+ }
+ }
@@ -1396,7 +1395,7 @@
+ return;
+ }
+
+ if (!this.hurtServer(level, this.damageSources().lightningBolt().eventEntityDamager(lightningBolt), 5.0F)) { // Paper - fix DamageSource API
+ if (!this.hurtServer(level, damageSource, 5.0F)) { // Paper - fix DamageSource API
+ return;
+ }
+ // CraftBukkit end
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/net/minecraft/world/entity/animal/goat/Goat.java
@@ -213,13 +_,22 @@
@@ -213,8 +_,16 @@
public InteractionResult mobInteract(final Player player, final InteractionHand hand) {
ItemStack heldItem = player.getItemInHand(hand);
if (heldItem.is(Items.BUCKET) && !this.isBaby()) {
@@ -18,13 +18,6 @@
player.setItemInHand(hand, bucketOrMilkBucket);
return InteractionResult.SUCCESS;
} else {
+ boolean isFood = this.isFood(heldItem); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739)
InteractionResult interactionResult = super.mobInteract(player, hand);
- if (interactionResult.consumesAction() && this.isFood(heldItem)) {
+ if (interactionResult.consumesAction() && isFood) { // Paper
this.playEatingSound();
}
@@ -328,8 +_,7 @@
double deltaY = Mth.randomBetween(this.random, 0.3F, 0.7F);
double deltaZ = Mth.randomBetween(this.random, -0.2F, 0.2F);
@@ -1,6 +1,15 @@
--- a/net/minecraft/world/entity/animal/happyghast/HappyGhast.java
+++ b/net/minecraft/world/entity/animal/happyghast/HappyGhast.java
@@ -314,8 +_,12 @@
@@ -300,7 +_,7 @@
@Override
protected void addPassenger(final Entity passenger) {
if (!this.isVehicle()) {
- this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_DOWN, this.getSoundSource(), 1.0F, 1.0F);
+ this.playSound(SoundEvents.HARNESS_GOGGLES_DOWN, 1.0F, 1.0F); // Paper - don't play sound if silenced (Fixes MC-297169)
}
super.addPassenger(passenger);
@@ -314,16 +_,21 @@
}
@Override
@@ -15,9 +24,11 @@
if (!this.level().isClientSide()) {
this.setServerStillTimeout(10);
}
@@ -324,6 +_,7 @@
if (!this.isVehicle()) {
this.clearHome();
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_UP, this.getSoundSource(), 1.0F, 1.0F);
- this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_UP, this.getSoundSource(), 1.0F, 1.0F);
+ this.playSound(SoundEvents.HARNESS_GOGGLES_UP, 1.0F, 1.0F); // Paper - don't play sound if silenced (Fixes MC-297169)
}
+ return true; // Paper - cancellable passengers
}
@@ -43,15 +43,15 @@
} else {
if (!this.isRemoved()) {
+ // CraftBukkit start - fire break events
+ Entity damager = (!source.isDirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API
+ org.bukkit.event.hanging.HangingBreakEvent event;
+ Entity damager = source.getDirectEntity();
+ final org.bukkit.event.hanging.HangingBreakEvent event;
+ if (damager != null) {
+ event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.ENTITY);
+ event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), new org.bukkit.craftbukkit.damage.CraftDamageSource(source), source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.ENTITY);
+ } else {
+ event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.DEFAULT);
+ }
+
+ this.level().getCraftServer().getPluginManager().callEvent(event);
+ event.callEvent();
+
+ if (this.isRemoved() || event.isCancelled()) {
+ return true;
@@ -8,7 +8,7 @@
+ this.splash(hitResult);
+ }
+
+ public void splash(final HitResult hitResult) {
+ public void splash(final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper
+ // Paper end - More projectile API
if (this.level() instanceof ServerLevel level) {
ItemStack potionItemStack = this.getItem();
@@ -37,7 +37,7 @@
- private void onHitAsWater(final ServerLevel level) {
+ private static final Predicate<LivingEntity> APPLY_WATER_GET_ENTITIES_PREDICATE = AbstractThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events
+
+ private boolean onHitAsWater(final ServerLevel level, final HitResult hitResult) { // Paper - Fix potions splash events
+ private boolean onHitAsWater(final ServerLevel level, final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper - Fix potions splash events
AABB aabb = this.getBoundingBox().inflate(4.0, 2.0, 4.0);
- for (LivingEntity entity : this.level().getEntitiesOfClass(LivingEntity.class, aabb, WATER_SENSITIVE_OR_ON_FIRE)) {
@@ -88,7 +88,7 @@
}
- protected abstract void onHitAsPotion(ServerLevel level, ItemStack potionItem, HitResult hitResult);
+ protected abstract boolean onHitAsPotion(ServerLevel level, ItemStack potionItem, HitResult hitResult); // Paper - Fix potions splash events & More Projectile API
+ protected abstract boolean onHitAsPotion(ServerLevel level, ItemStack potionItem, @org.jspecify.annotations.Nullable HitResult hitResult); // Paper - Fix potions splash events & More Projectile API
private void dowseFire(final BlockPos pos) {
BlockState blockState = this.level().getBlockState(pos);
@@ -5,7 +5,7 @@
@Override
- public void onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) {
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) { // Paper - More projectile API
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper - More projectile API
AreaEffectCloud cloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
if (this.getOwner() instanceof LivingEntity owner) {
cloud.setOwner(owner);
@@ -5,11 +5,12 @@
@Override
- public void onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) {
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final HitResult hitResult) { // Paper - More projectile API
+ public boolean onHitAsPotion(final ServerLevel level, final ItemStack potionItem, final @org.jspecify.annotations.Nullable HitResult hitResult) { // Paper - More projectile API
PotionContents contents = potionItem.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
float durationScale = potionItem.getOrDefault(DataComponents.POTION_DURATION_SCALE, 1.0F);
Iterable<MobEffectInstance> mobEffects = contents.getAllEffects();
AABB potionAabb = this.getBoundingBox().move(hitResult.getLocation().subtract(this.position()));
- AABB potionAabb = this.getBoundingBox().move(hitResult.getLocation().subtract(this.position()));
+ AABB potionAabb = hitResult == null ? this.getBoundingBox() : this.getBoundingBox().move(hitResult.getLocation().subtract(this.position())); // Paper - More projectile API
AABB effectAabb = potionAabb.inflate(4.0, 2.0, 4.0);
List<LivingEntity> entities = this.level().getEntitiesOfClass(LivingEntity.class, effectAabb);
+ java.util.Map<org.bukkit.entity.LivingEntity, Double> affected = new java.util.HashMap<>(); // CraftBukkit
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/entity/vehicle/VehicleEntity.java
+++ b/net/minecraft/world/entity/vehicle/VehicleEntity.java
@@ -32,12 +_,20 @@
@@ -32,12 +_,22 @@
}
@Override
@@ -13,9 +13,11 @@
} else {
+ // CraftBukkit start
+ org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
+ org.bukkit.entity.Entity attacker = (source.getEntity() == null) ? null : source.getEntity().getBukkitEntity();
+
+ org.bukkit.event.vehicle.VehicleDamageEvent event = new org.bukkit.event.vehicle.VehicleDamageEvent(vehicle, attacker, damage);
+ org.bukkit.craftbukkit.damage.CraftDamageSource damageSourceBukkit = new org.bukkit.craftbukkit.damage.CraftDamageSource(source);
+ org.bukkit.entity.Entity attacker = net.minecraft.Optionull.map(
+ source.eventEntityDamager() != null ? source.eventEntityDamager() : source.getDirectEntity(), Entity::getBukkitEntity
+ );
+ org.bukkit.event.vehicle.VehicleDamageEvent event = new org.bukkit.event.vehicle.VehicleDamageEvent(vehicle, damageSourceBukkit, attacker, damage);
+ if (!event.callEvent()) return false;
+ damage = (float) event.getDamage();
+ // CraftBukkit end
@@ -28,7 +30,7 @@
if (creativePlayer) {
- this.discard();
+ // CraftBukkit start
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, attacker);
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, damageSourceBukkit, attacker);
+ if (!destroyEvent.callEvent()) {
+ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
+ return true;
@@ -38,7 +40,7 @@
}
} else {
+ // CraftBukkit start
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, attacker);
+ org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, damageSourceBukkit, attacker);
+ if (!destroyEvent.callEvent()) {
+ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
+ return true;
@@ -90,7 +90,7 @@
+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(bukkitName, CraftNamespacedKey.fromMinecraft(dimension.identifier())); // Spigot
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.generator = generator;
+ this.world = new CraftWorld((ServerLevel) this, generator, biomeProvider, environment);
+ this.world = new CraftWorld((ServerLevel) this, dimension.identifier(), biomeProvider, environment);
+
+ for (SpawnCategory spawnCategory : SpawnCategory.values()) {
+ if (org.bukkit.craftbukkit.util.CraftSpawnCategory.isValidForLimits(spawnCategory)) {
@@ -1,5 +1,14 @@
--- a/net/minecraft/world/level/block/ComparatorBlock.java
+++ b/net/minecraft/world/level/block/ComparatorBlock.java
@@ -137,7 +_,7 @@
float pitch = state.getValue(MODE) == ComparatorMode.SUBTRACT ? 0.55F : 0.5F;
level.playSound(player, pos, SoundEvents.COMPARATOR_CLICK, SoundSource.BLOCKS, 0.3F, pitch);
level.setBlock(pos, state, Block.UPDATE_CLIENTS);
- this.refreshOutputState(level, pos, state);
+ if (level.getBlockState(pos).is(this)) { this.refreshOutputState(level, pos, state); } // Paper - Fix blockstate validation issues
return InteractionResult.SUCCESS;
}
}
@@ -168,8 +_,18 @@
boolean sourceOn = this.shouldTurnOn(level, pos, state);
boolean isOn = state.getValue(POWERED);
@@ -19,3 +19,11 @@
level.setBlock(pos, state.setValue(POWERED, true), Block.UPDATE_CLIENTS);
if (!shouldTurnOn) {
level.scheduleTick(pos, this, this.getDelay(state), TickPriority.VERY_HIGH);
@@ -85,6 +_,7 @@
protected void neighborChanged(
final BlockState state, final Level level, final BlockPos pos, final Block block, final @Nullable Orientation orientation, final boolean movedByPiston
) {
+ if (!level.getBlockState(pos).is(this)) return; // Paper - Fix blockstate validation issues
if (state.canSurvive(level, pos)) {
this.checkTickOnNeighbor(level, pos, state);
} else {
@@ -17,7 +17,7 @@
}
}
@@ -60,7 +_,7 @@
@@ -60,12 +_,17 @@
}
}
@@ -26,3 +26,13 @@
}
private boolean slightlyMelt(final BlockState state, final Level level, final BlockPos pos) {
int age = state.getValue(AGE);
if (age < 3) {
+ // Paper start - FrostedIce BlockFadeEvent
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, state.setValue(AGE, age + 1)).isCancelled()) {
+ return false;
+ }
+ // Paper end - FrostedIce BlockFadeEvent
level.setBlock(pos, state.setValue(AGE, age + 1), Block.UPDATE_CLIENTS);
return false;
} else {
@@ -8,7 +8,7 @@
public BeehiveBlockEntity(final BlockPos worldPosition, final BlockState blockState) {
super(BlockEntityType.BEEHIVE, worldPosition, blockState);
@@ -119,7 +_,7 @@
@@ -119,16 +_,17 @@
}
public boolean isFull() {
@@ -17,7 +17,9 @@
}
public void emptyAllLivingFromHive(final @Nullable Player player, final BlockState state, final BeehiveBlockEntity.BeeReleaseStatus releaseReason) {
@@ -128,7 +_,7 @@
+ if (this.level == null) return; // Paper - do not fire from worldgen thread
List<Entity> releasedFromHive = this.releaseAllOccupants(state, releaseReason);
if (player != null) {
for (Entity released : releasedFromHive) {
if (released instanceof Bee bee && player.position().distanceToSqr(released.position()) <= 16.0) {
if (!this.isSedated()) {
@@ -62,11 +62,24 @@
if (this.openCount == 0) {
this.onClose(level, pos, blockState);
level.gameEvent(entity, GameEvent.CONTAINER_CLOSE, pos);
@@ -74,6 +_,7 @@
@@ -74,8 +_,20 @@
}
int openCount = containerUsers.size();
+ if (this.opened) openCount++; // CraftBukkit - add dummy count from API
int prevCount = this.openCount;
if (prevCount != openCount) {
+ // CraftBukkit start - Call redstone event
+ if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
+ int oldPower = net.minecraft.util.Mth.clamp(prevCount, 0, 15);
+ int newPower = net.minecraft.util.Mth.clamp(openCount, 0, 15);
+
+ if (oldPower != newPower) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
+ }
+ }
+ // CraftBukkit end
+
boolean isOpen = openCount != 0;
boolean wasOpen = prevCount != 0;
if (isOpen && !wasOpen) {
@@ -1,14 +1,5 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -47,7 +_,7 @@
protected final RegionBitmap usedSectors = new RegionBitmap();
public RegionFile(final RegionStorageInfo info, final Path path, final Path externalFileDir, final boolean sync) throws IOException {
- this(info, path, externalFileDir, RegionFileVersion.getSelected(), sync);
+ this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
}
public RegionFile(final RegionStorageInfo info, final Path path, final Path externalFileDir, final RegionFileVersion version, final boolean sync) throws IOException {
@@ -83,6 +_,14 @@
if (offset != 0) {
int sectorNumber = getSectorNumber(offset);
@@ -1,19 +1,11 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -49,6 +_,16 @@
private final RegionFileVersion.StreamWrapper<InputStream> inputWrapper;
private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
+ // Paper start - Configurable region compression format
+ public static RegionFileVersion getCompressionFormat() {
+ return switch (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.compressionFormat) {
+ case GZIP -> VERSION_GZIP;
+ case ZLIB -> VERSION_DEFLATE;
+ case LZ4 -> VERSION_LZ4;
+ case NONE -> VERSION_NONE;
+ };
+ }
+ // Paper end - Configurable region compression format
private RegionFileVersion(
final int id,
final @Nullable String optionName,
@@ -24,7 +_,7 @@
private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
public static final RegionFileVersion VERSION_GZIP = register(
- new RegionFileVersion(1, null, in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out)))
+ new RegionFileVersion(1, "gzip", in -> new FastBufferedInputStream(new GZIPInputStream(in)), out -> new BufferedOutputStream(new GZIPOutputStream(out))) // Paper - Enable for Configurable region compression format
);
public static final RegionFileVersion VERSION_DEFLATE = register(
new RegionFileVersion(
@@ -5,7 +5,7 @@
if (spawnEnemies) {
if (level.getGameRules().get(GameRules.SPAWN_PHANTOMS)) {
+ // Paper start - Ability to control player's insomnia and phantoms
+ if (level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) {
+ if (level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0 || level.paperConfig().entities.behavior.playerInsomniaStartTicks < 0) {
+ return;
+ }
+ // Paper end - Ability to control player's insomnia and phantoms
@@ -25,6 +25,15 @@
BlockPos playerPos = player.blockPosition();
if (!level.dimensionType().hasSkyLight() || playerPos.getY() >= level.getSeaLevel() && level.canSeeSky(playerPos)) {
DifficultyInstance difficulty = level.getCurrentDifficultyAt(playerPos);
@@ -39,7 +_,7 @@
ServerStatsCounter stats = player.getStats();
int value = Mth.clamp(stats.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
int dayLength = 24000;
- if (random.nextInt(value) >= 72000) {
+ if (random.nextInt(value) >= level.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms
BlockPos spawnPos = playerPos.above(20 + random.nextInt(15))
.east(-10 + random.nextInt(21))
.south(-10 + random.nextInt(21));
@@ -50,11 +_,21 @@
int groupSize = 1 + random.nextInt(difficulty.getDifficulty().getId() + 1);
@@ -15,7 +15,7 @@
.mapOrElse(
Function.identity(),
error -> {
@@ -174,9 +_,9 @@
@@ -174,13 +_,13 @@
}
public static <T extends SavedData> DataResult<T> readExistingSavedData(
@@ -27,6 +27,11 @@
CompoundTag fileContents;
try {
- fileContents = NbtIo.readCompressed(dataLocation, NbtAccounter.defaultQuota());
+ fileContents = NbtIo.readCompressed(dataLocation, NbtAccounter.create(net.minecraft.nbt.NbtAccounter.DEFAULT_NBT_QUOTA * 50L)); // Paper - raise quota to account for custom data
} catch (IOException var6) {
return DataResult.error(var6::getMessage);
}
@@ -396,7 +_,7 @@
public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(final String levelId) throws IOException, ContentValidationException {
@@ -22,6 +22,9 @@ import org.bukkit.GameRule;
import org.bukkit.block.Biome;
import org.bukkit.craftbukkit.CraftGameRule;
import org.bukkit.craftbukkit.block.CraftBiome;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import org.bukkit.craftbukkit.damage.CraftDamageEffect;
import org.bukkit.craftbukkit.damage.CraftDamageSource;
import org.bukkit.craftbukkit.entity.CraftLivingEntity;
@@ -62,6 +65,11 @@ public class PaperServerInternalAPIBridge implements InternalAPIBridge {
);
}
@Override
public PoiType.Occupancy createOccupancy(final String enumNameEntry) {
return new PaperPoiType.PaperOccupancy(PoiManager.Occupancy.valueOf(enumNameEntry));
}
@Override
public CombatEntry createCombatEntry(
final DamageSource damageSource,
@@ -94,8 +94,9 @@ public final class EntityCommand implements PaperSubcommand {
return;
}
Map<Identifier, MutablePair<Integer, Map<ChunkPos, Integer>>> list = Maps.newHashMap();
@Nullable World bukkitWorld = Bukkit.getWorld(NamespacedKey.fromString(worldName));
if (bukkitWorld == null) {
@Nullable NamespacedKey worldKey = NamespacedKey.fromString(worldName);
@Nullable World bukkitWorld;
if (worldKey == null || (bukkitWorld = Bukkit.getWorld(worldKey)) == null) {
sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED));
sender.sendMessage(text("Usage: /paper entity list [filter] [worldKey]", RED));
return;
@@ -8,6 +8,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.ToIntFunction;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.JoinConfiguration;
@@ -91,7 +92,7 @@ public final class MobcapsCommand implements PaperSubcommand {
if (sender instanceof Player player) {
worlds = List.of(player.getWorld());
} else {
sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED));
sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps overworld'", NamedTextColor.RED));
return;
}
} else if (args.length == 1) {
@@ -99,8 +100,9 @@ public final class MobcapsCommand implements PaperSubcommand {
if (input.equals("*")) {
worlds = Bukkit.getWorlds();
} else {
final @Nullable World world = Bukkit.getWorld(input);
if (world == null) {
final @Nullable Key worldKey = NamespacedKey.fromString(input);
final @Nullable World world;
if (worldKey == null || (world = Bukkit.getWorld(worldKey)) == null) {
sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED));
return;
} else {
@@ -1,7 +1,6 @@
package io.papermc.paper.configuration;
import com.mojang.logging.LogUtils;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.configuration.constraint.Constraints;
import io.papermc.paper.configuration.serializer.collection.map.WriteKeyBack;
import io.papermc.paper.configuration.type.number.DoubleOr;
@@ -183,19 +182,10 @@ public class GlobalConfiguration extends ConfigurationPart {
public boolean allowHeadlessPistons = false;
@Comment("This setting controls if the vanilla damage tick should be skipped if damage was blocked via a shield.")
public boolean skipVanillaDamageTickWhenShieldBlocked = false;
@Comment("This setting controls what compression format is used for region files.")
public CompressionFormat compressionFormat = CompressionFormat.ZLIB;
@Comment("This setting controls if equipment should be updated when handling certain player actions.")
public boolean updateEquipmentOnPlayerActions = true;
@Comment("This setting controls what item data components don't need to be sanitized in oversized item obfuscation. Adding them re-enables exploits, but may be needed for certain resource packs. (Expected: minecraft:container, minecraft:charged_projectiles and minecraft:bundle_contents)")
public OversizedItemComponentSanitizer.AssetOversizedItemComponentSanitizerConfiguration oversizedItemComponentSanitizer = new OversizedItemComponentSanitizer.AssetOversizedItemComponentSanitizerConfiguration(Set.of());
public enum CompressionFormat {
GZIP,
ZLIB,
LZ4,
NONE
}
}
public Commands commands;
@@ -344,6 +334,8 @@ public class GlobalConfiguration extends ConfigurationPart {
public boolean enableNether = true;
@Comment("Keeps Paper's fix for MC-159283 enabled. Disable to use vanilla End ring terrain.")
public boolean fixFarEndTerrainGeneration = true;
@Comment("Fix for MC-301114. This removes the oldest combat entry when it hits the cap, to fix a memory leak on constant entity damage.")
public IntOr.Disabled maxTrackingCombatEntries = new IntOr.Disabled(OptionalInt.of(10240));
}
public BlockUpdates blockUpdates;
@@ -51,6 +51,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -150,7 +151,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
""";
@VisibleForTesting
public static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255), Key.key(RandomStringUtils.randomAlphabetic(255).toLowerCase())) {
public static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255), Key.key(RandomStringUtils.randomAlphabetic(255).toLowerCase(Locale.ROOT))) {
@Override // override to ensure "verbose" is false
public void init() {
SpigotConfig.readConfig(SpigotWorldConfig.class, this);
@@ -86,6 +86,7 @@ interface RemovedConfigurations {
path("misc", "fix-entity-position-desync"),
path("chunk-system", "gen-parallelism"),
path("logging"), // server is no longer obfuscated since 26.1
path("unsupported-settings", "compression-format"),
};
}
@@ -0,0 +1,20 @@
package io.papermc.paper.entity.poi;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record PaperPoiSearchResult(PoiType poiType, Location location) implements PoiSearchResult {
@Override
public Location location() {
return location.clone();
}
public static PoiSearchResult from(PoiRecord record, World world) {
return new PaperPoiSearchResult(PaperPoiType.minecraftHolderToBukkit(record.getPoiType()), CraftLocation.toBukkit(record.getPos(), world));
}
}
@@ -0,0 +1,50 @@
package io.papermc.paper.entity.poi;
import io.papermc.paper.registry.HolderableBase;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PaperPoiType extends HolderableBase<net.minecraft.world.entity.ai.village.poi.PoiType> implements PoiType {
public static PaperPoiType minecraftHolderToBukkit(final Holder<net.minecraft.world.entity.ai.village.poi.PoiType> minecraft) {
return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.POINT_OF_INTEREST_TYPE);
}
public static Holder<net.minecraft.world.entity.ai.village.poi.PoiType> bukkitToMinecraftHolder(final PoiType bukkit) {
return CraftRegistry.bukkitToMinecraftHolder(bukkit);
}
public PaperPoiType(final Holder<net.minecraft.world.entity.ai.village.poi.PoiType> holder) {
super(holder);
}
@Override
public boolean is(final BlockData data) {
return this.getHandle().is(((CraftBlockData) data).getState());
}
@Override
public boolean hasOccupants() {
return this.getHandle().maxTickets() != 0;
}
public record PaperOccupancy(PoiManager.Occupancy handle) implements Occupancy {
public static PoiManager.Occupancy bukkitToMinecraft(final Occupancy occupancy) {
return ((PaperOccupancy) occupancy).handle();
}
public static Occupancy minecraftToBukkit(final PoiManager.Occupancy occupancy) {
return switch (occupancy) {
case ANY -> Occupancy.ANY;
case HAS_SPACE -> Occupancy.HAS_SPACE;
case IS_OCCUPIED -> Occupancy.IS_OCCUPIED;
};
}
}
}
@@ -6,6 +6,8 @@ import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.PaperDataComponentType;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.PaperDialog;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.registry.data.PaperBannerPatternRegistryEntry;
import io.papermc.paper.registry.data.PaperCatTypeRegistryEntry;
import io.papermc.paper.registry.data.PaperChickenVariantRegistryEntry;
@@ -121,6 +123,7 @@ public final class PaperRegistries {
start(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT).craft(Sound.class, CraftSound::new, true).create(PaperSoundEventRegistryEntry.PaperBuilder::new, RegistryEntryMeta.RegistryModificationApiSupport.NONE),
start(Registries.DATA_COMPONENT_TYPE, RegistryKey.DATA_COMPONENT_TYPE).craft(DataComponentTypes.class, PaperDataComponentType::of).build(),
start(Registries.GAME_RULE, RegistryKey.GAME_RULE).craft(GameRule.class, CraftGameRule::new).build(),
start(Registries.POINT_OF_INTEREST_TYPE, RegistryKey.POINT_OF_INTEREST_TYPE).craft(PoiTypes.class, PaperPoiType::new).build(),
// data-driven
start(Registries.BIOME, RegistryKey.BIOME).craft(Biome.class, CraftBiome::new).build().delayed(),
@@ -145,6 +145,37 @@ public final class EntityScheduler {
}
}
/**
* Attempts to invoke the callback immediately if the current thread owns the Entity and the Entity is not retired,
* returning {@code null} when it happens. If the current thread neither owns the Entity nor the Entity is retired,
* then this function has the same effect as invoking {@link #schedule(Consumer, Consumer, long)} with no retired
* callback and a delay of {@code 1L}.
*
* @param run The callback to run, may not be null.
* @return {@code Boolean.TRUE} if the task was scheduled, which means that either the run function or the retired function
* will be invoked (but never both), or {@code Boolean.FALSE} indicating neither the run nor retired function will be invoked
* since the scheduler has been retired. Returns {@code null} if the callback was executed immediately.
*/
public Boolean scheduleOrExecute(final Consumer<? extends Entity> run) {
Objects.requireNonNull(run, "Run task may not be null");
final Entity handle = this.entity.getHandleRaw();
if (!TickThread.isTickThreadFor(handle)) {
return Boolean.valueOf(this.schedule(run, null, 1L));
}
synchronized (this.stateLock) {
if (this.tickCount == RETIRED_TICK_COUNT) {
return Boolean.FALSE;
}
}
((Consumer<Entity>)run).accept(handle);
return null;
}
/**
* Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity
* removed), then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay,
@@ -0,0 +1,34 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public interface BlockPlacementPredictor {
Optional<BlockState> getLatestBlockAt(BlockPos pos);
Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos);
Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos);
record BlockEntityPlacement(boolean removed, @Nullable BlockEntity blockEntity) {
public static final Optional<BlockEntityPlacement> ABSENT = Optional.of(new BlockEntityPlacement(false, null));
public @Nullable BlockEntity res() {
return this.removed ? null : this.blockEntity;
}
}
record LoadedBlockState(boolean present, @Nullable BlockState state) {
public static final Optional<LoadedBlockState> UNLOADED = Optional.of(new LoadedBlockState(false, null));
public @Nullable BlockState res() {
return this.present ? this.state : null;
}
}
}
@@ -0,0 +1,142 @@
package io.papermc.paper.util.capture;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.block.CraftBlockStates;
import org.jspecify.annotations.Nullable;
/*
The premise of this storage is to essentially "mock" block placement in the game.
This should attempt to mirror block placement the best it can, in order to provide a read solution of possibly what blocks
are going to be placed in the world.
The reason why we use a custom capture map rather than just storing the blocks placed in the world is to ensure
that blocks are placed in the correct order, and that the vanilla block chain can occur, which includes physics updates.
*/
public final class CaptureRecordMap {
private final Map<BlockPos, CaptureRecord> recordsByPos = new HashMap<>();
public void setLatestBlockStateAt(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
this.add(new CaptureRecord(pos, blockState, updateFlags));
}
public void setLatestBlockEntityAt(BlockPos pos, boolean clearPreviousBlockEntity, @Nullable BlockEntity add) {
CaptureRecord oldRecord = this.recordsByPos.get(pos);
if (oldRecord != null) {
oldRecord.setBlockEntity(clearPreviousBlockEntity, add);
}
}
private void add(CaptureRecord record) {
this.recordsByPos.put(record.pos, record);
}
public boolean isEmpty() {
return this.recordsByPos.isEmpty();
}
public @Nullable BlockState getLatestBlockStateAt(BlockPos pos) {
CaptureRecord record = this.recordsByPos.get(pos);
if (record == null) {
return null;
}
return record.blockState;
}
// Null indicates that it's not present, no override
// Optional empty indicates its being removed
public @Nullable Optional<BlockEntity> getLatestBlockEntityAt(BlockPos pos) {
CaptureRecord record = this.recordsByPos.get(pos);
if (record == null) {
return null;
}
return Optional.ofNullable(record.blockEntity);
}
public void applyBlockEntities(ServerLevel parent) {
this.recordsByPos.keySet().forEach((pos) -> {
Optional<BlockEntity> res = this.getLatestBlockEntityAt(pos);
if (res != null && res.isPresent()) {
parent.setBlockEntity(res.get());
}
});
}
public void applyApiPatch(ServerLevel level) {
this.recordsByPos.keySet().forEach((pos) -> {
this.recordsByPos.get(pos).applyApiPatch(level);
});
}
// TODO: Clean this up
public List<org.bukkit.block.BlockState> calculateLatestSnapshots(ServerLevel level) {
List<org.bukkit.block.BlockState> out = new ArrayList<>();
for (Map.Entry<BlockPos, CaptureRecord> entry : this.recordsByPos.entrySet()) {
CaptureRecord captureRecord = entry.getValue();
out.add(CraftBlockStates.getBlockState(level.getWorld(), entry.getKey(), captureRecord.blockState, captureRecord.blockEntity));
}
return out;
}
public Stream<org.bukkit.block.Block> getAffectedBlocks(ServerLevel level) {
return this.recordsByPos.keySet().stream().map(pos -> CraftBlock.at(level, pos));
}
public static class CaptureRecord {
private final BlockPos pos;
private BlockState blockState;
private @Nullable BlockEntity blockEntity;
private boolean clearPreviousBlockEntity;
private @Block.UpdateFlags int updateFlags;
public CaptureRecord(BlockPos pos, BlockState blockState, BlockEntity blockEntity) {
this.pos = pos;
this.blockState = blockState;
this.blockEntity = blockEntity;
}
public CaptureRecord(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
this.pos = pos;
this.blockState = blockState;
this.updateFlags = updateFlags;
}
public CaptureRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) {
this.clearPreviousBlockEntity = remove;
this.blockEntity = add;
this.pos = pos;
}
public void applyApiPatch(ServerLevel level) {
if (this.clearPreviousBlockEntity) {
level.removeBlockEntity(this.pos);
}
level.setBlock(this.pos, this.blockState, this.updateFlags);
if (this.blockEntity != null) {
level.setBlockEntity(this.blockEntity);
}
}
public void setBlockEntity(boolean clearPreviousBlockEntity, @Nullable BlockEntity add) {
this.clearPreviousBlockEntity = clearPreviousBlockEntity;
this.blockEntity = add;
}
}
}
@@ -0,0 +1,100 @@
package io.papermc.paper.util.capture;
import net.minecraft.Optionull;
import net.minecraft.core.Holder;
import net.minecraft.data.worldgen.features.TreeFeatures;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import org.bukkit.TreeType;
import org.jspecify.annotations.Nullable;
public final class GrowthContext {
public static GrowthContext empty() {
return new GrowthContext(null, false);
}
public static GrowthContext usingBoneMeal(@Nullable Player user) {
return new GrowthContext(user, true);
}
private final @Nullable Player player;
private final boolean usedBoneMeal;
public boolean cancelled = false;
public Holder<ConfiguredFeature<?, ?>> feature; // just make this a field
private GrowthContext(@Nullable Player player, boolean usedBoneMeal) {
this.player = player;
this.usedBoneMeal = usedBoneMeal;
}
public org.bukkit.entity.@Nullable Player getBukkitPlayer() {
return (org.bukkit.entity.Player) Optionull.map(this.player, Entity::getBukkitEntity);
}
public boolean usedBoneMeal() {
return this.usedBoneMeal;
}
public TreeType getTreeSpecies() {
return getTreeSpecies(this.feature);
}
private static TreeType getTreeSpecies(Holder<ConfiguredFeature<?, ?>> feature) {
if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) {
return TreeType.RED_MUSHROOM;
} else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) {
return TreeType.BROWN_MUSHROOM;
} else if (feature.is(TreeFeatures.WARPED_FUNGUS_PLANTED)) {
return TreeType.WARPED_FUNGUS;
} else if (feature.is(TreeFeatures.CRIMSON_FUNGUS_PLANTED)) {
return TreeType.CRIMSON_FUNGUS;
} else if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) {
return TreeType.TREE;
} else if (feature.is(TreeFeatures.JUNGLE_TREE)) {
return TreeType.COCOA_TREE;
} else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) {
return TreeType.SMALL_JUNGLE;
} else if (feature.is(TreeFeatures.PINE)) {
return TreeType.TALL_REDWOOD;
} else if (feature.is(TreeFeatures.SPRUCE)) {
return TreeType.REDWOOD;
} else if (feature.is(TreeFeatures.ACACIA)) {
return TreeType.ACACIA;
} else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) {
return TreeType.BIRCH;
} else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) {
return TreeType.TALL_BIRCH;
} else if (feature.is(TreeFeatures.SWAMP_OAK)) {
return TreeType.SWAMP;
} else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) {
return TreeType.BIG_TREE;
} else if (feature.is(TreeFeatures.JUNGLE_BUSH)) {
return TreeType.JUNGLE_BUSH;
} else if (feature.is(TreeFeatures.DARK_OAK)) {
return TreeType.DARK_OAK;
} else if (feature.is(TreeFeatures.MEGA_SPRUCE)) {
return TreeType.MEGA_REDWOOD;
} else if (feature.is(TreeFeatures.MEGA_PINE)) {
return TreeType.MEGA_PINE;
} else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) {
return TreeType.JUNGLE;
} else if (feature.is(TreeFeatures.AZALEA_TREE)) {
return TreeType.AZALEA;
} else if (feature.is(TreeFeatures.MANGROVE)) {
return TreeType.MANGROVE;
} else if (feature.is(TreeFeatures.TALL_MANGROVE)) {
return TreeType.TALL_MANGROVE;
} else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) {
return TreeType.CHERRY;
} else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) {
return TreeType.PALE_OAK;
} else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) {
return TreeType.PALE_OAK_CREAKING;
} else {
throw new IllegalArgumentException("Unknown tree feature: " + feature);
}
}
}
@@ -0,0 +1,46 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
public record LayeredBlockPlacementPredictor(
BlockPlacementPredictor... predictors
) implements BlockPlacementPredictor {
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
for (BlockPlacementPredictor predictor : this.predictors) {
Optional<BlockState> state = predictor.getLatestBlockAt(pos);
if (state.isPresent()) {
return state;
}
}
return Optional.empty();
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
for (BlockPlacementPredictor predictor : this.predictors) {
Optional<LoadedBlockState> state = predictor.getLatestBlockAtIfLoaded(pos);
if (state.isPresent()) {
return state;
}
}
return Optional.empty();
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
for (BlockPlacementPredictor predictor : this.predictors) {
Optional<BlockEntityPlacement> state = predictor.getLatestBlockEntityAt(pos);
if (state.isPresent()) {
return state;
}
}
return Optional.empty();
}
}
@@ -0,0 +1,46 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
public record LiveBlockPlacementLayer(WorldCapturer capturer, ServerLevel level) implements BlockPlacementPredictor {
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
return Optional.of(provideLive(() -> this.level.getBlockState(pos)));
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
BlockState state = provideLive(() -> this.level.getBlockStateIfLoaded(pos));
if (state == null) {
return LoadedBlockState.UNLOADED;
}
return Optional.of(new LoadedBlockState(true, state));
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
BlockEntity blockEntity = provideLive(() -> this.level.getBlockEntity(pos));
if (blockEntity == null) {
return BlockEntityPlacement.ABSENT;
}
return Optional.of(new BlockEntityPlacement(false, blockEntity));
}
public <T> @Nullable T provideLive(Supplier<@Nullable T> valueProvider) {
SimpleBlockCapture blockCapture = this.capturer.getCapture();
this.capturer.releaseCapture(null);
T value = valueProvider.get();
this.capturer.releaseCapture(blockCapture);
return value;
}
}
@@ -0,0 +1,448 @@
package io.papermc.paper.util.capture;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.attribute.EnvironmentAttributeReader;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gamerules.GameRules;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraft.world.ticks.ScheduledTick;
import org.jspecify.annotations.Nullable;
public class MinecraftCaptureBridge implements PaperCapturingWorldLevel {
private final ServerLevel parent;
private final List<Runnable> queuedTasks = new ArrayList<>();
// Effective represents plugin set blocks -> predicted blocks -> actual server level
private final BlockPlacementPredictor effectiveReadLayer;
private SimpleBlockPlacementPredictor writeLayer;
// This is the layer that is written to in the server level potentially.
// Mostly plugin set blocks
private final SimpleBlockPlacementPredictor serverLevelOverlayLayer;
private final CapturingTickAccess<Block> blocks;
private final CapturingTickAccess<Fluid> liquids;
private Consumer<Runnable> sink = this.queuedTasks::add;
public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor baseReadLayer) {
this.parent = parent;
this.serverLevelOverlayLayer = new SimpleBlockPlacementPredictor();
SimpleBlockPlacementPredictor predictedBlocks = new SimpleBlockPlacementPredictor();
this.effectiveReadLayer = new LayeredBlockPlacementPredictor(
this.serverLevelOverlayLayer, // The overlay layer represents plugin set blocks!
predictedBlocks, // Now predicted blocks
baseReadLayer
);
this.writeLayer = predictedBlocks; // Predicting
this.blocks = new CapturingTickAccess<>(parent.getBlockTicks());
this.liquids = new CapturingTickAccess<>(parent.getFluidTicks());
}
@Override
public long getSeed() {
return this.parent.getSeed();
}
@Override
public ServerLevel getLevel() {
return this.parent;
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) {
return this.parent.getCurrentDifficultyAt(pos);
}
@Override
public long nextSubTickCount() {
return this.parent.nextSubTickCount();
}
@Override
public LevelData getLevelData() {
return this.parent.getLevelData();
}
@Override
public @Nullable MinecraftServer getServer() {
return this.parent.getServer();
}
@Override
public ServerChunkCache getChunkSource() {
return this.parent.getChunkSource();
}
@Override
public boolean setBlockAndUpdate(BlockPos pos, BlockState blockState) {
return this.setBlock(pos, blockState, Block.UPDATE_ALL);
}
@Override
public SimpleBlockCapture forkCaptureSession() {
return this.parent.capturer.createCaptureSession(new BlockPlacementPredictor() {
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAt(pos);
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos);
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockEntityAt(pos);
}
});
}
@Override
public RandomSource getRandom() {
// TODO: Support rolling this back?
return this.parent.getRandom();
}
@Override
public void playSound(@Nullable Entity except, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) {
this.addTask((level) -> level.playSound(except, pos, sound, source, volume, pitch));
}
@Override
public void addParticle(ParticleOptions particle, double x, double y, double z, double xd, double yd, double zd) {
this.addTask((level) -> level.addParticle(particle, x, y, z, xd, yd, zd));
}
@Override
public void levelEvent(@Nullable Entity source, int type, BlockPos pos, int data) {
this.addTask((level) -> level.levelEvent(source, type, pos, data));
}
@Override
public void gameEvent(Holder<GameEvent> gameEvent, Vec3 position, GameEvent.Context context) {
this.addTask((level) -> level.gameEvent(gameEvent, position, context));
}
@Override
public LevelLightEngine getLightEngine() {
return this.parent.getLightEngine();
}
@Override
public WorldBorder getWorldBorder() {
return this.parent.getWorldBorder();
}
@Override
public @Nullable BlockEntity getBlockEntity(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockEntityAt(pos)
.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity)
.orElse(null);
}
@Override
public BlockState getBlockState(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockAt(pos).orElseThrow(); // Should not ever be null, parent should pass value
}
@Override
public @Nullable BlockState getBlockStateIfLoaded(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos).map(BlockPlacementPredictor.LoadedBlockState::state).orElse(null);
}
@Override
public @Nullable FluidState getFluidIfLoaded(BlockPos pos) {
return this.getBlockStateIfLoaded(pos).getFluidState();
}
@Override
public FluidState getFluidState(BlockPos pos) {
return this.getBlockState(pos).getFluidState();
}
@Override
public List<Entity> getEntities(@Nullable Entity except, AABB bb, Predicate<? super Entity> selector) {
return this.parent.getEntities(except, bb, selector);
}
@Override
public <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> entityTypeTest, AABB bb, Predicate<? super T> selector) {
return this.parent.getEntities(entityTypeTest, bb, selector);
}
@Override
public List<? extends Player> players() {
return this.parent.players();
}
@Override
public @Nullable ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status, boolean loadOrGenerate) {
return this.parent.getChunk(chunkX, chunkZ, status, loadOrGenerate);
}
@Override
public @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
return this.parent.getChunkIfLoadedImmediately(x, z);
}
@Override
public int getHeight(Heightmap.Types type, int x, int z) {
return this.parent.getHeight(type, x, z); // TODO?
}
@Override
public int getSkyDarken() {
return this.parent.getSkyDarken();
}
@Override
public BiomeManager getBiomeManager() {
return this.parent.getBiomeManager();
}
@Override
public Holder<Biome> getUncachedNoiseBiome(int quartX, int quartY, int quartZ) {
return this.parent.getUncachedNoiseBiome(quartX, quartY, quartZ);
}
@Override
public boolean isClientSide() {
return this.parent.isClientSide();
}
@Override
public int getSeaLevel() {
return this.parent.getSeaLevel();
}
@Override
public DimensionType dimensionType() {
return this.parent.dimensionType();
}
@Override
public RegistryAccess registryAccess() {
return this.parent.registryAccess();
}
@Override
public FeatureFlagSet enabledFeatures() {
return this.parent.enabledFeatures();
}
@Override
public EnvironmentAttributeReader environmentAttributes() {
return this.parent.environmentAttributes();
}
@Override
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> predicate) {
return predicate.test(this.getBlockState(pos));
}
@Override
public boolean isFluidAtPosition(BlockPos pos, Predicate<FluidState> predicate) {
return predicate.test(this.getFluidState(pos));
}
public boolean silentSet(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
return this.writeLayer.setBlockState(this.effectiveReadLayer, pos, blockState, updateFlags);
}
@Override
public boolean setBlock(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.setBlock(copy, blockState, updateFlags, updateLimit));
return this.writeLayer.setBlockState(this.effectiveReadLayer, copy, blockState, updateFlags);
}
@Override
public boolean removeBlock(BlockPos pos, boolean movedByPiston) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.removeBlock(copy, movedByPiston));
FluidState fluidState = this.getFluidState(copy);
return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL | (movedByPiston ? Block.UPDATE_MOVE_BY_PISTON : 0));
}
@Override
public boolean destroyBlock(BlockPos pos, boolean dropResources, @Nullable Entity breaker, int updateLimit) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.destroyBlock(copy, dropResources, breaker, updateLimit));
BlockState blockState = this.getBlockState(copy);
if (blockState.isAir()) {
return false;
} else {
FluidState fluidState = this.getFluidState(copy);
return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL);
}
}
@Override
public void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags) {
BlockPos copy = pos.immutable();
this.addTask((level) -> level.sendBlockUpdated(copy, old, current, updateFlags));
}
@Override
public void setBlockEntity(BlockEntity blockEntity) {
this.writeLayer.getRecordMap().setLatestBlockEntityAt(blockEntity.getBlockPos().immutable(), false, blockEntity);
}
@Override
public boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
BlockPos copy = pos.immutable();
return this.silentSet(copy, blockState, updateFlags);
}
@Override
public LevelTickAccess<Block> getBlockTicks() {
return this.blocks;
}
@Override
public LevelTickAccess<Fluid> getFluidTicks() {
return this.liquids;
}
@Override
public GameRules getGameRules() {
return this.parent.getGameRules();
}
@Override
public void addTask(Consumer<ServerLevel> level) {
this.sink.accept(() -> level.accept(this.parent));
}
public @Nullable BlockState getLatestBlockState(BlockPos pos) {
return this.effectiveReadLayer.getLatestBlockAt(pos).orElse(null);
}
public @Nullable Optional<BlockEntity> getLatestBlockEntity(BlockPos pos) {
Optional<BlockPlacementPredictor.BlockEntityPlacement> placement = this.effectiveReadLayer.getLatestBlockEntityAt(pos);
if (placement.isEmpty() || (placement.get().blockEntity() == null && !placement.get().removed())) {
return null;
}
return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity);
}
public List<org.bukkit.block.BlockState> calculateLatestSnapshots(ServerLevel level) {
return this.writeLayer.getRecordMap().calculateLatestSnapshots(level);
}
public Stream<org.bukkit.block.Block> getAffectedBlocks(ServerLevel level) {
return this.writeLayer.getRecordMap().getAffectedBlocks(level);
}
public void applyTasks() {
this.sink = Runnable::run;
for (Runnable runnable : this.queuedTasks) {
runnable.run();
}
this.blocks.apply();
this.liquids.apply();
// If we have changes that the plugin applied on top of the already existing changes, we know that we can apply them.
// So, do that!
if (!this.serverLevelOverlayLayer.isEmpty()) {
this.serverLevelOverlayLayer.getRecordMap().applyApiPatch(this.parent);
}
// Apply block entities, those may have been written in our estimation. So just apply them.
// I don't really like this, as I in general don't really want to apply things from the prediction layer.
// But, these may be mutated by anything.
if (!this.writeLayer.getRecordMap().isEmpty()) {
this.writeLayer.getRecordMap().applyBlockEntities(this.parent);
}
}
public void allowWriteOnLevel() {
this.writeLayer = this.serverLevelOverlayLayer;
}
public static class CapturingTickAccess<T> implements LevelTickAccess<T> {
private final LevelTickAccess<T> wrapped;
private final Set<BlockPos> scheduled = new HashSet<>();
private final List<ScheduledTick<T>> ticks = new ArrayList<>();
public CapturingTickAccess(LevelTickAccess<T> wrapped) {
this.wrapped = wrapped;
}
@Override
public boolean willTickThisTick(BlockPos pos, T type) {
return this.wrapped.willTickThisTick(pos, type);
}
@Override
public void schedule(ScheduledTick<T> tick) {
this.scheduled.add(tick.pos());
this.ticks.add(tick);
}
@Override
public boolean hasScheduledTick(BlockPos pos, T type) {
return this.wrapped.hasScheduledTick(pos, type) || this.scheduled.contains(pos);
}
@Override
public int count() {
return this.wrapped.count() + this.scheduled.size();
}
public void apply() {
this.ticks.forEach(this.wrapped::schedule);
}
}
}
@@ -0,0 +1,30 @@
package io.papermc.paper.util.capture;
import java.util.function.Consumer;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gamerules.GameRules;
public interface PaperCapturingWorldLevel extends WorldGenLevel {
GameRules getGameRules();
void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags);
void setBlockEntity(BlockEntity blockEntity);
boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit);
ServerChunkCache getChunkSource();
boolean setBlockAndUpdate(BlockPos pos, BlockState blockState);
void addTask(Consumer<ServerLevel> level);
SimpleBlockCapture forkCaptureSession();
}
@@ -0,0 +1,47 @@
package io.papermc.paper.util.capture;
import java.util.function.Consumer;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gamerules.GameRules;
public interface ServerLevelPaperCapturingWorldLevel extends PaperCapturingWorldLevel {
@Override
default GameRules getGameRules() {
return this.getLevel().getGameRules();
}
@Override
default void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags) {
this.getLevel().sendBlockUpdated(pos, old, current, updateFlags);
}
@Override
default void setBlockEntity(BlockEntity blockEntity) {
this.getLevel().setBlockEntity(blockEntity);
}
@Override
default boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
return this.getLevel().setBlock(pos, blockState, updateFlags, updateLimit);
}
@Override
default boolean setBlockAndUpdate(BlockPos pos, BlockState blockState) {
return this.getLevel().setBlockAndUpdate(pos, blockState);
}
@Override
default void addTask(Consumer<ServerLevel> level) {
level.accept(this.getLevel());
}
@Override
default SimpleBlockCapture forkCaptureSession() {
return this.getLevel().capturer.createCaptureSession();
}
}
@@ -0,0 +1,74 @@
package io.papermc.paper.util.capture;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.jspecify.annotations.Nullable;
public class SimpleBlockCapture implements AutoCloseable {
private final MinecraftCaptureBridge capturingWorldLevel;
private final ServerLevel level;
private final @Nullable SimpleBlockCapture oldCapture;
private boolean isOverlayingCaptureOnLevel = false;
public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel level, @Nullable SimpleBlockCapture oldCapture) {
this.capturingWorldLevel = new MinecraftCaptureBridge(level, base);
this.level = level;
this.oldCapture = oldCapture;
}
public MinecraftCaptureBridge capturingWorldLevel() {
return this.capturingWorldLevel;
}
public boolean isCapturing() {
return true;
}
public List<BlockState> getCapturedSnapshots() {
return this.capturingWorldLevel.calculateLatestSnapshots(this.level);
}
public Stream<Block> getAffectedBlocks() {
return this.capturingWorldLevel.getAffectedBlocks(this.level);
}
public net.minecraft.world.level.block.state.@Nullable BlockState getOverlayBlockState(BlockPos pos) {
return this.capturingWorldLevel.getLatestBlockState(pos);
}
public @Nullable Optional<BlockEntity> getOverlayBlockEntity(BlockPos pos) {
return this.capturingWorldLevel.getLatestBlockEntity(pos);
}
// This is done so that the captured blocks appear ontop of the world.
public void overlayCaptureOnLevel() {
this.isOverlayingCaptureOnLevel = true;
this.capturingWorldLevel.allowWriteOnLevel();
}
public boolean isOverlayingCaptureOnLevel() {
return this.isOverlayingCaptureOnLevel;
}
public void finalizePlacement() {
this.level.capturer.releaseCapture(this.oldCapture);
this.capturingWorldLevel.applyTasks();
}
@Override
public void close() {
this.level.capturer.releaseCapture(this.oldCapture);
}
public net.minecraft.world.level.block.state.@Nullable BlockState getCaptureBlockStateIfLoaded(BlockPos pos) {
return this.capturingWorldLevel.getBlockStateIfLoaded(pos);
}
}
@@ -0,0 +1,109 @@
package io.papermc.paper.util.capture;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
// Block state
class SimpleBlockPlacementPredictor implements BlockPlacementPredictor {
private final CaptureRecordMap guesstimationMap = new CaptureRecordMap();
public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
BlockState oldState = layer.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState());
// Don't do any processing if the same
if (oldState == blockState) {
return false;
} else {
Block newBlock = blockState.getBlock();
this.setLatestBlockAt(pos, blockState, updateFlags);
// TODO: local heightmaps?
// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, blockState);
// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, blockState);
// this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(i, y, i2, blockState);
// this.heightmaps.get(Heightmap.Types.WORLD_SURFACE).update(i, y, i2, blockState);
// LIGHT ENGINE CALCULATIONS
boolean blockChanged = !oldState.is(newBlock);
if (blockChanged && oldState.hasBlockEntity() && !blockState.shouldChangedStateKeepBlockEntity(oldState)) {
this.removeBlockEntity(pos);
}
// if ((blockChanged || newBlock instanceof BaseRailBlock) && ((updateFlags & Block.UPDATE_NEIGHBORS) != 0 || updateMoveByPiston)) {
// BlockState finalOldState = oldState;
// this.capturingWorldLevel.addTask((level) -> {
// finalOldState.affectNeighborsAfterRemoval(level, pos, updateMoveByPiston);
// });
// }
if (blockState.hasBlockEntity()) {
BlockEntity blockEntity = this.getLatestBlockEntityAt(pos).map(BlockEntityPlacement::blockEntity).orElse(null);
if (blockEntity != null && !blockEntity.isValidBlockState(blockState)) {
blockEntity = null;
}
if (blockEntity == null) {
blockEntity = ((EntityBlock) newBlock).newBlockEntity(pos, blockState);
if (blockEntity != null) {
this.addAndRegisterBlockEntity(blockEntity);
}
} else {
blockEntity.setBlockState(blockState);
}
}
}
return true;
}
private void addAndRegisterBlockEntity(BlockEntity blockEntity) {
this.guesstimationMap.setLatestBlockEntityAt(blockEntity.getBlockPos(), false, blockEntity);
}
private void removeBlockEntity(BlockPos pos) {
this.guesstimationMap.setLatestBlockEntityAt(pos, true, null);
}
public boolean isEmpty() {
return this.guesstimationMap.isEmpty();
}
@Override
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
Optional<BlockEntity> value = this.guesstimationMap.getLatestBlockEntityAt(pos);
if (value == null) {
return Optional.empty();
} else {
return value
.map(block -> new BlockEntityPlacement(false, block))
.or(() -> BlockEntityPlacement.ABSENT);
}
}
@Override
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos));
}
@Override
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos))
.map((state) -> new LoadedBlockState(true, state));
}
public void setLatestBlockAt(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
this.guesstimationMap.setLatestBlockStateAt(pos, blockState, updateFlags);
}
public CaptureRecordMap getRecordMap() {
return this.guesstimationMap;
}
}
@@ -0,0 +1,38 @@
package io.papermc.paper.util.capture;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.Nullable;
// TODO: Cleanup this state, because its held on the server world. I had proper state handling but threw it away at some point
public class WorldCapturer {
public final ServerLevel level;
private @Nullable SimpleBlockCapture capture;
public WorldCapturer(Level level) {
this.level = (ServerLevel) level;
}
public SimpleBlockCapture createCaptureSession(BlockPlacementPredictor blockPlacementPredictor) {
this.capture = new SimpleBlockCapture(blockPlacementPredictor, this.level, this.capture);
return this.capture;
}
public SimpleBlockCapture createCaptureSession() {
return this.createCaptureSession(new LiveBlockPlacementLayer(this, this.level));
}
public void releaseCapture(@Nullable SimpleBlockCapture oldCapture) {
this.capture = oldCapture;
}
public @Nullable SimpleBlockCapture getCapture() {
return this.capture;
}
public boolean isCapturing() {
return this.capture != null;
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.util.capture;
import org.jspecify.annotations.NullMarked;
@@ -1303,7 +1303,7 @@ public final class CraftServer implements Server {
}
this.console.addLevel(serverLevel);
this.console.initWorld(serverLevel);
this.console.initWorld(serverLevel, creator);
serverLevel.setSpawnSettings(true);
@@ -7,12 +7,18 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.entity.poi.PaperPoiSearchResult;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiSearchResult;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.raytracing.BlockCollisionMode;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilderImpl;
import io.papermc.paper.raytracing.RayTraceTarget;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.util.capture.MinecraftCaptureBridge;
import io.papermc.paper.util.capture.SimpleBlockCapture;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -61,6 +67,7 @@ import net.minecraft.world.attribute.EnvironmentAttributes;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.raid.Raids;
@@ -103,7 +110,6 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.boss.DragonBattle;
import org.bukkit.craftbukkit.block.CraftBiome;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.block.CraftBlockState;
import org.bukkit.craftbukkit.block.CraftBlockType;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.boss.CraftDragonBattle;
@@ -123,6 +129,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.CraftRayTraceResult;
import org.bukkit.craftbukkit.util.CraftSpawnCategory;
import org.bukkit.craftbukkit.util.CraftStructureSearchResult;
import org.bukkit.craftbukkit.util.RandomSourceWrapper;
import org.bukkit.entity.AbstractArrow;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
@@ -170,16 +177,16 @@ public class CraftWorld extends CraftRegionAccessor implements World {
.resolving(net.kyori.adventure.identity.Identity.UUID, World::getUID)
.build();
private final ServerLevel world;
private WorldBorder worldBorder;
private Environment environment;
private final CraftServer server = (CraftServer) Bukkit.getServer();
private final @Nullable ChunkGenerator generator;
private final @Nullable BiomeProvider biomeProvider;
private final List<BlockPopulator> populators = new ArrayList<>();
private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this);
private final Object2IntOpenHashMap<SpawnCategory> spawnCategoryLimit = new Object2IntOpenHashMap<>();
private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY);
private final ServerLevel world;
private final NamespacedKey key;
private final Environment environment;
private WorldBorder worldBorder;
// Paper start - void damage configuration
private boolean voidDamageEnabled;
private float voidDamageAmount;
@@ -296,9 +303,9 @@ public class CraftWorld extends CraftRegionAccessor implements World {
private static final Random rand = new Random();
public CraftWorld(ServerLevel world, @Nullable ChunkGenerator generator, @Nullable BiomeProvider biomeProvider, Environment environment) {
public CraftWorld(ServerLevel world, Identifier identifier, @Nullable BiomeProvider biomeProvider, Environment environment) {
this.world = world;
this.generator = generator;
this.key = CraftNamespacedKey.fromMinecraft(identifier);
this.biomeProvider = biomeProvider;
this.environment = environment;
@@ -337,7 +344,11 @@ public class CraftWorld extends CraftRegionAccessor implements World {
private boolean setSpawnLocation(int x, int y, int z, float yaw, float pitch) {
try {
this.world.setRespawnData(LevelData.RespawnData.of(this.world.dimension(), new BlockPos(x, y, z), yaw, pitch));
Location previousLocation = this.getSpawnLocation();
this.world.serverLevelData.setSpawn(LevelData.RespawnData.of(this.world.dimension(), new BlockPos(x, y, z), yaw, pitch));
this.server.getServer().updateEffectiveRespawnData();
new SpawnChangeEvent(this, previousLocation).callEvent();
return true;
} catch (Exception e) {
return false;
@@ -735,26 +746,20 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
this.world.captureTreeGeneration = true;
this.world.captureBlockStates = true;
boolean grownTree = this.generateTree(loc, type);
this.world.captureBlockStates = false;
this.world.captureTreeGeneration = false;
if (grownTree) { // Copy block data to delegate
for (BlockState blockstate : this.world.capturedBlockStates.values()) {
BlockPos position = ((CraftBlockState) blockstate).getPosition();
net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position);
int flags = ((CraftBlockState) blockstate).getFlags();
delegate.setBlockData(blockstate.getX(), blockstate.getY(), blockstate.getZ(), blockstate.getBlockData());
net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position);
this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flags, net.minecraft.world.level.block.Block.UPDATE_LIMIT);
}
this.world.capturedBlockStates.clear();
return true;
} else {
this.world.capturedBlockStates.clear();
return false;
}
try (SimpleBlockCapture capture = this.world.forkCaptureSession()) {
MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel();
BlockPos pos = CraftLocation.toBlockPos(loc);
boolean generated = this.generateTree(captureTreeGeneration, this.getHandle().getMinecraftWorld().getChunkSource().getGenerator(), pos, new RandomSourceWrapper(CraftWorld.rand), type);
if (generated) {
List<BlockState> blocks = captureTreeGeneration.calculateLatestSnapshots(this.world);
for (BlockState state : blocks) {
delegate.setBlockData(state.getX(), state.getY(), state.getZ(), state.getBlockData());
}
}
return generated;
}
}
@Override
@@ -769,7 +774,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public NamespacedKey getKey() {
return CraftNamespacedKey.fromMinecraft(this.world.dimension().identifier());
return this.key;
}
@Override
@@ -869,7 +874,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public @Nullable ChunkGenerator getGenerator() {
return this.generator;
return this.world.generator;
}
@Override
@@ -1355,7 +1360,9 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean isNatural() {
throw new UnsupportedOperationException("// TODO - snapshot");
return this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.CREAKING_ACTIVE)
&& this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.EYEBLOSSOM_OPEN).toBoolean(true)
&& this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.NETHER_PORTAL_SPAWNS_PIGLINS);
}
@Override
@@ -1392,8 +1399,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean isUltraWarm() {
return this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.WATER_EVAPORATES)
&& this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.FAST_LAVA)
&& this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.DEFAULT_DRIPSTONE_PARTICLE).equals(ParticleTypes.DRIPPING_DRIPSTONE_LAVA);
&& this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.FAST_LAVA);
}
@Override
@@ -1873,6 +1879,40 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return new CraftBiomeSearchResult(CraftBiome.minecraftHolderToBukkit(found.getSecond()), CraftLocation.toBukkit(found.getFirst(), this));
}
@Override
public Location locateNearestPoi(@NotNull final Location origin, @NotNull final PoiType poiType, final int radius, final PoiType.@NotNull Occupancy occupancy) {
Preconditions.checkArgument(origin != null, "Location cannot be null");
Preconditions.checkArgument(this.equals(origin.getWorld()), "The provided location must be in the same world");
Preconditions.checkArgument(poiType != null, "PoiType cannot be null");
Preconditions.checkArgument(radius > 0, "The provided radius must be greater than 0");
Preconditions.checkArgument(occupancy != null, "Occupancy cannot be null");
final Holder<net.minecraft.world.entity.ai.village.poi.PoiType> nms = PaperPoiType.bukkitToMinecraftHolder(poiType);
final PoiManager.Occupancy nmsOccupancy = PaperPoiType.PaperOccupancy.bukkitToMinecraft(occupancy);
final BlockPos sourcePos = CraftLocation.toBlockPos(origin);
return this.getHandle().getPoiManager().findClosestWithType(holder -> holder.is(nms), sourcePos, radius, nmsOccupancy)
.map(found -> CraftLocation.toBukkit(found.getSecond(), this))
.orElse(null);
}
@Override
public @NotNull List<PoiSearchResult> locateAllPoiInRange(@NotNull final Location origin, @NotNull final Predicate<PoiType> poiTypePredicate, final int radius, final PoiType.@NotNull Occupancy occupancy) {
Preconditions.checkArgument(origin != null, "Location cannot be null");
Preconditions.checkArgument(origin.getWorld().equals(this), "The provided location must be in the same world");
Preconditions.checkArgument(poiTypePredicate != null, "The predicate filter must not be null");
Preconditions.checkArgument(radius > 0, "The provided radius must be greater than 0");
Preconditions.checkArgument(occupancy != null, "Occupancy cannot be null");
final Predicate<Holder<net.minecraft.world.entity.ai.village.poi.PoiType>> predicate = type -> poiTypePredicate.test(PaperPoiType.minecraftHolderToBukkit(type));
final PoiManager.Occupancy nmsOccupancy = PaperPoiType.PaperOccupancy.bukkitToMinecraft(occupancy);
final BlockPos sourcePos = CraftLocation.toBlockPos(origin);
return this.getHandle().getPoiManager().getInRange(predicate, sourcePos, radius, nmsOccupancy)
.map(record -> PaperPoiSearchResult.from(record, this))
.toList();
}
@Override
public Raid locateNearestRaid(Location location, int radius) {
Preconditions.checkArgument(location != null, "Location cannot be null");
@@ -1,80 +0,0 @@
package org.bukkit.craftbukkit.block;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
@Deprecated(forRemoval = true)
public final class CapturedBlockState extends CraftBlockState {
private final boolean treeBlock;
public CapturedBlockState(Block block, @net.minecraft.world.level.block.Block.UpdateFlags int capturedFlags, boolean treeBlock) {
super(block, capturedFlags);
this.treeBlock = treeBlock;
}
private CapturedBlockState(CapturedBlockState state, Location location) {
super(state, location);
this.treeBlock = state.treeBlock;
}
@Override
public boolean update(boolean force, boolean applyPhysics) {
boolean result = super.update(force, applyPhysics);
if (result) {
this.addBees();
}
return result;
}
@Override
public boolean place(@net.minecraft.world.level.block.Block.UpdateFlags int flags) {
boolean result = super.place(flags);
this.addBees();
return result;
}
private void addBees() {
// SPIGOT-5537: Horrible hack to manually add bees given Level#captureTreeGeneration does not support block entities
if (this.treeBlock && this.getType() == Material.BEE_NEST) {
WorldGenLevel worldGenLevel = this.world.getHandle();
BlockPos pos = this.getPosition();
RandomSource randomSource = worldGenLevel.getRandom();
// Begin copied block from BeehiveDecorator
worldGenLevel.getBlockEntity(pos, BlockEntityType.BEEHIVE).ifPresent(beehiveBlockEntity -> {
int i1 = 2 + randomSource.nextInt(2);
for (int i2 = 0; i2 < i1; i2++) {
beehiveBlockEntity.storeBee(BeehiveBlockEntity.Occupant.create(randomSource.nextInt(599)));
}
});
// End copied block
}
}
@Override
public CapturedBlockState copy() {
return new CapturedBlockState(this, null);
}
@Override
public CapturedBlockState copy(Location location) {
return new CapturedBlockState(this, location);
}
public static CapturedBlockState getTreeBlockState(Level world, BlockPos pos, @net.minecraft.world.level.block.Block.UpdateFlags int flags) {
return new CapturedBlockState(CraftBlock.at(world, pos), flags, true);
}
}
@@ -521,41 +521,10 @@ public class CraftBlock implements Block {
Direction direction = blockFaceToNotch(face);
Preconditions.checkArgument(direction != null, face + " is not a valid cartesian face");
BlockFertilizeEvent event = null;
ServerLevel world = this.getCraftWorld().getHandle();
UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false));
// SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent
world.captureTreeGeneration = true;
InteractionResult result = BoneMealItem.applyBonemeal(context);
world.captureTreeGeneration = false;
if (!world.capturedBlockStates.isEmpty()) {
TreeType treeType = SaplingBlock.treeType;
SaplingBlock.treeType = null;
List<org.bukkit.block.BlockState> states = new ArrayList<>(world.capturedBlockStates.values());
world.capturedBlockStates.clear();
StructureGrowEvent structureEvent = null;
if (treeType != null) {
structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states);
Bukkit.getPluginManager().callEvent(structureEvent);
}
event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states);
event.setCancelled(structureEvent != null && structureEvent.isCancelled());
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
for (org.bukkit.block.BlockState state : states) {
CraftBlockState craftBlockState = (CraftBlockState) state;
craftBlockState.place(craftBlockState.getFlags());
world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState);
}
}
}
return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled());
return BoneMealItem.applyBonemeal(context) == InteractionResult.SUCCESS;
}
@Override
@@ -28,21 +28,16 @@ public class CraftBlockState implements BlockState {
protected final CraftWorld world;
private final BlockPos position;
protected net.minecraft.world.level.block.state.BlockState block;
@net.minecraft.world.level.block.Block.UpdateFlags
protected int capturedFlags; // todo move out of this class
private WeakReference<LevelAccessor> weakLevel;
protected CraftBlockState(final Block block) {
this(block.getWorld(), ((CraftBlock) block).getPosition(), ((CraftBlock) block).getBlockState());
this.capturedFlags = net.minecraft.world.level.block.Block.UPDATE_ALL;
this.setWorldHandle(((CraftBlock) block).getLevel());
}
@Deprecated
protected CraftBlockState(final Block block, @net.minecraft.world.level.block.Block.UpdateFlags int capturedFlags) {
this(block);
this.capturedFlags = capturedFlags;
}
// world can be null for non-placed BlockStates.
@@ -62,7 +57,6 @@ public class CraftBlockState implements BlockState {
this.position = CraftLocation.toBlockPos(location);
}
this.block = from.block;
this.capturedFlags = from.capturedFlags;
this.setWorldHandle(from.getWorldHandle());
}
@@ -181,14 +175,6 @@ public class CraftBlockState implements BlockState {
return this.block.getBukkitMaterial();
}
public void setFlags(@net.minecraft.world.level.block.Block.UpdateFlags int flags) {
this.capturedFlags = flags;
}
public @net.minecraft.world.level.block.Block.UpdateFlags int getFlags() {
return this.capturedFlags;
}
@Override
public byte getLightLevel() {
return this.getBlock().getLightLevel();
@@ -252,17 +238,6 @@ public class CraftBlockState implements BlockState {
return this.getWorldHandle().setBlock(this.position, this.block, flags);
}
// used to revert a block placement due to an event being cancelled for example
public boolean revertPlace() {
return this.place(
net.minecraft.world.level.block.Block.UPDATE_CLIENTS |
net.minecraft.world.level.block.Block.UPDATE_KNOWN_SHAPE |
net.minecraft.world.level.block.Block.UPDATE_SUPPRESS_DROPS |
net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE |
net.minecraft.world.level.block.Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS
);
}
@Override
public byte getRawData() {
return CraftMagicNumbers.toLegacyData(this.block);
@@ -31,6 +31,11 @@ public class CraftEnderDragonPart extends CraftComplexPart implements EnderDrago
this.getParent().damage(amount, source);
}
@Override
public void kill(DamageSource damageSource) {
this.getParent().kill(damageSource);
}
@Override
public double getHealth() {
return this.getParent().getHealth();
@@ -91,6 +91,7 @@ import org.bukkit.entity.Trident;
import org.bukkit.entity.WitherSkull;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.event.entity.EntityPotionEffectEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
@@ -146,12 +147,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
}
}
// Paper start - entity heal API
@Override
public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) {
public void heal(final double amount, final EntityRegainHealthEvent.RegainReason reason) {
this.getHandle().heal((float) amount, reason);
}
// Paper end - entity heal API
@Override
public double getAbsorptionAmount() {
@@ -401,6 +400,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
this.getHandle().setStingerCount(count);
}
@Override
public void kill(org.bukkit.damage.DamageSource damageSource) {
Preconditions.checkState(!this.getHandle().generation, "Cannot kill entity during world generation");
Preconditions.checkArgument(damageSource != null, "damageSource cannot be null");
this.getHandle().setHealth(0);
this.getHandle().die(((CraftDamageSource) damageSource).getHandle());
}
@Override
public void damage(double amount) {
this.damage(amount, this.getHandle().damageSources().generic());
@@ -2056,6 +2056,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa
for (net.minecraft.world.effect.MobEffectInstance mobEffect : handle.getActiveEffects()) {
connection.send(new net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket(handle.getId(), mobEffect, false));
}
sendHealthUpdate();
}
// Paper end
@@ -15,6 +15,10 @@ import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent;
import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent;
import io.papermc.paper.event.player.PlayerBedFailEnterEvent;
import io.papermc.paper.event.player.PlayerToggleEntityAgeLockEvent;
import io.papermc.paper.util.capture.GrowthContext;
import io.papermc.paper.util.capture.MinecraftCaptureBridge;
import io.papermc.paper.util.capture.PaperCapturingWorldLevel;
import io.papermc.paper.util.capture.SimpleBlockCapture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
@@ -108,6 +112,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemType;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.CraftVector;
import org.bukkit.entity.AbstractHorse;
@@ -148,6 +153,7 @@ import org.bukkit.event.block.BlockDispenseLootEvent;
import org.bukkit.event.block.BlockDropItemEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFadeEvent;
import org.bukkit.event.block.BlockFertilizeEvent;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockGrowEvent;
import org.bukkit.event.block.BlockIgniteEvent;
@@ -264,6 +270,7 @@ import org.bukkit.event.weather.LightningStrikeEvent;
import org.bukkit.event.world.EntitiesLoadEvent;
import org.bukkit.event.world.EntitiesUnloadEvent;
import org.bukkit.event.world.LootGenerateEvent;
import org.bukkit.event.world.StructureGrowEvent;
import org.bukkit.inventory.CraftingRecipe;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.InventoryView;
@@ -490,20 +497,20 @@ public class CraftEventFactory {
return event.getResonatedEntities().stream().map((bukkitEntity) -> ((CraftLivingEntity) bukkitEntity).getHandle());
}
public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(ServerLevel level, net.minecraft.world.entity.player.Player player, InteractionHand hand, List<BlockState> blockStates, BlockPos clickedPos) {
public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(ServerLevel level, net.minecraft.world.entity.player.Player player, InteractionHand hand, List<BlockState> replacedSnapshots, BlockPos clickedPos) {
Player cplayer = (Player) player.getBukkitEntity();
Block clickedBlock = CraftBlock.at(level, clickedPos);
boolean canBuild = true;
for (BlockState blockState : blockStates) {
if (!CraftEventFactory.canBuild(level, cplayer, blockState.getX(), blockState.getZ())) {
for (BlockState snapshot : replacedSnapshots) {
if (!CraftEventFactory.canBuild(level, cplayer, snapshot.getX(), snapshot.getZ())) {
canBuild = false;
break;
}
}
EquipmentSlot handSlot = CraftEquipmentSlot.getHand(hand);
BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, clickedBlock, cplayer.getInventory().getItem(handSlot), cplayer, canBuild, handSlot);
BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(replacedSnapshots, clickedBlock, cplayer.getInventory().getItem(handSlot), cplayer, canBuild, handSlot);
event.callEvent();
return event;
@@ -1278,7 +1285,7 @@ public class CraftEventFactory {
return event;
}
public static boolean handleBlockGrowEvent(Level level, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) {
public static boolean handleBlockGrowEvent(LevelAccessor level, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) {
CraftBlockState snapshot = CraftBlockStates.getBlockState(level, pos);
snapshot.setBlock(state);
@@ -2397,6 +2404,54 @@ public class CraftEventFactory {
return false;
}
// todo block list is sometimes empty for trees in both events
public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos pos, Function<PaperCapturingWorldLevel, Boolean> worldGenCapture, GrowthContext context) {
ServerLevel originalLevel = level.getLevel();
try (SimpleBlockCapture capture = level.forkCaptureSession()) {
MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel();
if (worldGenCapture.apply(captureTreeGeneration)) {
Location location = CraftLocation.toBukkit(pos, originalLevel);
List<BlockState> blocks = captureTreeGeneration.calculateLatestSnapshots(originalLevel);
StructureGrowEvent event = new StructureGrowEvent(location, context.getTreeSpecies(), context.usedBoneMeal(), context.getBukkitPlayer(), blocks);
event.setCancelled(context.cancelled);
if (event.callEvent()) {
capture.finalizePlacement(); // todo block list is mutable
return true;
} else {
context.cancelled = true;
}
} else {
capture.finalizePlacement();
}
}
return false;
}
// todo cancelling fertilize event doesn't work for azalea
public static boolean fertilizeBlock(ServerLevel level, BlockPos pos, Function<PaperCapturingWorldLevel, Boolean> worldGenCapture, GrowthContext context) {
try (SimpleBlockCapture capture = level.forkCaptureSession()) {
MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel();
if (worldGenCapture.apply(captureTreeGeneration)) {
List<BlockState> blocks = captureTreeGeneration.calculateLatestSnapshots(level);
BlockFertilizeEvent event = new BlockFertilizeEvent(CraftBlock.at(level, pos), context.getBukkitPlayer(), blocks);
event.setCancelled(context.cancelled);
if (event.callEvent()) {
capture.finalizePlacement(); // todo block list is mutable
return true;
} else {
context.cancelled = true;
}
} else {
capture.finalizePlacement();
}
}
return false;
}
public static boolean callPlayerToggleEntityAgeLockEvent(net.minecraft.world.entity.player.Player player, Mob target, ItemStack itemUsed, InteractionHand hand, boolean ageLocked, @Nullable EntityDataAccessor<?> accessorToResync) {
PlayerToggleEntityAgeLockEvent event = new PlayerToggleEntityAgeLockEvent(
(org.bukkit.entity.Player) player.getBukkitEntity(),
@@ -18,11 +18,11 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import org.bukkit.block.BlockState;
import org.bukkit.craftbukkit.block.CraftBlockState;
import org.bukkit.craftbukkit.block.CraftBlockStates;
@@ -38,7 +38,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
}
@Override
public net.minecraft.world.level.block.state.BlockState getBlockState(BlockPos pos) {
public BlockState getBlockState(BlockPos pos) {
CapturedBlock block = this.blocks.get(pos);
return block != null ? block.state() : this.level.getBlockState(pos);
}
@@ -56,7 +56,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
}
@Override
public boolean setBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
public boolean setBlock(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
pos = pos.immutable();
// remove first to keep last updated order
this.blocks.remove(pos);
@@ -81,7 +81,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
@Override
public boolean destroyBlock(BlockPos pos, boolean dropResources, Entity breaker, int updateLimit) {
net.minecraft.world.level.block.state.BlockState blockState = this.getBlockState(pos);
BlockState blockState = this.getBlockState(pos);
if (blockState.isAir()) {
return false;
}
@@ -101,7 +101,6 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
CraftBlockState snapshot = CraftBlockStates.getBlockState(
this.getMinecraftWorld().getWorld(), entry.getKey(), block.state(), block.blockEntity()
);
snapshot.setFlags(block.flags());
snapshot.setWorldHandle(this.level);
callback.accept(snapshot);
}
@@ -111,7 +110,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
this.placeSomeBlocks(_ -> true);
}
public void placeSomeBlocks(Predicate<? super BlockState> filter) {
public void placeSomeBlocks(Predicate<? super CraftBlockState> filter) {
this.placeSomeBlocks(_ -> {}, filter);
}
@@ -119,22 +118,28 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
this.placeSomeBlocks(beforeRun, _ -> true);
}
public void placeSomeBlocks(Consumer<? super CraftBlockState> beforeRun, Predicate<? super BlockState> filter) {
public void placeSomeBlocks(Consumer<? super CraftBlockState> beforeRun, Predicate<? super CraftBlockState> filter) {
for (CraftBlockState snapshot : this.getSnapshotBlocks()) {
if (filter.test(snapshot)) {
int flags = this.getEffectiveFlags(snapshot.getPosition());
beforeRun.accept(snapshot);
snapshot.place(snapshot.getFlags());
snapshot.place(flags);
}
}
}
public @Block.UpdateFlags int getEffectiveFlags(BlockPos pos) {
CapturedBlock block = this.blocks.get(pos);
return block != null ? block.flags() : Block.UPDATE_ALL; // fallback for new API-added blocks
}
public List<CraftBlockState> getSnapshotBlocks() {
if (this.snapshots == null) {
List<CraftBlockState> snapshots = new ArrayList<>();
this.iterateSnapshots(snapshots::add);
this.snapshots = snapshots;
}
return snapshots;
return this.snapshots;
}
// For tree generation
@@ -155,7 +160,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
}
@Override
public boolean isStateAtPosition(BlockPos pos, Predicate<net.minecraft.world.level.block.state.BlockState> predicate) {
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> predicate) {
return predicate.test(this.getBlockState(pos));
}
@@ -2,11 +2,14 @@ package org.bukkit.craftbukkit.util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.pathfinder.Node;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Location;
import org.bukkit.World;
import org.jspecify.annotations.Nullable;
public final class CraftLocation {
@@ -65,8 +68,9 @@ public final class CraftLocation {
return net.minecraft.core.GlobalPos.of(((org.bukkit.craftbukkit.CraftWorld) loc.getWorld()).getHandle().dimension(), toBlockPos(loc));
}
public static Location fromGlobalPos(net.minecraft.core.GlobalPos globalPos) {
return CraftLocation.toBukkit(globalPos.pos(), net.minecraft.server.MinecraftServer.getServer().getLevel(globalPos.dimension()));
public static @Nullable Location fromGlobalPos(net.minecraft.core.GlobalPos globalPos) {
ServerLevel level = MinecraftServer.getServer().getLevel(globalPos.dimension());
return level != null ? CraftLocation.toBukkit(globalPos.pos(), level) : null;
}
public static Vec3 toVec3(Location loc) {
@@ -4,6 +4,9 @@ import com.google.common.collect.Lists;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.PaperDialog;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.entity.poi.PoiTypes;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.RegistryKey;
import java.util.List;
@@ -157,6 +160,7 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
register(Registries.ZOMBIE_NAUTILUS_VARIANT, ZombieNautilus.Variant.class, CraftZombieNautilus.CraftVariant.class, ZombieNautilusVariant.class);
register(Registries.DIALOG, Dialog.class, PaperDialog.class, net.minecraft.server.dialog.Dialog.class);
register(Registries.GAME_RULE, GameRule.class, GameRules.class, CraftGameRule.class, net.minecraft.world.level.gamerules.GameRule.class);
register(Registries.POINT_OF_INTEREST_TYPE, PoiType.class, PoiTypes.class, PaperPoiType.class, net.minecraft.world.entity.ai.village.poi.PoiType.class);
}
private static <M, B extends Keyed> void register(ResourceKey<? extends Registry<? extends M>> registryKey, Class<B> api, Class<? extends B> impl, Class<M> internal) {
@@ -0,0 +1,86 @@
package io.papermc.testplugin;
import org.bukkit.Material;
import org.bukkit.TreeType;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockFertilizeEvent;
import org.bukkit.event.block.BlockMultiPlaceEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.event.world.StructureGrowEvent;
import static net.kyori.adventure.text.Component.text;
public record OwenPlayground() implements Listener {
public static final OwenPlayground INSTANCE = new OwenPlayground();
@EventHandler
public void on(BlockFertilizeEvent event) {
//event.setCancelled(true);
// event.getPlayer().sendBlockChanges(event.getBlocks());
//
// new BukkitRunnable(){
//
// @Override
// public void run() {
// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData()));
// }
// }.runTaskLater(this, 20 * 2);
}
@EventHandler
public void on(StructureGrowEvent event) {
// event.setCancelled(true);
//
// event.getPlayer().sendBlockChanges(event.getBlocks());
//
// new BukkitRunnable(){
//
// @Override
// public void run() {
// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData()));
// }
// }.runTaskLater(this, 20 * 2);
}
@EventHandler
public void on(PlayerSwapHandItemsEvent event) {
event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP);
event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]);
}
@EventHandler
public void on(BlockPlaceEvent event) {
event.getPlayer().sendActionBar(text("Replaced: " + event.getBlockReplacedState().getType()));
if (event.getPlayer().getInventory().contains(Material.DIAMOND)) {
event.getBlock().setType(Material.AIR);
} else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) {
event.setCancelled(true);
} else if (event.getPlayer().getInventory().contains(Material.EMERALD)) {
event.getBlock().setType(Material.STONE);
} else if (event.getPlayer().getInventory().contains(Material.GOLD_NUGGET)) {
event.getBlock().getRelative(BlockFace.SOUTH).setType(Material.STONE);
}
event.getPlayer().sendMessage(event.getBlock().getType().toString());
event.getBlockAgainst().setType(Material.DIAMOND_BLOCK);
}
@EventHandler
public void on(BlockMultiPlaceEvent event) {
event.getPlayer().sendActionBar(text("Replaced: " + event.getReplacedBlockStates().stream().map(BlockState::getBlockData).toList()));
event.getPlayer().sendMessage(event.getBlock().getType().toString());
if (event.getPlayer().getInventory().contains(Material.DIAMOND)) {
event.setCancelled(true);
}
}
}
@@ -8,6 +8,7 @@ public final class TestPlugin extends JavaPlugin implements Listener {
@Override
public void onEnable() {
this.getServer().getPluginManager().registerEvents(this, this);
this.getServer().getPluginManager().registerEvents(OwenPlayground.INSTANCE, this);
// io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
}
@@ -12,6 +12,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
public class IceCreamTypeArgument implements CustomArgumentType.Converted<IceCreamType, String> {
@@ -19,7 +20,7 @@ public class IceCreamTypeArgument implements CustomArgumentType.Converted<IceCre
@Override
public @NotNull IceCreamType convert(String nativeType) throws CommandSyntaxException {
try {
return IceCreamType.valueOf(nativeType.toUpperCase());
return IceCreamType.valueOf(nativeType.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ignored) {
throw new ComponentCommandExceptionType(Component.text("Invalid species %s!".formatted(nativeType), NamedTextColor.RED)).create();
}