一键导入
adding-mod-parsers
Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when adding new push* resolver functions inside resolveModsForOffenseSkill to handle mod-based combat mechanics like tangles, debuffs, or conditional damage bonuses (project)
Use when adding support mod parsers to convert support skill affix strings to typed SupportMod objects - guides the template-based parsing pattern (project)
Use when adding hero trait mod implementations in hero-trait-mods.ts - guides reading trait descriptions, creating mod factories, adding stackables/config, and wiring up the calculation engine (project)
Use when adding new configuration fields to the Configuration interface for combat conditions, buff stacks, or other build parameters used in damage calculations (project)
Use when implementing skill data generation from HTML sources for game build planners - guides the parser-factory-generation pattern for extracting level-scaling values for active and passive skills (project)
| name | adding-mod-parsers |
| description | Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project) |
The mod parser converts raw mod strings (e.g., "+10% all stats") into typed Mod objects used by the calculation engine. It uses a template-based system for pattern matching.
| Purpose | File Path |
|---|---|
| Mod type definitions | src/tli/mod.ts |
| Parser templates | src/tli/mod-parser/templates.ts |
| Enum registrations | src/tli/mod-parser/enums.ts |
| Calculation handlers | src/tli/calcs/offense.ts |
| Tests | src/tli/mod-parser.test.ts |
Look in src/tli/mod.ts under ModDefinitions. If the mod type doesn't exist, add it:
interface ModDefinitions {
// ... existing types ...
NewModType: { value: number; someField: string };
}
templates.tsTemplates use a DSL for pattern matching. Do not add comments to templates.ts - the template string itself is self-documenting.
t("{value:dec%} all stats").output((c) => ({
type: "StatPct",
value: c.value,
statModType: "all",
})),
t("{value:dec%} {statModType:StatWord}")
.enum("StatWord", StatWordMapping)
.output((c) => ({ type: "StatPct", value: c.value, statModType: c.statModType })),
t("{value:dec%} [additional] [{modType:DmgModType}] damage").output((c) => ({
type: "DmgPct",
value: c.value,
dmgModType: c.modType ?? "global",
addn: c.additional !== undefined,
})),
t("{value:dec%} attack and cast speed").outputMany([
spec((c) => ({ type: "AspdPct", value: c.value, addn: false })),
spec((c) => ({ type: "CspdPct", value: c.value, addn: false })),
]),
Template capture types:
| Type | Matches | Example Input → Output |
|---|---|---|
{name:int} | Unsigned integer | "5" → 5 |
{name:dec} | Unsigned decimal | "21.5" → 21.5 |
{name:int%} | Unsigned integer percent | "30%" → 30 |
{name:dec%} | Unsigned decimal percent | "96%" → 96 |
{name:+int} | Signed integer (requires + or -) | "+5" → 5, "-3" → -3 |
{name:+dec} | Signed decimal (requires + or -) | "+21.5" → 21.5 |
{name:+int%} | Signed integer percent | "+30%" → 30, "-15%" → -15 |
{name:+dec%} | Signed decimal percent | "+96%" → 96 |
{name:?int} | Optional-sign integer (matches with or without +/-) | "5" → 5, "+5" → 5, "-3" → -3 |
{name:?dec} | Optional-sign decimal | "21.5" → 21.5, "+21.5" → 21.5 |
{name:?int%} | Optional-sign integer percent | "30%" → 30, "+30%" → 30 |
{name:?dec%} | Optional-sign decimal percent | "96%" → 96, "+96%" → 96 |
{name:EnumType} | Enum lookup | {dmgType:DmgChunkType} |
Signed vs Unsigned vs Optional-sign Types:
dec%, int) when input NEVER has + or - (e.g., "8% additional damage applied to Life")+dec%, +int) when input ALWAYS has + or - (e.g., "+25% additional damage")?dec%, ?int) when input MAY OR MAY NOT have a sign — this avoids needing two separate templates for signed/unsigned variants?dec% over two separate dec%/+dec% templates when the same mod can appear with or without a signOptional syntax:
[additional] - Optional literal, sets c.additional?: true[{modType:DmgModType}] - Optional capture, sets c.modType?: DmgModType{(effect|damage)} - Alternation (regex-style)If you need custom word → value mapping, add to enums.ts:
export const StatWordMapping: Record<string, string> = {
strength: "str",
dexterity: "dex",
intelligence: "int",
};
registerEnum("StatWord", ["strength", "dexterity", "intelligence"]);
offense.ts (if new mod type)If you added a new mod type, add handling in calculateOffense() or relevant helper:
case "NewModType": {
break;
}
For existing mod types with new variants (like adding statModType: "all"), update existing handlers to also filter for the new variant:
const flat = sumByValue(
statMods.filter((m) => m.statModType === statType || m.statModType === "all"),
);
Add test cases in src/tli/mod_parser.test.ts:
test("parse percentage all stats", () => {
const result = parseMod("+10% all stats");
expect(result).toEqual([
{
type: "StatPct",
statModType: "all",
value: 10,
},
]);
});
pnpm test src/tli/mod_parser.test.ts
pnpm typecheck
pnpm check
IMPORTANT: More specific patterns must come before generic ones in allParsers array.
// Good: specific before generic
t("{value:dec%} all stats").output(...), // Specific
t("{value:dec%} {statModType:StatWord}").output(...), // Generic
// Bad: generic would match first and fail on "all stats"
Input: "+10% all stats" (starts with +)
t("{value:+dec%} all stats").output((c) => ({
type: "StatPct",
value: c.value,
statModType: "all",
})),
Input: "8% additional damage applied to Life" (no sign)
t("{value:dec%} additional damage applied to life").output((c) => ({
type: "DmgPct",
value: c.value,
dmgModType: "global",
addn: true,
})),
Input: "+40% damage if you have Blocked recently"
t("{value:+dec%} damage if you have blocked recently").output((c) => ({
type: "DmgPct",
value: c.value,
dmgModType: "global",
addn: false,
cond: "has_blocked_recently",
})),
Input: "Deals +1% additional damage to an enemy for every 2 points of Frostbite Rating the enemy has"
Note: The + appears AFTER "deals", so use {value:+dec%}:
t("deals {value:+dec%} additional damage to an enemy for every {amt:int} points of frostbite rating the enemy has")
.output((c) => ({
type: "DmgPct",
value: c.value,
dmgModType: "global",
addn: true,
per: { stackable: "frostbite_rating", amt: c.amt },
})),
Input: "+6% attack and cast speed"
t("{value:+dec%} [additional] attack and cast speed").outputMany([
spec((c) => ({ type: "AspdPct", value: c.value, addn: c.additional !== undefined })),
spec((c) => ({ type: "CspdPct", value: c.value, addn: c.additional !== undefined })),
]),
Input: "+166 Max Mana"
t("{value:+dec} max mana").output((c) => ({ type: "MaxMana", value: c.value })),
Input: "12.5% Sealed Mana Compensation for Spirit Magus Skills" OR "+12.5% Sealed Mana Compensation for Spirit Magus Skills"
Use ?dec% when the same mod string can appear with or without a +/- sign, avoiding the need for two separate templates:
t("{value:?dec%} sealed mana compensation for spirit magus skills").output(
(c) => ({ type: "SealedManaCompPct", value: c.value, addn: false, skillType: "spirit_magus" }),
),
Input: "Energy Shield starts to Charge when Blocking"
Use outputNone() when a mod string should be recognized (not flagged as unparsed) but has no effect on calculations:
t("energy shield starts to charge when blocking").outputNone(),
| Mistake | Fix |
|---|---|
Using dec% for input with + prefix | Use +dec% for inputs like "+25% damage", or ?dec% if sign is optional |
Using +dec% for input without sign | Use dec% for inputs like "8% damage applied to life", or ?dec% if sign is optional |
| Two templates for signed/unsigned variants of the same mod | Use ?dec% to match both in a single template |
| Template doesn't match input case | Templates are matched case-insensitively; input is normalized to lowercase |
Missing type field in output mapper | Include type: "ModType" in the returned object — contextual typing from the Mod discriminated union handles narrowing |
| Handler doesn't account for new variant | Update offense.ts to handle new values (e.g., statModType === "all") |
| Generic template before specific | Move specific templates earlier in allParsers array |
Raw string: "+10% all stats"
↓ normalize (lowercase, trim)
"10% all stats"
↓ template matching (allParsers)
{ type: "StatPct", value: 10, statModType: "all" }
↓ calculateStats() in offense.ts
Applied to str, dex, int calculations