implementing-game-skill-parsers

Implementing Game Skill Parsers

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "implementing-game-skill-parsers" with this command: npx skills add aclinia/torchlight-of-building/aclinia-torchlight-of-building-implementing-game-skill-parsers

Implementing Game Skill Parsers

Overview

Skill data generation follows a parser-factory-generation pattern:

  • Parser extracts numeric values from HTML/data sources with named keys

  • Factory defines how to build Mod objects using those named values

  • Generation script combines parsed values into levelValues output

Critical: Parser keys MUST match factory key usage exactly.

Note: This skill covers active and passive skills only. For support skills, see the adding-support-mod-parsers skill.

When to Use

  • Adding new active or passive skills with level-scaling properties

  • Extracting values from game data HTML pages

Project File Locations

Purpose File Path

Active factories src/tli/skills/active-factories.ts

Passive factories src/tli/skills/passive-factories.ts

Factory types & helpers src/tli/skills/types.ts

Active parsers src/scripts/skills/active-parsers.ts

Passive parsers src/scripts/skills/passive-parsers.ts

Parser registry src/scripts/skills/index.ts

Generation script src/scripts/generate-skill-data.ts

HTML data sources .garbage/tlidb/skill/{category}/{Skill_Name}.html

Categories: active , passive , activation_medium

Implementation Checklist

  1. Identify Data Source
  • HTML file at .garbage/tlidb/skill/{category}/{Skill_Name}.html

  • Find Progression /40 table - columns are: level, col0, col1, col2 (Descript)

  • Column indexing: values[0] = first column after level, values[2] = Descript

  • Input is clean text (HTML already stripped by buildProgressionTableInput )

  1. Define Factory (structure + key names)

// In active-factories.ts or passive-factories.ts import { v } from "./types";

"Ice Bond": (l, vals) => ({ buffMods: [ { type: "DmgPct", value: v(vals.coldDmgPctVsFrostbitten, l), // Define key name here addn: true, dmgModType: "cold", cond: "enemy_frostbitten", }, ], }),

Factory return types:

  • Active skills: { offense?: SkillOffense; mods?: Mod[]; buffMods?: Mod[] }

  • Passive skills: { mods?: Mod[]; buffMods?: Mod[] }

SkillOffense is a structured interface, NOT an array:

interface SkillOffense { weaponAtkDmgPct?: { value: number }; addedDmgEffPct?: { value: number }; persistentDmg?: { value: number; dmgType: DmgChunkType; duration: number }; spellDmg?: { value: DmgRange; dmgType: DmgChunkType; castTime: number }; // Multi-phase attack skills (e.g., Berserking Blade) sweepWeaponAtkDmgPct?: { value: number }; sweepAddedDmgEffPct?: { value: number }; steepWeaponAtkDmgPct?: { value: number }; steepAddedDmgEffPct?: { value: number }; }

The v(arr, level) helper safely accesses arr[level - 1] with bounds checking.

Key naming conventions:

  • Use descriptive camelCase names

  • Include context: dmgPctPerProjectile not just dmgPct

  1. Create Parser (extract values for those keys)

// In active-parsers.ts or passive-parsers.ts import { findColumn, validateAllLevels } from "./progression-table"; import { template } from "./template-compiler"; import type { SupportLevelParser } from "./types"; import { createConstantLevels } from "./utils";

export const iceBondParser: SupportLevelParser = (input) => { const { skillName, progressionTable } = input;

// Find column by header (uses substring matching) const descriptCol = findColumn(progressionTable, "descript", skillName); const coldDmgPctVsFrostbitten: Record<number, number> = {};

// Iterate over column rows (level → text) for (const [levelStr, text] of Object.entries(descriptCol.rows)) { const level = Number(levelStr); // Use template() for pattern matching - cleaner than regex const match = template("{value:dec%} additional cold damage").match( text, skillName, ); coldDmgPctVsFrostbitten[level] = match.value; }

validateAllLevels(coldDmgPctVsFrostbitten, skillName);

// Return named keys matching factory expectations return { coldDmgPctVsFrostbitten }; };

Template syntax for value extraction:

  • {name:int}

  • Integer (e.g., "5" → 5)

  • {name:dec}

  • Decimal (e.g., "21.5" → 21.5)

  • {name:dec%}

  • Percentage as decimal (e.g., "96%" → 96, NOT 0.96)

  • {name:int%}

  • Percentage as integer (e.g., "-30%" → -30)

For constant values (same across all levels): use createConstantLevels(value)

  1. Register Parser

// In index.ts { skillName: "Ice Bond", categories: ["active"], parser: iceBondParser }

  1. Regenerate & Verify

pnpm exec tsx src/scripts/generate_skill_data.ts pnpm test

Check generated output for levels 1, 20, 40 against source HTML.

Example: Complex Skill (Frost Spike)

Parser extracts multiple named values:

export const frostSpikeParser: SupportLevelParser = (input) => { const weaponAtkDmgPct: Record<number, number> = {}; const addedDmgEffPct: Record<number, number> = {}; // ... extract from columns ...

return { weaponAtkDmgPct, addedDmgEffPct, convertPhysicalToColdPct: createConstantLevels(convertValue), maxProjectile: createConstantLevels(maxProjValue), projectilePerFrostbiteRating: createConstantLevels(projPerRating), baseProjectile: createConstantLevels(baseProj), dmgPctPerProjectile: createConstantLevels(dmgPerProj), }; };

Factory uses those keys:

"Frost Spike": (l, vals) => ({ offense: { weaponAtkDmgPct: { value: v(vals.weaponAtkDmgPct, l) }, addedDmgEffPct: { value: v(vals.addedDmgEffPct, l) }, }, mods: [ { type: "ConvertDmgPct", value: v(vals.convertPhysicalToColdPct, l), from: "physical", to: "cold" }, { type: "MaxProjectile", value: v(vals.maxProjectile, l), override: true }, { type: "Projectile", value: v(vals.projectilePerFrostbiteRating, l), per: { stackable: "frostbite_rating", amt: 35 } }, { type: "BaseProjectileQuant", value: v(vals.baseProjectile, l) }, { type: "DmgPct", value: v(vals.dmgPctPerProjectile, l), dmgModType: "global", addn: true, per: { stackable: "projectile" } }, ], }),

Generated output:

levelValues: { weaponAtkDmgPct: [1.49, 1.51, 1.54, ...], addedDmgEffPct: [1.49, 1.51, 1.54, ...], convertPhysicalToColdPct: [1, 1, 1, ...], maxProjectile: [5, 5, 5, ...], projectilePerFrostbiteRating: [1, 1, 1, ...], baseProjectile: [2, 2, 2, ...], dmgPctPerProjectile: [0.08, 0.08, 0.08, ...], }

Example: Multi-Phase Attack Skill (Berserking Blade)

For skills with multiple attack phases, use the dedicated offense properties:

"Berserking Blade": (l, vals) => ({ offense: { // Sweep phase stats sweepWeaponAtkDmgPct: { value: v(vals.sweepWeaponAtkDmgPct, l) }, sweepAddedDmgEffPct: { value: v(vals.sweepAddedDmgEffPct, l) }, // Steep strike phase stats steepWeaponAtkDmgPct: { value: v(vals.steepWeaponAtkDmgPct, l) }, steepAddedDmgEffPct: { value: v(vals.steepAddedDmgEffPct, l) }, }, mods: [ { type: "SkillAreaPct", skillAreaModType: "global" as const, value: v(vals.skillAreaBuffPct, l), per: { stackable: "berserking_blade_buff" }, }, { type: "MaxBerserkingBladeStacks", value: v(vals.maxBerserkingBladeStacks, l) }, { type: "SteepStrikeChancePct", value: v(vals.steepStrikeChancePct, l) }, ], }),

Example: Spell Skill (Chain Lightning)

Spell skills use spellDmg with damage range and cast time:

"Chain Lightning": (l, vals) => ({ offense: { addedDmgEffPct: { value: v(vals.addedDmgEffPct, l) }, spellDmg: { value: { min: v(vals.spellDmgMin, l), max: v(vals.spellDmgMax, l) }, dmgType: "lightning", castTime: v(vals.castTime, l), }, }, mods: [{ type: "Jump", value: v(vals.jump, l) }], }),

Common Mistakes

Mistake Fix

Using array for offense offense is a SkillOffense object, NOT an array. Use offense: { weaponAtkDmgPct: { value: ... } }

Using modType in DmgPct mods Use dmgModType instead of modType

Using HTML regex on clean text Input is already .text().trim()

  • no HTML tags

Parser key doesn't match factory key Keys must match exactly: vals.dmgPct needs parser to return { dmgPct: ... }

Forgetting parser registration Add to SKILL_PARSERS array in index.ts

Missing factory Must add factory in *-factories.ts for mods to be applied at runtime

findColumn substring collision "damage" matches "Effectiveness of added damage" first - use exact matching (see below)

Missing levels 21-40 Many skills only have data for levels 1-20; fill 21-40 with level 20 values

findColumn Gotcha: Substring Matching

findColumn uses template substring matching. If column headers share substrings, you may get the wrong column:

// PROBLEM: "damage" is a substring of "Effectiveness of added damage" // This returns the WRONG column! const damageCol = findColumn(progressionTable, "damage", skillName);

// SOLUTION: Use exact header matching when there's a collision const damageCol = progressionTable.find( (col) => col.header.toLowerCase() === "damage", ); if (!damageCol) { throw new Error(${skillName}: no "damage" column found); }

Handling Levels 21-40 with Empty Data

Many skills only have progression data for levels 1-20. Fill levels 21-40 with level 20 values:

// Extract levels 1-20 for (const [levelStr, text] of Object.entries(someCol.rows)) { const level = Number(levelStr); if (level <= 20 && text !== "") { values[level] = parseValue(text); } }

// Fill levels 21-40 with level 20 value const level20Value = values[20]; if (level20Value === undefined) { throw new Error(${skillName}: level 20 value missing); } for (let level = 21; level <= 40; level++) { values[level] = level20Value; }

Data Flow

HTML Source → buildProgressionTableInput (strips HTML) → Parser (extracts values with named keys) → Generation Script (converts to levelValues arrays) → Output TypeScript file ↓ Runtime: Factory + levelValues → Mod objects

Benefits of Named Keys

  • Self-documenting: vals.projectilePerFrostbiteRating is clearer than vals[4]

  • Order-independent: Parser and factory don't need to agree on array order

  • Extensible: Adding new values doesn't shift existing indices

  • Type-safe: TypeScript can catch typos in key names

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

adding-support-mod-parsers

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

adding-mod-parsers

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

add-hero-trait

No summary provided by upstream source.

Repository SourceNeeds Review