From 4544678bf99bdebeec657b3b92587756b5f3a8a4 Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Mon, 30 Dec 2024 23:02:33 +0100 Subject: [PATCH 1/6] Rewrite attack and damage events --- SpongeAPI | 2 +- .../LivingEntityMixin_Forge_Damage.java | 82 ++++ .../player/PlayerMixin_Forge_Attack_Impl.java | 48 --- .../player/PlayerMixin_Forge_Damage.java | 30 +- .../resources/mixins.spongeforge.core.json | 4 +- .../mixins.spongeforge.core.shared.json | 4 +- .../LivingEntityMixin_Neo_Attack_Impl.java | 62 --- .../entity/LivingEntityMixin_Neo_Damage.java | 113 +++++ .../player/PlayerMixin_Neo_Attack_Impl.java | 60 --- .../entity/player/PlayerMixin_Neo_Damage.java | 74 ++++ .../resources/mixins.spongeneo.core.json | 4 +- .../world/entity/TrackedAttackBridge.java} | 16 +- .../world/entity/TrackedDamageBridge.java | 40 +- .../entity/damage/SpongeAttackTracker.java | 121 ++++++ .../cause/entity/damage/SpongeDamageStep.java | 199 +++++++++ ...ierType.java => SpongeDamageStepType.java} | 10 +- .../entity/damage/SpongeDamageTracker.java | 223 ++++++++++ .../common/registry/SpongeRegistries.java | 2 +- .../registry/loader/SpongeRegistryLoader.java | 43 +- .../common/util/DamageEventUtil.java | 361 ---------------- .../level/ServerPlayerMixin_Attack_impl.java | 81 ---- .../core/world/entity/ExperienceOrbMixin.java | 8 +- .../entity/LivingEntityMixin_Attack_Impl.java | 397 ------------------ .../entity/LivingEntityMixin_Damage.java | 235 +++++++++++ .../LivingEntityMixin_Shared_Attack_Impl.java | 52 --- .../LivingEntityMixin_Shared_Damage.java | 72 ++++ .../core/world/entity/MobMixin_Attack.java | 95 +++++ .../world/entity/MobMixin_Attack_Impl.java | 77 ---- .../boss/enderdragon/EndCrystalMixin.java | 6 +- .../entity/decoration/ArmorStandMixin.java | 67 ++- .../decoration/BlockAttachedEntityMixin.java | 8 +- .../entity/decoration/ItemFrameMixin.java | 11 +- .../world/entity/item/ItemEntityMixin.java | 9 +- .../entity/player/PlayerMixin_Attack.java | 311 ++++++++++++++ .../player/PlayerMixin_Attack_Impl.java | 318 -------------- .../player/PlayerMixin_Shared_Damage.java | 67 +++ .../entity/projectile/ShulkerBulletMixin.java | 8 +- .../entity/vehicle/MinecartTNTMixin.java | 7 +- .../entity/vehicle/VehicleEntityMixin.java | 8 +- .../enchantment/EnchantmentMixin_Attack.java | 72 ++++ src/mixins/resources/mixins.sponge.core.json | 9 +- .../spongepowered/test/damage/DamageTest.java | 65 +-- .../entity/LivingEntityMixin_Attack_Impl.java | 71 ---- .../LivingEntityMixin_Vanilla_Damage.java | 113 +++++ .../player/PlayerMixin_Vanilla_Damage.java | 25 +- .../resources/mixins.spongevanilla.core.json | 4 +- .../mixins.spongevanilla.core.shared.json | 4 +- 47 files changed, 1977 insertions(+), 1721 deletions(-) create mode 100644 forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java delete mode 100644 forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Attack_Impl.java rename vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Attack_Impl.java => forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Damage.java (57%) delete mode 100644 neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Attack_Impl.java create mode 100644 neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java delete mode 100644 neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Attack_Impl.java create mode 100644 neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java rename src/{mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin.java => main/java/org/spongepowered/common/bridge/world/entity/TrackedAttackBridge.java} (76%) rename forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Attack_Impl.java => src/main/java/org/spongepowered/common/bridge/world/entity/TrackedDamageBridge.java (56%) create mode 100644 src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java create mode 100644 src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java rename src/main/java/org/spongepowered/common/event/cause/entity/damage/{SpongeDamageModifierType.java => SpongeDamageStepType.java} (82%) create mode 100644 src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java delete mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin_Attack_impl.java delete mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java create mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java delete mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Attack_Impl.java create mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java create mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java delete mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack_Impl.java create mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java delete mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack_Impl.java create mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java create mode 100644 src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java delete mode 100644 vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java create mode 100644 vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java rename src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Attack_Impl.java => vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Damage.java (62%) diff --git a/SpongeAPI b/SpongeAPI index de5564d39d0..267c5df1e0f 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit de5564d39d01e2721f5521a77836d3fc1bff001a +Subproject commit 267c5df1e0fe396a0ffc7f7768bb845dff092bfe diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java new file mode 100644 index 00000000000..3053fac4c92 --- /dev/null +++ b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java @@ -0,0 +1,82 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.forge.mixin.core.world.entity; + +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraftforge.event.ForgeEventFactory; +import net.minecraftforge.event.entity.living.ShieldBlockEvent; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; +import org.spongepowered.common.item.util.ItemStackUtil; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin_Forge_Damage implements TrackedDamageBridge { + + @ModifyConstant(method = "actuallyHurt", constant = @Constant(floatValue = 0), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/ForgeHooks;onLivingHurt(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F"))) + private float damage$doNotReturnEarly(final float constant) { + return Float.NaN; + } + + @SuppressWarnings("UnstableApiUsage") + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/event/ForgeEventFactory;onShieldBlock(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)Lnet/minecraftforge/event/entity/living/ShieldBlockEvent;")) + private ShieldBlockEvent damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageSource source, final float originalDamage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return ForgeEventFactory.onShieldBlock(self, source, originalDamage); + } + + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, originalDamage, ItemStackUtil.snapshotOf(self.getUseItem())); + float damage = (float) step.applyModifiersBefore(); + final ShieldBlockEvent event; + if (step.isSkipped()) { + event = new ShieldBlockEvent(self, source, damage); + event.setCanceled(true); + } else { + event = ForgeEventFactory.onShieldBlock(self, source, damage); + if (!event.isCanceled()) { + damage -= event.getBlockedDamage(); + } + } + step.applyModifiersAfter(damage); + return event; + } + + @ModifyArg(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/ForgeHooks;onLivingDamage(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)F")) + private float damage$firePostEvent_Living(final float damage) { + return this.damage$firePostEvent(damage); + } +} diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Attack_Impl.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Attack_Impl.java deleted file mode 100644 index 91996604abb..00000000000 --- a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Attack_Impl.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.forge.mixin.core.world.entity.player; - -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; -import net.minecraftforge.common.ForgeHooks; -import net.minecraftforge.event.entity.player.CriticalHitEvent; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.common.util.DamageEventUtil; - -@Mixin(Player.class) -public class PlayerMixin_Forge_Attack_Impl { - private DamageEventUtil.Attack attackImpl$attack; - - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/ForgeHooks;getCriticalHit(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/entity/Entity;ZF)Lnet/minecraftforge/event/entity/player/CriticalHitEvent;")) - private CriticalHitEvent attackImpl$critHook(final Player player, final Entity target, final boolean vanillaCritical, final float damageModifier) { - final CriticalHitEvent event = ForgeHooks.getCriticalHit(player, target, vanillaCritical, damageModifier); - if (event != null) { - this.attackImpl$attack.functions().add(DamageEventUtil.provideCriticalAttackFunction(this.attackImpl$attack.sourceEntity(), event.getDamageModifier())); - } - return event; - } -} diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Attack_Impl.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Damage.java similarity index 57% rename from vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Attack_Impl.java rename to forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Damage.java index 90e32691442..efffd9fb1e6 100644 --- a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Attack_Impl.java +++ b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/player/PlayerMixin_Forge_Damage.java @@ -22,31 +22,29 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.vanilla.mixin.core.world.entity.player; +package org.spongepowered.forge.mixin.core.world.entity.player; import net.minecraft.world.entity.player.Player; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.Slice; -import org.spongepowered.common.util.DamageEventUtil; +import org.spongepowered.forge.mixin.core.world.entity.LivingEntityMixin_Forge_Damage; @Mixin(Player.class) -public abstract class PlayerMixin_Vanilla_Attack_Impl { - private DamageEventUtil.Attack attackImpl$attack; +public abstract class PlayerMixin_Forge_Damage extends LivingEntityMixin_Forge_Damage { - /** - * Captures the crit multiplier as a function - */ - @ModifyConstant(method = "attack", constant = @Constant(floatValue = 1.5F), - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isSprinting()Z", ordinal = 1), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnownMovement()Lnet/minecraft/world/phys/Vec3;")) - ) - public float attackImpl$critHook(final float constant) { - // if (isCritical) damage *= 1.5F; - final var bonusDamageFunc = DamageEventUtil.provideCriticalAttackFunction(this.attackImpl$attack.sourceEntity(), constant); - this.attackImpl$attack.functions().add(bonusDamageFunc); - return constant; + @ModifyConstant(method = "actuallyHurt", constant = @Constant(floatValue = 0), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/ForgeHooks;onLivingHurt(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F"))) + private float damage$doNotReturnEarly(final float constant) { + return Float.NaN; + } + + @ModifyArg(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/ForgeHooks;onLivingDamage(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)F")) + private float damage$firePostEvent_Player(final float damage) { + return this.damage$firePostEvent(damage); } } diff --git a/forge/src/mixins/resources/mixins.spongeforge.core.json b/forge/src/mixins/resources/mixins.spongeforge.core.json index 68f35f28eb2..7aae2731cc6 100644 --- a/forge/src/mixins/resources/mixins.spongeforge.core.json +++ b/forge/src/mixins/resources/mixins.spongeforge.core.json @@ -29,9 +29,9 @@ "server.network.ServerGamePacketListenerImplMixin_Forge", "tags.TagLoaderMixin_Forge", "world.entity.LivingEntityMixin_Forge", - "world.entity.LivingEntityMixin_Forge_Attack_Impl", + "world.entity.LivingEntityMixin_Forge_Damage", "world.entity.item.ItemEntityMixin_Forge", - "world.entity.player.PlayerMixin_Forge_Attack_Impl", + "world.entity.player.PlayerMixin_Forge_Damage", "world.entity.vehicle.BoatMixin_Forge", "world.level.ServerExplosionMixin_Forge", "world.level.block.FireBlockMixin_Forge", diff --git a/forge/src/mixins/resources/mixins.spongeforge.core.shared.json b/forge/src/mixins/resources/mixins.spongeforge.core.shared.json index b322779182e..e23ba17fbba 100644 --- a/forge/src/mixins/resources/mixins.spongeforge.core.shared.json +++ b/forge/src/mixins/resources/mixins.spongeforge.core.shared.json @@ -5,8 +5,8 @@ "priority": 1301, "mixins": [ "server.level.ServerEntityMixin_Shared", - "world.entity.LivingEntityMixin_Shared_Attack_Impl", - "world.entity.player.PlayerMixin_Shared_Attack_Impl", + "world.entity.LivingEntityMixin_Shared_Damage", + "world.entity.player.PlayerMixin_Shared_Damage", "world.entity.projectile.FishingHookMixin_Shared" ], "overwrites": { diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Attack_Impl.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Attack_Impl.java deleted file mode 100644 index 0701bb119c5..00000000000 --- a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Attack_Impl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.neoforge.mixin.core.world.entity; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.LivingEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.common.util.DamageEventUtil; - -@Mixin(LivingEntity.class) -public class LivingEntityMixin_Neo_Attack_Impl { - - protected DamageEventUtil.DamageEventResult attackImpl$actuallyHurtResult; - - /** - * Set absorbed damage after calling {@link LivingEntity#setAbsorptionAmount} in which we called the event - */ - @ModifyVariable(method = "actuallyHurt", ordinal = 2, - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setAbsorptionAmount(F)V", shift = At.Shift.AFTER)) - public float attackImpl$setAbsorbed(final float value) { - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - return 0; - } - return this.attackImpl$actuallyHurtResult.damageAbsorbed().orElse(0f); - } - - /** - * Prevents {@link ServerPlayer#awardStat} from running before event - */ - @Redirect(method = "getDamageAfterMagicAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V")) - public void attackImpl$onAwardStatDamageResist(final ServerPlayer instance, final ResourceLocation resourceLocation, final int i) { - // do nothing - } - -} diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java new file mode 100644 index 00000000000..34bdc2a842d --- /dev/null +++ b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java @@ -0,0 +1,113 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.neoforge.mixin.core.world.entity; + +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.damagesource.DamageContainer; +import net.neoforged.neoforge.event.entity.living.LivingShieldBlockEvent; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; +import org.spongepowered.common.item.util.ItemStackUtil; + +import java.util.Stack; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridge { + + @Shadow protected Stack damageContainers; + + @Override + public float damage$getContainerDamage(final float damage) { + return this.damageContainers.peek().getNewDamage(); + } + + @Override + public void damage$setContainerDamage(final float damage) { + this.damageContainers.peek().setNewDamage(damage); + } + + @SuppressWarnings("UnstableApiUsage") + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onDamageBlock(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;Z)Lnet/neoforged/neoforge/event/entity/living/LivingShieldBlockEvent;")) + private LivingShieldBlockEvent damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageContainer container, final boolean blocked) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !blocked) { // don't capture when vanilla wouldn't block + return CommonHooks.onDamageBlock(self, container, false); + } + + final float originalDamage = container.getNewDamage(); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, originalDamage, ItemStackUtil.snapshotOf(self.getUseItem())); + float damage = (float) step.applyModifiersBefore(); + container.setNewDamage(damage); + final LivingShieldBlockEvent event; + if (step.isSkipped()) { + event = new LivingShieldBlockEvent(self, container, true); + event.setBlocked(true); + } else { + event = CommonHooks.onDamageBlock(self, container, true); + container.setBlockedDamage(event); + damage = container.getNewDamage(); + } + step.applyModifiersAfter(damage); + return event; + } + + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setBlockedDamage(Lnet/neoforged/neoforge/event/entity/living/LivingShieldBlockEvent;)V")) + private void damage$cancelSetBlockedDamage(final DamageContainer container, final LivingShieldBlockEvent event) { + // We already did it above + } + + @ModifyVariable(method = "actuallyHurt", ordinal = 1, + at = @At(value = "INVOKE_ASSIGN", target = "Lnet/neoforged/neoforge/common/CommonHooks;onLivingDamagePre(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;)F")) + private float damage$modifyBeforeAbsorption(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); + } + + @SuppressWarnings("UnstableApiUsage") + @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setReduction(Lnet/neoforged/neoforge/common/damagesource/DamageContainer$Reduction;F)V"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onLivingDamagePre(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;)F"))) + private void damage$skipAbsorption(final DamageContainer container, final DamageContainer.Reduction reduction, final float absorbed) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { + container.setReduction(reduction, absorbed); + } + } + + @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), ordinal = 3, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getCombatTracker()Lnet/minecraft/world/damagesource/CombatTracker;"))) + private float damage$firePostEvent_Living(final float damage) { + return this.damage$firePostEvent(damage); + } +} diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Attack_Impl.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Attack_Impl.java deleted file mode 100644 index 4973ee5e62b..00000000000 --- a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Attack_Impl.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.neoforge.mixin.core.world.entity.player; - -import net.minecraft.world.entity.player.Player; -import net.neoforged.neoforge.event.entity.player.CriticalHitEvent; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.common.util.DamageEventUtil; -import org.spongepowered.neoforge.mixin.core.world.entity.LivingEntityMixin_Neo_Attack_Impl; - -@Mixin(Player.class) -public class PlayerMixin_Neo_Attack_Impl extends LivingEntityMixin_Neo_Attack_Impl { - private DamageEventUtil.Attack attackImpl$attack; - - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/event/entity/player/CriticalHitEvent;isCriticalHit()Z")) - private boolean attackImpl$critHook(final CriticalHitEvent event) { - if (event.isCriticalHit()) { - this.attackImpl$attack.functions().add(DamageEventUtil.provideCriticalAttackFunction(this.attackImpl$attack.sourceEntity(), event.getDamageMultiplier())); - return true; - } - return false; - } - - /** - * Set absorbed damage after calling {@link Player#setAbsorptionAmount} in which we called the event - */ - @ModifyVariable(method = "actuallyHurt", ordinal = 2, - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V", shift = At.Shift.AFTER)) - public float attackImpl$setAbsorbed(final float value) { - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - return 0; - } - return this.attackImpl$actuallyHurtResult.damageAbsorbed().orElse(0f); - } -} diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java new file mode 100644 index 00000000000..f1f10a35e24 --- /dev/null +++ b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java @@ -0,0 +1,74 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.neoforge.mixin.core.world.entity.player; + +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.common.damagesource.DamageContainer; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.common.bridge.world.entity.TrackedAttackBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; +import org.spongepowered.neoforge.mixin.core.world.entity.LivingEntityMixin_Neo_Damage; + +@Mixin(Player.class) +public abstract class PlayerMixin_Neo_Damage extends LivingEntityMixin_Neo_Damage implements TrackedAttackBridge { + + @Shadow public abstract double shadow$entityInteractionRange(); + + @Override + public double attack$interactionRangeSquared() { + return Mth.square(this.shadow$entityInteractionRange()); + } + + @ModifyVariable(method = "actuallyHurt", ordinal = 1, + at = @At(value = "INVOKE_ASSIGN", target = "Lnet/neoforged/neoforge/common/CommonHooks;onLivingDamagePre(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;)F")) + private float damage$modifyBeforeAbsorption(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); + } + + @SuppressWarnings("UnstableApiUsage") + @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setReduction(Lnet/neoforged/neoforge/common/damagesource/DamageContainer$Reduction;F)V"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onLivingDamagePre(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;)F"))) + private void damage$skipAbsorption(final DamageContainer container, final DamageContainer.Reduction reduction, final float absorbed) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { + container.setReduction(reduction, absorbed); + } + } + + @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), ordinal = 3, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V"))) + private float damage$firePostEvent_Player(final float damage) { + return this.damage$firePostEvent(damage); + } +} diff --git a/neoforge/src/mixins/resources/mixins.spongeneo.core.json b/neoforge/src/mixins/resources/mixins.spongeneo.core.json index edf633ff4ef..135ad8490aa 100644 --- a/neoforge/src/mixins/resources/mixins.spongeneo.core.json +++ b/neoforge/src/mixins/resources/mixins.spongeneo.core.json @@ -28,10 +28,10 @@ "tags.TagLoaderMixin_NeoForge", "world.entity.EntityMixin_Neo", "world.entity.LivingEntityMixin_Neo", - "world.entity.LivingEntityMixin_Neo_Attack_Impl", + "world.entity.LivingEntityMixin_Neo_Damage", "world.entity.animal.SnowGolemMixin_Neo", "world.entity.item.ItemEntityMixin_Neo", - "world.entity.player.PlayerMixin_Neo_Attack_Impl", + "world.entity.player.PlayerMixin_Neo_Damage", "world.entity.projectile.FishingHookMixin_Neo", "world.entity.vehicle.AbstractBoatMixin_Neo", "world.level.ServerExplosionMixin_NeoForge", diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin.java b/src/main/java/org/spongepowered/common/bridge/world/entity/TrackedAttackBridge.java similarity index 76% rename from src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin.java rename to src/main/java/org/spongepowered/common/bridge/world/entity/TrackedAttackBridge.java index 7968d13b542..debf57e2a62 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin.java +++ b/src/main/java/org/spongepowered/common/bridge/world/entity/TrackedAttackBridge.java @@ -22,12 +22,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.common.mixin.core.world.item.enchantment; +package org.spongepowered.common.bridge.world.entity; -import net.minecraft.world.item.enchantment.Enchantment; -import org.spongepowered.asm.mixin.Mixin; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.common.event.cause.entity.damage.SpongeAttackTracker; -@Mixin(Enchantment.class) -public abstract class EnchantmentMixin { +public interface TrackedAttackBridge { + @Nullable + SpongeAttackTracker attack$tracker(); + + // Neo hook + default double attack$interactionRangeSquared() { + return 9; + } } diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Attack_Impl.java b/src/main/java/org/spongepowered/common/bridge/world/entity/TrackedDamageBridge.java similarity index 56% rename from forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Attack_Impl.java rename to src/main/java/org/spongepowered/common/bridge/world/entity/TrackedDamageBridge.java index 1cb83895595..6ba9a52c8f3 100644 --- a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Attack_Impl.java +++ b/src/main/java/org/spongepowered/common/bridge/world/entity/TrackedDamageBridge.java @@ -22,25 +22,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.forge.mixin.core.world.entity; +package org.spongepowered.common.bridge.world.entity; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.stats.Stat; -import net.minecraft.world.entity.LivingEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; -@Mixin(LivingEntity.class) -public class LivingEntityMixin_Forge_Attack_Impl { +public interface TrackedDamageBridge { - /** - * Prevents {@link ServerPlayer#awardStat} from running before event - */ - @Redirect(method = "getDamageAfterMagicAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/stats/Stat;I)V")) - public void attackImpl$onAwardStatDamageResist(final ServerPlayer instance, final Stat resourceLocation, final int i) { - // do nothing + @Nullable + SpongeDamageTracker damage$tracker(); + + default float damage$firePostEvent(float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return damage; + } + damage = tracker.endStep(DamageStepTypes.ABSORPTION, damage); + damage = tracker.callDamagePostEvent((org.spongepowered.api.entity.Entity) this, damage); + this.damage$setContainerDamage(damage); + return damage; } + // Neo hook + default void damage$setContainerDamage(final float damage) {} + + // Neo hook + default float damage$getContainerDamage(final float damage) { + return damage; + } } diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java new file mode 100644 index 00000000000..6fc811873cb --- /dev/null +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java @@ -0,0 +1,121 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.event.cause.entity.damage; + +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.item.ItemStack; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.entity.AttackEntityEvent; +import org.spongepowered.api.event.entity.DamageCalculationEvent; +import org.spongepowered.api.item.inventory.ItemStackSnapshot; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.bridge.world.entity.TrackedAttackBridge; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.item.util.ItemStackUtil; + +import java.util.List; + +public class SpongeAttackTracker extends SpongeDamageTracker { + private final ItemStack weapon; + private final ItemStackSnapshot weaponSnapshot; + + private float attackStrength; + private boolean strongSprint = false; + + public SpongeAttackTracker(final DamageCalculationEvent.Pre preEvent, final ItemStack weapon) { + super(preEvent); + this.weapon = weapon; + this.weaponSnapshot = ItemStackUtil.snapshotOf(weapon); + } + + @Override + public AttackEntityEvent.Pre preEvent() { + return (AttackEntityEvent.Pre) super.preEvent(); + } + + @Override + public AttackEntityEvent.Post postEvent() { + return (AttackEntityEvent.Post) super.postEvent(); + } + + public ItemStack weapon() { + return this.weapon; + } + + public ItemStackSnapshot weaponSnapshot() { + return this.weaponSnapshot; + } + + public float attackStrength() { + return this.attackStrength; + } + + public void setAttackStrength(final float attackStrength) { + this.attackStrength = attackStrength; + } + + public boolean isStrongSprint() { + return this.strongSprint; + } + + public void setStrongSprint(final boolean strongSprint) { + this.strongSprint = strongSprint; + } + + public boolean callAttackPostEvent(final Entity entity, final DamageSource source, final float finalDamage, final float knockbackModifier) { + final List steps = this.preparePostEvent(); + + try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + SpongeDamageTracker.generateCauseFor(source, frame); + + final AttackEntityEvent.Post event = SpongeEventFactory.createAttackEntityEventPost(frame.currentCause(), + this.preEvent.originalBaseDamage(), this.preEvent.baseDamage(), finalDamage, finalDamage, knockbackModifier, knockbackModifier, entity, steps); + + this.postEvent = event; + return SpongeCommon.post(event); + } + } + + public static @Nullable SpongeAttackTracker callAttackPreEvent(final Entity entity, final DamageSource source, final float baseDamage, final ItemStack weapon) { + try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + SpongeDamageTracker.generateCauseFor(source, frame); + + final AttackEntityEvent.Pre event = SpongeEventFactory.createAttackEntityEventPre(frame.currentCause(), baseDamage, baseDamage, entity); + if (SpongeCommon.post(event)) { + return null; + } + + return new SpongeAttackTracker(event, weapon); + } + } + + public static @Nullable SpongeAttackTracker of(final DamageSource source) { + return source.getDirectEntity() instanceof TrackedAttackBridge tracked ? tracked.attack$tracker() : null; + } +} diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java new file mode 100644 index 00000000000..a5a912b4d30 --- /dev/null +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java @@ -0,0 +1,199 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.event.cause.entity.damage; + +import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.cause.entity.damage.DamageModifier; +import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; + +import java.util.List; +import java.util.StringJoiner; + +public final class SpongeDamageStep implements DamageStep { + private static final Logger LOGGER = LogManager.getLogger(); + + private final DamageStepType type; + private final Cause cause; + private final List modifiersBefore; + private final List modifiersAfter; + + private State state = State.BEFORE; + private boolean skipped; + + private final double damageBeforeModifiers; + private double damageBeforeStep; + private double damageAfterStep; + private double damageAfterModifiers; + + public SpongeDamageStep(final DamageStepType type, final double initialDamage, final Cause cause, + final List modifiersBefore, final List modifiersAfter) { + this.type = type; + this.cause = cause; + this.damageBeforeModifiers = initialDamage; + this.modifiersBefore = ImmutableList.copyOf(modifiersBefore); + this.modifiersAfter = ImmutableList.copyOf(modifiersAfter); + } + + @Override + public DamageStepType type() { + return this.type; + } + + @Override + public Cause cause() { + return this.cause; + } + + @Override + public boolean isSkipped() { + return this.skipped; + } + + @Override + public void setSkipped(boolean skipped) { + if (this.state != State.BEFORE) { + throw new IllegalStateException("Step can only be skipped before occurring"); + } + this.skipped = skipped; + } + + @Override + public double damageBeforeModifiers() { + return this.damageBeforeModifiers; + } + + @Override + public double damageBeforeStep() { + if (this.state == State.BEFORE) { + throw new IllegalStateException("Before modifiers haven't finished"); + } + return this.damageBeforeStep; + } + + @Override + public double damageAfterStep() { + if (this.state == State.BEFORE) { + throw new IllegalStateException("Step hasn't started"); + } + if (this.state == State.STEP) { + throw new IllegalStateException("Step hasn't finished"); + } + return this.damageAfterStep; + } + + @Override + public double damageAfterModifiers() { + if (this.state != State.END) { + throw new IllegalStateException("Modifiers haven't finished"); + } + return this.damageAfterModifiers; + } + + @Override + public List modifiersBefore() { + return this.modifiersBefore; + } + + @Override + public List modifiersAfter() { + return this.modifiersAfter; + } + + public State state() { + return this.state; + } + + @Override + public String toString() { + return new StringJoiner(", ", SpongeDamageStep.class.getSimpleName() + "[", "]") + .add("type=" + this.type) + .add("cause=" + this.cause) + .add("damageBeforeModifiers=" + this.damageBeforeModifiers) + .add("damageBeforeStep=" + this.damageBeforeStep) + .add("damageAfterStep=" + this.damageAfterStep) + .add("damageAfterModifiers=" + this.damageAfterModifiers) + .add("modifiersBefore=" + this.modifiersBefore) + .add("modifiersAfter=" + this.modifiersAfter) + .add("state=" + this.state) + .toString(); + } + + public double applyModifiersBefore() { + if (this.state != State.BEFORE) { + throw new IllegalStateException(); + } + + double damage = this.damageBeforeModifiers; + for (DamageModifier modifier : this.modifiersBefore) { + try { + damage = modifier.modify(this, damage); + } catch (Exception e) { + LOGGER.error("Failed to apply modifier {} before step {}", modifier, this, e); + } + } + + this.damageBeforeStep = damage; + this.state = State.STEP; + + return damage; + } + + public double applyModifiersAfter(double damage) { + if (this.state != State.STEP) { + throw new IllegalStateException(); + } + + if (this.skipped) { + damage = this.damageBeforeStep; + } + + this.damageAfterStep = damage; + this.state = State.AFTER; + + for (DamageModifier modifier : this.modifiersBefore) { + try { + damage = modifier.modify(this, damage); + } catch (Exception e) { + LOGGER.error("Failed to apply modifier {} after step {}", modifier, this, e); + } + } + + this.damageAfterModifiers = damage; + this.state = State.END; + + return damage; + } + + public enum State { + BEFORE, + STEP, + AFTER, + END + } +} diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifierType.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java similarity index 82% rename from src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifierType.java rename to src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java index f17035514b7..1d35edb2ba3 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifierType.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java @@ -25,16 +25,16 @@ package org.spongepowered.common.event.cause.entity.damage; import org.spongepowered.api.ResourceKey; -import org.spongepowered.api.event.cause.entity.damage.DamageModifierType; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; import org.spongepowered.api.registry.RegistryTypes; -public final class SpongeDamageModifierType implements DamageModifierType { +public final class SpongeDamageStepType implements DamageStepType { @Override public String toString() { - return RegistryTypes.DAMAGE_MODIFIER_TYPE.get().findValueKey(this) + return RegistryTypes.DAMAGE_STEP_TYPE.get().findValueKey(this) .map(ResourceKey::toString) - .map("DamageModifierType[%s]"::formatted) - .orElse(super.toString()); + .map("DamageStepType[%s]"::formatted) + .orElseGet(super::toString); } } diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java new file mode 100644 index 00000000000..8a363caf2ce --- /dev/null +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java @@ -0,0 +1,223 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.event.cause.entity.damage; + +import com.google.common.collect.ImmutableList; +import net.minecraft.core.BlockPos; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.player.Player; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.event.EventContext; +import org.spongepowered.api.event.EventContextKeys; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; +import org.spongepowered.api.event.entity.DamageCalculationEvent; +import org.spongepowered.api.event.entity.DamageEntityEvent; +import org.spongepowered.api.registry.DefaultedRegistryReference; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.bridge.CreatorTrackedBridge; +import org.spongepowered.common.bridge.world.damagesource.DamageSourceBridge; +import org.spongepowered.common.bridge.world.level.chunk.LevelChunkBridge; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.util.VecHelper; + +import java.util.ArrayList; +import java.util.List; + +public class SpongeDamageTracker { + private static final Logger LOGGER = LogManager.getLogger(); + + protected final List steps = new ArrayList<>(); + protected final DamageCalculationEvent.Pre preEvent; + protected DamageCalculationEvent.Post postEvent; + + public SpongeDamageTracker(final DamageCalculationEvent.Pre preEvent) { + this.preEvent = preEvent; + } + + public DamageCalculationEvent.Pre preEvent() { + return this.preEvent; + } + + public DamageCalculationEvent.Post postEvent() { + if (this.postEvent == null) { + throw new IllegalStateException("Post event not yet fired"); + } + return this.postEvent; + } + + public SpongeDamageStep newStep(final DefaultedRegistryReference typeRef, final float damage, final Object... causes) { + final DamageStepType type = typeRef.get(); + final SpongeDamageStep step = new SpongeDamageStep(type, damage, Cause.of(EventContext.empty(), List.of(causes)), this.preEvent.modifiersBefore(type), this.preEvent.modifiersAfter(type)); + + if (this.postEvent != null) { + LOGGER.warn("A new step {} is being captured after the post event.", step); + } + + if (!this.steps.isEmpty()) { + final SpongeDamageStep previous = this.steps.getLast(); + if (previous.state() != SpongeDamageStep.State.END) { + LOGGER.warn("A new step {} is being captured but previous step {} hasn't finished.", step, previous); + this.steps.removeLast(); + } + } + + this.steps.add(step); + return step; + } + + public float startStep(final DefaultedRegistryReference typeRef, final float damage, final Object... causes) { + return (float) this.newStep(typeRef, damage, causes).applyModifiersBefore(); + } + + public @Nullable SpongeDamageStep currentStep(final DefaultedRegistryReference typeRef) { + if (this.steps.isEmpty()) { + LOGGER.warn("Expected a current step of type {} but no step has been captured yet.", typeRef.location()); + return null; + } + + final DamageStepType type = typeRef.get(); + final SpongeDamageStep step = this.steps.getLast(); + if (step.type() != type) { + LOGGER.warn("Expected a current step of type {} but got {}.", type, step); + return null; + } + return step; + } + + public float endStep(final DefaultedRegistryReference typeRef, final float damage) { + final SpongeDamageStep step = this.currentStep(typeRef); + return step == null ? damage : (float) step.applyModifiersAfter(damage); + } + + public boolean isSkipped(final DefaultedRegistryReference typeRef) { + final SpongeDamageStep step = this.currentStep(typeRef); + return step != null && step.isSkipped(); + } + + public @Nullable SpongeDamageStep lastStep(final DefaultedRegistryReference typeRef) { + final DamageStepType type = typeRef.get(); + for (int i = this.steps.size() - 1; i >= 0; i--) { + final SpongeDamageStep step = this.steps.get(i); + if (step.type() == type) { + return step; + } + } + return null; + } + + public float damageAfter(final DefaultedRegistryReference typeRef) { + final SpongeDamageStep step = this.lastStep(typeRef); + return step == null ? 0 : (float) step.damageAfterModifiers(); + } + + protected List preparePostEvent() { + if (this.postEvent != null) { + throw new IllegalStateException("Post event already fired"); + } + + if (!this.steps.isEmpty()) { + final SpongeDamageStep last = this.steps.getLast(); + if (last.state() != SpongeDamageStep.State.END) { + LOGGER.warn("Calling post event but last step {} hasn't finished.", last); + return ImmutableList.copyOf(this.steps.subList(0, this.steps.size() - 1)); + } + } + + return ImmutableList.copyOf(this.steps); + } + + public float callDamagePostEvent(final Entity entity, final float finalDamage) { + final List steps = this.preparePostEvent(); + + try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + SpongeDamageTracker.generateCauseFor((DamageSource) this.preEvent.source(), frame); + + final DamageEntityEvent.Post event = SpongeEventFactory.createDamageEntityEventPost(frame.currentCause(), + this.preEvent.originalBaseDamage(), this.preEvent.baseDamage(), finalDamage, finalDamage, entity, steps); + + this.postEvent = event; + if (SpongeCommon.post(event)) { + return 0; + } + return (float) event.finalDamage(); + } + } + + protected static void generateCauseFor(final DamageSource source, final CauseStackManager.StackFrame frame) { + if (source.getDirectEntity() instanceof org.spongepowered.api.entity.Entity entity) { + if (!(entity instanceof Player) && entity instanceof CreatorTrackedBridge creatorBridge) { + creatorBridge.tracker$getCreatorUUID().ifPresent(creator -> frame.addContext(EventContextKeys.CREATOR, creator)); + creatorBridge.tracker$getNotifierUUID().ifPresent(notifier -> frame.addContext(EventContextKeys.NOTIFIER, notifier)); + } + } else if (((DamageSourceBridge) source).bridge$blockLocation() != null) { + final ServerLocation location = ((DamageSourceBridge) source).bridge$blockLocation(); + final BlockPos blockPos = VecHelper.toBlockPos(location); + final LevelChunkBridge chunkBridge = (LevelChunkBridge) ((net.minecraft.world.level.Level) location.world()).getChunkAt(blockPos); + chunkBridge.bridge$getBlockCreatorUUID(blockPos).ifPresent(creator -> frame.addContext(EventContextKeys.CREATOR, creator)); + chunkBridge.bridge$getBlockNotifierUUID(blockPos).ifPresent(notifier -> frame.addContext(EventContextKeys.NOTIFIER, notifier)); + } + frame.pushCause(source); + } + + public static @Nullable SpongeDamageTracker callDamagePreEvent(final Entity entity, final DamageSource source, final float baseDamage) { + try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + SpongeDamageTracker.generateCauseFor(source, frame); + + final DamageEntityEvent.Pre event = SpongeEventFactory.createDamageEntityEventPre(frame.currentCause(), baseDamage, baseDamage, entity); + if (SpongeCommon.post(event)) { + return null; + } + + return new SpongeDamageTracker(event); + } + } + + public static DamageEntityEvent.@Nullable Post callDamageEvents(final Entity entity, final DamageSource source, final float baseDamage) { + try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + SpongeDamageTracker.generateCauseFor(source, frame); + + final DamageEntityEvent.Pre preEvent = SpongeEventFactory.createDamageEntityEventPre(frame.currentCause(), baseDamage, baseDamage, entity); + if (SpongeCommon.post(preEvent)) { + return null; + } + + final DamageEntityEvent.Post postEvent = SpongeEventFactory.createDamageEntityEventPost(frame.currentCause(), + preEvent.originalBaseDamage(), preEvent.baseDamage(), preEvent.baseDamage(), preEvent.baseDamage(), entity, List.of()); + if (SpongeCommon.post(postEvent)) { + return null; + } + + return postEvent; + } + } +} diff --git a/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java b/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java index a377af375e3..c044c343400 100644 --- a/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java +++ b/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java @@ -63,7 +63,7 @@ public static void registerEarlyGlobalRegistries(final SpongeRegistryHolder hold holder.createFrozenRegistry(RegistryTypes.BODY_PART, SpongeRegistryLoader.bodyPart()); holder.createFrozenRegistry(RegistryTypes.CLICK_TYPE, SpongeRegistryLoader.clickType()); holder.createFrozenRegistry(RegistryTypes.CHUNK_REGENERATE_FLAG, SpongeRegistryLoader.chunkRegenerateFlag()); - holder.createFrozenRegistry(RegistryTypes.DAMAGE_MODIFIER_TYPE, SpongeRegistryLoader.damageModifierType()); + holder.createFrozenRegistry(RegistryTypes.DAMAGE_STEP_TYPE, SpongeRegistryLoader.damageStepType()); holder.createFrozenRegistry(RegistryTypes.DISMOUNT_TYPE, SpongeRegistryLoader.dismountType()); holder.createFrozenRegistry(RegistryTypes.GOAL_EXECUTOR_TYPE, SpongeRegistryLoader.goalExecutorType()); holder.createFrozenRegistry(RegistryTypes.GOAL_TYPE, SpongeRegistryLoader.goalType()); diff --git a/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java b/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java index db0d4af33a2..d6dd0c8a252 100644 --- a/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java +++ b/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java @@ -65,8 +65,8 @@ import org.spongepowered.api.event.cause.entity.MovementTypes; import org.spongepowered.api.event.cause.entity.SpawnType; import org.spongepowered.api.event.cause.entity.SpawnTypes; -import org.spongepowered.api.event.cause.entity.damage.DamageModifierType; -import org.spongepowered.api.event.cause.entity.damage.DamageModifierTypes; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.api.item.inventory.ItemStackSnapshot; import org.spongepowered.api.item.inventory.menu.ClickType; import org.spongepowered.api.item.inventory.menu.ClickTypes; @@ -126,7 +126,7 @@ import org.spongepowered.common.event.cause.entity.SpongeMovementType; import org.spongepowered.common.event.cause.entity.SpongeSpawnType; import org.spongepowered.common.event.cause.entity.SpongeSpawnTypes; -import org.spongepowered.common.event.cause.entity.damage.SpongeDamageModifierType; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStepType; import org.spongepowered.common.inventory.menu.handler.SpongeClickType; import org.spongepowered.common.inventory.query.SpongeOneParamQueryType; import org.spongepowered.common.inventory.query.SpongeQueryTypes; @@ -219,25 +219,24 @@ public static RegistryLoader> clickType() { ))); } - public static RegistryLoader damageModifierType() { - return RegistryLoader.of(l -> l.mapping(SpongeDamageModifierType::new, m -> m.add( - DamageModifierTypes.ABSORPTION, - DamageModifierTypes.ARMOR, - DamageModifierTypes.ARMOR_ENCHANTMENT, - DamageModifierTypes.ATTACK_COOLDOWN, - DamageModifierTypes.CRITICAL_HIT, - DamageModifierTypes.DEFENSIVE_POTION_EFFECT, - DamageModifierTypes.DIFFICULTY, - DamageModifierTypes.HARD_HAT, - DamageModifierTypes.MAGIC, - DamageModifierTypes.NEGATIVE_POTION_EFFECT, - DamageModifierTypes.OFFENSIVE_POTION_EFFECT, - DamageModifierTypes.SHIELD, - DamageModifierTypes.SWEEPING, - DamageModifierTypes.WEAPON_ENCHANTMENT, - DamageModifierTypes.WEAPON_BONUS, - DamageModifierTypes.ATTACK_STRENGTH, - DamageModifierTypes.FREEZING_BONUS + public static RegistryLoader damageStepType() { + return RegistryLoader.of(l -> l.mapping(SpongeDamageStepType::new, m -> m.add( + DamageStepTypes.ABSORPTION, + DamageStepTypes.ARMOR, + DamageStepTypes.ARMOR_ENCHANTMENT, + DamageStepTypes.BASE_COOLDOWN, + DamageStepTypes.CRITICAL_HIT, + DamageStepTypes.DEFENSIVE_POTION_EFFECT, + DamageStepTypes.ENCHANTMENT_COOLDOWN, + DamageStepTypes.FREEZING_BONUS, + DamageStepTypes.HARD_HAT, + DamageStepTypes.MAGIC, + DamageStepTypes.NEGATIVE_POTION_EFFECT, + DamageStepTypes.OFFENSIVE_POTION_EFFECT, + DamageStepTypes.SHIELD, + DamageStepTypes.SWEEPING, + DamageStepTypes.WEAPON_BONUS, + DamageStepTypes.WEAPON_ENCHANTMENT ))); } diff --git a/src/main/java/org/spongepowered/common/util/DamageEventUtil.java b/src/main/java/org/spongepowered/common/util/DamageEventUtil.java index cef66e91d88..ac40ff9fc85 100644 --- a/src/main/java/org/spongepowered/common/util/DamageEventUtil.java +++ b/src/main/java/org/spongepowered/common/util/DamageEventUtil.java @@ -25,111 +25,19 @@ package org.spongepowered.common.util; import net.minecraft.core.BlockPos; -import net.minecraft.core.component.DataComponents; -import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; -import net.minecraft.world.damagesource.CombatRules; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.Mob; -import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.Enchantment; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.ItemEnchantments; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.phys.AABB; -import org.apache.commons.lang3.mutable.MutableFloat; -import org.spongepowered.api.event.Cause; -import org.spongepowered.api.event.CauseStackManager; -import org.spongepowered.api.event.EventContext; -import org.spongepowered.api.event.EventContextKeys; -import org.spongepowered.api.event.SpongeEventFactory; -import org.spongepowered.api.event.cause.entity.damage.DamageFunction; -import org.spongepowered.api.event.cause.entity.damage.DamageModifier; -import org.spongepowered.api.event.cause.entity.damage.DamageModifierType; -import org.spongepowered.api.event.cause.entity.damage.DamageModifierTypes; -import org.spongepowered.api.event.entity.AttackEntityEvent; -import org.spongepowered.api.event.entity.DamageEntityEvent; -import org.spongepowered.api.registry.DefaultedRegistryReference; -import org.spongepowered.api.util.Tuple; import org.spongepowered.api.world.server.ServerLocation; import org.spongepowered.api.world.server.ServerWorld; -import org.spongepowered.common.SpongeCommon; -import org.spongepowered.common.bridge.CreatorTrackedBridge; -import org.spongepowered.common.bridge.world.damagesource.DamageSourceBridge; -import org.spongepowered.common.bridge.world.level.chunk.LevelChunkBridge; -import org.spongepowered.common.event.cause.entity.damage.SpongeDamageSources; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.item.util.ItemStackUtil; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.DoubleUnaryOperator; import java.util.function.Predicate; public final class DamageEventUtil { - private DamageEventUtil() { - } - - - @SuppressWarnings("ConstantConditions") - public static DamageFunction createHardHatModifier(final ItemStack headItem, final float multiplier) { - final var snapshot = ItemStackUtil.snapshotOf(headItem); - final var modifier = DamageEventUtil.buildDamageReductionModifier(DamageModifierTypes.HARD_HAT, snapshot); - return new DamageFunction(modifier, damage -> damage * multiplier); - } - - /** - * LivingEntity#getDamageAfterArmorAbsorb - */ - public static DamageFunction createArmorModifiers(final LivingEntity living, final DamageSource damageSource) { - final DoubleUnaryOperator function = dmg -> CombatRules.getDamageAfterAbsorb(living, (float) dmg, - damageSource, living.getArmorValue(), (float) living.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); - final var modifier = DamageEventUtil.buildDamageReductionModifierWithFrame(DamageModifierTypes.ARMOR, living, Attributes.ARMOR_TOUGHNESS); - - return DamageFunction.of(modifier, function); - } - - /** - * LivingEntity#getDamageAfterMagicAbsorb - */ - public static DamageFunction createResistanceModifier(final LivingEntity living) { - final var effect = living.getEffect(MobEffects.DAMAGE_RESISTANCE); - var modifier = DamageEventUtil.buildDamageReductionModifier(DamageModifierTypes.DEFENSIVE_POTION_EFFECT, effect); - return new DamageFunction(modifier, DamageEventUtil.createResistanceFunction(living)); - } - - public static DoubleUnaryOperator createResistanceFunction(final LivingEntity living) { - final var effect = living.getEffect(MobEffects.DAMAGE_RESISTANCE); - final int base = effect == null ? 0 : (effect.getAmplifier() + 1) * 5; - final int modifier = 25 - base; - return damage -> Math.max(((damage * modifier) / 25.0F), 0.0f); - } - - - /** - * LivingEntity#getDamageAfterMagicAbsorb - */ - public static DamageFunction createEnchantmentModifiers(final LivingEntity living, final float damageProtection) { - final DoubleUnaryOperator func = damage -> CombatRules.getDamageAfterMagicAbsorb((float) damage, damageProtection); - final var modifier = DamageEventUtil.buildDamageReductionModifierWithFrame(DamageModifierTypes.ARMOR_ENCHANTMENT, living); - return new DamageFunction(modifier, func); - } - - public static DamageFunction createAbsorptionModifier(final LivingEntity living, final float absorptionAmount) { - final var modifier = DamageEventUtil.buildDamageReductionModifier(DamageModifierTypes.ABSORPTION, living); - return new DamageFunction(modifier, damage -> Math.max(damage - absorptionAmount, 0.0F)); - } - public static ServerLocation findFirstMatchingBlock(final Entity entity, final AABB bb, final Predicate predicate) { final int i = Mth.floor(bb.minX); final int j = Mth.floor(bb.maxX + 1.0D); @@ -156,273 +64,4 @@ public static ServerLocation findFirstMatchingBlock(final Entity entity, final A // Entity is source of fire return ((org.spongepowered.api.entity.Entity) entity).serverLocation(); } - - /** - * This applies various contexts based on the type of {@link DamageSource}, whether - * it's provided by sponge or vanilla. This is not stack neutral, which is why it requires - * a {@link CauseStackManager.StackFrame} reference to push onto the stack. - */ - public static void generateCauseFor(final DamageSource damageSource, final CauseStackManager.StackFrame frame) { - if (damageSource.getDirectEntity() instanceof org.spongepowered.api.entity.Entity entity) { - if (!(entity instanceof Player) && entity instanceof CreatorTrackedBridge creatorBridge) { - creatorBridge.tracker$getCreatorUUID().ifPresent(creator -> frame.addContext(EventContextKeys.CREATOR, creator)); - creatorBridge.tracker$getNotifierUUID().ifPresent(notifier -> frame.addContext(EventContextKeys.NOTIFIER, notifier)); - } - } else if (((DamageSourceBridge) damageSource).bridge$blockLocation() != null) { - final ServerLocation location = ((DamageSourceBridge) damageSource).bridge$blockLocation(); - final BlockPos blockPos = VecHelper.toBlockPos(location); - final LevelChunkBridge chunkBridge = (LevelChunkBridge) ((net.minecraft.world.level.Level) location.world()).getChunkAt(blockPos); - chunkBridge.bridge$getBlockCreatorUUID(blockPos).ifPresent(creator -> frame.addContext(EventContextKeys.CREATOR, creator)); - chunkBridge.bridge$getBlockNotifierUUID(blockPos).ifPresent(notifier -> frame.addContext(EventContextKeys.NOTIFIER, notifier)); - } - frame.pushCause(damageSource); - } - - /** - * Mirrors {@link EnchantmentHelper#modifyDamage} - */ - public static List createAttackEnchantmentFunction(final ItemStack weapon, final Entity entity, final DamageSource damageSource) { - final var enchantments = weapon.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); - final var snapshot = ItemStackUtil.snapshotOf(weapon); - - return enchantments.entrySet().stream().map(entry -> { - final var enchantment = entry.getKey().value(); - final int level = entry.getIntValue(); - - final var modifier = DamageEventUtil.buildAttackEnchantmentModifier(DamageModifierTypes.WEAPON_ENCHANTMENT, snapshot, enchantment); - - return new DamageFunction(modifier, damage -> - DamageEventUtil.enchantmentDamageFunction(weapon, entity, damageSource, damage, enchantment, level)); - }).toList(); - } - - public static DamageFunction provideSeparateEnchantmentFromBaseDamageFunction(final float baseDamage, final ItemStack weapon) { - final var modifier = DamageEventUtil.buildAttackEnchantmentModifier(DamageModifierTypes.WEAPON_ENCHANTMENT, ItemStackUtil.snapshotOf(weapon)); - return new DamageFunction(modifier, damage -> damage - baseDamage); - } - - public static DamageFunction provideCooldownEnchantmentStrengthFunction(final ItemStack weapon, final float attackStrength) { - final var snapshot = ItemStackUtil.snapshotOf(weapon); - final var modifier = DamageEventUtil.buildAttackEnchantmentModifier(DamageModifierTypes.ATTACK_STRENGTH, snapshot); - return new DamageFunction(modifier, damage -> damage * attackStrength); - } - - private static double enchantmentDamageFunction(final ItemStack weapon, final Entity entity, - final DamageSource damageSource, final double damage, final Enchantment enchantment, final int level) { - var totalDamage = new MutableFloat(damage); - enchantment.modifyDamage((ServerLevel) entity.level(), level, weapon, entity, damageSource, totalDamage); - return totalDamage.doubleValue(); - } - - public static DamageFunction provideCriticalAttackFunction(final Player player, double criticalModifier) { - final var modifier = DamageEventUtil.buildAttackDamageModifier(DamageModifierTypes.CRITICAL_HIT, player); - final DoubleUnaryOperator function = (damage) -> damage * criticalModifier; - return new DamageFunction(modifier, function); - } - - public static DamageFunction provideCooldownAttackStrengthFunction(final Player player, final float attackStrength) { - final var modifier = DamageEventUtil.buildAttackDamageModifier(DamageModifierTypes.ATTACK_COOLDOWN, player); - final DoubleUnaryOperator function = (damage) -> damage * (0.2F + attackStrength * attackStrength * 0.8F); - return new DamageFunction(modifier, function); - } - - public static DamageFunction provideWeaponAttackDamageBonusFunction(final Entity targetEntity, final ItemStack weapon, final DamageSource damageSource) { - final var modifier = DamageEventUtil.buildAttackDamageModifier(DamageModifierTypes.WEAPON_BONUS, targetEntity); - final DoubleUnaryOperator function = (damage) -> damage + weapon.getItem().getAttackDamageBonus(targetEntity, (float) damage, damageSource); - return new DamageFunction(modifier, function); - } - - public static DamageFunction provideSweepingDamageRatioFunction(final ItemStack held, final Player player, final double attackDamage) { - final var modifier = DamageEventUtil.buildAttackEnchantmentModifier(DamageModifierTypes.SWEEPING, ItemStackUtil.snapshotOf(held)); - return DamageFunction.of(modifier, damage -> damage + player.getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * attackDamage); - } - - @SuppressWarnings("ConstantConditions") - public static DamageFunction createShieldFunction(final LivingEntity entity) { - final var snapshot = ItemStackUtil.snapshotOf(entity.getUseItem()); - final var modifier = DamageEventUtil.buildDamageReductionModifier(DamageModifierTypes.SHIELD, entity, snapshot); - return new DamageFunction(modifier, (damage) -> 0); - } - - public static DamageFunction createFreezingBonus(final LivingEntity entity, final DamageSource damageSource, float multiplier) { - final var modifier = DamageEventUtil.buildDamageReductionModifier(DamageModifierTypes.FREEZING_BONUS, damageSource, entity); - return new DamageFunction(modifier, (damage) -> damage * multiplier); - } - - private static DamageModifier buildDamageReductionModifierWithFrame(final DefaultedRegistryReference modifierType, Object... causes) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { - for (final Object cause : causes) { - frame.pushCause(cause); - } - return DamageModifier.builder().damageReductionGroup() - .cause(frame.currentCause()).type(modifierType).build(); - } - } - - private static DamageModifier buildAttackDamageModifier(final DefaultedRegistryReference modifierType, Object... causes) { - return DamageModifier.builder().attackDamageGroup() - .cause(Cause.of(EventContext.empty(), Arrays.asList(causes))).type(modifierType).build(); - } - - private static DamageModifier buildAttackEnchantmentModifier(final DefaultedRegistryReference modifierType, Object... causes) { - return DamageModifier.builder().attackEnchantmentGroup() - .cause(Cause.of(EventContext.empty(), Arrays.asList(causes))).type(modifierType).build(); - } - - private static DamageModifier buildDamageReductionModifier(final DefaultedRegistryReference modifierType, Object... causes) { - return DamageModifier.builder().damageReductionGroup() - .cause(Cause.of(EventContext.empty(), Arrays.asList(causes))).type(modifierType).build(); - } - - public static AttackEntityEvent callPlayerAttackEntityEvent(final Attack attack, final float knockbackModifier) { - final boolean isMainthread = !attack.sourceEntity().level().isClientSide; - if (isMainthread) { - PhaseTracker.getInstance().pushCause(attack.dmgSource()); - } - final var currentCause = isMainthread - ? PhaseTracker.getInstance().currentCause() - : Cause.of(EventContext.empty(), attack.dmgSource()); - final var event = attack.postEvent(knockbackModifier, currentCause); - if (isMainthread) { - PhaseTracker.getInstance().popCause(); - } - return event; - } - - /** - * {@link Mob#doHurtTarget} - */ - public static AttackEntityEvent callMobAttackEvent(final Attack attack, final float knockbackModifier) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { - frame.pushCause(attack.dmgSource()); - return attack.postEvent(knockbackModifier, frame.currentCause()); - } - } - - /** - * For {@link Entity#hurt} overrides without super call: - * {@link net.minecraft.world.entity.decoration.BlockAttachedEntity#hurt} - * {@link net.minecraft.world.entity.vehicle.VehicleEntity#hurt} - * {@link net.minecraft.world.entity.decoration.ItemFrame#hurt} - * {@link net.minecraft.world.entity.vehicle.MinecartTNT#hurt} - * {@link net.minecraft.world.entity.boss.enderdragon.EndCrystal#hurt} - * {@link net.minecraft.world.entity.projectile.ShulkerBullet#hurt} - * {@link net.minecraft.world.entity.ExperienceOrb#hurt} - * {@link net.minecraft.world.entity.item.ItemEntity#hurt} - */ - public static AttackEntityEvent callOtherAttackEvent( - final Entity targetEntity, - final DamageSource damageSource, - final double damage) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { - frame.pushCause(damageSource); - final AttackEntityEvent event = SpongeEventFactory.createAttackEntityEvent(frame.currentCause(), (org.spongepowered.api.entity.Entity) targetEntity, new ArrayList<>(), 0, damage); - SpongeCommon.post(event); - return event; - } - } - - public static DamageEventResult callLivingDamageEntityEvent(final Hurt hurt, final ActuallyHurt actuallyHurt) { - - try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { - DamageEventUtil.generateCauseFor(actuallyHurt.dmgSource(), frame); - - final List originalFunctions = new ArrayList<>(); - originalFunctions.addAll(hurt.functions()); - originalFunctions.addAll(actuallyHurt.functions()); - final var event = SpongeEventFactory.createDamageEntityEvent(frame.currentCause(), - (org.spongepowered.api.entity.Entity) actuallyHurt.entity(), - originalFunctions, - actuallyHurt.baseDamage()); - - if (actuallyHurt.dmgSource() != SpongeDamageSources.IGNORED) { // Basically, don't throw an event if it's our own damage source - SpongeCommon.post(event); - } - - return new DamageEventResult(event, - actuallyHurt.dmgSource(), - DamageEventUtil.findDamageBefore(event, DamageModifierTypes.SHIELD), - DamageEventUtil.findDamageDifference(event, DamageModifierTypes.SHIELD), - DamageEventUtil.findDamageBefore(event, DamageModifierTypes.HARD_HAT), - DamageEventUtil.findDamageBefore(event, DamageModifierTypes.ARMOR), - DamageEventUtil.findDamageDifference(event, DamageModifierTypes.DEFENSIVE_POTION_EFFECT), // TODO Math.max(0, resisted)? - DamageEventUtil.findDamageDifference(event, DamageModifierTypes.ABSORPTION) - ); - } - - } - - private static Optional findDamageDifference(DamageEntityEvent event, DefaultedRegistryReference type) { - return DamageEventUtil.findModifier(event, type).map(event::damage).map(tuple -> tuple.first() - tuple.second()).map(Double::floatValue); - } - - - private static Optional findDamageBefore(DamageEntityEvent event, DefaultedRegistryReference type) { - return DamageEventUtil.findModifier(event, type).map(event::damage).map(Tuple::first).map(Double::floatValue); - } - - private static Optional findModifier(DamageEntityEvent event, DefaultedRegistryReference type) { - return event.originalFunctions().stream() - .map(DamageFunction::modifier) - .filter(mod -> type.get().equals(mod.type())) - .findFirst(); - } - - - public static DamageEntityEvent callSimpleDamageEntityEvent(final DamageSource source, final Entity targetEntity, final double amount) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { - DamageEventUtil.generateCauseFor(source, frame); - final var event = SpongeEventFactory.createDamageEntityEvent(frame.currentCause(), (org.spongepowered.api.entity.Entity) targetEntity, new ArrayList<>(), amount); - SpongeCommon.post(event); - return event; - } - } - - - - - public record DamageEventResult(DamageEntityEvent event, - DamageSource source, - Optional damageToShield, - Optional damageBlockedByShield, - Optional damageToHelmet, - Optional damageToArmor, - Optional damageResisted, - Optional damageAbsorbed - ) { - - } - - - public record Hurt(DamageSource dmgSource, List functions) { - - } - - public record ActuallyHurt(LivingEntity entity, - List functions, - DamageSource dmgSource, - float baseDamage) { - - } - - public record Attack(T sourceEntity, - Entity target, - ItemStack weapon, - DamageSource dmgSource, - float strengthScale, - float baseDamage, - List functions) { - - private AttackEntityEvent postEvent(final float knockbackModifier, final Cause cause) { - final var event = SpongeEventFactory.createAttackEntityEvent( - cause, - (org.spongepowered.api.entity.Entity) this.target, - this.functions, - knockbackModifier, - this.baseDamage); - SpongeCommon.post(event); - return event; - } - - } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin_Attack_impl.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin_Attack_impl.java deleted file mode 100644 index cb063381820..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin_Attack_impl.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.mixin.core.server.level; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.common.mixin.core.world.entity.player.PlayerMixin_Attack_Impl; -import org.spongepowered.common.util.DamageEventUtil; - -import java.util.ArrayList; - -@Mixin(value = ServerPlayer.class, priority = 900) -public abstract class ServerPlayerMixin_Attack_impl extends PlayerMixin_Attack_Impl { - - @Override - protected void attackImpl$enchanttDamageFunc(Entity $$0, CallbackInfo ci) { - final var weapon = this.attackImpl$attack.weapon(); - // this.getEnchantedDamage(targetEntity, damage, damageSource) - damage; - final var functions = DamageEventUtil.createAttackEnchantmentFunction(weapon, this.attackImpl$attack.target(), this.attackImpl$attack.dmgSource()); - final var separateFunc = DamageEventUtil.provideSeparateEnchantmentFromBaseDamageFunction(this.attackImpl$attack.baseDamage(), weapon); - // enchantmentDamage *= attackStrength; - final var strengthScaleFunc = DamageEventUtil.provideCooldownEnchantmentStrengthFunction(weapon, this.attackImpl$attack.strengthScale()); - - this.attackImpl$attack.functions().addAll(functions); - this.attackImpl$attack.functions().add(separateFunc); - this.attackImpl$attack.functions().add(strengthScaleFunc); - } - - @Override - protected double attackImpl$beforeSweepHurt( - final Player instance, final Entity sweepTarget, final Operation original - ) { - final var distanceToSqr = original.call(instance, sweepTarget); - if (!(distanceToSqr < 9.0)) { - return distanceToSqr; // Too far - no event - } - - final var mainAttack = this.attackImpl$attack; - final var mainAttackDamage = this.attackImpl$finalDamageAmounts.getOrDefault("minecraft:attack_damage", 0.0).floatValue(); - - var sweepAttack = new DamageEventUtil.Attack<>(mainAttack.sourceEntity(), sweepTarget, mainAttack.weapon(), mainAttack.dmgSource(), mainAttack.strengthScale(), 1, new ArrayList<>()); - // float sweepBaseDamage = 1.0F + (float)this.getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * attackDamage; - sweepAttack.functions().add(DamageEventUtil.provideSweepingDamageRatioFunction(mainAttack.weapon(), mainAttack.sourceEntity(), mainAttackDamage)); - // float sweepFullDamage = this.getEnchantedDamage(sweepTarget, sweepBaseDamage, $$3) * strengthScale; - sweepAttack.functions().addAll(DamageEventUtil.createAttackEnchantmentFunction(mainAttack.weapon(), sweepTarget, mainAttack.dmgSource())); - sweepAttack.functions().add(DamageEventUtil.provideCooldownEnchantmentStrengthFunction(mainAttack.weapon(), mainAttack.strengthScale())); - - this.attackImpl$attackEvent = DamageEventUtil.callPlayerAttackEntityEvent(sweepAttack, 1.0F); - if (attackImpl$attackEvent.isCancelled()) { - return Double.MAX_VALUE; - } - - return distanceToSqr; - } -} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/ExperienceOrbMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/ExperienceOrbMixin.java index 451d8b72663..f3172f61959 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/ExperienceOrbMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/ExperienceOrbMixin.java @@ -26,14 +26,14 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ExperienceOrb; +import org.spongepowered.api.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.common.util.DamageEventUtil; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; @Mixin(ExperienceOrb.class) public abstract class ExperienceOrbMixin extends EntityMixin { @@ -48,8 +48,8 @@ public abstract class ExperienceOrbMixin extends EntityMixin { @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ExperienceOrb;markHurt()V")) - private void attackImpl$onAttackEntityFrom(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + private void attack$onHurt(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java deleted file mode 100644 index e9f46905c2d..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.mixin.core.world.entity; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.stats.Stats; -import net.minecraft.tags.DamageTypeTags; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import org.apache.logging.log4j.Level; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.*; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.common.SpongeCommon; -import org.spongepowered.common.bridge.world.entity.LivingEntityBridge; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.event.tracking.context.transaction.inventory.PlayerInventoryTransaction; -import org.spongepowered.common.util.DamageEventUtil; -import org.spongepowered.common.util.PrettyPrinter; - -import java.util.ArrayList; - -@Mixin(value = LivingEntity.class, priority = 900) -public abstract class LivingEntityMixin_Attack_Impl extends EntityMixin implements LivingEntityBridge { - - //@formatter:off - @Shadow protected abstract void shadow$playHurtSound(DamageSource param0); - @Shadow protected abstract void shadow$hurtHelmet(final DamageSource $$0, final float $$1); - @Shadow protected abstract void shadow$hurtCurrentlyUsedShield(final float $$0); - @Shadow protected abstract void shadow$blockUsingShield(final LivingEntity $$0); - @Shadow protected abstract void shadow$hurtArmor(DamageSource source, float damage); - @Shadow protected abstract float shadow$getKnockback(final Entity $$0, final DamageSource $$1); - @Shadow public abstract float shadow$getAbsorptionAmount(); - @Shadow public abstract @NonNull ItemStack shadow$getWeaponItem(); - @Shadow public abstract void setAbsorptionAmount(final float $$0); - @Shadow protected int attackStrengthTicker; - @Shadow protected float lastHurt; - private float attackImpl$baseDamage; - // @formatter:on - - private float attackImpl$lastHurt; - private int attackImpl$InvulnerableTime; - - protected DamageEventUtil.Hurt attackImpl$hurt; - protected DamageEventUtil.ActuallyHurt attackImpl$actuallyHurt; - protected DamageEventUtil.DamageEventResult attackImpl$actuallyHurtResult; - protected float attackImpl$actuallyHurtFinalDamage; - protected boolean attackImpl$actuallyHurtCancelled; - protected float attackImpl$actuallyHurtBlockedDamage; - - /** - * Forge onLivingAttack Hook - */ - @Inject(method = "hurtServer", at = @At("HEAD"), cancellable = true) - private void attackImpl$beforeHurt(final ServerLevel level, final DamageSource source, final float damageTaken, final CallbackInfoReturnable cir) { - if (source == null) { - new PrettyPrinter(60).centre().add("Null DamageSource").hr() - .addWrapped("Sponge has found a null damage source! This should NEVER happen " - + "as the DamageSource is used for all sorts of calculations. Usually" - + " this can be considered developer error. Please report the following" - + " stacktrace to the most appropriate mod/plugin available.") - .add() - .add(new IllegalArgumentException("Null DamageSource")) - .log(SpongeCommon.logger(), Level.WARN); - cir.setReturnValue(false); - } - this.attackImpl$baseDamage = damageTaken; - } - - /** - * Prepare {@link org.spongepowered.common.util.DamageEventUtil.Hurt} for damage event - */ - @Inject(method = "hurtServer", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;noActionTime:I")) - private void attackImpl$preventEarlyBlock1(final ServerLevel level, final DamageSource $$0, final float $$1, final CallbackInfoReturnable cir) { - this.attackImpl$hurt = new DamageEventUtil.Hurt($$0, new ArrayList<>()); - } - - /** - * Prevents shield usage before event - * Captures the blocked damage as a function - */ - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtCurrentlyUsedShield(F)V")) - private void attackImpl$preventEarlyBlock1(final LivingEntity instance, final float damageToShield) { - // this.hurtCurrentlyUsedShield(damageToShield); - this.attackImpl$hurt.functions().add(DamageEventUtil.createShieldFunction(instance)); - } - - /** - * Prevents shield usage before event - */ - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;blockUsingShield(Lnet/minecraft/world/entity/LivingEntity;)V")) - private void attackImpl$preventEarlyBlock2(final LivingEntity instance, final LivingEntity livingDamageSource) { - // this.blockUsingShield(livingDamageSource); - } - - /** - * Capture the bonus freezing damage as a function - */ - @Inject(method = "hurtServer", at = @At(value = "CONSTANT", args = "floatValue=5.0F")) - private void attackImpl$freezingBonus(final ServerLevel level, final DamageSource $$0, final float $$1, final CallbackInfoReturnable cir) { - this.attackImpl$hurt.functions().add(DamageEventUtil.createFreezingBonus((LivingEntity) (Object) this, $$0, 5.0F)); - } - - /** - * Prevents {@link #shadow$hurtHelmet} before the event - * Captures the hard hat damage reduction as a function - */ - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtHelmet(Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void attackImpl$hardHat(final LivingEntity instance, final DamageSource $$0, final float $$1) { - // this.hurtHelmet($$0, $$1); - this.attackImpl$hurt.functions().add(DamageEventUtil.createHardHatModifier(instance.getItemBySlot(EquipmentSlot.HEAD), 0.75F)); - } - - /** - * Capture the old values to reset if we end up cancelling or blocking. - */ - @Inject(method = "hurtServer", at = @At(value = "FIELD", - target = "Lnet/minecraft/world/entity/LivingEntity;walkAnimation:Lnet/minecraft/world/entity/WalkAnimationState;")) - private void attackImpl$beforeActuallyHurt(final ServerLevel level, final DamageSource source, final float damageTaken, final CallbackInfoReturnable cir) { - // Save old values - this.attackImpl$lastHurt = this.lastHurt; - this.attackImpl$InvulnerableTime = this.invulnerableTime; - this.attackImpl$actuallyHurtCancelled = false; - } - - @ModifyArg(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;actuallyHurt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V", ordinal = 0)) - private float attackImp$useBaseDamage1(final float $$0) { - return this.attackImpl$baseDamage - this.attackImpl$lastHurt; - } - - @ModifyArg(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;actuallyHurt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V", ordinal = 1)) - private float attackImp$useBaseDamage2(final float $$0) { - return this.attackImpl$baseDamage; - } - - /** - * After calling #actuallyHurt (even when invulnerable), if cancelled return early or is still invulnerable - * and reset {@link #lastHurt} and {@link #invulnerableTime} - */ - @Inject(method = "hurtServer", cancellable = true, - at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 0, - target = "Lnet/minecraft/world/entity/LivingEntity;actuallyHurt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void attackImpl$afterActuallyHurt1(final ServerLevel level, final DamageSource source, final float damageTaken, final CallbackInfoReturnable cir) { - if (this.attackImpl$actuallyHurtCancelled || damageTaken <= this.lastHurt) { - this.invulnerableTime = this.attackImpl$InvulnerableTime; - this.lastHurt = this.attackImpl$lastHurt; - cir.setReturnValue(false); - } - } - - /** - * After calling #actuallyHurt, if cancelled return early - * Also reset values - */ - @Inject(method = "hurtServer", cancellable = true, - at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 1, - target = "Lnet/minecraft/world/entity/LivingEntity;actuallyHurt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void attackImpl$afterActuallyHurt2(ServerLevel $$0, DamageSource $$1, float $$2, CallbackInfoReturnable cir) { - if (this.attackImpl$actuallyHurtCancelled) { - this.invulnerableTime = this.attackImpl$InvulnerableTime; - cir.setReturnValue(false); - } - } - - - /** - * Set final damage after #actuallyHurt and lastHurt has been set. - */ - @ModifyVariable(method = "hurtServer", argsOnly = true, - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/DamageSource;getEntity()Lnet/minecraft/world/entity/Entity;"), - slice = @Slice( - from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;actuallyHurt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/DamageSource;getEntity()Lnet/minecraft/world/entity/Entity;"))) - private float attackImpl$modifyDamageTaken(float damageTaken) { - return this.attackImpl$actuallyHurtFinalDamage; - } - - // TODO - Pending on a Mixin bug: https://github.com/SpongePowered/Mixin/issues/684 -// /** -// * Sets blocked damage after #actuallyHurt -// */ -// @ModifyVariable(method = "hurtServer", ordinal = 1, -// at = @At(value = "INVOKE", -// target = "Lnet/minecraft/advancements/critereon/EntityHurtPlayerTrigger;trigger(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/damagesource/DamageSource;FFZ)V", -// shift = At.Shift.AFTER -// ), -// slice = @Slice( -// from = @At(value = "FIELD", target = "Lnet/minecraft/advancements/CriteriaTriggers;ENTITY_HURT_PLAYER:Lnet/minecraft/advancements/critereon/EntityHurtPlayerTrigger;"), -// to = @At(value = "FIELD", target = "Lnet/minecraft/stats/Stats;DAMAGE_BLOCKED_BY_SHIELD:Lnet/minecraft/resources/ResourceLocation;") -// )) -// private float attackImpl$modifyBlockedDamage(float damageBlocked) { -// return this.attackImpl$actuallyHurtBlockedDamage; -// } - - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;playHurtSound(Lnet/minecraft/world/damagesource/DamageSource;)V")) - private void attackImpl$onHurtSound(final LivingEntity instance, final DamageSource $$0) { - if (this.bridge$vanishState().createsSounds()) { - this.shadow$playHurtSound($$0); - } - } - - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;makeSound(Lnet/minecraft/sounds/SoundEvent;)V")) - private void attackImpl$onMakeSound(final LivingEntity instance, final SoundEvent $$0) { - if (this.bridge$vanishState().createsSounds()) { - instance.makeSound($$0); - } - } - - @Inject(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F")) - protected void attackImpl$startActuallyHurt( - final ServerLevel level, final DamageSource damageSource, - final float originalDamage, final CallbackInfo ci) { - // TODO check for direct call? - this.attackImpl$actuallyHurt = new DamageEventUtil.ActuallyHurt((LivingEntity) (Object) this, new ArrayList<>(), damageSource, originalDamage); - } - - /** - * Prevents LivingEntity#hurtArmor from running before event - * and capture the armor absorption as a function - */ - @Redirect(method = "getDamageAfterArmorAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtArmor(Lnet/minecraft/world/damagesource/DamageSource;F)V")) - protected void attackImpl$onDamageAfterArmorAbsorb(final LivingEntity instance, final DamageSource $$0, final float $$1) { - if (this.attackImpl$actuallyHurt != null) { - // prevents this.hurtArmor($$0, $$1); - // $$1 = CombatRules.getDamageAfterAbsorb(this, $$1, $$0, (float)this.getArmorValue(), (float)this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); - var func = DamageEventUtil.createArmorModifiers(instance, this.attackImpl$actuallyHurt.dmgSource()); - this.attackImpl$actuallyHurt.functions().add(func); - } - } - - /** - * Captures the damage resistance as a function - */ - @Inject(method = "getDamageAfterMagicAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getEffect(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/effect/MobEffectInstance;")) - protected void attackImpl$onDamageAfterMagicAbsorb(final DamageSource $$0, final float $$1, final CallbackInfoReturnable cir) { - if (this.attackImpl$actuallyHurt != null) { - var func = DamageEventUtil.createResistanceModifier(this.attackImpl$actuallyHurt.entity()); - this.attackImpl$actuallyHurt.functions().add(func); - } - } - - - /** - * Captures the damage protection as a function - */ - @WrapOperation(method = "getDamageAfterMagicAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/CombatRules;getDamageAfterMagicAbsorb(FF)F")) - protected float attackImpl$onDetDamageProtection(float damage, float protection, Operation original) { - if (this.attackImpl$actuallyHurt != null) { - var func = DamageEventUtil.createEnchantmentModifiers(this.attackImpl$actuallyHurt.entity(), protection); - this.attackImpl$actuallyHurt.functions().add(func); - } - return original.call(damage, protection); - } - - /** - * Prevents setting absorption before event - * Captures the absorption amount as a functions - * Then calls the DamageEntityEvent - */ - @Inject(method = "setAbsorptionAmount", cancellable = true, at = @At("HEAD")) - protected void attackImpl$onSetAbsorptionAmount(final float newAmount, final CallbackInfo ci) { - if (this.attackImpl$actuallyHurt != null) { - ci.cancel(); // Always cancel this - var oldAmount = this.shadow$getAbsorptionAmount(); - if (oldAmount > 0) { - var func = DamageEventUtil.createAbsorptionModifier(this.attackImpl$actuallyHurt.entity(), oldAmount); - this.attackImpl$actuallyHurt.functions().add(func); - } - - // Use local and clear the variable to prevent re-entry if a plugin modifies the absorption hearts inside the event. - final DamageEventUtil.ActuallyHurt actuallyHurt = this.attackImpl$actuallyHurt; - this.attackImpl$actuallyHurt = null; - - this.attackImpl$actuallyHurtResult = DamageEventUtil.callLivingDamageEntityEvent(this.attackImpl$hurt, actuallyHurt); - - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - this.attackImpl$actuallyHurtCancelled = true; - this.attackImpl$actuallyHurtFinalDamage = 0; - this.attackImpl$actuallyHurtBlockedDamage = 0; - return; // Cancel vanilla behaviour by setting absorbed & finalDamage to 0 - } - - this.attackImpl$actuallyHurtFinalDamage = (float) this.attackImpl$actuallyHurtResult.event().finalDamage(); - this.attackImpl$actuallyHurtResult.damageAbsorbed().ifPresent(absorbed -> this.setAbsorptionAmount(oldAmount - absorbed)); - this.attackImpl$actuallyHurtBlockedDamage = this.attackImpl$actuallyHurtResult.damageBlockedByShield().orElse(0f); - } - } - - /** - * Set final damage after calling {@link LivingEntity#setAbsorptionAmount} in which we called the event - * !!NOTE that var9 is actually decompiled incorrectly!! - * It is NOT the final damage value instead the method parameter is mutated - */ - @ModifyVariable(method = "actuallyHurt", ordinal = 0, - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setAbsorptionAmount(F)V", - ordinal = 0, shift = At.Shift.AFTER), argsOnly = true) - protected float attackImpl$setFinalDamage(final float value) { - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - return 0; - } - return this.attackImpl$actuallyHurtFinalDamage; - } - - /** - * Replay prevented - * {@link #shadow$hurtCurrentlyUsedShield} and {@link #shadow$blockUsingShield} - * {@link #shadow$hurtHelmet} - * {@link #shadow$hurtArmor} - * {@link ServerPlayer#awardStat} for {@link Stats#DAMAGE_RESISTED} and {@link Stats#DAMAGE_DEALT} - * from {@link LivingEntity#hurt} and #actuallyHurt - *

- * And capture inventory changes if needed - */ - protected void attackImpl$handlePostDamage() { - final var result = this.attackImpl$actuallyHurtResult; - if (result != null && !this.attackImpl$actuallyHurtCancelled) { - final var damageSource = result.source(); - result.damageToShield().ifPresent(dmg -> { - this.shadow$hurtCurrentlyUsedShield(dmg); - if (!damageSource.is(DamageTypeTags.IS_PROJECTILE)) { - if (damageSource.getDirectEntity() instanceof LivingEntity livingSource) { - this.shadow$blockUsingShield(livingSource); - } - } - }); - result.damageToHelmet().ifPresent(dmg -> - this.shadow$hurtHelmet(damageSource, dmg)); - result.damageToArmor().ifPresent(dmg -> - this.shadow$hurtArmor(damageSource, dmg)); - result.damageResisted().ifPresent(dmg -> { - if ((Object) this instanceof ServerPlayer player) { - player.awardStat(Stats.DAMAGE_RESISTED, Math.round(dmg * 10.0F)); - } else if (damageSource.getEntity() instanceof ServerPlayer player) { - player.awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(dmg * 10.0F)); - } - }); - - // Capture inventory change if we modified stacks - if ((result.damageToShield().isPresent() || - result.damageToHelmet().isPresent() || - result.damageToArmor().isPresent()) - && (Object) this instanceof Player player) { - PhaseTracker.getWorldInstance((ServerLevel) this.shadow$level()).getPhaseContext().getTransactor().logPlayerInventoryChange(player, PlayerInventoryTransaction.EventCreator.STANDARD); - player.inventoryMenu.broadcastChanges(); // capture - } - } - } - - /** - * Cleanup - * also reverts {@link #attackImpl$beforeActuallyHurt} - */ - @Inject(method = "actuallyHurt", at = @At("RETURN")) - protected void attackImpl$cleanupActuallyHurt(final ServerLevel level, final DamageSource $$0, final float $$1, final CallbackInfo ci) { - this.attackImpl$handlePostDamage(); - this.attackImpl$actuallyHurt = null; - this.attackImpl$actuallyHurtResult = null; - this.lastHurt = this.attackImpl$lastHurt; - } - -} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java new file mode 100644 index 00000000000..99f8bd35fd4 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java @@ -0,0 +1,235 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.entity; + +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.damagesource.CombatRules; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.common.bridge.world.entity.LivingEntityBridge; +import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.event.tracking.context.transaction.inventory.PlayerInventoryTransaction; + +import java.util.Deque; +import java.util.LinkedList; + +@Mixin(value = LivingEntity.class, priority = 900) +public abstract class LivingEntityMixin_Damage extends EntityMixin implements LivingEntityBridge, TrackedDamageBridge { + + //@formatter:off + @Shadow protected abstract void shadow$playHurtSound(final DamageSource source); + @Shadow protected abstract float shadow$getKnockback(final Entity entity, final DamageSource source); + @Shadow public abstract @NonNull ItemStack shadow$getWeaponItem(); + @Shadow public abstract ItemStack shadow$getItemBySlot(final EquipmentSlot slot); + @Shadow protected abstract void shadow$hurtHelmet(final DamageSource source, final float damage); + @Shadow protected abstract void shadow$hurtArmor(final DamageSource source, final float damage); + @Shadow public abstract @Nullable MobEffectInstance shadow$getEffect(final Holder effect); + @Shadow public abstract double shadow$getAttributeValue(final Holder attribute); + // @formatter:on + + private final Deque damage$trackers = new LinkedList<>(); + private boolean damage$inventoryChanged = false; + + @Override + public final @Nullable SpongeDamageTracker damage$tracker() { + return this.damage$trackers.peekLast(); + } + + @Inject(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isSleeping()Z"), cancellable = true) + private void damage$firePreEvent(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + final SpongeDamageTracker tracker = SpongeDamageTracker.callDamagePreEvent((org.spongepowered.api.entity.Entity) this, source, this.damage$getContainerDamage(damage)); + if (tracker == null) { + cir.setReturnValue(false); + } else { + this.damage$trackers.addLast(tracker); + this.damage$setContainerDamage((float) tracker.preEvent().baseDamage()); + } + } + + @Inject(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtCurrentlyUsedShield(F)V")) + private void damage$onHurtShield(final CallbackInfoReturnable cir) { + this.damage$inventoryChanged = true; + } + + @ModifyVariable(method = "hurtServer", argsOnly = true, at = @At("STORE"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtCurrentlyUsedShield(F)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;blockUsingShield(Lnet/minecraft/world/entity/LivingEntity;)V") + )) + private float damage$setDamageAfterShield(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return damage; + } + final SpongeDamageStep step = tracker.currentStep(DamageStepTypes.SHIELD); + return step == null ? damage : (float) step.damageAfterModifiers(); + } + + @ModifyVariable(method = "hurtServer", at = @At("LOAD"), argsOnly = true, slice = @Slice( + from = @At(value = "FIELD", target = "Lnet/minecraft/tags/EntityTypeTags;FREEZE_HURTS_EXTRA_TYPES:Lnet/minecraft/tags/TagKey;"), + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;DAMAGES_HELMET:Lnet/minecraft/tags/TagKey;") + )) + private float damage$modifyBeforeFreezingBonus(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.FREEZING_BONUS, damage, tracker.preEvent().source(), this); + } + + @ModifyVariable(method = "hurtServer", at = @At("STORE"), argsOnly = true, slice = @Slice( + from = @At(value = "FIELD", target = "Lnet/minecraft/tags/EntityTypeTags;FREEZE_HURTS_EXTRA_TYPES:Lnet/minecraft/tags/TagKey;"), + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;DAMAGES_HELMET:Lnet/minecraft/tags/TagKey;") + )) + private float damage$modifyAfterFreezingBonus(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.FREEZING_BONUS, damage); + } + + @ModifyVariable(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtHelmet(Lnet/minecraft/world/damagesource/DamageSource;F)V"), argsOnly = true) + private float damage$modifyBeforeHardHat(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.HARD_HAT, damage, this.shadow$getItemBySlot(EquipmentSlot.HEAD)); + } + + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtHelmet(Lnet/minecraft/world/damagesource/DamageSource;F)V")) + private void damage$skipHardHat(final LivingEntity self, final DamageSource source, final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.HARD_HAT)) { + this.shadow$hurtHelmet(source, damage); + this.damage$inventoryChanged = true; + } + } + + @ModifyVariable(method = "hurtServer", at = @At("STORE"), argsOnly = true, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtHelmet(Lnet/minecraft/world/damagesource/DamageSource;F)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/WalkAnimationState;setSpeed(F)V") + )) + private float damage$modifyAfterHardHat(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.HARD_HAT, damage); + } + + @ModifyVariable(method = "getDamageAfterArmorAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtArmor(Lnet/minecraft/world/damagesource/DamageSource;F)V"), argsOnly = true) + private float damage$modifyBeforeArmor(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.ARMOR, damage, this, Attributes.ARMOR_TOUGHNESS); + } + + @Redirect(method = "getDamageAfterArmorAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtArmor(Lnet/minecraft/world/damagesource/DamageSource;F)V")) + private void damage$skipArmor(final LivingEntity self, final DamageSource source, final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ARMOR)) { + this.shadow$hurtArmor(source, damage); + this.damage$inventoryChanged = true; + } + } + + @ModifyVariable(method = "getDamageAfterArmorAbsorb", at = @At("STORE"), argsOnly = true) + private float damage$modifyAfterArmor(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.ARMOR, damage); + } + + @ModifyVariable(method = "getDamageAfterMagicAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getEffect(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/effect/MobEffectInstance;"), argsOnly = true) + private float damage$modifyBeforeDefensivePotionEffect(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.DEFENSIVE_POTION_EFFECT, damage, this.shadow$getEffect(MobEffects.DAMAGE_RESISTANCE)); + } + + @ModifyVariable(method = "getDamageAfterMagicAbsorb", at = @At("STORE"), argsOnly = true, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getEffect(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/effect/MobEffectInstance;"), + to = @At(value = "FIELD", target = "Lnet/minecraft/stats/Stats;DAMAGE_RESISTED:Lnet/minecraft/resources/ResourceLocation;"))) + private float damage$modifyAfterDefensivePotionEffect(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.DEFENSIVE_POTION_EFFECT, damage); + } + + @Redirect(method = "getDamageAfterMagicAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/CombatRules;getDamageAfterMagicAbsorb(FF)F")) + private float damage$modifyBeforeAndAfterArmorEnchantment(float damage, final float protection) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return CombatRules.getDamageAfterMagicAbsorb(damage, protection); + } + + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.ARMOR_ENCHANTMENT, damage, this); + damage = (float) step.applyModifiersBefore(); + if (!step.isSkipped()) { + damage = CombatRules.getDamageAfterMagicAbsorb(damage, protection); + } + return (float) step.applyModifiersAfter(damage); + } + + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;playHurtSound(Lnet/minecraft/world/damagesource/DamageSource;)V")) + private void damage$onHurtSound(final LivingEntity self, final DamageSource source) { + if (this.bridge$vanishState().createsSounds()) { + this.shadow$playHurtSound(source); + } + } + + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;makeSound(Lnet/minecraft/sounds/SoundEvent;)V")) + private void damage$onMakeSound(final LivingEntity self, final SoundEvent sound) { + if (this.bridge$vanishState().createsSounds()) { + self.makeSound(sound); + } + } + + @Inject(method = "hurtServer", at = @At("RETURN"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isSleeping()Z", shift = At.Shift.AFTER) + )) + private void damage$removeTrackerAndCaptureInventory(final CallbackInfoReturnable cir) { + this.damage$trackers.removeLast(); + + if (this.damage$inventoryChanged) { + this.damage$inventoryChanged = false; + + if ((Object) this instanceof Player player) { + PhaseTracker.getWorldInstance((ServerLevel) player.level()).getPhaseContext().getTransactor().logPlayerInventoryChange(player, PlayerInventoryTransaction.EventCreator.STANDARD); + player.inventoryMenu.broadcastChanges(); + } + } + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Attack_Impl.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Attack_Impl.java deleted file mode 100644 index 323ee6c6143..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Attack_Impl.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.mixin.core.world.entity; - -import net.minecraft.world.entity.LivingEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Slice; -import org.spongepowered.common.util.DamageEventUtil; - -// Forge and Vanilla -@Mixin(value = LivingEntity.class, priority = 900) -public class LivingEntityMixin_Shared_Attack_Impl { - - protected DamageEventUtil.DamageEventResult attackImpl$actuallyHurtResult; - - /** - * Set absorbed damage after calling {@link LivingEntity#setAbsorptionAmount} in which we called the event - */ - @ModifyVariable(method = "actuallyHurt", ordinal = 2, - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setAbsorptionAmount(F)V", ordinal = 0)), - at = @At(value = "STORE", ordinal = 0)) - public float attackImpl$setAbsorbed(final float value) { - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - return 0; - } - return this.attackImpl$actuallyHurtResult.damageAbsorbed().orElse(0f); - } -} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java new file mode 100644 index 00000000000..38ea8814a95 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java @@ -0,0 +1,72 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.entity; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; + +// Forge and Vanilla +@Mixin(value = LivingEntity.class, priority = 900) +public abstract class LivingEntityMixin_Shared_Damage implements TrackedDamageBridge { + + @ModifyVariable(method = "hurtServer", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;noActionTime:I"), argsOnly = true) + private float damage$setBaseDamage(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : (float) tracker.preEvent().baseDamage(); + } + + @ModifyVariable(method = "actuallyHurt", at = @At("STORE"), argsOnly = true, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getDamageAfterMagicAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getAbsorptionAmount()F", ordinal = 0))) + private float damage$modifyBeforeAbsorption(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); + } + + @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setAbsorptionAmount(F)V", ordinal = 0)) + private void damage$skipAbsorption(final LivingEntity self, final float absorption) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { + self.setAbsorptionAmount(absorption); + } + } + + @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V")) + private void damage$skipAbsorptionStat(final ServerPlayer self, final ResourceLocation stat, final int amount) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { + self.awardStat(stat, amount); + } + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java new file mode 100644 index 00000000000..9d9addfd679 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java @@ -0,0 +1,95 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.entity; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.item.ItemStack; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import org.spongepowered.common.bridge.world.entity.TrackedAttackBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeAttackTracker; + +import java.util.Deque; +import java.util.LinkedList; + +@Mixin(Mob.class) +public abstract class MobMixin_Attack extends LivingEntityMixin_Damage implements TrackedAttackBridge { + private final Deque attack$trackers = new LinkedList<>(); + + @Override + public final @Nullable SpongeAttackTracker attack$tracker() { + return this.attack$trackers.peekLast(); + } + + @Inject( + method = "doHurtTarget", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;modifyDamage(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;F)F"), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void attack$firePreEvent(final ServerLevel level, final Entity target, final CallbackInfoReturnable cir, + final float damage, final ItemStack weapon, final DamageSource source) { + final SpongeAttackTracker tracker = SpongeAttackTracker.callAttackPreEvent((org.spongepowered.api.entity.Entity) target, source, damage, weapon); + if (tracker == null) { + cir.setReturnValue(false); + } else { + this.attack$trackers.addLast(tracker); + } + } + + @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z")) + private boolean attack$firePostEvent(final Entity target, final ServerLevel level, final DamageSource source, float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker != null) { + final float knockbackModifier = this.shadow$getKnockback(target, source); + if (tracker.callAttackPostEvent((org.spongepowered.api.entity.Entity) target, source, damage, knockbackModifier)) { + return false; + } + damage = (float) tracker.postEvent().finalDamage(); + } + return target.hurtServer(level, source, damage); + } + + @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$knockbackModifier(final Mob self, final Entity target, final DamageSource source) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? this.shadow$getKnockback(target, source) : (float) tracker.postEvent().knockbackModifier(); + } + + @Inject(method = "doHurtTarget", at = @At("RETURN"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;modifyDamage(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;F)F", shift = At.Shift.AFTER))) + private void attack$removeTracker(CallbackInfoReturnable cir) { + this.attack$trackers.removeLast(); + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack_Impl.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack_Impl.java deleted file mode 100644 index 9de678c164e..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack_Impl.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.mixin.core.world.entity; - -import net.minecraft.core.Holder; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Mob; -import net.minecraft.world.entity.ai.attributes.Attribute; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.common.util.DamageEventUtil; - -import java.util.ArrayList; - -@Mixin(Mob.class) -public abstract class MobMixin_Attack_Impl extends LivingEntityMixin_Attack_Impl { - - private double impl$hurtTargetDamage; - private double impl$knockbackModifier; - - @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/entity/Mob;getAttributeValue(Lnet/minecraft/core/Holder;)D")) - private double attackImpl$onCanGrief(final Mob instance, final Holder attackDamageAttribute) { - this.impl$hurtTargetDamage = instance.getAttributeValue(attackDamageAttribute); - return this.impl$hurtTargetDamage; - } - - @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/entity/Entity;hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z")) - private boolean attackImpl$onCanGrief(final net.minecraft.world.entity.Entity targetEntity, final ServerLevel level, final DamageSource damageSource, final float mcFinalDamage) { - final var thisEntity = (Mob) (Object) this; - - float knockbackModifier = this.shadow$getKnockback(targetEntity, damageSource); - - var attack = new DamageEventUtil.Attack<>(thisEntity, targetEntity, this.shadow$getWeaponItem(), damageSource, 1, (float) this.impl$hurtTargetDamage, new ArrayList<>()); - if (this.shadow$level() instanceof ServerLevel) { - // baseDamage = EnchantmentHelper.modifyDamage(level, thisEntity.getWeaponItem(), targetEntity, damageSource, baseDamage);// - attack.functions().addAll(DamageEventUtil.createAttackEnchantmentFunction(attack.weapon(), targetEntity, damageSource)); - } - - final var event = DamageEventUtil.callMobAttackEvent(attack, knockbackModifier); - this.impl$knockbackModifier = event.knockbackModifier(); - - return targetEntity.hurtServer(level, damageSource, (float) event.finalOutputDamage()); - } - - @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/entity/Mob;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) - private float attackImpl$onCanGrief(final Mob instance, final net.minecraft.world.entity.Entity entity, final DamageSource damageSource) { - return (float) this.impl$knockbackModifier; - } - -} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/boss/enderdragon/EndCrystalMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/boss/enderdragon/EndCrystalMixin.java index 65061c79398..e544f39038f 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/boss/enderdragon/EndCrystalMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/boss/enderdragon/EndCrystalMixin.java @@ -39,10 +39,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.common.bridge.explosives.ExplosiveBridge; import org.spongepowered.common.bridge.world.entity.boss.enderdragon.EndCrystalBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.mixin.core.world.entity.EntityMixin; import org.spongepowered.common.util.Constants; -import org.spongepowered.common.util.DamageEventUtil; import java.util.Optional; @@ -92,8 +92,8 @@ public abstract class EndCrystalMixin extends EntityMixin implements ExplosiveBr @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/boss/enderdragon/EndCrystal;remove(Lnet/minecraft/world/entity/Entity$RemovalReason;)V")) - private void attackImpl$onAttackEntityFrom(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + private void attack$onHurt(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((org.spongepowered.api.entity.Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java index 7ca9cfd76d5..243b669fd9c 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java @@ -32,6 +32,8 @@ import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.level.gameevent.GameEvent; import org.objectweb.asm.Opcodes; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.event.entity.DamageEntityEvent; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; @@ -43,29 +45,27 @@ import org.spongepowered.common.bridge.world.level.LevelBridge; import org.spongepowered.common.event.ShouldFire; import org.spongepowered.common.event.SpongeCommonEventFactory; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.event.tracking.context.transaction.EffectTransactor; import org.spongepowered.common.mixin.core.world.entity.LivingEntityMixin; -import org.spongepowered.common.util.DamageEventUtil; @Mixin(ArmorStand.class) public abstract class ArmorStandMixin extends LivingEntityMixin { // @formatter:off - @Shadow protected abstract void shadow$causeDamage(ServerLevel level, DamageSource damageSource, float damage); // damageArmorStand - @Shadow protected abstract void shadow$brokenByPlayer(final ServerLevel $$0, final DamageSource $$1); - + @Shadow protected abstract void shadow$causeDamage(ServerLevel level, DamageSource source, float damage); // damageArmorStand + @Shadow protected abstract void shadow$brokenByPlayer(final ServerLevel level, final DamageSource source); // @formatter:on /** * The return value is set to false if the entity should not be completely destroyed. */ private void impl$callDamageBeforeKill(final DamageSource source, final CallbackInfoReturnable cir) { - var event = DamageEventUtil.callSimpleDamageEntityEvent(source, (ArmorStand) (Object) this, Math.max(1000, this.shadow$getHealth())); - if (event.isCancelled()) { + final DamageEntityEvent.Post event = SpongeDamageTracker.callDamageEvents((Entity) this, source, this.shadow$getHealth()); + if (event == null) { cir.setReturnValue(false); - } - if (event.finalDamage() < this.shadow$getHealth()) { // Deal reduced damage? + } else if (event.finalDamage() < this.shadow$getHealth()) { // Deal reduced damage? this.shadow$causeDamage((ServerLevel) this.shadow$level(), source, (float) event.finalDamage()); cir.setReturnValue(false); } @@ -78,7 +78,7 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { @Inject(method = "hurtServer", cancellable = true, slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;BYPASSES_INVULNERABILITY:Lnet/minecraft/tags/TagKey;")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;kill(Lnet/minecraft/server/level/ServerLevel;)V", ordinal = 0)) - private void impl$fireDamageEventOutOfWorld(final ServerLevel level, final DamageSource source, final float $$1, final CallbackInfoReturnable cir) { + private void impl$fireDamageEventOutOfWorld(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { this.impl$callDamageBeforeKill(source, cir); } @@ -88,7 +88,7 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { @Inject(method = "hurtServer", cancellable = true, slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;IS_EXPLOSION:Lnet/minecraft/tags/TagKey;")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;brokenByAnything(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;)V")) - private void impl$fireDamageEventExplosion(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { + private void impl$fireDamageEventExplosion(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { this.impl$callDamageBeforeKill(source, cir); } @@ -100,9 +100,9 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { @Redirect(method = "hurtServer", slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;IGNITES_ARMOR_STANDS:Lnet/minecraft/tags/TagKey;")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;causeDamage(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void impl$fireDamageEventDamage(final ArmorStand self, final ServerLevel level, final DamageSource source, final float amount) { - var event = DamageEventUtil.callSimpleDamageEntityEvent(source, self, amount); - if (!event.isCancelled()) { + private void impl$fireDamageEventDamage(final ArmorStand self, final ServerLevel level, final DamageSource source, final float damage) { + final DamageEntityEvent.Post event = SpongeDamageTracker.callDamageEvents((Entity) this, source, damage); + if (event != null) { this.shadow$causeDamage(level, source, (float) event.finalDamage()); } } @@ -113,7 +113,7 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { @Inject(method = "hurtServer", slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/DamageSource;isCreativePlayer()Z")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;playBrokenSound()V"), cancellable = true) - private void impl$fireDamageEventCreativePunch(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { + private void impl$fireDamageEventCreativePunch(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { this.impl$callDamageBeforeKill(source, cir); } @@ -123,17 +123,17 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { @Inject(method = "hurtServer", cancellable = true, slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;lastHit:J", opcode = Opcodes.GETFIELD)), at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;broadcastEntityEvent(Lnet/minecraft/world/entity/Entity;B)V")) - private void impl$fireDamageEventFirstPunch(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { + private void impl$fireDamageEventFirstPunch(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { // While this doesn't technically "damage" the armor stand, it feels like damage in other respects, so fire an event. - var event = DamageEventUtil.callSimpleDamageEntityEvent(source, (ArmorStand) (Object) this, 0); - if (event.isCancelled()) { + final DamageEntityEvent.Post event = SpongeDamageTracker.callDamageEvents((Entity) this, source, 0); + if (event == null) { cir.setReturnValue(false); } } @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;brokenByPlayer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;)V")) - private void impl$beforeBrokenByPlayer(final ServerLevel level, final DamageSource $$0, final float $$1, final CallbackInfoReturnable cir) { + private void impl$beforeBrokenByPlayer(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { if (ShouldFire.DESTRUCT_ENTITY_EVENT && !((LevelBridge) this.shadow$level()).bridge$isFake()) { final var event = SpongeCommonEventFactory.callDestructEntityEventDeath((ArmorStand) (Object) this, null); if (event.isCancelled()) { @@ -145,10 +145,9 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;brokenByPlayer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;)V")) - public void impl$onBrokenByPlayer(final ArmorStand instance, final ServerLevel $$0, final DamageSource $$1) - { - try (final EffectTransactor ignored = PhaseTracker.getWorldInstance($$0).getPhaseContext().getTransactor().ensureEntityDropTransactionEffect((LivingEntity) (Object) this)) { - this.shadow$brokenByPlayer($$0, $$1); + public void impl$onBrokenByPlayer(final ArmorStand self, final ServerLevel level, final DamageSource source) { + try (final EffectTransactor ignored = PhaseTracker.getWorldInstance(level).getPhaseContext().getTransactor().ensureEntityDropTransactionEffect((LivingEntity) (Object) this)) { + this.shadow$brokenByPlayer(level, source); } } @@ -157,21 +156,21 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { */ @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;brokenByPlayer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;)V")) - private void impl$fireDamageEventSecondPunch(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { + private void impl$fireDamageEventSecondPunch(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { this.impl$callDamageBeforeKill(source, cir); } /** - * To avoid a loop between {@link #kill} and {@link ArmorStand#hurt}, - * we make sure that any killing within the {@link ArmorStand#hurt} + * To avoid a loop between {@link #kill} and {@link ArmorStand#hurtServer}, + * we make sure that any killing within the {@link ArmorStand#hurtServer} * method calls this instead of the {@link Overwrite} * - * @param target the killed stand + * @param self the killed stand */ @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;kill(Lnet/minecraft/server/level/ServerLevel;)V")) - private void impl$actuallyKill(final ArmorStand target, final ServerLevel level) { - target.remove(RemovalReason.KILLED); - target.gameEvent(GameEvent.ENTITY_DIE); + private void impl$actuallyKill(final ArmorStand self, final ServerLevel level) { + self.remove(RemovalReason.KILLED); + self.gameEvent(GameEvent.ENTITY_DIE); } /** @@ -179,12 +178,12 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { * we make sure that any killing within the {@link #shadow$causeDamage} * method calls this instead of the {@link Overwrite} * - * @param target the killed stand + * @param self the killed stand */ @Redirect(method = "causeDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;kill(Lnet/minecraft/server/level/ServerLevel;)V")) - private void impl$actuallyKill2(final ArmorStand target, final ServerLevel level) { - target.remove(RemovalReason.KILLED); - target.gameEvent(GameEvent.ENTITY_DIE); + private void impl$actuallyKill2(final ArmorStand self, final ServerLevel level) { + self.remove(RemovalReason.KILLED); + self.gameEvent(GameEvent.ENTITY_DIE); } /** @@ -192,7 +191,7 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { * @reason EntityArmorStand "simplifies" this method to simply call {@link * #shadow$remove(RemovalReason)}. However, this ignores our custom event. * Instead, delegate to the superclass causing it to use - * {@link ArmorStand#hurt(DamageSource, float)} with {@link DamageTypes#GENERIC_KILL} + * {@link ArmorStand#hurtServer} with {@link DamageTypes#GENERIC_KILL} * * This needs to be reimplemented in {@link #impl$actuallyKill}! */ diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/BlockAttachedEntityMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/BlockAttachedEntityMixin.java index 763e0ad5bc9..8ae562e80a5 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/BlockAttachedEntityMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/BlockAttachedEntityMixin.java @@ -26,16 +26,16 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.decoration.BlockAttachedEntity; +import org.spongepowered.api.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.mixin.core.world.entity.EntityMixin; -import org.spongepowered.common.util.DamageEventUtil; @Mixin(BlockAttachedEntity.class) public abstract class BlockAttachedEntityMixin extends EntityMixin { @@ -57,8 +57,8 @@ public abstract class BlockAttachedEntityMixin extends EntityMixin { @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/BlockAttachedEntity;kill(Lnet/minecraft/server/level/ServerLevel;)V")) - private void attackImpl$postEventOnAttackEntityFrom(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + private void attackImpl$postEventOnAttackEntityFrom(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ItemFrameMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ItemFrameMixin.java index c09f61c3b90..0ffd03487b8 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ItemFrameMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ItemFrameMixin.java @@ -26,21 +26,20 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; +import org.spongepowered.api.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.common.util.DamageEventUtil; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; @Mixin(net.minecraft.world.entity.decoration.ItemFrame.class) public abstract class ItemFrameMixin extends HangingEntityMixin { @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/entity/decoration/ItemFrame;dropItem(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Z)V")) - private void attackImpl$onAttackEntityFrom(final ServerLevel level, final DamageSource source, - final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + target = "Lnet/minecraft/world/entity/decoration/ItemFrame;dropItem(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Z)V")) + private void attack$onHurt(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/item/ItemEntityMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/item/ItemEntityMixin.java index d570c63db57..2d138fd14f1 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/item/ItemEntityMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/item/ItemEntityMixin.java @@ -26,10 +26,10 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.Entity; import org.spongepowered.api.entity.Item; import org.spongepowered.api.event.Cause; import org.spongepowered.api.event.SpongeEventFactory; @@ -47,10 +47,10 @@ import org.spongepowered.common.bridge.world.level.LevelBridge; import org.spongepowered.common.config.SpongeGameConfigs; import org.spongepowered.common.data.provider.entity.ItemData; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.mixin.core.world.entity.EntityMixin; import org.spongepowered.common.util.Constants; -import org.spongepowered.common.util.DamageEventUtil; @Mixin(ItemEntity.class) public abstract class ItemEntityMixin extends EntityMixin implements ItemEntityBridge { @@ -146,9 +146,8 @@ public abstract class ItemEntityMixin extends EntityMixin implements ItemEntityB @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/item/ItemEntity;markHurt()V")) - private void attackImpl$onAttackEntityFrom(final ServerLevel level, final DamageSource source, - final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + private void attack$onHurt(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java new file mode 100644 index 00000000000..1c4310f3cbd --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java @@ -0,0 +1,311 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.entity.player; + +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.api.event.entity.AttackEntityEvent; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import org.spongepowered.common.bridge.world.entity.TrackedAttackBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeAttackTracker; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.event.tracking.context.transaction.inventory.PlayerInventoryTransaction; +import org.spongepowered.common.mixin.core.world.entity.LivingEntityMixin_Damage; + +import java.util.Deque; +import java.util.LinkedList; + +@SuppressWarnings("ConstantConditions") +@Mixin(value = Player.class, priority = 900) +public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implements TrackedAttackBridge { + + //@formatter:off + @Shadow protected abstract float shadow$getEnchantedDamage(final Entity target, final float damage, final DamageSource source); + @Shadow @Final public InventoryMenu inventoryMenu; + //@formatter:on + + private final Deque attack$trackers = new LinkedList<>(); + + @Override + public final @Nullable SpongeAttackTracker attack$tracker() { + return this.attack$trackers.peekLast(); + } + + @Inject(method = "attack", locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true, + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F", ordinal = 0)) + private void attack$firePreEvent(final Entity target, final CallbackInfo ci, final float damage, final ItemStack weapon, final DamageSource source) { + final SpongeAttackTracker tracker = SpongeAttackTracker.callAttackPreEvent((org.spongepowered.api.entity.Entity) target, source, damage, weapon); + if (tracker == null) { + ci.cancel(); + } else { + this.attack$trackers.addLast(tracker); + } + } + + @ModifyVariable(method = "attack", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F", ordinal = 0)) + private float attack$setBaseDamage(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : (float) tracker.preEvent().baseDamage(); + } + + @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F")) + private float attack$captureAttackStrength(final Player self, final float param) { + final float value = self.getAttackStrengthScale(param); + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker != null) { + tracker.setAttackStrength(value); + } + return value; + } + + @ModifyVariable(method = "attack", at = @At("LOAD"), ordinal = 0, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + )) + private float attack$modifyBeforeBaseCooldown(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.BASE_COOLDOWN, damage, this); + } + + @ModifyVariable(method = "attack", at = @At("STORE"), ordinal = 0, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + )) + private float attack$modifyAfterBaseCooldown(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.BASE_COOLDOWN, damage); + } + + @ModifyVariable(method = "attack", at = @At("LOAD"), ordinal = 1, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + )) + private float attack$modifyBeforeEnchantmentCooldown(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.ENCHANTMENT_COOLDOWN, damage, tracker.weaponSnapshot()); + } + + @ModifyVariable(method = "attack", at = @At("STORE"), ordinal = 1, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + )) + private float attack$modifyAfterEnchantmentCooldown(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.ENCHANTMENT_COOLDOWN, damage); + } + + @Inject(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isSprinting()Z", ordinal = 0), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F"))) + private void attack$captureStrongSprint(final Entity target, final CallbackInfo ci) { + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker != null) { + tracker.setStrongSprint(true); + } + } + + @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$modifyBeforeAndAfterWeaponBonus(final Item item, final Entity target, final float originalDamage, final DamageSource source) { + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker == null) { + return item.getAttackDamageBonus(target, originalDamage, source); + } + + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_BONUS, originalDamage, tracker.weaponSnapshot()); + float damage = (float) step.applyModifiersBefore(); + if (!step.isSkipped()) { + damage += item.getAttackDamageBonus(target, damage, source); + } + return (float) step.applyModifiersAfter(damage) - originalDamage; + } + + @ModifyVariable(method = "attack", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isSprinting()Z", ordinal = 1), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;horizontalDistanceSqr()D") + )) + private float attack$modifyBeforeCriticalHit(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.CRITICAL_HIT, damage, this); + } + + @ModifyVariable(method = "attack", at = @At("STORE"), ordinal = 0, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isSprinting()Z", ordinal = 1), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;horizontalDistanceSqr()D") + )) + private float attack$modifyAfterCriticalHit(final float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? damage : tracker.endStep(DamageStepTypes.CRITICAL_HIT, damage); + } + + @SuppressWarnings("deprecation") + @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtOrSimulate(Lnet/minecraft/world/damagesource/DamageSource;F)Z"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getDeltaMovement()Lnet/minecraft/world/phys/Vec3;"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F") + )) + private boolean attack$firePostEvent(final Entity target, final DamageSource source, float damage) { + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker != null) { + final float knockbackModifier = this.shadow$getKnockback(target, source) + (tracker.isStrongSprint() ? 1.0F : 0.0F); + if (tracker.callAttackPostEvent((org.spongepowered.api.entity.Entity) target, source, damage, knockbackModifier)) { + return false; + } + damage = (float) tracker.postEvent().finalDamage(); + } + return target.hurtOrSimulate(source, damage); + } + + @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$knockbackModifier(final Player self, final Entity target, final DamageSource source) { + final SpongeAttackTracker tracker = this.attack$tracker(); + return tracker == null ? this.shadow$getKnockback(target, source) : ((float) tracker.postEvent().knockbackModifier() - (tracker.isStrongSprint() ? 1.0F : 0.0F)); + } + + @Redirect(method = "attack", + slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V")), + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V")) + private void attack$preventSound(final Level level, final Player player, final double x, final double y, final double z, + final SoundEvent sound, final SoundSource source, final float volume, final float pitch) { + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker == null || !tracker.postEvent().isCancelled()) { + level.playSound(player, x, y, z, sound, source, volume, pitch); + } + } + + @Inject(method = "attack", at = @At("RETURN"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F", ordinal = 0) + )) + private void attack$removeTracker(CallbackInfo ci) { + this.attack$trackers.removeLast(); + } + + @Redirect(method = "attack", + slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")), + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;distanceToSqr(Lnet/minecraft/world/entity/Entity;)D")) + private double sweepAttack$fireEvents(final Player self, final Entity sweepTarget) { + final double distanceSquared = self.distanceToSqr(sweepTarget); + if (!(distanceSquared < this.attack$interactionRangeSquared())) { + return distanceSquared; + } + + final SpongeAttackTracker mainTracker = this.attack$tracker(); + if (mainTracker == null) { + return distanceSquared; + } + + final AttackEntityEvent.Post mainEvent = mainTracker.postEvent(); + DamageSource source = (DamageSource) mainEvent.source(); + float damage = (float) (mainEvent.finalDamage() - mainTracker.damageAfter(DamageStepTypes.ENCHANTMENT_COOLDOWN)); + + final SpongeAttackTracker sweepTracker = SpongeAttackTracker.callAttackPreEvent((org.spongepowered.api.entity.Entity) sweepTarget, source, damage, mainTracker.weapon()); + if (sweepTracker == null) { + return Double.MAX_VALUE; + } + + this.attack$trackers.addLast(sweepTracker); + damage = (float) sweepTracker.preEvent().baseDamage(); + + // In vanilla, this step is outside the loop, but we move it to here so it can be modified per target + SpongeDamageStep step = sweepTracker.newStep(DamageStepTypes.SWEEPING, damage, sweepTracker.weaponSnapshot()); + damage = (float) step.applyModifiersBefore(); + if (!step.isSkipped()) { + damage = 1.0F + (float) this.shadow$getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * damage; + } + damage = (float) step.applyModifiersAfter(damage); + + damage = this.shadow$getEnchantedDamage(sweepTarget, damage, source); + + step = sweepTracker.newStep(DamageStepTypes.ENCHANTMENT_COOLDOWN, damage, sweepTracker.weaponSnapshot()); + damage = (float) step.applyModifiersBefore(); + if (!step.isSkipped()) { + damage *= mainTracker.attackStrength(); + } + damage = (float) step.applyModifiersAfter(damage); + + if (sweepTracker.callAttackPostEvent((org.spongepowered.api.entity.Entity) sweepTarget, source, damage, 0.4F)) { + this.attack$trackers.removeLast(); + return Double.MAX_VALUE; + } + + return distanceSquared; + } + + @Redirect(method = "attack", + slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")), + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) + private float sweepAttack$cancelEnchantedDamage(final Player self, final Entity sweepTarget, final float damage, final DamageSource source) { + return damage; // We already did it above + } + + @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V"))) + private void sweepAttack$knockbackModifier(final LivingEntity sweepTarget, double modifier, final double dirX, final double dirZ) { + final SpongeAttackTracker sweepTracker = this.attack$tracker(); + if (sweepTracker != null) { + modifier = sweepTracker.postEvent().knockbackModifier(); + } + sweepTarget.knockback(modifier, dirX, dirZ); + } + + @SuppressWarnings("deprecation") + @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V"), + slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;"))) + private void sweepAttack$finalDamage(final LivingEntity sweepTarget, final DamageSource source, float damage) { + final SpongeAttackTracker sweepTracker = this.attack$tracker(); + if (sweepTracker != null) { + damage = (float) sweepTracker.postEvent().finalDamage(); + } + sweepTarget.hurt(source, damage); + this.attack$trackers.removeLast(); + } + + @Inject(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setItemInHand(Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/item/ItemStack;)V", shift = At.Shift.AFTER)) + private void attack$captureInventoryChange(final CallbackInfo ci) { + PhaseTracker.getWorldInstance(this.shadow$level()).getPhaseContext().getTransactor().logPlayerInventoryChange((Player) (Object) this, PlayerInventoryTransaction.EventCreator.STANDARD); + this.inventoryMenu.broadcastChanges(); + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack_Impl.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack_Impl.java deleted file mode 100644 index 47f8ea08f90..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack_Impl.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.mixin.core.world.entity.player; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.InventoryMenu; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.spongepowered.api.ResourceKey; -import org.spongepowered.api.event.entity.AttackEntityEvent; -import org.spongepowered.api.event.impl.entity.AbstractModifierEvent; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Constant; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.Slice; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; -import org.spongepowered.common.event.tracking.PhaseContext; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.event.tracking.context.transaction.TransactionalCaptureSupplier; -import org.spongepowered.common.event.tracking.context.transaction.inventory.PlayerInventoryTransaction; -import org.spongepowered.common.mixin.core.world.entity.LivingEntityMixin_Attack_Impl; -import org.spongepowered.common.util.DamageEventUtil; - -import java.util.ArrayList; -import java.util.Map; - -@SuppressWarnings("ConstantConditions") -@Mixin(value = Player.class, priority = 900) -public abstract class PlayerMixin_Attack_Impl extends LivingEntityMixin_Attack_Impl { - - //@formatter:off - @Shadow @Final public InventoryMenu inventoryMenu; - @Shadow public abstract float shadow$getAttackStrengthScale(final float $$0); - - //@formatter:on - - private void impl$playAttackSound(Player thisPlayer, SoundEvent sound) { - if (this.bridge$vanishState().createsSounds()) { - thisPlayer.level().playSound(null, thisPlayer.getX(), thisPlayer.getY(), thisPlayer.getZ(), sound, thisPlayer.getSoundSource()); - } - } - - protected DamageEventUtil.Attack attackImpl$attack; - protected AttackEntityEvent attackImpl$attackEvent; - protected Map attackImpl$finalDamageAmounts; - - protected int attackImpl$attackStrengthTicker; - protected boolean attackImpl$isStrongSprintAttack; - - /** - * Cleanup - */ - @Inject(method = "attack", at = @At("RETURN")) - protected void attackImpl$onReturnCleanup(final Entity $$0, final CallbackInfo ci) { - this.attackImpl$attack = null; - this.attackImpl$attackEvent = null; - this.attackImpl$finalDamageAmounts = null; - } - - /** - * Captures the base damage for the {@link AttackEntityEvent} in {@link #attackImpl$attack} - * and the {@link #attackStrengthTicker} in case we need to roll it back. - * Reset {@link #attackImpl$isStrongSprintAttack} - */ - @Inject(method = "attack", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F", shift = At.Shift.BEFORE)) - protected void attackImpl$captureAttackStart(final Entity target, final CallbackInfo ci, final float baseDamage, final ItemStack weapon, final DamageSource source) { - final var strengthScale = this.shadow$getAttackStrengthScale(0.5F); - this.attackImpl$attack = new DamageEventUtil.Attack<>((Player) (Object) this, target, weapon, source, strengthScale, baseDamage, new ArrayList<>()); - this.attackImpl$attackStrengthTicker = this.attackStrengthTicker; - this.attackImpl$isStrongSprintAttack = false; - } - - /** - * Captures the enchantment damage calculations as functions - */ - @Inject(method = "attack", at = @At(value = "INVOKE", ordinal = 0, - target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) - protected void attackImpl$enchanttDamageFunc(final Entity $$0, final CallbackInfo ci) { - // This is overridden in ServerPlayerMixin_Attack_Impl since enchantments can only be calculated on the - // server worlds. - } - - - /** - * Captures the attack-strength damage scaling as a function - */ - @Inject(method = "attack", at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V")) - protected void attackImpl$attackStrengthScalingDamageFunc(final Entity $$0, final CallbackInfo ci) { - // damage *= 0.2F + attackStrength * attackStrength * 0.8F; - final var strengthScaleFunc = DamageEventUtil.provideCooldownAttackStrengthFunction((Player) (Object) this, this.attackImpl$attack.strengthScale()); - this.attackImpl$attack.functions().add(strengthScaleFunc); - } - - /** - * Prevents the {@link SoundEvents#PLAYER_ATTACK_KNOCKBACK} from playing before the event. - * Captures if {@link #attackImpl$isStrongSprintAttack} for later - */ - @Redirect(method = "attack", - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;isSprinting()Z", ordinal = 0), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V")) - protected void attackImpl$preventSprintingAttackSound(final Level instance, final Player $$0, final double $$1, final double $$2, final double $$3, final SoundEvent $$4, - final SoundSource $$5, final float $$6, final float $$7) { - // prevent sound - this.attackImpl$isStrongSprintAttack = true; - } - - /** - * Captures the weapon bonus damage as a function - */ - @Inject(method = "attack", at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) - protected void attackImpl$attackDamageFunc(final Entity $$0, final CallbackInfo ci) { - // damage += weaponItem.getItem().getAttackDamageBonus(targetEntity, damage, damageSource); - final var bonusDamageFunc = DamageEventUtil.provideWeaponAttackDamageBonusFunction(this.attackImpl$attack.target(), this.attackImpl$attack.weapon(), this.attackImpl$attack.dmgSource()); - this.attackImpl$attack.functions().add(bonusDamageFunc); - } - - /** - * Capture damageSource for sweep attacks event later - * Calculate knockback earlier than vanilla for event - * call the AttackEntityEvent - * Play prevented sound from {@link #attackImpl$preventSprintingAttackSound} - * returns false if canceled, appearing for vanilla as an invulnerable target. {@link #attackImpl$onNoDamageSound} - */ - @Redirect(method = "attack", - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getDeltaMovement()Lnet/minecraft/world/phys/Vec3;"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtOrSimulate(Lnet/minecraft/world/damagesource/DamageSource;F)Z")) - protected boolean attackImpl$onHurt(final Entity targetEntity, final DamageSource damageSource, final float mcDamage) { - - float knockbackModifier = this.shadow$getKnockback(targetEntity, damageSource) + (this.attackImpl$isStrongSprintAttack ? 1.0F : 0.0F); - this.attackImpl$attackEvent = DamageEventUtil.callPlayerAttackEntityEvent(this.attackImpl$attack, knockbackModifier); - - if (this.attackImpl$attackEvent.isCancelled()) { - // TODO this is actually not really doing anything because a ServerboundSwingPacket also resets it immediatly after - this.attackStrengthTicker = this.attackImpl$attackStrengthTicker; // Reset to old value - return false; - } - - this.attackImpl$finalDamageAmounts = AbstractModifierEvent.finalAmounts(this.attackImpl$attackEvent.originalDamage(), this.attackImpl$attackEvent.modifiers()); - - if (this.attackImpl$isStrongSprintAttack) { - // Play prevented sprint attack sound - this.impl$playAttackSound((Player) (Object) this, SoundEvents.PLAYER_ATTACK_KNOCKBACK); - } - - if (targetEntity.level() instanceof ServerLevel sl) { - return targetEntity.hurtServer(sl, damageSource, (float) this.attackImpl$attackEvent.finalOutputDamage()); - } - - return targetEntity.hurtClient(damageSource); - } - - /** - * Set enchantment damage with value from event - */ - @ModifyVariable(method = "attack", ordinal = 1, - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtOrSimulate(Lnet/minecraft/world/damagesource/DamageSource;F)Z"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V", ordinal = 0)), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) - protected float attackImpl$enchentmentDamageFromEvent(final float enchDmg) { - return this.attackImpl$finalDamageAmounts.getOrDefault(ResourceKey.minecraft("attack_enchantment"), 0.0).floatValue(); - } - - /** - * Redirects Player#getKnockback to the attack event value - */ - @Redirect(method = "attack", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) - protected float attackImpl$sweepHook(final Player instance, final Entity entity, final DamageSource damageSource) { - return this.attackImpl$attackEvent.knockbackModifier(); - } - - @ModifyConstant(method = "attack", constant = @Constant(floatValue = 1.0F), slice = @Slice( - from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")) - ) - private float attackImpl$noDoubleAddKb(final float constant) { - return 0.0F; - } - - /** - * Prevents the {@link SoundEvents#PLAYER_ATTACK_NODAMAGE} when event was canceled - */ - @Redirect(method = "attack", - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V")), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V")) - protected void attackImpl$onNoDamageSound( - final Level instance, final Player $$0, final double $$1, final double $$2, final double $$3, - final SoundEvent $$4, final SoundSource $$5, final float $$6, final float $$7 - ) { - if (!this.attackImpl$attackEvent.isCancelled()) { - this.impl$playAttackSound((Player) (Object) this, SoundEvents.PLAYER_ATTACK_NODAMAGE); - } - } - - /** - * Call Sweep Attack Events - */ - @WrapOperation(method = "attack", - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;distanceToSqr(Lnet/minecraft/world/entity/Entity;)D")) - protected double attackImpl$beforeSweepHurt(final Player instance, final Entity entity, final Operation original) { - return original.call(instance, entity); - } - - /** - * Redirect Player#getEnchantedDamage to sweep event value - */ - @Redirect(method = "attack", - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) - protected float attackImpl$beforeSweepHurt(final Player instance, final Entity $$0, final float $$1, final DamageSource $$2) { - return (float) this.attackImpl$attackEvent.finalOutputDamage(); - } - - /** - * Redirect {@link LivingEntity#knockback} to use modified event knockback - */ - @Redirect(method = "attack", - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V")), - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")) - protected void attackImpl$modifyKnockback(final LivingEntity instance, final double $$0, final double $$1, final double $$2) { - instance.knockback($$0 * this.attackImpl$attackEvent.knockbackModifier(), $$1, $$2); - } - - /** - * Captures inventory changes - */ - @Redirect(method = "attack", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setItemInHand(Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/item/ItemStack;)V")) - protected void attackImpl$causeInventoryCapture(final Player instance, final InteractionHand interactionHand, final ItemStack stack) { - instance.setItemInHand(interactionHand, stack); - - // Capture... - final PhaseContext<@NonNull ?> context = PhaseTracker.getWorldInstance((ServerLevel) this.shadow$level()).getPhaseContext(); - final TransactionalCaptureSupplier transactor = context.getTransactor(); - transactor.logPlayerInventoryChange(instance, PlayerInventoryTransaction.EventCreator.STANDARD); - this.inventoryMenu.broadcastChanges(); - } - - @Inject(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F")) - protected void attackImpl$startActuallyHurt(final ServerLevel level, DamageSource damageSource, float originalDamage, CallbackInfo ci) { - // TODO check for direct call? - this.attackImpl$actuallyHurt = new DamageEventUtil.ActuallyHurt((LivingEntity) (Object) this, new ArrayList<>(), damageSource, originalDamage); - } - - /** - * Set final damage after calling {@link Player#setAbsorptionAmount} in which we called the event - * !!NOTE that var7 is actually decompiled incorrectly!! - * It is NOT the final damage value instead the method parameter is mutated - */ - @ModifyVariable(method = "actuallyHurt", ordinal = 0, - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V", - shift = At.Shift.AFTER), argsOnly = true) - protected float attackImpl$setFinalDamage(final float value) { - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - return 0; - } - return this.attackImpl$actuallyHurtFinalDamage; - } - - /** - * Cleanup - */ - @Inject(method = "actuallyHurt", at = @At("RETURN")) - protected void attackImpl$afterActuallyHurt(final ServerLevel level, final DamageSource $$0, final float $$1, final CallbackInfo ci) { - this.attackImpl$handlePostDamage(); - this.attackImpl$actuallyHurt = null; - this.attackImpl$actuallyHurtResult = null; - } - - -} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java new file mode 100644 index 00000000000..9e95476c7e6 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java @@ -0,0 +1,67 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.entity.player; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; +import org.spongepowered.common.mixin.core.world.entity.LivingEntityMixin_Damage; + +// Forge and Vanilla +@Mixin(value = Player.class, priority = 900) +public abstract class PlayerMixin_Shared_Damage extends LivingEntityMixin_Damage { + + @ModifyVariable(method = "actuallyHurt", at = @At("STORE"), argsOnly = true, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getDamageAfterMagicAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAbsorptionAmount()F", ordinal = 0))) + private float damage$modifyBeforeAbsorption(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); + } + + @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V")) + private void damage$skipAbsorption(final Player self, final float absorption) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { + self.setAbsorptionAmount(absorption); + } + } + + @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V"))) + private void damage$skipAbsorptionStat(final Player self, final ResourceLocation stat, final int amount) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { + self.awardStat(stat, amount); + } + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/projectile/ShulkerBulletMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/projectile/ShulkerBulletMixin.java index 3efc4c01c22..79649ea2e18 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/projectile/ShulkerBulletMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/projectile/ShulkerBulletMixin.java @@ -26,9 +26,9 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.projectile.ShulkerBullet; import net.minecraft.world.phys.HitResult; +import org.spongepowered.api.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -36,7 +36,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.common.bridge.world.level.LevelBridge; import org.spongepowered.common.event.SpongeCommonEventFactory; -import org.spongepowered.common.util.DamageEventUtil; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; @Mixin(ShulkerBullet.class) public abstract class ShulkerBulletMixin extends ProjectileMixin { @@ -53,8 +53,8 @@ private void onBulletHitBlock(final HitResult result, final CallbackInfo ci) { @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/ShulkerBullet;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V")) - private void attackImpl$onAttackEntityFrom(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + private void attack$onHurt(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/MinecartTNTMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/MinecartTNTMixin.java index bde38fc5333..7a42f72ca64 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/MinecartTNTMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/MinecartTNTMixin.java @@ -26,7 +26,6 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.vehicle.MinecartTNT; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.data.Keys; @@ -40,9 +39,9 @@ import org.spongepowered.common.bridge.explosives.ExplosiveBridge; import org.spongepowered.common.bridge.explosives.FusedExplosiveBridge; import org.spongepowered.common.bridge.world.level.LevelBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.util.Constants; -import org.spongepowered.common.util.DamageEventUtil; import java.util.Optional; @@ -150,8 +149,8 @@ public abstract class MinecartTNTMixin extends AbstractMinecartMixin implements @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/vehicle/MinecartTNT;explode(Lnet/minecraft/world/damagesource/DamageSource;D)V")) - private void attackImpl$postOnAttackEntityFrom(final ServerLevel level, final DamageSource source, final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((Entity) (Object) this, source, amount).isCancelled()) { + private void attackImpl$postOnAttackEntityFrom(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((org.spongepowered.api.entity.Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/VehicleEntityMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/VehicleEntityMixin.java index de39fb029c3..3ea934f0d1a 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/VehicleEntityMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/vehicle/VehicleEntityMixin.java @@ -27,21 +27,21 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.vehicle.VehicleEntity; +import org.spongepowered.api.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.mixin.core.world.entity.EntityMixin; -import org.spongepowered.common.util.DamageEventUtil; @Mixin(VehicleEntity.class) public abstract class VehicleEntityMixin extends EntityMixin { @Inject(method = "hurtServer", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/vehicle/VehicleEntity;shouldSourceDestroy(Lnet/minecraft/world/damagesource/DamageSource;)Z")) - private void attackImpl$postOnAttackEntityFrom(final ServerLevel level, final DamageSource source, - final float amount, final CallbackInfoReturnable cir) { - if (DamageEventUtil.callOtherAttackEvent((net.minecraft.world.entity.Entity) (Object) this, source, amount).isCancelled()) { + private void attack$onHurt(final ServerLevel level, final DamageSource source, final float damage, final CallbackInfoReturnable cir) { + if (SpongeDamageTracker.callDamageEvents((Entity) this, source, damage) == null) { cir.setReturnValue(true); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java new file mode 100644 index 00000000000..ee4f6589349 --- /dev/null +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java @@ -0,0 +1,72 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.mixin.core.world.item.enchantment; + +import net.minecraft.core.component.DataComponentType; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.ConditionalEffect; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.effects.EnchantmentValueEffect; +import org.apache.commons.lang3.mutable.MutableFloat; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.common.event.cause.entity.damage.SpongeAttackTracker; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; +import org.spongepowered.common.item.util.ItemStackUtil; + +import java.util.List; + +@Mixin(Enchantment.class) +public abstract class EnchantmentMixin_Attack { + + @Shadow protected abstract void shadow$modifyDamageFilteredValue( + final DataComponentType>> component, + final ServerLevel level, final int enchantmentLevel, final ItemStack weapon, final Entity target, final DamageSource source, final MutableFloat damage); + + @Redirect(method = "modifyDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/Enchantment;modifyDamageFilteredValue(Lnet/minecraft/core/component/DataComponentType;Lnet/minecraft/server/level/ServerLevel;ILnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;Lorg/apache/commons/lang3/mutable/MutableFloat;)V")) + private void attack$modifyWeaponEnchantment( + final Enchantment self, final DataComponentType>> component, + final ServerLevel level, final int enchantmentLevel, final ItemStack weapon, final Entity target, final DamageSource source, final MutableFloat damage) { + + final SpongeAttackTracker tracker = SpongeAttackTracker.of(source); + if (tracker == null) { + this.shadow$modifyDamageFilteredValue(component, level, enchantmentLevel, weapon, target, source, damage); + return; + } + + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_ENCHANTMENT, damage.floatValue(), ItemStackUtil.snapshotOf(weapon), self); + damage.setValue((float) step.applyModifiersBefore()); + if (!step.isSkipped()) { + this.shadow$modifyDamageFilteredValue(component, level, enchantmentLevel, weapon, target, source, damage); + } + damage.setValue((float) step.applyModifiersAfter(damage.floatValue())); + } +} diff --git a/src/mixins/resources/mixins.sponge.core.json b/src/mixins/resources/mixins.sponge.core.json index 83c765bfecd..3474f16504c 100644 --- a/src/mixins/resources/mixins.sponge.core.json +++ b/src/mixins/resources/mixins.sponge.core.json @@ -86,7 +86,6 @@ "server.level.ServerPlayer_ContainerListenerMixin", "server.level.ServerPlayerGameModeMixin", "server.level.ServerPlayerMixin", - "server.level.ServerPlayerMixin_Attack_impl", "server.level.ServerPlayerMixin_HealthScale", "server.level.TicketMixin", "server.network.LegacyQueryHandlerMixin", @@ -129,9 +128,9 @@ "world.entity.LeashableMixin", "world.entity.LightningBoltMixin", "world.entity.LivingEntityMixin", - "world.entity.LivingEntityMixin_Attack_Impl", + "world.entity.LivingEntityMixin_Damage", "world.entity.MobMixin", - "world.entity.MobMixin_Attack_Impl", + "world.entity.MobMixin_Attack", "world.entity.PortalProcessorMixin", "world.entity.ai.goal.BreakDoorGoalMixin", "world.entity.ai.goal.BreedGoalMixin", @@ -173,7 +172,7 @@ "world.entity.npc.VillagerMixin", "world.entity.npc.WanderingTraderMixin", "world.entity.player.PlayerMixin", - "world.entity.player.PlayerMixin_Attack_Impl", + "world.entity.player.PlayerMixin_Attack", "world.entity.projectile.AbstractArrowMixin", "world.entity.projectile.AbstractHurtingProjectileMixin", "world.entity.projectile.EyeOfEnderMixin", @@ -218,7 +217,7 @@ "world.item.crafting.ShapelessRecipeMixin", "world.item.crafting.SingleItemRecipeMixin", "world.item.crafting.SmithingTransformRecipeMixin", - "world.item.enchantment.EnchantmentMixin", + "world.item.enchantment.EnchantmentMixin_Attack", "world.level.BaseSpawnerMixin", "world.level.BlockGetterMixin", "world.level.Explosion_BlockInteractionMixin", diff --git a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java index 36495a6884e..f08ac01bd8a 100644 --- a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java @@ -24,9 +24,12 @@ */ package org.spongepowered.test.damage; +import static net.kyori.adventure.text.Component.text; + import com.google.inject.Inject; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.spongepowered.api.ResourceKey; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.Command; @@ -34,14 +37,13 @@ import org.spongepowered.api.command.parameter.CommandContext; import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.cause.entity.damage.DamageFunction; -import org.spongepowered.api.event.cause.entity.damage.DamageModifier; import org.spongepowered.api.event.cause.entity.damage.DamageScalings; +import org.spongepowered.api.event.cause.entity.damage.DamageStep; import org.spongepowered.api.event.cause.entity.damage.DamageType; import org.spongepowered.api.event.cause.entity.damage.DamageTypes; import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; import org.spongepowered.api.event.entity.AttackEntityEvent; -import org.spongepowered.api.event.entity.DamageEntityEvent; +import org.spongepowered.api.event.entity.DamageCalculationEvent; import org.spongepowered.api.event.filter.cause.Root; import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; import org.spongepowered.api.event.lifecycle.RegisterRegistryValueEvent; @@ -51,11 +53,13 @@ import org.spongepowered.api.registry.RegistryRegistrationSet; import org.spongepowered.api.registry.RegistryTypes; import org.spongepowered.api.tag.DamageTypeTags; -import org.spongepowered.api.util.Tuple; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; import org.spongepowered.test.LoadableModule; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; @Plugin("damagetest") public class DamageTest implements LoadableModule { @@ -120,38 +124,41 @@ private void onRegisterTagEvent(final RegisterTagEvent event) { } private static class DamageListener { + private static final DecimalFormat decimalFormat = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ROOT)); + + private static String format(final double value) { + return DamageListener.decimalFormat.format(value); + } + @Listener - private void onAttack(final AttackEntityEvent event, @Root DamageSource damageSource) { + private void onDamagePre(final DamageCalculationEvent.Pre event, @Root final DamageSource damageSource) { + final Component eventName = event instanceof AttackEntityEvent ? + text("AttackEntityEvent", NamedTextColor.RED) : text("DamageEntityEvent", NamedTextColor.BLUE); + final Audience audience = Sponge.server(); - audience.sendMessage(Component.text("------------AttackEntityEvent------------")); - audience.sendMessage(Component.text().content("entity: ").append(event.entity().displayName().get()).build()); - audience.sendMessage(Component.text("damage type: " + damageSource.type().key(RegistryTypes.DAMAGE_TYPE))); - audience.sendMessage(Component.text("damage: " + event.originalDamage())); - audience.sendMessage(Component.text("modifiers:")); - for (final DamageFunction f : event.originalFunctions()) { - final DamageModifier modifier = f.modifier(); - final Tuple tuple = event.originalModifierDamage(modifier); - audience.sendMessage(Component.text(" " + ResourceKey.resolve(modifier.group()).value() + "/" + modifier.type().key(RegistryTypes.DAMAGE_MODIFIER_TYPE).value() + ": " + tuple.first() + " -> " + tuple.second())); - } - audience.sendMessage(Component.text("final damage: " + event.originalFinalDamage())); - audience.sendMessage(Component.text("-----------------------------------------")); + audience.sendMessage(text().content("-------------").append(eventName, text(".Pre", NamedTextColor.YELLOW), text("---------------"))); + audience.sendMessage(text().content(damageSource.type().key(RegistryTypes.DAMAGE_TYPE).value()) + .color(NamedTextColor.GOLD).append(text(" -> ", NamedTextColor.WHITE), event.entity().displayName().get())); + audience.sendMessage(text("base damage: " + format(event.baseDamage()))); + audience.sendMessage(text("-----------------------------------------------")); } @Listener - private void onDamage(final DamageEntityEvent event, @Root DamageSource damageSource) { + private void onDamagePost(final DamageCalculationEvent.Post event, @Root final DamageSource damageSource) { + final Component eventName = event instanceof AttackEntityEvent ? + text("AttackEntityEvent", NamedTextColor.RED) : text("DamageEntityEvent", NamedTextColor.BLUE); + final Audience audience = Sponge.server(); - audience.sendMessage(Component.text("------------DamageEntityEvent------------")); - audience.sendMessage(Component.text().content("entity: ").append(event.entity().displayName().get()).build()); - audience.sendMessage(Component.text("damage type: " + damageSource.type().key(RegistryTypes.DAMAGE_TYPE))); - audience.sendMessage(Component.text("damage: " + event.originalDamage())); - audience.sendMessage(Component.text("modifiers:")); - for (final DamageFunction f : event.originalFunctions()) { - final DamageModifier modifier = f.modifier(); - final Tuple tuple = event.originalModifierDamage(modifier); - audience.sendMessage(Component.text(" " + ResourceKey.resolve(modifier.group()).value() + "/" + modifier.type().key(RegistryTypes.DAMAGE_MODIFIER_TYPE).value() + ": " + tuple.first() + " -> " + tuple.second())); + audience.sendMessage(text().content("-------------").append(eventName, text(".Post", NamedTextColor.GREEN), text("--------------"))); + audience.sendMessage(text().content(damageSource.type().key(RegistryTypes.DAMAGE_TYPE).value()) + .color(NamedTextColor.GOLD).append(text(" -> ", NamedTextColor.WHITE), event.entity().displayName().get())); + audience.sendMessage(text("base damage: " + format(event.baseDamage()))); + audience.sendMessage(text("steps:")); + for (final DamageStep step : event.steps()) { + audience.sendMessage(text(" " + step.type().key(RegistryTypes.DAMAGE_STEP_TYPE).value() + ": " + format(step.damageBeforeStep()) + " -> " + format(step.damageAfterStep()))); } - audience.sendMessage(Component.text("final damage: " + event.originalFinalDamage())); - audience.sendMessage(Component.text("-----------------------------------------")); + audience.sendMessage(text("final damage: " + format(event.originalFinalDamage()))); + audience.sendMessage(text("-----------------------------------------------")); } } } diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java deleted file mode 100644 index ccb7d33c84a..00000000000 --- a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.vanilla.mixin.core.world.entity; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.TamableAnimal; -import net.minecraft.world.entity.animal.Wolf; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Coerce; -import org.spongepowered.asm.mixin.injection.Constant; -import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.Redirect; - - -@Mixin(value = LivingEntity.class, priority = 900) -public abstract class LivingEntityMixin_Attack_Impl { - - @SuppressWarnings("InvalidInjectorMethodSignature") - @ModifyConstant(method = "resolvePlayerResponsibleForDamage", constant = @Constant(classValue = Wolf.class, ordinal = 0)) - private Class attackImpl$onWolfCast(final Object entity, final Class wolf) { - return TamableAnimal.class; - } - - @Redirect(method = "resolvePlayerResponsibleForDamage", - at = @At(value = "INVOKE" , target = "Lnet/minecraft/world/entity/animal/Wolf;isTame()Z")) - private boolean attackImpl$onWolfIsTame(@Coerce final Object instance) { - return ((TamableAnimal)instance).isTame(); - } - - @Redirect(method = "resolvePlayerResponsibleForDamage", - at = @At(value = "INVOKE" , target = "Lnet/minecraft/world/entity/animal/Wolf;getOwner()Lnet/minecraft/world/entity/LivingEntity;")) - private LivingEntity attackImpl$onWolfGetOwner(@Coerce final Object instance) { - return ((TamableAnimal)instance).getOwner(); - } - - /** - * Prevents {@link ServerPlayer#awardStat} from running before event - */ - @Redirect(method = "getDamageAfterMagicAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V")) - public void attackImpl$onAwardStatDamageResist(final ServerPlayer instance, final ResourceLocation resourceLocation, final int i) { - // do nothing - } - - -} diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java new file mode 100644 index 00000000000..3e0a634bd9e --- /dev/null +++ b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java @@ -0,0 +1,113 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.vanilla.mixin.core.world.entity; + +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.TamableAnimal; +import net.minecraft.world.entity.animal.Wolf; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; +import org.spongepowered.common.item.util.ItemStackUtil; + +@Mixin(value = LivingEntity.class, priority = 900) +public abstract class LivingEntityMixin_Vanilla_Damage implements TrackedDamageBridge { + + @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isDamageSourceBlocked(Lnet/minecraft/world/damagesource/DamageSource;)Z")) + private boolean damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageSource source) { + if (!self.isDamageSourceBlocked(source)) { + return false; + } + + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return true; + } + + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, (float) tracker.preEvent().baseDamage(), ItemStackUtil.snapshotOf(self.getUseItem())); + step.applyModifiersBefore(); + step.applyModifiersAfter(0); + return !step.isSkipped(); + } + + @ModifyVariable(method = "hurtServer", ordinal = 2, at = @At("STORE"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtCurrentlyUsedShield(F)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;blockUsingShield(Lnet/minecraft/world/entity/LivingEntity;)V") + )) + private float damage$setBlockedDamage(final float damage) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return damage; + } + final SpongeDamageStep step = tracker.currentStep(DamageStepTypes.SHIELD); + return step == null ? damage : (float) Math.max(step.damageBeforeStep(), 0); + } + + @ModifyVariable(method = "hurtServer", at = @At("STORE"), slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;blockUsingShield(Lnet/minecraft/world/entity/LivingEntity;)V"), + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;IS_FREEZING:Lnet/minecraft/tags/TagKey;") + )) + private boolean damage$setBlockedFlag(final boolean blocked) { + final SpongeDamageTracker tracker = this.damage$tracker(); + if (tracker == null) { + return blocked; + } + final SpongeDamageStep step = tracker.currentStep(DamageStepTypes.SHIELD); + return step == null ? blocked : step.damageAfterModifiers() <= 0; + } + + @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), argsOnly = true, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getCombatTracker()Lnet/minecraft/world/damagesource/CombatTracker;"))) + private float damage$firePostEvent_Living(final float damage) { + return this.damage$firePostEvent(damage); + } + + @SuppressWarnings("InvalidInjectorMethodSignature") + @ModifyConstant(method = "resolvePlayerResponsibleForDamage", constant = @Constant(classValue = Wolf.class, ordinal = 0)) + private Class damage$onWolfCast(final Object entity, final Class wolf) { + return TamableAnimal.class; + } + + @Redirect(method = "resolvePlayerResponsibleForDamage", at = @At(value = "INVOKE" , target = "Lnet/minecraft/world/entity/animal/Wolf;isTame()Z")) + private boolean damage$onWolfIsTame(@Coerce final Object instance) { + return ((TamableAnimal) instance).isTame(); + } + + @Redirect(method = "resolvePlayerResponsibleForDamage", at = @At(value = "INVOKE" , target = "Lnet/minecraft/world/entity/animal/Wolf;getOwner()Lnet/minecraft/world/entity/LivingEntity;")) + private LivingEntity damage$onWolfGetOwner(@Coerce final Object instance) { + return ((TamableAnimal) instance).getOwner(); + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Attack_Impl.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Damage.java similarity index 62% rename from src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Attack_Impl.java rename to vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Damage.java index fb7aceaf7b3..26e6c1fb227 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Attack_Impl.java +++ b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/player/PlayerMixin_Vanilla_Damage.java @@ -22,29 +22,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.common.mixin.core.world.entity.player; +package org.spongepowered.vanilla.mixin.core.world.entity.player; import net.minecraft.world.entity.player.Player; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Slice; -import org.spongepowered.common.mixin.core.world.entity.LivingEntityMixin_Attack_Impl; +import org.spongepowered.vanilla.mixin.core.world.entity.LivingEntityMixin_Vanilla_Damage; -// Forge and Vanilla -@Mixin(value = Player.class, priority = 900) -public abstract class PlayerMixin_Shared_Attack_Impl extends LivingEntityMixin_Attack_Impl { +@Mixin(Player.class) +public abstract class PlayerMixin_Vanilla_Damage extends LivingEntityMixin_Vanilla_Damage { - /** - * Set absorbed damage after calling {@link Player#setAbsorptionAmount} in which we called the event - */ - @ModifyVariable(method = "actuallyHurt", ordinal = 2, - slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V")), - at = @At(value = "STORE", ordinal = 0)) - public float attackImpl$setAbsorbed(final float value) { - if (this.attackImpl$actuallyHurtResult.event().isCancelled()) { - return 0; - } - return this.attackImpl$actuallyHurtResult.damageAbsorbed().orElse(0f); + @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), argsOnly = true, slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V"))) + private float damage$firePostEvent_Player(final float damage) { + return this.damage$firePostEvent(damage); } } diff --git a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json index 01d3e67384b..d2775112b79 100644 --- a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json +++ b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json @@ -32,12 +32,12 @@ "tags.TagLoaderMixin_Vanilla", "world.entity.EntityMixin_Vanilla", "world.entity.EntityTypeMixin_Vanilla", - "world.entity.LivingEntityMixin_Attack_Impl", + "world.entity.LivingEntityMixin_Vanilla_Damage", "world.entity.LivingEntityMixin_Vanilla", "world.entity.ai.attributes.DefaultAttributesMixin", "world.entity.animal.SnowGolemMixin_Vanilla", "world.entity.item.ItemEntityMixin_Vanilla", - "world.entity.player.PlayerMixin_Vanilla_Attack_Impl", + "world.entity.player.PlayerMixin_Vanilla_Damage", "world.entity.vehicle.AbstractBoatMixin_Vanilla", "world.level.ServerExplosionMixin_Vanilla", "world.level.block.FireBlockMixin_Vanilla", diff --git a/vanilla/src/mixins/resources/mixins.spongevanilla.core.shared.json b/vanilla/src/mixins/resources/mixins.spongevanilla.core.shared.json index b322779182e..e23ba17fbba 100644 --- a/vanilla/src/mixins/resources/mixins.spongevanilla.core.shared.json +++ b/vanilla/src/mixins/resources/mixins.spongevanilla.core.shared.json @@ -5,8 +5,8 @@ "priority": 1301, "mixins": [ "server.level.ServerEntityMixin_Shared", - "world.entity.LivingEntityMixin_Shared_Attack_Impl", - "world.entity.player.PlayerMixin_Shared_Attack_Impl", + "world.entity.LivingEntityMixin_Shared_Damage", + "world.entity.player.PlayerMixin_Shared_Damage", "world.entity.projectile.FishingHookMixin_Shared" ], "overwrites": { From 90a7857367995914ce55842fa3e7e77eba74bff9 Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Tue, 11 Feb 2025 20:18:49 +0100 Subject: [PATCH 2/6] Damage step API improvements --- .../LivingEntityMixin_Forge_Damage.java | 6 +- .../entity/LivingEntityMixin_Neo_Damage.java | 6 +- .../entity/damage/SpongeAttackTracker.java | 30 ++- .../entity/damage/SpongeDamageModifier.java | 81 +++++++ .../cause/entity/damage/SpongeDamageStep.java | 224 +++++++++++------- .../entity/damage/SpongeDamageTracker.java | 108 ++++----- .../registry/SpongeBuilderProvider.java | 3 + .../registry/SpongeFactoryProvider.java | 3 + .../common/registry/SpongeRegistries.java | 2 +- .../registry/loader/SpongeRegistryLoader.java | 2 + .../entity/LivingEntityMixin_Damage.java | 8 +- .../entity/player/PlayerMixin_Attack.java | 18 +- .../enchantment/EnchantmentMixin_Attack.java | 6 +- .../spongepowered/test/damage/DamageTest.java | 81 ++++++- .../LivingEntityMixin_Vanilla_Damage.java | 10 +- 15 files changed, 404 insertions(+), 184 deletions(-) create mode 100644 src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java index 3053fac4c92..cb083e764ea 100644 --- a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java +++ b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java @@ -59,8 +59,8 @@ public abstract class LivingEntityMixin_Forge_Damage implements TrackedDamageBri return ForgeEventFactory.onShieldBlock(self, source, originalDamage); } - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, originalDamage, ItemStackUtil.snapshotOf(self.getUseItem())); - float damage = (float) step.applyModifiersBefore(); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, ItemStackUtil.snapshotOf(self.getUseItem())); + float damage = (float) step.applyChildrenBefore(originalDamage); final ShieldBlockEvent event; if (step.isSkipped()) { event = new ShieldBlockEvent(self, source, damage); @@ -71,7 +71,7 @@ public abstract class LivingEntityMixin_Forge_Damage implements TrackedDamageBri damage -= event.getBlockedDamage(); } } - step.applyModifiersAfter(damage); + step.applyChildrenAfter(damage); return event; } diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java index 34bdc2a842d..603730e6801 100644 --- a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java +++ b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java @@ -66,8 +66,8 @@ public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridg } final float originalDamage = container.getNewDamage(); - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, originalDamage, ItemStackUtil.snapshotOf(self.getUseItem())); - float damage = (float) step.applyModifiersBefore(); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, ItemStackUtil.snapshotOf(self.getUseItem())); + float damage = (float) step.applyChildrenBefore(originalDamage); container.setNewDamage(damage); final LivingShieldBlockEvent event; if (step.isSkipped()) { @@ -78,7 +78,7 @@ public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridg container.setBlockedDamage(event); damage = container.getNewDamage(); } - step.applyModifiersAfter(damage); + step.applyChildrenAfter(damage); return event; } diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java index 6fc811873cb..820fbcd3a13 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeAttackTracker.java @@ -30,7 +30,7 @@ import org.spongepowered.api.entity.Entity; import org.spongepowered.api.event.CauseStackManager; import org.spongepowered.api.event.SpongeEventFactory; -import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.api.event.entity.AttackEntityEvent; import org.spongepowered.api.event.entity.DamageCalculationEvent; import org.spongepowered.api.item.inventory.ItemStackSnapshot; @@ -39,8 +39,6 @@ import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.item.util.ItemStackUtil; -import java.util.List; - public class SpongeAttackTracker extends SpongeDamageTracker { private final ItemStack weapon; private final ItemStackSnapshot weaponSnapshot; @@ -48,8 +46,8 @@ public class SpongeAttackTracker extends SpongeDamageTracker { private float attackStrength; private boolean strongSprint = false; - public SpongeAttackTracker(final DamageCalculationEvent.Pre preEvent, final ItemStack weapon) { - super(preEvent); + public SpongeAttackTracker(final DamageCalculationEvent.Pre preEvent, final DamageSource source, final ItemStack weapon) { + super(preEvent, source); this.weapon = weapon; this.weaponSnapshot = ItemStackUtil.snapshotOf(weapon); } @@ -88,14 +86,18 @@ public void setStrongSprint(final boolean strongSprint) { this.strongSprint = strongSprint; } - public boolean callAttackPostEvent(final Entity entity, final DamageSource source, final float finalDamage, final float knockbackModifier) { - final List steps = this.preparePostEvent(); + public boolean callAttackPostEvent(final Entity entity, final DamageSource source, float finalDamage, final float knockbackModifier) { + if (this.postEvent != null) { + throw new IllegalStateException("Post event already fired"); + } + + finalDamage = (float) this.newStep(DamageStepTypes.END).apply(finalDamage); - try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { SpongeDamageTracker.generateCauseFor(source, frame); final AttackEntityEvent.Post event = SpongeEventFactory.createAttackEntityEventPost(frame.currentCause(), - this.preEvent.originalBaseDamage(), this.preEvent.baseDamage(), finalDamage, finalDamage, knockbackModifier, knockbackModifier, entity, steps); + knockbackModifier, knockbackModifier, entity, this, this.preEvent.baseDamage(), finalDamage); this.postEvent = event; return SpongeCommon.post(event); @@ -103,16 +105,20 @@ public boolean callAttackPostEvent(final Entity entity, final DamageSource sourc } public static @Nullable SpongeAttackTracker callAttackPreEvent(final Entity entity, final DamageSource source, final float baseDamage, final ItemStack weapon) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + final SpongeAttackTracker tracker; + try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { SpongeDamageTracker.generateCauseFor(source, frame); - final AttackEntityEvent.Pre event = SpongeEventFactory.createAttackEntityEventPre(frame.currentCause(), baseDamage, baseDamage, entity); + final AttackEntityEvent.Pre event = SpongeEventFactory.createAttackEntityEventPre(frame.currentCause(), entity, baseDamage); if (SpongeCommon.post(event)) { return null; } - return new SpongeAttackTracker(event, weapon); + tracker = new SpongeAttackTracker(event, source, weapon); } + + tracker.newStep(DamageStepTypes.START).apply(baseDamage); + return tracker; } public static @Nullable SpongeAttackTracker of(final DamageSource source) { diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java new file mode 100644 index 00000000000..9458aa7c365 --- /dev/null +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java @@ -0,0 +1,81 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.event.cause.entity.damage; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.event.cause.entity.damage.DamageModifier; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +public record SpongeDamageModifier(DamageStepType type, Optional> frame, Optional damage) implements DamageModifier { + + public static class Builder implements DamageModifier.Builder { + private @Nullable DamageStepType type; + private @Nullable Consumer frame; + private Function function; + + public Builder() { + this.reset(); + } + + @Override + public DamageModifier.Builder reset() { + this.type = null; + this.frame = null; + this.function = (step, damage) -> damage; + return this; + } + + @Override + public DamageModifier.Builder type(DamageStepType type) { + this.type = Objects.requireNonNull(type, "type"); + return this; + } + + @Override + public DamageModifier.Builder frame(Consumer frame) { + this.frame = Objects.requireNonNull(frame, "frame"); + return this; + } + + @Override + public DamageModifier.Builder damage(Function function) { + this.function = Objects.requireNonNull(function, "function"); + return this; + } + + @Override + public DamageModifier build() { + if (this.type == null) { + throw new IllegalStateException("type must be set"); + } + return new SpongeDamageModifier(this.type, Optional.ofNullable(this.frame), Optional.ofNullable(this.function)); + } + } +} diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java index a5a912b4d30..4394ca58d05 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java @@ -27,37 +27,112 @@ import com.google.common.collect.ImmutableList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.event.Cause; +import org.spongepowered.api.event.CauseStackManager; import org.spongepowered.api.event.cause.entity.damage.DamageModifier; import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.cause.entity.damage.DamageStepHistory; import org.spongepowered.api.event.cause.entity.damage.DamageStepType; +import org.spongepowered.common.event.tracking.PhaseTracker; +import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.Set; import java.util.StringJoiner; +import java.util.function.Consumer; public final class SpongeDamageStep implements DamageStep { private static final Logger LOGGER = LogManager.getLogger(); + private final SpongeDamageTracker tracker; private final DamageStepType type; - private final Cause cause; - private final List modifiersBefore; - private final List modifiersAfter; + private final @Nullable SpongeDamageStep parent; + private final @Nullable Consumer frameModifier; + private final DamageModifier.@Nullable Function damageFunction; - private State state = State.BEFORE; + private Cause cause; private boolean skipped; - private final double damageBeforeModifiers; - private double damageBeforeStep; - private double damageAfterStep; - private double damageAfterModifiers; + private OptionalDouble damageBeforeChildren = OptionalDouble.empty(); + private OptionalDouble damageBeforeSelf = OptionalDouble.empty(); + private OptionalDouble damageAfterSelf = OptionalDouble.empty(); + private OptionalDouble damageAfterChildren = OptionalDouble.empty(); - public SpongeDamageStep(final DamageStepType type, final double initialDamage, final Cause cause, - final List modifiersBefore, final List modifiersAfter) { + private List childrenBefore; + private List childrenAfter; + + public SpongeDamageStep(final SpongeDamageTracker tracker, final DamageStepType type, final Object[] causes) { + this.tracker = tracker; + this.parent = null; this.type = type; - this.cause = cause; - this.damageBeforeModifiers = initialDamage; - this.modifiersBefore = ImmutableList.copyOf(modifiersBefore); - this.modifiersAfter = ImmutableList.copyOf(modifiersAfter); + this.frameModifier = (frame) -> { + SpongeDamageTracker.generateCauseFor(tracker.source, frame); + for (Object cause : causes) { + frame.pushCause(cause); + } + }; + this.damageFunction = null; + } + + public SpongeDamageStep(final SpongeDamageStep parent, final DamageModifier modifier) { + this.tracker = parent.tracker; + this.parent = parent; + this.type = modifier.type(); + this.frameModifier = modifier.frame().orElse(null); + this.damageFunction = modifier.damage().orElse(null); + } + + void populateChildren() { + this.populateChildren(new HashSet<>()); + } + + private void populateChildren(final Set parentTypes) { + parentTypes.add(this.type); + + final ImmutableList.Builder before = ImmutableList.builder(); + for (final DamageModifier modifier : this.tracker.preEvent().modifiersBefore(this.type)) { + if (parentTypes.contains(modifier.type())) { + LOGGER.warn("Modifier {} is supposed to be a child before step {} but this would cause a cycle so it will be ignored.", modifier, this); + } else { + before.add(new SpongeDamageStep(this, modifier)); + } + } + this.childrenBefore = before.build(); + + final ImmutableList.Builder after = ImmutableList.builder(); + for (final DamageModifier modifier : this.tracker.preEvent().modifiersAfter(this.type)) { + if (parentTypes.contains(modifier.type())) { + LOGGER.warn("Modifier {} is supposed to be a child after step {} but this would cause a cycle so it will be ignored.", modifier, this); + } else { + after.add(new SpongeDamageStep(this, modifier)); + } + } + this.childrenAfter = after.build(); + + final PhaseTracker phaseTracker = PhaseTracker.getInstance(); + try (final CauseStackManager.StackFrame frame = this.frameModifier == null ? null : phaseTracker.pushCauseFrame()) { + if (frame != null) { + try { + this.frameModifier.accept(frame); + } catch (final Throwable t) { + LOGGER.error("Failed to apply frame modifier of step {}", this, t); + } + } + + this.cause = phaseTracker.currentCause(); + + for (final SpongeDamageStep child : this.childrenBefore) { + child.populateChildren(parentTypes); + } + for (final SpongeDamageStep child : this.childrenAfter) { + child.populateChildren(parentTypes); + } + } + + parentTypes.remove(this.type); } @Override @@ -77,123 +152,110 @@ public boolean isSkipped() { @Override public void setSkipped(boolean skipped) { - if (this.state != State.BEFORE) { + if (this.damageAfterSelf.isPresent()) { throw new IllegalStateException("Step can only be skipped before occurring"); } this.skipped = skipped; } @Override - public double damageBeforeModifiers() { - return this.damageBeforeModifiers; + public OptionalDouble damageBeforeChildren() { + return this.damageBeforeChildren; } @Override - public double damageBeforeStep() { - if (this.state == State.BEFORE) { - throw new IllegalStateException("Before modifiers haven't finished"); - } - return this.damageBeforeStep; + public OptionalDouble damageBeforeSelf() { + return this.damageBeforeSelf; } @Override - public double damageAfterStep() { - if (this.state == State.BEFORE) { - throw new IllegalStateException("Step hasn't started"); - } - if (this.state == State.STEP) { - throw new IllegalStateException("Step hasn't finished"); - } - return this.damageAfterStep; + public OptionalDouble damageAfterSelf() { + return this.damageAfterSelf; } @Override - public double damageAfterModifiers() { - if (this.state != State.END) { - throw new IllegalStateException("Modifiers haven't finished"); - } - return this.damageAfterModifiers; + public OptionalDouble damageAfterChildren() { + return this.damageAfterChildren; } @Override - public List modifiersBefore() { - return this.modifiersBefore; + public DamageStepHistory history() { + return this.tracker; } @Override - public List modifiersAfter() { - return this.modifiersAfter; + public Optional parent() { + return Optional.ofNullable(this.parent); } - public State state() { - return this.state; + @Override + public List childrenBefore() { + return (List) this.childrenBefore; + } + + @Override + public List childrenAfter() { + return (List) this.childrenAfter; } @Override public String toString() { + // don't add the parent to avoid infinite recursion return new StringJoiner(", ", SpongeDamageStep.class.getSimpleName() + "[", "]") .add("type=" + this.type) .add("cause=" + this.cause) - .add("damageBeforeModifiers=" + this.damageBeforeModifiers) - .add("damageBeforeStep=" + this.damageBeforeStep) - .add("damageAfterStep=" + this.damageAfterStep) - .add("damageAfterModifiers=" + this.damageAfterModifiers) - .add("modifiersBefore=" + this.modifiersBefore) - .add("modifiersAfter=" + this.modifiersAfter) - .add("state=" + this.state) + .add("frameModifier=" + this.frameModifier) + .add("damageFunction=" + this.damageFunction) + .add("skipped=" + this.skipped) + .add("damageBeforeChildren=" + this.damageBeforeChildren) + .add("damageBeforeSelf=" + this.damageBeforeSelf) + .add("damageAfterSelf=" + this.damageAfterSelf) + .add("damageAfterChildren=" + this.damageAfterChildren) + .add("childrenBefore=" + this.childrenBefore) + .add("childrenAfter=" + this.childrenAfter) .toString(); } - public double applyModifiersBefore() { - if (this.state != State.BEFORE) { + public double applyChildrenBefore(double damage) { + if (this.damageBeforeChildren.isPresent()) { throw new IllegalStateException(); } - double damage = this.damageBeforeModifiers; - for (DamageModifier modifier : this.modifiersBefore) { - try { - damage = modifier.modify(this, damage); - } catch (Exception e) { - LOGGER.error("Failed to apply modifier {} before step {}", modifier, this, e); - } + this.damageBeforeChildren = OptionalDouble.of(damage); + for (final SpongeDamageStep child : this.childrenBefore) { + damage = child.apply(damage); } - - this.damageBeforeStep = damage; - this.state = State.STEP; - + this.damageBeforeSelf = OptionalDouble.of(damage); return damage; } - public double applyModifiersAfter(double damage) { - if (this.state != State.STEP) { + public double applyChildrenAfter(double damage) { + if (this.damageAfterSelf.isPresent() || this.damageBeforeSelf.isEmpty()) { throw new IllegalStateException(); } if (this.skipped) { - damage = this.damageBeforeStep; + damage = this.damageBeforeSelf.getAsDouble(); } - this.damageAfterStep = damage; - this.state = State.AFTER; + this.damageAfterSelf = OptionalDouble.of(damage); + for (final SpongeDamageStep child : this.childrenAfter) { + damage = child.apply(damage); + } + this.damageAfterChildren = OptionalDouble.of(damage); + return damage; + } - for (DamageModifier modifier : this.modifiersBefore) { + public double apply(double damage) { + damage = this.applyChildrenBefore(damage); + if (!this.skipped && this.damageFunction != null) { try { - damage = modifier.modify(this, damage); - } catch (Exception e) { - LOGGER.error("Failed to apply modifier {} after step {}", modifier, this, e); + damage = this.damageFunction.modify(this, damage); + } catch (final Throwable t) { + LOGGER.error("Failed to apply damage function of step {}", this, t); } } - - this.damageAfterModifiers = damage; - this.state = State.END; - + damage = this.applyChildrenAfter(damage); return damage; } - - public enum State { - BEFORE, - STEP, - AFTER, - END - } } diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java index 8a363caf2ce..769deaf458c 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java @@ -24,7 +24,6 @@ */ package org.spongepowered.common.event.cause.entity.damage; -import com.google.common.collect.ImmutableList; import net.minecraft.core.BlockPos; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.player.Player; @@ -32,13 +31,13 @@ import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.entity.Entity; -import org.spongepowered.api.event.Cause; import org.spongepowered.api.event.CauseStackManager; -import org.spongepowered.api.event.EventContext; import org.spongepowered.api.event.EventContextKeys; import org.spongepowered.api.event.SpongeEventFactory; import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.cause.entity.damage.DamageStepHistory; import org.spongepowered.api.event.cause.entity.damage.DamageStepType; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.api.event.entity.DamageCalculationEvent; import org.spongepowered.api.event.entity.DamageEntityEvent; import org.spongepowered.api.registry.DefaultedRegistryReference; @@ -51,17 +50,25 @@ import org.spongepowered.common.util.VecHelper; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -public class SpongeDamageTracker { +public class SpongeDamageTracker implements DamageStepHistory { private static final Logger LOGGER = LogManager.getLogger(); - protected final List steps = new ArrayList<>(); + private final List rootSteps = new ArrayList<>(); protected final DamageCalculationEvent.Pre preEvent; + protected final DamageSource source; protected DamageCalculationEvent.Post postEvent; - public SpongeDamageTracker(final DamageCalculationEvent.Pre preEvent) { + public SpongeDamageTracker(final DamageCalculationEvent.Pre preEvent, final DamageSource source) { this.preEvent = preEvent; + this.source = source; + } + + @Override + public List rootSteps() { + return Collections.unmodifiableList(this.rootSteps); } public DamageCalculationEvent.Pre preEvent() { @@ -75,40 +82,41 @@ public DamageCalculationEvent.Post postEvent() { return this.postEvent; } - public SpongeDamageStep newStep(final DefaultedRegistryReference typeRef, final float damage, final Object... causes) { + public SpongeDamageStep newStep(final DefaultedRegistryReference typeRef, final Object... causes) { final DamageStepType type = typeRef.get(); - final SpongeDamageStep step = new SpongeDamageStep(type, damage, Cause.of(EventContext.empty(), List.of(causes)), this.preEvent.modifiersBefore(type), this.preEvent.modifiersAfter(type)); + final SpongeDamageStep step = new SpongeDamageStep(this, type, causes); + step.populateChildren(); if (this.postEvent != null) { - LOGGER.warn("A new step {} is being captured after the post event.", step); + LOGGER.warn("A new root step {} is being captured after the post event.", step); } - if (!this.steps.isEmpty()) { - final SpongeDamageStep previous = this.steps.getLast(); - if (previous.state() != SpongeDamageStep.State.END) { - LOGGER.warn("A new step {} is being captured but previous step {} hasn't finished.", step, previous); - this.steps.removeLast(); + if (!this.rootSteps.isEmpty()) { + final SpongeDamageStep previous = this.rootSteps.getLast(); + if (previous.damageAfterChildren().isEmpty()) { + LOGGER.warn("A new root step {} is being captured but previous root step {} hasn't finished.", step, previous); + this.rootSteps.removeLast(); } } - this.steps.add(step); + this.rootSteps.add(step); return step; } public float startStep(final DefaultedRegistryReference typeRef, final float damage, final Object... causes) { - return (float) this.newStep(typeRef, damage, causes).applyModifiersBefore(); + return (float) this.newStep(typeRef, causes).applyChildrenBefore(damage); } public @Nullable SpongeDamageStep currentStep(final DefaultedRegistryReference typeRef) { - if (this.steps.isEmpty()) { - LOGGER.warn("Expected a current step of type {} but no step has been captured yet.", typeRef.location()); + if (this.rootSteps.isEmpty()) { + LOGGER.warn("Expected a current root step of type {} but no step has been captured yet.", typeRef.location()); return null; } final DamageStepType type = typeRef.get(); - final SpongeDamageStep step = this.steps.getLast(); + final SpongeDamageStep step = this.rootSteps.getLast(); if (step.type() != type) { - LOGGER.warn("Expected a current step of type {} but got {}.", type, step); + LOGGER.warn("Expected a current root step of type {} but got {}.", type, step); return null; } return step; @@ -116,7 +124,7 @@ public float startStep(final DefaultedRegistryReference typeRef, public float endStep(final DefaultedRegistryReference typeRef, final float damage) { final SpongeDamageStep step = this.currentStep(typeRef); - return step == null ? damage : (float) step.applyModifiersAfter(damage); + return step == null ? damage : (float) step.applyChildrenAfter(damage); } public boolean isSkipped(final DefaultedRegistryReference typeRef) { @@ -126,8 +134,8 @@ public boolean isSkipped(final DefaultedRegistryReference typeRe public @Nullable SpongeDamageStep lastStep(final DefaultedRegistryReference typeRef) { final DamageStepType type = typeRef.get(); - for (int i = this.steps.size() - 1; i >= 0; i--) { - final SpongeDamageStep step = this.steps.get(i); + for (int i = this.rootSteps.size() - 1; i >= 0; i--) { + final SpongeDamageStep step = this.rootSteps.get(i); if (step.type() == type) { return step; } @@ -137,33 +145,21 @@ public boolean isSkipped(final DefaultedRegistryReference typeRe public float damageAfter(final DefaultedRegistryReference typeRef) { final SpongeDamageStep step = this.lastStep(typeRef); - return step == null ? 0 : (float) step.damageAfterModifiers(); + return step == null ? 0 : (float) step.damageAfterChildren().orElse(0); } - protected List preparePostEvent() { + public float callDamagePostEvent(final Entity entity, float finalDamage) { if (this.postEvent != null) { throw new IllegalStateException("Post event already fired"); } - if (!this.steps.isEmpty()) { - final SpongeDamageStep last = this.steps.getLast(); - if (last.state() != SpongeDamageStep.State.END) { - LOGGER.warn("Calling post event but last step {} hasn't finished.", last); - return ImmutableList.copyOf(this.steps.subList(0, this.steps.size() - 1)); - } - } + finalDamage = (float) this.newStep(DamageStepTypes.END).apply(finalDamage); - return ImmutableList.copyOf(this.steps); - } - - public float callDamagePostEvent(final Entity entity, final float finalDamage) { - final List steps = this.preparePostEvent(); - - try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { SpongeDamageTracker.generateCauseFor((DamageSource) this.preEvent.source(), frame); final DamageEntityEvent.Post event = SpongeEventFactory.createDamageEntityEventPost(frame.currentCause(), - this.preEvent.originalBaseDamage(), this.preEvent.baseDamage(), finalDamage, finalDamage, entity, steps); + entity, this, this.preEvent.baseDamage(), finalDamage); this.postEvent = event; if (SpongeCommon.post(event)) { @@ -190,34 +186,30 @@ protected static void generateCauseFor(final DamageSource source, final CauseSta } public static @Nullable SpongeDamageTracker callDamagePreEvent(final Entity entity, final DamageSource source, final float baseDamage) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { + final SpongeDamageTracker tracker; + try (final CauseStackManager.StackFrame frame = PhaseTracker.getInstance().pushCauseFrame()) { SpongeDamageTracker.generateCauseFor(source, frame); - final DamageEntityEvent.Pre event = SpongeEventFactory.createDamageEntityEventPre(frame.currentCause(), baseDamage, baseDamage, entity); + final DamageEntityEvent.Pre event = SpongeEventFactory.createDamageEntityEventPre(frame.currentCause(), entity, baseDamage); if (SpongeCommon.post(event)) { return null; } - return new SpongeDamageTracker(event); + tracker = new SpongeDamageTracker(event, source); } + + tracker.newStep(DamageStepTypes.START).apply(baseDamage); + return tracker; } public static DamageEntityEvent.@Nullable Post callDamageEvents(final Entity entity, final DamageSource source, final float baseDamage) { - try (final CauseStackManager.StackFrame frame = PhaseTracker.getCauseStackManager().pushCauseFrame()) { - SpongeDamageTracker.generateCauseFor(source, frame); - - final DamageEntityEvent.Pre preEvent = SpongeEventFactory.createDamageEntityEventPre(frame.currentCause(), baseDamage, baseDamage, entity); - if (SpongeCommon.post(preEvent)) { - return null; - } - - final DamageEntityEvent.Post postEvent = SpongeEventFactory.createDamageEntityEventPost(frame.currentCause(), - preEvent.originalBaseDamage(), preEvent.baseDamage(), preEvent.baseDamage(), preEvent.baseDamage(), entity, List.of()); - if (SpongeCommon.post(postEvent)) { - return null; - } - - return postEvent; + final @Nullable SpongeDamageTracker tracker = SpongeDamageTracker.callDamagePreEvent(entity, source, baseDamage); + if (tracker == null) { + return null; } + final DamageStep step = tracker.currentStep(DamageStepTypes.START); + final float finalDamage = step == null ? baseDamage : (float) step.damageAfterChildren().orElse(baseDamage); + tracker.callDamagePostEvent(entity, finalDamage); + return (DamageEntityEvent.Post) tracker.postEvent; } } diff --git a/src/main/java/org/spongepowered/common/registry/SpongeBuilderProvider.java b/src/main/java/org/spongepowered/common/registry/SpongeBuilderProvider.java index 5694031f9ad..821ca0dcb09 100644 --- a/src/main/java/org/spongepowered/common/registry/SpongeBuilderProvider.java +++ b/src/main/java/org/spongepowered/common/registry/SpongeBuilderProvider.java @@ -68,6 +68,7 @@ import org.spongepowered.api.entity.attribute.AttributeModifier; import org.spongepowered.api.entity.living.player.tab.TabListEntry; import org.spongepowered.api.event.EventContextKey; +import org.spongepowered.api.event.cause.entity.damage.DamageModifier; import org.spongepowered.api.event.cause.entity.damage.DamageType; import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; import org.spongepowered.api.fluid.FluidStack; @@ -179,6 +180,7 @@ import org.spongepowered.common.entity.attribute.SpongeAttributeModifierBuilder; import org.spongepowered.common.entity.player.tab.TabListEntryBuilder; import org.spongepowered.common.event.SpongeEventContextKeyBuilder; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageModifier; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageSourceBuilder; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTypeBuilder; import org.spongepowered.common.fluid.SpongeFluidStackBuilder; @@ -292,6 +294,7 @@ public void registerDefaultBuilders() { .register(Team.Builder.class, SpongeTeamBuilder::new) .register(Scoreboard.Builder.class, SpongeScoreboardBuilder::new) .register(DamageSource.Builder.class, SpongeDamageSourceBuilder::new) + .register(DamageModifier.Builder.class, SpongeDamageModifier.Builder::new) .register(Explosion.Builder.class, SpongeExplosionBuilder::new) .register(BlockState.Builder.class, SpongeBlockStateBuilder::new) .register(BlockSnapshot.Builder.class, SpongeBlockSnapshot.BuilderImpl::unpooled) diff --git a/src/main/java/org/spongepowered/common/registry/SpongeFactoryProvider.java b/src/main/java/org/spongepowered/common/registry/SpongeFactoryProvider.java index 8891c2041ad..201ebe38af7 100644 --- a/src/main/java/org/spongepowered/common/registry/SpongeFactoryProvider.java +++ b/src/main/java/org/spongepowered/common/registry/SpongeFactoryProvider.java @@ -46,6 +46,7 @@ import org.spongepowered.api.effect.ForwardingViewer; import org.spongepowered.api.effect.VanishState; import org.spongepowered.api.event.EventListenerRegistration; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; import org.spongepowered.api.item.inventory.ItemStack; import org.spongepowered.api.item.inventory.ItemStackComparators; @@ -123,6 +124,7 @@ import org.spongepowered.common.effect.SpongeCustomForwardingViewer; import org.spongepowered.common.entity.effect.SpongeVanishState; import org.spongepowered.common.event.SpongeEventListenerRegistration; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStepType; import org.spongepowered.common.event.tracking.BlockChangeFlagManager; import org.spongepowered.common.item.SpongeItemStack; import org.spongepowered.common.item.SpongeItemStackSnapshot; @@ -244,6 +246,7 @@ public void registerDefaultFactories() { .registerFactory(RegistryReference.Factory.class, new SpongeRegistryReference.FactoryImpl()) .registerFactory(BlockVolumeFactory.class, new SpongeBlockVolumeFactory()) .registerFactory(DamageSource.Factory.class, new SpongeDamageSourceFactory()) + .registerFactory(DamageStepType.Factory.class, SpongeDamageStepType::new) .registerFactory(PaletteReference.Factory.class, new SpongePaletteReferenceFactory()) .registerFactory(EntityArchetypeEntry.Factory.class, new SpongeEntityArchetypeEntryFactory()) .registerFactory(ServerLocationCreator.Factory.class, new SpongeServerLocationCreatorFactory()) diff --git a/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java b/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java index c044c343400..29ba343d72a 100644 --- a/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java +++ b/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java @@ -63,7 +63,6 @@ public static void registerEarlyGlobalRegistries(final SpongeRegistryHolder hold holder.createFrozenRegistry(RegistryTypes.BODY_PART, SpongeRegistryLoader.bodyPart()); holder.createFrozenRegistry(RegistryTypes.CLICK_TYPE, SpongeRegistryLoader.clickType()); holder.createFrozenRegistry(RegistryTypes.CHUNK_REGENERATE_FLAG, SpongeRegistryLoader.chunkRegenerateFlag()); - holder.createFrozenRegistry(RegistryTypes.DAMAGE_STEP_TYPE, SpongeRegistryLoader.damageStepType()); holder.createFrozenRegistry(RegistryTypes.DISMOUNT_TYPE, SpongeRegistryLoader.dismountType()); holder.createFrozenRegistry(RegistryTypes.GOAL_EXECUTOR_TYPE, SpongeRegistryLoader.goalExecutorType()); holder.createFrozenRegistry(RegistryTypes.GOAL_TYPE, SpongeRegistryLoader.goalType()); @@ -95,6 +94,7 @@ private static void registerEarlyDynamicRegistries(final SpongeRegistryHolder ho holder.createRegistry(RegistryTypes.COMMAND_REGISTRAR_TYPE, CommandRegistryLoader.commandRegistrarType(), true); holder.createRegistry(RegistryTypes.PLACEHOLDER_PARSER, DynamicSpongeRegistryLoader.placeholderParser(), true); holder.createRegistry(RegistryTypes.TELEPORT_HELPER_FILTER, DynamicSpongeRegistryLoader.teleportHelperFilter(), true); + holder.createRegistry(RegistryTypes.DAMAGE_STEP_TYPE, SpongeRegistryLoader.damageStepType(), true); } @SuppressWarnings({"unchecked", "rawtypes"}) diff --git a/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java b/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java index d6dd0c8a252..b8f9f664823 100644 --- a/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java +++ b/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java @@ -228,12 +228,14 @@ public static RegistryLoader damageStepType() { DamageStepTypes.CRITICAL_HIT, DamageStepTypes.DEFENSIVE_POTION_EFFECT, DamageStepTypes.ENCHANTMENT_COOLDOWN, + DamageStepTypes.END, DamageStepTypes.FREEZING_BONUS, DamageStepTypes.HARD_HAT, DamageStepTypes.MAGIC, DamageStepTypes.NEGATIVE_POTION_EFFECT, DamageStepTypes.OFFENSIVE_POTION_EFFECT, DamageStepTypes.SHIELD, + DamageStepTypes.START, DamageStepTypes.SWEEPING, DamageStepTypes.WEAPON_BONUS, DamageStepTypes.WEAPON_ENCHANTMENT diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java index 99f8bd35fd4..4956e0836a3 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java @@ -108,7 +108,7 @@ public abstract class LivingEntityMixin_Damage extends EntityMixin implements Li return damage; } final SpongeDamageStep step = tracker.currentStep(DamageStepTypes.SHIELD); - return step == null ? damage : (float) step.damageAfterModifiers(); + return step == null ? damage : (float) step.damageAfterChildren().getAsDouble(); } @ModifyVariable(method = "hurtServer", at = @At("LOAD"), argsOnly = true, slice = @Slice( @@ -195,12 +195,12 @@ public abstract class LivingEntityMixin_Damage extends EntityMixin implements Li return CombatRules.getDamageAfterMagicAbsorb(damage, protection); } - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.ARMOR_ENCHANTMENT, damage, this); - damage = (float) step.applyModifiersBefore(); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.ARMOR_ENCHANTMENT, this); + damage = (float) step.applyChildrenBefore(damage); if (!step.isSkipped()) { damage = CombatRules.getDamageAfterMagicAbsorb(damage, protection); } - return (float) step.applyModifiersAfter(damage); + return (float) step.applyChildrenAfter(damage); } @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;playHurtSound(Lnet/minecraft/world/damagesource/DamageSource;)V")) diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java index 1c4310f3cbd..adb9799fa76 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java @@ -155,12 +155,12 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem return item.getAttackDamageBonus(target, originalDamage, source); } - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_BONUS, originalDamage, tracker.weaponSnapshot()); - float damage = (float) step.applyModifiersBefore(); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_BONUS, tracker.weaponSnapshot()); + float damage = (float) step.applyChildrenBefore(originalDamage); if (!step.isSkipped()) { damage += item.getAttackDamageBonus(target, damage, source); } - return (float) step.applyModifiersAfter(damage) - originalDamage; + return (float) step.applyChildrenAfter(damage) - originalDamage; } @ModifyVariable(method = "attack", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, slice = @Slice( @@ -249,21 +249,21 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem damage = (float) sweepTracker.preEvent().baseDamage(); // In vanilla, this step is outside the loop, but we move it to here so it can be modified per target - SpongeDamageStep step = sweepTracker.newStep(DamageStepTypes.SWEEPING, damage, sweepTracker.weaponSnapshot()); - damage = (float) step.applyModifiersBefore(); + SpongeDamageStep step = sweepTracker.newStep(DamageStepTypes.SWEEPING, sweepTracker.weaponSnapshot()); + damage = (float) step.applyChildrenBefore(damage); if (!step.isSkipped()) { damage = 1.0F + (float) this.shadow$getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * damage; } - damage = (float) step.applyModifiersAfter(damage); + damage = (float) step.applyChildrenAfter(damage); damage = this.shadow$getEnchantedDamage(sweepTarget, damage, source); - step = sweepTracker.newStep(DamageStepTypes.ENCHANTMENT_COOLDOWN, damage, sweepTracker.weaponSnapshot()); - damage = (float) step.applyModifiersBefore(); + step = sweepTracker.newStep(DamageStepTypes.ENCHANTMENT_COOLDOWN, sweepTracker.weaponSnapshot()); + damage = (float) step.applyChildrenBefore(damage); if (!step.isSkipped()) { damage *= mainTracker.attackStrength(); } - damage = (float) step.applyModifiersAfter(damage); + damage = (float) step.applyChildrenAfter(damage); if (sweepTracker.callAttackPostEvent((org.spongepowered.api.entity.Entity) sweepTarget, source, damage, 0.4F)) { this.attack$trackers.removeLast(); diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java index ee4f6589349..dd01f14e2c1 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java @@ -62,11 +62,11 @@ public abstract class EnchantmentMixin_Attack { return; } - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_ENCHANTMENT, damage.floatValue(), ItemStackUtil.snapshotOf(weapon), self); - damage.setValue((float) step.applyModifiersBefore()); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_ENCHANTMENT, ItemStackUtil.snapshotOf(weapon), self); + damage.setValue((float) step.applyChildrenBefore(damage.floatValue())); if (!step.isSkipped()) { this.shadow$modifyDamageFilteredValue(component, level, enchantmentLevel, weapon, target, source, damage); } - damage.setValue((float) step.applyModifiersAfter(damage.floatValue())); + damage.setValue((float) step.applyChildrenAfter(damage.floatValue())); } } diff --git a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java index f08ac01bd8a..1659546f1e9 100644 --- a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java @@ -28,8 +28,12 @@ import com.google.inject.Inject; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.spongepowered.api.ResourceKey; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.Command; @@ -37,8 +41,11 @@ import org.spongepowered.api.command.parameter.CommandContext; import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.cause.entity.damage.DamageModifier; import org.spongepowered.api.event.cause.entity.damage.DamageScalings; import org.spongepowered.api.event.cause.entity.damage.DamageStep; +import org.spongepowered.api.event.cause.entity.damage.DamageStepType; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.api.event.cause.entity.damage.DamageType; import org.spongepowered.api.event.cause.entity.damage.DamageTypes; import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; @@ -59,12 +66,18 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import java.util.OptionalDouble; @Plugin("damagetest") public class DamageTest implements LoadableModule { private static final ResourceKey EXHAUSTING_DAMAGE = ResourceKey.of("damagetest", "test"); + private static final DamageStepType DOUBLE_CRITICAL = DamageStepType.create(); + private static final DamageStepType DOUBLE_DOUBLE_CRITICAL = DamageStepType.create(); + class Test { static final class Registry { @@ -123,13 +136,40 @@ private void onRegisterTagEvent(final RegisterTagEvent event) { event.tag(DamageTypeTags.BYPASSES_INVULNERABILITY).append(RegistryKey.of(RegistryTypes.DAMAGE_TYPE, EXHAUSTING_DAMAGE)); } + @Listener + public void onRegisterDamageStepType(final RegisterRegistryValueEvent.GameScoped event) { + event.registry(RegistryTypes.DAMAGE_STEP_TYPE, (r) -> { + r.register(ResourceKey.of(this.plugin, "double_critical"), DOUBLE_CRITICAL); + r.register(ResourceKey.of(this.plugin, "double_double_critical"), DOUBLE_DOUBLE_CRITICAL); + }); + } + + private static class CustomCause {} + private static class DamageListener { private static final DecimalFormat decimalFormat = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ROOT)); + private static String format(final OptionalDouble value) { + return DamageListener.format(value.orElse(Double.NaN)); + } + private static String format(final double value) { return DamageListener.decimalFormat.format(value); } + @Listener + private void onAttackPre(final AttackEntityEvent.Pre event) { + event.modifiersBefore(DamageStepTypes.CRITICAL_HIT) + .add(DamageModifier.builder().type(DOUBLE_CRITICAL) + .frame((frame) -> frame.pushCause(new CustomCause())) + .damage((step, damage) -> { + step.parent().get().skip(); + return damage * 2; + }).build()); + event.modifiersAfter(DOUBLE_CRITICAL) + .add(DamageModifier.builder().type(DOUBLE_DOUBLE_CRITICAL).damage((step, damage) -> damage * 2).build()); + } + @Listener private void onDamagePre(final DamageCalculationEvent.Pre event, @Root final DamageSource damageSource) { final Component eventName = event instanceof AttackEntityEvent ? @@ -138,7 +178,7 @@ private void onDamagePre(final DamageCalculationEvent.Pre event, @Root final Dam final Audience audience = Sponge.server(); audience.sendMessage(text().content("-------------").append(eventName, text(".Pre", NamedTextColor.YELLOW), text("---------------"))); audience.sendMessage(text().content(damageSource.type().key(RegistryTypes.DAMAGE_TYPE).value()) - .color(NamedTextColor.GOLD).append(text(" -> ", NamedTextColor.WHITE), event.entity().displayName().get())); + .color(NamedTextColor.GOLD).append(text(" → ", NamedTextColor.WHITE), event.entity().displayName().get())); audience.sendMessage(text("base damage: " + format(event.baseDamage()))); audience.sendMessage(text("-----------------------------------------------")); } @@ -151,14 +191,45 @@ private void onDamagePost(final DamageCalculationEvent.Post event, @Root final D final Audience audience = Sponge.server(); audience.sendMessage(text().content("-------------").append(eventName, text(".Post", NamedTextColor.GREEN), text("--------------"))); audience.sendMessage(text().content(damageSource.type().key(RegistryTypes.DAMAGE_TYPE).value()) - .color(NamedTextColor.GOLD).append(text(" -> ", NamedTextColor.WHITE), event.entity().displayName().get())); + .color(NamedTextColor.GOLD).append(text(" → ", NamedTextColor.WHITE), event.entity().displayName().get())); audience.sendMessage(text("base damage: " + format(event.baseDamage()))); audience.sendMessage(text("steps:")); - for (final DamageStep step : event.steps()) { - audience.sendMessage(text(" " + step.type().key(RegistryTypes.DAMAGE_STEP_TYPE).value() + ": " + format(step.damageBeforeStep()) + " -> " + format(step.damageAfterStep()))); + for (final DamageStep step : event.history().rootSteps()) { + audience.sendMessage(formatStep(step)); } - audience.sendMessage(text("final damage: " + format(event.originalFinalDamage()))); + audience.sendMessage(text("final damage: " + format(event.finalDamage()))); audience.sendMessage(text("-----------------------------------------------")); } + + private static Component formatStep(final DamageStep step) { + final List components = new ArrayList<>(); + components.add(Component.text(format(step.damageBeforeChildren()))); + formatStep(step, components); + return Component.join(JoinConfiguration.separator(Component.text(" → ", NamedTextColor.GRAY)), components); + } + + private static void formatStep(final DamageStep step, final List components) { + for (final DamageStep child : step.childrenBefore()) { + formatStep(child, components); + } + + final List causeClasses = new ArrayList<>(); + for (final Object obj : step.cause().all()) { + causeClasses.add(obj.getClass().getSimpleName()); + } + + components.add(Component.text() + .content(step.type().findKey(RegistryTypes.DAMAGE_STEP_TYPE).map(Key::value).orElse("?")) + .color(step.parent().isEmpty() ? NamedTextColor.AQUA : NamedTextColor.YELLOW) + .decoration(TextDecoration.STRIKETHROUGH, step.isSkipped()) + .hoverEvent(HoverEvent.showText(Component.text(String.join(" ", causeClasses)))) + .build()); + + components.add(Component.text(format(step.damageAfterSelf()))); + + for (final DamageStep child : step.childrenAfter()) { + formatStep(child, components); + } + } } } diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java index 3e0a634bd9e..b91ee7554c7 100644 --- a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java +++ b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java @@ -56,9 +56,9 @@ public abstract class LivingEntityMixin_Vanilla_Damage implements TrackedDamageB return true; } - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, (float) tracker.preEvent().baseDamage(), ItemStackUtil.snapshotOf(self.getUseItem())); - step.applyModifiersBefore(); - step.applyModifiersAfter(0); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, ItemStackUtil.snapshotOf(self.getUseItem())); + step.applyChildrenBefore((float) tracker.preEvent().baseDamage()); + step.applyChildrenAfter(0); return !step.isSkipped(); } @@ -72,7 +72,7 @@ public abstract class LivingEntityMixin_Vanilla_Damage implements TrackedDamageB return damage; } final SpongeDamageStep step = tracker.currentStep(DamageStepTypes.SHIELD); - return step == null ? damage : (float) Math.max(step.damageBeforeStep(), 0); + return step == null ? damage : (float) Math.max(step.damageBeforeSelf().getAsDouble(), 0); } @ModifyVariable(method = "hurtServer", at = @At("STORE"), slice = @Slice( @@ -85,7 +85,7 @@ public abstract class LivingEntityMixin_Vanilla_Damage implements TrackedDamageB return blocked; } final SpongeDamageStep step = tracker.currentStep(DamageStepTypes.SHIELD); - return step == null ? blocked : step.damageAfterModifiers() <= 0; + return step == null ? blocked : step.damageAfterChildren().getAsDouble() <= 0; } @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), argsOnly = true, slice = @Slice( From c99f3d32910b93185e123610bea92d2f29eca66e Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Tue, 11 Mar 2025 10:34:13 +0100 Subject: [PATCH 3/6] Update and optimize attack injections --- .../entity/damage/SpongeDamageStepType.java | 1 - .../entity/damage/SpongeDamageTracker.java | 6 +- .../core/world/entity/MobMixin_Attack.java | 33 +++++++-- .../entity/player/PlayerMixin_Attack.java | 70 +++++++++---------- .../enchantment/EnchantmentMixin_Attack.java | 16 ++--- 5 files changed, 69 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java index 1d35edb2ba3..959438dabd1 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStepType.java @@ -34,7 +34,6 @@ public final class SpongeDamageStepType implements DamageStepType { public String toString() { return RegistryTypes.DAMAGE_STEP_TYPE.get().findValueKey(this) .map(ResourceKey::toString) - .map("DamageStepType[%s]"::formatted) .orElseGet(super::toString); } } diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java index 769deaf458c..a4492c6123f 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageTracker.java @@ -88,13 +88,13 @@ public SpongeDamageStep newStep(final DefaultedRegistryReference step.populateChildren(); if (this.postEvent != null) { - LOGGER.warn("A new root step {} is being captured after the post event.", step); + LOGGER.warn("A new root step {} is being captured after the post event.", step.type()); } if (!this.rootSteps.isEmpty()) { final SpongeDamageStep previous = this.rootSteps.getLast(); if (previous.damageAfterChildren().isEmpty()) { - LOGGER.warn("A new root step {} is being captured but previous root step {} hasn't finished.", step, previous); + LOGGER.warn("A new root step {} is being captured but previous root step {} hasn't finished.", step.type(), previous.type()); this.rootSteps.removeLast(); } } @@ -116,7 +116,7 @@ public float startStep(final DefaultedRegistryReference typeRef, final DamageStepType type = typeRef.get(); final SpongeDamageStep step = this.rootSteps.getLast(); if (step.type() != type) { - LOGGER.warn("Expected a current root step of type {} but got {}.", type, step); + LOGGER.warn("Expected a current root step of type {} but got {}.", type, step.type()); return null; } return step; diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java index 9d9addfd679..0c35598a428 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/MobMixin_Attack.java @@ -24,21 +24,25 @@ */ package org.spongepowered.common.mixin.core.world.entity; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import org.spongepowered.common.bridge.world.entity.TrackedAttackBridge; import org.spongepowered.common.event.cause.entity.damage.SpongeAttackTracker; +import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; import java.util.Deque; import java.util.LinkedList; @@ -68,8 +72,23 @@ public abstract class MobMixin_Attack extends LivingEntityMixin_Damage implement } } - @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z")) - private boolean attack$firePostEvent(final Entity target, final ServerLevel level, final DamageSource source, float damage) { + @WrapOperation(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$modifyBeforeAndAfterWeaponBonus(final Item item, final Entity target, final float originalDamage, final DamageSource source, final Operation operation) { + final SpongeAttackTracker tracker = this.attack$tracker(); + if (tracker == null) { + return operation.call(item, target, originalDamage, source); + } + + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_BONUS, tracker.weaponSnapshot()); + float damage = (float) step.applyChildrenBefore(originalDamage); + if (!step.isSkipped()) { + damage += operation.call(item, target, damage, source); + } + return (float) step.applyChildrenAfter(damage) - originalDamage; + } + + @WrapOperation(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z")) + private boolean attack$firePostEvent(final Entity target, final ServerLevel level, final DamageSource source, float damage, final Operation operation) { final SpongeAttackTracker tracker = this.attack$tracker(); if (tracker != null) { final float knockbackModifier = this.shadow$getKnockback(target, source); @@ -78,13 +97,13 @@ public abstract class MobMixin_Attack extends LivingEntityMixin_Damage implement } damage = (float) tracker.postEvent().finalDamage(); } - return target.hurtServer(level, source, damage); + return operation.call(target, level, source, damage); } - @Redirect(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) - private float attack$knockbackModifier(final Mob self, final Entity target, final DamageSource source) { + @WrapOperation(method = "doHurtTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$knockbackModifier(final Mob self, final Entity target, final DamageSource source, final Operation operation) { final SpongeAttackTracker tracker = this.attack$tracker(); - return tracker == null ? this.shadow$getKnockback(target, source) : (float) tracker.postEvent().knockbackModifier(); + return tracker == null ? operation.call(self, target, source) : (float) tracker.postEvent().knockbackModifier(); } @Inject(method = "doHurtTarget", at = @At("RETURN"), slice = @Slice( diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java index adb9799fa76..c58ded67972 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack.java @@ -24,6 +24,10 @@ */ package org.spongepowered.common.mixin.core.world.entity.player; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.world.damagesource.DamageSource; @@ -44,7 +48,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @@ -92,9 +95,8 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem return tracker == null ? damage : (float) tracker.preEvent().baseDamage(); } - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F")) - private float attack$captureAttackStrength(final Player self, final float param) { - final float value = self.getAttackStrengthScale(param); + @ModifyExpressionValue(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F")) + private float attack$captureAttackStrength(final float value) { final SpongeAttackTracker tracker = this.attack$tracker(); if (tracker != null) { tracker.setAttackStrength(value); @@ -104,7 +106,7 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem @ModifyVariable(method = "attack", at = @At("LOAD"), ordinal = 0, slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/EntityTypeTags;REDIRECTABLE_PROJECTILE:Lnet/minecraft/tags/TagKey;") )) private float attack$modifyBeforeBaseCooldown(final float damage) { final SpongeAttackTracker tracker = this.attack$tracker(); @@ -113,7 +115,7 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem @ModifyVariable(method = "attack", at = @At("STORE"), ordinal = 0, slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/EntityTypeTags;REDIRECTABLE_PROJECTILE:Lnet/minecraft/tags/TagKey;") )) private float attack$modifyAfterBaseCooldown(final float damage) { final SpongeAttackTracker tracker = this.attack$tracker(); @@ -122,7 +124,7 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem @ModifyVariable(method = "attack", at = @At("LOAD"), ordinal = 1, slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/EntityTypeTags;REDIRECTABLE_PROJECTILE:Lnet/minecraft/tags/TagKey;") )) private float attack$modifyBeforeEnchantmentCooldown(final float damage) { final SpongeAttackTracker tracker = this.attack$tracker(); @@ -131,7 +133,7 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem @ModifyVariable(method = "attack", at = @At("STORE"), ordinal = 1, slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getAttackStrengthScale(F)F"), - to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;resetAttackStrengthTicker()V") + to = @At(value = "FIELD", target = "Lnet/minecraft/tags/EntityTypeTags;REDIRECTABLE_PROJECTILE:Lnet/minecraft/tags/TagKey;") )) private float attack$modifyAfterEnchantmentCooldown(final float damage) { final SpongeAttackTracker tracker = this.attack$tracker(); @@ -148,17 +150,17 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem } } - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) - private float attack$modifyBeforeAndAfterWeaponBonus(final Item item, final Entity target, final float originalDamage, final DamageSource source) { + @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/Item;getAttackDamageBonus(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$modifyBeforeAndAfterWeaponBonus(final Item item, final Entity target, final float originalDamage, final DamageSource source, final Operation operation) { final SpongeAttackTracker tracker = this.attack$tracker(); if (tracker == null) { - return item.getAttackDamageBonus(target, originalDamage, source); + return operation.call(item, target, originalDamage, source); } final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_BONUS, tracker.weaponSnapshot()); float damage = (float) step.applyChildrenBefore(originalDamage); if (!step.isSkipped()) { - damage += item.getAttackDamageBonus(target, damage, source); + damage += operation.call(item, target, damage, source); } return (float) step.applyChildrenAfter(damage) - originalDamage; } @@ -181,12 +183,11 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem return tracker == null ? damage : tracker.endStep(DamageStepTypes.CRITICAL_HIT, damage); } - @SuppressWarnings("deprecation") - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtOrSimulate(Lnet/minecraft/world/damagesource/DamageSource;F)Z"), slice = @Slice( + @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hurtOrSimulate(Lnet/minecraft/world/damagesource/DamageSource;F)Z"), slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getDeltaMovement()Lnet/minecraft/world/phys/Vec3;"), to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F") )) - private boolean attack$firePostEvent(final Entity target, final DamageSource source, float damage) { + private boolean attack$firePostEvent(final Entity target, final DamageSource source, float damage, final Operation operation) { final SpongeAttackTracker tracker = this.attack$tracker(); if (tracker != null) { final float knockbackModifier = this.shadow$getKnockback(target, source) + (tracker.isStrongSprint() ? 1.0F : 0.0F); @@ -195,24 +196,22 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem } damage = (float) tracker.postEvent().finalDamage(); } - return target.hurtOrSimulate(source, damage); + return operation.call(target, source, damage); } - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) - private float attack$knockbackModifier(final Player self, final Entity target, final DamageSource source) { + @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getKnockback(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)F")) + private float attack$knockbackModifier(final Player self, final Entity target, final DamageSource source, final Operation operation) { final SpongeAttackTracker tracker = this.attack$tracker(); - return tracker == null ? this.shadow$getKnockback(target, source) : ((float) tracker.postEvent().knockbackModifier() - (tracker.isStrongSprint() ? 1.0F : 0.0F)); + return tracker == null ? operation.call(self, target, source) : ((float) tracker.postEvent().knockbackModifier() - (tracker.isStrongSprint() ? 1.0F : 0.0F)); } - @Redirect(method = "attack", + @WrapWithCondition(method = "attack", slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;DDDLnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V")) - private void attack$preventSound(final Level level, final Player player, final double x, final double y, final double z, + private boolean attack$preventSound(final Level level, final Player player, final double x, final double y, final double z, final SoundEvent sound, final SoundSource source, final float volume, final float pitch) { final SpongeAttackTracker tracker = this.attack$tracker(); - if (tracker == null || !tracker.postEvent().isCancelled()) { - level.playSound(player, x, y, z, sound, source, volume, pitch); - } + return tracker == null || !tracker.postEvent().isCancelled(); } @Inject(method = "attack", at = @At("RETURN"), slice = @Slice( @@ -222,11 +221,11 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem this.attack$trackers.removeLast(); } - @Redirect(method = "attack", + @WrapOperation(method = "attack", slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;distanceToSqr(Lnet/minecraft/world/entity/Entity;)D")) - private double sweepAttack$fireEvents(final Player self, final Entity sweepTarget) { - final double distanceSquared = self.distanceToSqr(sweepTarget); + private double sweepAttack$fireEvents(final Player self, final Entity sweepTarget, final Operation operation) { + final double distanceSquared = operation.call(self, sweepTarget); if (!(distanceSquared < this.attack$interactionRangeSquared())) { return distanceSquared; } @@ -273,33 +272,32 @@ public abstract class PlayerMixin_Attack extends LivingEntityMixin_Damage implem return distanceSquared; } - @Redirect(method = "attack", + @WrapOperation(method = "attack", slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getEnchantedDamage(Lnet/minecraft/world/entity/Entity;FLnet/minecraft/world/damagesource/DamageSource;)F")) - private float sweepAttack$cancelEnchantedDamage(final Player self, final Entity sweepTarget, final float damage, final DamageSource source) { + private float sweepAttack$cancelEnchantedDamage(final Player self, final Entity sweepTarget, final float damage, final DamageSource source, final Operation operation) { return damage; // We already did it above } - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V"), slice = @Slice( + @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V"), slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;"), to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V"))) - private void sweepAttack$knockbackModifier(final LivingEntity sweepTarget, double modifier, final double dirX, final double dirZ) { + private void sweepAttack$knockbackModifier(final LivingEntity sweepTarget, double modifier, final double dirX, final double dirZ, final Operation operation) { final SpongeAttackTracker sweepTracker = this.attack$tracker(); if (sweepTracker != null) { modifier = sweepTracker.postEvent().knockbackModifier(); } - sweepTarget.knockback(modifier, dirX, dirZ); + operation.call(sweepTarget, modifier, dirX, dirZ); } - @SuppressWarnings("deprecation") - @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V"), + @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurt(Lnet/minecraft/world/damagesource/DamageSource;F)V"), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getEntitiesOfClass(Ljava/lang/Class;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;"))) - private void sweepAttack$finalDamage(final LivingEntity sweepTarget, final DamageSource source, float damage) { + private void sweepAttack$finalDamage(final LivingEntity sweepTarget, final DamageSource source, float damage, final Operation operation) { final SpongeAttackTracker sweepTracker = this.attack$tracker(); if (sweepTracker != null) { damage = (float) sweepTracker.postEvent().finalDamage(); } - sweepTarget.hurt(source, damage); + operation.call(sweepTarget, source, damage); this.attack$trackers.removeLast(); } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java index dd01f14e2c1..95dd69cfd9b 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/item/enchantment/EnchantmentMixin_Attack.java @@ -24,6 +24,8 @@ */ package org.spongepowered.common.mixin.core.world.item.enchantment; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.core.component.DataComponentType; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; @@ -35,9 +37,7 @@ import org.apache.commons.lang3.mutable.MutableFloat; import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.common.event.cause.entity.damage.SpongeAttackTracker; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; import org.spongepowered.common.item.util.ItemStackUtil; @@ -47,25 +47,21 @@ @Mixin(Enchantment.class) public abstract class EnchantmentMixin_Attack { - @Shadow protected abstract void shadow$modifyDamageFilteredValue( - final DataComponentType>> component, - final ServerLevel level, final int enchantmentLevel, final ItemStack weapon, final Entity target, final DamageSource source, final MutableFloat damage); - - @Redirect(method = "modifyDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/Enchantment;modifyDamageFilteredValue(Lnet/minecraft/core/component/DataComponentType;Lnet/minecraft/server/level/ServerLevel;ILnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;Lorg/apache/commons/lang3/mutable/MutableFloat;)V")) + @WrapOperation(method = "modifyDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/Enchantment;modifyDamageFilteredValue(Lnet/minecraft/core/component/DataComponentType;Lnet/minecraft/server/level/ServerLevel;ILnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;Lorg/apache/commons/lang3/mutable/MutableFloat;)V")) private void attack$modifyWeaponEnchantment( final Enchantment self, final DataComponentType>> component, - final ServerLevel level, final int enchantmentLevel, final ItemStack weapon, final Entity target, final DamageSource source, final MutableFloat damage) { + final ServerLevel level, final int enchantmentLevel, final ItemStack weapon, final Entity target, final DamageSource source, final MutableFloat damage, final Operation operation) { final SpongeAttackTracker tracker = SpongeAttackTracker.of(source); if (tracker == null) { - this.shadow$modifyDamageFilteredValue(component, level, enchantmentLevel, weapon, target, source, damage); + operation.call(self, component, level, enchantmentLevel, weapon, target, source, damage); return; } final SpongeDamageStep step = tracker.newStep(DamageStepTypes.WEAPON_ENCHANTMENT, ItemStackUtil.snapshotOf(weapon), self); damage.setValue((float) step.applyChildrenBefore(damage.floatValue())); if (!step.isSkipped()) { - this.shadow$modifyDamageFilteredValue(component, level, enchantmentLevel, weapon, target, source, damage); + operation.call(self, component, level, enchantmentLevel, weapon, target, source, damage); } damage.setValue((float) step.applyChildrenAfter(damage.floatValue())); } From 6813f749eddb934c396ef14f7f6dcf5c63c5703c Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Tue, 11 Mar 2025 17:26:32 +0100 Subject: [PATCH 4/6] Optimize damage injections --- .../LivingEntityMixin_Forge_Damage.java | 13 +++--- .../entity/LivingEntityMixin_Neo_Damage.java | 27 ++++++----- .../entity/player/PlayerMixin_Neo_Damage.java | 11 ++--- .../entity/LivingEntityMixin_Damage.java | 46 +++++++++---------- .../LivingEntityMixin_Shared_Damage.java | 18 +++----- .../entity/decoration/ArmorStandMixin.java | 8 ++-- .../player/PlayerMixin_Shared_Damage.java | 18 +++----- .../LivingEntityMixin_Vanilla_Damage.java | 10 ++-- 8 files changed, 68 insertions(+), 83 deletions(-) diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java index cb083e764ea..78d36540f89 100644 --- a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java +++ b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/world/entity/LivingEntityMixin_Forge_Damage.java @@ -24,9 +24,10 @@ */ package org.spongepowered.forge.mixin.core.world.entity; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.LivingEntity; -import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.event.entity.living.ShieldBlockEvent; import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.asm.mixin.Mixin; @@ -34,7 +35,6 @@ import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; @@ -51,12 +51,11 @@ public abstract class LivingEntityMixin_Forge_Damage implements TrackedDamageBri return Float.NaN; } - @SuppressWarnings("UnstableApiUsage") - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/event/ForgeEventFactory;onShieldBlock(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)Lnet/minecraftforge/event/entity/living/ShieldBlockEvent;")) - private ShieldBlockEvent damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageSource source, final float originalDamage) { + @WrapOperation(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/event/ForgeEventFactory;onShieldBlock(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/damagesource/DamageSource;F)Lnet/minecraftforge/event/entity/living/ShieldBlockEvent;")) + private ShieldBlockEvent damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageSource source, final float originalDamage, final Operation operation) { final SpongeDamageTracker tracker = this.damage$tracker(); if (tracker == null) { - return ForgeEventFactory.onShieldBlock(self, source, originalDamage); + return operation.call(self, source, originalDamage); } final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, ItemStackUtil.snapshotOf(self.getUseItem())); @@ -66,7 +65,7 @@ public abstract class LivingEntityMixin_Forge_Damage implements TrackedDamageBri event = new ShieldBlockEvent(self, source, damage); event.setCanceled(true); } else { - event = ForgeEventFactory.onShieldBlock(self, source, damage); + event = operation.call(self, source, damage); if (!event.isCanceled()) { damage -= event.getBlockedDamage(); } diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java index 603730e6801..c9b309cc362 100644 --- a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java +++ b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/LivingEntityMixin_Neo_Damage.java @@ -24,8 +24,10 @@ */ package org.spongepowered.neoforge.mixin.core.world.entity; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.world.entity.LivingEntity; -import net.neoforged.neoforge.common.CommonHooks; import net.neoforged.neoforge.common.damagesource.DamageContainer; import net.neoforged.neoforge.event.entity.living.LivingShieldBlockEvent; import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; @@ -33,7 +35,6 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageStep; @@ -58,11 +59,11 @@ public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridg } @SuppressWarnings("UnstableApiUsage") - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onDamageBlock(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;Z)Lnet/neoforged/neoforge/event/entity/living/LivingShieldBlockEvent;")) - private LivingShieldBlockEvent damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageContainer container, final boolean blocked) { + @WrapOperation(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onDamageBlock(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;Z)Lnet/neoforged/neoforge/event/entity/living/LivingShieldBlockEvent;")) + private LivingShieldBlockEvent damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageContainer container, final boolean blocked, final Operation operation) { final SpongeDamageTracker tracker = this.damage$tracker(); if (tracker == null || !blocked) { // don't capture when vanilla wouldn't block - return CommonHooks.onDamageBlock(self, container, false); + return operation.call(self, container, false); } final float originalDamage = container.getNewDamage(); @@ -74,7 +75,7 @@ public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridg event = new LivingShieldBlockEvent(self, container, true); event.setBlocked(true); } else { - event = CommonHooks.onDamageBlock(self, container, true); + event = operation.call(self, container, true); container.setBlockedDamage(event); damage = container.getNewDamage(); } @@ -82,9 +83,10 @@ public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridg return event; } - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setBlockedDamage(Lnet/neoforged/neoforge/event/entity/living/LivingShieldBlockEvent;)V")) - private void damage$cancelSetBlockedDamage(final DamageContainer container, final LivingShieldBlockEvent event) { + @WrapWithCondition(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setBlockedDamage(Lnet/neoforged/neoforge/event/entity/living/LivingShieldBlockEvent;)V")) + private boolean damage$cancelSetBlockedDamage(final DamageContainer container, final LivingShieldBlockEvent event) { // We already did it above + return false; } @ModifyVariable(method = "actuallyHurt", ordinal = 1, @@ -94,14 +96,11 @@ public abstract class LivingEntityMixin_Neo_Damage implements TrackedDamageBridg return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); } - @SuppressWarnings("UnstableApiUsage") - @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setReduction(Lnet/neoforged/neoforge/common/damagesource/DamageContainer$Reduction;F)V"), slice = @Slice( + @WrapWithCondition(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setReduction(Lnet/neoforged/neoforge/common/damagesource/DamageContainer$Reduction;F)V"), slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onLivingDamagePre(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;)F"))) - private void damage$skipAbsorption(final DamageContainer container, final DamageContainer.Reduction reduction, final float absorbed) { + private boolean damage$skipAbsorption(final DamageContainer container, final DamageContainer.Reduction reduction, final float absorbed) { final SpongeDamageTracker tracker = this.damage$tracker(); - if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { - container.setReduction(reduction, absorbed); - } + return tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION); } @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), ordinal = 3, slice = @Slice( diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java index f1f10a35e24..e25fc9102cd 100644 --- a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java +++ b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/world/entity/player/PlayerMixin_Neo_Damage.java @@ -24,6 +24,7 @@ */ package org.spongepowered.neoforge.mixin.core.world.entity.player; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import net.minecraft.util.Mth; import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.common.damagesource.DamageContainer; @@ -32,7 +33,6 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.common.bridge.world.entity.TrackedAttackBridge; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; @@ -55,14 +55,11 @@ public abstract class PlayerMixin_Neo_Damage extends LivingEntityMixin_Neo_Damag return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); } - @SuppressWarnings("UnstableApiUsage") - @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setReduction(Lnet/neoforged/neoforge/common/damagesource/DamageContainer$Reduction;F)V"), slice = @Slice( + @WrapWithCondition(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/damagesource/DamageContainer;setReduction(Lnet/neoforged/neoforge/common/damagesource/DamageContainer$Reduction;F)V"), slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;onLivingDamagePre(Lnet/minecraft/world/entity/LivingEntity;Lnet/neoforged/neoforge/common/damagesource/DamageContainer;)F"))) - private void damage$skipAbsorption(final DamageContainer container, final DamageContainer.Reduction reduction, final float absorbed) { + private boolean damage$skipAbsorption(final DamageContainer container, final DamageContainer.Reduction reduction, final float absorbed) { final SpongeDamageTracker tracker = this.damage$tracker(); - if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { - container.setReduction(reduction, absorbed); - } + return tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION); } @ModifyVariable(method = "actuallyHurt", at = @At("LOAD"), ordinal = 3, slice = @Slice( diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java index 4956e0836a3..e2cbc64f6f8 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Damage.java @@ -24,10 +24,12 @@ */ package org.spongepowered.common.mixin.core.world.entity; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.core.Holder; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.damagesource.CombatRules; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; @@ -47,7 +49,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.common.bridge.world.entity.LivingEntityBridge; @@ -64,12 +65,9 @@ public abstract class LivingEntityMixin_Damage extends EntityMixin implements LivingEntityBridge, TrackedDamageBridge { //@formatter:off - @Shadow protected abstract void shadow$playHurtSound(final DamageSource source); @Shadow protected abstract float shadow$getKnockback(final Entity entity, final DamageSource source); @Shadow public abstract @NonNull ItemStack shadow$getWeaponItem(); @Shadow public abstract ItemStack shadow$getItemBySlot(final EquipmentSlot slot); - @Shadow protected abstract void shadow$hurtHelmet(final DamageSource source, final float damage); - @Shadow protected abstract void shadow$hurtArmor(final DamageSource source, final float damage); @Shadow public abstract @Nullable MobEffectInstance shadow$getEffect(final Holder effect); @Shadow public abstract double shadow$getAttributeValue(final Holder attribute); // @formatter:on @@ -135,13 +133,14 @@ public abstract class LivingEntityMixin_Damage extends EntityMixin implements Li return tracker == null ? damage : tracker.startStep(DamageStepTypes.HARD_HAT, damage, this.shadow$getItemBySlot(EquipmentSlot.HEAD)); } - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtHelmet(Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void damage$skipHardHat(final LivingEntity self, final DamageSource source, final float damage) { + @WrapWithCondition(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtHelmet(Lnet/minecraft/world/damagesource/DamageSource;F)V")) + private boolean damage$skipHardHat(final LivingEntity self, final DamageSource source, final float damage) { final SpongeDamageTracker tracker = this.damage$tracker(); if (tracker == null || !tracker.isSkipped(DamageStepTypes.HARD_HAT)) { - this.shadow$hurtHelmet(source, damage); this.damage$inventoryChanged = true; + return true; } + return false; } @ModifyVariable(method = "hurtServer", at = @At("STORE"), argsOnly = true, slice = @Slice( @@ -159,13 +158,14 @@ public abstract class LivingEntityMixin_Damage extends EntityMixin implements Li return tracker == null ? damage : tracker.startStep(DamageStepTypes.ARMOR, damage, this, Attributes.ARMOR_TOUGHNESS); } - @Redirect(method = "getDamageAfterArmorAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtArmor(Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void damage$skipArmor(final LivingEntity self, final DamageSource source, final float damage) { + @WrapWithCondition(method = "getDamageAfterArmorAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;hurtArmor(Lnet/minecraft/world/damagesource/DamageSource;F)V")) + private boolean damage$skipArmor(final LivingEntity self, final DamageSource source, final float damage) { final SpongeDamageTracker tracker = this.damage$tracker(); if (tracker == null || !tracker.isSkipped(DamageStepTypes.ARMOR)) { - this.shadow$hurtArmor(source, damage); this.damage$inventoryChanged = true; + return true; } + return false; } @ModifyVariable(method = "getDamageAfterArmorAbsorb", at = @At("STORE"), argsOnly = true) @@ -188,33 +188,29 @@ public abstract class LivingEntityMixin_Damage extends EntityMixin implements Li return tracker == null ? damage : tracker.endStep(DamageStepTypes.DEFENSIVE_POTION_EFFECT, damage); } - @Redirect(method = "getDamageAfterMagicAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/CombatRules;getDamageAfterMagicAbsorb(FF)F")) - private float damage$modifyBeforeAndAfterArmorEnchantment(float damage, final float protection) { + @WrapOperation(method = "getDamageAfterMagicAbsorb", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/CombatRules;getDamageAfterMagicAbsorb(FF)F")) + private float damage$modifyBeforeAndAfterArmorEnchantment(float damage, final float protection, final Operation operation) { final SpongeDamageTracker tracker = this.damage$tracker(); if (tracker == null) { - return CombatRules.getDamageAfterMagicAbsorb(damage, protection); + return operation.call(damage, protection); } final SpongeDamageStep step = tracker.newStep(DamageStepTypes.ARMOR_ENCHANTMENT, this); damage = (float) step.applyChildrenBefore(damage); if (!step.isSkipped()) { - damage = CombatRules.getDamageAfterMagicAbsorb(damage, protection); + damage = operation.call(damage, protection); } return (float) step.applyChildrenAfter(damage); } - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;playHurtSound(Lnet/minecraft/world/damagesource/DamageSource;)V")) - private void damage$onHurtSound(final LivingEntity self, final DamageSource source) { - if (this.bridge$vanishState().createsSounds()) { - this.shadow$playHurtSound(source); - } + @WrapWithCondition(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;playHurtSound(Lnet/minecraft/world/damagesource/DamageSource;)V")) + private boolean damage$onHurtSound(final LivingEntity self, final DamageSource source) { + return this.bridge$vanishState().createsSounds(); } - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;makeSound(Lnet/minecraft/sounds/SoundEvent;)V")) - private void damage$onMakeSound(final LivingEntity self, final SoundEvent sound) { - if (this.bridge$vanishState().createsSounds()) { - self.makeSound(sound); - } + @WrapWithCondition(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;makeSound(Lnet/minecraft/sounds/SoundEvent;)V")) + private boolean damage$onMakeSound(final LivingEntity self, final SoundEvent sound) { + return this.bridge$vanishState().createsSounds(); } @Inject(method = "hurtServer", at = @At("RETURN"), slice = @Slice( diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java index 38ea8814a95..206e6a53ee4 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Shared_Damage.java @@ -24,6 +24,7 @@ */ package org.spongepowered.common.mixin.core.world.entity; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.LivingEntity; @@ -31,7 +32,6 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.common.bridge.world.entity.TrackedDamageBridge; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; @@ -54,19 +54,15 @@ public abstract class LivingEntityMixin_Shared_Damage implements TrackedDamageBr return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); } - @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setAbsorptionAmount(F)V", ordinal = 0)) - private void damage$skipAbsorption(final LivingEntity self, final float absorption) { + @WrapWithCondition(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setAbsorptionAmount(F)V", ordinal = 0)) + private boolean damage$skipAbsorption(final LivingEntity self, final float absorption) { final SpongeDamageTracker tracker = this.damage$tracker(); - if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { - self.setAbsorptionAmount(absorption); - } + return tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION); } - @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V")) - private void damage$skipAbsorptionStat(final ServerPlayer self, final ResourceLocation stat, final int amount) { + @WrapWithCondition(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V")) + private boolean damage$skipAbsorptionStat(final ServerPlayer self, final ResourceLocation stat, final int amount) { final SpongeDamageTracker tracker = this.damage$tracker(); - if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { - self.awardStat(stat, amount); - } + return tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java index 243b669fd9c..0788bb811e1 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/decoration/ArmorStandMixin.java @@ -24,6 +24,8 @@ */ package org.spongepowered.common.mixin.core.world.entity.decoration; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageTypes; @@ -97,13 +99,13 @@ public abstract class ArmorStandMixin extends LivingEntityMixin { * IGNITES_ARMOR_STANDS + isOnFire * BURNS_ARMOR_STANDS + health > 0.5 */ - @Redirect(method = "hurtServer", + @WrapOperation(method = "hurtServer", slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/tags/DamageTypeTags;IGNITES_ARMOR_STANDS:Lnet/minecraft/tags/TagKey;")), at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/decoration/ArmorStand;causeDamage(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)V")) - private void impl$fireDamageEventDamage(final ArmorStand self, final ServerLevel level, final DamageSource source, final float damage) { + private void impl$fireDamageEventDamage(final ArmorStand self, final ServerLevel level, final DamageSource source, final float damage, final Operation operation) { final DamageEntityEvent.Post event = SpongeDamageTracker.callDamageEvents((Entity) this, source, damage); if (event != null) { - this.shadow$causeDamage(level, source, (float) event.finalDamage()); + operation.call(self, level, source, (float) event.finalDamage()); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java index 9e95476c7e6..1dd64ca7ac7 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Shared_Damage.java @@ -24,13 +24,13 @@ */ package org.spongepowered.common.mixin.core.world.entity.player; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Player; import org.spongepowered.api.event.cause.entity.damage.DamageStepTypes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.common.event.cause.entity.damage.SpongeDamageTracker; import org.spongepowered.common.mixin.core.world.entity.LivingEntityMixin_Damage; @@ -47,21 +47,17 @@ public abstract class PlayerMixin_Shared_Damage extends LivingEntityMixin_Damage return tracker == null ? damage : tracker.startStep(DamageStepTypes.ABSORPTION, damage, this); } - @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V")) - private void damage$skipAbsorption(final Player self, final float absorption) { + @WrapWithCondition(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V")) + private boolean damage$skipAbsorption(final Player self, final float absorption) { final SpongeDamageTracker tracker = this.damage$tracker(); - if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { - self.setAbsorptionAmount(absorption); - } + return tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION); } - @Redirect(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), slice = @Slice( + @WrapWithCondition(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/resources/ResourceLocation;I)V"), slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;setAbsorptionAmount(F)V"), to = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;causeFoodExhaustion(F)V"))) - private void damage$skipAbsorptionStat(final Player self, final ResourceLocation stat, final int amount) { + private boolean damage$skipAbsorptionStat(final Player self, final ResourceLocation stat, final int amount) { final SpongeDamageTracker tracker = this.damage$tracker(); - if (tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION)) { - self.awardStat(stat, amount); - } + return tracker == null || !tracker.isSkipped(DamageStepTypes.ABSORPTION); } } diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java index b91ee7554c7..e50181c6f12 100644 --- a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java +++ b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/world/entity/LivingEntityMixin_Vanilla_Damage.java @@ -24,7 +24,7 @@ */ package org.spongepowered.vanilla.mixin.core.world.entity; -import net.minecraft.world.damagesource.DamageSource; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.TamableAnimal; import net.minecraft.world.entity.animal.Wolf; @@ -45,9 +45,9 @@ @Mixin(value = LivingEntity.class, priority = 900) public abstract class LivingEntityMixin_Vanilla_Damage implements TrackedDamageBridge { - @Redirect(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isDamageSourceBlocked(Lnet/minecraft/world/damagesource/DamageSource;)Z")) - private boolean damage$modifyBeforeAndAfterShield(final LivingEntity self, final DamageSource source) { - if (!self.isDamageSourceBlocked(source)) { + @ModifyExpressionValue(method = "hurtServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isDamageSourceBlocked(Lnet/minecraft/world/damagesource/DamageSource;)Z")) + private boolean damage$modifyBeforeAndAfterShield(final boolean original) { + if (!original) { return false; } @@ -56,7 +56,7 @@ public abstract class LivingEntityMixin_Vanilla_Damage implements TrackedDamageB return true; } - final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, ItemStackUtil.snapshotOf(self.getUseItem())); + final SpongeDamageStep step = tracker.newStep(DamageStepTypes.SHIELD, ItemStackUtil.snapshotOf(((LivingEntity) (Object) this).getUseItem())); step.applyChildrenBefore((float) tracker.preEvent().baseDamage()); step.applyChildrenAfter(0); return !step.isSkipped(); From 95344a9d7d86c8070e4ba35c7140a4a7ecdea55b Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Tue, 11 Mar 2025 18:06:46 +0100 Subject: [PATCH 5/6] Only allow adding modifiers --- .../java/org/spongepowered/test/damage/DamageTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java index 1659546f1e9..157437a135d 100644 --- a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java @@ -159,15 +159,14 @@ private static String format(final double value) { @Listener private void onAttackPre(final AttackEntityEvent.Pre event) { - event.modifiersBefore(DamageStepTypes.CRITICAL_HIT) - .add(DamageModifier.builder().type(DOUBLE_CRITICAL) - .frame((frame) -> frame.pushCause(new CustomCause())) + event.addModifierBefore(DamageStepTypes.CRITICAL_HIT, + DamageModifier.builder().type(DOUBLE_CRITICAL).frame((frame) -> frame.pushCause(new CustomCause())) .damage((step, damage) -> { step.parent().get().skip(); return damage * 2; }).build()); - event.modifiersAfter(DOUBLE_CRITICAL) - .add(DamageModifier.builder().type(DOUBLE_DOUBLE_CRITICAL).damage((step, damage) -> damage * 2).build()); + event.addModifierAfter(DOUBLE_CRITICAL, + DamageModifier.builder().type(DOUBLE_DOUBLE_CRITICAL).damage((step, damage) -> damage * 2).build()); } @Listener From 46e47f99054c42332ada377f369b3eecfbbb3dae Mon Sep 17 00:00:00 2001 From: Yeregorix Date: Wed, 12 Mar 2025 21:03:12 +0100 Subject: [PATCH 6/6] Rename methods in DamageModifier --- .../event/cause/entity/damage/SpongeDamageModifier.java | 6 +++--- .../common/event/cause/entity/damage/SpongeDamageStep.java | 4 ++-- .../main/java/org/spongepowered/test/damage/DamageTest.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java index 9458aa7c365..e5c90842f13 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageModifier.java @@ -33,7 +33,7 @@ import java.util.Optional; import java.util.function.Consumer; -public record SpongeDamageModifier(DamageStepType type, Optional> frame, Optional damage) implements DamageModifier { +public record SpongeDamageModifier(DamageStepType type, Optional> frameModifier, Optional damageFunction) implements DamageModifier { public static class Builder implements DamageModifier.Builder { private @Nullable DamageStepType type; @@ -59,13 +59,13 @@ public DamageModifier.Builder type(DamageStepType type) { } @Override - public DamageModifier.Builder frame(Consumer frame) { + public DamageModifier.Builder frameModifier(Consumer frame) { this.frame = Objects.requireNonNull(frame, "frame"); return this; } @Override - public DamageModifier.Builder damage(Function function) { + public DamageModifier.Builder damageFunction(Function function) { this.function = Objects.requireNonNull(function, "function"); return this; } diff --git a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java index 4394ca58d05..a3564a43653 100644 --- a/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java +++ b/src/main/java/org/spongepowered/common/event/cause/entity/damage/SpongeDamageStep.java @@ -81,8 +81,8 @@ public SpongeDamageStep(final SpongeDamageStep parent, final DamageModifier modi this.tracker = parent.tracker; this.parent = parent; this.type = modifier.type(); - this.frameModifier = modifier.frame().orElse(null); - this.damageFunction = modifier.damage().orElse(null); + this.frameModifier = modifier.frameModifier().orElse(null); + this.damageFunction = modifier.damageFunction().orElse(null); } void populateChildren() { diff --git a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java index 157437a135d..afdcb5b27e9 100644 --- a/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/damage/DamageTest.java @@ -160,13 +160,13 @@ private static String format(final double value) { @Listener private void onAttackPre(final AttackEntityEvent.Pre event) { event.addModifierBefore(DamageStepTypes.CRITICAL_HIT, - DamageModifier.builder().type(DOUBLE_CRITICAL).frame((frame) -> frame.pushCause(new CustomCause())) - .damage((step, damage) -> { + DamageModifier.builder().type(DOUBLE_CRITICAL).frameModifier((frame) -> frame.pushCause(new CustomCause())) + .damageFunction((step, damage) -> { step.parent().get().skip(); return damage * 2; }).build()); event.addModifierAfter(DOUBLE_CRITICAL, - DamageModifier.builder().type(DOUBLE_DOUBLE_CRITICAL).damage((step, damage) -> damage * 2).build()); + DamageModifier.builder().type(DOUBLE_DOUBLE_CRITICAL).damageFunction((step, damage) -> damage * 2).build()); } @Listener