/** * PokeRogue Type Data * Type IDs match PokeRogue's PokemonType enum (src/enums/pokemon-type.ts) * Type chart derived from PokeRogue's getTypeChartMultiplier (src/data/type.ts) * Colors from PokeRogue's getTypeRgb (src/data/type.ts) */ // Type ID constants matching PokeRogue's enum const TYPES = { NORMAL: 0, FIGHTING: 1, FLYING: 2, POISON: 3, GROUND: 4, ROCK: 5, BUG: 6, GHOST: 7, STEEL: 8, FIRE: 9, WATER: 10, GRASS: 11, ELECTRIC: 12, PSYCHIC: 13, ICE: 14, DRAGON: 15, DARK: 16, FAIRY: 17 }; // Human-readable names const TYPE_NAMES = { 0: 'Normal', 1: 'Fighting', 2: 'Flying', 3: 'Poison', 4: 'Ground', 5: 'Rock', 6: 'Bug', 7: 'Ghost', 8: 'Steel', 9: 'Fire', 10: 'Water', 11: 'Grass', 12: 'Electric', 13: 'Psychic', 14: 'Ice', 15: 'Dragon', 16: 'Dark', 17: 'Fairy' }; // RGB colors from PokeRogue source (getTypeRgb) const TYPE_COLORS = { 0: [168, 168, 120], // Normal 1: [192, 48, 40], // Fighting 2: [168, 144, 240], // Flying 3: [160, 64, 160], // Poison 4: [224, 192, 104], // Ground 5: [184, 160, 56], // Rock 6: [168, 184, 32], // Bug 7: [112, 88, 152], // Ghost 8: [184, 184, 208], // Steel 9: [240, 128, 48], // Fire 10: [104, 144, 240], // Water 11: [120, 200, 80], // Grass 12: [248, 208, 48], // Electric 13: [248, 88, 136], // Psychic 14: [152, 216, 216], // Ice 15: [112, 56, 248], // Dragon 16: [112, 88, 72], // Dark 17: [232, 136, 200] // Fairy }; // Move categories matching PokeRogue's MoveCategory enum const MOVE_CATEGORIES = { 0: 'Physical', 1: 'Special', 2: 'Status' }; const MOVE_CATEGORY_ICONS = { 0: '\u2694\uFE0F', // Physical - swords 1: '\uD83D\uDD2E', // Special - crystal ball 2: '\u2B50' // Status - star }; /** * TYPE_CHART[attackType][defenseType] = multiplier * * Derived from PokeRogue's getTypeChartMultiplier() which uses the * standard Gen 6+ type chart. Only non-1.0 entries are stored; * missing entries default to 1.0. * * Multipliers: 0 (immune), 0.5 (not very effective), 2 (super effective) * For dual types, multiply both: e.g. 2 * 2 = 4, 2 * 0.5 = 1, etc. */ const TYPE_CHART = { // NORMAL attacking 0: { 5: 0.5, 7: 0, 8: 0.5 }, // FIGHTING attacking 1: { 0: 2, 2: 0.5, 3: 0.5, 5: 2, 6: 0.5, 7: 0, 8: 2, 13: 0.5, 14: 2, 16: 2, 17: 0.5 }, // FLYING attacking 2: { 1: 2, 5: 0.5, 6: 2, 8: 0.5, 11: 2, 12: 0.5 }, // POISON attacking 3: { 3: 0.5, 4: 0.5, 5: 0.5, 7: 0.5, 8: 0, 11: 2, 17: 2 }, // GROUND attacking 4: { 2: 0, 6: 0.5, 3: 2, 5: 2, 8: 2, 9: 2, 11: 0.5, 12: 2 }, // ROCK attacking 5: { 1: 0.5, 2: 2, 4: 0.5, 6: 2, 8: 0.5, 9: 2, 14: 2 }, // BUG attacking 6: { 1: 0.5, 2: 0.5, 3: 0.5, 7: 0.5, 8: 0.5, 9: 0.5, 11: 2, 13: 2, 16: 2, 17: 0.5 }, // GHOST attacking 7: { 0: 0, 7: 2, 13: 2, 16: 0.5 }, // STEEL attacking 8: { 5: 2, 8: 0.5, 9: 0.5, 10: 0.5, 12: 0.5, 14: 2, 17: 2 }, // FIRE attacking 9: { 5: 0.5, 6: 2, 8: 2, 9: 0.5, 10: 0.5, 11: 2, 14: 2, 15: 0.5 }, // WATER attacking 10: { 4: 2, 5: 2, 9: 2, 10: 0.5, 11: 0.5, 15: 0.5 }, // GRASS attacking 11: { 2: 0.5, 3: 0.5, 4: 2, 5: 2, 6: 0.5, 8: 0.5, 9: 0.5, 10: 2, 11: 0.5, 15: 0.5 }, // ELECTRIC attacking 12: { 2: 2, 4: 0, 10: 2, 11: 0.5, 12: 0.5, 15: 0.5 }, // PSYCHIC attacking 13: { 1: 2, 3: 2, 8: 0.5, 13: 0.5, 16: 0 }, // ICE attacking 14: { 2: 2, 4: 2, 8: 0.5, 9: 0.5, 10: 0.5, 11: 2, 14: 0.5, 15: 2 }, // DRAGON attacking 15: { 8: 0.5, 15: 2, 17: 0 }, // DARK attacking 16: { 1: 0.5, 7: 2, 13: 2, 16: 0.5, 17: 0.5 }, // FAIRY attacking 17: { 1: 2, 3: 0.5, 8: 0.5, 9: 0.5, 15: 2, 16: 2 } }; /** * Get the effectiveness multiplier for an attack type vs a single defense type. * @param {number} attackType - Attacker's move type ID * @param {number} defenseType - Defender's type ID * @returns {number} Multiplier (0, 0.5, 1, or 2) */ function getTypeMult(attackType, defenseType) { if (attackType < 0 || attackType > 17 || defenseType < 0 || defenseType > 17) { return 1; } const row = TYPE_CHART[attackType]; if (!row || row[defenseType] === undefined) { return 1; } return row[defenseType]; } /** * Get effectiveness multiplier for an attack type vs a Pokemon's type(s). * For dual-type Pokemon, multiplies both matchups. * @param {number} attackType - Attacker's move type ID * @param {number[]} defenseTypes - Array of defender's type IDs (1 or 2 types) * @returns {number} Combined multiplier (0, 0.25, 0.5, 1, 2, or 4) */ function getEffectiveness(attackType, defenseTypes) { if (!defenseTypes || defenseTypes.length === 0) return 1; let mult = 1; for (const defType of defenseTypes) { mult *= getTypeMult(attackType, defType); } return mult; } /** * Get a human-readable effectiveness label. * @param {number} multiplier * @returns {string} */ function getEffectivenessLabel(multiplier) { if (multiplier === 0) return 'Immune'; if (multiplier === 0.25) return 'Double Resist'; if (multiplier === 0.5) return 'Not Effective'; if (multiplier === 1) return 'Neutral'; if (multiplier === 2) return 'Super Effective'; if (multiplier === 4) return 'Ultra Effective'; return `${multiplier}x`; } /** * Get the CSS color for a type badge background. * @param {number} typeId * @returns {string} CSS rgb() color */ function getTypeColor(typeId) { const rgb = TYPE_COLORS[typeId]; if (!rgb) return 'rgb(128, 128, 128)'; return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; } /** * Get the CSS color for an effectiveness multiplier (offense coloring). * Colors from PokeRogue's getTypeDamageMultiplierColor. * @param {number} multiplier * @returns {string} Hex color */ function getEffectivenessColor(multiplier) { if (multiplier === 0) return '#929292'; if (multiplier <= 0.25) return '#FF5500'; if (multiplier === 0.5) return '#FE8E00'; if (multiplier === 1) return '#CCCCCC'; if (multiplier === 2) return '#4AA500'; if (multiplier >= 4) return '#52C200'; return '#CCCCCC'; } /** * Determine if text on a type badge should be white or dark. * @param {number} typeId * @returns {string} 'white' or '#222' */ function getTypeBadgeTextColor(typeId) { const rgb = TYPE_COLORS[typeId]; if (!rgb) return 'white'; // Perceived brightness const brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; return brightness > 160 ? '#222' : 'white'; }