Add realistic tackle mechanics with approach angle and velocity modifiers
- Calculate approach angle between tackler velocity and target direction - Apply angle-based modifiers: head-on (100%) > side (70%) > behind (40%) > opposite (20%) - Calculate closing speed from relative velocity differential - Apply velocity modifiers: weak <1 m/s (0.3x), solid >3 m/s (1.5x) - Combine modifiers: finalSuccess = base × angleModifier × velocityModifier - Enhanced debug logging shows all components of tackle calculation - Head-on charges now much less effective (~15-30% vs previous ~50%) - Rewards proper positioning, blindside hits, and high closing speeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3a48483973
commit
1d257a5757
@ -56,7 +56,7 @@ export const CENTER_DOT_RADIUS = 5; // pixels
|
||||
|
||||
// AI/Behavior constants
|
||||
export const SHOOTING_RANGE = 10; // meters - max distance to attempt shot
|
||||
export const SHOOTING_ANGLE_THRESHOLD = Math.PI / 4; // radians (45 degrees)
|
||||
export const SHOOTING_ANGLE_THRESHOLD = Math.PI / 3; // radians (60 degrees)
|
||||
export const GOALIE_RANGE = 3; // meters - how far goalie moves from center
|
||||
|
||||
// Collision avoidance constants
|
||||
@ -69,3 +69,19 @@ export const TACKLE_PUCK_LOOSE_CHANCE = 0.6; // Chance puck becomes loose
|
||||
export const TACKLE_COOLDOWN = 1000; // ms - time between tackles for same player
|
||||
export const TACKLE_FALL_DURATION = 500; // ms - time player stays down after being tackled
|
||||
export const TACKLE_MIN_SPEED = 2; // m/s - minimum speed required to execute tackle
|
||||
|
||||
// Tackle angle modifiers (based on approach angle)
|
||||
export const TACKLE_ANGLE_HEAD_ON_THRESHOLD = Math.PI / 4; // 45° - best check angle
|
||||
export const TACKLE_ANGLE_SIDE_THRESHOLD = Math.PI / 2; // 90° - glancing blow
|
||||
export const TACKLE_ANGLE_BEHIND_THRESHOLD = (3 * Math.PI) / 4; // 135° - awkward angle
|
||||
|
||||
export const TACKLE_ANGLE_HEAD_ON_MODIFIER = 1.0; // 100% effectiveness
|
||||
export const TACKLE_ANGLE_SIDE_MODIFIER = 0.7; // 70% effectiveness
|
||||
export const TACKLE_ANGLE_BEHIND_MODIFIER = 0.4; // 40% effectiveness
|
||||
export const TACKLE_ANGLE_OPPOSITE_MODIFIER = 0.2; // 20% effectiveness
|
||||
|
||||
// Tackle velocity differential modifiers
|
||||
export const TACKLE_CLOSING_SPEED_WEAK = 1; // m/s - very weak check
|
||||
export const TACKLE_CLOSING_SPEED_SOLID = 3; // m/s - solid check
|
||||
export const TACKLE_VELOCITY_MODIFIER_MIN = 0.3; // Minimum modifier for very low closing speed
|
||||
export const TACKLE_VELOCITY_MODIFIER_MAX = 1.5; // Maximum modifier for high closing speed
|
||||
|
||||
@ -18,7 +18,18 @@ import {
|
||||
SHOT_SPEED,
|
||||
TACKLE_SUCCESS_MODIFIER,
|
||||
TACKLE_PUCK_LOOSE_CHANCE,
|
||||
TACKLE_MIN_SPEED
|
||||
TACKLE_MIN_SPEED,
|
||||
TACKLE_ANGLE_HEAD_ON_THRESHOLD,
|
||||
TACKLE_ANGLE_SIDE_THRESHOLD,
|
||||
TACKLE_ANGLE_BEHIND_THRESHOLD,
|
||||
TACKLE_ANGLE_HEAD_ON_MODIFIER,
|
||||
TACKLE_ANGLE_SIDE_MODIFIER,
|
||||
TACKLE_ANGLE_BEHIND_MODIFIER,
|
||||
TACKLE_ANGLE_OPPOSITE_MODIFIER,
|
||||
TACKLE_CLOSING_SPEED_WEAK,
|
||||
TACKLE_CLOSING_SPEED_SOLID,
|
||||
TACKLE_VELOCITY_MODIFIER_MIN,
|
||||
TACKLE_VELOCITY_MODIFIER_MAX
|
||||
} from '../config/constants';
|
||||
import { Goal } from './Goal';
|
||||
import { Puck } from '../entities/Puck';
|
||||
@ -326,16 +337,85 @@ export class GameScene extends Phaser.Scene {
|
||||
return; // Tackle cannot occur
|
||||
}
|
||||
|
||||
// Calculate tackle success based on tackling vs balance
|
||||
// Formula: (tackling / (tackling + balance)) * modifier
|
||||
// 1. Calculate approach angle modifier
|
||||
// Get velocity vectors (using physics body velocity)
|
||||
const tacklerVelX = tackler.body.velocity.x / SCALE; // Convert to m/s
|
||||
const tacklerVelY = tackler.body.velocity.y / SCALE;
|
||||
const tackledVelX = tackled.body.velocity.x / SCALE;
|
||||
const tackledVelY = tackled.body.velocity.y / SCALE;
|
||||
|
||||
// Calculate angle of attack: angle between tackler's velocity and direction to tackled player
|
||||
const dx = tackled.gameX - tackler.gameX;
|
||||
const dy = tackled.gameY - tackler.gameY;
|
||||
const angleToTarget = Math.atan2(dy, dx);
|
||||
const tacklerVelocityAngle = Math.atan2(tacklerVelY, tacklerVelX);
|
||||
|
||||
// Angle difference (how aligned is tackler's movement with target direction)
|
||||
let approachAngle = Math.abs(angleToTarget - tacklerVelocityAngle);
|
||||
// Normalize to 0-PI range
|
||||
if (approachAngle > Math.PI) approachAngle = 2 * Math.PI - approachAngle;
|
||||
|
||||
// Determine angle modifier based on approach angle
|
||||
let angleModifier: number;
|
||||
if (approachAngle <= TACKLE_ANGLE_HEAD_ON_THRESHOLD) {
|
||||
angleModifier = TACKLE_ANGLE_HEAD_ON_MODIFIER; // Head-on
|
||||
} else if (approachAngle <= TACKLE_ANGLE_SIDE_THRESHOLD) {
|
||||
angleModifier = TACKLE_ANGLE_SIDE_MODIFIER; // Side angle
|
||||
} else if (approachAngle <= TACKLE_ANGLE_BEHIND_THRESHOLD) {
|
||||
angleModifier = TACKLE_ANGLE_BEHIND_MODIFIER; // From behind
|
||||
} else {
|
||||
angleModifier = TACKLE_ANGLE_OPPOSITE_MODIFIER; // Opposite direction
|
||||
}
|
||||
|
||||
// 2. Calculate relative velocity differential (closing speed)
|
||||
// Project velocities onto the line connecting the players
|
||||
const distSq = dx * dx + dy * dy;
|
||||
const dist = Math.sqrt(distSq);
|
||||
|
||||
if (dist < 0.01) {
|
||||
// Players are on top of each other, skip velocity calculation
|
||||
return;
|
||||
}
|
||||
|
||||
const dirX = dx / dist; // Unit vector toward tackled player
|
||||
const dirY = dy / dist;
|
||||
|
||||
// Closing velocity = tackler's velocity toward target - tackled's velocity toward tackler
|
||||
const tacklerClosingVel = tacklerVelX * dirX + tacklerVelY * dirY;
|
||||
const tackledClosingVel = -(tackledVelX * dirX + tackledVelY * dirY); // Negative because measuring toward tackler
|
||||
const closingSpeed = tacklerClosingVel + tackledClosingVel;
|
||||
|
||||
// Calculate velocity modifier based on closing speed
|
||||
// Map closing speed to modifier range
|
||||
let velocityModifier: number;
|
||||
if (closingSpeed <= TACKLE_CLOSING_SPEED_WEAK) {
|
||||
velocityModifier = TACKLE_VELOCITY_MODIFIER_MIN;
|
||||
} else if (closingSpeed >= TACKLE_CLOSING_SPEED_SOLID) {
|
||||
velocityModifier = TACKLE_VELOCITY_MODIFIER_MAX;
|
||||
} else {
|
||||
// Linear interpolation between weak and solid
|
||||
const ratio = (closingSpeed - TACKLE_CLOSING_SPEED_WEAK) /
|
||||
(TACKLE_CLOSING_SPEED_SOLID - TACKLE_CLOSING_SPEED_WEAK);
|
||||
velocityModifier = TACKLE_VELOCITY_MODIFIER_MIN +
|
||||
ratio * (TACKLE_VELOCITY_MODIFIER_MAX - TACKLE_VELOCITY_MODIFIER_MIN);
|
||||
}
|
||||
|
||||
// 3. Calculate base tackle success based on tackling vs balance
|
||||
const tacklerSkill = tackler.attributes.tackling;
|
||||
const tackledBalance = tackled.attributes.balance;
|
||||
|
||||
const successChance = (tacklerSkill / (tacklerSkill + tackledBalance)) * TACKLE_SUCCESS_MODIFIER;
|
||||
const success = Math.random() < successChance;
|
||||
const baseSuccess = (tacklerSkill / (tacklerSkill + tackledBalance)) * TACKLE_SUCCESS_MODIFIER;
|
||||
|
||||
// 4. Apply all modifiers
|
||||
const finalSuccessChance = baseSuccess * angleModifier * velocityModifier;
|
||||
const success = Math.random() < finalSuccessChance;
|
||||
|
||||
console.log(
|
||||
`[Tackle] ${tackler.id} (tackling: ${tacklerSkill}, speed: ${tacklerSpeed.toFixed(1)} m/s) tackles ${tackled.id} (balance: ${tackledBalance}) - ${success ? 'SUCCESS' : 'FAILED'} (${(successChance * 100).toFixed(1)}%)`
|
||||
`[Tackle] ${tackler.id} → ${tackled.id} | ` +
|
||||
`Base: ${(baseSuccess * 100).toFixed(1)}% | ` +
|
||||
`Angle: ${(approachAngle * 180 / Math.PI).toFixed(0)}° (×${angleModifier.toFixed(2)}) | ` +
|
||||
`Closing: ${closingSpeed.toFixed(1)} m/s (×${velocityModifier.toFixed(2)}) | ` +
|
||||
`Final: ${(finalSuccessChance * 100).toFixed(1)}% → ${success ? 'SUCCESS' : 'FAILED'}`
|
||||
);
|
||||
|
||||
// Mark tackle performed (cooldown starts)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user