diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index a1e4347d98b..da492673c23 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -2009,6 +2009,10 @@ public boolean onUpdate(int currentTick) { } this.inAirTicks = 0; this.highestPosition = this.y; + // 地面阻力 + this.motionX *= 0.3; + this.motionY = 0; + this.motionZ *= 0.3; } else { if (this.checkMovement && !this.isGliding() && !server.getAllowFlight() && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && this.inAirTicks > 20 && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING)) { double expectedVelocity = (-this.getGravity()) / ((double) this.getDrag()) - ((-this.getGravity()) / ((double) this.getDrag())) * Math.exp(-((double) this.getDrag()) * ((double) (this.inAirTicks - this.startAirTicks))); @@ -2036,10 +2040,18 @@ public boolean onUpdate(int currentTick) { if (this.isGliding()) this.resetFallDistance(); - ++this.inAirTicks; + // 空气阻力 + this.motionX *= 0.9900000095367432; + this.motionY *= 0.9800000190734863; + this.motionZ *= 0.9900000095367432; + ++this.inAirTicks; } + if (Math.abs(this.motionX) < 0.0001) this.motionX = 0; + if (Math.abs(this.motionY) < 0.0001) this.motionY = 0; + if (Math.abs(this.motionZ) < 0.0001) this.motionZ = 0; + if (this.isSurvivalLike()) { if (this.getFoodData() != null) this.getFoodData().update(tickDiff); } @@ -4826,16 +4838,9 @@ public boolean attack(EntityDamageEvent source) { if (add) { source.setDamage((float) (source.getDamage() * 1.3)); - - AnimatePacket animate = new AnimatePacket(); - animate.action = AnimatePacket.Action.CRITICAL_HIT; - animate.eid = getId(); - this.getLevel().addChunkPacket(damager.getChunkX(), damager.getChunkZ(), animate); - - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_STRONG); } - if (doubleCritical) { + /*if (doubleCritical) { //正在叠刀 Player damagerPlayer = (Player)((EntityDamageByEntityEvent) source).getDamager(); if (damagerPlayer.attackCriticalThisJump < 2) { @@ -4852,7 +4857,7 @@ public boolean attack(EntityDamageEvent source) { damagerPlayer.dataPacket(animate); this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_STRONG, new Player[]{damagerPlayer}); - } + }*/ } if (super.attack(source)) { //!source.isCancelled() @@ -4862,7 +4867,7 @@ public boolean attack(EntityDamageEvent source) { EntityEventPacket pk = new EntityEventPacket(); pk.eid = this.id; pk.event = EntityEventPacket.HURT_ANIMATION; - Server.broadcastPacket(this.hasSpawned.values(), pk); + // 这边只发给自己,因为广播给他人的已经在EntityLiving中发送了 this.dataPacket(pk); if (add) { if (((EntityDamageByEntityEvent) source).getDamager() instanceof Player) { @@ -4870,20 +4875,20 @@ public boolean attack(EntityDamageEvent source) { (((EntityDamageByEntityEvent) source).getDamager()).addEffect(Effect.getEffect(Effect.SLOWNESS).setDuration(10).setAmplifier(1).setVisible(false));*/ ((Player)(((EntityDamageByEntityEvent) source).getDamager())).attackCriticalThisJump++; } - /* - Random random = ThreadLocalRandom.current(); - for (int i = 0; i < 10; i++) { - CriticalParticle par = new CriticalParticle(new Vector3(this.x + random.nextDouble() * 2 - 1, this.y + random.nextDouble() * 2, this.z + random.nextDouble() * 2 - 1)); - this.getLevel().addParticle(par); - }*/ + // 在这里发送暴击,因为事件可能被取消 + AnimatePacket animate = new AnimatePacket(); + animate.action = AnimatePacket.Action.CRITICAL_HIT; + animate.eid = getId(); + this.getLevel().addChunkPacket(this.getChunkX(), this.getChunkZ(), animate); + + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_STRONG); } - if (doubleCritical) { + /*if (doubleCritical) { if (((EntityDamageByEntityEvent) source).getDamager() instanceof Player) { Player damagerPlayer = (Player)((EntityDamageByEntityEvent) source).getDamager(); damagerPlayer.attackCriticalThisJump++; } - } - + }*/ } return true; } else { diff --git a/src/main/java/cn/nukkit/entity/Entity.java b/src/main/java/cn/nukkit/entity/Entity.java index e37761c424e..4bdefafaa71 100644 --- a/src/main/java/cn/nukkit/entity/Entity.java +++ b/src/main/java/cn/nukkit/entity/Entity.java @@ -2,11 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.Server; -import cn.nukkit.block.Block; -import cn.nukkit.block.BlockDripstonePointed; -import cn.nukkit.block.BlockID; -import cn.nukkit.block.BlockLiquid; -import cn.nukkit.block.BlockWater; +import cn.nukkit.block.*; import cn.nukkit.blockentity.BlockEntityPistonArm; import cn.nukkit.entity.attribute.Attribute; import cn.nukkit.entity.data.*; @@ -18,7 +14,6 @@ import cn.nukkit.event.player.PlayerInteractEvent.Action; import cn.nukkit.event.player.PlayerTeleportEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.GameRule; import cn.nukkit.level.Level; import cn.nukkit.level.Location; @@ -46,7 +41,10 @@ import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.util.*; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; @@ -1080,37 +1078,17 @@ public void despawnFrom(Player player) { } } + public boolean isInvulnerableTo(EntityDamageEvent damageSource) { + return this.isClosed() || this.invulnerable && damageSource.getCause() != DamageCause.VOID && !(damageSource.getEntity() instanceof Player player && player.isCreativeLike()); + } + public boolean attack(EntityDamageEvent source) { - if ((source.getCause() == DamageCause.FIRE || source.getCause() == DamageCause.FIRE_TICK - || source.getCause() == DamageCause.LAVA || source.getCause() == DamageCause.MAGMA) - && (fireProof || hasEffect(Effect.FIRE_RESISTANCE))) { + if (this.isInvulnerableTo(source)) { return false; - } - - getServer().getPluginManager().callEvent(source); - if (source.isCancelled()) { + } else { + // this.markHurt(); // Java Edition return false; } - - if (source.getCause() != DamageCause.SUICIDE) { - // Make fire aspect to set the target in fire before dealing any damage so the target is in fire on death even if killed by the first hit - if (source instanceof EntityDamageByEntityEvent) { - Enchantment[] enchantments = ((EntityDamageByEntityEvent) source).getWeaponEnchantments(); - if (enchantments != null) { - for (Enchantment enchantment : enchantments) { - enchantment.doAttack(((EntityDamageByEntityEvent) source).getDamager(), this); - } - } - } - - if (this.absorption > 0) { // Damage Absorption - this.setAbsorption(Math.max(0, this.getAbsorption() + source.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION))); - } - } - - setLastDamageCause(source); - setHealth(getHealth() - source.getFinalDamage()); - return true; } public boolean attack(float damage) { diff --git a/src/main/java/cn/nukkit/entity/EntityHumanType.java b/src/main/java/cn/nukkit/entity/EntityHumanType.java index 6b087e76e1e..235494a9371 100644 --- a/src/main/java/cn/nukkit/entity/EntityHumanType.java +++ b/src/main/java/cn/nukkit/entity/EntityHumanType.java @@ -152,7 +152,7 @@ public Item[] getDrops() { } @Override - public boolean attack(EntityDamageEvent source) { + protected boolean damageEntity0(EntityDamageEvent source) { if (this.isClosed() || !this.isAlive()) { return false; } @@ -173,14 +173,14 @@ public boolean attack(EntityDamageEvent source) { } source.setDamage(-source.getFinalDamage() * Math.min(Mth.ceil(Math.min(epf, 25) * ((float) ThreadLocalRandom.current().nextInt(50, 100) / 100)), 20) * 0.04f, - DamageModifier.ARMOR_ENCHANTMENTS); + DamageModifier.ARMOR_ENCHANTMENTS); } if (source.getCause() != DamageCause.SUICIDE) { source.setDamage(-Math.min(this.getAbsorption(), source.getFinalDamage()), DamageModifier.ABSORPTION); } - if (super.attack(source)) { + if (super.damageEntity0(source)) { if (source.getCause() == DamageCause.SUICIDE) { return true; } @@ -208,18 +208,18 @@ public boolean attack(EntityDamageEvent source) { } if (source.getCause() != DamageCause.VOID - && source.getCause() != DamageCause.MAGIC - && source.getCause() != DamageCause.WITHER - && source.getCause() != DamageCause.HUNGER - && source.getCause() != DamageCause.DROWNING - && source.getCause() != DamageCause.SUFFOCATION - && source.getCause() != DamageCause.FIRE_TICK - && source.getCause() != DamageCause.FREEZE - && source.getCause() != DamageCause.TEMPERATURE - && source.getCause() != DamageCause.FALL - && source.getCause() != DamageCause.STALAGMITE - && source.getCause() != DamageCause.FLY_INTO_WALL - && source.getCause() != DamageCause.SONIC_BOOM + && source.getCause() != DamageCause.MAGIC + && source.getCause() != DamageCause.WITHER + && source.getCause() != DamageCause.HUNGER + && source.getCause() != DamageCause.DROWNING + && source.getCause() != DamageCause.SUFFOCATION + && source.getCause() != DamageCause.FIRE_TICK + && source.getCause() != DamageCause.FREEZE + && source.getCause() != DamageCause.TEMPERATURE + && source.getCause() != DamageCause.FALL + && source.getCause() != DamageCause.STALAGMITE + && source.getCause() != DamageCause.FLY_INTO_WALL + && source.getCause() != DamageCause.SONIC_BOOM ) { // No armor damage if (armor.isUnbreakable() || armor instanceof ItemSkull || armor.getId() == ItemBlockID.CARVED_PUMPKIN || armor.getId() == Item.ELYTRA) { continue; @@ -241,6 +241,15 @@ public boolean attack(EntityDamageEvent source) { } } + @Override + public boolean attack(EntityDamageEvent source) { + if (this.isClosed() || !this.isAlive()) { + return false; + } + + return super.attack(source); + } + protected double calculateEnchantmentProtectionFactor(Item item, EntityDamageEvent source) { if (!item.hasEnchantments()) { return 0; diff --git a/src/main/java/cn/nukkit/entity/EntityLiving.java b/src/main/java/cn/nukkit/entity/EntityLiving.java index 7d5980540e4..58d2dba917f 100644 --- a/src/main/java/cn/nukkit/entity/EntityLiving.java +++ b/src/main/java/cn/nukkit/entity/EntityLiving.java @@ -1,6 +1,5 @@ package cn.nukkit.entity; -import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; @@ -8,14 +7,16 @@ import cn.nukkit.entity.data.FloatEntityData; import cn.nukkit.entity.data.ShortEntityData; import cn.nukkit.entity.passive.EntityWaterAnimal; -import cn.nukkit.event.entity.*; +import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; +import cn.nukkit.event.entity.EntityDeathEvent; import cn.nukkit.inventory.PlayerInventory; import cn.nukkit.item.Item; import cn.nukkit.item.ItemID; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.sound.SoundEnum; import cn.nukkit.math.Mth; import cn.nukkit.math.Vector2; import cn.nukkit.math.Vector3; @@ -24,6 +25,7 @@ import cn.nukkit.network.protocol.EntityEventPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.potion.Effect; +import cn.nukkit.potion.EffectID; import cn.nukkit.utils.BlockIterator; import java.util.ArrayList; @@ -54,6 +56,7 @@ protected float getDrag() { @Deprecated protected int attackTime = 0; protected long nextAllowAttack = 0; // EC优化,在低TPS时也确保正确的攻击冷却时间 + protected float lastHurt; protected boolean invisible = false; @@ -121,24 +124,17 @@ public void collidingWith(Entity ent) { // can override (IronGolem|Bats) @Override public boolean attack(EntityDamageEvent source) { - if (source.getCause() != DamageCause.SUICIDE) { - if (System.currentTimeMillis() < this.nextAllowAttack/*this.attackTime > 0*/) { - EntityDamageEvent lastCause = this.getLastDamageCause(); - if (this instanceof Player) { - if (lastCause != null && (lastCause.getFinalDamage() == 0 || lastCause.getCause() == DamageCause.FIRE_TICK)) { - //上次伤害是0,这次允许输出 - } else { - //叠刀时的自我安慰 - if (source instanceof EntityDamageByEntityEvent && source.getCause() == DamageCause.ENTITY_ATTACK && ((EntityDamageByEntityEvent) source).getDamager() instanceof Player) - this.getLevel().addSound(this.add(0, 15, 0), SoundEnum.GAME_PLAYER_HURT, 1, 1, (Player) ((EntityDamageByEntityEvent) source).getDamager()); - return false; - } - } else { - if (lastCause != null && lastCause.getDamage() >= source.getDamage()) { - return false; - } - } + if (this.isInvulnerableTo(source)) { + return false; + } else if (this.isClosed() || !this.isAlive()) { + return false; + } else if (source.getCause() == DamageCause.FIRE_TICK || source.getCause() == DamageCause.LAVA || source.getCause() == DamageCause.FIRE) { + if (this.hasEffect(EffectID.FIRE_RESISTANCE)) { + return false; } + } + + if (source.getCause() != DamageCause.SUICIDE) { if (this.noDamageTicks > 0) { EntityDamageEvent lastCause = this.getLastDamageCause(); if (lastCause != null && lastCause.getDamage() >= source.getDamage()) { @@ -147,33 +143,96 @@ public boolean attack(EntityDamageEvent source) { } } - if (super.attack(source)) { + float damage = source.getDamage(); + boolean hurtAffect = true; + if (source.getCause() != DamageCause.SUICIDE) { + // 冷却中 + if (System.currentTimeMillis() < this.nextAllowAttack) { + if (damage <= this.lastHurt) { + return false; + } + source.setDamage(damage - lastHurt); + if (!damageEntity0(source)) { + return false; + } + this.lastHurt = damage; + // 这边不修改冷却时间,因为只是补上了冷却期间的伤害差 + hurtAffect = false; + } else { + if (!damageEntity0(source)) { + return false; + } + this.lastHurt = damage; + // EC优化,在低TPS时也确保正确的攻击冷却时间 + this.nextAllowAttack = System.currentTimeMillis() + source.getAttackCooldown() * 50L; + } + } + if (hurtAffect) { + // 变红效果和音效 + this.onHurt(source); + // 击退 + if (source instanceof EntityDamageByEntityEvent ev && ev.hasKnockBack()) { + double deltaX = this.x - ev.getDamager().x; + double deltaZ = this.z - ev.getDamager().z; + this.knockBack(ev.getDamager(), damage, deltaX, deltaZ, ((EntityDamageByEntityEvent) source).getKnockBackH(), ((EntityDamageByEntityEvent) source).getKnockBackV()); + } + } + return hurtAffect; + + /*if (super.attack(source)) { if (source instanceof EntityDamageByEntityEvent) { Entity e = ((EntityDamageByEntityEvent) source).getDamager(); - /*if (source instanceof EntityDamageByChildEntityEvent) { + *//*if (source instanceof EntityDamageByChildEntityEvent) { e = ((EntityDamageByChildEntityEvent) source).getChild(); - }*/ + }*//* if (e.isOnFire() && !(e instanceof Player)) { this.setOnFire(2 * this.server.getDifficulty()); } - - if (((EntityDamageByEntityEvent) source).hasKnockBack()) { - double deltaX = this.x - e.x; - double deltaZ = this.z - e.z; - this.knockBack(e, source.getDamage(), deltaX, deltaZ, ((EntityDamageByEntityEvent) source).getKnockBackH(), ((EntityDamageByEntityEvent) source).getKnockBackV()); - } } onHurt(source); // this.attackTime = source.getAttackCooldown(); - this.nextAllowAttack = System.currentTimeMillis() + source.getAttackCooldown() * 50L; // EC优化,在低TPS时也确保正确的攻击冷却时间 + // this.nextAllowAttack 在super.attack中已经设置 + // this.nextAllowAttack = System.currentTimeMillis() + source.getAttackCooldown() * 50L; // EC优化,在低TPS时也确保正确的攻击冷却时间 return true; } else { return false; + }*/ + } + + /** + * 属于EntityLiving的处理伤害逻辑 + * 在此处应用护甲和附魔、药水等计算 + * @param source 伤害来源(事件) + * @return 是否成功造成伤害 + */ + protected boolean damageEntity0(EntityDamageEvent source) { + source.call(); + if (source.isCancelled()) { + return false; } + if (source.getCause() != DamageCause.SUICIDE) { + // Make fire aspect to set the target in fire before dealing any damage so the target is in fire on death even if killed by the first hit + if (source instanceof EntityDamageByEntityEvent damageByEntityEvent) { + Enchantment[] enchantments = damageByEntityEvent.getWeaponEnchantments(); + if (enchantments != null) { + for (Enchantment enchantment : enchantments) { + enchantment.doAttack(damageByEntityEvent.getDamager(), this); + } + } + } + + if (this.absorption > 0) { // Damage Absorption + this.setAbsorption(Math.max(0, this.getAbsorption() + source.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION))); + } + } + + setLastDamageCause(source); + setHealth(getHealth() - source.getFinalDamage()); + return true; } protected void onHurt(EntityDamageEvent source) {