CronusZenWiki
GPC Scripting

Your First GPC Script: Rapid-Fire Toggle Step by Step

Build your first GPC script for the Cronus Zen step by step: a rapid-fire toggle using get_val, set_val, event_press, and a timed combo block.

Updated September 2, 20266 min readCECZW EditorialBeginner15 min

Your first GPC script should be a rapid-fire toggle: it is short, it teaches the four ideas every Cronus Zen script is built from — the main loop, reading inputs, combos, and toggle state — and you can feel it working within seconds of flashing it. This tutorial builds the script one line at a time, and every intermediate stage is a complete script you can compile and test. By the end you will have a toggle you can switch on and off in-game and, more usefully, you will understand every line of it.

If you have not read what GPC is yet, skim it first — this page assumes you know that a script's main block re-runs continuously while the script is active.

Before you start

You need three things:

  1. Zen Studio installed on your PC and able to see your Zen.
  2. The Zen wired up to your console and controller in its normal playing configuration.
  3. A game where rapid fire is observable — anything with a semi-automatic weapon works.

This tutorial uses XB1_ identifiers. They are aliases for the same internal input slots as the PS4_ names, so the script works on either console family; use whichever names you find more readable.

Tip

A note on expectations: rapid fire only helps with semi-automatic inputs, and many games cap effective fire rate on the server side. The point of this script is to learn GPC, not to win an arms race.

Step 1: The smallest valid script

Open Zen Studio's compiler view and start with the minimum:

// rapid_fire.gpc — step 1: the smallest useful skeleton.
// The main block runs over and over while the script is active.

main {
    // Empty. The Zen passes your controller through untouched.
}

Compile it. It does nothing, deliberately — but a clean compile confirms your toolchain works before any real logic exists, which makes every later error unambiguously yours.

Step 2: Read the trigger with get_val

get_val() returns the current value of one control: 0 to 100 for buttons and triggers, -100 to +100 for stick axes. In an if condition, any nonzero value counts as true.

// Step 2: react while the right trigger is held.
// Complete script — compiles and runs.

main {
    if (get_val(XB1_RT)) {
        // The trigger is pressed at least slightly.
        // Nothing here yet — the combo arrives in step 3.
    }
}

Because triggers are analog, get_val(XB1_RT) goes true at the lightest pull. If you later want the script to engage only on a deliberate full pull, change the condition to get_val(XB1_RT) > 90.

Step 3: Pulse the trigger with a combo

set_val() inside main only lasts for the single pass where it executes. For anything with duration you use a combo block: each wait(ms) holds whatever the lines above it set, for that many milliseconds.

// Step 3: convert a held trigger into timed pulses.
// Complete script — this is rapid fire, minus the toggle.

main {
    if (get_val(XB1_RT)) {
        combo_run(RapidFire);
    }
}

combo RapidFire {
    set_val(XB1_RT, 100);   // hold the trigger fully...
    wait(40);               // ...for 40 ms (the "shot")
    set_val(XB1_RT, 0);     // then force it released...
    wait(30);               // ...for 30 ms (the gap)
}

Two details here do the real work:

  • The forced release is the whole trick. Your finger is physically holding the trigger the entire time. During the second window, set_val(XB1_RT, 0) overrides that and the game sees a clean release — which is what lets the next pull register as a new shot.
  • Calling combo_run() every pass is fine. While a combo is already running, combo_run() on it has no effect (restarting one mid-flight is what combo_restart() is for). So the combo loops seamlessly for as long as you hold the trigger, and stops when you let go.

Flash this to a slot and try it — it should already feel like rapid fire. The problem: it is always on. Every trigger pull is now pulsed, in every weapon and every menu.

Step 4: Add the toggle

A toggle needs two things: a variable that survives between passes of main, and an edge detector so one button press flips it exactly once. That second part is event_press(), which is true only on the single pass where a button goes from released to pressed. If you used get_val() instead, the flag would flip on every pass for as long as you held the button — hundreds of times per second.

// Step 4: toggle rapid fire with LT + D-pad Up.
// Complete script.

int rapid_on;   // 0 = off, 1 = on; ints start at 0

main {
    // Flip the flag once per LT+Up chord press.
    if (get_val(XB1_LT) && event_press(XB1_UP)) {
        rapid_on = !rapid_on;
    }

    // Only pulse when the toggle is on.
    if (rapid_on && get_val(XB1_RT)) {
        combo_run(RapidFire);
    }
}

combo RapidFire {
    set_val(XB1_RT, 100);
    wait(40);
    set_val(XB1_RT, 0);
    wait(30);
}

The chord (get_val(XB1_LT) && event_press(XB1_UP)) is a standard GPC idiom: the held half uses get_val, the tapped half uses event_press, so the toggle fires exactly once per deliberate combination and never from an accidental D-pad tap alone — unless LT happens to be held, which in a shooter usually means aiming. Pick a chord that suits your game.

Step 5: Name your numbers, then flash it

Magic numbers rot. Promote the two timing values to define constants so future-you can retune them in one place, and add a header comment saying what the script does:

// ============================================================
// rapid_fire_toggle.gpc
// Hold LT and press D-pad Up to toggle rapid fire on or off.
// While enabled, holding RT fires in timed pulses.
// Tune HOLD_MS and RELEASE_MS per game and weapon.
// ============================================================

define HOLD_MS    = 40;   // how long each simulated pull lasts
define RELEASE_MS = 30;   // gap between pulls

int rapid_on;             // 0 = off, 1 = on

main {
    // Toggle: hold Left Trigger, tap D-pad Up.
    if (get_val(XB1_LT) && event_press(XB1_UP)) {
        rapid_on = !rapid_on;
    }

    // While enabled, convert a held Right Trigger into pulses.
    if (rapid_on && get_val(XB1_RT)) {
        combo_run(RapidFire);
    }
}

combo RapidFire {
    set_val(XB1_RT, 100);   // press the trigger fully...
    wait(HOLD_MS);          // ...and hold it
    set_val(XB1_RT, 0);     // force a release (overrides your finger)
    wait(RELEASE_MS);       // stay released before the next pull
}

To get it running:

  1. Compile in Zen Studio. Fix any reported line before continuing.
  2. Program the compiled script to one of the Zen's memory slots.
  3. Select that slot on the device.
  4. In-game, hold LT and tap D-pad Up, then hold RT with a semi-automatic weapon. Toggle it off the same way and confirm the trigger behaves normally again.

Exact Zen Studio button labels change between versions, so if the interface differs from a tutorial you are following, trust Cronus's current documentation.

Tip

If 40/30 feels wrong in your game, tune RELEASE_MS first. Too short and the game drops shots (it never sees a clean release); too long and you are slower than your own finger.

Troubleshooting

Symptom Likely cause Fix
Fires one shot, then nothing Game needs a longer release to register a new press Raise RELEASE_MS in steps of 10
Toggle never engages Chord conflicts with the game or wrong identifiers Try a different chord; confirm you are testing the slot you flashed
Toggle flips on and off rapidly Used get_val instead of event_press for the tap half Re-check the step 4 condition
Fires while aiming without shooting Trigger threshold too sensitive Use get_val(XB1_RT) > 90 as the fire condition
Script compiles but nothing changes in-game Wrong memory slot selected on the Zen Cycle to the slot you actually programmed

If the script misbehaves in ways a table cannot solve, the combos guide explains the timing model in depth, and the syntax reference covers every keyword used here.

FAQ

What should my first GPC script be?

A rapid-fire toggle, as built above. It exercises the main loop, get_val/set_val, a combo with wait() timing, and event_press toggle state — the four building blocks nearly every other script recombines.

Why does my rapid fire feel slower than advertised numbers?

Each cycle here is 70 ms (40 hold + 30 release), which is an upper bound of about 14 presses per second — but games apply their own input buffering and fire-rate caps, often on the server. No script exceeds a game's real cap, regardless of the wait() values.

Does the script keep running when I turn the toggle off?

The combo finishes its current cycle (at most 70 ms) and then nothing re-triggers it, so the trigger reverts to fully manual. int variables keep their value until the script restarts.

Can I use PlayStation button names instead?

Yes. Swap XB1_RT for PS4_R2, XB1_LT for PS4_L2, and XB1_UP for PS4_UP. The identifier families are interchangeable aliases, so this is purely about readability.

Is a rapid-fire script allowed online?

Using input-automation devices violates most games' terms of service, and enforcement approaches differ by publisher and change over time. Treat this tutorial as a programming lesson and check the current stance for your game before using it in multiplayer.