Files
csgowtf/src/lib/utils/economyUtils.ts
vikingowl 235ef65556 feat: Merge economy and rounds pages with unified economy utilities
- Create economyUtils.ts with team-aware buy type classification
  (CT has higher thresholds due to M4 cost)
- Add Economy Overview toggle to rounds page with charts
- Resolve player names/avatars in round economy display
- Remove standalone Economy tab (merged into Rounds)
- Document missing backend API data (round winner, win reason)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 19:58:06 +01:00

215 lines
4.8 KiB
TypeScript

/**
* CS2 Economy Utilities
*
* Unified economy classification and display utilities for consistent
* buy type detection across the application.
*
* Thresholds based on:
* - Leetify economy groupings
* - Steam community guides
* - Professional CS2 analysis standards
*/
export type BuyType = 'pistol' | 'eco' | 'force' | 'full';
export type TeamSide = 'T' | 'CT';
export type EconomyHealth = 'healthy' | 'tight' | 'broken';
export interface BuyTypeConfig {
label: string;
color: string;
bgColor: string;
borderColor: string;
}
export interface EconomyHealthConfig {
label: string;
color: string;
bgColor: string;
description: string;
}
/**
* Buy type thresholds based on average equipment value per player
* CT side has higher thresholds due to more expensive rifles (M4 vs AK)
*/
const BUY_THRESHOLDS = {
T: {
eco: 1500,
force: 3500,
full: 3500
},
CT: {
eco: 1500,
force: 4000,
full: 4000
}
} as const;
/**
* Economy health thresholds based on average bank per player
*/
const ECONOMY_HEALTH_THRESHOLDS = {
healthy: 4000, // Can full-buy next round
tight: 2000 // Force-buy possible but risky
// Below tight = broken
} as const;
/**
* Pistol round starting money
*/
export const PISTOL_ROUND_MONEY = 800;
/**
* Visual configuration for each buy type
*/
export const BUY_TYPE_CONFIG: Record<BuyType, BuyTypeConfig> = {
pistol: {
label: 'Pistol',
color: 'text-neon-purple',
bgColor: 'bg-neon-purple/20',
borderColor: 'border-neon-purple'
},
eco: {
label: 'Eco',
color: 'text-red-400',
bgColor: 'bg-red-500/20',
borderColor: 'border-red-500'
},
force: {
label: 'Force',
color: 'text-yellow-400',
bgColor: 'bg-yellow-500/20',
borderColor: 'border-yellow-500'
},
full: {
label: 'Full Buy',
color: 'text-green-400',
bgColor: 'bg-green-500/20',
borderColor: 'border-green-500'
}
};
/**
* Visual configuration for economy health status
*/
export const ECONOMY_HEALTH_CONFIG: Record<EconomyHealth, EconomyHealthConfig> = {
healthy: {
label: 'Healthy',
color: 'text-green-400',
bgColor: 'bg-green-500/20',
description: 'Can full-buy next round'
},
tight: {
label: 'Tight',
color: 'text-yellow-400',
bgColor: 'bg-yellow-500/20',
description: 'Force-buy possible, risky'
},
broken: {
label: 'Broken',
color: 'text-red-400',
bgColor: 'bg-red-500/20',
description: 'Must eco or half-buy'
}
};
/**
* Determine buy type based on average equipment value
*
* @param avgEquipment - Average equipment value per player
* @param teamSide - Team side ('T' or 'CT')
* @param isPistolRound - Whether this is a pistol round
* @returns Buy type classification
*/
export function getBuyType(
avgEquipment: number,
teamSide: TeamSide,
isPistolRound: boolean = false
): BuyType {
if (isPistolRound) {
return 'pistol';
}
const thresholds = BUY_THRESHOLDS[teamSide];
if (avgEquipment < thresholds.eco) {
return 'eco';
}
if (avgEquipment < thresholds.force) {
return 'force';
}
return 'full';
}
/**
* Get visual configuration for a buy type
*/
export function getBuyTypeConfig(buyType: BuyType): BuyTypeConfig {
return BUY_TYPE_CONFIG[buyType];
}
/**
* Determine economy health based on average bank per player
*
* @param avgBank - Average bank per player
* @returns Economy health status
*/
export function getEconomyHealth(avgBank: number): EconomyHealth {
if (avgBank >= ECONOMY_HEALTH_THRESHOLDS.healthy) {
return 'healthy';
}
if (avgBank >= ECONOMY_HEALTH_THRESHOLDS.tight) {
return 'tight';
}
return 'broken';
}
/**
* Get visual configuration for economy health
*/
export function getEconomyHealthConfig(health: EconomyHealth): EconomyHealthConfig {
return ECONOMY_HEALTH_CONFIG[health];
}
/**
* Check if a round is a pistol round
*
* @param roundNumber - Current round number (1-indexed)
* @param halftimeRound - The halftime round number (12 for MR12, 15 for MR15)
* @returns Whether this is a pistol round
*/
export function isPistolRound(roundNumber: number, halftimeRound: number): boolean {
return roundNumber === 1 || roundNumber === halftimeRound + 1;
}
/**
* Get the halftime round based on max rounds
*
* @param maxRounds - Maximum rounds in the match (24 for MR12, 30 for MR15)
* @returns Halftime round number
*/
export function getHalftimeRound(maxRounds: number): number {
return maxRounds === 30 ? 15 : 12;
}
/**
* Calculate total team economy (bank + equipment value)
*
* @param totalBank - Total team bank
* @param totalEquipment - Total team equipment value
* @returns Combined economy value
*/
export function calculateTeamEconomy(totalBank: number, totalEquipment: number): number {
return totalBank + totalEquipment;
}
/**
* Format money value for display
*
* @param value - Money value
* @returns Formatted string with $ prefix and comma separators
*/
export function formatMoney(value: number): string {
return `$${value.toLocaleString()}`;
}