Combat Roll
LALT plus a movement direction rolls you that way. Eight directions, each a two-phase animation.
Using it
Hold a movement direction and press LALT. The direction is read from the movement controls at the moment the roll starts, exactly the way the original picks them.
| Direction held | Clip pair |
|---|---|
| Forward | combatroll_fwd_p1_00 / _p2_00 |
| Forward-right | combatroll_fwd_p1_45 |
| Forward-left | combatroll_bwd_p1_-45 |
| Right | combatroll_fwd_p1_90 |
| Left | combatroll_bwd_p1_-90 |
| Back-right | combatroll_bwd_p1_135 |
| Back-left | combatroll_bwd_p1_-135 |
| Back | combatroll_bwd_p1_180 |
All eight come out of mech_strafe@generic@roll@base. With no direction held you get the forward roll.
There is a 900 ms cooldown between rolls (Config.Roll.cooldown).
The three trigger modes
This is the one place the port had to add something the original did not have.
The original had no key at all
It watched GET_PED_IS_DOING_COMBAT_ROLL and replaced the animation the game was already about to play — and only for John (player_three). On a server where the game never triggers its own combat roll, and where characters are arbitrary models, that does nothing at all. So a key trigger was added, and it is the default.
Config.Roll.trigger | Behaviour |
|---|---|
'key' | the key starts a roll outright — the default |
'native' | only replace the animation when the game itself starts a combat roll, exactly like the original |
'both' | either |
'native' is there for parity and for servers that have something else driving the game's own roll. On a normal server it will look like the feature is doing nothing.
The original's JOHN_ONLY option is dropped — it is meaningless with arbitrary character models.
Settings
| Default | ||
|---|---|---|
Config.Roll.enabled | true | the feature itself |
Config.Roll.key | 'LALT' | same names as Config.SlideKey |
Config.Roll.suppressWhileHeld | {} | INPUT_* actions to swallow while held |
Config.Roll.cooldown | 900 | ms between rolls |
Config.Roll.requireAiming | false | the game's own roll is an aiming move; true restores that |
Config.Roll.allowFromStandstill | true | roll without moving first |
Config.Roll.faceCamera | true | snap to face the camera before rolling |
Config.Roll.lockHeading | true | hold that heading for the whole roll |
Config.Roll.disablePainAudio | true | silence pain grunts during the roll, as the original does |
faceCamera and lockHeading together are what the original does.
When you get the controls back
Two phase values decide how the roll ends, and they are the ones to reach for if it feels wrong.
| Default | ||
|---|---|---|
handoverPhase | 0.84 | where phase 1 hands over to phase 2 |
exitPhase | 0.005 | where phase 2 hands control back to you |
phaseTimeoutMs | 2000 | safety cap per phase |
The problem exitPhase solves: phase 2's last stretch is the character pushing back up and settling. The heading stays pinned and the roll counts as still running for all of it, so the roll looked finished while the player still had no control — a snag at the end of every roll.
Breaking out early only gives the controls back. The clip is never stopped, so the stand-up still plays out in full.
Tuning it
Measured clip lengths are 700 ms for phase 1 and 767 ms for phase 2. Raise exitPhase if control comes back while you are still visibly tumbling, which reads as a skid. Lower it if control still arrives late.
The menu slider cannot reach the shipped value
Combat Roll → Control back at has a range of 0.50–1.00, but config.lua ships exitPhase = 0.005. Nudging that row once will jump the value to at least 0.50 and bring the end-of-roll snag back with it, and there is no way to get to 0.005 again from the panel. Change it in config.lua and leave that row alone, or hide it:
Config.Menu.options['roll.control-back-at'] = falseRun cs_menuids roll to confirm the id on your build before relying on it.
phaseTimeoutMs is a safety net. Both phases are given a phase target — handoverPhase for phase 1 and exitPhase for phase 2 — but each phase loop also waits on IS_ENTITY_PLAYING_ANIM, so a clip that never reaches its target and whose flags hold the last frame would never report itself finished and the roll would stay "running" forever, blocking every later roll and slide. The cap is generous enough that it never fires on a healthy roll; at the shipped exitPhase = 0.005 phase 2 breaks out on the first frame it reads. With Config.Debug on, the per-phase read-out says cut at <phase> when the cap does fire.
The animation
| Dictionary | mech_strafe@generic@roll@base |
| Filter | noleftarm_filter |
| ikFlags | 0x20000A0 |
| Blend in / out | 8.0 / -8.0 |
| Flags | 0 |
Why the flags are 0
This is the one value in the roll that could not be recovered from the decompile with confidence — the caller sets the flags up on the stack and Ghidra lost them. 0 is a plain full-body task, which works. If you want to experiment, cs_rollflags changes them live with no restart and decodes the bits by name.
Menu rows
Combat Moves → Combat Roll, or the Roll tab.
| Section | Rows |
|---|---|
| (no heading) | Enabled, Trigger, Cooldown, Control back at, Phase 1 handover, Face the camera, Hold heading, Require aiming, From standstill |
| Try it | Roll now |
Roll now closes the panel and rolls immediately — hold a direction first to choose which way.
Mutual exclusion
The exclusion is one-directional, not mutual. A roll will not start during a slide — canRoll checks IsSliding(). A gun trick will not start during a slide or a roll, and a trick already running is cut if either starts. Diving stands down while any of the three is running.
A slide, though, can start during a roll or a gun trick. canSlide checks neither IsRolling() nor IsSpinning(), and with the shipped Config.MinSpeed = 2.5 and Config.RequireSprint = false a roll's own root motion can satisfy the speed gate. The two then run at once: the roll keeps pinning the heading every frame while lockHeading is on, while the slide drives its own heading, velocity and camera. If you are extending the resource, check explicitly for every move you need to stand down for rather than assuming the three already guard each other. See Developer API.
Turning it off
Config.Roll.enabled = falseOr at runtime, per client:
exports.rm_combat_moves:SetRollEnabled(false)
TriggerClientEvent('combat_slide:setRollEnabled', src, false)Hiding the page in the menu does not disable it — the feature carries on running on whatever config.lua says. See Server setup.