mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-02 17:32:03 +01:00
17d76ae42b
The existing logic in LivingEntity#checkTotemDeathProtection completes by checking whether the death protection component is null or not. In cases where the event was cancelled, the component needs to be nulled out to prevent the method from returning true, causing the player to properly die.
1811 lines
90 KiB
Diff
1811 lines
90 KiB
Diff
--- a/net/minecraft/world/entity/LivingEntity.java
|
|
+++ b/net/minecraft/world/entity/LivingEntity.java
|
|
@@ -42,6 +_,8 @@
|
|
import net.minecraft.core.particles.ParticleOptions;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
+import net.minecraft.nbt.FloatTag;
|
|
+import net.minecraft.nbt.IntTag;
|
|
import net.minecraft.nbt.ListTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.Tag;
|
|
@@ -136,6 +_,29 @@
|
|
import net.minecraft.world.scores.Scoreboard;
|
|
import org.slf4j.Logger;
|
|
|
|
+// CraftBukkit start
|
|
+import java.util.ArrayList;
|
|
+import java.util.HashSet;
|
|
+import java.util.Set;
|
|
+import java.util.LinkedList;
|
|
+import java.util.UUID;
|
|
+import net.minecraft.world.item.component.Consumable;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.craftbukkit.attribute.CraftAttributeMap;
|
|
+import org.bukkit.craftbukkit.event.CraftEventFactory;
|
|
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
|
+import org.bukkit.event.entity.ArrowBodyCountChangeEvent;
|
|
+import org.bukkit.event.entity.EntityDamageEvent;
|
|
+import org.bukkit.event.entity.EntityDamageEvent.DamageModifier;
|
|
+import org.bukkit.event.entity.EntityKnockbackEvent;
|
|
+import org.bukkit.event.entity.EntityPotionEffectEvent;
|
|
+import org.bukkit.event.entity.EntityRegainHealthEvent;
|
|
+import org.bukkit.event.entity.EntityRemoveEvent;
|
|
+import org.bukkit.event.entity.EntityResurrectEvent;
|
|
+import org.bukkit.event.entity.EntityTeleportEvent;
|
|
+import org.bukkit.event.player.PlayerItemConsumeEvent;
|
|
+// CraftBukkit end
|
|
+
|
|
public abstract class LivingEntity extends Entity implements Attackable {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final String TAG_ACTIVE_EFFECTS = "active_effects";
|
|
@@ -266,11 +_,29 @@
|
|
EquipmentSlot.class
|
|
);
|
|
protected float appliedScale = 1.0F;
|
|
+ // CraftBukkit start
|
|
+ public int expToDrop;
|
|
+ public ArrayList<DefaultDrop> drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior
|
|
+ public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
|
|
+ public boolean collides = true;
|
|
+ public Set<UUID> collidableExemptions = new HashSet<>();
|
|
+ public boolean bukkitPickUpLoot;
|
|
+ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
|
|
+ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
|
|
+ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
|
|
+
|
|
+ @Override
|
|
+ public float getBukkitYaw() {
|
|
+ return this.getYHeadRot();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
|
|
protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType));
|
|
- this.setHealth(this.getMaxHealth());
|
|
+ this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
|
|
+ // CraftBukkit - this.setHealth(this.getMaxHealth()) inlined and simplified to skip the instanceof check for Player, as getBukkitEntity() is not initialized in constructor
|
|
+ this.entityData.set(LivingEntity.DATA_HEALTH_ID, this.getMaxHealth());
|
|
this.blocksBuilding = true;
|
|
this.rotA = (float)((Math.random() + 1.0) * 0.01F);
|
|
this.reapplyPosition();
|
|
@@ -360,7 +_,13 @@
|
|
float f = Mth.ceil(this.fallDistance - attributeValue);
|
|
double min = Math.min((double)(0.2F + f / 15.0F), 2.5);
|
|
int i = (int)(150.0 * min);
|
|
- serverLevel.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
|
|
+ // CraftBukkit start - visiblity api
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ serverLevel.sendParticlesSource((ServerPlayer) this, new BlockParticleOption(ParticleTypes.BLOCK, state), false, false, x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
|
|
+ } else {
|
|
+ serverLevel.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
|
|
@@ -566,7 +_,7 @@
|
|
this.deathTime++;
|
|
if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) {
|
|
this.level().broadcastEntityEvent(this, (byte)60);
|
|
- this.remove(Entity.RemovalReason.KILLED);
|
|
+ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
|
|
}
|
|
}
|
|
|
|
@@ -658,7 +_,7 @@
|
|
}
|
|
|
|
public boolean shouldDiscardFriction() {
|
|
- return this.discardFriction;
|
|
+ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API
|
|
}
|
|
|
|
public void setDiscardFriction(boolean discardFriction) {
|
|
@@ -670,11 +_,16 @@
|
|
}
|
|
|
|
public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem) {
|
|
+ // CraftBukkit start
|
|
+ this.onEquipItem(slot, oldItem, newItem, false);
|
|
+ }
|
|
+ public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem, boolean silent) {
|
|
+ // CraftBukkit end
|
|
if (!this.level().isClientSide() && !this.isSpectator()) {
|
|
boolean flag = newItem.isEmpty() && oldItem.isEmpty();
|
|
if (!flag && !ItemStack.isSameItemSameComponents(oldItem, newItem) && !this.firstTick) {
|
|
Equippable equippable = newItem.get(DataComponents.EQUIPPABLE);
|
|
- if (!this.isSilent() && equippable != null && slot == equippable.slot()) {
|
|
+ if (!this.isSilent() && equippable != null && slot == equippable.slot() && !silent) { // CraftBukkit
|
|
this.level()
|
|
.playSeededSound(
|
|
null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong()
|
|
@@ -690,11 +_,18 @@
|
|
|
|
@Override
|
|
public void remove(Entity.RemovalReason reason) {
|
|
+ // CraftBukkit start - add Bukkit remove cause
|
|
+ this.remove(reason, null);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void remove(Entity.RemovalReason reason, EntityRemoveEvent.Cause eventCause) {
|
|
+ // CraftBukkit end
|
|
if ((reason == Entity.RemovalReason.KILLED || reason == Entity.RemovalReason.DISCARDED) && this.level() instanceof ServerLevel serverLevel) {
|
|
this.triggerOnDeathMobEffects(serverLevel, reason);
|
|
}
|
|
|
|
- super.remove(reason);
|
|
+ super.remove(reason, eventCause); // CraftBukkit
|
|
this.brain.clearMemories();
|
|
}
|
|
|
|
@@ -703,11 +_,17 @@
|
|
mobEffectInstance.onMobRemoved(level, this, removalReason);
|
|
}
|
|
|
|
+ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); // CraftBukkit
|
|
this.activeEffects.clear();
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag compound) {
|
|
+ // Paper start - Friction API
|
|
+ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) {
|
|
+ compound.putString("Paper.FrictionState", this.frictionState.toString());
|
|
+ }
|
|
+ // Paper end - Friction API
|
|
compound.putFloat("Health", this.getHealth());
|
|
compound.putShort("HurtTime", (short)this.hurtTime);
|
|
compound.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp);
|
|
@@ -736,7 +_,23 @@
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag compound) {
|
|
- this.internalSetAbsorptionAmount(compound.getFloat("AbsorptionAmount"));
|
|
+ // Paper start - Check for NaN
|
|
+ float absorptionAmount = compound.getFloat("AbsorptionAmount");
|
|
+ if (Float.isNaN(absorptionAmount)) {
|
|
+ absorptionAmount = 0;
|
|
+ }
|
|
+ this.internalSetAbsorptionAmount(absorptionAmount);
|
|
+ // Paper end - Check for NaN
|
|
+ // Paper start - Friction API
|
|
+ if (compound.contains("Paper.FrictionState")) {
|
|
+ String frictionState = compound.getString("Paper.FrictionState");
|
|
+ try {
|
|
+ this.frictionState = net.kyori.adventure.util.TriState.valueOf(frictionState);
|
|
+ } catch (Exception ignored) {
|
|
+ LOGGER.error("Unknown friction state " + frictionState + " for " + this);
|
|
+ }
|
|
+ }
|
|
+ // Paper end - Friction API
|
|
if (compound.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) {
|
|
this.getAttributes().load(compound.getList("attributes", 10));
|
|
}
|
|
@@ -753,6 +_,16 @@
|
|
}
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ if (compound.contains("Bukkit.MaxHealth")) {
|
|
+ Tag maxHealthTag = compound.get("Bukkit.MaxHealth");
|
|
+ if (maxHealthTag.getId() == 5) {
|
|
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) maxHealthTag).getAsDouble());
|
|
+ } else if (maxHealthTag.getId() == 3) {
|
|
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) maxHealthTag).getAsDouble());
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (compound.contains("Health", 99)) {
|
|
this.setHealth(compound.getFloat("Health"));
|
|
}
|
|
@@ -764,6 +_,7 @@
|
|
String string = compound.getString("Team");
|
|
Scoreboard scoreboard = this.level().getScoreboard();
|
|
PlayerTeam playerTeam = scoreboard.getPlayerTeam(string);
|
|
+ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { playerTeam = null; } // Paper - Perf: Disable Scoreboards for non players by default
|
|
boolean flag = playerTeam != null && scoreboard.addPlayerToTeam(this.getStringUUID(), playerTeam);
|
|
if (!flag) {
|
|
LOGGER.warn("Unable to add mob to team \"{}\" (that team probably doesn't exist)", string);
|
|
@@ -776,11 +_,13 @@
|
|
|
|
if (compound.contains("SleepingX", 99) && compound.contains("SleepingY", 99) && compound.contains("SleepingZ", 99)) {
|
|
BlockPos blockPos = new BlockPos(compound.getInt("SleepingX"), compound.getInt("SleepingY"), compound.getInt("SleepingZ"));
|
|
+ if (this.position().distanceToSqr(blockPos.getX(), blockPos.getY(), blockPos.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong
|
|
this.setSleepingPos(blockPos);
|
|
this.entityData.set(DATA_POSE, Pose.SLEEPING);
|
|
if (!this.firstTick) {
|
|
this.setPosToBed(blockPos);
|
|
}
|
|
+ } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong
|
|
}
|
|
|
|
if (compound.contains("Brain", 10)) {
|
|
@@ -788,15 +_,44 @@
|
|
}
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ private boolean isTickingEffects = false;
|
|
+ private List<ProcessableEffect> effectsToProcess = Lists.newArrayList();
|
|
+
|
|
+ private static class ProcessableEffect {
|
|
+
|
|
+ private Holder<MobEffect> type;
|
|
+ private MobEffectInstance effect;
|
|
+ private final EntityPotionEffectEvent.Cause cause;
|
|
+
|
|
+ private ProcessableEffect(MobEffectInstance effect, EntityPotionEffectEvent.Cause cause) {
|
|
+ this.effect = effect;
|
|
+ this.cause = cause;
|
|
+ }
|
|
+
|
|
+ private ProcessableEffect(Holder<MobEffect> type, EntityPotionEffectEvent.Cause cause) {
|
|
+ this.type = type;
|
|
+ this.cause = cause;
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
protected void tickEffects() {
|
|
Iterator<Holder<MobEffect>> iterator = this.activeEffects.keySet().iterator();
|
|
|
|
+ this.isTickingEffects = true; // CraftBukkit
|
|
try {
|
|
while (iterator.hasNext()) {
|
|
Holder<MobEffect> holder = iterator.next();
|
|
MobEffectInstance mobEffectInstance = this.activeEffects.get(holder);
|
|
if (!mobEffectInstance.tick(this, () -> this.onEffectUpdated(mobEffectInstance, true, null))) {
|
|
if (!this.level().isClientSide) {
|
|
+ // CraftBukkit start
|
|
+ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobEffectInstance, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION);
|
|
+ if (event.isCancelled()) {
|
|
+ continue;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
iterator.remove();
|
|
this.onEffectsRemoved(List.of(mobEffectInstance));
|
|
}
|
|
@@ -807,6 +_,17 @@
|
|
} catch (ConcurrentModificationException var6) {
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ this.isTickingEffects = false;
|
|
+ for (ProcessableEffect effect : this.effectsToProcess) {
|
|
+ if (effect.effect != null) {
|
|
+ this.addEffect(effect.effect, effect.cause);
|
|
+ } else {
|
|
+ this.removeEffect(effect.type, effect.cause);
|
|
+ }
|
|
+ }
|
|
+ this.effectsToProcess.clear();
|
|
+ // CraftBukkit end
|
|
if (this.effectsDirty) {
|
|
if (!this.level().isClientSide) {
|
|
this.updateInvisibilityStatus();
|
|
@@ -912,15 +_,33 @@
|
|
}
|
|
|
|
public boolean removeAllEffects() {
|
|
+ // CraftBukkit start
|
|
+ return this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
|
|
+ }
|
|
+ public boolean removeAllEffects(EntityPotionEffectEvent.Cause cause) {
|
|
+ // CraftBukkit end
|
|
if (this.level().isClientSide) {
|
|
return false;
|
|
} else if (this.activeEffects.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
- Map<Holder<MobEffect>, MobEffectInstance> map = Maps.newHashMap(this.activeEffects);
|
|
- this.activeEffects.clear();
|
|
- this.onEffectsRemoved(map.values());
|
|
- return true;
|
|
+ // CraftBukkit start
|
|
+ List<MobEffectInstance> toRemove = new LinkedList<>();
|
|
+ Iterator<MobEffectInstance> iterator = this.activeEffects.values().iterator();
|
|
+ while (iterator.hasNext()) {
|
|
+ MobEffectInstance effect = iterator.next();
|
|
+ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
|
|
+ if (event.isCancelled()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ iterator.remove();
|
|
+ toRemove.add(effect);
|
|
+ }
|
|
+
|
|
+ this.onEffectsRemoved(toRemove);
|
|
+ return !toRemove.isEmpty();
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
|
|
@@ -942,21 +_,57 @@
|
|
}
|
|
|
|
public final boolean addEffect(MobEffectInstance effectInstance) {
|
|
- return this.addEffect(effectInstance, null);
|
|
+ return this.addEffect(effectInstance, (Entity) null); // CraftBukkit
|
|
+ }
|
|
+
|
|
+ // CraftBukkit start
|
|
+ public boolean addEffect(MobEffectInstance effectInstance, EntityPotionEffectEvent.Cause cause) {
|
|
+ return this.addEffect(effectInstance, (Entity) null, cause);
|
|
}
|
|
|
|
public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) {
|
|
+ return this.addEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
|
|
+ }
|
|
+
|
|
+ public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
|
|
+ // Paper start - Don't fire sync event during generation
|
|
+ return this.addEffect(effectInstance, entity, cause, true);
|
|
+ }
|
|
+ public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
|
|
+ // Paper end - Don't fire sync event during generation
|
|
+ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API
|
|
+ if (this.isTickingEffects) {
|
|
+ this.effectsToProcess.add(new ProcessableEffect(effectInstance, cause));
|
|
+ return true;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (!this.canBeAffected(effectInstance)) {
|
|
return false;
|
|
} else {
|
|
MobEffectInstance mobEffectInstance = this.activeEffects.get(effectInstance.getEffect());
|
|
boolean flag = false;
|
|
+ // CraftBukkit start
|
|
+ boolean override = false;
|
|
+ if (mobEffectInstance != null) {
|
|
+ override = new MobEffectInstance(mobEffectInstance).update(effectInstance);
|
|
+ }
|
|
+
|
|
+ if (fireEvent) { // Paper - Don't fire sync event during generation
|
|
+ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobEffectInstance, effectInstance, cause, override);
|
|
+ override = event.isOverride(); // Paper - Don't fire sync event during generation
|
|
+ if (event.isCancelled()) {
|
|
+ return false;
|
|
+ }
|
|
+ } // Paper - Don't fire sync event during generation
|
|
+ // CraftBukkit end
|
|
if (mobEffectInstance == null) {
|
|
this.activeEffects.put(effectInstance.getEffect(), effectInstance);
|
|
this.onEffectAdded(effectInstance, entity);
|
|
flag = true;
|
|
effectInstance.onEffectAdded(this);
|
|
- } else if (mobEffectInstance.update(effectInstance)) {
|
|
+ // CraftBukkit start
|
|
+ } else if (override) { // Paper - Don't fire sync event during generation
|
|
+ mobEffectInstance.update(effectInstance);
|
|
this.onEffectUpdated(mobEffectInstance, true, entity);
|
|
flag = true;
|
|
}
|
|
@@ -995,11 +_,37 @@
|
|
|
|
@Nullable
|
|
public MobEffectInstance removeEffectNoUpdate(Holder<MobEffect> effect) {
|
|
+ // CraftBukkit start
|
|
+ return this.removeEffectNoUpdate(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public MobEffectInstance removeEffectNoUpdate(Holder<MobEffect> effect, EntityPotionEffectEvent.Cause cause) {
|
|
+ if (this.isTickingEffects) {
|
|
+ this.effectsToProcess.add(new ProcessableEffect(effect, cause));
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ MobEffectInstance effectInstance = this.activeEffects.get(effect);
|
|
+ if (effectInstance == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effectInstance, null, cause);
|
|
+ if (event.isCancelled()) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
return this.activeEffects.remove(effect);
|
|
}
|
|
|
|
public boolean removeEffect(Holder<MobEffect> effect) {
|
|
- MobEffectInstance mobEffectInstance = this.removeEffectNoUpdate(effect);
|
|
+ return this.removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
|
|
+ }
|
|
+
|
|
+ public boolean removeEffect(Holder<MobEffect> effect, EntityPotionEffectEvent.Cause cause) {
|
|
+ MobEffectInstance mobEffectInstance = this.removeEffectNoUpdate(effect, cause);
|
|
+ // CraftBukkit end
|
|
if (mobEffectInstance != null) {
|
|
this.onEffectsRemoved(List.of(mobEffectInstance));
|
|
return true;
|
|
@@ -1080,17 +_,62 @@
|
|
}
|
|
|
|
public void heal(float healAmount) {
|
|
+ // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
|
|
+ this.heal(healAmount, EntityRegainHealthEvent.RegainReason.CUSTOM);
|
|
+ }
|
|
+
|
|
+ public void heal(float healAmount, EntityRegainHealthEvent.RegainReason regainReason) {
|
|
+ // Paper start - Forward
|
|
+ this.heal(healAmount, regainReason, false);
|
|
+ }
|
|
+
|
|
+ public void heal(float healAmount, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) {
|
|
+ // Paper end - Forward
|
|
float health = this.getHealth();
|
|
if (health > 0.0F) {
|
|
- this.setHealth(health + healAmount);
|
|
+ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), healAmount, regainReason, isFastRegen); // Paper
|
|
+ // Suppress during worldgen
|
|
+ if (this.valid) {
|
|
+ this.level().getCraftServer().getPluginManager().callEvent(event);
|
|
+ }
|
|
+
|
|
+ if (!event.isCancelled()) {
|
|
+ this.setHealth((float) (this.getHealth() + event.getAmount()));
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
|
|
public float getHealth() {
|
|
+ // CraftBukkit start - Use unscaled health
|
|
+ if (this instanceof ServerPlayer serverPlayer) {
|
|
+ return (float) serverPlayer.getBukkitEntity().getHealth();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
return this.entityData.get(DATA_HEALTH_ID);
|
|
}
|
|
|
|
public void setHealth(float health) {
|
|
+ // Paper start - Check for NaN
|
|
+ if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) {
|
|
+ System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set");
|
|
+ } } // Paper end - Check for NaN
|
|
+ // CraftBukkit start - Handle scaled health
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity();
|
|
+ // Squeeze
|
|
+ if (health < 0.0F) {
|
|
+ player.setRealHealth(0.0D);
|
|
+ } else if (health > player.getMaxHealth()) {
|
|
+ player.setRealHealth(player.getMaxHealth());
|
|
+ } else {
|
|
+ player.setRealHealth(health);
|
|
+ }
|
|
+
|
|
+ player.updateScaledHealth(false);
|
|
+ return;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.entityData.set(DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
|
|
}
|
|
|
|
@@ -1102,7 +_,7 @@
|
|
public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
|
|
if (this.isInvulnerableTo(level, damageSource)) {
|
|
return false;
|
|
- } else if (this.isDeadOrDying()) {
|
|
+ } else if (this.isRemoved() || this.dead || this.getHealth() <= 0.0F) { // CraftBukkit - Don't allow entities that got set to dead/killed elsewhere to get damaged and die
|
|
return false;
|
|
} else if (damageSource.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
|
|
return false;
|
|
@@ -1116,47 +_,71 @@
|
|
amount = 0.0F;
|
|
}
|
|
|
|
- float f = amount;
|
|
- boolean flag = false;
|
|
+ float f = amount; final float originalAmount = amount; // Paper - revert to vanilla #hurt - OBFHELPER
|
|
+ boolean flag = amount > 0.0F && this.isDamageSourceBlocked(damageSource); // Copied from below;
|
|
float f1 = 0.0F;
|
|
- if (amount > 0.0F && this.isDamageSourceBlocked(damageSource)) {
|
|
+ // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f and actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage
|
|
+ if (false && amount > 0.0F && this.isDamageSourceBlocked(damageSource)) {
|
|
this.hurtCurrentlyUsedShield(amount);
|
|
f1 = amount;
|
|
amount = 0.0F;
|
|
- if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity) {
|
|
+ if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity && livingEntity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Check distance in entity interactions
|
|
this.blockUsingShield(livingEntity);
|
|
}
|
|
|
|
flag = true;
|
|
}
|
|
|
|
- if (damageSource.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
|
|
+ // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f
|
|
+ if (false && damageSource.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
|
|
amount *= 5.0F;
|
|
}
|
|
|
|
- if (damageSource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
|
|
+ // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f and actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage
|
|
+ if (false && damageSource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
|
|
this.hurtHelmet(damageSource, amount);
|
|
amount *= 0.75F;
|
|
}
|
|
|
|
+ EntityDamageEvent event; // CraftBukkit // Paper - move this into the actual invuln check....
|
|
this.walkAnimation.setSpeed(1.5F);
|
|
if (Float.isNaN(amount) || Float.isInfinite(amount)) {
|
|
amount = Float.MAX_VALUE;
|
|
}
|
|
|
|
boolean flag1 = true;
|
|
- if (this.invulnerableTime > 10.0F && !damageSource.is(DamageTypeTags.BYPASSES_COOLDOWN)) {
|
|
+ if (this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && !damageSource.is(DamageTypeTags.BYPASSES_COOLDOWN)) { // CraftBukkit - restore use of maxNoDamageTicks
|
|
if (amount <= this.lastHurt) {
|
|
return false;
|
|
}
|
|
|
|
- this.actuallyHurt(level, damageSource, amount - this.lastHurt);
|
|
+ // Paper start - only call damage event when actuallyHurt will be called - move call logic down
|
|
+ event = this.handleEntityDamage(damageSource, amount, this.lastHurt); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction
|
|
+ amount = computeAmountFromEntityDamageEvent(event);
|
|
+ // Paper end - only call damage event when actuallyHurt will be called - move call logic down
|
|
+
|
|
+ // CraftBukkit start
|
|
+ if (!this.actuallyHurt(level, damageSource, (float) event.getFinalDamage(), event)) { // Paper - fix invulnerability reduction in EntityDamageEvent - no longer subtract lastHurt, that is part of the damage event calc now
|
|
+ return false;
|
|
+ }
|
|
+ if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event.
|
|
+ // CraftBukkit end
|
|
this.lastHurt = amount;
|
|
flag1 = false;
|
|
} else {
|
|
+ // Paper start - only call damage event when actuallyHurt will be called - move call logic down
|
|
+ event = this.handleEntityDamage(damageSource, amount, 0); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction (none in this branch)
|
|
+ amount = computeAmountFromEntityDamageEvent(event);
|
|
+ // Paper end - only call damage event when actuallyHurt will be called - move call logic down
|
|
+ // CraftBukkit start
|
|
+ if (!this.actuallyHurt(level, damageSource, (float) event.getFinalDamage(), event)) {
|
|
+ return false;
|
|
+ }
|
|
+ if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event.
|
|
this.lastHurt = amount;
|
|
- this.invulnerableTime = 20;
|
|
- this.actuallyHurt(level, damageSource, amount);
|
|
+ this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks
|
|
+ // this.actuallyHurt(level, damageSource, amount);
|
|
+ // CraftBukkit end
|
|
this.hurtDuration = 10;
|
|
this.hurtTime = this.hurtDuration;
|
|
}
|
|
@@ -1170,7 +_,7 @@
|
|
level.broadcastDamageEvent(this, damageSource);
|
|
}
|
|
|
|
- if (!damageSource.is(DamageTypeTags.NO_IMPACT) && (!flag || amount > 0.0F)) {
|
|
+ if (!damageSource.is(DamageTypeTags.NO_IMPACT) && !flag) { // CraftBukkit - Prevent marking hurt if the damage is blocked
|
|
this.markHurt();
|
|
}
|
|
|
|
@@ -1185,8 +_,16 @@
|
|
d = damageSource.getSourcePosition().x() - this.getX();
|
|
d1 = damageSource.getSourcePosition().z() - this.getZ();
|
|
}
|
|
+ // Paper start - Check distance in entity interactions; see for loop in knockback method
|
|
+ if (Math.abs(d) > 200) {
|
|
+ d = Math.random() - Math.random();
|
|
+ }
|
|
+ if (Math.abs(d1) > 200) {
|
|
+ d1 = Math.random() - Math.random();
|
|
+ }
|
|
+ // Paper end - Check distance in entity interactions
|
|
|
|
- this.knockback(0.4F, d, d1);
|
|
+ this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
|
|
if (!flag) {
|
|
this.indicateDamage(d, d1);
|
|
}
|
|
@@ -1195,17 +_,18 @@
|
|
|
|
if (this.isDeadOrDying()) {
|
|
if (!this.checkTotemDeathProtection(damageSource)) {
|
|
- if (flag1) {
|
|
- this.makeSound(this.getDeathSound());
|
|
- }
|
|
+ // Paper start - moved into CraftEventFactory event caller for cancellable death event
|
|
+ this.silentDeath = !flag1; // mark entity as dying silently
|
|
+ // Paper end
|
|
|
|
this.die(damageSource);
|
|
+ this.silentDeath = false; // Paper - cancellable death event - reset to default
|
|
}
|
|
} else if (flag1) {
|
|
this.playHurtSound(damageSource);
|
|
}
|
|
|
|
- boolean flag2 = !flag || amount > 0.0F;
|
|
+ boolean flag2 = !flag; // CraftBukkit - Ensure to return false if damage is blocked
|
|
if (flag2) {
|
|
this.lastDamageSource = damageSource;
|
|
this.lastDamageStamp = this.level().getGameTime();
|
|
@@ -1259,12 +_,24 @@
|
|
}
|
|
}
|
|
|
|
+ // Paper start - only call damage event when actuallyHurt will be called - move out amount computation logic
|
|
+ private float computeAmountFromEntityDamageEvent(final EntityDamageEvent event) {
|
|
+ // Taken from hurt()'s craftbukkit diff.
|
|
+ float amount = 0;
|
|
+ amount += (float) event.getDamage(DamageModifier.BASE);
|
|
+ amount += (float) event.getDamage(DamageModifier.BLOCKING);
|
|
+ amount += (float) event.getDamage(DamageModifier.FREEZING);
|
|
+ amount += (float) event.getDamage(DamageModifier.HARD_HAT);
|
|
+ return amount;
|
|
+ }
|
|
+ // Paper end - only call damage event when actuallyHurt will be called - move out amount computation logic
|
|
+
|
|
protected void blockUsingShield(LivingEntity attacker) {
|
|
attacker.blockedByShield(this);
|
|
}
|
|
|
|
protected void blockedByShield(LivingEntity defender) {
|
|
- defender.knockback(0.5, defender.getX() - this.getX(), defender.getZ() - this.getZ());
|
|
+ defender.knockback(0.5, defender.getX() - this.getX(), defender.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events
|
|
}
|
|
|
|
private boolean checkTotemDeathProtection(DamageSource damageSource) {
|
|
@@ -1274,18 +_,39 @@
|
|
ItemStack itemStack = null;
|
|
DeathProtection deathProtection = null;
|
|
|
|
+ // CraftBukkit start
|
|
+ InteractionHand hand = null;
|
|
+ ItemStack itemInHand = ItemStack.EMPTY;
|
|
for (InteractionHand interactionHand : InteractionHand.values()) {
|
|
- ItemStack itemInHand = this.getItemInHand(interactionHand);
|
|
+ itemInHand = this.getItemInHand(interactionHand);
|
|
deathProtection = itemInHand.get(DataComponents.DEATH_PROTECTION);
|
|
if (deathProtection != null) {
|
|
+ hand = interactionHand; // CraftBukkit
|
|
itemStack = itemInHand.copy();
|
|
+ // itemInHand.shrink(1); // CraftBukkit
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
|
|
+ final EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
|
|
+ event.setCancelled(itemStack == null);
|
|
+ this.level().getCraftServer().getPluginManager().callEvent(event);
|
|
+ if (event.isCancelled()) {
|
|
+ // Set death protection to null as the event was cancelled. Prevent any attempt at ressurection.
|
|
+ deathProtection = null;
|
|
+ } else {
|
|
+ if (!itemInHand.isEmpty() && itemStack != null) { // Paper - only reduce item if actual totem was found
|
|
itemInHand.shrink(1);
|
|
- break;
|
|
- }
|
|
- }
|
|
-
|
|
- if (itemStack != null) {
|
|
- if (this instanceof ServerPlayer serverPlayer) {
|
|
+ }
|
|
+ // Paper start - fix NPE when pre-cancelled EntityResurrectEvent is uncancelled
|
|
+ // restore the previous behavior in that case by defaulting to vanillas totem of undying efect
|
|
+ if (deathProtection == null) {
|
|
+ deathProtection = DeathProtection.TOTEM_OF_UNDYING;
|
|
+ }
|
|
+ // Paper end - fix NPE when pre-cancelled EntityResurrectEvent is uncancelled
|
|
+ if (itemStack != null && this instanceof final ServerPlayer serverPlayer) {
|
|
+ // CraftBukkit end
|
|
serverPlayer.awardStat(Stats.ITEM_USED.get(itemStack.getItem()));
|
|
CriteriaTriggers.USED_TOTEM.trigger(serverPlayer, itemStack);
|
|
this.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
|
|
@@ -1364,6 +_,7 @@
|
|
if (!this.isRemoved() && !this.dead) {
|
|
Entity entity = damageSource.getEntity();
|
|
LivingEntity killCredit = this.getKillCredit();
|
|
+ /* // Paper - move down to make death event cancellable - this is the awardKillScore below
|
|
if (killCredit != null) {
|
|
killCredit.awardKillScore(this, damageSource);
|
|
}
|
|
@@ -1373,68 +_,142 @@
|
|
}
|
|
|
|
if (!this.level().isClientSide && this.hasCustomName()) {
|
|
- LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString());
|
|
+ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
|
|
}
|
|
+ */ // Paper - move down to make death event cancellable - this is the awardKillScore below
|
|
|
|
this.dead = true;
|
|
- this.getCombatTracker().recheckStatus();
|
|
+ // Paper - moved into if below
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
- if (entity == null || entity.killedEntity(serverLevel, this)) {
|
|
+ // Paper - move below into if for onKill
|
|
+ // Paper start
|
|
+ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(serverLevel, damageSource);
|
|
+ if (deathEvent == null || !deathEvent.isCancelled()) {
|
|
+ //if (entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent
|
|
+ // entityliving.awardKillScore(this, damageSource);
|
|
+ //}
|
|
+ // Paper start - clear equipment if event is not cancelled
|
|
+ if (this instanceof Mob) {
|
|
+ for (EquipmentSlot slot : this.clearedEquipmentSlots) {
|
|
+ this.setItemSlot(slot, ItemStack.EMPTY);
|
|
+ }
|
|
+ this.clearedEquipmentSlots.clear();
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
+ if (this.isSleeping()) {
|
|
+ this.stopSleeping();
|
|
+ }
|
|
+
|
|
+ if (!this.level().isClientSide && this.hasCustomName()) {
|
|
+ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
|
|
+ }
|
|
+
|
|
+ this.getCombatTracker().recheckStatus();
|
|
+ if (entity != null) {
|
|
+ entity.killedEntity((ServerLevel) this.level(), this);
|
|
+ }
|
|
this.gameEvent(GameEvent.ENTITY_DIE);
|
|
- this.dropAllDeathLoot(serverLevel, damageSource);
|
|
+ } else {
|
|
+ this.dead = false;
|
|
+ this.setHealth((float) deathEvent.getReviveHealth());
|
|
+ }
|
|
+ // Paper end
|
|
this.createWitherRose(killCredit);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ if (this.dead) { // Paper
|
|
this.level().broadcastEntityEvent(this, (byte)3);
|
|
- }
|
|
|
|
this.setPose(Pose.DYING);
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
protected void createWitherRose(@Nullable LivingEntity entitySource) {
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
boolean var6 = false;
|
|
- if (entitySource instanceof WitherBoss) {
|
|
+ if (this.dead && entitySource instanceof WitherBoss) { // Paper
|
|
if (serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
|
BlockPos blockPos = this.blockPosition();
|
|
BlockState blockState = Blocks.WITHER_ROSE.defaultBlockState();
|
|
if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) {
|
|
- this.level().setBlock(blockPos, blockState, 3);
|
|
- var6 = true;
|
|
+ var6 = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockPos, blockState, 3, this); // CraftBukkit - call EntityBlockFormEvent for Wither Rose
|
|
}
|
|
}
|
|
|
|
if (!var6) {
|
|
ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
|
|
+ // CraftBukkit start
|
|
+ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
|
|
+ CraftEventFactory.callEvent(event);
|
|
+ if (event.isCancelled()) {
|
|
+ return;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.level().addFreshEntity(itemEntity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- protected void dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
|
|
+ // Paper start
|
|
+ protected boolean clearEquipmentSlots = true;
|
|
+ protected Set<EquipmentSlot> clearedEquipmentSlots = new java.util.HashSet<>();
|
|
+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
|
|
+ // Paper end
|
|
boolean flag = this.lastHurtByPlayerTime > 0;
|
|
+ this.dropEquipment(level); // CraftBukkit - from below
|
|
if (this.shouldDropLoot() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
|
|
this.dropFromLootTable(level, damageSource, flag);
|
|
+ // Paper start
|
|
+ final boolean prev = this.clearEquipmentSlots;
|
|
+ this.clearEquipmentSlots = false;
|
|
+ this.clearedEquipmentSlots.clear();
|
|
+ // Paper end
|
|
this.dropCustomDeathLoot(level, damageSource, flag);
|
|
+ this.clearEquipmentSlots = prev; // Paper
|
|
}
|
|
|
|
- this.dropEquipment(level);
|
|
+ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
|
|
+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> {
|
|
+ final LivingEntity entityliving = this.getKillCredit();
|
|
+ if (entityliving != null) {
|
|
+ entityliving.awardKillScore(this, damageSource);
|
|
+ }
|
|
+ }); // Paper end
|
|
+ this.postDeathDropItems(deathEvent); // Paper
|
|
+ this.drops = new ArrayList<>();
|
|
+ // this.dropEquipment(level); // CraftBukkit - moved up
|
|
+ // CraftBukkit end
|
|
this.dropExperience(level, damageSource.getEntity());
|
|
+ return deathEvent; // Paper
|
|
}
|
|
|
|
protected void dropEquipment(ServerLevel level) {
|
|
}
|
|
+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
|
|
|
|
- protected void dropExperience(ServerLevel level, @Nullable Entity entity) {
|
|
+ public int getExpReward(ServerLevel level, @Nullable Entity entity) { // CraftBukkit
|
|
if (!this.wasExperienceConsumed()
|
|
&& (
|
|
this.isAlwaysExperienceDropper()
|
|
|| this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)
|
|
)) {
|
|
- ExperienceOrb.award(level, this.position(), this.getExperienceReward(level, entity));
|
|
- }
|
|
+ return this.getExperienceReward(level, entity); // CraftBukkit
|
|
+ }
|
|
+ return 0; // CraftBukkit
|
|
+ }
|
|
+
|
|
+ protected void dropExperience(ServerLevel level, @Nullable Entity entity) {
|
|
+ // CraftBukkit start - Update getExpReward() above if the removed if() changes!
|
|
+ if (!(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
|
|
+ ExperienceOrb.award(level, this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, entity, this); // Paper
|
|
+ this.expToDrop = 0;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
|
|
@@ -1513,9 +_,14 @@
|
|
}
|
|
|
|
public void knockback(double strength, double x, double z) {
|
|
+ // CraftBukkit start - EntityKnockbackEvent
|
|
+ this.knockback(strength, x, z, null, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.UNKNOWN); // Paper - knockback events
|
|
+ }
|
|
+
|
|
+ public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause eventCause) { // Paper - knockback events
|
|
strength *= 1.0 - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
|
|
- if (!(strength <= 0.0)) {
|
|
- this.hasImpulse = true;
|
|
+ if (true || !(strength <= 0.0)) { // CraftBukkit - Call event even when force is 0
|
|
+ // this.hasImpulse = true; // CraftBukkit - Move down
|
|
Vec3 deltaMovement = this.getDeltaMovement();
|
|
|
|
while (x * x + z * z < 1.0E-5F) {
|
|
@@ -1524,11 +_,22 @@
|
|
}
|
|
|
|
Vec3 vec3 = new Vec3(x, 0.0, z).normalize().scale(strength);
|
|
- this.setDeltaMovement(
|
|
+ // Paper start - knockback events
|
|
+ Vec3 finalVelocity = new Vec3(
|
|
deltaMovement.x / 2.0 - vec3.x,
|
|
this.onGround() ? Math.min(0.4, deltaMovement.y / 2.0 + strength) : deltaMovement.y,
|
|
deltaMovement.z / 2.0 - vec3.z
|
|
);
|
|
+ Vec3 diff = finalVelocity.subtract(deltaMovement);
|
|
+ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, eventCause, strength, diff);
|
|
+ // Paper end - knockback events
|
|
+ if (event.isCancelled()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ this.hasImpulse = true;
|
|
+ this.setDeltaMovement(deltaMovement.add(event.getKnockback().getX(), event.getKnockback().getY(), event.getKnockback().getZ())); // Paper - knockback events
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
|
|
@@ -1584,6 +_,20 @@
|
|
return new LivingEntity.Fallsounds(SoundEvents.GENERIC_SMALL_FALL, SoundEvents.GENERIC_BIG_FALL);
|
|
}
|
|
|
|
+ // CraftBukkit start - Add delegate methods
|
|
+ public SoundEvent getHurtSound0(DamageSource damagesource) {
|
|
+ return this.getHurtSound(damagesource);
|
|
+ }
|
|
+
|
|
+ public SoundEvent getDeathSound0() {
|
|
+ return this.getDeathSound();
|
|
+ }
|
|
+
|
|
+ public SoundEvent getFallDamageSound0(int fallHeight) {
|
|
+ return this.getFallDamageSound(fallHeight);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public Optional<BlockPos> getLastClimbablePos() {
|
|
return this.lastClimbablePos;
|
|
}
|
|
@@ -1617,7 +_,7 @@
|
|
|
|
@Override
|
|
public boolean isAlive() {
|
|
- return !this.isRemoved() && this.getHealth() > 0.0F;
|
|
+ return !this.isRemoved() && this.getHealth() > 0.0F && !this.dead; // Paper - Check this.dead
|
|
}
|
|
|
|
public boolean isLookingAtMe(LivingEntity entity, double tolerance, boolean scaleByDistance, boolean visual, double... yValues) {
|
|
@@ -1651,9 +_,14 @@
|
|
boolean flag = super.causeFallDamage(fallDistance, multiplier, source);
|
|
int i = this.calculateFallDamage(fallDistance, multiplier);
|
|
if (i > 0) {
|
|
+ // CraftBukkit start
|
|
+ if (!this.hurtServer((ServerLevel) this.level(), source, (float) i)) {
|
|
+ return true;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.playSound(this.getFallDamageSound(i), 1.0F, 1.0F);
|
|
this.playBlockFallSound();
|
|
- this.hurt(source, i);
|
|
+ // this.hurt(source, i); // CraftBukkit - moved up
|
|
return true;
|
|
} else {
|
|
return flag;
|
|
@@ -1718,7 +_,7 @@
|
|
|
|
protected float getDamageAfterArmorAbsorb(DamageSource damageSource, float damageAmount) {
|
|
if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
|
|
- this.hurtArmor(damageSource, damageAmount);
|
|
+ // this.hurtArmor(damageSource, damageAmount); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for damage handling
|
|
damageAmount = CombatRules.getDamageAfterAbsorb(
|
|
this, damageAmount, damageSource, this.getArmorValue(), (float)this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)
|
|
);
|
|
@@ -1731,7 +_,8 @@
|
|
if (damageSource.is(DamageTypeTags.BYPASSES_EFFECTS)) {
|
|
return damageAmount;
|
|
} else {
|
|
- if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damageSource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
|
|
+ // CraftBukkit - Moved to handleEntityDamage(DamageSource, float)
|
|
+ if (false && this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damageSource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
|
|
int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
|
|
int i1 = 25 - i;
|
|
float f = damageAmount * i1;
|
|
@@ -1768,24 +_,212 @@
|
|
}
|
|
}
|
|
|
|
- protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
|
|
+ // CraftBukkit start
|
|
+ private EntityDamageEvent handleEntityDamage(final DamageSource damagesource, float amount, final float invulnerabilityRelatedLastDamage) { // Paper - fix invulnerability reduction in EntityDamageEvent
|
|
+ float originalDamage = amount;
|
|
+ // Paper start - fix invulnerability reduction in EntityDamageEvent
|
|
+ final com.google.common.base.Function<Double, Double> invulnerabilityReductionEquation = d -> {
|
|
+ if (invulnerabilityRelatedLastDamage == 0) return 0D; // no last damage, no reduction
|
|
+ // last damage existed, this means the reduction *technically* is (new damage - last damage).
|
|
+ // If the event damage was changed to something less than invul damage, hard lock it at 0.
|
|
+ if (d < invulnerabilityRelatedLastDamage) return 0D;
|
|
+ return (double) -invulnerabilityRelatedLastDamage;
|
|
+ };
|
|
+ final float originalInvulnerabilityReduction = invulnerabilityReductionEquation.apply((double) amount).floatValue();
|
|
+ amount += originalInvulnerabilityReduction;
|
|
+ // Paper end - fix invulnerability reduction in EntityDamageEvent
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> freezing = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ if (damagesource.is(DamageTypeTags.IS_FREEZING) && LivingEntity.this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
|
|
+ return -(f - (f * 5.0F));
|
|
+ }
|
|
+ return -0.0;
|
|
+ }
|
|
+ };
|
|
+ float freezingModifier = freezing.apply((double) amount).floatValue();
|
|
+ amount += freezingModifier;
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> hardHat = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !LivingEntity.this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
|
|
+ return -(f - (f * 0.75F));
|
|
+ }
|
|
+ return -0.0;
|
|
+ }
|
|
+ };
|
|
+ float hardHatModifier = hardHat.apply((double) amount).floatValue();
|
|
+ amount += hardHatModifier;
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> blocking = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0);
|
|
+ }
|
|
+ };
|
|
+ float blockingModifier = blocking.apply((double) amount).floatValue();
|
|
+ amount += blockingModifier;
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> armor = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ return -(f - LivingEntity.this.getDamageAfterArmorAbsorb(damagesource, f.floatValue()));
|
|
+ }
|
|
+ };
|
|
+ float armorModifier = armor.apply((double) amount).floatValue();
|
|
+ amount += armorModifier;
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> resistance = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ if (!damagesource.is(DamageTypeTags.BYPASSES_EFFECTS) && LivingEntity.this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damagesource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
|
|
+ int i = (LivingEntity.this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
|
|
+ int j = 25 - i;
|
|
+ float f1 = f.floatValue() * (float) j;
|
|
+
|
|
+ return -(f - Math.max(f1 / 25.0F, 0.0F));
|
|
+ }
|
|
+ return -0.0;
|
|
+ }
|
|
+ };
|
|
+ float resistanceModifier = resistance.apply((double) amount).floatValue();
|
|
+ amount += resistanceModifier;
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> magic = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ return -(f - LivingEntity.this.getDamageAfterMagicAbsorb(damagesource, f.floatValue()));
|
|
+ }
|
|
+ };
|
|
+ float magicModifier = magic.apply((double) amount).floatValue();
|
|
+ amount += magicModifier;
|
|
+
|
|
+ com.google.common.base.Function<Double, Double> absorption = new com.google.common.base.Function<Double, Double>() {
|
|
+ @Override
|
|
+ public Double apply(Double f) {
|
|
+ return -(Math.max(f - Math.max(f - LivingEntity.this.getAbsorptionAmount(), 0.0F), 0.0F));
|
|
+ }
|
|
+ };
|
|
+ float absorptionModifier = absorption.apply((double) amount).floatValue();
|
|
+
|
|
+ // Paper start - fix invulnerability reduction in EntityDamageEvent
|
|
+ return CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, freezingModifier, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, freezing, hardHat, blocking, armor, resistance, magic, absorption, (damageModifierDoubleMap, damageModifierFunctionMap) -> {
|
|
+ damageModifierFunctionMap.put(DamageModifier.INVULNERABILITY_REDUCTION, invulnerabilityReductionEquation);
|
|
+ damageModifierDoubleMap.put(DamageModifier.INVULNERABILITY_REDUCTION, (double) originalInvulnerabilityReduction);
|
|
+ });
|
|
+ // Paper end - fix invulnerability reduction in EntityDamageEvent
|
|
+ }
|
|
+
|
|
+ protected boolean actuallyHurt(ServerLevel level, final DamageSource damageSource, float amount, final EntityDamageEvent event) { // void -> boolean, add final
|
|
if (!this.isInvulnerableTo(level, damageSource)) {
|
|
- amount = this.getDamageAfterArmorAbsorb(damageSource, amount);
|
|
- amount = this.getDamageAfterMagicAbsorb(damageSource, amount);
|
|
- float var10 = Math.max(amount - this.getAbsorptionAmount(), 0.0F);
|
|
- this.setAbsorptionAmount(this.getAbsorptionAmount() - (amount - var10));
|
|
- float f1 = amount - var10;
|
|
+ if (event.isCancelled()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (damageSource.getEntity() instanceof net.minecraft.world.entity.player.Player) {
|
|
+ // Paper start - PlayerAttackEntityCooldownResetEvent
|
|
+ //((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired
|
|
+ if (damageSource.getEntity() instanceof ServerPlayer) {
|
|
+ ServerPlayer player = (ServerPlayer) damageSource.getEntity();
|
|
+ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) {
|
|
+ player.resetAttackStrengthTicker();
|
|
+ }
|
|
+ } else {
|
|
+ ((net.minecraft.world.entity.player.Player) damageSource.getEntity()).resetAttackStrengthTicker();
|
|
+ }
|
|
+ // Paper end - PlayerAttackEntityCooldownResetEvent
|
|
+ }
|
|
+
|
|
+ // Resistance
|
|
+ if (event.getDamage(DamageModifier.RESISTANCE) < 0) {
|
|
+ float f3 = (float) -event.getDamage(DamageModifier.RESISTANCE);
|
|
+ if (f3 > 0.0F && f3 < 3.4028235E37F) {
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F));
|
|
+ } else if (damageSource.getEntity() instanceof ServerPlayer) {
|
|
+ ((ServerPlayer) damageSource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Apply damage to helmet
|
|
+ if (damageSource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
|
|
+ this.hurtHelmet(damageSource, amount);
|
|
+ }
|
|
+
|
|
+ // Apply damage to armor
|
|
+ if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
|
|
+ float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
|
|
+ this.hurtArmor(damageSource, armorDamage);
|
|
+ }
|
|
+
|
|
+ // Apply blocking code // PAIL: steal from above
|
|
+ if (event.getDamage(DamageModifier.BLOCKING) < 0) {
|
|
+ this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
|
|
+ Entity entity = damageSource.getDirectEntity();
|
|
+
|
|
+ if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Fix shield disable inconsistency & Check distance in entity interactions
|
|
+ this.blockUsingShield((LivingEntity) entity);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ boolean human = this instanceof net.minecraft.world.entity.player.Player;
|
|
+ float originalDamage = (float) event.getDamage();
|
|
+ float absorptionModifier = (float) -event.getDamage(DamageModifier.ABSORPTION);
|
|
+ this.setAbsorptionAmount(Math.max(this.getAbsorptionAmount() - absorptionModifier, 0.0F));
|
|
+ float f1 = absorptionModifier;
|
|
+
|
|
+ if (f1 > 0.0F && f1 < 3.4028235E37F && this instanceof Player player) {
|
|
+ player.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f1 * 10.0F));
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (f1 > 0.0F && f1 < 3.4028235E37F && damageSource.getEntity() instanceof ServerPlayer serverPlayer) {
|
|
serverPlayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f1 * 10.0F));
|
|
}
|
|
|
|
- if (var10 != 0.0F) {
|
|
- this.getCombatTracker().recordDamage(damageSource, var10);
|
|
- this.setHealth(this.getHealth() - var10);
|
|
- this.setAbsorptionAmount(this.getAbsorptionAmount() - var10);
|
|
+ // CraftBukkit start
|
|
+ if (amount > 0 || !human) {
|
|
+ if (human) {
|
|
+ // PAIL: Be sure to drag all this code from the EntityHuman subclass each update.
|
|
+ ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damageSource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
|
|
+ if (amount < 3.4028235E37F) {
|
|
+ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(amount * 10.0F));
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ this.getCombatTracker().recordDamage(damageSource, amount);
|
|
+ this.setHealth(this.getHealth() - amount);
|
|
+ // CraftBukkit start
|
|
+ if (!human) {
|
|
+ this.setAbsorptionAmount(this.getAbsorptionAmount() - amount);
|
|
+ }
|
|
this.gameEvent(GameEvent.ENTITY_DAMAGE);
|
|
+ return true;
|
|
+ } else {
|
|
+ // Duplicate triggers if blocking
|
|
+ if (event.getDamage(DamageModifier.BLOCKING) < 0) {
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damageSource, originalDamage, amount, true); // Paper - fix taken/dealt param order
|
|
+ f1 = (float) -event.getDamage(DamageModifier.BLOCKING);
|
|
+ if (f1 > 0.0F && f1 < 3.4028235E37F) {
|
|
+ ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (damageSource.getEntity() instanceof ServerPlayer) {
|
|
+ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damageSource.getEntity(), this, damageSource, originalDamage, amount, true); // Paper - fix taken/dealt param order
|
|
+ }
|
|
+
|
|
+ return !io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.skipVanillaDamageTickWhenShieldBlocked; // Paper - this should always return true, however expose an unsupported setting to flip this to false to enable "shield stunning".
|
|
+ } else {
|
|
+ return true; // Paper - return false ONLY if event was cancelled
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
+ return true; // CraftBukkit // Paper - return false ONLY if event was cancelled
|
|
}
|
|
|
|
public CombatTracker getCombatTracker() {
|
|
@@ -1814,7 +_,17 @@
|
|
}
|
|
|
|
public final void setArrowCount(int count) {
|
|
- this.entityData.set(DATA_ARROW_COUNT_ID, count);
|
|
+ // CraftBukkit start
|
|
+ this.setArrowCount(count, false);
|
|
+ }
|
|
+
|
|
+ public final void setArrowCount(int count, boolean reset) {
|
|
+ ArrowBodyCountChangeEvent event = CraftEventFactory.callArrowBodyCountChangeEvent(this, this.getArrowCount(), count, reset);
|
|
+ if (event.isCancelled()) {
|
|
+ return;
|
|
+ }
|
|
+ this.entityData.set(DATA_ARROW_COUNT_ID, event.getNewAmount());
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public final int getStingerCount() {
|
|
@@ -1957,7 +_,7 @@
|
|
|
|
@Override
|
|
protected void onBelowWorld() {
|
|
- this.hurt(this.damageSources().fellOutOfWorld(), 4.0F);
|
|
+ this.hurt(this.damageSources().fellOutOfWorld(), this.level().getWorld().getVoidDamageAmount()); // Paper - use configured void damage amount
|
|
}
|
|
|
|
protected void updateSwingTime() {
|
|
@@ -2052,6 +_,12 @@
|
|
|
|
public abstract ItemStack getItemBySlot(EquipmentSlot slot);
|
|
|
|
+ // CraftBukkit start
|
|
+ public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
|
|
+ this.setItemSlot(enumitemslot, itemstack);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
|
|
|
|
public Iterable<ItemStack> getHandSlots() {
|
|
@@ -2158,14 +_,27 @@
|
|
return this.hasEffect(MobEffects.JUMP) ? 0.1F * (this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F;
|
|
}
|
|
|
|
+ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits
|
|
@VisibleForTesting
|
|
public void jumpFromGround() {
|
|
float jumpPower = this.getJumpPower();
|
|
if (!(jumpPower <= 1.0E-5F)) {
|
|
Vec3 deltaMovement = this.getDeltaMovement();
|
|
+ // Paper start - Prevent excessive velocity through repeated crits
|
|
+ long time = System.nanoTime();
|
|
+ boolean canCrit = true;
|
|
+ if (this instanceof net.minecraft.world.entity.player.Player) {
|
|
+ canCrit = false;
|
|
+ if (time - this.lastJumpTime > (long)(0.250e9)) {
|
|
+ this.lastJumpTime = time;
|
|
+ canCrit = true;
|
|
+ }
|
|
+ }
|
|
+ // Paper end - Prevent excessive velocity through repeated crits
|
|
this.setDeltaMovement(deltaMovement.x, Math.max((double)jumpPower, deltaMovement.y), deltaMovement.z);
|
|
if (this.isSprinting()) {
|
|
float f = this.getYRot() * (float) (Math.PI / 180.0);
|
|
+ if (canCrit) // Paper - Prevent excessive velocity through repeated crits
|
|
this.addDeltaMovement(new Vec3(-Mth.sin(f) * 0.2, 0.0, Mth.cos(f) * 0.2));
|
|
}
|
|
|
|
@@ -2425,7 +_,7 @@
|
|
}
|
|
|
|
protected float getFlyingSpeed() {
|
|
- return this.getControllingPassenger() instanceof Player ? this.getSpeed() * 0.1F : 0.02F;
|
|
+ return this.getControllingPassenger() instanceof net.minecraft.world.entity.player.Player ? this.getSpeed() * 0.1F : 0.02F;
|
|
}
|
|
|
|
public float getSpeed() {
|
|
@@ -2471,7 +_,7 @@
|
|
}
|
|
}
|
|
|
|
- this.detectEquipmentUpdates();
|
|
+ this.detectEquipmentUpdatesPublic(); // CraftBukkit
|
|
if (this.tickCount % 20 == 0) {
|
|
this.getCombatTracker().recheckStatus();
|
|
}
|
|
@@ -2519,37 +_,14 @@
|
|
profilerFiller.pop();
|
|
profilerFiller.push("rangeChecks");
|
|
|
|
- while (this.getYRot() - this.yRotO < -180.0F) {
|
|
- this.yRotO -= 360.0F;
|
|
- }
|
|
-
|
|
- while (this.getYRot() - this.yRotO >= 180.0F) {
|
|
- this.yRotO += 360.0F;
|
|
- }
|
|
-
|
|
- while (this.yBodyRot - this.yBodyRotO < -180.0F) {
|
|
- this.yBodyRotO -= 360.0F;
|
|
- }
|
|
-
|
|
- while (this.yBodyRot - this.yBodyRotO >= 180.0F) {
|
|
- this.yBodyRotO += 360.0F;
|
|
- }
|
|
-
|
|
- while (this.getXRot() - this.xRotO < -180.0F) {
|
|
- this.xRotO -= 360.0F;
|
|
- }
|
|
-
|
|
- while (this.getXRot() - this.xRotO >= 180.0F) {
|
|
- this.xRotO += 360.0F;
|
|
- }
|
|
-
|
|
- while (this.yHeadRot - this.yHeadRotO < -180.0F) {
|
|
- this.yHeadRotO -= 360.0F;
|
|
- }
|
|
-
|
|
- while (this.yHeadRot - this.yHeadRotO >= 180.0F) {
|
|
- this.yHeadRotO += 360.0F;
|
|
- }
|
|
+ // Paper start - stop large pitch and yaw changes from crashing the server
|
|
+ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F;
|
|
+
|
|
+ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F;
|
|
+
|
|
+ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F;
|
|
+
|
|
+ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
|
|
|
|
profilerFiller.pop();
|
|
this.animStep += f2;
|
|
@@ -2573,7 +_,7 @@
|
|
this.elytraAnimationState.tick();
|
|
}
|
|
|
|
- public void detectEquipmentUpdates() {
|
|
+ public void detectEquipmentUpdatesPublic() { // CraftBukkit
|
|
Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
|
|
if (map != null) {
|
|
this.handleHandSwap(map);
|
|
@@ -2595,6 +_,13 @@
|
|
};
|
|
ItemStack itemBySlot = this.getItemBySlot(equipmentSlot);
|
|
if (this.equipmentHasChanged(itemStack, itemBySlot)) {
|
|
+ // Paper start - PlayerArmorChangeEvent
|
|
+ if (this instanceof ServerPlayer && equipmentSlot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
|
|
+ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemStack);
|
|
+ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemBySlot);
|
|
+ new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((org.bukkit.entity.Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(equipmentSlot.name()), oldItem, newItem).callEvent();
|
|
+ }
|
|
+ // Paper end - PlayerArmorChangeEvent
|
|
if (map == null) {
|
|
map = Maps.newEnumMap(EquipmentSlot.class);
|
|
}
|
|
@@ -2664,7 +_,7 @@
|
|
this.lastBodyItemStack = itemStack;
|
|
}
|
|
});
|
|
- ((ServerLevel)this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list));
|
|
+ ((ServerLevel)this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list, true)); // Paper - data sanitization
|
|
}
|
|
|
|
private ItemStack getLastArmorItem(EquipmentSlot slot) {
|
|
@@ -2765,8 +_,10 @@
|
|
if (!flag || this.onGround() && !(fluidHeight > fluidJumpThreshold)) {
|
|
if (!this.isInLava() || this.onGround() && !(fluidHeight > fluidJumpThreshold)) {
|
|
if ((this.onGround() || flag && fluidHeight <= fluidJumpThreshold) && this.noJumpDelay == 0) {
|
|
+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
|
|
this.jumpFromGround();
|
|
this.noJumpDelay = 10;
|
|
+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
|
|
}
|
|
} else {
|
|
this.jumpInLiquid(FluidTags.LAVA);
|
|
@@ -2805,7 +_,7 @@
|
|
this.calculateEntityAnimation(this instanceof FlyingAnimal);
|
|
profilerFiller.pop();
|
|
profilerFiller.push("freezing");
|
|
- if (!this.level().isClientSide && !this.isDeadOrDying()) {
|
|
+ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API
|
|
int ticksFrozen = this.getTicksFrozen();
|
|
if (this.isInPowderSnow && this.canFreeze()) {
|
|
this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), ticksFrozen + 1));
|
|
@@ -2829,6 +_,20 @@
|
|
|
|
this.pushEntities();
|
|
profilerFiller.pop();
|
|
+ // Paper start - Add EntityMoveEvent
|
|
+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) {
|
|
+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
|
|
+ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
|
|
+ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
|
|
+ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone());
|
|
+ if (!event.callEvent()) {
|
|
+ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
|
|
+ } else if (!to.equals(event.getTo())) {
|
|
+ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end - Add EntityMoveEvent
|
|
if (this.level() instanceof ServerLevel serverLevel && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
|
|
this.hurtServer(serverLevel, this.damageSources().drown(), 1.0F);
|
|
}
|
|
@@ -2842,6 +_,7 @@
|
|
this.checkSlowFallDistance();
|
|
if (!this.level().isClientSide) {
|
|
if (!this.canGlide()) {
|
|
+ if (this.getSharedFlag(7) != false && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit
|
|
this.setSharedFlag(7, false);
|
|
return;
|
|
}
|
|
@@ -2881,9 +_,24 @@
|
|
if (!(this.level() instanceof ServerLevel serverLevel)) {
|
|
this.level().getEntities(EntityTypeTest.forClass(Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush);
|
|
} else {
|
|
- List<Entity> entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this));
|
|
+ // Paper start - don't run getEntities if we're not going to use its result
|
|
+ if (!this.isPushable()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ net.minecraft.world.scores.Team team = this.getTeam();
|
|
+ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ int _int = serverLevel.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
|
|
+ if (_int <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) {
|
|
+ return;
|
|
+ }
|
|
+ // Paper end - don't run getEntities if we're not going to use its result
|
|
+ List<Entity> entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule
|
|
if (!entities.isEmpty()) {
|
|
- int _int = serverLevel.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
|
|
+ // Paper - don't run getEntities if we're not going to use its result; moved up
|
|
if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) {
|
|
int i = 0;
|
|
|
|
@@ -2898,7 +_,16 @@
|
|
}
|
|
}
|
|
|
|
+ // Paper start - Cap entity collisions
|
|
+ this.numCollisions = Math.max(0, this.numCollisions - this.level().paperConfig().collisions.maxEntityCollisions);
|
|
for (Entity entity1 : entities) {
|
|
+ if (this.numCollisions >= this.level().paperConfig().collisions.maxEntityCollisions) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ entity1.numCollisions++;
|
|
+ this.numCollisions++;
|
|
+ // Paper end - Cap entity collisions
|
|
this.doPush(entity1);
|
|
}
|
|
}
|
|
@@ -2941,9 +_,16 @@
|
|
|
|
@Override
|
|
public void stopRiding() {
|
|
+ // Paper start - Force entity dismount during teleportation
|
|
+ this.stopRiding(false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void stopRiding(boolean suppressCancellation) {
|
|
+ // Paper end - Force entity dismount during teleportation
|
|
Entity vehicle = this.getVehicle();
|
|
- super.stopRiding();
|
|
- if (vehicle != null && vehicle != this.getVehicle() && !this.level().isClientSide) {
|
|
+ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation
|
|
+ if (vehicle != null && vehicle != this.getVehicle() && !this.level().isClientSide && vehicle.valid) { // Paper - don't process on world gen
|
|
this.dismountVehicle(vehicle);
|
|
}
|
|
}
|
|
@@ -3007,7 +_,7 @@
|
|
}
|
|
|
|
public void onItemPickup(ItemEntity itemEntity) {
|
|
- Entity owner = itemEntity.getOwner();
|
|
+ Entity owner = itemEntity.thrower != null ? this.level().getGlobalPlayerByUUID(itemEntity.thrower) : null; // Paper - check global player list where appropriate
|
|
if (owner instanceof ServerPlayer) {
|
|
CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer)owner, itemEntity.getItem(), this);
|
|
}
|
|
@@ -3017,7 +_,7 @@
|
|
if (!entity.isRemoved()
|
|
&& !this.level().isClientSide
|
|
&& (entity instanceof ItemEntity || entity instanceof AbstractArrow || entity instanceof ExperienceOrb)) {
|
|
- ((ServerLevel)this.level()).getChunkSource().broadcast(entity, new ClientboundTakeItemEntityPacket(entity.getId(), this.getId(), amount));
|
|
+ ((ServerLevel)this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(entity.getId(), this.getId(), amount)); // Paper - broadcast with collector as source
|
|
}
|
|
}
|
|
|
|
@@ -3031,7 +_,8 @@
|
|
} else {
|
|
Vec3 vec3 = new Vec3(this.getX(), this.getEyeY(), this.getZ());
|
|
Vec3 vec31 = new Vec3(entity.getX(), y, entity.getZ());
|
|
- return !(vec31.distanceTo(vec3) > 128.0) && this.level().clip(new ClipContext(vec3, vec31, block, fluid, this)).getType() == HitResult.Type.MISS;
|
|
+ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists
|
|
+ return !(vec31.distanceToSqr(vec3) > 128.0D * 128.0D) && this.level().clip(new ClipContext(vec3, vec31, block, fluid, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared
|
|
}
|
|
}
|
|
|
|
@@ -3051,13 +_,27 @@
|
|
|
|
@Override
|
|
public boolean isPickable() {
|
|
- return !this.isRemoved();
|
|
+ return !this.isRemoved() && this.collides; // CraftBukkit
|
|
}
|
|
|
|
+ // Paper start - Climbing should not bypass cramming gamerule
|
|
@Override
|
|
public boolean isPushable() {
|
|
- return this.isAlive() && !this.isSpectator() && !this.onClimbable();
|
|
- }
|
|
+ return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isCollidable(boolean ignoreClimbing) {
|
|
+ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit
|
|
+ // Paper end - Climbing should not bypass cramming gamerule
|
|
+ }
|
|
+
|
|
+ // CraftBukkit start - collidable API
|
|
+ @Override
|
|
+ public boolean canCollideWithBukkit(Entity entity) {
|
|
+ return this.isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID());
|
|
+ }
|
|
+ // CraftBukkit end
|
|
|
|
@Override
|
|
public float getYHeadRot() {
|
|
@@ -3088,7 +_,7 @@
|
|
}
|
|
|
|
public final void setAbsorptionAmount(float absorptionAmount) {
|
|
- this.internalSetAbsorptionAmount(Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption()));
|
|
+ this.internalSetAbsorptionAmount(!Float.isNaN(absorptionAmount) ? Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption()) : 0.0F); // Paper - Check for NaN
|
|
}
|
|
|
|
protected void internalSetAbsorptionAmount(float absorptionAmount) {
|
|
@@ -3115,6 +_,15 @@
|
|
return (this.entityData.get(DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
|
|
}
|
|
|
|
+ // Paper start - Properly cancel usable items
|
|
+ public void resyncUsingItem(ServerPlayer serverPlayer) {
|
|
+ this.resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer);
|
|
+ }
|
|
+ // Paper end - Properly cancel usable items
|
|
+ // Paper start - lag compensate eating
|
|
+ protected long eatStartTime;
|
|
+ protected int totalEatTimeTicks;
|
|
+ // Paper end - lag compensate eating
|
|
private void updatingUsingItem() {
|
|
if (this.isUsingItem()) {
|
|
if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
|
|
@@ -3128,7 +_,12 @@
|
|
|
|
protected void updateUsingItem(ItemStack usingItem) {
|
|
usingItem.onUseTick(this.level(), this, this.getUseItemRemainingTicks());
|
|
- if (--this.useItemRemaining == 0 && !this.level().isClientSide && !usingItem.useOnRelease()) {
|
|
+ // Paper start - lag compensate eating
|
|
+ // we add 1 to the expected time to avoid lag compensating when we should not
|
|
+ final boolean shouldLagCompensate = this.useItem.has(DataComponents.FOOD) && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1L + this.totalEatTimeTicks) * 50L * (1000L * 1000L));
|
|
+ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !usingItem.useOnRelease()) {
|
|
+ this.useItemRemaining = 0;
|
|
+ // Paper end - lag compensate eating
|
|
this.completeUsingItem();
|
|
}
|
|
}
|
|
@@ -3154,10 +_,18 @@
|
|
}
|
|
|
|
public void startUsingItem(InteractionHand hand) {
|
|
+ // Paper start - Prevent consuming the wrong itemstack
|
|
+ this.startUsingItem(hand, false);
|
|
+ }
|
|
+ public void startUsingItem(InteractionHand hand, boolean forceUpdate) {
|
|
+ // Paper end - Prevent consuming the wrong itemstack
|
|
ItemStack itemInHand = this.getItemInHand(hand);
|
|
- if (!itemInHand.isEmpty() && !this.isUsingItem()) {
|
|
+ if (!itemInHand.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
|
|
this.useItem = itemInHand;
|
|
- this.useItemRemaining = itemInHand.getUseDuration(this);
|
|
+ // Paper start - lag compensate eating
|
|
+ this.useItemRemaining = this.totalEatTimeTicks = itemInHand.getUseDuration(this);
|
|
+ this.eatStartTime = System.nanoTime();
|
|
+ // Paper end - lag compensate eating
|
|
if (!this.level().isClientSide) {
|
|
this.setLivingEntityFlag(1, true);
|
|
this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
|
|
@@ -3181,7 +_,10 @@
|
|
}
|
|
} else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
|
|
this.useItem = ItemStack.EMPTY;
|
|
- this.useItemRemaining = 0;
|
|
+ // Paper start - lag compensate eating
|
|
+ this.useItemRemaining = this.totalEatTimeTicks = 0;
|
|
+ this.eatStartTime = -1L;
|
|
+ // Paper end - lag compensate eating
|
|
}
|
|
}
|
|
}
|
|
@@ -3220,12 +_,49 @@
|
|
this.releaseUsingItem();
|
|
} else {
|
|
if (!this.useItem.isEmpty() && this.isUsingItem()) {
|
|
- ItemStack itemStack = this.useItem.finishUsingItem(this.level(), this);
|
|
+ this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack
|
|
+ // CraftBukkit start - fire PlayerItemConsumeEvent
|
|
+ ItemStack itemStack;
|
|
+ PlayerItemConsumeEvent event = null; // Paper
|
|
+ if (this instanceof ServerPlayer serverPlayer) {
|
|
+ org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem);
|
|
+ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(usedItemHand);
|
|
+ event = new PlayerItemConsumeEvent((org.bukkit.entity.Player) this.getBukkitEntity(), craftItem, hand); // Paper
|
|
+ this.level().getCraftServer().getPluginManager().callEvent(event);
|
|
+
|
|
+ if (event.isCancelled()) {
|
|
+ // Update client
|
|
+ Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE);
|
|
+ if (consumable != null) {
|
|
+ consumable.cancelUsingItem(serverPlayer, this.useItem);
|
|
+ }
|
|
+ serverPlayer.getBukkitEntity().updateInventory();
|
|
+ serverPlayer.getBukkitEntity().updateScaledHealth();
|
|
+ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ itemStack = (craftItem.equals(event.getItem())) ? this.useItem.finishUsingItem(this.level(), this) : CraftItemStack.asNMSCopy(event.getItem()).finishUsingItem(this.level(), this);
|
|
+ } else {
|
|
+ itemStack = this.useItem.finishUsingItem(this.level(), this);
|
|
+ }
|
|
+ // Paper start - save the default replacement item and change it if necessary
|
|
+ final ItemStack defaultReplacement = itemStack;
|
|
+ if (event != null && event.getReplacement() != null) {
|
|
+ itemStack = CraftItemStack.asNMSCopy(event.getReplacement());
|
|
+ }
|
|
+ // Paper end
|
|
+ // CraftBukkit end
|
|
if (itemStack != this.useItem) {
|
|
this.setItemInHand(usedItemHand, itemStack);
|
|
}
|
|
|
|
this.stopUsingItem();
|
|
+ // Paper start
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ ((ServerPlayer) this).getBukkitEntity().updateInventory();
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
}
|
|
}
|
|
@@ -3248,6 +_,7 @@
|
|
|
|
public void releaseUsingItem() {
|
|
if (!this.useItem.isEmpty()) {
|
|
+ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((org.bukkit.entity.Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent
|
|
this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks());
|
|
if (this.useItem.useOnRelease()) {
|
|
this.updatingUsingItem();
|
|
@@ -3267,7 +_,10 @@
|
|
}
|
|
|
|
this.useItem = ItemStack.EMPTY;
|
|
- this.useItemRemaining = 0;
|
|
+ // Paper start - lag compensate eating
|
|
+ this.useItemRemaining = this.totalEatTimeTicks = 0;
|
|
+ this.eatStartTime = -1L;
|
|
+ // Paper end - lag compensate eating
|
|
}
|
|
|
|
public boolean isBlocking() {
|
|
@@ -3281,12 +_,69 @@
|
|
if (item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK) {
|
|
return null;
|
|
} else {
|
|
- return item.getUseDuration(this.useItem, this) - this.useItemRemaining < 5 ? null : this.useItem;
|
|
+ return item.getUseDuration(this.useItem, this) - this.useItemRemaining < this.getShieldBlockingDelay() ? null : this.useItem; // Paper - Make shield blocking delay configurable
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
+
|
|
+ // Paper start - Make shield blocking delay configurable
|
|
+ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) {
|
|
+ if (maxDistance < 1 || maxDistance > 120) {
|
|
+ throw new IllegalArgumentException("maxDistance must be between 1-120");
|
|
+ }
|
|
+
|
|
+ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ());
|
|
+ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance);
|
|
+ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ());
|
|
+ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this);
|
|
+
|
|
+ return this.level().clip(raytrace);
|
|
+ }
|
|
+
|
|
+ public @Nullable net.minecraft.world.phys.EntityHitResult getTargetEntity(int maxDistance) {
|
|
+ if (maxDistance < 1 || maxDistance > 120) {
|
|
+ throw new IllegalArgumentException("maxDistance must be between 1-120");
|
|
+ }
|
|
+
|
|
+ Vec3 start = this.getEyePosition(1.0F);
|
|
+ Vec3 direction = this.getLookAngle();
|
|
+ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance);
|
|
+
|
|
+ List<Entity> entityList = this.level().getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_SPECTATORS.and(Entity::isPickable));
|
|
+
|
|
+ double distance = 0.0D;
|
|
+ net.minecraft.world.phys.EntityHitResult result = null;
|
|
+
|
|
+ for (Entity entity : entityList) {
|
|
+ final double inflationAmount = (double) entity.getPickRadius();
|
|
+ AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount);
|
|
+ Optional<Vec3> rayTraceResult = aabb.clip(start, end);
|
|
+
|
|
+ if (rayTraceResult.isPresent()) {
|
|
+ Vec3 rayTrace = rayTraceResult.get();
|
|
+ double distanceTo = start.distanceToSqr(rayTrace);
|
|
+ if (distanceTo < distance || distance == 0.0D) {
|
|
+ result = new net.minecraft.world.phys.EntityHitResult(entity, rayTrace);
|
|
+ distance = distanceTo;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay;
|
|
+
|
|
+ public int getShieldBlockingDelay() {
|
|
+ return shieldBlockingDelay;
|
|
+ }
|
|
+
|
|
+ public void setShieldBlockingDelay(int shieldBlockingDelay) {
|
|
+ this.shieldBlockingDelay = shieldBlockingDelay;
|
|
+ }
|
|
+ // Paper end - Make shield blocking delay configurable
|
|
|
|
public boolean isSuppressingSlidingDownLadder() {
|
|
return this.isShiftKeyDown();
|
|
@@ -3306,6 +_,12 @@
|
|
}
|
|
|
|
public boolean randomTeleport(double x, double y, double z, boolean broadcastTeleport) {
|
|
+ // CraftBukkit start
|
|
+ return this.randomTeleport(x, y, z, broadcastTeleport, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN).orElse(false);
|
|
+ }
|
|
+
|
|
+ public Optional<Boolean> randomTeleport(double x, double y, double z, boolean broadcastTeleport, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
|
|
+ // CraftBukkit end
|
|
double x1 = this.getX();
|
|
double y1 = this.getY();
|
|
double z1 = this.getZ();
|
|
@@ -3328,16 +_,39 @@
|
|
}
|
|
|
|
if (flag1) {
|
|
- this.teleportTo(x, d, z);
|
|
+ // CraftBukkit start - Teleport event
|
|
+ // first set position, to check if the place to teleport is valid
|
|
+ this.setPos(x, d, z);
|
|
if (level.noCollision(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
|
|
flag = true;
|
|
}
|
|
+ // now revert and call event if the teleport place is valid
|
|
+ this.setPos(x1, y1, z1);
|
|
+
|
|
+ if (flag) {
|
|
+ if (!(this instanceof ServerPlayer)) {
|
|
+ EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), x1, y1, z1), new Location(this.level().getWorld(), x, d, z));
|
|
+ this.level().getCraftServer().getPluginManager().callEvent(teleport);
|
|
+ if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper
|
|
+ Location to = teleport.getTo();
|
|
+ this.teleportTo(to.getX(), to.getY(), to.getZ());
|
|
+ } else {
|
|
+ return Optional.empty();
|
|
+ }
|
|
+ } else {
|
|
+ // player teleport event is called in the underlining code
|
|
+ if (!((ServerPlayer) this).connection.teleport(x, d, z, this.getYRot(), this.getXRot(), cause)) {
|
|
+ return Optional.empty();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
|
|
if (!flag) {
|
|
- this.teleportTo(x1, y1, z1);
|
|
- return false;
|
|
+ // this.teleportTo(x1, y1, z1); // CraftBukkit - already set the location back
|
|
+ return Optional.of(false); // CraftBukkit
|
|
} else {
|
|
if (broadcastTeleport) {
|
|
level.broadcastEntityEvent(this, (byte)46);
|
|
@@ -3347,7 +_,7 @@
|
|
pathfinderMob.getNavigation().stop();
|
|
}
|
|
|
|
- return true;
|
|
+ return Optional.of(true); // CraftBukkit
|
|
}
|
|
}
|
|
|