diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/ScoreboardTagsChangeEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/ScoreboardTagsChangeEvent.java new file mode 100644 index 000000000000..11ed48079d16 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/ScoreboardTagsChangeEvent.java @@ -0,0 +1,67 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import java.util.Collections; +import java.util.List; + +/** + * Called right before the scoreboard tags for an entity are changed. + */ +@NullMarked +public class ScoreboardTagsChangeEvent extends EntityEvent { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final List tags; + private final Change change; + + @ApiStatus.Internal + public ScoreboardTagsChangeEvent(final Entity entity, final List tags, final Change change) { + super(entity); + this.tags = Collections.unmodifiableList(tags); + this.change = change; + } + + /** + * Get the tags that are being added/removed/set. + * + * @return Tags being added/removed/set. Unmodifiable. + */ + public List getTags() { + return this.tags; + } + + /** + * Gets the type of change happening to the entity's tags. + * + * @return Type of change happening. + */ + public Change getChange() { + return this.change; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + /** + * Represents the possible changes made to the entity's tags. + */ + public enum Change { + /** The tags were added to the entity. */ + ADD, + /** The tags were removed from the entity. */ + REMOVE, + /** The tags replaced the entity's existing tags. */ + SET, + } +} diff --git a/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch index d5d1e0363ceb..d37b095202c5 100644 --- a/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch +++ b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch @@ -484,7 +484,7 @@ index c70a58f5f633fa8e255f74c42f5e87c96b7b013a..ec20a5a6d7c8f65abda528fec36bec7b public void tick() { super.tick(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index b9f0745cdc42a093b69f46e353b99adf0c5aac56..a5d65e1b31e2e43cf039b8a19286a5324a739bbe 100644 +index a5c1c1f88f2e1930f46c18654a384b906ca66515..f9fea0ab2c7c5093ce7838073020f9985d6e58d0 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -409,6 +409,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -517,7 +517,7 @@ index b9f0745cdc42a093b69f46e353b99adf0c5aac56..a5d65e1b31e2e43cf039b8a19286a532 SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this); builder.define(DATA_SHARED_FLAGS_ID, (byte)0); builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply()); -@@ -984,6 +1000,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -986,6 +1002,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); } else { if (type == MoverType.PISTON) { @@ -528,7 +528,7 @@ index b9f0745cdc42a093b69f46e353b99adf0c5aac56..a5d65e1b31e2e43cf039b8a19286a532 movement = this.limitPistonMovement(movement); if (movement.equals(Vec3.ZERO)) { return; -@@ -997,6 +1017,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -999,6 +1019,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.stuckSpeedMultiplier = Vec3.ZERO; this.setDeltaMovement(Vec3.ZERO); } @@ -543,7 +543,7 @@ index b9f0745cdc42a093b69f46e353b99adf0c5aac56..a5d65e1b31e2e43cf039b8a19286a532 movement = this.maybeBackOffFromEdge(movement, type); Vec3 vec3 = this.collide(movement); diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 8dd8a3d2a862998a920f226b2a6d9a877aac70a8..0c52e315557e1dae6a852694786e72241fff1e29 100644 +index bcdd4ad0482297c9ea376e3632722570e2dd4c23..0268e02d2ef2cb3d699644a804e23a6da4521f4c 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -3215,6 +3215,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin @@ -562,7 +562,7 @@ index 8dd8a3d2a862998a920f226b2a6d9a877aac70a8..0c52e315557e1dae6a852694786e7224 public void tick() { super.tick(); diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index c737fd87e804129fac95be7d19b4aafab38d8b94..21c6d4746c6a905ec312dc1e35535cbd13868322 100644 +index 63f0b0a39e51b1cd30c2d131414d9512886a664f..e0b3cb2b2694768803ed347a1026b881fd624951 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -207,6 +207,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -838,7 +838,7 @@ index 52acc72841f0c6980f5f3f8ef21d0b29dd472ce3..41a6ec508a10a49a37539d2f10171d15 + } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 06069d3ac598f5f12feab038de4f1199794298f6..980eaba27ce2616c1573a4760cf4acc2dd251190 100644 +index 27dc02fd57dfe8942dc835d610b922dd7e66f309..a3926741a46756d8f7fdeb934685ba36122add76 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -143,6 +143,12 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index e64c79dfb5ac..3484c6034ada 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -28728,7 +28728,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896 + // Paper end - block counting } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a5a96b8bf 100644 +index 990305f720cf0817a5b369c6454cd2c61b423bc7..b4392f9914d0a104d9e933c06ac72e5363510184 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter; @@ -28979,7 +28979,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a public Entity(EntityType entityType, Level level) { this.type = entityType; -@@ -1378,35 +1484,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -1380,35 +1486,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return distance; } @@ -29081,7 +29081,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a } private static float[] collectCandidateStepUpHeights(AABB box, List colliders, float deltaY, float maxUpStep) { -@@ -2680,21 +2828,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -2682,21 +2830,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public boolean isInWall() { @@ -29203,7 +29203,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a } public InteractionResult interact(Player player, InteractionHand hand) { -@@ -4290,15 +4527,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4292,15 +4529,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public Iterable getIndirectPassengers() { @@ -29229,7 +29229,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a } public int countPlayerPassengers() { -@@ -4441,77 +4680,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4443,77 +4682,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return Mth.lerp(partialTick, this.yRotO, this.yRot); } @@ -29420,7 +29420,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a public boolean touchingUnloadedChunk() { AABB aabb = this.getBoundingBox().inflate(1.0); -@@ -4666,6 +4964,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4668,6 +4966,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { @@ -29436,7 +29436,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a if (!checkPosition(this, x, y, z)) { return; } -@@ -4817,6 +5124,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4819,6 +5126,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @Override public final void setRemoved(Entity.RemovalReason removalReason, @Nullable org.bukkit.event.entity.EntityRemoveEvent.Cause cause) { // CraftBukkit - add Bukkit remove cause @@ -29449,7 +29449,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { -@@ -4827,7 +5140,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4829,7 +5142,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.stopRiding(); } @@ -29458,7 +29458,7 @@ index 17d9e4699e8e2ce934c25d34d7269bdda5c87d00..295d40341df325b3fb6f14164283863a this.levelCallback.onRemove(removalReason); this.onRemoval(removalReason); // Paper start - Folia schedulers -@@ -4861,7 +5174,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4863,7 +5176,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public boolean shouldBeSaved() { return (this.removalReason == null || this.removalReason.shouldSave()) && !this.isPassenger() diff --git a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch index 351ea5724239..d3940b5d682e 100644 --- a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch @@ -67,10 +67,10 @@ index fd93b0c887420789276ebf92023d3e02b99cc1c9..9243bb11e3f968d0bf0eb2e3dc9295c0 io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper profilerFiller.push("commandFunctions"); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 295d40341df325b3fb6f14164283863a5a96b8bf..9a102b2c58446bd0aac5bd7f00e647f0270e7983 100644 +index b4392f9914d0a104d9e933c06ac72e5363510184..8e92d02c5a4b2040e9e351454d817409bfa1840b 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -5164,6 +5164,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5166,6 +5166,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.getBukkitEntity().taskScheduler.retire(); } // Paper end - Folia schedulers diff --git a/paper-server/patches/sources/net/minecraft/server/commands/data/EntityDataAccessor.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/data/EntityDataAccessor.java.patch new file mode 100644 index 000000000000..21dc83ec0c33 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/data/EntityDataAccessor.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/server/commands/data/EntityDataAccessor.java ++++ b/net/minecraft/server/commands/data/EntityDataAccessor.java +@@ -4,7 +_,9 @@ + import com.mojang.brigadier.context.CommandContext; + import com.mojang.brigadier.exceptions.CommandSyntaxException; + import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; ++import com.mojang.serialization.Codec; + import com.mojang.logging.LogUtils; ++import java.util.List; + import java.util.Locale; + import java.util.UUID; + import java.util.function.Function; +@@ -39,6 +_,7 @@ + return builder.then(Commands.literal("entity").then(action.apply(Commands.argument(argumentName, EntityArgument.entity())))); + } + }; ++ private static final Codec> TAG_LIST_CODEC = Codec.STRING.sizeLimitedListOf(1024); // Paper - add ScoreboardTagsChangeEvent + private final Entity entity; + + public EntityDataAccessor(Entity entity) { +@@ -54,6 +_,11 @@ + + try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(this.entity.problemPath(), LOGGER)) { + this.entity.load(TagValueInput.create(scopedCollector, this.entity.registryAccess(), other)); ++ // Paper start - add ScoreboardTagsChangeEvent ++ other.read("Tags", TAG_LIST_CODEC).ifPresent(tags -> { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callScoreboardTagsChangeEvent(this.entity, tags, io.papermc.paper.event.entity.ScoreboardTagsChangeEvent.Change.SET); ++ }); ++ // Paper end - add ScoreboardTagsChangeEvent + this.entity.setUUID(uuid); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch index fca7e199321f..936a118c4d38 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -197,16 +197,18 @@ public boolean isSpectator() { return false; } -@@ -362,7 +_,7 @@ +@@ -362,20 +_,28 @@ } public boolean addTag(String tag) { - return this.tags.size() < 1024 && this.tags.add(tag); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callScoreboardTagsChangeEvent(this, tag, io.papermc.paper.event.entity.ScoreboardTagsChangeEvent.Change.ADD); // Paper - add ScoreboardTagsChangeEvent + return this.tags.add(tag); // Paper - fully limit tag size - replace set impl } public boolean removeTag(String tag) { -@@ -370,12 +_,18 @@ ++ org.bukkit.craftbukkit.event.CraftEventFactory.callScoreboardTagsChangeEvent(this, tag, io.papermc.paper.event.entity.ScoreboardTagsChangeEvent.Change.REMOVE); // Paper - add ScoreboardTagsChangeEvent + return this.tags.remove(tag); } public void kill(ServerLevel level) { @@ -848,7 +850,7 @@ Vec2 vec2 = input.read("Rotation", Vec2.CODEC).orElse(Vec2.ZERO); this.setDeltaMovement(Math.abs(vec31.x) > 10.0 ? 0.0 : vec31.x, Math.abs(vec31.y) > 10.0 ? 0.0 : vec31.y, Math.abs(vec31.z) > 10.0 ? 0.0 : vec31.z); this.hasImpulse = true; -@@ -1988,7 +_,20 @@ +@@ -1988,10 +_,23 @@ this.setNoGravity(input.getBooleanOr("NoGravity", false)); this.setGlowingTag(input.getBooleanOr("Glowing", false)); this.setTicksFrozen(input.getIntOr("TicksFrozen", 0)); @@ -869,7 +871,11 @@ + // Paper end this.customData = input.read("data", CustomData.CODEC).orElse(CustomData.EMPTY); this.tags.clear(); - input.read("Tags", TAG_LIST_CODEC).ifPresent(this.tags::addAll); +- input.read("Tags", TAG_LIST_CODEC).ifPresent(this.tags::addAll); ++ input.read("Tags", TAG_LIST_CODEC).ifPresent(tags::addAll); + this.readAdditionalSaveData(input); + if (this.repositionEntityAfterLoad()) { + this.reapplyPosition(); @@ -1999,6 +_,59 @@ } else { throw new IllegalStateException("Entity has invalid rotation"); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 9878d6842ec7..a7128296194f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -18,12 +18,12 @@ import io.papermc.paper.connection.HorriblePlayerLoginEventHack; import io.papermc.paper.connection.PlayerConnection; import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; +import io.papermc.paper.event.entity.ScoreboardTagsChangeEvent; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -2115,4 +2115,16 @@ public static Component handleLoginResult(PlayerList.LoginResult result, PlayerC return disconnectReason; } + + // Paper start - add ScoreboardTagsChangeEvent + public static ScoreboardTagsChangeEvent callScoreboardTagsChangeEvent(Entity entity, List tags, ScoreboardTagsChangeEvent.Change change) { + final var event = new ScoreboardTagsChangeEvent(entity.getBukkitEntity(), tags, change); + Bukkit.getPluginManager().callEvent(event); + return event; + } + + public static ScoreboardTagsChangeEvent callScoreboardTagsChangeEvent(Entity entity, String tag, ScoreboardTagsChangeEvent.Change change) { + return callScoreboardTagsChangeEvent(entity, List.of(tag), change); + } + // Paper end - add ScoreboardTagsChangeEvent }