Compare commits
No commits in common. "8e68202b23a3bc86e6c3ab5f7080dcc84619b4a5" and "7412d917eede586cab7b3bad61aadf0429bac769" have entirely different histories.
8e68202b23
...
7412d917ee
@ -49,16 +49,6 @@ class Player {
|
|||||||
this.homePosition = new Vector2(x, y);
|
this.homePosition = new Vector2(x, y);
|
||||||
this.angle = 0;
|
this.angle = 0;
|
||||||
this.targetAngle = 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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,21 +72,12 @@ class Player {
|
|||||||
/**
|
/**
|
||||||
* Updates player physics including movement toward target, velocity limits, and collision bounds
|
* Updates player physics including movement toward target, velocity limits, and collision bounds
|
||||||
* Applies acceleration toward target position with deceleration when close
|
* 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 {number} deltaTime - Time elapsed since last frame in seconds
|
||||||
* @param {Object} gameState - Current game state for boundary checking
|
* @param {Object} gameState - Current game state for boundary checking
|
||||||
*/
|
*/
|
||||||
updateMovement(deltaTime, gameState) {
|
updateMovement(deltaTime, gameState) {
|
||||||
// Update stuck detection before movement
|
const direction = this.targetPosition.subtract(this.position).normalize();
|
||||||
this.updateStuckDetection(deltaTime);
|
const distance = this.position.distance(this.targetPosition);
|
||||||
|
|
||||||
// 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) {
|
if (distance > 10) {
|
||||||
const force = direction.multiply(this.acceleration * deltaTime);
|
const force = direction.multiply(this.acceleration * deltaTime);
|
||||||
@ -104,11 +85,6 @@ class Player {
|
|||||||
} else {
|
} else {
|
||||||
// Slow down when close to target
|
// Slow down when close to target
|
||||||
this.velocity = this.velocity.multiply(0.8);
|
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);
|
this.velocity = this.velocity.limit(this.maxSpeed);
|
||||||
@ -136,98 +112,6 @@ 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
|
* Main AI decision making system - handles reaction timing, faceoffs, and behavioral switching
|
||||||
* Delegates to specific behavior methods based on team puck possession
|
* Delegates to specific behavior methods based on team puck possession
|
||||||
|
|||||||
@ -12,9 +12,6 @@ class Puck {
|
|||||||
this.bounceCount = 0;
|
this.bounceCount = 0;
|
||||||
this.trail = [];
|
this.trail = [];
|
||||||
this.maxTrailLength = 10;
|
this.maxTrailLength = 10;
|
||||||
|
|
||||||
// Collision cooldown tracking
|
|
||||||
this.goalieCollisionCooldowns = new Map(); // Maps player ID to cooldown end time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime, gameState, players) {
|
update(deltaTime, gameState, players) {
|
||||||
@ -23,11 +20,6 @@ class Puck {
|
|||||||
this.checkPlayerCollisions(players, gameState);
|
this.checkPlayerCollisions(players, gameState);
|
||||||
this.checkPuckPossession(players);
|
this.checkPuckPossession(players);
|
||||||
this.updateTrail();
|
this.updateTrail();
|
||||||
|
|
||||||
// Check for goals continuously, not just on board collisions
|
|
||||||
if (this.isInGoal(gameState)) {
|
|
||||||
this.handleGoal(gameState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(deltaTime) {
|
updatePosition(deltaTime) {
|
||||||
@ -94,24 +86,6 @@ class Puck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isGoalieOnCooldown(goalie) {
|
|
||||||
const playerId = goalie.id || `${goalie.team}-${goalie.role}`;
|
|
||||||
const cooldownEnd = this.goalieCollisionCooldowns.get(playerId);
|
|
||||||
if (cooldownEnd && Date.now() < cooldownEnd) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Clean up expired cooldowns
|
|
||||||
if (cooldownEnd && Date.now() >= cooldownEnd) {
|
|
||||||
this.goalieCollisionCooldowns.delete(playerId);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGoalieCooldown(goalie, durationMs = 500) {
|
|
||||||
const playerId = goalie.id || `${goalie.team}-${goalie.role}`;
|
|
||||||
this.goalieCollisionCooldowns.set(playerId, Date.now() + durationMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkBoardCollisions(gameState) {
|
checkBoardCollisions(gameState) {
|
||||||
const rink = gameState.rink;
|
const rink = gameState.rink;
|
||||||
let collision = false;
|
let collision = false;
|
||||||
@ -178,7 +152,7 @@ class Puck {
|
|||||||
collision = true;
|
collision = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check left goal side walls and front posts
|
// Check left goal side walls
|
||||||
if (this.position.x >= leftGoalLeft && this.position.x <= leftGoalRight) {
|
if (this.position.x >= leftGoalLeft && this.position.x <= leftGoalRight) {
|
||||||
// Top side wall
|
// Top side wall
|
||||||
if (this.position.y - this.radius <= leftGoalTop && this.position.y >= leftGoalTop - 20) {
|
if (this.position.y - this.radius <= leftGoalTop && this.position.y >= leftGoalTop - 20) {
|
||||||
@ -194,22 +168,6 @@ class Puck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check left goal front posts (prevent entering from sides)
|
|
||||||
if (this.position.x + this.radius >= leftGoalRight && this.position.x <= leftGoalRight + 10) {
|
|
||||||
// Top post
|
|
||||||
if (this.position.y >= leftGoalTop - 10 && this.position.y <= leftGoalTop + 10) {
|
|
||||||
this.velocity.x = -Math.abs(this.velocity.x);
|
|
||||||
this.position.x = leftGoalRight - this.radius;
|
|
||||||
collision = true;
|
|
||||||
}
|
|
||||||
// Bottom post
|
|
||||||
if (this.position.y >= leftGoalBottom - 10 && this.position.y <= leftGoalBottom + 10) {
|
|
||||||
this.velocity.x = -Math.abs(this.velocity.x);
|
|
||||||
this.position.x = leftGoalRight - this.radius;
|
|
||||||
collision = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check right goal side walls
|
// Check right goal side walls
|
||||||
if (this.position.x >= rightGoalLeft && this.position.x <= rightGoalRight) {
|
if (this.position.x >= rightGoalLeft && this.position.x <= rightGoalRight) {
|
||||||
// Top side wall
|
// Top side wall
|
||||||
@ -226,45 +184,15 @@ class Puck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check right goal front posts (prevent entering from sides)
|
|
||||||
if (this.position.x - this.radius <= rightGoalLeft && this.position.x >= rightGoalLeft - 10) {
|
|
||||||
// Top post
|
|
||||||
if (this.position.y >= rightGoalTop - 10 && this.position.y <= rightGoalTop + 10) {
|
|
||||||
this.velocity.x = Math.abs(this.velocity.x);
|
|
||||||
this.position.x = rightGoalLeft + this.radius;
|
|
||||||
collision = true;
|
|
||||||
}
|
|
||||||
// Bottom post
|
|
||||||
if (this.position.y >= rightGoalBottom - 10 && this.position.y <= rightGoalBottom + 10) {
|
|
||||||
this.velocity.x = Math.abs(this.velocity.x);
|
|
||||||
this.position.x = rightGoalLeft + this.radius;
|
|
||||||
collision = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return collision;
|
return collision;
|
||||||
}
|
}
|
||||||
|
|
||||||
isInGoal(gameState) {
|
isInGoal(gameState) {
|
||||||
const rink = gameState.rink;
|
const goalY = gameState.rink.centerY;
|
||||||
const goalY = rink.centerY;
|
const goalHeight = gameState.rink.goalHeight;
|
||||||
const goalHeight = rink.goalHeight;
|
|
||||||
const goalDepth = 25;
|
|
||||||
const goalXOffset = gameState.renderer?.goalXOffset || 80;
|
|
||||||
|
|
||||||
// Check if puck is in the vertical range of the goals
|
|
||||||
if (this.position.y >= goalY - goalHeight && this.position.y <= goalY + goalHeight) {
|
if (this.position.y >= goalY - goalHeight && this.position.y <= goalY + goalHeight) {
|
||||||
// Left goal (light red area)
|
if (this.position.x <= 20 || this.position.x >= gameState.rink.width - 20) {
|
||||||
const leftGoalRight = goalXOffset;
|
|
||||||
const leftGoalLeft = goalXOffset - goalDepth;
|
|
||||||
|
|
||||||
// Right goal (light red area)
|
|
||||||
const rightGoalLeft = rink.width - goalXOffset;
|
|
||||||
const rightGoalRight = rink.width - goalXOffset + goalDepth;
|
|
||||||
|
|
||||||
// Check if puck is inside either light red goal area
|
|
||||||
if ((this.position.x >= leftGoalLeft && this.position.x <= leftGoalRight) ||
|
|
||||||
(this.position.x >= rightGoalLeft && this.position.x <= rightGoalRight)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,27 +203,13 @@ class Puck {
|
|||||||
// Determine which goal was scored in and validate the direction
|
// Determine which goal was scored in and validate the direction
|
||||||
let scoringTeam = null;
|
let scoringTeam = null;
|
||||||
|
|
||||||
const rink = gameState.rink;
|
if (this.position.x <= 20) {
|
||||||
const goalY = rink.centerY;
|
|
||||||
const goalHeight = rink.goalHeight;
|
|
||||||
const goalDepth = 25;
|
|
||||||
const goalXOffset = gameState.renderer?.goalXOffset || 80;
|
|
||||||
|
|
||||||
// Left goal (light red area)
|
|
||||||
const leftGoalRight = goalXOffset;
|
|
||||||
const leftGoalLeft = goalXOffset - goalDepth;
|
|
||||||
|
|
||||||
// Right goal (light red area)
|
|
||||||
const rightGoalLeft = rink.width - goalXOffset;
|
|
||||||
const rightGoalRight = rink.width - goalXOffset + goalDepth;
|
|
||||||
|
|
||||||
if (this.position.x >= leftGoalLeft && this.position.x <= leftGoalRight) {
|
|
||||||
// Puck is in the LEFT goal (home team's goal)
|
// Puck is in the LEFT goal (home team's goal)
|
||||||
// Only count as goal if puck came from the right side (positive x velocity)
|
// Only count as goal if puck came from the right side (positive x velocity)
|
||||||
if (this.velocity.x > 0 || (this.lastTeamTouch === 'away')) {
|
if (this.velocity.x > 0 || (this.lastTeamTouch === 'away')) {
|
||||||
scoringTeam = 'away'; // Away team scored on home team's goal
|
scoringTeam = 'away'; // Away team scored on home team's goal
|
||||||
}
|
}
|
||||||
} else if (this.position.x >= rightGoalLeft && this.position.x <= rightGoalRight) {
|
} else if (this.position.x >= gameState.rink.width - 20) {
|
||||||
// Puck is in the RIGHT goal (away team's goal)
|
// Puck is in the RIGHT goal (away team's goal)
|
||||||
// Only count as goal if puck came from the left side (negative x velocity)
|
// Only count as goal if puck came from the left side (negative x velocity)
|
||||||
if (this.velocity.x < 0 || (this.lastTeamTouch === 'home')) {
|
if (this.velocity.x < 0 || (this.lastTeamTouch === 'home')) {
|
||||||
@ -323,11 +237,6 @@ class Puck {
|
|||||||
let closestDistance = Infinity;
|
let closestDistance = Infinity;
|
||||||
|
|
||||||
players.forEach(player => {
|
players.forEach(player => {
|
||||||
// Skip goalies who are on collision cooldown
|
|
||||||
if (player.role === 'G' && this.isGoalieOnCooldown(player)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const distance = this.position.distance(player.position);
|
const distance = this.position.distance(player.position);
|
||||||
const collisionDistance = this.radius + player.radius;
|
const collisionDistance = this.radius + player.radius;
|
||||||
|
|
||||||
@ -418,13 +327,10 @@ class Puck {
|
|||||||
difficulty: difficulty
|
difficulty: difficulty
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed save - setting collision cooldown")
|
console.log("Failed save")
|
||||||
// Failed save - puck continues with reduced velocity (already bounced from collision)
|
// 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
|
// 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
|
this.velocity = this.velocity.multiply(0.8); // Slightly reduce speed but let it continue
|
||||||
|
|
||||||
// Set collision cooldown to prevent immediate re-collision with goalie
|
|
||||||
this.setGoalieCooldown(goalie, 500); // 500ms cooldown
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,9 +352,6 @@ class Puck {
|
|||||||
this.lastTeamTouch = null;
|
this.lastTeamTouch = null;
|
||||||
this.bounceCount = 0;
|
this.bounceCount = 0;
|
||||||
this.trail = [];
|
this.trail = [];
|
||||||
|
|
||||||
// Clear all goalie collision cooldowns
|
|
||||||
this.goalieCollisionCooldowns.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
faceoffDrop(winningTeam, location, participants) {
|
faceoffDrop(winningTeam, location, participants) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user