From e151b6fc3f36c6fbecba352b694ff5ba7351e1f8 Mon Sep 17 00:00:00 2001 From: Noah van der Aa <ndvdaa@gmail.com> Date: Wed, 15 Sep 2021 20:44:22 +0200 Subject: [PATCH] Friction API --- .../world/entity/LivingEntity.java.patch | 272 ++++++++++-------- .../world/entity/item/ItemEntity.java.patch | 69 +++-- .../vehicle/AbstractMinecart.java.patch | 46 ++- .../vehicle/NewMinecartBehavior.java.patch | 9 +- .../vehicle/OldMinecartBehavior.java.patch | 3 +- .../bukkit/craftbukkit/entity/CraftItem.java | 12 + .../craftbukkit/entity/CraftLivingEntity.java | 13 + .../craftbukkit/entity/CraftMinecart.java | 14 + 8 files changed, 286 insertions(+), 152 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch index 7177340201..c70d0f009b 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -17,10 +17,11 @@ import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.item.AxeItem; -@@ -136,6 +137,30 @@ +@@ -135,6 +136,30 @@ + import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Scoreboard; import org.slf4j.Logger; - ++ +// CraftBukkit start +import java.util.ArrayList; +import java.util.HashSet; @@ -44,10 +45,9 @@ +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(); @@ -174,7 +199,7 @@ public static final float DEFAULT_BABY_SCALE = 0.5F; public static final String ATTRIBUTES_FIELD = "attributes"; @@ -66,7 +66,7 @@ public int lastHurtByPlayerTime; protected boolean dead; protected int noActionTime; -@@ -260,7 +285,29 @@ +@@ -260,7 +285,30 @@ protected boolean skipDropExperience; private final EnumMap<EquipmentSlot, Reference2ObjectMap<Enchantment, Set<EnchantmentLocationBasedEffect>>> activeLocationDependentEnchantments; protected float appliedScale; @@ -79,6 +79,7 @@ + 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() { @@ -96,7 +97,7 @@ protected LivingEntity(EntityType<? extends LivingEntity> type, Level world) { super(type, world); this.lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY); -@@ -276,7 +323,9 @@ +@@ -276,7 +324,9 @@ this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class); this.appliedScale = 1.0F; this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); @@ -107,7 +108,7 @@ this.blocksBuilding = true; this.rotA = (float) ((Math.random() + 1.0D) * 0.009999999776482582D); this.reapplyPosition(); -@@ -356,7 +405,13 @@ +@@ -356,7 +406,13 @@ double d8 = Math.min((double) (0.2F + f / 15.0F), 2.5D); int i = (int) (150.0D * d8); @@ -122,7 +123,7 @@ } } } -@@ -402,7 +457,7 @@ +@@ -402,7 +458,7 @@ } if (this.isAlive()) { @@ -131,7 +132,7 @@ Level world1 = this.level(); ServerLevel worldserver1; double d0; -@@ -424,7 +479,7 @@ +@@ -424,7 +480,7 @@ } if (this.isEyeInFluid(FluidTags.WATER) && !this.level().getBlockState(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())).is(Blocks.BUBBLE_COLUMN)) { @@ -140,7 +141,7 @@ if (flag1) { this.setAirSupply(this.decreaseAirSupply(this.getAirSupply())); -@@ -573,7 +628,7 @@ +@@ -573,7 +629,7 @@ ++this.deathTime; if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) { this.level().broadcastEntityEvent(this, (byte) 60); @@ -149,7 +150,7 @@ } } -@@ -629,7 +684,7 @@ +@@ -629,7 +685,7 @@ return this.lastHurtByMobTimestamp; } @@ -158,28 +159,36 @@ this.lastHurtByPlayer = attacking; this.lastHurtByPlayerTime = this.tickCount; } -@@ -679,17 +734,23 @@ +@@ -667,7 +723,7 @@ + } + + public boolean shouldDiscardFriction() { +- return this.discardFriction; ++ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API + } + + public void setDiscardFriction(boolean noDrag) { +@@ -679,17 +735,23 @@ } public void onEquipItem(EquipmentSlot slot, ItemStack oldStack, ItemStack newStack) { -- if (!this.level().isClientSide() && !this.isSpectator()) { -- boolean flag = newStack.isEmpty() && oldStack.isEmpty(); + // CraftBukkit start + this.onEquipItem(slot, oldStack, newStack, false); + } ++ ++ public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) { ++ // CraftBukkit end + if (!this.level().isClientSide() && !this.isSpectator()) { +- boolean flag = newStack.isEmpty() && oldStack.isEmpty(); ++ boolean flag = itemstack1.isEmpty() && itemstack.isEmpty(); - if (!flag && !ItemStack.isSameItemSameComponents(oldStack, newStack) && !this.firstTick) { - Equippable equippable = (Equippable) newStack.get(DataComponents.EQUIPPABLE); -+ public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) { -+ // CraftBukkit end -+ if (!this.level().isClientSide() && !this.isSpectator()) { -+ boolean flag = itemstack1.isEmpty() && itemstack.isEmpty(); ++ if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) { ++ Equippable equippable = (Equippable) itemstack1.get(DataComponents.EQUIPPABLE); - if (!this.isSilent() && equippable != null && slot == equippable.slot()) { - this.level().playSeededSound((Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong()); -+ if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) { -+ Equippable equippable = (Equippable) itemstack1.get(DataComponents.EQUIPPABLE); -+ + if (!this.isSilent() && equippable != null && enumitemslot == equippable.slot() && !silent) { // CraftBukkit + this.level().playSeededSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong()); } @@ -189,7 +198,7 @@ this.gameEvent(equippable != null ? GameEvent.EQUIP : GameEvent.UNEQUIP); } -@@ -699,17 +760,24 @@ +@@ -699,17 +761,24 @@ @Override public void remove(Entity.RemovalReason reason) { @@ -217,7 +226,7 @@ this.brain.clearMemories(); } -@@ -722,6 +790,7 @@ +@@ -722,11 +791,17 @@ mobeffect.onMobRemoved(world, this, reason); } @@ -225,7 +234,17 @@ this.activeEffects.clear(); } -@@ -763,7 +832,13 @@ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putFloat("Health", this.getHealth()); + nbt.putShort("HurtTime", (short) this.hurtTime); + nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); +@@ -763,7 +838,23 @@ @Override public void readAdditionalSaveData(CompoundTag nbt) { @@ -237,10 +256,20 @@ + } + this.internalSetAbsorptionAmount(absorptionAmount); + // Paper end - Check for NaN ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API if (nbt.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) { this.getAttributes().load(nbt.getList("attributes", 10)); } -@@ -781,6 +856,17 @@ +@@ -781,6 +872,17 @@ } } @@ -258,7 +287,7 @@ if (nbt.contains("Health", 99)) { this.setHealth(nbt.getFloat("Health")); } -@@ -792,6 +878,7 @@ +@@ -792,6 +894,7 @@ String s = nbt.getString("Team"); Scoreboard scoreboard = this.level().getScoreboard(); PlayerTeam scoreboardteam = scoreboard.getPlayerTeam(s); @@ -266,11 +295,10 @@ boolean flag = scoreboardteam != null && scoreboard.addPlayerToTeam(this.getStringUUID(), scoreboardteam); if (!flag) { -@@ -818,10 +905,33 @@ - } +@@ -819,9 +922,32 @@ } -+ + + // CraftBukkit start + private boolean isTickingEffects = false; + private List<ProcessableEffect> effectsToProcess = Lists.newArrayList(); @@ -292,7 +320,7 @@ + } + } + // CraftBukkit end - ++ protected void tickEffects() { Iterator<Holder<MobEffect>> iterator = this.activeEffects.keySet().iterator(); @@ -300,7 +328,7 @@ try { while (iterator.hasNext()) { Holder<MobEffect> holder = (Holder) iterator.next(); -@@ -831,6 +941,12 @@ +@@ -831,6 +957,12 @@ this.onEffectUpdated(mobeffect, true, (Entity) null); })) { if (!this.level().isClientSide) { @@ -313,7 +341,7 @@ iterator.remove(); this.onEffectsRemoved(List.of(mobeffect)); } -@@ -841,6 +957,17 @@ +@@ -841,6 +973,17 @@ } catch (ConcurrentModificationException concurrentmodificationexception) { ; } @@ -331,7 +359,7 @@ if (this.effectsDirty) { if (!this.level().isClientSide) { -@@ -921,7 +1048,7 @@ +@@ -921,7 +1064,7 @@ } public boolean canAttack(LivingEntity target) { @@ -340,7 +368,7 @@ } public boolean canBeSeenAsEnemy() { -@@ -952,17 +1079,36 @@ +@@ -952,17 +1095,36 @@ this.entityData.set(LivingEntity.DATA_EFFECT_PARTICLES, List.of()); } @@ -381,7 +409,7 @@ } } -@@ -987,24 +1133,55 @@ +@@ -987,24 +1149,55 @@ return this.addEffect(effect, (Entity) null); } @@ -410,9 +438,6 @@ + MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(mobeffect.getEffect()); boolean flag = false; -- if (mobeffect1 == null) { -- this.activeEffects.put(effect.getEffect(), effect); -- this.onEffectAdded(effect, source); + // CraftBukkit start + boolean override = false; + if (mobeffect1 != null) { @@ -425,7 +450,9 @@ + } + // CraftBukkit end + -+ if (mobeffect1 == null) { + if (mobeffect1 == null) { +- this.activeEffects.put(effect.getEffect(), effect); +- this.onEffectAdded(effect, source); + this.activeEffects.put(mobeffect.getEffect(), mobeffect); + this.onEffectAdded(mobeffect, entity); flag = true; @@ -446,7 +473,7 @@ return flag; } } -@@ -1031,14 +1208,40 @@ +@@ -1031,14 +1224,40 @@ return this.getType().is(EntityTypeTags.INVERTED_HEALING_AND_HARM); } @@ -489,7 +516,7 @@ if (mobeffect != null) { this.onEffectsRemoved(List.of(mobeffect)); return true; -@@ -1142,20 +1345,65 @@ +@@ -1142,20 +1361,65 @@ } @@ -556,7 +583,7 @@ this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth())); } -@@ -1167,7 +1415,7 @@ +@@ -1167,7 +1431,7 @@ public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { if (this.isInvulnerableTo(world, source)) { return false; @@ -565,7 +592,7 @@ return false; } else if (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) { return false; -@@ -1182,10 +1430,11 @@ +@@ -1182,10 +1446,11 @@ } float f1 = amount; @@ -579,7 +606,7 @@ this.hurtCurrentlyUsedShield(amount); f2 = amount; amount = 0.0F; -@@ -1202,15 +1451,26 @@ +@@ -1202,15 +1467,26 @@ flag = true; } @@ -608,7 +635,7 @@ this.walkAnimation.setSpeed(1.5F); if (Float.isNaN(amount) || Float.isInfinite(amount)) { amount = Float.MAX_VALUE; -@@ -1218,18 +1478,27 @@ +@@ -1218,18 +1494,27 @@ boolean flag1 = true; @@ -640,7 +667,7 @@ this.hurtDuration = 10; this.hurtTime = this.hurtDuration; } -@@ -1243,7 +1512,7 @@ +@@ -1243,7 +1528,7 @@ world.broadcastDamageEvent(this, source); } @@ -649,7 +676,7 @@ this.markHurt(); } -@@ -1263,7 +1532,7 @@ +@@ -1263,7 +1548,7 @@ d1 = source.getSourcePosition().z() - this.getZ(); } @@ -658,7 +685,7 @@ if (!flag) { this.indicateDamage(d0, d1); } -@@ -1272,17 +1541,18 @@ +@@ -1272,17 +1557,18 @@ if (this.isDeadOrDying()) { if (!this.checkTotemDeathProtection(source)) { @@ -681,7 +708,7 @@ if (flag2) { this.lastDamageSource = source; -@@ -1329,10 +1599,10 @@ +@@ -1329,10 +1615,10 @@ } @Nullable @@ -694,7 +721,7 @@ this.lastHurtByPlayerTime = 100; this.lastHurtByPlayer = entityhuman; return entityhuman; -@@ -1342,8 +1612,8 @@ +@@ -1342,8 +1628,8 @@ this.lastHurtByPlayerTime = 100; LivingEntity entityliving = entitywolf.getOwner(); @@ -705,7 +732,7 @@ this.lastHurtByPlayer = entityhuman1; } else { -@@ -1363,7 +1633,7 @@ +@@ -1363,7 +1649,7 @@ } protected void blockedByShield(LivingEntity target) { @@ -714,7 +741,7 @@ } private boolean checkTotemDeathProtection(DamageSource source) { -@@ -1375,20 +1645,33 @@ +@@ -1375,20 +1661,33 @@ InteractionHand[] aenumhand = InteractionHand.values(); int i = aenumhand.length; @@ -752,7 +779,7 @@ ServerPlayer entityplayer = (ServerPlayer) this; entityplayer.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); -@@ -1468,6 +1751,7 @@ +@@ -1468,6 +1767,7 @@ Entity entity = damageSource.getEntity(); LivingEntity entityliving = this.getKillCredit(); @@ -760,7 +787,7 @@ if (entityliving != null) { entityliving.awardKillScore(this, damageSource); } -@@ -1477,26 +1761,61 @@ +@@ -1477,26 +1777,61 @@ } if (!this.level().isClientSide && this.hasCustomName()) { @@ -827,7 +854,7 @@ } } -@@ -1506,20 +1825,28 @@ +@@ -1506,20 +1841,28 @@ if (world instanceof ServerLevel worldserver) { boolean flag = false; @@ -859,7 +886,7 @@ this.level().addFreshEntity(entityitem); } } -@@ -1527,25 +1854,58 @@ +@@ -1527,27 +1870,60 @@ } } @@ -909,9 +936,9 @@ + if (!this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) { + return this.getExperienceReward(worldserver, entity); // CraftBukkit } } -+ + + return 0; // CraftBukkit -+ } + } + protected void dropExperience(ServerLevel world, @Nullable Entity attacker) { + // CraftBukkit start - Update getExpReward() above if the removed if() changes! @@ -920,10 +947,12 @@ + this.expToDrop = 0; + } + // CraftBukkit end - } - ++ } ++ protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {} -@@ -1612,19 +1972,35 @@ + + public long getLootTableSeed() { +@@ -1612,19 +1988,35 @@ } public void knockback(double strength, double x, double z) { @@ -966,12 +995,10 @@ } } -@@ -1681,7 +2057,21 @@ - - public LivingEntity.Fallsounds getFallSounds() { +@@ -1683,6 +2075,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); @@ -983,12 +1010,13 @@ + + public SoundEvent getFallDamageSound0(int fallHeight) { + return this.getFallDamageSound(fallHeight); - } ++ } + // CraftBukkit end - ++ public Optional<BlockPos> getLastClimbablePos() { return this.lastClimbablePos; -@@ -1757,9 +2147,14 @@ + } +@@ -1757,9 +2163,14 @@ int i = this.calculateFallDamage(fallDistance, damageMultiplier); if (i > 0) { @@ -1004,7 +1032,7 @@ return true; } else { return flag; -@@ -1830,7 +2225,7 @@ +@@ -1830,7 +2241,7 @@ protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) { if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) { @@ -1013,7 +1041,7 @@ amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); } -@@ -1841,7 +2236,8 @@ +@@ -1841,7 +2252,8 @@ if (source.is(DamageTypeTags.BYPASSES_EFFECTS)) { return amount; } else { @@ -1023,7 +1051,7 @@ int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5; int j = 25 - i; float f1 = amount * (float) j; -@@ -1884,18 +2280,154 @@ +@@ -1884,18 +2296,154 @@ } } @@ -1187,7 +1215,7 @@ if (entity instanceof ServerPlayer) { ServerPlayer entityplayer = (ServerPlayer) entity; -@@ -1904,13 +2436,48 @@ +@@ -1904,13 +2452,48 @@ } } @@ -1240,7 +1268,7 @@ } public CombatTracker getCombatTracker() { -@@ -1935,9 +2502,19 @@ +@@ -1935,9 +2518,19 @@ } public final void setArrowCount(int stuckArrowCount) { @@ -1261,7 +1289,7 @@ public final int getStingerCount() { return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID); } -@@ -1999,7 +2576,7 @@ +@@ -1999,7 +2592,7 @@ this.playSound(soundeffect, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); } @@ -1270,7 +1298,7 @@ this.setHealth(0.0F); this.die(this.damageSources().generic()); } -@@ -2182,6 +2759,12 @@ +@@ -2182,6 +2775,12 @@ public abstract ItemStack getItemBySlot(EquipmentSlot slot); @@ -1283,7 +1311,7 @@ public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack); public Iterable<ItemStack> getHandSlots() { -@@ -2292,17 +2875,29 @@ +@@ -2292,17 +2891,29 @@ return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F; } @@ -1314,7 +1342,7 @@ this.addDeltaMovement(new Vec3((double) (-Mth.sin(f1)) * 0.2D, 0.0D, (double) Mth.cos(f1) * 0.2D)); } -@@ -2494,7 +3089,7 @@ +@@ -2494,7 +3105,7 @@ } @@ -1323,7 +1351,7 @@ Vec3 vec3d1 = this.getRiddenInput(controllingPlayer, movementInput); this.tickRidden(controllingPlayer, vec3d1); -@@ -2507,13 +3102,13 @@ +@@ -2507,13 +3118,13 @@ } @@ -1340,7 +1368,7 @@ return this.getSpeed(); } -@@ -2571,7 +3166,7 @@ +@@ -2571,7 +3182,7 @@ double d1 = Mth.clamp(motion.z, -0.15000000596046448D, 0.15000000596046448D); double d2 = Math.max(motion.y, -0.15000000596046448D); @@ -1349,7 +1377,7 @@ d2 = 0.0D; } -@@ -2586,7 +3181,7 @@ +@@ -2586,7 +3197,7 @@ } protected float getFlyingSpeed() { @@ -1358,7 +1386,7 @@ } public float getSpeed() { -@@ -2634,7 +3229,7 @@ +@@ -2634,7 +3245,7 @@ } } @@ -1367,7 +1395,7 @@ if (this.tickCount % 20 == 0) { this.getCombatTracker().recheckStatus(); } -@@ -2687,38 +3282,16 @@ +@@ -2687,37 +3298,15 @@ gameprofilerfiller.pop(); gameprofilerfiller.push("rangeChecks"); @@ -1385,14 +1413,11 @@ - while (this.yBodyRot - this.yBodyRotO < -180.0F) { - this.yBodyRotO -= 360.0F; - } -+ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; - +- - while (this.yBodyRot - this.yBodyRotO >= 180.0F) { - this.yBodyRotO += 360.0F; - } -+ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; -+ // Paper end - +- - while (this.getXRot() - this.xRotO < -180.0F) { - this.xRotO -= 360.0F; - } @@ -1404,15 +1429,17 @@ - while (this.yHeadRot - this.yHeadRotO < -180.0F) { - this.yHeadRotO -= 360.0F; - } -- ++ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; + - while (this.yHeadRot - this.yHeadRotO >= 180.0F) { - this.yHeadRotO += 360.0F; - } -- ++ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; ++ // Paper end + gameprofilerfiller.pop(); this.animStep += f2; - if (this.isFallFlying()) { -@@ -2741,7 +3314,7 @@ +@@ -2741,7 +3330,7 @@ this.elytraAnimationState.tick(); } @@ -1421,7 +1448,7 @@ Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges(); if (map != null) { -@@ -2778,10 +3351,17 @@ +@@ -2778,10 +3367,17 @@ throw new MatchException((String) null, (Throwable) null); } @@ -1441,7 +1468,7 @@ if (map == null) { map = Maps.newEnumMap(EquipmentSlot.class); } -@@ -2974,8 +3554,10 @@ +@@ -2974,8 +3570,10 @@ } else if (this.isInLava() && (!this.onGround() || d3 > d4)) { this.jumpInLiquid(FluidTags.LAVA); } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) { @@ -1452,7 +1479,7 @@ } } else { this.noJumpDelay = 0; -@@ -3000,7 +3582,7 @@ +@@ -3000,7 +3598,7 @@ { LivingEntity entityliving = this.getControllingPassenger(); @@ -1461,7 +1488,7 @@ if (this.isAlive()) { this.travelRidden(entityhuman, vec3d1); break label112; -@@ -3017,7 +3599,7 @@ +@@ -3017,7 +3615,7 @@ this.calculateEntityAnimation(this instanceof FlyingAnimal); gameprofilerfiller.pop(); gameprofilerfiller.push("freezing"); @@ -1470,7 +1497,7 @@ int i = this.getTicksFrozen(); if (this.isInPowderSnow && this.canFreeze()) { -@@ -3046,6 +3628,20 @@ +@@ -3046,6 +3644,20 @@ this.pushEntities(); gameprofilerfiller.pop(); @@ -1491,7 +1518,7 @@ world = this.level(); if (world instanceof ServerLevel worldserver) { if (this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { -@@ -3063,6 +3659,7 @@ +@@ -3063,6 +3675,7 @@ this.checkSlowFallDistance(); if (!this.level().isClientSide) { if (!this.canGlide()) { @@ -1499,7 +1526,7 @@ this.setSharedFlag(7, false); return; } -@@ -3113,12 +3710,26 @@ +@@ -3113,12 +3726,26 @@ Level world = this.level(); if (!(world instanceof ServerLevel worldserver)) { @@ -1529,7 +1556,7 @@ if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { int j = 0; -@@ -3138,10 +3749,12 @@ +@@ -3138,10 +3765,12 @@ } Iterator iterator1 = list.iterator(); @@ -1544,7 +1571,7 @@ this.doPush(entity1); } } -@@ -3190,10 +3803,16 @@ +@@ -3190,10 +3819,16 @@ @Override public void stopRiding() { @@ -1563,7 +1590,7 @@ this.dismountVehicle(entity); } -@@ -3258,7 +3877,7 @@ +@@ -3258,7 +3893,7 @@ } public void onItemPickup(ItemEntity item) { @@ -1572,7 +1599,7 @@ if (entity instanceof ServerPlayer) { CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this); -@@ -3284,7 +3903,8 @@ +@@ -3284,7 +3919,8 @@ Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); Vec3 vec3d1 = new Vec3(entity.getX(), entityY, entity.getZ()); @@ -1582,7 +1609,7 @@ } } -@@ -3305,15 +3925,29 @@ +@@ -3305,15 +3941,29 @@ @Override public boolean isPickable() { @@ -1614,7 +1641,7 @@ public float getYHeadRot() { return this.yHeadRot; } -@@ -3342,7 +3976,7 @@ +@@ -3342,7 +3992,7 @@ } public final void setAbsorptionAmount(float absorptionAmount) { @@ -1623,7 +1650,7 @@ } protected void internalSetAbsorptionAmount(float absorptionAmount) { -@@ -3410,9 +4044,14 @@ +@@ -3410,9 +4060,14 @@ } public void startUsingItem(InteractionHand hand) { @@ -1639,7 +1666,7 @@ this.useItem = itemstack; this.useItemRemaining = itemstack.getUseDuration(this); if (!this.level().isClientSide) { -@@ -3483,13 +4122,50 @@ +@@ -3483,13 +4138,50 @@ this.releaseUsingItem(); } else { if (!this.useItem.isEmpty() && this.isUsingItem()) { @@ -1653,7 +1680,7 @@ + org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand); + event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper + this.level().getCraftServer().getPluginManager().callEvent(event); -+ + + if (event.isCancelled()) { + // Update client + Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE); @@ -1677,7 +1704,7 @@ + } + // Paper end + // CraftBukkit end - ++ if (itemstack != this.useItem) { this.setItemInHand(enumhand, itemstack); } @@ -1691,7 +1718,7 @@ } } -@@ -3512,6 +4188,7 @@ +@@ -3512,6 +4204,7 @@ public void releaseUsingItem() { if (!this.useItem.isEmpty()) { @@ -1699,7 +1726,7 @@ this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks()); if (this.useItem.useOnRelease()) { this.updatingUsingItem(); -@@ -3544,11 +4221,68 @@ +@@ -3544,12 +4237,69 @@ if (this.isUsingItem() && !this.useItem.isEmpty()) { Item item = this.useItem.getItem(); @@ -1708,8 +1735,8 @@ } else { return null; } -+ } -+ + } + + // Paper start - Make shield blocking delay configurable + public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { + if (maxDistance < 1 || maxDistance > 120) { @@ -1764,12 +1791,13 @@ + + public void setShieldBlockingDelay(int shieldBlockingDelay) { + this.shieldBlockingDelay = shieldBlockingDelay; - } ++ } + // Paper end - Make shield blocking delay configurable - ++ public boolean isSuppressingSlidingDownLadder() { return this.isShiftKeyDown(); -@@ -3568,12 +4302,18 @@ + } +@@ -3568,12 +4318,18 @@ } public boolean randomTeleport(double x, double y, double z, boolean particleEffects) { @@ -1790,7 +1818,7 @@ Level world = this.level(); if (world.hasChunkAt(blockposition)) { -@@ -3592,18 +4332,43 @@ +@@ -3592,18 +4348,43 @@ } if (flag2) { @@ -1838,7 +1866,7 @@ world.broadcastEntityEvent(this, (byte) 46); } -@@ -3613,7 +4378,7 @@ +@@ -3613,7 +4394,7 @@ entitycreature.getNavigation().stop(); } @@ -1847,7 +1875,7 @@ } } -@@ -3706,7 +4471,7 @@ +@@ -3706,7 +4487,7 @@ } public void stopSleeping() { @@ -1856,7 +1884,7 @@ Level world = this.level(); java.util.Objects.requireNonNull(world); -@@ -3718,9 +4483,9 @@ +@@ -3718,9 +4499,9 @@ this.level().setBlock(blockposition, (BlockState) iblockdata.setValue(BedBlock.OCCUPIED, false), 3); Vec3 vec3d = (Vec3) BedBlock.findStandUpPosition(this.getType(), this.level(), blockposition, enumdirection, this.getYRot()).orElseGet(() -> { @@ -1868,7 +1896,7 @@ }); Vec3 vec3d1 = Vec3.atBottomCenterOf(blockposition).subtract(vec3d).normalize(); float f = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D); -@@ -3740,7 +4505,7 @@ +@@ -3740,7 +4521,7 @@ @Nullable public Direction getBedOrientation() { @@ -1877,7 +1905,7 @@ return blockposition != null ? BedBlock.getBedOrientation(this.level(), blockposition) : null; } -@@ -3905,7 +4670,7 @@ +@@ -3905,7 +4686,7 @@ public float maxUpStep() { float f = (float) this.getAttributeValue(Attributes.STEP_HEIGHT); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch index 187c2fd40f..6a10c73943 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch @@ -55,17 +55,18 @@ public class ItemEntity extends Entity implements TraceableEntity { -@@ -52,6 +60,9 @@ +@@ -52,6 +60,10 @@ @Nullable public UUID target; public final float bobOffs; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit + public boolean canMobPickup = true; // Paper - Item#canEntityPickup + private int despawnRate = -1; // Paper - Alternative item-despawn-rate ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API public ItemEntity(EntityType<? extends ItemEntity> type, Level world) { super(type, world); -@@ -61,7 +72,12 @@ +@@ -61,7 +73,12 @@ } public ItemEntity(Level world, double x, double y, double z, ItemStack stack) { @@ -79,7 +80,7 @@ } public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) { -@@ -133,12 +149,15 @@ +@@ -133,12 +150,15 @@ @Override public void tick() { if (this.getItem().isEmpty()) { @@ -99,7 +100,7 @@ this.xo = this.getX(); this.yo = this.getY(); -@@ -162,7 +181,7 @@ +@@ -162,12 +182,16 @@ } } @@ -108,7 +109,17 @@ this.move(MoverType.SELF, this.getDeltaMovement()); this.applyEffectsFromBlocks(); float f = 0.98F; -@@ -188,9 +207,11 @@ + +- if (this.onGround()) { ++ // Paper start - Friction API ++ if (frictionState == net.kyori.adventure.util.TriState.FALSE) { ++ f = 1F; ++ } else if (this.onGround()) { ++ // Paper end - Friction API + f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F; + } + +@@ -188,9 +212,11 @@ this.mergeWithNeighbours(); } @@ -120,7 +131,7 @@ this.hasImpulse |= this.updateInWaterStateAndDoFluidPushing(); if (!this.level().isClientSide) { -@@ -201,14 +222,42 @@ +@@ -201,14 +227,42 @@ } } @@ -165,7 +176,7 @@ public BlockPos getBlockPosBelowThatAffectsMyMovement() { return this.getOnPos(0.999999F); } -@@ -229,7 +278,10 @@ +@@ -229,7 +283,10 @@ private void mergeWithNeighbours() { if (this.isMergable()) { @@ -177,7 +188,7 @@ return entityitem != this && entityitem.isMergable(); }); Iterator iterator = list.iterator(); -@@ -238,6 +290,14 @@ +@@ -238,6 +295,14 @@ ItemEntity entityitem = (ItemEntity) iterator.next(); if (entityitem.isMergable()) { @@ -192,7 +203,7 @@ this.tryToMerge(entityitem); if (this.isRemoved()) { break; -@@ -251,7 +311,7 @@ +@@ -251,7 +316,7 @@ private boolean isMergable() { ItemStack itemstack = this.getItem(); @@ -201,7 +212,7 @@ } private void tryToMerge(ItemEntity other) { -@@ -259,7 +319,7 @@ +@@ -259,7 +324,7 @@ ItemStack itemstack1 = other.getItem(); if (Objects.equals(this.target, other.target) && ItemEntity.areMergable(itemstack, itemstack1)) { @@ -210,7 +221,7 @@ ItemEntity.merge(this, itemstack, other, itemstack1); } else { ItemEntity.merge(other, itemstack1, this, itemstack); -@@ -287,11 +347,16 @@ +@@ -287,11 +352,16 @@ } private static void merge(ItemEntity targetEntity, ItemStack targetStack, ItemEntity sourceEntity, ItemStack sourceStack) { @@ -228,7 +239,7 @@ } } -@@ -320,12 +385,17 @@ +@@ -320,12 +390,17 @@ } else if (!this.getItem().canBeHurtBy(source)) { return false; } else { @@ -247,9 +258,33 @@ } return true; -@@ -382,22 +452,86 @@ +@@ -339,6 +414,11 @@ + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putShort("Health", (short) this.health); + nbt.putShort("Age", (short) this.age); + nbt.putShort("PickupDelay", (short) this.pickupDelay); +@@ -381,23 +461,98 @@ + this.setItem(ItemStack.EMPTY); } ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API ++ if (this.getItem().isEmpty()) { - this.discard(); + this.discard(null); // CraftBukkit - add Bukkit remove cause @@ -264,7 +299,7 @@ ItemStack itemstack = this.getItem(); Item item = itemstack.getItem(); int i = itemstack.getCount(); - ++ + // CraftBukkit start - fire PlayerPickupItemEvent + int canHold = player.getInventory().canHold(itemstack); + int remaining = i - canHold; @@ -280,7 +315,7 @@ + if (flyAtPlayer) { + player.take(this, i); + } -+ + + return; + } + } @@ -337,7 +372,7 @@ itemstack.setCount(i); } -@@ -438,6 +572,7 @@ +@@ -438,6 +593,7 @@ public void setItem(ItemStack stack) { this.getEntityData().set(ItemEntity.DATA_ITEM, stack); @@ -345,7 +380,7 @@ } @Override -@@ -492,7 +627,7 @@ +@@ -492,7 +648,7 @@ public void makeFakeItem() { this.setNeverPickUp(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch index a2de0f0c89..e7b99cb727 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch @@ -14,7 +14,7 @@ public abstract class AbstractMinecart extends VehicleEntity { -@@ -76,6 +83,17 @@ +@@ -76,6 +83,18 @@ enummap.put(RailShape.NORTH_EAST, Pair.of(baseblockposition2, baseblockposition1)); }); @@ -28,11 +28,12 @@ + private double flyingZ = 0.95; + public Double maxSpeed; + // CraftBukkit end ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + protected AbstractMinecart(EntityType<?> type, Level world) { super(type, world); this.blocksBuilding = true; -@@ -101,7 +119,7 @@ +@@ -101,7 +120,7 @@ @Nullable public static <T extends AbstractMinecart> T createMinecart(Level world, double x, double y, double z, EntityType<T> type, EntitySpawnReason reason, ItemStack stack, @Nullable Player player) { @@ -41,7 +42,7 @@ if (t0 != null) { t0.setInitialPos(x, y, z); -@@ -139,11 +157,19 @@ +@@ -139,11 +158,19 @@ @Override public boolean canCollideWith(Entity other) { @@ -63,7 +64,7 @@ return true; } -@@ -262,6 +288,14 @@ +@@ -262,6 +289,14 @@ @Override public void tick() { @@ -78,7 +79,7 @@ if (this.getHurtTime() > 0) { this.setHurtTime(this.getHurtTime() - 1); } -@@ -271,8 +305,20 @@ +@@ -271,8 +306,20 @@ } this.checkBelowWorld(); @@ -100,7 +101,7 @@ this.updateInWaterStateAndDoFluidPushing(); if (this.isInLava()) { this.lavaHurt(); -@@ -385,12 +431,16 @@ +@@ -385,12 +432,16 @@ this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); if (this.onGround()) { @@ -119,7 +120,36 @@ } } -@@ -520,7 +570,16 @@ +@@ -502,6 +553,16 @@ + + this.flipped = nbt.getBoolean("FlippedRotation"); + this.firstTick = nbt.getBoolean("HasTicked"); ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API + } + + @Override +@@ -514,13 +575,28 @@ + + nbt.putBoolean("FlippedRotation", this.flipped); + nbt.putBoolean("HasTicked", this.firstTick); ++ ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + } + + @Override public void push(Entity entity) { if (!this.level().isClientSide) { if (!entity.noPhysics && !this.noPhysics) { @@ -136,7 +166,7 @@ double d0 = entity.getX() - this.getX(); double d1 = entity.getZ() - this.getZ(); double d2 = d0 * d0 + d1 * d1; -@@ -645,4 +704,27 @@ +@@ -645,4 +721,27 @@ public boolean isFurnace() { return false; } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch index 689dd5c249..8bed619b55 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch @@ -24,16 +24,17 @@ return (double) world.getGameRules().getInt(GameRules.RULE_MINECART_MAX_SPEED) * (this.minecart.isInWater() ? 0.5D : 1.0D) / 20.0D; } -@@ -544,7 +554,7 @@ +@@ -544,7 +554,8 @@ @Override public double getSlowdownFactor() { - return this.minecart.isVehicle() ? 0.997D : 0.975D; ++ if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper + return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.975D; // CraftBukkit - add !this.slowWhenEmpty } @Override -@@ -571,6 +581,14 @@ +@@ -571,6 +582,14 @@ Entity entity = (Entity) iterator.next(); if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.minecart.isVehicle() && !entity.isPassenger()) { @@ -48,7 +49,7 @@ boolean flag = entity.startRiding(this.minecart); if (flag) { -@@ -597,6 +615,16 @@ +@@ -597,6 +616,16 @@ Entity entity = (Entity) iterator.next(); if (entity instanceof Player || entity instanceof IronGolem || entity instanceof AbstractMinecart || this.minecart.isVehicle() || entity.isPassenger()) { @@ -65,7 +66,7 @@ entity.push((Entity) this.minecart); flag = true; } -@@ -609,6 +637,14 @@ +@@ -609,6 +638,14 @@ Entity entity1 = (Entity) iterator1.next(); if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch index 67506888ef..5f791f5901 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch @@ -53,7 +53,7 @@ entity1.push((Entity) this.minecart); } } -@@ -487,11 +517,17 @@ +@@ -487,11 +517,18 @@ @Override public double getMaxSpeed(ServerLevel world) { @@ -69,6 +69,7 @@ @Override public double getSlowdownFactor() { - return this.minecart.isVehicle() ? 0.997D : 0.96D; ++ if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper + return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java index 1a291dd8a2..30d62ee4d5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java @@ -99,6 +99,18 @@ public class CraftItem extends CraftEntity implements Item { this.getHandle().age = willAge ? 0 : NO_AGE_TIME; } + @org.jetbrains.annotations.NotNull + @Override + public net.kyori.adventure.util.TriState getFrictionState() { + return this.getHandle().frictionState; + } + + @Override + public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { + java.util.Objects.requireNonNull(state, "state may not be null"); + this.getHandle().frictionState = state; + } + @Override public int getHealth() { return this.getHandle().health; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index 20b84c87ef..812194dac1 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -1198,4 +1198,17 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { nmsStack.hurtAndBreak(amount, this.getHandle(), slot, true); } // Paper end - ItemStack damage API + // Paper start - friction API + @org.jetbrains.annotations.NotNull + @Override + public net.kyori.adventure.util.TriState getFrictionState() { + return this.getHandle().frictionState; + } + + @Override + public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { + java.util.Objects.requireNonNull(state, "state may not be null"); + this.getHandle().frictionState = state; + } + // Paper end - friction API } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java index e02ecbdc15..b42bce0c4f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java @@ -127,4 +127,18 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { public int getDisplayBlockOffset() { return this.getHandle().getDisplayOffset(); } + + // Paper start - Friction API + @org.jetbrains.annotations.NotNull + @Override + public net.kyori.adventure.util.TriState getFrictionState() { + return this.getHandle().frictionState; + } + + @Override + public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { + java.util.Objects.requireNonNull(state, "state may not be null"); + this.getHandle().frictionState = state; + } + // Paper end - Friction API }