AimForge DOCS
Pricing Login
docs / examples / recoil

Recoil control

A recoil-compensation script that pulls the crosshair downward (and side-to-side) while you're firing, with per-weapon profiles selectable from a dropdown.

Full script

//!gui:dropdown var=weapon label="Weapon" options="AK,SCAR,MP5,Sniper" default=0

//!gui:section label="Pattern" mode="expanded"
//!gui:slider var=vRecoil label="Vertical Pull" min=0 max=30 default=10 step=0.5
//!gui:slider var=hRecoil label="Horizontal Drift" min=-10 max=10 default=0 step=0.5
//!gui:slider var=rampUp label="Ramp Up (shots)" min=1 max=30 default=8
//!gui:endsection

//!gui:section label="Behavior" mode="expanded"
//!gui:slider var=recoverRate label="Recover Speed" min=0.5 max=10 default=2 step=0.5
//!gui:checkbox var=onlyOnAds label="Only when aiming" default=1
//!gui:endsection

// --- Detect firing button (device-aware) ---
function isFiring() {
  if (device == "makcu") { return makcu.left; }
  if (device == "controller") { return controller.rt; }
  return 0;
}

function isAds() {
  if (device == "makcu") { return makcu.right; }
  if (device == "controller") { return controller.lt; }
  return 0;
}

// --- Initialize state ---
if (!isset(state.shotIndex)) { state.shotIndex = 0; }
if (!isset(state.lastFired)) { state.lastFired = 0; }

// --- Gate by ADS if configured ---
if (settings.onlyOnAds && !isAds()) {
  state.shotIndex = 0;
  return (x, y);
}

// --- Per-weapon multiplier (could be expanded with arrays) ---
weaponMul = 1.0;
if (settings.weapon == 1) { weaponMul = 0.85; }   // SCAR — slower
if (settings.weapon == 2) { weaponMul = 1.20; }   // MP5 — faster
if (settings.weapon == 3) { weaponMul = 0.30; }   // Sniper — minimal

// --- Apply recoil while firing ---
if (isFiring()) {
  state.shotIndex = state.shotIndex + 1;
  state.lastFired = time;

  // Ramp up over the first N shots, then plateau
  rampMul = clamp(state.shotIndex / settings.rampUp, 0.3, 1.5);

  y = y + settings.vRecoil * rampMul * weaponMul;
  x = x + settings.hRecoil * rampMul * weaponMul;
} else {
  // Recover the shot counter when not firing
  state.shotIndex = max(0, state.shotIndex - settings.recoverRate);
}

monitor("shots", state.shotIndex);

return (x, y);

How it works

Firing & ADS detection

isFiring() and isAds() are helper functions that translate "is the user holding fire" and "is the user aiming" into the right device-specific button:

Device Fire ADS
makcu Left mouse Right mouse
controller Right trigger Left trigger

If you use a different mapping, edit the helpers.

Shot counter

The state.shotIndex tracks how many consecutive shots have been fired. It increments each frame while firing, and decreases (at recoverRate per frame) when not firing.

This lets us model the typical FPS recoil shape: minimal kick on the first shot, ramping up over the first ~8 shots, then plateauing.

Ramp curve

rampMul = clamp(state.shotIndex / settings.rampUp, 0.3, 1.5);

This produces:

Tune the Ramp Up (shots) slider to match your weapon's actual recoil profile.

Per-weapon multipliers

A simple if chain scales the recoil for different weapon classes. For more complex patterns, store recoil profiles in arrays:

if (!isset(state.akPattern)) {
  state.akPattern = [0.5, 0.7, 1.0, 1.2, 1.3, 1.4, 1.5, 1.4, 1.3, 1.2];
}
shotInPattern = state.shotIndex % len(state.akPattern);
y = y + settings.vRecoil * state.akPattern[shotInPattern];

Tuning tips

Extension ideas