Skip to content

Cast Modes & Channeling

Previous: Targeting & Target Selection · Next: Costs, Conditions & Variables


Every skill has a cast mode (cast_mode) that decides, after "the trigger is pressed," how this skill is cast:

  • Does it fire immediately on press?
  • Or press once to turn on, press again to turn off?
  • Or does it have to "channel" for a while, firing only when the channel completes and failing if interrupted partway?

These three behaviors map to three values. This page breaks them down one by one, from the simplest instant cast to the most complex channeled cast.

🖼️ [Image placeholder] A comparison diagram of the three cast modes (instant / toggle / channel) · suggested assets/cast-modes.png


🎯 Overview of the Three Cast Modes

cast_modeNameBehaviorBest for
instantInstant (default)Fires on press, no channelThe vast majority of skills: fireballs, dashes, AOEs
toggleToggleEach trigger flips between on / offPersistent auras, shields, stances, fields
channelChanneled castTakes effect only after channeling for a while; fails if interrupted partwayCharged ultimates, teleports, channeled abilities

To write it, just add one line at the top level of the skill yml:

yaml
cast_mode: instant        # this line is optional; the default is instant

⚡ instant (default)

The most common behavior, and the default when cast_mode is omitted.

  • Fires on press, with no channel and no delay.
  • Cooldown, resource cost, blood sacrifice, GCD… all costs are deducted instantly at the moment the cast succeeds.
  • If the gates (unlock / cooldown / resource / condition…) don't pass, it's blocked outright and nothing is deducted.
yaml
id: fire_wave
display: "&c火焰波"
cast_mode: instant         # can be omitted
cooldown:
  base: 3000               # goes on 3-second cooldown immediately after a successful cast
resource:
  mana: 15                 # deducts 15 mana immediately after a successful cast

These skills don't need the rest of this page—reading this far is enough. The two modes below are advanced.


🔁 toggle

Stance / aura skills that "press once to turn on, press again to turn off."

How it works

  1. First trigger → QS flips the state to on.
  2. Second trigger → QS flips the state to off.
  3. And so on.

Key point: QS only maintains the state of "is it currently on or off"; it does not handle the actual on/off effect. QS passes the current state to MythicMobs as the variable toggle_state (value "on" or "off"), and your MM skill decides, based on toggle_state, whether to "add a shield" or "remove the shield."

⚠️ The toggle state is in-memory, stored in ToggleTracker. A player relog resets it to off; it does not persist.

💡 The toggle "cooldown" is the cooldown per switch, not "how long the shield lasts." Every switch (whether on or off) runs through one cooldown + cost. The persistent effect is maintained on the MM side.

Corresponding placeholder: %qinhskills_<skill>_toggled% (true / false).

Full example: the shield skill (bundled example, copied verbatim)

yaml
#==============================================================================
#  护盾 shield  ——  toggle skill: press once to turn on, press again to turn off
#
#  This example teaches you: cast_mode: toggle —— switches between "on/off", rather than ending after one cast.
#  QS passes the current toggle state as the variable toggle_state(on/off) to MythicMobs,
#  and your MM skill decides whether to "add a shield" or "remove the shield" accordingly.
#  Trigger: sneak + left-click   |   Type: reactive   |   Category: utility
#
#  Note: the toggle "cooldown" is the per-switch cooldown, not the shield duration; the persistent effect is maintained on the MM side.
#==============================================================================

id: shield
display: "&e护盾"

meta:
  category: utility
  type: reactive                # reactive = reactive type (defensive/counter), just a classification label; runtime still follows the top-level type below
trigger:
  primary: SHIFT_LEFT_CLICK
state:
  required: IDLE
graph:
  entry: shield
execution:
  mythic_skill: shield

type: active                    # top level is active (the loader runs the active logic accordingly); meta.type's reactive is only used for classification
max_level: 1
resource:
  mana: 20

cooldown:
  base: 6000                    # cooldown for each "on/off" switch

cast_mode: toggle               # ★ key: toggle mode. Each trigger flips on/off and passes toggle_state to MM

How the MM side branches on toggle_state

QS passes over toggle_state, and the MM skill reads it with <skill.var.toggle_state>. Two common approaches:

Approach 1: MM condition check (branching within one skill)

yaml
# plugins/MythicMobs/skills/shield.yml
shield:
  Skills:
  # toggle_state == on → add shield
  - skill{s=ShieldOn} @Self ?check{var=toggle_state;value=on}
  # toggle_state == off → remove shield
  - skill{s=ShieldOff} @Self ?check{var=toggle_state;value=off}

Approach 2: split into two sub-skills (clearer)

yaml
ShieldOn:
  Skills:
  - potion{type=ABSORPTION;duration=999999;level=2} @Self
  - particle{p=enchantmenttable;amount=40} @Self

ShieldOff:
  Skills:
  - removepotion{type=ABSORPTION} @Self

The exact ?check/variable syntax depends on your MM version; this just demonstrates the idea of "branching on toggle_state." For the full explanation of MM receiving QS variables, see Integrating with MythicMobs.


📿 channel — Channeled Cast

Charged / channeled skills that "after pressing, must channel for a while, firing only when the channel completes and failing if interrupted partway."

Full field table (channel.*)

FieldDefaultDescription
time_ticksChannel duration (ticks, 20 = 1 second). Must be > 0 to enter channeling, otherwise it degrades to instant
bar_typebossbarChannel UI: bossbar (top health bar, recommended) / actionbar (text at the bottom) / none (no built-in UI, leave it to BetterHud to draw from placeholders)
interrupt_on_movetrueInterrupt on movement
move_threshold0.5Movement threshold (blocks), exceeding it interrupts; 0 = any movement interrupts
interrupt_on_damagetrueInterrupt on taking damage
cost_on_startfalsefalse = resource is deducted only on completion (no loss if interrupted); true = deducted on start (anti-abuse)
cooldown_on_startfalsefalse = goes on CD only on completion; true = goes on CD on start

When these fields are omitted, they take the global defaults from the channel.* section in config.yml; the skill yml overrides them per item—you only write the few you want to change, and the rest follow the global defaults. For the config.yml defaults, see Configuration File.

The full channeling flow

After the trigger is pressed, QS proceeds in this order:

1. Gates (unlock/cooldown/resource/condition… including the resource-sufficiency check)
        ↓ all pass
2. If cost_on_start: true → deduct resource now
3. If cooldown_on_start: true → go on cooldown now

4. Start the ChannelManager countdown (time_ticks)
        ↓ check for interruption every tick:
        · movement > move_threshold (when interrupt_on_move is enabled)
        · taking damage (when interrupt_on_damage is enabled)
        · disconnect / death / world change

   ┌──────────────┬──────────────────────────┐
   │ Channel done  │ Interrupted partway        │
   ├──────────────┼──────────────────────────┤
   │ If not yet    │ Notify "§7[QS] §c吟唱被打断" │
   │ deducted on   │ Not deducted on start → no  │
   │ start → deduct │ deduction; already deducted │
   │ resource now, │ on start → already lost (this│
   │ go on CD, fire│ is the anti-abuse cost)     │
   │ () to call MM │                            │
   └──────────────┴──────────────────────────┘

💡 How to choose cost_on_start? To let players "lose nothing if interrupted" (friendlier), keep the default false; to prevent players from repeatedly starting to scam resources / stall CD (more hardcore), set true. cooldown_on_start works the same way.

UI text during channeling

  • bossbar: top health-bar text §e吟唱 §f{display name} §7{percentage}%, the bar fills with progress.
  • actionbar: a progress bar at the bottom of the screen ▰▰▰▱▱.
  • none: QS draws nothing, only updates the placeholders, leaving it to HUD plugins like BetterHud to draw.

The unified interrupt notice: §7[QS] §c吟唱被打断.

Corresponding placeholders:

PlaceholderMeaning
%qinhskills_<skill>_channeling%Whether this skill is currently being channeled (true/false)
%qinhskills_<skill>_channel_progress%Channel progress percentage

Full example: demo_slash_charged, the demo charged slash (bundled example, copied verbatim)

yaml
#==============================================================================
#  演示蓄力斩 demo_slash_charged  ——  channeled cast skill (channel)
#
#  This example teaches you: cast_mode: channel —— after pressing, it must "channel" for a while before firing,
#  and being interrupted during the channel (movement/damage) fails it. Good for charged ultimates, teleports, and channeled skills.
#  Trigger: sneak + right-click   |   Type: active   |   Category: combat
#
#  Channel progress goes to the top BossBar by default; to leave it to BetterHud to draw, set bar_type to none and read the placeholders.
#  ⚙ This skill also ships with the server by default and auto-unlocks (config.yml unlock.starter_skills).
#==============================================================================

id: demo_slash_charged
display: "&b演示蓄力斩"

meta:
  category: combat
  type: active
  rank: basic
trigger:
  primary: SHIFT_RIGHT_CLICK
state:
  required: IDLE
graph:
  entry: demo_slash_charged
execution:
  mythic_skill: demo_slash_charged

type: active
max_level: 1

cooldown:
  base: 3000
resource:
  mana: 10

cast_mode: channel              # ★ key: channeled cast mode
channel:
  time_ticks: 40                # channel duration: 40 ticks = 2 seconds (20 ticks = 1 second)
  bar_type: bossbar             # channel display: bossbar top health bar (recommended) / actionbar text at bottom of screen / none leave it to an external HUD
  interrupt_on_move: true       # interrupt on movement
  move_threshold: 0.5           # only counts as "moved" if displacement exceeds 0.5 blocks (slight animation jitter won't interrupt)
  interrupt_on_damage: true     # interrupt on taking damage
  cost_on_start: false          # false = deduct resource only when the channel completes (no loss if interrupted); true = deduct on start (anti-abuse)
  cooldown_on_start: false      # false = go on cooldown only on completion; true = go on cooldown on start

Only after the channel completes does QS go and fire() the MM skill of the same name, demo_slash_charged—so the MM side's setup is no different from a normal instant skill; all the channeling logic is handled here on the QS side.


⚠️ Common Pitfalls

SymptomCause
Set cast_mode: channel but it fires instantlychannel.time_ticks is missing or set to 0 → degrades to instant. Must be > 0
Toggle skill "won't turn off" after relogThe state is in-memory and resets to off on relog; just press once more to re-enter the toggle cycle
Channel interrupts on the slightest movementmove_threshold is too small or set to 0; to tolerate slight animation jitter, keep the default 0.5
Players repeatedly start to farm resourcesSet channel.cost_on_start: true to deduct on start
The toggle on / off effects don't take effectQS only passes toggle_state; the actual effect must branch on it on the MM side (see above)

📚 Continue Reading