Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting if the critical hit should disable sweep attack in CriticalHitEvent, adding SweepAttackEvent #1496

Merged
merged 10 commits into from
Sep 24, 2024
17 changes: 13 additions & 4 deletions patches/net/minecraft/world/entity/player/Player.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/net/neoforged/neoforge/common/CommonHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@

/**
* 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
* <p>
* 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.
* <p>
* 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;
private final boolean isVanillaCritical;

private float dmgMultiplier;
private boolean isCriticalHit;
private boolean disableSweep = true;

/**
* Fire via {@link CommonHooks#fireCriticalHit(Player, Entity, boolean, float)}
Expand All @@ -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}.
* <p>
* 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;
Expand All @@ -55,8 +56,8 @@ public float getDamageMultiplier() {
* Sets the damage multiplier for the critical hit. Not used if {@link #isCriticalHit()} is false.
* <p>
* 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) {
Expand All @@ -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) {
Expand All @@ -86,7 +87,7 @@ public void setCriticalHit(boolean isCriticalHit) {
* Gets the original damage multiplier set by vanilla.
* <p>
* If the event {@link #isVanillaCritical()}, the damage multiplier will be 1.5, otherwise it will be 1.0
*
*
* @see #getDamageMultiplier()
*/
public float getVanillaMultiplier() {
Expand All @@ -99,4 +100,27 @@ public float getVanillaMultiplier() {
public boolean isVanillaCritical() {
return this.isVanillaCritical;
}

/**
* Sets if this attack should prevent a sweep from occurring.
* <p>
* 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.
* <p>
* If this attack is <b>not</b> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* This event can be used to force an attack to trigger a sweep, or to prevent a sweep from occurring.
* <p>
* 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.
* <p>
* The vanilla rules are as follows. All of them must be true for a vanilla sweep to occur:
* <ol>
* <li>The player's attack strength is greater than 90%.</li>
* <li>The attack is not a critical hit, or is a critical hit which does not {@linkplain CriticalHitEvent#disableSweep() disable the sweep attack}.</li>
* <li>The player is on the ground.</li>
* <li>The distance the player has traveled this tick is less than their speed.</li>
* <li>The player's weapon supports sweep attacks via {@link ItemAbilities#SWORD_SWEEP}.</li>
* </ol>
*/
public boolean isVanillaSweep() {
return this.isVanillaSweep;
}

/**
* Returns true if the attack will be trigger a sweep.
*/
public boolean isSweeping() {
return this.isSweeping;
}

/**
* @param sweep Whether to enable a sweep for this attack.
*/
public void setSweeping(boolean sweep) {
this.isSweeping = sweep;
}

/**
* Cancels the event, preventing further event handlers from acting. Canceling the event will use the current value of {@link #isSweeping()}.
* <p>
* 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);
}
}
Loading