Input and controls
Input handling is entirely client-side: key binds for your own features, and two levels of gating over the game’s built-in controls — an all-or-nothing switch and a per-ability style mask. The server never intercepts input; it ships a mask down once and the client applies it locally.
Key binds
Section titled “Key binds”Key.bind("f6", "down", () => togglePanel());
// Both edges: the handler receives the key and which edge fired.Key.bind("b", "both", (key, state) => { console.log(`${key} ${state}, held=${Key.isDown(key)}`);});
Key.unbind("f6");Key.bind(key, state, handler) takes "down", "up", or "both"; passing the handler as the second argument defaults to "down". Key names are case-insensitive ("f6", "b", …).
Two ownership rules keep binds predictable:
- Binds fire only in-session while no UI owns input — an open chat box, the escape menu, the debug cursor, or a focused web view suspends them. The native world map does not: binds keep firing while it is up, which is how a toggle bind closes it.
- Binds are resource-owned: they are cleared automatically when your resource stops.
A bind that should have a replicated effect relays to the server, which applies it authoritatively — the client never mutates shared state itself:
Key.bind("r", "down", () => { Events.emitServer("mygm:vehicleToggle", { accessory: "engine" });});The matching, whitelist-validating server handler is shown in the Events guide.
Enabling and disabling all input
Section titled “Enabling and disabling all input”Controls.setEnabled(false); // freeze movement, combat, vehicle input; no cursor shownControls.setEnabled(true);Controls.isEnabled();This is the blunt instrument — cutscenes, freeze-on-countdown. For anything finer, use styles.
Control styles: gating individual abilities
Section titled “Control styles: gating individual abilities”Controls.setStyle(mask) gates individual abilities with the bits in Controls.Style: Fire, WeaponSelect, WeaponManip, CarDrive, CarInOut, Sprint, Covers, Crouch, and more, plus the precomputed All, None, and NoWeapons.
Controls.setStyle(Controls.Style.NoWeapons); // no firing/reloading/drawing; melee and movement stayControls.setStyle(Controls.Style.All & ~Controls.Style.CarDrive); // everything but drivingControls.setStyle(Controls.Style.None); // frozenControls.setStyle(Controls.Style.All); // back to normalControls.getStyle();NoWeapons is the classic no-weapon-zone building block. The style survives respawn, so clear it explicitly when the restriction ends.
Server-driven gating is the usual intent pattern — the server decides who is restricted, the client applies the mask:
// Entering the safe zone:clientCall(player, "controls.setStyle", { style: "noweapons" });// Leaving:clientCall(player, "controls.setStyle", { style: "all" });const STYLES = { all: () => Controls.Style.All, none: () => Controls.Style.None, noweapons: () => Controls.Style.NoWeapons,};CLIENT_CALLS["controls.setStyle"] = (a) => { const style = STYLES[a.style]; if (style) Controls.setStyle(style());};Sending a named style instead of a raw number keeps the client in charge of what the bits mean and gives the server a readable vocabulary.
Reading the local player
Section titled “Reading the local player”Some input features need the local player’s physical state:
const lp = LocalPlayer; // null until spawnedif (lp) { const head = lp.getBoneTransform(Bone.Head); // { position, rotation, forward } | null const vehicle = lp.getVehicle(); // Vehicle | null if (vehicle) { const trunk = vehicle.getPartTransform(VehiclePart.Trunk); }}Bone enumerates the full Mafia II skeleton, VehiclePart the semantic vehicle slots (doors by hinge side and row, hood, trunk); a part the model lacks returns null. Combined with Camera.worldToScreen, bone and part transforms anchor Render2D or web-view overlays to things in the world.
