GH-371 Fix/knockback roof teleport#371
GH-371 Fix/knockback roof teleport#371MassiveLag wants to merge 2 commits intoEternalCodeTeam:masterfrom
Conversation
…k handling Fixed an issue where players using fight charge could remain inside knockback regions and get teleported onto the map roof (above playable area). ### Key changes: - Reworked knockback direction to push players toward nearest region edge instead of center-based vector - Added velocity dampening system for more consistent knockback behavior - Improved vertical handling: - Separate ground vs air vertical values - Prevent excessive vertical stacking while airborne ### Teleport fallback system: - Introduced optional smart fallback teleport when knockback fails - Added continuous velocity check before teleporting (prevents false triggers) - Prevent duplicate fallback tasks using active tracking set - Improved force knockback logic with edge distance + velocity validation ### Safe location handling: - Implemented safe ground detection system: - Avoid unsafe blocks (lava, cactus, magma, etc.) - Ensure 2-block air clearance above ground - Added configurable fallback scanning from highest block - Prevent teleport if no safe location is found (optional) ### Stability improvements: - Added recursion limit for region expansion to prevent infinite loops - Improved region overlap handling during location generation - Reduced unnecessary teleports and edge-case glitches ### Config additions: - vertical / maxAirVertical - dampenVelocity / dampenFactor - useTeleport (smart fallback) - safeGroundCheck + safeHighestFallback - unsafeGroundBlocks - groundOffset - maxAttempts - cancelIfNoSafeGround Result: - Eliminates roof teleport exploit - More natural and predictable knockback - Safer teleport fallback system - Better handling of edge cases and overlapping regions
There was a problem hiding this comment.
Code Review
This pull request refactors the knockback system, introducing a more robust mechanism for calculating knockback vectors and handling safe teleportation fallback. The changes include new configuration options for knockback behavior, such as velocity dampening and custom safe ground detection. My feedback highlights several areas for improvement: the use of magic numbers should be addressed by introducing constants or configuration options, code duplication in the fallback logic and safe ground checks should be refactored into helper methods, and a null check should be added to the findSafeGround method to prevent potential NullPointerExceptions.
| if (velocity > 0.02) { | ||
| return; | ||
| } | ||
|
|
||
| if (player.isInsideVehicle()) { | ||
| player.leaveVehicle(); | ||
| if (!region.contains(loc)) { | ||
| return; | ||
| } | ||
|
|
||
| double distanceToEdge = getDistanceToEdge(region, loc); | ||
|
|
||
| if (distanceToEdge > 1.5) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
The values 0.02 (on line 83) and 1.5 (on line 93) are magic numbers. They make the code harder to understand and maintain. These should be extracted into named constants with descriptive names (e.g., MIN_VELOCITY_FOR_TELEPORT, MAX_DISTANCE_FOR_TELEPORT) or, even better, made configurable in KnockbackSettings to allow server administrators to tweak them.
The value 0.02 is also used in scheduleSmartFallback (line 140), which reinforces the need for a constant.
| taskRef[0] = scheduler.timer(() -> { | ||
|
|
||
| Location check = player.getLocation(); | ||
| double velocity = player.getVelocity().lengthSquared(); | ||
|
|
||
| if (!region.contains(check)) { | ||
| fallbackActive.remove(uuid); | ||
| taskRef[0].cancel(); | ||
| return; | ||
| } | ||
|
|
||
| if (velocity > 0.02) { | ||
| return; | ||
| } | ||
|
|
||
| Location generated = generate( | ||
| player.getLocation(), | ||
| Point2D.from(region.getMin()), | ||
| Point2D.from(region.getMax()), | ||
| 0 | ||
| ); | ||
|
|
||
| Location safe = makeSafe(generated); | ||
| if (safe == null || safe.getWorld() == null) { | ||
| fallbackActive.remove(uuid); | ||
| taskRef[0].cancel(); | ||
| return; | ||
| } | ||
|
|
||
| PaperLib.teleportAsync(player, safe.clone(), TeleportCause.PLUGIN); | ||
|
|
||
| fallbackActive.remove(uuid); | ||
| taskRef[0].cancel(); | ||
|
|
||
| }, |
There was a problem hiding this comment.
The logic to remove the player's UUID from fallbackActive and cancel the task is repeated three times within this lambda. This duplicated code can be extracted into a private helper method to improve readability and maintainability. For example:
private void cancelSmartFallback(UUID uuid, Task task) {
this.fallbackActive.remove(uuid);
if (task != null) {
task.cancel();
}
}You could then call this.cancelSmartFallback(uuid, taskRef[0]); where needed.
|
|
||
| private Location findSafeGround(Location loc) { | ||
|
|
||
| if (loc.getWorld() == null) return loc; |
There was a problem hiding this comment.
The findSafeGround method checks if loc.getWorld() is null, but it doesn't check if loc itself is null. This could lead to a NullPointerException if the method is ever called with a null location. For better robustness, you should add a check for loc == null at the beginning of the method, similar to what's done in getSafeHighest and makeSafe.
| if (loc.getWorld() == null) return loc; | |
| if (loc == null || loc.getWorld() == null) return loc; |
| if (type.isSolid() | ||
| && !config.knockback.unsafeGroundBlocks.contains(type) | ||
| && above.isAir() | ||
| && above2.isAir()) { | ||
|
|
||
| return check.clone().add(0, config.knockback.groundOffset, 0); | ||
| } |
There was a problem hiding this comment.
The logic to check if a location is safe to stand on is duplicated in findSafeGround and getSafeHighest. This logic could be extracted into a private helper method to avoid code duplication and make it easier to maintain. For example:
private boolean isSafeStandable(Location location) {
Material type = location.getBlock().getType();
Material above = location.clone().add(0, 1, 0).getBlock().getType();
Material above2 = location.clone().add(0, 2, 0).getBlock().getType();
return type.isSolid()
&& !this.config.knockback.unsafeGroundBlocks.contains(type)
&& above.isAir()
&& above2.isAir();
}Then you could simplify the loops in both methods by calling if (isSafeStandable(check)) { ... }.
| if (Math.abs(min - dxMin) < 1e-6) return new Vector(-1, 0, 0); | ||
| if (Math.abs(min - dxMax) < 1e-6) return new Vector(1, 0, 0); | ||
| if (Math.abs(min - dzMin) < 1e-6) return new Vector(0, 0, -1); |
There was a problem hiding this comment.
fix(knockback): prevent roof teleport exploit & improve safe knockback handling
Fixed an issue where players using fight charge could remain inside knockback regions
and get teleported onto the map roof (above playable area).
Key changes:
instead of center-based vector
Teleport fallback system:
Safe location handling:
Stability improvements:
Config additions:
Result: