diff --git a/src/entities/player.js b/src/entities/player.js index 0cce319..e6b058f 100644 --- a/src/entities/player.js +++ b/src/entities/player.js @@ -59,7 +59,7 @@ class Player { * @param {Array} players - Array of all players on the ice */ update(deltaTime, gameState, puck, players) { - this.updateMovement(deltaTime); + this.updateMovement(deltaTime, gameState); this.updateAngle(deltaTime); if (this.role !== 'G') { @@ -73,8 +73,9 @@ class Player { * Updates player physics including movement toward target, velocity limits, and collision bounds * Applies acceleration toward target position with deceleration when close * @param {number} deltaTime - Time elapsed since last frame in seconds + * @param {Object} gameState - Current game state for boundary checking */ - updateMovement(deltaTime) { + updateMovement(deltaTime, gameState) { const direction = this.targetPosition.subtract(this.position).normalize(); const distance = this.position.distance(this.targetPosition); @@ -93,7 +94,7 @@ class Player { this.position = this.position.add(this.velocity.multiply(deltaTime)); - this.keepInBounds(); + this.keepInBounds(gameState); } /** @@ -914,9 +915,113 @@ class Player { * Constrains player position to stay within rink boundaries * Uses hardcoded rink dimensions of 1000x600 */ - keepInBounds() { + keepInBounds(gameState = null) { + // Basic rink boundary constraints this.position.x = Math.max(this.radius, Math.min(1000 - this.radius, this.position.x)); this.position.y = Math.max(this.radius, Math.min(600 - this.radius, this.position.y)); + + // Goal wall collision constraints + this.checkGoalWallCollisions(gameState); + } + + checkGoalWallCollisions(gameState) { + if (!gameState) return; // Skip if no game state provided + + const rink = gameState.rink; + const goalY = rink.centerY; + const goalHeight = rink.goalHeight; + const goalDepth = 25; + const goalXOffset = gameState.renderer?.goalXOffset || 80; + + // Goal square dimensions (the light red areas) + const leftGoalSquare = { + left: goalXOffset - goalDepth, + right: goalXOffset, + top: goalY - goalHeight, + bottom: goalY + goalHeight + }; + + const rightGoalSquare = { + left: rink.width - goalXOffset, + right: rink.width - goalXOffset + goalDepth, + top: goalY - goalHeight, + bottom: goalY + goalHeight + }; + + // Allow goalies to move freely in their own goal square only + if (this.role === 'G') { + // Check if goalie is in their own goal square + const inOwnLeftGoal = this.team === 'home' && + this.position.x >= leftGoalSquare.left && this.position.x <= leftGoalSquare.right && + this.position.y >= leftGoalSquare.top && this.position.y <= leftGoalSquare.bottom; + + const inOwnRightGoal = this.team === 'away' && + this.position.x >= rightGoalSquare.left && this.position.x <= rightGoalSquare.right && + this.position.y >= rightGoalSquare.top && this.position.y <= rightGoalSquare.bottom; + + if (inOwnLeftGoal || inOwnRightGoal) { + return; // Goalie can stay in their own goal square + } + // If goalie is not in their own goal square, they still get blocked like any other player + } + + // Prevent any player from entering the left goal square + if (this.position.x + this.radius > leftGoalSquare.left && + this.position.x - this.radius < leftGoalSquare.right && + this.position.y + this.radius > leftGoalSquare.top && + this.position.y - this.radius < leftGoalSquare.bottom) { + + // Push player out of goal square to the nearest edge + const distToLeft = Math.abs(this.position.x + this.radius - leftGoalSquare.left); + const distToRight = Math.abs(this.position.x - this.radius - leftGoalSquare.right); + const distToTop = Math.abs(this.position.y + this.radius - leftGoalSquare.top); + const distToBottom = Math.abs(this.position.y - this.radius - leftGoalSquare.bottom); + + const minDist = Math.min(distToLeft, distToRight, distToTop, distToBottom); + + if (minDist === distToLeft) { + this.position.x = leftGoalSquare.left - this.radius; + this.velocity.x = Math.min(0, this.velocity.x); + } else if (minDist === distToRight) { + this.position.x = leftGoalSquare.right + this.radius; + this.velocity.x = Math.max(0, this.velocity.x); + } else if (minDist === distToTop) { + this.position.y = leftGoalSquare.top - this.radius; + this.velocity.y = Math.min(0, this.velocity.y); + } else if (minDist === distToBottom) { + this.position.y = leftGoalSquare.bottom + this.radius; + this.velocity.y = Math.max(0, this.velocity.y); + } + } + + // Prevent any player from entering the right goal square + if (this.position.x + this.radius > rightGoalSquare.left && + this.position.x - this.radius < rightGoalSquare.right && + this.position.y + this.radius > rightGoalSquare.top && + this.position.y - this.radius < rightGoalSquare.bottom) { + + // Push player out of goal square to the nearest edge + const distToLeft = Math.abs(this.position.x + this.radius - rightGoalSquare.left); + const distToRight = Math.abs(this.position.x - this.radius - rightGoalSquare.right); + const distToTop = Math.abs(this.position.y + this.radius - rightGoalSquare.top); + const distToBottom = Math.abs(this.position.y - this.radius - rightGoalSquare.bottom); + + const minDist = Math.min(distToLeft, distToRight, distToTop, distToBottom); + + if (minDist === distToLeft) { + this.position.x = rightGoalSquare.left - this.radius; + this.velocity.x = Math.min(0, this.velocity.x); + } else if (minDist === distToRight) { + this.position.x = rightGoalSquare.right + this.radius; + this.velocity.x = Math.max(0, this.velocity.x); + } else if (minDist === distToTop) { + this.position.y = rightGoalSquare.top - this.radius; + this.velocity.y = Math.min(0, this.velocity.y); + } else if (minDist === distToBottom) { + this.position.y = rightGoalSquare.bottom + this.radius; + this.velocity.y = Math.max(0, this.velocity.y); + } + } } /** diff --git a/src/entities/puck.js b/src/entities/puck.js index e1e9c5e..e2b5357 100644 --- a/src/entities/puck.js +++ b/src/entities/puck.js @@ -90,6 +90,10 @@ class Puck { const rink = gameState.rink; let collision = false; + // Check goal wall collisions first + collision = this.checkGoalWallCollisions(gameState) || collision; + + // Check regular board collisions (but skip goal areas) if (this.position.x - this.radius <= 0 || this.position.x + this.radius >= rink.width) { if (this.isInGoal(gameState)) { this.handleGoal(gameState); @@ -112,6 +116,77 @@ class Puck { } } + checkGoalWallCollisions(gameState) { + const rink = gameState.rink; + const goalY = rink.centerY; + const goalHeight = rink.goalHeight; + const goalDepth = 25; + const goalXOffset = gameState.renderer?.goalXOffset || 80; + let collision = false; + + // Left goal walls + const leftGoalRight = goalXOffset; + const leftGoalLeft = goalXOffset - goalDepth; + const leftGoalTop = goalY - goalHeight; + const leftGoalBottom = goalY + goalHeight; + + // Right goal walls + const rightGoalLeft = rink.width - goalXOffset; + const rightGoalRight = rink.width - goalXOffset + goalDepth; + const rightGoalTop = goalY - goalHeight; + const rightGoalBottom = goalY + goalHeight; + + // Check left goal back wall collision + if (this.position.x - this.radius <= leftGoalLeft && + this.position.y >= leftGoalTop && this.position.y <= leftGoalBottom) { + this.velocity.x = Math.abs(this.velocity.x); // Bounce away from wall + this.position.x = leftGoalLeft + this.radius; + collision = true; + } + + // Check right goal back wall collision + if (this.position.x + this.radius >= rightGoalRight && + this.position.y >= rightGoalTop && this.position.y <= rightGoalBottom) { + this.velocity.x = -Math.abs(this.velocity.x); // Bounce away from wall + this.position.x = rightGoalRight - this.radius; + collision = true; + } + + // Check left goal side walls + if (this.position.x >= leftGoalLeft && this.position.x <= leftGoalRight) { + // Top side wall + if (this.position.y - this.radius <= leftGoalTop && this.position.y >= leftGoalTop - 20) { + this.velocity.y = Math.abs(this.velocity.y); + this.position.y = leftGoalTop + this.radius; + collision = true; + } + // Bottom side wall + if (this.position.y + this.radius >= leftGoalBottom && this.position.y <= leftGoalBottom + 20) { + this.velocity.y = -Math.abs(this.velocity.y); + this.position.y = leftGoalBottom - this.radius; + collision = true; + } + } + + // Check right goal side walls + if (this.position.x >= rightGoalLeft && this.position.x <= rightGoalRight) { + // Top side wall + if (this.position.y - this.radius <= rightGoalTop && this.position.y >= rightGoalTop - 20) { + this.velocity.y = Math.abs(this.velocity.y); + this.position.y = rightGoalTop + this.radius; + collision = true; + } + // Bottom side wall + if (this.position.y + this.radius >= rightGoalBottom && this.position.y <= rightGoalBottom + 20) { + this.velocity.y = -Math.abs(this.velocity.y); + this.position.y = rightGoalBottom - this.radius; + collision = true; + } + } + + return collision; + } + isInGoal(gameState) { const goalY = gameState.rink.centerY; const goalHeight = gameState.rink.goalHeight; @@ -125,16 +200,36 @@ class Puck { } handleGoal(gameState) { - const team = this.position.x <= 20 ? 'away' : 'home'; - gameState.addGoal(team); + // Determine which goal was scored in and validate the direction + let scoringTeam = null; + if (this.position.x <= 20) { + // Puck is in the LEFT goal (home team's goal) + // Only count as goal if puck came from the right side (positive x velocity) + if (this.velocity.x > 0 || (this.lastTeamTouch === 'away')) { + scoringTeam = 'away'; // Away team scored on home team's goal + } + } else if (this.position.x >= gameState.rink.width - 20) { + // Puck is in the RIGHT goal (away team's goal) + // Only count as goal if puck came from the left side (negative x velocity) + if (this.velocity.x < 0 || (this.lastTeamTouch === 'home')) { + scoringTeam = 'home'; // Home team scored on away team's goal + } + } + + // Only award goal if it was a valid scoring direction + if (scoringTeam) { + gameState.addGoal(scoringTeam); + + gameState.emit('goal-scored', { + team: scoringTeam, + scorer: this.lastPlayerTouch, + position: this.position.copy() + }); + } + + // Reset puck position regardless of whether goal was valid this.reset(gameState.rink.centerX, gameState.rink.centerY); - - gameState.emit('goal-scored', { - team, - scorer: this.lastPlayerTouch, - position: this.position.copy() - }); } checkPlayerCollisions(players, gameState) { @@ -173,15 +268,25 @@ class Puck { if (speed > 0) return; - const totalMass = this.mass + player.mass; - const impulse = 2 * speed / totalMass; + // Handle goalies differently - check for save BEFORE applying collision physics + if (player.role === 'G' && this.velocity.magnitude() > 50) { + // Store original velocity before collision + const originalVelocity = this.velocity.copy(); - this.velocity = this.velocity.subtract(direction.multiply(impulse * player.mass * this.restitution)); + // Apply collision physics first + const totalMass = this.mass + player.mass; + const impulse = 2 * speed / totalMass; + this.velocity = this.velocity.subtract(direction.multiply(impulse * player.mass * this.restitution)); + + // Now check if goalie makes the save + this.handleGoalieSave(player, gameState, originalVelocity); + } else if (player.role !== 'G') { + // Regular player collision - apply physics and pickup puck + const totalMass = this.mass + player.mass; + const impulse = 2 * speed / totalMass; + this.velocity = this.velocity.subtract(direction.multiply(impulse * player.mass * this.restitution)); - if (player.role !== 'G') { this.pickupPuck(player, gameState, players); - } else if (player.role === 'G' && this.velocity.magnitude() > 50) { - this.handleGoalieSave(player, gameState); } } } @@ -203,12 +308,16 @@ class Puck { }); } - handleGoalieSave(goalie, gameState) { + handleGoalieSave(goalie, gameState, originalVelocity = null) { + // Use original velocity for save calculation if provided, otherwise current velocity + const velocityForCalculation = originalVelocity || this.velocity; const saveChance = goalie.attributes.defense / 100; - const shotSpeed = this.velocity.magnitude(); + const shotSpeed = velocityForCalculation.magnitude(); const difficulty = Math.min(1, shotSpeed / 500); if (Math.random() < saveChance * (1 - difficulty * 0.5)) { + console.log("Successful save") + // Successful save - reduce puck velocity significantly this.velocity = this.velocity.multiply(-0.3); gameState.addSave(goalie.team); @@ -217,6 +326,11 @@ class Puck { team: goalie.team, difficulty: difficulty }); + } else { + console.log("Failed save") + // Failed save - puck continues with reduced velocity (already bounced from collision) + // The collision physics have already been applied, so the puck will continue toward the goal + this.velocity = this.velocity.multiply(0.8); // Slightly reduce speed but let it continue } }