4444import it .unimi .dsi .fastutil .ints .Int2ObjectMap ;
4545import it .unimi .dsi .fastutil .ints .Int2ObjectOpenHashMap ;
4646import org .jetbrains .annotations .NotNull ;
47+ import org .jetbrains .annotations .Nullable ;
4748
4849import java .util .ArrayList ;
4950import java .util .Arrays ;
@@ -112,14 +113,46 @@ public void onPacketReceive(final PacketReceiveEvent event) {
112113
113114 InteractionHand hand = action .getAction () == WrapperPlayClientInteractEntity .InteractAction .ATTACK ?
114115 InteractionHand .MAIN_HAND : action .getHand (); // attacks can be only performed with the main hand
115- ItemStack itemInHand = player .inventory .getItemInHand (hand );
116+
117+ ItemStack currentStack = player .inventory .getItemInHand (hand );
118+ ItemStack startStack = player .inventory .getStartOfTickStack ();
119+
120+ boolean hasRange = false ;
121+ float maxReach = 0f ;
122+ float hitboxMargin = 0f ;
123+
124+ if (ATTACK_RANGE_COMPONENT_EXISTS ) {
125+ ItemAttackRange startRange = startStack .getComponentOr (ComponentTypes .ATTACK_RANGE , null );
126+
127+ // If the start stack has no range component, the client defaults to vanilla reach behavior,
128+ // regardless of what the current stack is (No Range -> X = No Range used).
129+ if (startRange != null ) {
130+ ItemAttackRange currentRange = currentStack .getComponentOr (ComponentTypes .ATTACK_RANGE , null );
131+ if (currentRange == null ) {
132+ // Range (Start) -> No Range (Current)
133+ // Client logic uses Start Range
134+ hasRange = true ;
135+ maxReach = startRange .getMaxRange ();
136+ hitboxMargin = startRange .getHitboxMargin ();
137+ } else {
138+ // Range (Start) -> Range (Current)
139+ // Client logic requires satisfying BOTH constraints
140+ hasRange = true ;
141+ maxReach = Math .min (startRange .getMaxRange (), currentRange .getMaxRange ());
142+ hitboxMargin = Math .min (startRange .getHitboxMargin (), currentRange .getHitboxMargin ());
143+ }
144+ }
145+ }
116146
117147 boolean tooManyAttacks = playerAttackQueue .size () > 10 ;
118148 if (!tooManyAttacks ) {
119- playerAttackQueue .put (action .getEntityId (), new InteractionData (new Vector3d (player .x , player .y , player .z ), itemInHand )); // Queue for next tick for very precise check
149+ playerAttackQueue .put (action .getEntityId (), new InteractionData (
150+ player .x , player .y , player .z ,
151+ hasRange , maxReach , hitboxMargin
152+ )); // Queue for next tick for very precise check
120153 }
121154
122- boolean knownInvalid = isKnownInvalid (entity , itemInHand );
155+ boolean knownInvalid = isKnownInvalid (entity , hasRange , maxReach , hitboxMargin );
123156
124157 if ((shouldModifyPackets () && cancelImpossibleHits && knownInvalid ) || tooManyAttacks ) {
125158 event .setCancelled (true );
@@ -141,7 +174,7 @@ public void onPacketReceive(final PacketReceiveEvent event) {
141174 // than this method. If this method flags, the other method WILL flag.
142175 //
143176 // Meaning that the other check should be the only one that flags.
144- private boolean isKnownInvalid (PacketEntity reachEntity , ItemStack itemInHand ) {
177+ private boolean isKnownInvalid (PacketEntity reachEntity , boolean hasAttackRange , float itemMaxReach , float itemHitboxMargin ) {
145178 // If the entity doesn't exist, or if it is exempt, or if it is dead
146179 if ((blacklisted .contains (reachEntity .type ) || !reachEntity .isLivingEntity ) && reachEntity .type != EntityTypes .END_CRYSTAL )
147180 return false ; // exempt
@@ -152,12 +185,12 @@ private boolean isKnownInvalid(PacketEntity reachEntity, ItemStack itemInHand) {
152185
153186 // Filter out what we assume to be cheats
154187 if (cancelBuffer != 0 ) {
155- CheckResult result = checkReach (reachEntity , new Vector3d ( player .x , player .y , player .z ), itemInHand , true );
188+ CheckResult result = checkReach (reachEntity , player .x , player .y , player .z , hasAttackRange , itemMaxReach , itemHitboxMargin , true );
156189 return result .isFlag (); // If they flagged
157190 } else {
158191 SimpleCollisionBox targetBox = getTargetBox (reachEntity );
159192
160- double maxReach = applyReachModifiers (targetBox , itemInHand , !player .packetStateData .didLastMovementIncludePosition );
193+ double maxReach = applyReachModifiers (targetBox , hasAttackRange , itemMaxReach , itemHitboxMargin , !player .packetStateData .didLastMovementIncludePosition );
161194
162195 return ReachUtils .getMinReachToBox (player , targetBox ) > maxReach ;
163196 }
@@ -169,7 +202,7 @@ private void tickBetterReachCheckWithAngle() {
169202 if (reachEntity == null ) continue ;
170203
171204 InteractionData interactionData = attack .getValue ();
172- CheckResult result = checkReach (reachEntity , interactionData .vector () , interactionData .itemInHand () , false );
205+ CheckResult result = checkReach (reachEntity , interactionData .x , interactionData .y , interactionData . z , interactionData . hasAttackRange , interactionData . maxReach , interactionData . hitboxMargin , false );
173206 switch (result .type ()) {
174207 case REACH -> {
175208 String added = ", type=" + reachEntity .type .getName ().getKey ();
@@ -192,10 +225,10 @@ private void tickBetterReachCheckWithAngle() {
192225 }
193226
194227 @ NotNull
195- private CheckResult checkReach (PacketEntity reachEntity , Vector3d from , ItemStack itemInHand , boolean isPrediction ) {
228+ private CheckResult checkReach (PacketEntity reachEntity , double x , double y , double z , boolean hasAttackRange , float itemMaxReach , float itemHitboxMargin , boolean isPrediction ) {
196229 SimpleCollisionBox targetBox = getTargetBox (reachEntity );
197230
198- double maxReach = applyReachModifiers (targetBox , itemInHand , !player .packetStateData .didLastLastMovementIncludePosition );
231+ double maxReach = applyReachModifiers (targetBox , hasAttackRange , itemMaxReach , itemHitboxMargin , !player .packetStateData .didLastLastMovementIncludePosition );
199232 double minDistance = Double .MAX_VALUE ;
200233
201234 // https://bugs.mojang.com/browse/MC-67665
@@ -221,10 +254,10 @@ private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, ItemStac
221254
222255
223256 final double [] possibleEyeHeights = player .getPossibleEyeHeights ();
224- final Vector3dm eyePos = new Vector3dm (from . getX () , 0 , from . getZ () );
257+ final Vector3dm eyePos = new Vector3dm (x , 0 , z );
225258 for (Vector3dm lookVec : possibleLookDirs ) {
226259 for (double eye : possibleEyeHeights ) {
227- eyePos .setY (from . getY () + eye );
260+ eyePos .setY (y + eye );
228261 Vector3dm endReachPos = eyePos .clone ().add (lookVec .getX () * distance , lookVec .getY () * distance , lookVec .getZ () * distance );
229262
230263 Vector3dm intercept = ReachUtils .calculateIntercept (targetBox , eyePos , endReachPos ).first ();
@@ -263,20 +296,13 @@ private SimpleCollisionBox getTargetBox(PacketEntity reachEntity) {
263296 return reachEntity .getPossibleCollisionBoxes ();
264297 }
265298
266- private double applyReachModifiers (SimpleCollisionBox targetBox , ItemStack itemInHand , boolean giveMovementThreshold ) {
299+ private double applyReachModifiers (SimpleCollisionBox targetBox , boolean hasAttackRange , float itemMaxReach , float itemHitboxMargin , boolean giveMovementThreshold ) {
267300 double maxReach ;
268301 double hitboxMargin = threshold ;
269302
270- ItemAttackRange attackRange = null ;
271-
272- if (player .getClientVersion ().isNewerThanOrEquals (ClientVersion .V_1_21_11 ) && ATTACK_RANGE_COMPONENT_EXISTS ) {
273- // TODO: ViaVersion support https://github.com/ViaVersion/ViaVersion/pull/4733
274- attackRange = itemInHand .getComponentOr (ComponentTypes .ATTACK_RANGE , null );
275- }
276-
277- if (attackRange != null ) {
278- maxReach = attackRange .getMaxRange ();
279- hitboxMargin += attackRange .getHitboxMargin ();
303+ if (hasAttackRange ) {
304+ maxReach = itemMaxReach ;
305+ hitboxMargin += itemHitboxMargin ;
280306 } else {
281307 maxReach = player .compensatedEntities .self .getAttributeValue (Attributes .ENTITY_INTERACTION_RANGE );
282308 // 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
@@ -318,7 +344,6 @@ public boolean isFlag() {
318344 }
319345 }
320346
321- private record InteractionData (Vector3d vector , ItemStack itemInHand ) {
347+ private record InteractionData (double x , double y , double z , boolean hasAttackRange , float maxReach , float hitboxMargin ) {
322348 }
323-
324349}
0 commit comments