diff --git a/patches/net/minecraft/world/entity/player/Player.java.patch b/patches/net/minecraft/world/entity/player/Player.java.patch index f3804ed582..b56eec23b3 100644 --- a/patches/net/minecraft/world/entity/player/Player.java.patch +++ b/patches/net/minecraft/world/entity/player/Player.java.patch @@ -254,12 +254,12 @@ if (p_36347_.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && p_36347_ instanceof Projectile projectile && projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) { -@@ -1170,8 +_,12 @@ +@@ -1170,19 +_,28 @@ && !this.isPassenger() && p_36347_ instanceof LivingEntity && !this.isSprinting(); + // Neo: Fire the critical hit event and override the critical hit status and damage multiplier based on the event. -+ // The boolean local above (flag2) is the vanilla critical hit result. ++ // The boolean local above (flag1) is the vanilla critical hit result. + var critEvent = net.neoforged.neoforge.common.CommonHooks.fireCriticalHit(this, p_36347_, flag1, flag1 ? 1.5F : 1.0F); + flag1 = critEvent.isCriticalHit(); if (flag1) { @@ -268,17 +268,26 @@ } float f3 = f + f1; -@@ -1179,9 +_,7 @@ + boolean flag2 = false; double d0 = (double)(this.walkDist - this.walkDistO); - if (flag4 && !flag1 && !flag && this.onGround() && d0 < (double)this.getSpeed()) { +- if (flag4 && !flag1 && !flag && this.onGround() && d0 < (double)this.getSpeed()) { ++ // Neo: Replace !flag1 (!isCriticalHit) with the logic from the CriticalHitEvent. ++ boolean critBlocksSweep = critEvent.isCriticalHit() && critEvent.disableSweep(); ++ if (flag4 && !critBlocksSweep && !flag && this.onGround() && d0 < (double)this.getSpeed()) { ++ // Neo: Make sweep attacks check SWORD_SWEEP instead of instanceof SwordItem. ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND); - if (itemstack1.getItem() instanceof SwordItem) { - flag2 = true; - } + flag2 = itemstack1.canPerformAction(net.neoforged.neoforge.common.ItemAbilities.SWORD_SWEEP); } ++ ++ // Neo: Fire the SweepAttackEvent and overwrite the value of flag2 (the local controlling if a sweep will occur). ++ var sweepEvent = net.neoforged.neoforge.common.CommonHooks.fireSweepAttack(this, p_36347_, flag2); ++ flag2 = sweepEvent.isSweeping(); float f6 = 0.0F; + if (p_36347_ instanceof LivingEntity livingentity) { @@ -1217,11 +_,12 @@ for (LivingEntity livingentity2 : this.level() diff --git a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java index e434bbb54f..5c7e2f992f 100644 --- a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java +++ b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java @@ -205,6 +205,7 @@ import net.neoforged.neoforge.event.entity.player.PlayerEnchantItemEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; +import net.neoforged.neoforge.event.entity.player.SweepAttackEvent; import net.neoforged.neoforge.event.level.BlockDropsEvent; import net.neoforged.neoforge.event.level.BlockEvent; import net.neoforged.neoforge.event.level.NoteBlockEvent; @@ -923,6 +924,17 @@ public static CriticalHitEvent fireCriticalHit(Player player, Entity target, boo return NeoForge.EVENT_BUS.post(new CriticalHitEvent(player, target, damageModifier, vanillaCritical)); } + /** + * Fires the {@link SweepAttackEvent} and returns the resulting event. + * + * @param player The attacking player. + * @param target The attack target. + * @param isVanillaSweep If the attack would have been a sweep attack by vanilla's rules in {@link Player#attack(Entity)}. + */ + public static SweepAttackEvent fireSweepAttack(Player player, Entity target, boolean isVanillaSweep) { + return NeoForge.EVENT_BUS.post(new SweepAttackEvent(player, target, isVanillaSweep)); + } + /** * Hook to fire {@link ItemAttributeModifierEvent}. Modders should use {@link ItemStack#forEachModifier(EquipmentSlot, BiConsumer)} instead. */ diff --git a/src/main/java/net/neoforged/neoforge/event/entity/player/CriticalHitEvent.java b/src/main/java/net/neoforged/neoforge/event/entity/player/CriticalHitEvent.java index 03b4c3e32b..53c6d5ba52 100644 --- a/src/main/java/net/neoforged/neoforge/event/entity/player/CriticalHitEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/entity/player/CriticalHitEvent.java @@ -11,10 +11,12 @@ /** * This event is fired when a player attacks an entity in {@link Player#attack(Entity)}. - * It can be used to change the critical hit status and damage modifier *
- * In the event the attack was not a critical hit, the event will still be fired, but it will be preemptively cancelled. - **/ + * It can be used to change the critical hit status and the critical damage multiplier. + * Additionally, this event allows controlling if the critical hit will impact sweep conditions. + *
+ * This event is fired on both the logical client and logical server. + */ public class CriticalHitEvent extends PlayerEvent { private final Entity target; private final float vanillaDmgMultiplier; @@ -22,6 +24,7 @@ public class CriticalHitEvent extends PlayerEvent { private float dmgMultiplier; private boolean isCriticalHit; + private boolean disableSweep = true; /** * Fire via {@link CommonHooks#fireCriticalHit(Player, Entity, boolean, float)} @@ -44,8 +47,6 @@ public Entity getTarget() { * The damage multiplier is applied to the base attack's damage if the attack {@linkplain #isCriticalHit() critically hits}. *
* A damage multiplier of 1.0 will not change the damage, a value of 1.5 will increase the damage by 50%, and so on. - * - * @param modifier The new damage modifier. */ public float getDamageMultiplier() { return this.dmgMultiplier; @@ -55,8 +56,8 @@ public float getDamageMultiplier() { * Sets the damage multiplier for the critical hit. Not used if {@link #isCriticalHit()} is false. *
* Changing the damage modifier to zero does not guarantee that the attack does zero damage. - * - * @param modifier The new damage modifier. Must not be negative. + * + * @param dmgMultiplier The new damage modifier. Must not be negative. * @see #getDamageMultiplier() */ public void setDamageMultiplier(float dmgMultiplier) { @@ -75,7 +76,7 @@ public boolean isCriticalHit() { /** * Changes the critical hit state. - * + * * @param isCriticalHit true if the attack should critically hit */ public void setCriticalHit(boolean isCriticalHit) { @@ -86,7 +87,7 @@ public void setCriticalHit(boolean isCriticalHit) { * Gets the original damage multiplier set by vanilla. *
* If the event {@link #isVanillaCritical()}, the damage multiplier will be 1.5, otherwise it will be 1.0 - * + * * @see #getDamageMultiplier() */ public float getVanillaMultiplier() { @@ -99,4 +100,27 @@ public float getVanillaMultiplier() { public boolean isVanillaCritical() { return this.isVanillaCritical; } + + /** + * Sets if this attack should prevent a sweep from occurring. + *
+ * In vanilla, a critical hit always prevents a sweep from occurring. + * This method can allow an attack to both critically hit and sweep without having to validate the other sweep conditions. + * + * @see {@link SweepAttackEvent} for more advanced sweep attack handling. + */ + public void setDisableSweep(boolean disableSweep) { + this.disableSweep = disableSweep; + } + + /** + * If this attack is a {@linkplain #isCriticalHit() critical hit}, returns if a sweep should be prevented. + *
+ * If this attack is not a critical hit, the return value of this method is meaningless. + * + * @see {@link SweepAttackEvent} for more advanced sweep attack handling. + */ + public boolean disableSweep() { + return disableSweep; + } } diff --git a/src/main/java/net/neoforged/neoforge/event/entity/player/SweepAttackEvent.java b/src/main/java/net/neoforged/neoforge/event/entity/player/SweepAttackEvent.java new file mode 100644 index 0000000000..eb07599ef1 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/event/entity/player/SweepAttackEvent.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.event.entity.player; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.ICancellableEvent; +import net.neoforged.neoforge.common.ItemAbilities; + +/** + * The SweepAttackEvent is fired when a {@link Player} attacks a target, after the {@link CriticalHitEvent} has been fired. + *
+ * This event can be used to force an attack to trigger a sweep, or to prevent a sweep from occurring. + *
+ * This event is fired on both the logical client and logical server. + */ +public class SweepAttackEvent extends PlayerEvent implements ICancellableEvent { + private final Entity target; + private final boolean isVanillaSweep; + + private boolean isSweeping; + + public SweepAttackEvent(Player player, Entity target, boolean isVanillaSweep) { + super(player); + this.target = target; + this.isSweeping = this.isVanillaSweep = isVanillaSweep; + } + + /** + * Returns the target of the attack, which is guaranteed to be a valid attack target. + */ + public Entity getTarget() { + return this.target; + } + + /** + * Returns true if the attack would cause a sweep by utilizing the vanilla rules. + *
+ * The vanilla rules are as follows. All of them must be true for a vanilla sweep to occur: + *
+ * If you intend to perform a custom sweep attack, you should cancel the event and {@link #setSweeping} to false before performing your handling. + */ + @Override + public void setCanceled(boolean canceled) { + ICancellableEvent.super.setCanceled(canceled); + } +}