Skip to content

GH-371 Fix/knockback roof teleport#371

Open
MassiveLag wants to merge 2 commits intoEternalCodeTeam:masterfrom
MassiveLag:fix/knockback-roof-teleport
Open

GH-371 Fix/knockback roof teleport#371
MassiveLag wants to merge 2 commits intoEternalCodeTeam:masterfrom
MassiveLag:fix/knockback-roof-teleport

Conversation

@MassiveLag
Copy link
Copy Markdown
Contributor

@MassiveLag MassiveLag commented Apr 9, 2026

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:

  • 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

…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
@CitralFlo CitralFlo changed the title Fix/knockback roof teleport GH-371 Fix/knockback roof teleport Apr 9, 2026
@CitralFlo CitralFlo requested a review from Rollczi April 9, 2026 18:08
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +83 to +95
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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Comment on lines +129 to +163
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();

},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
if (loc.getWorld() == null) return loc;
if (loc == null || loc.getWorld() == null) return loc;

Comment on lines +190 to +196
if (type.isSolid()
&& !config.knockback.unsafeGroundBlocks.contains(type)
&& above.isAir()
&& above2.isAir()) {

return check.clone().add(0, config.knockback.groundOffset, 0);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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)) { ... }.

Comment on lines +272 to +274
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value 1e-6 is a magic number used as an epsilon for floating-point comparisons. It's a good practice to define this as a named constant at the class level, such as private static final double EPSILON = 1e-6;, to improve readability and make it clear what the value represents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant