math
Extends the Lua stdlib (does not override sin/cos/pi/floor/random/...); adds extra functions to the math table.
Vector Types
math.Vec2(x, y) → Vec2
math.Vec3(x, y, z) → Vec3
Note: CamelCase
Vec2/Vec3, notvec2/vec3(lowercasevec2is a different internal type).
Vec2 / Vec3 Fields and Operators
Fields: .x / .y / .z (Vec3 only)
Supported meta-operators (work in both directions, operand order doesn't matter):
| Operation | Behavior |
|---|---|
a + b | Per-component addition |
a - b | Per-component subtraction |
v * scalar | Scalar multiplication |
v / scalar | Scalar division |
-v | Negate |
tostring(v) | Text form, e.g. Vec3(1.000, 2.000, 3.000) |
Instance methods:
| Method | Returns | Description |
|---|---|---|
v:length() | number | Magnitude |
v:length_2d() (Vec3 only) | number | XY-plane magnitude |
v:length_sq() | number | Squared magnitude (avoids sqrt) |
v:dot(other) | number | Dot product |
v:cross(other) (Vec3 only) | Vec3 | Cross product |
v:dist(other) | number | Distance |
v:dist_2d(other) (Vec3 only) | number | XY-plane distance |
v:to_2d() (Vec3 only) | Vec2 | Projects to Vec2 (x, y) |
v:normalize() | new Vec | In-place normalize, but sol2 returns a copy of the updated value (not a self reference). Chains like v:normalize():length() do not see the original v |
v:normalized() | new Vec | Copy then normalize; original v unchanged |
Scalar Operations
math.Lerp(a, b, t) → number
Linear interpolation. t is not clamped; extrapolation is legal (user's responsibility).
math.Clamp(x, lo, hi) → number
math.Smoothstep(edge0, edge1, x) → number
Hermite interpolation; input x within [edge0, edge1] smoothly transitions to [0, 1], saturating at both ends.
math.RemapVal(val, a, b, c, d) → number
Linear remap: val is mapped from [a, b] onto [c, d]. No clamping (values outside [a, b] extrapolate).
math.RemapValClamped(val, a, b, c, d) → number
Same as RemapVal, but val is saturated to [a, b] first (out-of-range inputs clamp to the endpoints).
-- Map hp 0..100 to saturation 0.2..1.0
local sat = math.RemapValClamped(hp, 0, 100, 0.2, 1.0)math.AngleNormalize(angle_deg) → number
Normalizes an arbitrary degree value into [-180, 180).
Angles / Projection
math.CalcAngle(from_pos, to_pos) → pitch, yaw
Two Vec3 → (pitch, yaw) in degrees; pitch is negative pointing up.
math.WorldToScreen(world_pos) → sx, sy, ok
Three return values; when ok=false, sx/sy are meaningless (out-of-game / off-screen / view matrix not ready).
Big-map coordinates
Built-in calibration table covers every official map (Storm Point / World's Edge / Kings Canyon / Olympus / Broken Moon ...). Scripts don't need to maintain their own scale tables.
math.WorldToMapPixel(wx, wy, disp_w, disp_h) → px, py, ok
World (wx, wy) → big-map PNG pixel position on a (disp_w, disp_h) canvas.
ok = false: lobby / unknown map / not in-game →(px, py)falls back to canvas center- Scale-independent (uses 4-corner calibration); disp scaling follows the canvas size
math.WorldRadiusToMapPixel(r_world, disp_size) → r_pixel, ok
World-inch radius → big-map pixel radius. disp_size is typically the canvas short edge (same-direction scaling avoids ellipse distortion).
- For death rings, blast radii, marker circles, etc.
ok = falsereturns0
event.on("frame_update", function()
-- Custom mini radar canvas: top-left 200x200
for _, p in pairs(game.entities.players) do
if not p.is_teammate then
local px, py, ok = math.WorldToMapPixel(p.origin.x, p.origin.y, 200, 200)
if ok then
draw.circle(px, py, 3, draw.rgba(1, 0.2, 0.2, 1), { filled = true })
end
end
end
-- Death-ring radius
local rp, ok = math.WorldRadiusToMapPixel(ring_radius_world, 200)
if ok then
draw.circle(100, 100, rp, draw.rgba(0.4, 0.7, 1, 0.6), { thickness = 2 })
end
end)Animation Curves
All ease_* take t : number (auto-clamped to [0, 1]) and return [0, 1].
| Function | Curve | Use case |
|---|---|---|
math.ease_in(t) | t² slow start, fast finish | Fade in / starting from rest |
math.ease_out(t) | 1-(1-t)² fast start, slow finish | Deceleration to stop / popup toast |
math.ease_in_out(t) | cosine, bidirectional | General-purpose smoothing |
math.ease_in_cubic(t) | t³ more dramatic slow start | High-contrast acceleration |
math.ease_out_cubic(t) | 1-(1-t)³ more dramatic fast-then-slow | High-contrast deceleration |
math.ease_bounce(t) | Bounces at the end | Elastic appearance / notification jitter |
Example
-- Vector arithmetic
local lp = game.localplayer
local p = game.entities.players -- assume targets exist
for _, enemy in pairs(p) do
if not enemy.is_teammate then
local delta = enemy.origin - lp.origin
print("Enemy distance:", delta:length(), "meters")
local angle_deg = math.CalcAngle(lp.camera_origin, enemy.head_pos)
end
end
-- Screen projection
event.on("frame_update", function(e)
for _, p in pairs(game.entities.players) do
if not p.is_teammate and p.head_pos then
local sx, sy, ok = math.WorldToScreen(p.head_pos)
if ok then
draw.circle(sx, sy, 3, draw.rgba(1, 0, 0, 1), { filled = true })
end
end
end
end)
-- Easing animation: notify pops up 0..100% opacity over 0.5s
local start_t = time.now()
event.on("frame_update", function(e)
local elapsed = time.now() - start_t
local progress = math.Clamp(elapsed / 0.5, 0, 1)
local alpha = math.ease_out_cubic(progress)
-- Use alpha to draw the toast
end)