fix(generator): fix 6 generation bugs

- Fix JumpDirector diagonal drift - axis-aligned vectors per border side
- Fix schematic end-block - only last block triggers completion, skip scoring
- Fix JumpOffsetGenerator - clamp random offset to validator limits
- Fix JumpCalculator retry height clamp to -2
- Fix JumpValidator duplication - Vector.canJump delegates to common impl
- Remove rotateAroundY (offset already aligned with heading)
This commit is contained in:
loki5512344 2026-06-08 14:33:43 +02:00
parent 0b6409397b
commit 5b946c4222
4 changed files with 55 additions and 67 deletions

View file

@ -1,6 +1,5 @@
package dev.loki.loparkour.generator.jump.calculation;
import dev.loki.loparkour.config.options.Option;
import dev.loki.loparkour.generator.core.model.GeneratorOption;
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
import dev.loki.loparkour.generator.jump.placement.BlockSelector;
@ -100,7 +99,10 @@ public class JumpCalculator {
@NotNull
private Vector calculateJumpOffset(int distance, int height) {
double sd = java.util.Arrays.asList(generator.generatorOptions).contains(GeneratorOption.REDUCE_RANDOM_BLOCK_SELECTION_ANGLE) ? 0.5 : 1;
int randomOffset = new JumpOffsetGenerator(height, distance).getRandomOffset(0, sd);
int randomOffset = Math.min(
new JumpOffsetGenerator(height, distance).getRandomOffset(0, sd),
(int) Math.floor(Math.sqrt(JumpValidator.MAX_HORIZONTAL_DISTANCE * JumpValidator.MAX_HORIZONTAL_DISTANCE - distance * distance))
);
Vector offset = generator.state.heading.clone().multiply(distance).setY(height);
if (offset.getX() == 0) {
@ -109,7 +111,6 @@ public class JumpCalculator {
offset.setZ(randomOffset);
}
offset.rotateAroundY(angleInY(generator.state.heading, Option.HEADING.getDirection()));
return offset;
}
@ -121,7 +122,7 @@ public class JumpCalculator {
while (!validator.canJump(current.getLocation(), candidate.getLocation()) && attempts < 10) {
// Reduce distance and height to make jump easier
distance = Math.max(1, distance - 1);
height = Math.max(-1, height - 1);
height = Math.max(-2, height - 1);
Vector offset = calculateJumpOffset(distance, height);
candidate = current.getLocation().add(offset).getBlock();
@ -150,11 +151,7 @@ public class JumpCalculator {
default -> 3; // Default restriction
};
}
private double angleInY(@NotNull Vector a, @NotNull Vector b) {
return Math.atan2(b.getZ(), b.getX()) - Math.atan2(a.getZ(), a.getX());
}
/**
* Represents jump constraints for a specific block type.
*/

View file

@ -80,30 +80,43 @@ public class JumpDirector {
double tz = progress[2][0];
double borderMarginZ = progress[2][1];
Vector recommendedHeading = new Vector(0, 0, 0);
// check border
// Determine per-axis corrections needed
boolean needX = false;
boolean needZ = false;
double dirX = 0;
double dirZ = 0;
if (tx < borderMarginX) {
// x should increase
recommendedHeading = new Vector(1, 0, 1);
needX = true;
dirX = 1;
} else if (tx > 1 - borderMarginX) {
// x should decrease
recommendedHeading = new Vector(-1, 0, -1);
needX = true;
dirX = -1;
}
if (tz < borderMarginZ) {
// z should increase
recommendedHeading = new Vector(1, 0, 1);
needZ = true;
dirZ = 1;
} else if (tz > 1 - borderMarginZ) {
// z should decrease
recommendedHeading = new Vector(-1, 0, -1);
needZ = true;
dirZ = -1;
}
if (recommendedHeading.lengthSquared() == 0) {
if (!needX && !needZ) {
return current;
} else {
// Normalize so diagonal vectors don't multiply distance by √2
return recommendedHeading.normalize();
}
if (needX && needZ) {
// Both axes need correction — return a diagonal
return new Vector(dirX, 0, dirZ).normalize();
}
if (needX) {
return new Vector(dirX, 0, 0);
}
// needZ is true
return new Vector(0, 0, dirZ);
}
/**

View file

@ -6,10 +6,10 @@ import org.jetbrains.annotations.NotNull;
public class JumpValidator {
private static final double MAX_JUMP_DISTANCE = 4.5;
private static final double MAX_HORIZONTAL_DISTANCE = 4.1;
private static final double MAX_VERTICAL_UP = 1.25;
private static final double MAX_VERTICAL_DOWN = 3.0;
public static final double MAX_JUMP_DISTANCE = 4.5;
public static final double MAX_HORIZONTAL_DISTANCE = 4.1;
public static final double MAX_VERTICAL_UP = 1.25;
public static final double MAX_VERTICAL_DOWN = 3.0;
private final double maxDistance;
private final double maxHorizontal;
@ -122,43 +122,7 @@ public class JumpValidator {
// Check if jump is possible: sqrt(dx² + dy² + dz²) <= maxDistance
public boolean canJump(@NotNull Vector from, @NotNull Vector to) {
// Null safety check
if (from == null || to == null) {
return false;
}
double dx = to.getX() - from.getX();
double dy = to.getY() - from.getY();
double dz = to.getZ() - from.getZ();
// Check if same location (no jump)
double horizontalDistance = Math.sqrt(dx * dx + dz * dz);
if (horizontalDistance < 0.01 && Math.abs(dy) < 0.01) {
return false; // Same location
}
// Check horizontal distance limit
if (horizontalDistance > maxHorizontal) {
return false;
}
// Check vertical limits
if (dy > maxVerticalUp || dy < -maxVerticalDown) {
return false;
}
// For upward jumps, reduce max horizontal distance
// Player can't jump as far when jumping up
if (dy > 0) {
double adjustedMaxHorizontal = maxHorizontal - (dy * 0.5); // Reduce by 0.5 blocks per block up
if (horizontalDistance > adjustedMaxHorizontal) {
return false;
}
}
// Check total distance
double totalDistance = Math.sqrt(dx * dx + dy * dy + dz * dz);
return totalDistance <= maxDistance;
return canJumpWithAdjustment(from, to, 0.0);
}
public double calculateDistance(@NotNull Location from, @NotNull Location to) {

View file

@ -110,14 +110,23 @@ public class PlayerInteractionHandler {
// Check if it's a schematic end block
if (isSchematicEndBlock(block)) {
handleSchematicEndBlock(block);
} else if (isOnSchematicBlock(block)) {
// Schematic block that is NOT the last — skip scoring
return;
}
// Check for scoring (compare by coordinates, not reference — Bukkit Block uses identity)
if (historyContains(generator.state.history, block) && !player.hasScored(block)) {
player.markScored(block);
handleScore();
}
}
/** Check if block is any schematic block (not just the last one). */
private boolean isOnSchematicBlock(@NotNull Block block) {
return generator.state.schematicBlocks != null
&& historyContains(generator.state.schematicBlocks, block);
}
private void handleSchematicEndBlock(@NotNull Block block) {
// Remove schematic blocks and continue with normal generation
@ -131,8 +140,13 @@ public class PlayerInteractionHandler {
}
private boolean isSchematicEndBlock(@NotNull Block block) {
return generator.state.schematicBlocks != null
&& historyContains(generator.state.schematicBlocks, block);
List<Block> schematicBlocks = generator.state.schematicBlocks;
if (schematicBlocks == null || schematicBlocks.isEmpty()) {
return false;
}
// Only the LAST block of the schematic triggers completion
Block last = schematicBlocks.get(schematicBlocks.size() - 1);
return isSameBlock(block, last);
}
/** Block directly under feet; {@code null} in air — scoring runs when landed. */