diff --git a/app/features/build-analyzer/analyzer-types.ts b/app/features/build-analyzer/analyzer-types.ts index 20acdf4eb..a9a422672 100644 --- a/app/features/build-analyzer/analyzer-types.ts +++ b/app/features/build-analyzer/analyzer-types.ts @@ -120,8 +120,10 @@ export interface BaseWeaponStats { Range_BrakeGravity?: number; /** Frames in brake phase (typically 4) */ Range_BrakeToFreeStateFrame?: number; - /** Max frames before projectile disappears (Splatanas only) */ + /** Max frames (Splatanas) or max bounces (Bloblobber) before projectile stops */ Range_BurstFrame?: number; + /** Speed multiplier after bouncing (Bloblobber only) */ + Range_BounceAfterMaxSpeed?: number; // Range parameters for chargers (direct distance values) /** Charger full charge range */ diff --git a/app/features/build-analyzer/core/weapon-params.ts b/app/features/build-analyzer/core/weapon-params.ts index 2ddb2269d..f57a01887 100644 --- a/app/features/build-analyzer/core/weapon-params.ts +++ b/app/features/build-analyzer/core/weapon-params.ts @@ -688,6 +688,8 @@ export const weaponParams = { Range_BrakeGravity: 0.01, Range_BrakeToFreeStateFrame: 1, Range_ZRate: 0.5, + Range_BounceAfterMaxSpeed: 0.6, + Range_BurstFrame: 3, MoveSpeed: 0.05, DamageParam_ValueDirect: 300, InkRecoverStop: 40, diff --git a/app/features/comp-analyzer/core/weapon-range.ts b/app/features/comp-analyzer/core/weapon-range.ts index d04c2613e..cb00b6325 100644 --- a/app/features/comp-analyzer/core/weapon-range.ts +++ b/app/features/comp-analyzer/core/weapon-range.ts @@ -12,6 +12,7 @@ interface TrajectoryParams { brakeGravity: number; brakeToFreeFrame: number; burstFrame?: number; + bounceAfterMaxSpeed?: number; } export interface TrajectoryPoint { @@ -49,6 +50,11 @@ function calculateGroundRange(trajectory: TrajectoryPoint[]): number { return lastPoint?.z ?? 0; } +function calculateBouncingRange(trajectory: TrajectoryPoint[]): number { + const lastPoint = trajectory[trajectory.length - 1]; + return lastPoint?.z ?? 0; +} + function simulateTrajectoryPoints(params: TrajectoryParams): TrajectoryPoint[] { const { spawnSpeed, @@ -60,15 +66,21 @@ function simulateTrajectoryPoints(params: TrajectoryParams): TrajectoryPoint[] { brakeGravity, brakeToFreeFrame, burstFrame, + bounceAfterMaxSpeed, } = params; - const maxFrames = burstFrame ?? 300; + const isBouncing = bounceAfterMaxSpeed !== undefined; + const maxBounces = + isBouncing && burstFrame !== undefined ? burstFrame : undefined; + const maxFrames = isBouncing ? 300 : (burstFrame ?? 300); + const points: TrajectoryPoint[] = []; let z = 0; let y = PLAYER_HEIGHT; let vz = spawnSpeed; let vy = 0; let frame = 0; + let bounceCount = 0; points.push({ z, y }); @@ -89,17 +101,52 @@ function simulateTrajectoryPoints(params: TrajectoryParams): TrajectoryPoint[] { frame++; if (y < 0) { - return points; + if (isBouncing) { + bounceCount++; + if (maxBounces !== undefined && bounceCount >= maxBounces) { + return points; + } + y = Math.abs(y); + vy = Math.abs(vy); + vz *= bounceAfterMaxSpeed; + } else { + return points; + } } } - while (frame < maxFrames && y >= 0) { + while (frame < maxFrames) { vz *= 1 - freeAirResist; vy -= freeGravity; z += vz; y += vy; + + if (y < 0) { + if (isBouncing) { + bounceCount++; + if (maxBounces !== undefined && bounceCount >= maxBounces) { + points.push({ z, y: 0 }); + return points; + } + y = Math.abs(y); + vy = Math.abs(vy); + vz *= bounceAfterMaxSpeed; + } else { + points.push({ z, y }); + return points; + } + } + points.push({ z, y }); frame++; + + if (!isBouncing && y < 0) { + return points; + } + + if (vz < 0.01) { + return points; + } } return points; @@ -149,10 +196,14 @@ function getWeaponRange(weaponId: MainWeaponId): WeaponRangeResult { brakeToFreeFrame: params.Range_BrakeToFreeStateFrame ?? DEFAULT_BRAKE_TO_FREE_FRAME, burstFrame: params.Range_BurstFrame, + bounceAfterMaxSpeed: params.Range_BounceAfterMaxSpeed, }; const trajectory = simulateTrajectoryPoints(trajectoryParams); - const range = calculateGroundRange(trajectory); + const range = + params.Range_BounceAfterMaxSpeed !== undefined + ? calculateBouncingRange(trajectory) + : calculateGroundRange(trajectory); return { range, diff --git a/scripts/create-analyzer-json.ts b/scripts/create-analyzer-json.ts index 9c4a098e3..471ee9894 100644 --- a/scripts/create-analyzer-json.ts +++ b/scripts/create-analyzer-json.ts @@ -265,6 +265,7 @@ function extractRangeParams(weapon: MainWeapon, params: any) { units?.[0], ); const moveParam = unitForRange?.MoveParam; + const isBloblobber = weapon.Id === 3030 || weapon.Id === 3031; return { Range_SpawnSpeed: unitForRange?.SpawnSpeedGround, Range_GoStraightStateEndMaxSpeed: moveParam?.GoStraightStateEndMaxSpeed, @@ -274,6 +275,8 @@ function extractRangeParams(weapon: MainWeapon, params: any) { Range_BrakeGravity: moveParam?.BrakeGravity, Range_BrakeToFreeStateFrame: moveParam?.BrakeToFreeStateFrame, Range_ZRate: params.spl__SpawnBulletAdditionMovePlayerParam?.ZRate, + Range_BounceAfterMaxSpeed: isBloblobber ? 0.6 : undefined, + Range_BurstFrame: isBloblobber ? 3 : undefined, }; }