Skip to main content
Glama
crew.ts4 kB
import { readXML } from './xml.js'; import { getString } from './strings.js'; import { join } from 'path'; import type { TankClass } from '../data/types.js'; interface RawSkillData { userString?: string; effectDescription?: string; tipDescription?: string; featuresDescription?: string; type?: string; [key: string]: unknown; } interface RawAvatarData { root: { skillsByClasses: Record<string, string>; skills: Record<string, RawSkillData>; }; } export interface CrewSkill { id: string; name: string; effect: string; tip?: string; features?: string; type: 'trigger' | 'continuous'; tankClasses: TankClass[]; bonuses: Record<string, number>; } let skillsCache: CrewSkill[] = []; let skillsByClass: Record<TankClass, string[]> = { lightTank: [], mediumTank: [], heavyTank: [], 'AT-SPG': [], }; function parseLocalizedString(key: string | undefined): string { if (!key) return ''; const result = getString(key); return result .replace(/%\(highlight_start\)s?/g, '') .replace(/%\(highlight_end\)s?/g, '') .replace(/%\((\w+)\)s?/g, (_, name) => `{${name}}`); } function extractBonuses(skill: RawSkillData): Record<string, number> { const bonuses: Record<string, number> = {}; const bonusKeys = [ 'chancePerLevel', 'shotDispersionFactor', 'enginePowerFactorPerLevel', 'shotDispersionFactorPerLevel', 'shotInBurstDispersionFactorPerLevel', 'activationChancePerLevel', 'rangedHitDamageCoefficient', 'captureSpeedFactorPerLevel', 'reloadFactorPerLevel', 'armourPiercingFactor', 'xpBonusFactorPerLevel', 'penaltyFactorPerLevel', 'turretRotationSpeedFactorPerLevel', 'healthBurnPerSecLossFactorPerLevel', 'gunReloadTimeFactorPerLevel', 'repairSpeedFactorPerLevel', 'deviceChanceToHitBoost', 'visionRadiusFactorPerLevel', 'aimingFactorPerLevel', 'camouflageFactorPerLevel', 'rotationSpeedFactorPerLevel', 'enemyInRadius', 'enemyCount', 'vehicleHealthFraction', ]; for (const key of bonusKeys) { if (typeof skill[key] === 'number') { bonuses[key] = skill[key] as number; } else if (typeof skill[key] === 'string') { const num = parseFloat(skill[key] as string); if (!isNaN(num)) bonuses[key] = num; } } return bonuses; } export async function loadCrewSkills(vehiclesPath: string): Promise<void> { const avatarPath = join(vehiclesPath, '..', 'tankmen', 'avatar.xml'); const data = await readXML<RawAvatarData>(avatarPath); const classSkills = data.root.skillsByClasses; for (const tankClass of Object.keys(classSkills) as TankClass[]) { const skillList = classSkills[tankClass]; skillsByClass[tankClass] = typeof skillList === 'string' ? skillList.split(/\s+/) : []; } const skills = data.root.skills; skillsCache = []; for (const [id, rawSkill] of Object.entries(skills)) { const skill = rawSkill as RawSkillData; const tankClasses: TankClass[] = []; for (const [tankClass, skillIds] of Object.entries(skillsByClass)) { if (skillIds.includes(id)) { tankClasses.push(tankClass as TankClass); } } skillsCache.push({ id, name: parseLocalizedString(skill.userString), effect: parseLocalizedString(skill.effectDescription), tip: skill.tipDescription ? parseLocalizedString(skill.tipDescription) : undefined, features: skill.featuresDescription ? parseLocalizedString(skill.featuresDescription) : undefined, type: (skill.type as 'trigger' | 'continuous') || 'continuous', tankClasses, bonuses: extractBonuses(skill), }); } } export function getCrewSkills(): CrewSkill[] { return skillsCache; } export function getSkillsByClass(tankClass: TankClass): CrewSkill[] { const skillIds = skillsByClass[tankClass]; return skillsCache.filter(s => skillIds.includes(s.id)); } export function getSkillById(id: string): CrewSkill | undefined { return skillsCache.find(s => s.id === id); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Revenant30102000/wotblitz-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server