Skip to content

Skill Definition: All Fields

Previous: Skill File Structure · Next: graph & combos

This page is a field-by-field reference manual for the skill definition file (skills/<category>/xxx.yml). All fields are grouped by function, with one table + a small example per group. While configuring a skill, jump to the relevant section whenever you get stuck.

💡 No need to read it all at once. For your first skill, you only need to write id / display / meta / trigger / type / cooldown to get it running (see the "minimal working template" at the end). Add the rest as needed.

The fields on this page reflect what SkillDefinitionLoader actually loads, corresponding to QS 1.0.22.

🖼️ [Image placeholder] A diagram of skill yml field groups (basic / trigger / gating / target / cost / advanced) · suggested assets/field-groups.png


🔑 One rule running through the whole page: aliases and priority

Many fields have two ways to write them: a nested block (e.g. cooldown.base) and a top-level alias (e.g. cooldown_ms). Both work, but when both are present, who wins follows a fixed priority:

Nested form (wins)Top-level aliasWho wins
cooldown.base / cooldown.group / cooldown.chargescooldown_ms / cooldown_group / chargesNested block wins
cost.health / cost.hungerhealth_cost / hunger_costNested block wins
gcd.triggers_ms / gcd.ignoregcd_ms / ignore_gcdNested block wins
execution.mythic_skillmythic_skill (top-level)execution.mythic_skill wins

⚠️ Don't write both and then wonder why your change had no effect. Remember one sentence: the nested block / execution.mythic_skill always wins. The examples on this page all use the nested form.


1️⃣ Basic identity

FieldTypeDefaultDescription
idStringfilenameUnique skill id, all lowercase; everything else (items / combos / commands) references it
displayString= idDisplay name, supports & color codes
typeactive / passiveactiveThis top-level line is what actually decides active / passive; the loader reads it to set SkillType
max_levelInt1Maximum level, paired with levels to build growth curves
tagsList[]Tags purely for display / search
yaml
id: fire_wave
display: "&c火焰波"
type: active                 # ← this line decides active/passive (write passive for passive skills)
max_level: 5
tags: [fire, aoe, combat]

2️⃣ Ecosystem info (meta / trigger / state / graph / execution)

This set of fields feeds the /qs reload consistency check and serves as a reference for combo orchestration. A missing meta raises a schema warning.

FieldValuesDescription
meta.categorycombat/movement/utility/boss/comboCategory, must match the folder the file is in
meta.typeactive/passive/reactiveLabel only (what actually takes effect is the top-level type)
meta.rankbasic/advanced/elite/bossDisplay-only tier
trigger.primaryTriggerTypePrimary trigger key, must be in the graph entry node's triggers
state.requiredSkillStateState required to cast, defaults to IDLE; must match the entry node's require_state
graph.entryStringWhich graph to use (found by graph_id)
execution.mythic_skillStringThe MM skill actually executed, highest priority (> top-level mythic_skill)
yaml
meta:
  category: combat
  type: active                # label, classification only
  rank: basic                 # display only
trigger:
  primary: RIGHT_CLICK        # must appear in the graph entry node's triggers
state:
  required: IDLE              # must match the entry node's require_state
graph:
  entry: fire_wave            # found by graph_id
execution:
  mythic_skill: fire_wave     # the MM skill actually executed (takes priority over top-level mythic_skill)

For all TriggerTypes and graph node details, see graph & combos.


3️⃣ Cooldown / charges / cooldown group (cooldown)

Field (nested / alias)TypeDefaultDescription
cooldown.base / cooldown_msLong(ms)0Cooldown duration, in milliseconds. 3000 = 3 seconds
cooldown.group / cooldown_groupString?noneCooldown group: skills in the same group share a cooldown
cooldown.charges / chargesInt1Charge stacks; when >1 it replaces the binary cooldown, recovering one stack at a time per base
yaml
cooldown:
  base: 3000                  # 3-second cooldown
  group: fire                 # same group (e.g. all fire skills) shares a cooldown
  charges: 3                  # 3 charges: after spending one it recovers, refilling one stack every 3 seconds

💡 Binary cooldown vs charges: leaving charges unset or = 1 gives the ordinary "ready / on cooldown" binary state; charges > 1 switches to a charge system of "you can cast as many times as you have stacks," each stack recovering over base time. See Cooldown / Charges / GCD & Conflicts for details.

⚠️ cooldown.base has a prerequisite — it requires meta.category. The entire meta.* / trigger.* / graph.* / execution.* ecosystem info (including the cooldown.base nested block) is only parsed when the skill file contains meta.category — when SkillSchemaParser sees no meta.category, it returns null immediately. Without meta.category, cooldown.base has no effect and you must use the top-level cooldown_ms instead.

Also: when a levels.N omits its cooldown, it only falls back to the top-level cooldown_ms, not to cooldown.base. So if you define cooldown via cooldown.base but some level doesn't write cooldown_ms, that level gets 0 (no cooldown).

Recommendation: either write both meta.category + cooldown.base, or stick to the top-level cooldown_ms and write cooldown_ms explicitly in every level.

(Note: all bundled examples include meta.category, so the cooldown.base in the examples all work correctly.)


4️⃣ Resource cost (resource)

FieldTypeDescription
resource.<key>MapResource cost, e.g. mana: 15
yaml
resource:
  mana: 15                    # casting costs 15 mana

⚠️ About mana: resource pools like mana will eventually be managed by QinhClass (QC); for now they're a temporary placeholder. Use it like this for now — once QC takes over, the skill yml won't need to change. See the responsibility boundaries in Core Concepts.


5️⃣ Cast mode (cast_mode)

ValueMeaning
instant (default)Instant cast
toggleToggle (press again to turn off)
channelChanneled cast bar (charge-up bar)
yaml
cast_mode: instant           # instant / toggle / channel

For the full channel (channel) fields see §10 below; for toggle (toggle) details see Cast Modes & Channeling.


6️⃣ Target & targeting (target)

target can be written as a scalar (mode only) or a block (with range / filter, etc.).

Scalar form:

yaml
target: NEAREST              # write the mode directly

Available modes: SELF (yourself) / LOOK (crosshair ray) / NEAREST (closest) / FARTHEST (farthest) / LOWEST_HP (most wounded, for finishing) / HIGHEST_HP (tankiest) / RANDOM (random).

Block form (more granular):

FieldDefaultDescription
target.modeSELFOne of the modes above
target.range30Targeting radius (blocks), range 1–100
target.filterLIVINGANY / LIVING / MONSTERS (mobs only) / PLAYERS (players only) / NOT_PLAYERS (non-player creatures)
target.requiredfalsetrue = if nothing is locked, the cast fails ("no available target" prompt)
target.require_losfalsetrue = only lock targets with line of sight (not through walls)
yaml
target:
  mode: NEAREST              # pick the closest
  range: 6                   # within 6 blocks
  filter: MONSTERS           # mobs only
  required: true             # fails if no target
  require_los: true          # only lock visible ones (not through walls)

💡 The target QS picks is passed to the MM skill as @Target. require_los mainly affects NEAREST (which locks through walls by default); LOOK is a ray to begin with. See Target & Targeting for the full explanation.


7️⃣ Global cooldown GCD (gcd)

Field (nested / alias)TypeDefaultDescription
gcd.triggers_ms / gcd_msLong(ms)0Duration of a brief lockout of all character skills after casting; 0 = doesn't trigger GCD
gcd.ignore / ignore_gcdBooleanfalsetrue = this skill is not subject to the GCD
yaml
gcd:
  triggers_ms: 800           # for 0.8s after casting, all skills are blocked
  ignore: false              # whether this skill itself is subject to the GCD

💡 Damage skills often set triggers_ms to prevent firing several skills in a single frame; instant mobility / interrupt skills often set ignore: true so they aren't locked by another skill's GCD.


8️⃣ Health / hunger cost (cost)

Uses vanilla health / hunger as the cost (not the QC mana pool), suitable for "blood-sacrifice" skills.

Field (nested / alias)TypeDescription
cost.health / health_costDoubleHealth deducted, half a heart = 1; if health ≤ this value the cast fails, and after deduction it floors at 0.5
cost.hunger / hunger_costIntHunger deducted
yaml
cost:
  health: 4.0                # deduct 2 hearts; fails if health is insufficient, leaves at least 0.5
  hunger: 2                  # deduct 2 hunger

9️⃣ Cooldown ready notify (ready_notify)

Sends an actionbar + sound the instant the cooldown ends.

Field (nested / alias)DefaultDescription
ready_notify.enabled / ready_notify: truefalseToggle
ready_notify.soundblock.note_block.plingSound id (dots / underscores both fine)
ready_notify.message&a{skill} &7已就绪Actionbar text, {skill} = display name
yaml
ready_notify:
  enabled: true
  sound: block.note_block.pling
  message: "&7{skill} &f已就绪"   # {skill} is replaced with the display name

⚠️ Only applies to binary-cooldown skills; not applicable to charge skills (charges > 1).


🔟 Channeled cast bar (channel)

Only takes effect with cast_mode: channel; channeling only truly begins when time_ticks > 0, otherwise it degrades to instant cast.

FieldDefaultDescription
channel.time_ticks (alias channel.ticks)0Cast bar duration (ticks, 20 = 1 second), >0 to enter channeling; the alias channel.ticks is equivalent
channel.bar_typebossbarCast bar UI: bossbar (top) / actionbar (text below) / none
channel.interrupt_on_movetrue (from config)Interrupt on movement
channel.move_threshold0.5Movement threshold (blocks); exceeding it interrupts
channel.interrupt_on_damagetrueInterrupt on taking damage
channel.cost_on_startfalsetrue = deduct resources at the start (anti-spam); false = deduct on completion
channel.cooldown_on_startfalsetrue = enter cooldown at the start; false = enter cooldown on completion
yaml
cast_mode: channel
channel:
  time_ticks: 40             # 2-second cast bar
  bar_type: bossbar          # top boss-bar style progress
  interrupt_on_move: true    # interrupt on moving
  move_threshold: 0.5
  interrupt_on_damage: true  # interrupt on taking damage
  cost_on_start: false       # only deduct on completion (no loss if interrupted)
  cooldown_on_start: false   # only enter cooldown on completion

For the full channeling mechanics see Cast Modes & Channeling.


1️⃣1️⃣ Cast conditions (conditions)

A declarative list of prerequisites; the skill casts only when all are met; an empty list = no restrictions.

yaml
conditions:
  - "player_level:>=5"        # player level ≥ 5
  - "player_in_world:world"   # in the main world
  - "player_health_pct:>=50"  # health ≥ 50%
  - "has_target:true"         # has a target
  - "target_type:ZOMBIE"      # target is a zombie
  - "target_distance:<=10"    # target within 10 blocks

The syntax is key:value, where the value may carry a comparator >= <= == != > < = (defaults to =). Unknown keys are always true (a typo won't lock the skill out). Overview of available keys:

KeyDescription
player_levelPlayer level
player_health / player_health_pctAbsolute health / percentage (0–100)
player_foodHunger value
player_in_worldWorld name
player_has_permissionPermission node
player_gamemodeSURVIVAL / CREATIVE…
player_sneaking / player_sprinting / player_on_fire / player_on_groundBoolean
player_yY coordinate
has_targetWhether there's a target
target_typeTarget entity type (e.g. ZOMBIE)
target_distanceDistance to the target

For full condition usage see Cost / Conditions / Variables. For complex logic, use script.pre_js in §15 below.


1️⃣2️⃣ Conflict groups & combo markers

FieldTypeDescription
conflict_groupsSet<String>Conflict group: after casting, same-group skills are briefly unavailable, for a window of gate.conflict_window_ms (default 1000ms)
combo_groupString?Combo grouping marker
yaml
conflict_groups:
  - melee_burst              # for about 1 second after casting, other same-group skills can't be cast
combo_group: fire_combo      # combo grouping marker

1️⃣3️⃣ Variable passthrough (variables)

Passes custom key-value pairs through to MM skill variables <skill.var.key>.

yaml
variables:
  element: fire              # in MM, read "fire" via <skill.var.element>

QS also auto-injects built-in variables like mode / source / slot / player / origin; see Cost / Conditions / Variables for details.


1️⃣4️⃣ Per-level overrides (levels)

Override cooldown / cost / params by level. params are passed through to MM (read in MM via <skill.var.power>).

yaml
levels:
  1:
    cooldown_ms: 3000
    resource: { mana: 15 }
    params: { power: "1.0" }   # level-1 power
  2:
    cooldown_ms: 2800
    resource: { mana: 18 }
    params: { power: "1.2" }   # upgrade: shorter cooldown, higher power
Sub-fieldDescription
levels.N.cooldown_msCooldown for that level
levels.N.resourceResource cost for that level
levels.N.paramsNumeric params passed through to MM (MM decides how to use them)

1️⃣5️⃣ Script hooks (script)

FieldDescription
script.pre_jsRuns before casting; returning false blocks the cast
script.post_jsRuns after a successful cast (side effects)
yaml
script:
  pre_js: "qinhskills:demo.js:canCast"    # returning false means the cast fails
  post_js: "qinhskills:demo.js:onCast"     # runs effects after a successful cast

For how to write scripts see Scripting.


1️⃣6️⃣ Trigger sources (active_triggers / passive_triggers)

Active trigger sources active_triggers, defaulting to [QI_ACTION] (QinhItems item trigger):

yaml
active_triggers:
  - type: KEY_SLOT           # KEY_SLOT / QI_ACTION / COMMAND / API
    slot: 1                  # optional; only meaningful for KEY_SLOT (which skill slot to bind to)
  - type: QI_ACTION          # default; QinhItems item trigger
FieldTypeDefaultDescription
typeEnumQI_ACTIONTrigger source: KEY_SLOT / QI_ACTION / COMMAND / API
slotIntnoneOptional, only meaningful for type: KEY_SLOT — which skill slot to bind to
typeTrigger source
KEY_SLOTKey-bound skill slot (use slot to specify the slot)
QI_ACTIONQinhItems item key (default)
COMMANDCommand bridge /qs cast
APIExternal plugin API

Passive triggers passive_triggers (only for type: passive skills). Each entry supports the following fields:

FieldTypeDefaultDescription
typeEnumrequiredPassive trigger type (on damaged / attack / kill / low health…)
idString= type lowercasedIdentifier for this passive
cooldown_msLong(ms)0Rate-limit cooldown for this passive; be sure to set it for high-frequency passives
threshold_pctDouble30.0Health percentage threshold; only used by threshold-type triggers like ON_LOW_HEALTH
yaml
type: passive                # write passive at the top level too
passive_triggers:
  - type: ON_DAMAGED         # triggers on taking damage
    id: thorns
    cooldown_ms: 1500        # always rate-limit high-frequency passives
  - type: ON_LOW_HEALTH      # low-health trigger (threshold type)
    id: last_stand
    threshold_pct: 30.0      # only triggers when health ≤ 30%

For the full description of all 11 passive triggers (on damaged / attack / kill / low health / sneak / jump / sprint / mine / respawn / fall / periodic), see Passive Skills.


📊 Priority quick reference

When both nested and alias forms are written, who takes effect:

text
cooldown.base / .group / .charges   >   cooldown_ms / cooldown_group / charges
cost.health / .hunger               >   health_cost / hunger_cost
gcd.triggers_ms / .ignore           >   gcd_ms / ignore_gcd
execution.mythic_skill              >   mythic_skill (top-level)

Mnemonic: the nested block always wins; execution.mythic_skill always wins.


📄 Worked examples (annotated field by field)

Example A: fire_wave — the most basic active skill

yaml
id: fire_wave                   # unique skill id, all lowercase; defaults to filename if omitted
display: "&c火焰波"              # display name, supports & color codes

#--- Ecosystem info: category filing + reload consistency check ---
meta:
  category: combat              # one of the five fixed categories
  type: active                  # classification label: active / passive / reactive
  rank: basic                   # tier label (display only)
trigger:
  primary: RIGHT_CLICK          # primary trigger key, must be in the graph entry node's triggers
state:
  required: IDLE                # state required to cast. Must match the entry node's require_state
graph:
  entry: fire_wave              # which graph to use (found by graph_id)
execution:
  mythic_skill: fire_wave       # the MM skill actually executed; must match the entry node's mythic_skill

#--- Runtime fields ---
type: active                    # this line is what actually decides active/passive
max_level: 5                    # max level, paired with levels to build a growth curve

cooldown:
  base: 3000                    # 3-second cooldown

resource:
  mana: 15                      # casting cost (⚠ mana will eventually be QinhClass's; temporary placeholder)

cast_mode: instant              # instant / channel / toggle

# Per-level values: each level takes different cooldown/cost, and passes params through to MythicMobs
levels:
  1:
    cooldown_ms: 3000
    resource: { mana: 15 }
    params: { power: "1.0" }    # level-1 power (read in MM via <skill.var.power>)
  2:
    cooldown_ms: 2800
    resource: { mana: 18 }
    params: { power: "1.2" }    # upgrade: shorter cooldown, higher power

# Custom variables passed through to MythicMobs (read in MM via <skill.var.element>)
variables:
  element: fire

tags: [fire, aoe, combat]       # custom tags, purely display/search

Example B: blade_slash — targeting + health cost + GCD + conditions + ready notify + conflict

yaml
id: blade_slash
display: "&7刃斩"

meta:
  category: combat
  type: active
  rank: advanced
trigger:
  primary: LEFT_CLICK           # left-click trigger (must match the graph entry node's triggers)
state:
  required: IDLE
graph:
  entry: blade_slash
execution:
  mythic_skill: blade_slash

type: active
max_level: 3

cooldown:
  base: 2500

# Auto-targeting: QS picks the target on cast and passes it to MM as @Target
target:
  mode: NEAREST                 # NEAREST closest / FARTHEST / LOWEST_HP finishing / HIGHEST_HP / RANDOM
  range: 6                      # targeting radius (blocks)
  filter: MONSTERS              # ANY / LIVING / MONSTERS mobs only / PLAYERS / NOT_PLAYERS
  required: true                # true = cast fails if no target is locked
  require_los: true             # true = only lock targets with line of sight (not through walls)

# Cost: uses health and hunger, no mana (no resource: block, pure blood sacrifice)
cost:
  health: 2.0                   # deduct 2 health (1 point = half a heart); fails if insufficient, floors at 0.5
  hunger: 1                     # deduct 1 hunger

# Global cooldown (GCD): for 0.8s after this skill, casting any skill is blocked
gcd:
  triggers_ms: 800

# Cooldown ready notify: sends actionbar + sound the instant cooldown ends (binary-cooldown skills only)
ready_notify:
  enabled: true
  sound: block.note_block.pling
  message: "&7{skill} &f已就绪"  # {skill} is replaced with the display name

# Conflict group: for a short time after casting (default 1s), other same-group skills can't be cast
conflict_groups:
  - melee_burst

# Cast prerequisites: casts only if all are met (a mistyped key is treated as always true, won't lock the skill)
conditions:
  - "player_level:>=3"          # player level ≥ 3
  - "has_target:true"           # must have a target (double safeguard with target.required)

levels:
  1: { cooldown_ms: 2500 }
  2: { cooldown_ms: 2200 }
  3: { cooldown_ms: 2000 }

🧱 Ready-to-use templates

Minimal working skill (copy, rename, and run)

yaml
id: my_skill
display: "&a我的技能"

meta:
  category: combat
  type: active
trigger:
  primary: RIGHT_CLICK
state:
  required: IDLE
graph:
  entry: my_skill
execution:
  mythic_skill: my_skill

type: active
cooldown:
  base: 2000

You also need a minimal companion graph; see the entry node example in graph & combos.

Full-field template (trim as needed)

yaml
id: full_demo
display: "&6完整示例"

meta: { category: combat, type: active, rank: advanced }
trigger: { primary: RIGHT_CLICK }
state: { required: IDLE }
graph: { entry: full_demo }
execution: { mythic_skill: full_demo }

type: active
max_level: 3

cooldown: { base: 3000, group: fire, charges: 1 }
resource: { mana: 15 }
cast_mode: instant

target: { mode: NEAREST, range: 20, filter: MONSTERS, required: false, require_los: false }
gcd: { triggers_ms: 800, ignore: false }
cost: { health: 0.0, hunger: 0 }

ready_notify: { enabled: true, sound: block.note_block.pling, message: "&a{skill} &7已就绪" }

conditions:
  - "player_level:>=3"

conflict_groups: [melee_burst]
combo_group: fire_combo

variables: { element: fire }

script: { pre_js: "", post_js: "" }

levels:
  1: { cooldown_ms: 3000, resource: { mana: 15 }, params: { power: "1.0" } }
  2: { cooldown_ms: 2800, resource: { mana: 18 }, params: { power: "1.2" } }
  3: { cooldown_ms: 2600, resource: { mana: 20 }, params: { power: "1.4" } }

active_triggers:
  - type: QI_ACTION

tags: [fire, aoe, combat]

Keep reading