diff --git a/SpongeAPI b/SpongeAPI index 05573747a54..4dc9ca4cbf2 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit 05573747a54dc967d4592e1244e6bde88651afe4 +Subproject commit 4dc9ca4cbf2845e4a128904e6ab2dd9914456b9b 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 82aa52ae4c8..68b1f298904 100644 --- a/forge/src/mixins/resources/mixins.spongeforge.core.json +++ b/forge/src/mixins/resources/mixins.spongeforge.core.json @@ -26,9 +26,9 @@ "server.level.ServerPlayerMixin_Forge", "server.network.ServerGamePacketListenerImplMixin_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 82d10e7dbf2..f57d9f0f3a8 100644 --- a/neoforge/src/mixins/resources/mixins.spongeneo.core.json +++ b/neoforge/src/mixins/resources/mixins.spongeneo.core.json @@ -25,10 +25,10 @@ "server.network.ServerGamePacketListenerImplMixin_Neo", "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 fcde5c162f6..753445a018c 100644 --- a/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java +++ b/src/main/java/org/spongepowered/common/registry/SpongeRegistries.java @@ -61,7 +61,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 54cc7107c21..91f7788bc93 100644 --- a/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java +++ b/src/main/java/org/spongepowered/common/registry/loader/SpongeRegistryLoader.java @@ -67,8 +67,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; @@ -125,7 +125,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; @@ -218,25 +218,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 e471f06d4d6..ac40ff9fc85 100644 --- a/src/main/java/org/spongepowered/common/util/DamageEventUtil.java +++ b/src/main/java/org/spongepowered/common/util/DamageEventUtil.java @@ -25,112 +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.Sponge; -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); @@ -157,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 = Sponge.server().causeStackManager().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.getCauseStackManager().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.getCauseStackManager().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.getCauseStackManager().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.getCauseStackManager().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/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 4f8bacb5f5e..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/LivingEntityMixin_Attack_Impl.java +++ /dev/null @@ -1,395 +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.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.CombatRules; -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")) - public 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")) - public 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;")) - public 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 - */ - @Redirect(method = "getDamageAfterMagicAbsorb", - at = @At(value = "INVOKE", target = "Lnet/minecraft/world/damagesource/CombatRules;getDamageAfterMagicAbsorb(FF)F")) - public float attackImpl$onDetDamageProtection(final float damage, final float protection) { - if (this.attackImpl$actuallyHurt != null) { - var func = DamageEventUtil.createEnchantmentModifiers(this.attackImpl$actuallyHurt.entity(), protection); - this.attackImpl$actuallyHurt.functions().add(func); - } - return CombatRules.getDamageAfterMagicAbsorb(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")) - public 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) - public 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")) - public 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 353999a85f9..72ef028834a 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 199d69d5742..8474f36bf13 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; @@ -48,10 +48,10 @@ import org.spongepowered.common.bridge.world.level.storage.PrimaryLevelDataBridge; 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 { @@ -147,9 +147,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 6bcc120f9a5..00000000000 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/player/PlayerMixin_Attack_Impl.java +++ /dev/null @@ -1,342 +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 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()); - } - } - - private DamageEventUtil.Attack attackImpl$attack; - private AttackEntityEvent attackImpl$attackEvent; - private Map attackImpl$finalDamageAmounts; - - private int attackImpl$attackStrengthTicker; - private boolean attackImpl$isStrongSprintAttack; - - /** - * Cleanup - */ - @Inject(method = "attack", at = @At("RETURN")) - public 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)) - public 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")) - public void attackImpl$enchanttDamageFunc(final Entity $$0, final 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); - } - - - /** - * 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")) - public 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")) - public 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")) - public 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")) - public 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")) - public 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")) - public 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")) - public 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 - */ - @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")) - public double attackImpl$beforeSweepHurt(final Player instance, final Entity sweepTarget) { - final var distanceToSqr = instance.distanceToSqr(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; - } - - /** - * 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")) - public 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")) - public 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")) - public 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")) - public 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) - public 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")) - public 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 567b00b895e..9278e7e5608 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 59a85456dc7..ddfd2f117de 100644 --- a/src/mixins/resources/mixins.sponge.core.json +++ b/src/mixins/resources/mixins.sponge.core.json @@ -122,9 +122,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", @@ -165,7 +165,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", @@ -217,7 +217,7 @@ "world.item.crafting.SmithingTransformRecipe_SerializerMixin", "world.item.crafting.SmithingTransformRecipeMixin", "world.item.crafting.StonecutterRecipe_SerializerMixin", - "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 e82b08214bf..fc183e7c8c3 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; @@ -35,15 +38,14 @@ import org.spongepowered.api.datapack.DataPacks; 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.DamageTypeTemplate; 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.RegisterDataPackValueEvent; @@ -51,11 +53,13 @@ import org.spongepowered.api.registry.RegistryTypes; import org.spongepowered.api.tag.DamageTypeTags; import org.spongepowered.api.tag.TagTemplate; -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 { @@ -111,38 +115,41 @@ private void onDamageTypeTagPack(final RegisterDataPackValueEvent 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 f5a6e04c79b..f6f18b7bf99 100644 --- a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json +++ b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json @@ -29,12 +29,12 @@ "server.packs.repository.PackRepositoryMixin_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": {