From 8e68202b23a3bc86e6c3ab5f7080dcc84619b4a5 Mon Sep 17 00:00:00 2001 From: Pierre Wessman <4029607+pierrewessman@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:19:38 +0200 Subject: [PATCH] . --- src/entities/player.js | 120 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/src/entities/player.js b/src/entities/player.js index e6b058f..7456ffd 100644 --- a/src/entities/player.js +++ b/src/entities/player.js @@ -49,6 +49,16 @@ class Player { this.homePosition = new Vector2(x, y); this.angle = 0; this.targetAngle = 0; + + // Stuck detection and pathfinding + this.stuckDetection = { + lastPosition: new Vector2(x, y), + stuckTimer: 0, + isStuck: false, + alternativeTarget: null, + stuckThreshold: 500, // 500ms + movementThreshold: 5 // minimum movement distance to not be considered stuck + }; } /** @@ -72,12 +82,21 @@ class Player { /** * Updates player physics including movement toward target, velocity limits, and collision bounds * Applies acceleration toward target position with deceleration when close + * Includes stuck detection and alternative pathfinding when blocked * @param {number} deltaTime - Time elapsed since last frame in seconds * @param {Object} gameState - Current game state for boundary checking */ updateMovement(deltaTime, gameState) { - const direction = this.targetPosition.subtract(this.position).normalize(); - const distance = this.position.distance(this.targetPosition); + // Update stuck detection before movement + this.updateStuckDetection(deltaTime); + + // Choose target - either original or alternative if stuck + const currentTarget = this.stuckDetection.isStuck && this.stuckDetection.alternativeTarget + ? this.stuckDetection.alternativeTarget + : this.targetPosition; + + const direction = currentTarget.subtract(this.position).normalize(); + const distance = this.position.distance(currentTarget); if (distance > 10) { const force = direction.multiply(this.acceleration * deltaTime); @@ -85,6 +104,11 @@ class Player { } else { // Slow down when close to target this.velocity = this.velocity.multiply(0.8); + + // If we reached alternative target, clear stuck state + if (this.stuckDetection.isStuck && this.stuckDetection.alternativeTarget) { + this.clearStuckState(); + } } this.velocity = this.velocity.limit(this.maxSpeed); @@ -112,6 +136,98 @@ class Player { } } + /** + * Updates stuck detection system - tracks if player hasn't moved significantly + * and triggers alternative pathfinding when stuck for more than threshold time + * @param {number} deltaTime - Time elapsed since last frame in seconds + */ + updateStuckDetection(deltaTime) { + const currentPosition = this.position; + const deltaTimeMs = deltaTime * 1000; + + // Calculate movement since last position check + const movementDistance = currentPosition.distance(this.stuckDetection.lastPosition); + + if (movementDistance < this.stuckDetection.movementThreshold) { + // Player hasn't moved much, increment stuck timer + this.stuckDetection.stuckTimer += deltaTimeMs; + + if (this.stuckDetection.stuckTimer >= this.stuckDetection.stuckThreshold) { + if (!this.stuckDetection.isStuck) { + // Player just became stuck, find alternative path + this.handleStuckState(); + } + } + } else { + // Player moved significantly, reset stuck detection + this.stuckDetection.stuckTimer = 0; + if (this.stuckDetection.isStuck) { + this.clearStuckState(); + } + this.stuckDetection.lastPosition = currentPosition.copy(); + } + } + + /** + * Handles when a player becomes stuck - finds alternative pathfinding target + * Creates waypoint around obstacles to reach the original target + */ + handleStuckState() { + this.stuckDetection.isStuck = true; + + // Find alternative target by trying different angles around the obstacle + const originalTarget = this.targetPosition; + const currentPos = this.position; + const directionToTarget = originalTarget.subtract(currentPos).normalize(); + + // Try different angles (45°, 90°, -45°, -90°) to find a clear path + const angles = [Math.PI / 4, Math.PI / 2, -Math.PI / 4, -Math.PI / 2]; + const wayPointDistance = 60; // Distance to create waypoint + + for (let angle of angles) { + const rotatedDirection = this.rotateVector(directionToTarget, angle); + const wayPoint = currentPos.add(rotatedDirection.multiply(wayPointDistance)); + + // Simple bounds check for waypoint + if (wayPoint.x > 50 && wayPoint.x < 950 && + wayPoint.y > 50 && wayPoint.y < 550) { + this.stuckDetection.alternativeTarget = wayPoint; + break; + } + } + + // If no good waypoint found, try backing up slightly + if (!this.stuckDetection.alternativeTarget) { + const backupDirection = directionToTarget.multiply(-1); + this.stuckDetection.alternativeTarget = currentPos.add(backupDirection.multiply(30)); + } + } + + /** + * Clears stuck state and resets stuck detection variables + */ + clearStuckState() { + this.stuckDetection.isStuck = false; + this.stuckDetection.alternativeTarget = null; + this.stuckDetection.stuckTimer = 0; + this.stuckDetection.lastPosition = this.position.copy(); + } + + /** + * Rotates a 2D vector by the given angle + * @param {Vector2} vector - Vector to rotate + * @param {number} angle - Rotation angle in radians + * @returns {Vector2} Rotated vector + */ + rotateVector(vector, angle) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + return new Vector2( + vector.x * cos - vector.y * sin, + vector.x * sin + vector.y * cos + ); + } + /** * Main AI decision making system - handles reaction timing, faceoffs, and behavioral switching * Delegates to specific behavior methods based on team puck possession