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; 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.model.GeneratorOption;
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
import dev.loki.loparkour.generator.jump.placement.BlockSelector; import dev.loki.loparkour.generator.jump.placement.BlockSelector;
@ -100,7 +99,10 @@ public class JumpCalculator {
@NotNull @NotNull
private Vector calculateJumpOffset(int distance, int height) { 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; 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); Vector offset = generator.state.heading.clone().multiply(distance).setY(height);
if (offset.getX() == 0) { if (offset.getX() == 0) {
@ -109,7 +111,6 @@ public class JumpCalculator {
offset.setZ(randomOffset); offset.setZ(randomOffset);
} }
offset.rotateAroundY(angleInY(generator.state.heading, Option.HEADING.getDirection()));
return offset; return offset;
} }
@ -121,7 +122,7 @@ public class JumpCalculator {
while (!validator.canJump(current.getLocation(), candidate.getLocation()) && attempts < 10) { while (!validator.canJump(current.getLocation(), candidate.getLocation()) && attempts < 10) {
// Reduce distance and height to make jump easier // Reduce distance and height to make jump easier
distance = Math.max(1, distance - 1); distance = Math.max(1, distance - 1);
height = Math.max(-1, height - 1); height = Math.max(-2, height - 1);
Vector offset = calculateJumpOffset(distance, height); Vector offset = calculateJumpOffset(distance, height);
candidate = current.getLocation().add(offset).getBlock(); candidate = current.getLocation().add(offset).getBlock();
@ -150,11 +151,7 @@ public class JumpCalculator {
default -> 3; // Default restriction 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. * Represents jump constraints for a specific block type.
*/ */

View file

@ -80,30 +80,43 @@ public class JumpDirector {
double tz = progress[2][0]; double tz = progress[2][0];
double borderMarginZ = progress[2][1]; double borderMarginZ = progress[2][1];
Vector recommendedHeading = new Vector(0, 0, 0); // Determine per-axis corrections needed
// check border boolean needX = false;
boolean needZ = false;
double dirX = 0;
double dirZ = 0;
if (tx < borderMarginX) { if (tx < borderMarginX) {
// x should increase needX = true;
recommendedHeading = new Vector(1, 0, 1); dirX = 1;
} else if (tx > 1 - borderMarginX) { } else if (tx > 1 - borderMarginX) {
// x should decrease needX = true;
recommendedHeading = new Vector(-1, 0, -1); dirX = -1;
} }
if (tz < borderMarginZ) { if (tz < borderMarginZ) {
// z should increase needZ = true;
recommendedHeading = new Vector(1, 0, 1); dirZ = 1;
} else if (tz > 1 - borderMarginZ) { } else if (tz > 1 - borderMarginZ) {
// z should decrease needZ = true;
recommendedHeading = new Vector(-1, 0, -1); dirZ = -1;
} }
if (recommendedHeading.lengthSquared() == 0) { if (!needX && !needZ) {
return current; 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 { public class JumpValidator {
private static final double MAX_JUMP_DISTANCE = 4.5; public static final double MAX_JUMP_DISTANCE = 4.5;
private static final double MAX_HORIZONTAL_DISTANCE = 4.1; public static final double MAX_HORIZONTAL_DISTANCE = 4.1;
private static final double MAX_VERTICAL_UP = 1.25; public static final double MAX_VERTICAL_UP = 1.25;
private static final double MAX_VERTICAL_DOWN = 3.0; public static final double MAX_VERTICAL_DOWN = 3.0;
private final double maxDistance; private final double maxDistance;
private final double maxHorizontal; private final double maxHorizontal;
@ -122,43 +122,7 @@ public class JumpValidator {
// Check if jump is possible: sqrt(dx² + dy² + dz²) <= maxDistance // Check if jump is possible: sqrt(dx² + dy² + dz²) <= maxDistance
public boolean canJump(@NotNull Vector from, @NotNull Vector to) { public boolean canJump(@NotNull Vector from, @NotNull Vector to) {
// Null safety check return canJumpWithAdjustment(from, to, 0.0);
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;
} }
public double calculateDistance(@NotNull Location from, @NotNull Location to) { 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 // Check if it's a schematic end block
if (isSchematicEndBlock(block)) { if (isSchematicEndBlock(block)) {
handleSchematicEndBlock(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) // Check for scoring (compare by coordinates, not reference — Bukkit Block uses identity)
if (historyContains(generator.state.history, block) && !player.hasScored(block)) { if (historyContains(generator.state.history, block) && !player.hasScored(block)) {
player.markScored(block); player.markScored(block);
handleScore(); 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) { private void handleSchematicEndBlock(@NotNull Block block) {
// Remove schematic blocks and continue with normal generation // Remove schematic blocks and continue with normal generation
@ -131,8 +140,13 @@ public class PlayerInteractionHandler {
} }
private boolean isSchematicEndBlock(@NotNull Block block) { private boolean isSchematicEndBlock(@NotNull Block block) {
return generator.state.schematicBlocks != null List<Block> schematicBlocks = generator.state.schematicBlocks;
&& historyContains(generator.state.schematicBlocks, block); 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. */ /** Block directly under feet; {@code null} in air — scoring runs when landed. */