Types & Constants
Vec2/vec3 math types and the named constant tables (modifier_flag, slot, bone, hero_id, bucket, modifier_token, player_flag and VK).
vec3
3D vector. Returned by position queries and used as input to drawing functions.
local v = vec3(100, 200, 50)
v.x, v.y, v.z -- field access| Method | Returns | Description |
|---|---|---|
v:length() | number | Magnitude |
v:length_sqr() | number | Squared magnitude (cheaper than length()) |
v:length_2d() | number | XY magnitude (ignores Z) |
v:normalized() | vec3 | Unit vector |
v:dot(other) | number | Dot product |
v:cross(other) | vec3 | Cross product |
v:distance(other) | number | Distance to another point |
v:angle_to(other) | number | Angle between the two vectors in degrees, 0 to 180. 0 if either is a zero vector |
v:empty() | boolean | True if all zero |
Operators
| Expression | Result | Notes |
|---|---|---|
v + other, v - other | vec3 | Component-wise |
v + scalar, v - scalar | vec3 | The scalar is broadcast to every component. Only this order works. scalar + v raises tsuki_vec3 expected, got number |
v * scalar, scalar * v | vec3 | Both orders work |
v * other | vec3 | Component-wise multiply. For the dot product, use v:dot(other) |
v / scalar | vec3 | Scalar only, and only on the right. A vector divisor raises number expected, got tsuki_vec3. Dividing by 0 divides by 1e-10 instead, so you do not get inf |
There is no __unm, __eq, __lt or __len. -v and #v raise an error. Write v * -1 to negate a vector. a == b compares object identity, so two vectors with the same components are never equal. Compare the fields instead, or test (a - b):length() < eps.
vec2
2D vector. Create one with vec2(x, y). It has the same operators as vec3, including the component-wise v * other. It has no cross and no length_2d.
| Method | Returns | Description |
|---|---|---|
v:length() | number | Magnitude |
v:length_sqr() | number | Squared magnitude (cheaper than length()) |
v:normalized() | vec2 | Unit vector |
v:dot(other) | number | Dot product |
v:distance(other) | number | Distance to another point |
v:angle_to(other) | number | Direction from v to other in degrees, -180 to 180 (atan2). Not the angle between two vectors, unlike vec3 |
v:empty() | boolean | True if both components are zero |
modifier_flag
Status flags you can check with player:has_modifier_flag(...). The table below lists the ones most scripts use. For the full list, see Modifier Flags Reference.
if player:has_modifier_flag(modifier_flag.STUNNED) then ... endUse the name, not the number. The numbers change when a game update adds new states. A name the game removed reads as nil, and has_modifier_flag(nil) returns false.
There is no string shorthand for modifier_flag checks. player:is_(flag) is a different check that takes an integer player_flag bitmask, such as player_flag.onground, and passing it a string throws a Lua error. To check a modifier by name, use player:has_modifier("stunned") instead.
| Flag | Value |
|---|---|
IMMOBILIZED | 13 |
DISARMED | 14 |
MUTED | 15 |
ITEMS_DISABLED | 16 |
SILENCED | 17 |
SILENCE_MOVEMENT_ABILITES | 18 |
STUNNED | 20 |
INVULNERABLE | 21 |
STATUS_IMMUNE | 26 |
UNSTOPPABLE | 27 |
COMMAND_RESTRICTED | 30 |
CHARGING | 31 |
OBSCURED | 32 |
INVISIBLE_TO_ENEMY | 33 |
INVISIBLE_TO_ENEMY_CAST | 34 |
SPRINTING | 38 |
UNKILLABLE | 39 |
IN_SHOP | 50 |
IN_FOUNTAIN | 51 |
DASH_DISABLED | 57 |
BURNING | 59 |
SLOWED | 66 |
SHOOTING_DISABLED | 67 |
SLIDING | 70 |
VISIBLE_TO_ENEMY | 73 |
IS_ASLEEP | 80 |
USING_ZIPLINE | 88 |
BULLET_INVULNERABLE | 95 |
MELEE_DISABLED | 110 |
GLITCHED | 113 |
RELOAD_DISABLED | 117 |
FLYING | 124 |
SCOPED | 125 |
VISCOUS_CUBED | 127 |
IN_COMBAT | 168 |
YAMATO_SHADOW_FORM | 171 |
FROZEN | 203 |
PARRY_ACTIVE | 229 |
WEREWOLF | 269 |
Caveat
Not every flag is set in every situation. STUNNED fires for almost every hard stun (Knockdown, Cursed Relic, Dynamo ult), and INVISIBLE_TO_ENEMY fires for Smoke Bomb and Shadow Weave. Other flags may not fire even when the player is clearly in that state. For example:
SLIDINGmay not be set while a player is slidingUSING_ZIPLINEmay not be set while a player is on a ziplineIS_IN_CHARGE_MELEEmay not be set during a heavy charged meleeSLOWEDandBURNINGare inconsistent. Some sources set them, but others apply the effect through stat modifiers and never set the flag
Some flags are unreliable, so test a flag before you rely on it. Use the Debugging Modifiers workflow to confirm it flips. If it does not, fall back to player:get_modifiers() by name or token.
slot
Ability, item, and action slot indexes.
| Constant | Value | Description |
|---|---|---|
slot.ability1 | 0 | Hero ability 1 |
slot.ability2 | 1 | Hero ability 2 |
slot.ability3 | 2 | Hero ability 3 |
slot.ability4 | 3 | Ultimate |
slot.item1 | 4 | Active item 1 |
slot.item2 | 5 | Active item 2 |
slot.item3 | 6 | Active item 3 |
slot.item4 | 7 | Active item 4 |
slot.held | 8 | Currently-held / channeled action |
slot.zipline | 9 | Zipline ride |
slot.mantle | 10 | Mantle (ledge grab) |
slot.climb_rope | 11 | Climb rope |
slot.jump | 12 | Jump |
slot.slide | 13 | Slide |
slot.teleport | 14 | Teleport |
slot.zipline_boost | 15 | Zipline boost |
slot.cosmetic1 | 16 | Cosmetic slot 1 |
slot.innate1 | 17 | Innate ability 1 |
slot.innate2 | 18 | Innate ability 2 |
slot.innate3 | 19 | Innate ability 3 |
slot.weapon_secondary | 20 | Secondary weapon |
slot.weapon_primary | 21 | Primary weapon |
slot.weapon_melee | 22 | Melee weapon |
Slots 0-3 are abilities, 4-7 are items, 8-22 are action/weapon slots. for i = slot.ability1, slot.ability4 do ... end iterates 0,1,2,3 and for i = slot.item1, slot.item4 do ... end iterates 4,5,6,7.
local key = slot_to_key(slot.ability3)
input.press_key(key)bone
String constants for player:bone_pos(...) and the targeting helpers. Bones are read from Deadlock's game files at startup. Custom mods and Deadlock Mod Manager are supported. Bones from a custom model are read when the model uses standard VPK skeleton names.
local head_pos = player:bone_pos("head") -- string literal works
local head_pos = player:bone_pos(bone.head) -- bone.head == "head"
local head_pos = player:bone_pos(7) -- raw integer index, legacyNames are case-insensitive: "arm_upper_L" and "arm_upper_l" both resolve to the same bone.
Named aliases
These standard aliases map to VPK bones with fallback chains. If a hero's model does not have the primary bone, the alias falls back to the next one in the chain.
| Alias | Primary bone | Fallback chain |
|---|---|---|
bone.head | head | (exact only) |
bone.neck | neck_0 | (exact only) |
bone.chest | spine_3 | spine_2 → spine_1 → spine_0 |
bone.lower_chest | spine_2 | spine_1 → chest result |
bone.stomach | spine_0 | spine_1 |
bone.pelvis | pelvis | (exact only) |
Body bones
All standard humanoid body bones are available as bone.* constants. They have no fallback chain. If the model does not have the bone, bone_pos returns the player's origin (the feet) instead, so check player:bone_names() when it matters. That list keeps the model's own spelling, such as arm_upper_L, so compare names in lower case.
| Constant | Value |
|---|---|
bone.clavicle_l, bone.clavicle_r | "clavicle_l", "clavicle_r" |
bone.arm_upper_l, bone.arm_upper_r | "arm_upper_l", "arm_upper_r" |
bone.arm_lower_l, bone.arm_lower_r | "arm_lower_l", "arm_lower_r" |
bone.hand_l, bone.hand_r | "hand_l", "hand_r" |
bone.leg_upper_l, bone.leg_upper_r | "leg_upper_l", "leg_upper_r" |
bone.leg_lower_l, bone.leg_lower_r | "leg_lower_l", "leg_lower_r" |
bone.ankle_l, bone.ankle_r | "ankle_l", "ankle_r" |
bone.ball_l, bone.ball_r | "ball_l", "ball_r" |
To list every bone on a model at runtime, use player:bone_names(). The bone set varies between heroes. Some have extra bones such as ears, tails or weapon bones.
Raw spine bones
The aliases above (bone.chest, bone.lower_chest, bone.stomach) map to spine bones with fallback chains. To target one vertebra with no fallback, use the raw bone constants:
| Constant | Value |
|---|---|
bone.spine_0 | "spine_0" (stomach) |
bone.spine_1 | "spine_1" |
bone.spine_2 | "spine_2" (lower chest) |
bone.spine_3 | "spine_3" (chest) |
bone.neck_0 | "neck_0" (neck) |
Notes
- Bone names are only looked up on enemies. On a teammate, names other than
headusually return the player's origin.headstill returns the head, and a raw integer index still works. - See Debugging Modifiers for the in-menu bone label overlay that shows every bone on a hero in real time.
hero_id
Hero IDs, used by the top-level id global and returned by player:get_hero_id().
id = hero_id.any -- script runs regardless of which hero is selected
-- Gate logic on a specific hero at runtime:
local lp = local_player()
if lp and lp:get_hero_id() == hero_id.haze then ... end| Constant | Value |
|---|---|
hero_id.any | -1 |
hero_id.none | 0 |
hero_id.infernus | 1 |
hero_id.seven | 2 |
hero_id.vindicta | 3 |
hero_id.ladygeist | 4 |
hero_id.abrams | 6 |
hero_id.wraith | 7 |
hero_id.mcginnis | 8 |
hero_id.paradox | 10 |
hero_id.dynamo | 11 |
hero_id.kelvin | 12 |
hero_id.haze | 13 |
hero_id.holliday | 14 |
hero_id.bebop | 15 |
hero_id.calico | 16 |
hero_id.greytalon | 17 |
hero_id.moandkrill | 18 |
hero_id.shiv | 19 |
hero_id.ivy | 20 |
hero_id.warden | 25 |
hero_id.yamato | 27 |
hero_id.lash | 31 |
hero_id.viscous | 35 |
hero_id.pocket | 50 |
hero_id.mirage | 52 |
hero_id.dummy | 55 |
hero_id.viper | 58 |
hero_id.vyper | 58 |
hero_id.sinclair | 60 |
hero_id.mina | 63 |
hero_id.drifter | 64 |
hero_id.venator | 65 |
hero_id.victor | 66 |
hero_id.paige | 67 |
hero_id.doorman | 69 |
hero_id.billy | 72 |
hero_id.graves | 76 |
hero_id.apollo | 77 |
hero_id.rem | 79 |
hero_id.silver | 80 |
hero_id.celeste | 81 |
hero_id.viper and hero_id.vyper have the same value (58). IDs are not sequential, and some numbers in the range are not assigned. To find the ID of a hero that is not listed yet, run print(player:hero_name(), player:get_hero_id()).
bucket
Item upgrade bucket categories. An active item's bucket field always reads 0 for now, because the game stopped sending it, so don't filter items by it.
| Constant | Value | Description |
|---|---|---|
bucket.innate | 0 | Innate / passive |
bucket.weapon | 1 | Weapon |
bucket.vitality | 2 | Vitality |
bucket.spirit | 3 | Spirit |
local me = local_player()
if me then
for _, item in ipairs(me:get_active_items()) do
print(item.name, item.cooldown)
end
endmodifier_token
Named subclass token IDs for well-known modifiers. Pass one to player:has_modifier(token). To compare one with a modifier's token field, mask it first: m.token == (modifier_token.mo_burrow & 0xFFFFFFFF). Four of them (bebop_sticky_bomb, paige_captivating_read, viktor_rebirth and mo_burrow) read as negative numbers in Lua, and the token field is never negative.
if player:has_modifier(modifier_token.knockdown) then
print("target is knocked down")
end| Constant | Value |
|---|---|
modifier_token.knockdown | 0x4ABFEA98 |
modifier_token.bebop_sticky_bomb | 0x96512C8E |
modifier_token.lash_death_slam | 0x17A9E77A |
modifier_token.warden_binding_word | 0x62C27FFE |
modifier_token.paige_captivating_read | 0xB6177F0A |
modifier_token.seven_static_charge | 0x400ED943 |
modifier_token.doorman_call_bell | 0x062B3CCC |
modifier_token.viktor_rebirth | 0xD04073B3 |
modifier_token.calico_cat | 0x3C249027 |
modifier_token.mo_burrow | 0x8F74398D |
modifier_token.rem_ability | 0x5D82ADA5 |
player_flag
Player entity flags (FL_* bitfield). Test with player:is_(flag). Values are bitmasks (1<<N), not raw bit indices. Do not pass them to has_modifier_flag.
if player:is_(player_flag.onground) then
print("player is on the ground")
end| Constant | Value | Bit |
|---|---|---|
player_flag.onground | 1 | 1<<0 |
player_flag.ducking | 2 | 1<<1 |
player_flag.waterjump | 4 | 1<<2 |
player_flag.frozen | 32 | 1<<5 |
player_flag.atcontrols | 64 | 1<<6 |
player_flag.client | 128 | 1<<7 |
player_flag.fakeclient | 256 | 1<<8 |
player_flag.fly | 16384 | 1<<14 |
player_flag.godmode | 32768 | 1<<15 |
player_flag.notarget | 65536 | 1<<16 |
player_flag.aimtarget | 131072 | 1<<17 |
player_flag.staticprop | 262144 | 1<<18 |
player_flag.grenade | 524288 | 1<<19 |
player_flag.donttouch | 1048576 | 1<<20 |
player_flag.basevelocity | 2097152 | 1<<21 |
player_flag.worldbrush | 4194304 | 1<<22 |
player_flag.object | 8388608 | 1<<23 |
player_flag.onfire | 33554432 | 1<<25 |
player_flag.dissolving | 67108864 | 1<<26 |
player_flag.transragdoll | 134217728 | 1<<27 |
player_flag.unblockable_by_player | 268435456 | 1<<28 |
VK codes (common)
Windows virtual-key codes for input.*. The full list is in Microsoft's docs.
The VK table has named constants, so you don't have to remember raw hex values:
-- Named constants
input.is_key_held(VK.LBUTTON) -- left mouse
input.is_key_held(VK.SHIFT) -- Shift
input.is_key_held(VK.F) -- F key
input.is_key_held(VK.LCONTROL) -- Left Ctrl
-- Raw hex works too
input.is_key_held(0x01)| Constant | Code | Key |
|---|---|---|
VK.LBUTTON | 0x01 | Left mouse |
VK.RBUTTON | 0x02 | Right mouse |
VK.MBUTTON | 0x04 | Middle mouse |
VK.XBUTTON1 | 0x05 | Mouse 4 |
VK.XBUTTON2 | 0x06 | Mouse 5 |
VK.RETURN | 0x0D | Enter / Return |
VK.SHIFT | 0x10 | Shift |
VK.CONTROL | 0x11 | Ctrl (either) |
VK.ESCAPE | 0x1B | Escape |
VK.SPACE | 0x20 | Space |
VK.KEY_0-VK.KEY_9 | 0x30-0x39 | Digit keys 0-9 |
VK.A-VK.Z | 0x41-0x5A | A-Z |
VK.F1-VK.F12 | 0x70-0x7B | F1-F12 |
VK.LCONTROL | 0xA2 | Left Ctrl |
VK.BACK | 0x08 | Backspace |
VK.TAB | 0x09 | Tab |
VK.MENU | 0x12 | Alt (either) |
VK.CAPITAL | 0x14 | Caps Lock |
VK.PRIOR, VK.NEXT | 0x21, 0x22 | Page Up, Page Down |
VK.END, VK.HOME | 0x23, 0x24 | End, Home |
VK.LEFT, VK.UP, VK.RIGHT, VK.DOWN | 0x25-0x28 | Arrow keys |
VK.INSERT, VK.DELETE | 0x2D, 0x2E | Insert, Delete |
VK.NUMPAD0-VK.NUMPAD9 | 0x60-0x69 | Numpad 0-9 |
VK.LSHIFT, VK.RSHIFT | 0xA0, 0xA1 | Left and right Shift |
VK.RCONTROL | 0xA3 | Right Ctrl |
VK.LMENU, VK.RMENU | 0xA4, 0xA5 | Left and right Alt |
VK.VOLUME_MUTE | 0xAD | Mute |
VK.VOLUME_DOWN, VK.VOLUME_UP | 0xAE, 0xAF | Volume down, volume up |
VK.MEDIA_NEXT_TRACK | 0xB0 | Next track |
VK.MEDIA_PREV_TRACK | 0xB1 | Previous track |
VK.MEDIA_STOP | 0xB2 | Stop media |
VK.MEDIA_PLAY_PAUSE | 0xB3 | Play / pause media |
VK.LAUNCH_MEDIA_SELECT | 0xB5 | Open the media player |
The media and volume keys work with input.press_key, so a script can control music playing in another app. Like every script key press, they are only sent while the game window is focused and the game's cursor is hidden (no game menu, shop or chat open).