mirror of
https://github.com/PaperMC/Paper.git
synced 2026-11-04 17:00:09 -05:00
Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6409d5ce9e | ||
|
|
2de25adfc9 | ||
|
|
182b2f8a2a | ||
|
|
a8c05a35c2 | ||
|
|
945aea9961 | ||
|
|
4aef3f0a67 | ||
|
|
60073714ba | ||
|
|
4f184db3c9 | ||
|
|
57d84f68a3 | ||
|
|
49daadd25d | ||
|
|
b2b1e177cb | ||
|
|
90aee8e007 | ||
|
|
d74f6bc77a | ||
|
|
dd203a2716 | ||
|
|
dcf5577aae | ||
|
|
c39822ae08 | ||
|
|
4af86ff3e0 | ||
|
|
1a8417b12b | ||
|
|
65461225ef | ||
|
|
7b31cc2b6b | ||
|
|
aa6f09b098 | ||
|
|
944ce44a4a | ||
|
|
6fe7eb5aae | ||
|
|
948acae8c2 | ||
|
|
ef4e85d6db | ||
|
|
3611a815e6 | ||
|
|
1d14197790 | ||
|
|
6bac3c95bf | ||
|
|
9b1dd57135 | ||
|
|
4be1a310c2 | ||
|
|
66d3bbed38 | ||
|
|
d0be5e20fd | ||
|
|
dd0810c4af | ||
|
|
04ee8eac9b | ||
|
|
c58cba39fb | ||
|
|
4731020c9d | ||
|
|
dbd8747173 | ||
|
|
d4895653fa | ||
|
|
4397b319a7 | ||
|
|
298a8bfd32 | ||
|
|
bcf5a3fffe | ||
|
|
84a73d8984 | ||
|
|
f46fbbe20b | ||
|
|
ad45c84240 | ||
|
|
e4114b0e0e | ||
|
|
d56b548552 | ||
|
|
8e32dcbfe7 | ||
|
|
6968eb065e | ||
|
|
d55864cc09 | ||
|
|
c77b8418bf | ||
|
|
f4074a073e | ||
|
|
05eb2e4543 |
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.
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
|
||||
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 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();
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+18
-1
@@ -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);
|
||||
|
||||
+14
-3
@@ -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
|
||||
}
|
||||
|
||||
+4
-4
@@ -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;
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+3
-2
@@ -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
|
||||
|
||||
+8
-6
@@ -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)) {
|
||||
|
||||
+9
@@ -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 {
|
||||
+11
-1
@@ -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 {
|
||||
+4
-2
@@ -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()) {
|
||||
|
||||
+14
-1
@@ -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) {
|
||||
-9
@@ -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);
|
||||
|
||||
+9
-17
@@ -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(
|
||||
+10
-1
@@ -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);
|
||||
|
||||
|
||||
+6
-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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -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();
|
||||
}
|
||||
+47
@@ -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);
|
||||
}
|
||||
}
|
||||
+109
@@ -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(),
|
||||
|
||||
+15
-10
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user