Summer 2026 Music Kit Competition is now here! - Create a summer themed electronic music kit and submit it by - Enter Now!

Scripting
Scripting Cookbook

Ready-made recipes for things mappers ask for most. Every recipe is a complete, working script: copy it into a .js file, point a lambda_point_script at it, and tweak from there. If any of the code looks unfamiliar, JavaScript Basics explains it all.

Open a door when a button is pressed

The "hello world" of map scripting. In Hammer, add OnPressed → map_script → OpenDoor to your button.

import { Instance } from "lambda_script/point_script";

Instance.OnScriptInput("OpenDoor", () => {
	Instance.EntFireAtName({
		name: "security_door",
		input: "Open",
	});
});

EntFireAtName can fire any input at any named entity: Open, Unlock, Trigger, Enable, whatever the target accepts. It's your general-purpose replacement for output chains.

Only open after three presses

Where scripting starts beating math_counter:

import { Instance } from "lambda_script/point_script";

let presses = 0;

Instance.OnScriptInput("PressButton", () => {
	presses++;

	if (presses === 3) {
		Instance.EntFireAtName({ name: "vault_door", input: "Open" });
	} else {
		Instance.Msg(`${3 - presses} presses to go`);
	}
});

Show a message to every player

import { HUD, Instance } from "lambda_script/point_script";

Instance.OnScriptInput("AnnounceStart", () => {
	Instance.PrintMessage(HUD.PRINTCENTER, "The event has begun!");
});

Swap HUD.PRINTCENTER for HUD.PRINTTALK (chat) or HUD.PRINTCONSOLE (console). To message one specific player, call player.PrintMessage(...) on a player entity instead.

Do something after a delay

There is no setTimeout in map scripting. For a one-off delayed entity input, use the built-in delay option:

Instance.EntFireAtName({
	name: "explosion_effect",
	input: "Trigger",
	delay: 3, // seconds
});

For delayed code, use Instance.SimpleTimer:

import { Instance } from "lambda_script/point_script";

Instance.OnScriptInput("StartCountdown", () => {
	Instance.SimpleTimer(3, () => {
		Instance.Msg("Three seconds have passed!");
	});
});

Repeat something every second

Instance.CreateTimer runs a callback on a repeating interval. This countdown runs exactly ten times, one second apart:

import { HUD, Instance } from "lambda_script/point_script";

let secondsLeft = 10;

Instance.CreateTimer({
	delay: 1,
	repetitions: 10,
	callback: () => {
		Instance.PrintMessage(HUD.PRINTCENTER, `Starting in ${secondsLeft}...`);
		secondsLeft--;

		if (secondsLeft === 0) {
			Instance.EntFireAtName({ name: "arena_door", input: "Open" });
		}
	},
});

Set repetitions: 0 to repeat forever. The returned timer object can be paused, resumed, and removed, so one script can run as many independent timers as it needs; see the timer reference for everything a Timer can do.

Make an objective glow

import { Instance } from "lambda_script/point_script";

Instance.OnScriptInput("HighlightObjective", () => {
	const objective = Instance.FindEntityByName("mission_objective");
	objective?.Glow({ r: 255, g: 180, b: 0 }); // Orange glow.
});

Instance.OnScriptInput("ClearHighlight", () => {
	Instance.FindEntityByName("mission_objective")?.Unglow();
});

The ?. means "only if the entity was found", so a typo in the name does nothing instead of causing an error.

Teleport a player

import { Instance } from "lambda_script/point_script";

Instance.OnScriptInput("TeleportAll", () => {
	for (const player of Instance.GetAllPlayerControllers()) {
		player.Teleport({
			position: { x: 0, y: 512, z: 64 },
			velocity: { x: 0, y: 0, z: 0 }, // Stop their momentum too.
		});
	}
});

Coordinates are the same world units you see in Hammer's status bar.

React to chat commands

import { Instance } from "lambda_script/point_script";

Instance.OnPlayerChat(({ player, text }) => {
	if (text === "!dance") {
		Instance.EntFireAtName({ name: "disco_lights", input: "Toggle" });
		Instance.Msg(`${player.GetPlayerName()} started the disco`);
	}
});

Give players a weapon

import { Instance } from "lambda_script/point_script";

Instance.OnScriptInput("ArmEveryone", () => {
	for (const player of Instance.GetAllPlayerControllers()) {
		if (player.IsAlive() && !player.HasWeapon("weapon_ak47")) {
			player.GiveWeapon("weapon_ak47");
		}
	}
});

HasWeapon stops a player collecting duplicates, and player.SelectWeapon("weapon_ak47") switches them to it. To take a weapon away again, use player.StripWeapon("weapon_ak47"). See Player Methods for the full list.

Welcome players when they join

OnPlayerLoaded fires once a player has fully loaded their profile, which makes it the safest "player has arrived" moment:

import { HUD, Instance } from "lambda_script/point_script";

Instance.OnPlayerLoaded((player) => {
	player.PrintMessage(HUD.PRINTTALK, `Welcome to ${Instance.GetMapName()}, ${player.GetPlayerName()}!`);
});

Detect jumps and landings

import { Instance } from "lambda_script/point_script";

let jumps = 0;

Instance.OnPlayerJump((player) => {
	jumps++;
	Instance.Msg(`${player.GetPlayerName()} jumped (${jumps} total)`);
});

Instance.OnPlayerLand((player) => {
	Instance.Msg(`${player.GetPlayerName()} landed`);
});

Check what buttons a player is holding

Useful for custom mechanics. This one only lets ducking players through a door:

import { Input, Instance } from "lambda_script/point_script";

Instance.OnScriptInput("TryEnter", () => {
	const player = Instance.GetPlayerController(0);

	if (player?.IsInputPressed(Input.DUCK)) {
		Instance.EntFireAtName({ name: "crawl_door", input: "Open" });
	}
});

Combine flags with | to match any of several inputs: Input.ATTACK | Input.ATTACK2.

Remember things between map loads

All variables reset when the map restarts. Save data survives. It's one text string stored per map, so the usual trick is keeping an object in it as JSON:

import { Instance } from "lambda_script/point_script";

const state = JSON.parse(Instance.GetSaveData() || "{}");

Instance.OnScriptInput("SecretFound", () => {
	state.secretsFound = (state.secretsFound ?? 0) + 1;
	Instance.SetSaveData(JSON.stringify(state));
	Instance.Msg(`Secrets found across all sessions: ${state.secretsFound}`);
});

React to Counter-Strike rounds

On Counter-Strike servers the CS API adds round and gameplay events:

import { CS, CSRoundEndReason, CSTeam, HUD, Instance } from "lambda_script/point_script";

CS.OnRoundStart(() => {
	Instance.EntFireAtName({ name: "map_reset_relay", input: "Trigger" });
});

CS.OnRoundEnd((winningTeam, reason) => {
	if (winningTeam === CSTeam.CT && reason === CSRoundEndReason.TARGET_SAVED) {
		Instance.PrintMessage(HUD.PRINTCENTER, "The bomb was defused!");
	}
});

CS.OnBombPlant((plantedC4, planter) => {
	Instance.Msg(`${planter.GetPlayerName()} planted the bomb`);
});

CS only exists when the Counter-Strike gamemode is running. If your script might run elsewhere, see optional APIs for how to check availability safely.

Spawn entities from a template

Set up a point_template in Hammer with your entity as its template, then spawn copies on demand:

import { Instance, PointTemplate } from "lambda_script/point_script";

Instance.OnScriptInput("SpawnCrate", () => {
	const spawner = Instance.FindEntityByName("crate_template");

	if (spawner instanceof PointTemplate) {
		const spawned = spawner.ForceSpawn();
		spawned?.[0]?.Glow({ r: 0, g: 255, b: 0 });
	}
});

Debugging: see what your script is doing

Sprinkle Instance.Msg calls to trace what runs and when, draw debug shapes in the world, or register a console command you can trigger by hand (requires sv_cheats 1):

import { Instance } from "lambda_script/point_script";

Instance.RegisterCheatCommand("test_door", () => {
	Instance.EntFireAtName({ name: "security_door", input: "Open" });
});

Instance.DebugBox({
	mins: { x: -16, y: -16, z: 0 },
	maxs: { x: 16, y: 16, z: 72 },
	duration: 10,
	color: { r: 0, g: 255, b: 0, a: 64 },
});

More debugging tools are covered in Scripting: In-Depth.