add-hero-trait

📁 aclinia/torchlight-of-building 📅 Today
0
总安装量
1
周安装量
安装命令
npx skills add https://github.com/aclinia/torchlight-of-building --skill add-hero-trait

Agent 安装分布

amp 1
cline 1
opencode 1
cursor 1
continue 1
kimi-cli 1

Skill 文档

Adding Hero Trait Mods

Overview

Hero traits are implemented as mod factories in src/tli/hero/hero-trait-mods.ts. Each factory takes a level index (0-4) and returns an array of Mod objects. The trait’s description in src/data/hero-trait/hero-traits.ts is the source of truth for what mods and values to produce.

When to Use

  • Implementing a hero trait that isn’t yet in heroTraitModFactories
  • Adding calculation support for a hero trait’s mechanics

Step 0: Read the Trait Description

Always start here. Look up the trait in src/data/hero-trait/hero-traits.ts and read its affix field. This determines:

  • What mods to create and their types
  • The per-level values (formatted as (v1/v2/v3/v4/v5) for levels 1-5)
  • Whether it’s a player buff or enemy debuff (“damage taken by the enemy” = isEnemyDebuff: true)
  • Whether it stacks and the max stack count
  • Any conditions for activation

Project File Locations

Purpose File Path
Trait descriptions (source of truth) src/data/hero-trait/hero-traits.ts
Trait mod factories src/tli/hero/hero-trait-mods.ts
Mod type definitions src/tli/mod.ts
Stackable types src/tli/mod.ts (Stackables)
Condition types src/tli/mod.ts (Conditions)
Configuration interface & defaults src/tli/core.ts
Configuration schema src/lib/schemas/config.schema.ts
Configuration UI src/components/configuration/ConfigurationTab.tsx
Calculation engine src/tli/calcs/offense.ts

Implementation Checklist

1. Add Mod Factory (src/tli/hero/hero-trait-mods.ts)

Add an entry to heroTraitModFactories. The key must match the trait’s name field in hero-traits.ts exactly (it’s typed as HeroTraitName).

Constant mods (no level scaling):

"Trait Name": () => [
  { type: "SomeFlag" },
  { type: "DmgPct", value: 20, dmgModType: "cold", addn: true, cond: "some_condition" },
],

Level-scaled mods:

"Trait Name": (i) => [
  { type: "FrostbiteEffPct", value: [65, 90, 110, 130, 150][i] },
],

Stackable mods (per-stack scaling):

"Trait Name": (i) => [
  {
    type: "DmgPct",
    value: [8, 10, 12, 15, 18][i],
    dmgModType: "cold",
    addn: true,
    isEnemyDebuff: true,
    per: { stackable: "dance_of_frost", limit: 4 },
  },
],

Place the factory near other traits for the same hero, using the comment format:

// Frostfire Gemma: Frostbitten Heart (#2)

2. Add New Mod Types (if needed, src/tli/mod.ts)

If the trait needs a mod type not in ModDefinitions, add it:

interface ModDefinitions {
  // ... existing types ...
  NewModType: { value: number };  // or object for flag mods
}

3. Add New Stackable (if needed, src/tli/mod.ts)

If the trait has a per-stack mechanic, add a stackable to Stackables:

export const Stackables = [
  // ... existing values ...
  // hero-specific
  "stalker",
  "twisted_spacetime",
  "dance_of_frost",       // Add near other hero-specific stackables
  // ...
] as const;

4. Add New Condition (if needed, src/tli/mod.ts)

If the trait has a conditional activation, add to Conditions:

export const Conditions = [
  // ... existing values ...
  "frostbitten_heart_is_active",
  "new_condition_name",           // Add here
] as const;

Then wire it up in filterModsByCond in src/tli/calcs/offense.ts (the .with() chain).

5. Add Configuration Field (if needed)

If the trait introduces a user-configurable value (stack count, toggle, etc.), use the /add-configuration skill or follow these steps:

a. Add to Configuration interface (src/tli/core.ts):

// hero-specific config section
// default to 0
danceOfFrostStacks?: number;

b. Add default to DEFAULT_CONFIGURATION (src/tli/core.ts):

danceOfFrostStacks: undefined,

c. Add schema field (src/lib/schemas/config.schema.ts):

danceOfFrostStacks: z.number().optional().catch(d.danceOfFrostStacks),

d. Add UI control (src/components/configuration/ConfigurationTab.tsx):

<label className="text-right text-zinc-50">
  Dance of Frost Stacks
  <InfoTooltip text="Frostfire Gemma: Dance of Frost trait stacks" />
</label>
<NumberInput
  value={config.danceOfFrostStacks}
  onChange={(v) => onUpdate({ danceOfFrostStacks: v })}
  min={0}
/>

Place near other hero-specific config fields (after frostbittenHeartIsActive).

6. Wire Up in Calculation Engine (src/tli/calcs/offense.ts)

For stackables: Add a normalize() call in resolveModsForOffenseSkill:

normalize("dance_of_frost", config.danceOfFrostStacks ?? 0);

Place near other hero-specific normalizations (near pushErika1, pushYouga2, etc.).

For conditions: Add a .with() case in filterModsByCond:

.with("new_condition_name", () => config.newConditionField)

7. Verify

pnpm typecheck
pnpm test
pnpm check

Common Trait Patterns

Pattern Example Trait Implementation
Simple constant buff Frostbitten Heart () => [{ type: "DmgPct", ... }]
Level-scaled value Deepfreeze (i) => [{ ..., value: [v1,v2,v3,v4,v5][i] }]
Per-stack with config Dance of Frost per: { stackable: "x", limit: N } + config + normalize
Conditional activation Frostbitten Heart cond: "condition_name" + config boolean
Flag mod (enables mechanic) Wind Stalker { type: "WindStalker" } (object mod)
Override limit Deepfreeze { type: "MaxFrostbiteRatingLimitOverride", value: X }

Common Mistakes

Mistake Fix
Not reading the trait description first Always check src/data/hero-trait/hero-traits.ts for the affix field
Guessing values instead of reading description Values come from the (v1/v2/v3/v4/v5) format in the affix
Missing isEnemyDebuff: true “damage taken by the enemy” = enemy debuff, not player buff
Forgetting limit on per-stackable “stacks up to N times” needs limit: N
Not adding normalize() for new stackables Per-stackable mods won’t resolve without normalize() in offense.ts
Placing config UI in wrong section Hero-specific config goes near other hero fields
Trait name doesn’t match data Key must exactly match name in hero-traits.ts (typed as HeroTraitName)