diff --git a/paper-api/src/main/java/org/bukkit/entity/Mob.java b/paper-api/src/main/java/org/bukkit/entity/Mob.java
index ca773a807..9c995ed31 100644
--- a/paper-api/src/main/java/org/bukkit/entity/Mob.java
+++ b/paper-api/src/main/java/org/bukkit/entity/Mob.java
@@ -1,25 +1,63 @@
package org.bukkit.entity;
+import com.destroystokyo.paper.entity.Pathfinder;
+import io.papermc.paper.entity.Leashable;
+import net.kyori.adventure.util.TriState;
+import org.bukkit.Location;
import org.bukkit.Sound;
+import org.bukkit.inventory.EntityEquipment;
+import org.bukkit.loot.LootTable;
import org.bukkit.loot.Lootable;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
/**
* Represents a Mob. Mobs are living entities with simple AI.
*/
-public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Leashable { // Paper - Leashable API
+@NullMarked
+public interface Mob extends LivingEntity, Lootable, Leashable {
+
+ /**
+ * Check if a mob should be despawned when the world is set to peaceful difficulty.
+ * This also takes the {@link Mob#getDespawnInPeacefulOverride()} into account.
+ *
+ * @return True if the entity should be removed in peaceful
+ */
+ boolean shouldDespawnInPeaceful();
+
+ /**
+ * Sets if the entity should be despawned when the game is set to peaceful difficulty.
+ *
+ * - {@link TriState#NOT_SET} – Use the default behavior for the entity
+ * - {@link TriState#TRUE} – The entity will be removed in Peaceful difficulty
+ * - {@link TriState#FALSE} – The entity will not automatically be removed in Peaceful difficulty
+ *
+ *
+ * @param state a TriState representing the state of the override
+ */
+ void setDespawnInPeacefulOverride(TriState state);
+
+ /**
+ * Gets the current override value for whether this entity should despawn in peaceful difficulty.
+ *
+ * - {@link TriState#NOT_SET} – Use the default behavior for the entity
+ * - {@link TriState#TRUE} – The entity will be removed in Peaceful difficulty
+ * - {@link TriState#FALSE} – The entity will not automatically be removed in Peaceful difficulty
+ *
+ *
+ * @return a TriState representing the state of the override
+ * @see Mob#setDespawnInPeacefulOverride(TriState)
+ */
+ TriState getDespawnInPeacefulOverride();
- // Paper start
@Override
- org.bukkit.inventory.@org.jetbrains.annotations.NotNull EntityEquipment getEquipment();
+ EntityEquipment getEquipment();
/**
* Enables access to control the pathing of an Entity
* @return Pathfinding Manager for this entity
*/
- @NotNull
- com.destroystokyo.paper.entity.Pathfinder getPathfinder();
+ Pathfinder getPathfinder();
/**
* Check if this mob is exposed to daylight
@@ -35,7 +73,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
*
* @param location location to look at
*/
- void lookAt(@NotNull org.bukkit.Location location);
+ void lookAt(Location location);
/**
* Instruct this Mob to look at a specific Location
@@ -46,7 +84,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
* @param headRotationSpeed head rotation speed
* @param maxHeadPitch max head pitch rotation
*/
- void lookAt(@NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch);
+ void lookAt(Location location, float headRotationSpeed, float maxHeadPitch);
/**
* Instruct this Mob to look at a specific Entity
@@ -57,7 +95,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
*
* @param entity entity to look at
*/
- void lookAt(@NotNull Entity entity);
+ void lookAt(Entity entity);
/**
* Instruct this Mob to look at a specific Entity
@@ -70,7 +108,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
* @param headRotationSpeed head rotation speed
* @param maxHeadPitch max head pitch rotation
*/
- void lookAt(@NotNull Entity entity, float headRotationSpeed, float maxHeadPitch);
+ void lookAt(Entity entity, float headRotationSpeed, float maxHeadPitch);
/**
* Instruct this Mob to look at a specific position
@@ -109,7 +147,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
* @return the max head pitch rotation
*/
int getMaxHeadPitch();
- // Paper end
+
/**
* Instructs this Mob to set the specified LivingEntity as its target.
*
@@ -118,26 +156,25 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
*
* @param target New LivingEntity to target, or null to clear the target
*/
- public void setTarget(@Nullable LivingEntity target);
+ void setTarget(@Nullable LivingEntity target);
/**
* Gets the current target of this Mob
*
* @return Current target of this creature, or null if none exists
*/
- @Nullable
- public LivingEntity getTarget();
+ @Nullable LivingEntity getTarget();
/**
* Sets whether this mob is aware of its surroundings.
- *
+ *
* Unaware mobs will still move if pushed, attacked, etc. but will not move
* or perform any actions on their own. Unaware mobs may also have other
* unspecified behaviours disabled, such as drowning.
*
* @param aware whether the mob is aware
*/
- public void setAware(boolean aware);
+ void setAware(boolean aware);
/**
* Gets whether this mob is aware of its surroundings.
@@ -148,7 +185,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
*
* @return whether the mob is aware
*/
- public boolean isAware();
+ boolean isAware();
/**
* Get the {@link Sound} this mob makes while ambiently existing. This sound
@@ -160,18 +197,14 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
*
* @return the ambient sound, or null if this entity is ambiently quiet
*/
- @Nullable
- public Sound getAmbientSound();
+ @Nullable Sound getAmbientSound();
- // Paper start - LootTable API
@Override
- default void setLootTable(final @Nullable org.bukkit.loot.LootTable table, final long seed) {
+ default void setLootTable(final @Nullable LootTable table, final long seed) {
this.setLootTable(table);
this.setSeed(seed);
}
- // Paper end - LootTable API
- // Paper start - Missing Entity API
/**
* Some mobs will raise their arm(s) when aggressive:
*
@@ -203,30 +236,25 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
* @see #isAggressive()
*/
void setAggressive(boolean aggressive);
- // Paper end - Missing Entity API
- // Paper start - left-handed API
/**
* Check if Mob is left-handed
*
* @return True if left-handed
*/
- public boolean isLeftHanded();
+ boolean isLeftHanded();
/**
* Set if Mob is left-handed
*
* @param leftHanded True if left-handed
*/
- public void setLeftHanded(boolean leftHanded);
- // Paper end - left-handed API
+ void setLeftHanded(boolean leftHanded);
- // Paper start - mob xp reward API
/**
* Gets the amount of experience the mob will possibly drop. This value is randomized and it can give different results
*
* @return the amount of experience the mob will possibly drop
*/
- public int getPossibleExperienceReward();
- // Paper end - mob xp reward API
+ int getPossibleExperienceReward();
}
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 8849064ae..d5d1e0363 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 75f81a6bc156a6455a616b8de0d7701fd2255a2d..b3d951670b3a097d04cfe347d7df496b1d0a0e09 100644
+index b9f0745cdc42a093b69f46e353b99adf0c5aac56..a5d65e1b31e2e43cf039b8a19286a5324a739bbe 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
@@ -543,7 +543,7 @@ index 75f81a6bc156a6455a616b8de0d7701fd2255a2d..b3d951670b3a097d04cfe347d7df496b
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 13cf40918ead3e2cb2699398ac659a3e1806294b..1ba342a1a60951f828034d3ed535b577b3990bf6 100644
+index 8dd8a3d2a862998a920f226b2a6d9a877aac70a8..0c52e315557e1dae6a852694786e72241fff1e29 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,10 +562,10 @@ index 13cf40918ead3e2cb2699398ac659a3e1806294b..1ba342a1a60951f828034d3ed535b577
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
-index 3047763580fabd069db5e90a9a854396c6243763..0470c4bbf8be7e48ce8dfa4910c3b9f5ebb23360 100644
+index c737fd87e804129fac95be7d19b4aafab38d8b94..21c6d4746c6a905ec312dc1e35535cbd13868322 100644
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
-@@ -206,6 +206,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
+@@ -207,6 +207,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
return this.lookControl;
}
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
index 74a0fcfb9..06848bbbf 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
@@ -23,11 +23,12 @@
public GoalSelector targetSelector;
@Nullable
private LivingEntity target;
-@@ -129,6 +_,7 @@
+@@ -129,6 +_,8 @@
private Leashable.LeashData leashData;
private BlockPos homePosition = BlockPos.ZERO;
private int homeRadius = -1;
+ public boolean aware = true; // CraftBukkit
++ public net.kyori.adventure.util.TriState despawnInPeacefulOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - allow changing despawnInPeaceful
protected Mob(EntityType extends Mob> entityType, Level level) {
super(entityType, level);
@@ -84,11 +85,16 @@
}
@Override
-@@ -365,13 +_,22 @@
+@@ -365,13 +_,27 @@
if (this.isNoAi()) {
output.putBoolean("NoAI", this.isNoAi());
}
+ output.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
++ // Paper start - allow changing despawnInPeaceful
++ if (this.despawnInPeacefulOverride != net.kyori.adventure.util.TriState.NOT_SET) {
++ output.putString("Paper.DespawnInPeacefulOverride", this.despawnInPeacefulOverride.name());
++ }
++ // Paper end - allow changing despawnInPeaceful
}
@Override
@@ -109,11 +115,12 @@
this.dropChances = input.read("drop_chances", DropChances.CODEC).orElse(DropChances.DEFAULT);
this.readLeashData(input);
this.homeRadius = input.getIntOr("home_radius", -1);
-@@ -383,6 +_,7 @@
+@@ -383,6 +_,8 @@
this.lootTable = input.read("DeathLootTable", LootTable.KEY_CODEC);
this.lootTableSeed = input.getLongOr("DeathLootTableSeed", 0L);
this.setNoAi(input.getBooleanOr("NoAI", false));
+ this.aware = input.getBooleanOr("Bukkit.Aware", true); // CraftBukkit
++ this.despawnInPeacefulOverride = input.read("Paper.DespawnInPeacefulOverride", io.papermc.paper.util.PaperCodecs.TRI_STATE_CODEC).orElse(net.kyori.adventure.util.TriState.NOT_SET); // Paper - allow changing despawnInPeaceful
}
@Override
@@ -176,11 +183,25 @@
}
ItemStack itemStack = equipmentSlotForItem.limit(stack);
-@@ -608,22 +_,29 @@
+@@ -601,29 +_,42 @@
+ return this.isPassenger();
+ }
+
++ // Paper start - allow changing despawnInPeaceful
++ public final boolean shouldActuallyDespawnInPeaceful() {
++ return this.despawnInPeacefulOverride.toBooleanOrElse(this.shouldDespawnInPeaceful());
++ }
++ // Paper end - allow changing despawnInPeaceful
++
+ protected boolean shouldDespawnInPeaceful() {
+ return false;
+ }
+
@Override
public void checkDespawn() {
- if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
+- if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
- this.discard();
++ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldActuallyDespawnInPeaceful()) { //Paper - allow changing despawnInPeaceful
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
} else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
- Entity nearestPlayer = this.level().getNearestPlayer(this, -1.0);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
index 1a0799626..e6254f4be 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
@@ -95,8 +95,9 @@
@Override
public void checkDespawn() {
- if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
+- if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
- this.discard();
++ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldActuallyDespawnInPeaceful()) { //Paper - allow changing despawnInPeaceful
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
} else {
this.noActionTime = 0;
diff --git a/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java b/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java
index 10317465e..e6427f226 100644
--- a/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java
+++ b/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java
@@ -15,6 +15,7 @@ import io.papermc.paper.registry.TypedKey;
import java.util.Optional;
import java.util.function.Function;
import net.kyori.adventure.key.Key;
+import net.kyori.adventure.util.TriState;
import net.minecraft.core.Holder;
import net.minecraft.resources.RegistryOps;
import org.bukkit.Keyed;
@@ -24,6 +25,14 @@ import org.jspecify.annotations.NullMarked;
@NullMarked
public final class PaperCodecs {
+ public static final Codec TRI_STATE_CODEC = Codec.STRING.comapFlatMap(s -> {
+ try {
+ return DataResult.success(TriState.valueOf(s));
+ } catch (Exception e) {
+ return DataResult.error(() -> "Unknown TriState value: " + s);
+ }
+ }, TriState::name);
+
/**
* This codec is lenient on decoding and encoding compared to native OptionalFieldCodec
* which only has options to be lenient on decoding.
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
index e2e7743bd..5e1a3e7ca 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
@@ -2,6 +2,7 @@ package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import java.util.Optional;
+import net.kyori.adventure.util.TriState;
import net.minecraft.sounds.SoundEvent;
import org.bukkit.Sound;
import org.bukkit.craftbukkit.CraftLootTable;
@@ -20,6 +21,22 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob, io.pape
this.paperPathfinder = new com.destroystokyo.paper.entity.PaperPathfinder(entity); // Paper - Mob Pathfinding API
}
+ @Override
+ public boolean shouldDespawnInPeaceful() {
+ return this.getHandle().shouldActuallyDespawnInPeaceful();
+ }
+
+ @Override
+ public void setDespawnInPeacefulOverride(final TriState state) {
+ Preconditions.checkArgument(state != null, "TriState cannot be null");
+ this.getHandle().despawnInPeacefulOverride = state;
+ }
+
+ @Override
+ public TriState getDespawnInPeacefulOverride() {
+ return this.getHandle().despawnInPeacefulOverride;
+ }
+
@Override
public net.minecraft.world.entity.Mob getHandle() {
return (net.minecraft.world.entity.Mob) this.entity;