-
Notifications
You must be signed in to change notification settings - Fork 1
Library
Provides a system for tracking simple hand poses and gestures.
Gestures are added with a unique name and a [0-1] curl range for each finger, or nil if the finger shouldn't be taken into consideration.
Gesture:AddGesture("AllFingersNoThumb", 1, 1, 1, 1, 0)Gestures can be removed, including built-in gestures, can be removed to lower a small amount of processing cost, however this might produce undesirable results for other mods using the same gesture script if you plan to use a system like Scalable Init.
Gesture:RemoveGestures({"OpenHand", "ClosedFist"})Recommended usage for tracking gestures is to register a callback function with a given set of conditions:
Gesture:RegisterCallback("start", 1, "ThumbsUp", nil, function(gesture)
---@cast gesture GESTURE_CALLBACK
print(("Player made %s gesture at %d"):format(gesture.name, gesture.time))
end)Generic gesture functions exist for all other times:
local g = Gesture:GetGesture(Player.PrimaryHand)
if g.name == "ThumbsUp" then
print("Player did thumbs up")
end
local g = Gesture:GetGestureRaw(Player.PrimaryHand)
if g.index > 0.5 then
print("Player has index more than half extended")
endHaptic sequences allow for more complex vibrations than the one-shot pulses that the base API provides.
A HapticSequence is created with a total duration, vibration strength, and pulse interval. The following sequence lasts for 1 second and vibrates at half strength every 10th of a second. This will pulse 10 times in total.
local hapticSeq = HapticSequence(1, 0.5, 0.1)After creating the sequence we can fire it at any point. If the sequence is fired again before finishing it will cancel the currently running sequence and start again.
hapticSeq:Fire()Simplifies the tracking of button presses/releases
By default the input system is set to turn on by default.
If you want to turn this off and your script is a mod/init script you can simply set the AutoStart property before the player spawns.
Input.AutoStart = falseYou can also start and stop the system at any point.
-- If you don't provide a thinking entity (as below) the player MUST exist when calling Start
Input:Start()
-- All tracking will stop immediately, but registered listeners will still exist when you start the system again
Input:Stop()Note
As of version v3.0.0 you no longer need to track buttons in order to listen to them.
Common usage is to register a callback function which will fire whenever the conditions are met.
---Print a message every time the primary hand presses the grenade arm button 3 times in a row
---@param params INPUT_PRESS_CALLBACK
Input:ListenToButton("press", INPUT_HAND_PRIMARY, DIGITAL_INPUT_ARM_GRENADE, 3, function(params)
print(("Button %s pressed at %.2f on %s"):format(
Input:GetButtonDescription(params.button),
params.press_time,
Input:GetHandName(params.hand)
))
end)
---Print a message whenever a fire trigger has been released after being held for at least 1 second
---@param params INPUT_RELEASE_CALLBACK
Input:ListenToButton("release", INPUT_HAND_BOTH, DIGITAL_INPUT_FIRE, nil, function(params)
if params.held_time >= 1 then
print(("Button %s charged for %d seconds on %s"):format(
Input:GetButtonDescription(params.button),
params.held_time,
Input:GetHandName(params.hand)
))
end
end)
-- You can also give any context value which will be sent as the first argument to the callback function
---@param params INPUT_PRESS_CALLBACK
function thisEntity:Callback(params)
print(self:GetName() .. " pressed button " .. Input:GetButtonDescription(params.button))
end
Input:ListenToButton("press", INPUT_HAND_BOTH, DIGITAL_INPUT_ARM_GRENADE, 1, thisEntity.Callback, thisEntity)Important
General button request functions were removed in v3.0.0 These might return in a future update.
Easy modification of colors between RGB and HSL systems
This class registers with
Storagefor easy saving/loading
-- Create a red color with full alpha
local red = Color(255, 0, 0, 255)
-- Or by just providing the red. Blue and green will implicitly be 0, and alpha will implicitly be 255
red = Color(255)
-- Same implicit values but with green
local green = Color(nil, 255)
-- Make the color 50% darker
green:SetHSL(nil, nil, green.lightness * 0.5)
-- Get/Set any value individually
green.r = 128 -- Accepts a range of [0-255]
green.g = 0 -- Accepts a range of [0-255]
green.b = 255 -- Accepts a range of [0-255]
green.a = 0 -- Accepts a range of [0-255]
green.hue = 300 -- Accepts a range of [0-360]
green.saturation = 50 -- Accepts a range of [0-100]%
green.lightness = 25 -- Accepts a range of [0-100]%
-- Get the hexadecimal version of a color
print(green:ToHexString())
-- Check if any value is an instance of the color class
if IsColor(green) then
print("Is a color")
endAn inventory is a table where each key has an integer value assigned to it. When a value hits 0 the key is removed from the table.
This class registers with
Storagefor easy saving/loading
-- The inventory table may use any type that Lua allows as a key, including other tables, but the value part MUST be a number type
-- Create an inventory with 2 initial keys.
local inv = Inventory({
gun = 1,
metal = 4
})
-- Remove 1 from metal, returns the new value after removal
print(inv:Remove("metal")) -- Prints "3"
-- Add 3 to gun, returns the new value after adding
print(inv:Add("gun", 3)) -- Prints "4"
-- Get the highest key/value pair in the inventory
local key, val = inv:Highest()
print(key, val) -- Prints "gun 4"
-- To loop over the items you can reference the inventory `items` property directly
for key, value in pairs(inv.items) do
print(key, value)
end
-- Or use the `pairs` helper method:
for key, value in inv:pairs() do
print(key, value)
endThis class supports storage with Storage.SaveInventory
Inventories are also natively saved using Storage.Save() or if encountered in a table being saved.
Storage:SaveInventory('inv', inv)
inv = Storage:LoadInventory('inv')A queue is a data structure where items are added at one end and removed from the other, so the first item added is the first one taken out.
This class registers with
Storagefor easy saving/loading
-- Create a queue with 3 initial values.
-- 3 is the front of the queue.
local queue = Queue(1, 2, 3)
-- Remove the item from the front of the queue and return it
print(queue:Dequeue()) -- Prints "3"
-- Remove multiple values at a time
local a, b = queue:Dequeue(2)
print(a, b) -- Prints "2 1"
-- Add any values to the queue in the order given
stack:Enqueue('a', 'b', 'c') -- Queue is now { 'a', 'b', 'c' }
-- To loop over the items you can reference the `items` property directly
for index, value in ipairs(queue.items) do
print(index, value)
end
-- Or use the `pairs` helper method
for index, value in queue:pairs() do
print(index, value)
endThis class supports storage with Storage.SaveQueue.
Queues are also natively saved using Storage.Save() or if encountered in a table being saved.
Storage:SaveQueue('queue', queue)
queue = Storage:LoadQueue('queue')A stack is a data structure where elements are added to the top and removed from the top, with the most recently added item being the first to be removed.
This class registers with
Storagefor easy saving/loading
-- Create a stack with 3 initial values.
-- 1 is the top of the stack.
local stack = Stack(1, 2, 3)
-- Pop the top value off the stack and return it
print(stack:Pop()) -- Prints "1"
-- Pop multiple values and return them
local a, b = stack:Pop(2)
print(a, b) -- Prints "2 3"
-- Push any values onto the stack in the order given
stack:Push('a', 'b', 'c') -- Stack is now has 'a' at the top and 'c' at the bottom
-- To loop over the items you can reference the `items` property directly
for index, value in ipairs(stack.items) do
print(index, value)
end
-- Or use the `pairs` helper function
for index, value in stack:pairs() do
print(index, value)
endThis class supports storage with Storage.SaveStack.
Stacks are also natively saved using Storage.Save or if encountered in a table being saved.
Storage:SaveStack('stack', stack)
stack = Storage:LoadStack('stack')Common debug utility functions to make testing easier
-- Print a list of entities and any properties desired, including their parent relationships
Debug.PrintEntityList(Entities:FindAllByClassname("prop_physics"))
-- Or provide your own search properties to display
Debug.PrintEntityList(
Entities:FindAllByClassname("prop_physics"),
-- The each property must be a variable or function which takes no arguments and returns a value
{ "GetClassname", "GetEntityIndex", "GetOrigin" }
)
-- A comprehensive table printer listing keys, values and types, including nested tables
Debug.PrintTable(_G)
-- You can also print any meta tables discovered and ignore tables you don't want to see
Debug.PrintTable(
thisEntity, -- Table to print
nil, -- No set prefix
{complexMetaTable}, -- Ignore this list of tables
true -- Print metatables
)
-- Neatly print the first level of a table only
Debug.PrintTableShallow(_G)
-- Print an ordered list with a number before each value
Debug.PrintList({1, 2, 3, 4})
-- Draw a line to an entity in-game to help locate it during testing for 10 seconds
Debug.ShowEntity(thisEntity, 10)
-- Print all criteria for an entity. Short-hand for using GatherCriteria and a for-loop
Debug.PrintEntityCriteria(thisEntity)
-- Print all base criteria for an entity, that is criteria which was not added by storage.lua
Debug.PrintEntityBaseCriteria(thisEntity)
-- Print a visual graph of key/value pairs
Debug.PrintGraph(6, 0, 1, {
val1 = RandomFloat(0, 1),
val2 = RandomFloat(0, 1),
val3 = RandomFloat(0, 1)
})
--- Example output:
--- 1^ []
--- | [] []
--- | [] [] []
--- | [] [] []
--- | [] [] []
--- 0 ---------->
--- v v v
--- a a a
--- l l l
--- 3 1 2
--- val3 = 0.96067351102829
--- val1 = 0.5374761223793
--- val2 = 0.7315416932106Allows quick debugging of VR controllers.
Currently this script only creates new console commands.
See VR Controller Commands wiki section
Adds functions and console commands to help debugging outside VR mode.
See No-VR Commands wiki section
See No-VR Debugging wiki page
Adds console commands to help debugging VR specific features.
Entity modifications are scripts which can be attached to an existing Half-Life Alyx entity to change the way it works.
To attach a script to your entity write the name of it in the property Misc > Entity Scripts with the format entity_mods/script_name, e.g. entity_mods/trigger_look_arbitrary
Works with: trigger_look
Attaching this script to your trigger_look entity will augment it to allow testing the look from any given class or targetname.
Because there is no easy way to hijack the actual outputs, this script instead fires the OnUser* outputs. This means it is important to remember to NOT use the OnStartLook, OnEndLook, or OnTrigger outputs, as these are still tied to the player eyes. However this also means you can test from the player eyes AND another entity at the same time.
The following OnUser* outputs are called:
- OnUser1 - Equivalent to OnStartLook, fired only once when the entity looks at the target.
- OnUser2 - Equivalent to OnEndLook, fired only once when the entity looks away from the target.
- OnUser3 - Equivalent to OnTrigger, fired every "Look Time" seconds while looking at the target.
Every OnUser* output gets the target entity that was looked at, passed into !activator. This is generally only useful when you have multiple targets and are using CheckAllTargets.
It is recommended that you untick the "Fire Once" spawnflag because when the player looks at the target it will disable the trigger. Instead you should disable the trigger through other means such as OnUser1.
Several custom keys can be added to your trigger_look to set the behaviour. To add a key click the "+ Add Key" button at the top of the object properties.

| Name | Type | Description |
|---|---|---|
| LookFromClass | (string) | The classname of the entity to do the look test from. This may be omitted if you are using LookFromName. |
| LookFromName | (string) | The targetname of the entity to do the look test from. This property takes precedence over LookFromClass. This may be omitted if you are using LookFromClass. |
| LookFromAttachment | (string) | The attachment name on the model to look from. This is both the origin and forward direction. This may be omitted if you are not using an attachment. |
| CheckAllTargets | (integer) | Set to 1 to allow multiple targets with the same name to be checked at once. The first one to be looked at will be passed as the !activator when OnUser* is fired. This may be omitted if you don't want to use it. |
You can set the above properties in-game as well as in Hammer. Changes made in-game are persistent with game save/load. Send an input of RunScriptCode to your trigger_look with a parameter override as one of the following:
- SetLookFromClass('class_name_here')
- SetLookFromName('target_name_here')
- SetLookFromAttachment('attach_name_here')
- SetCheckAllTargets(true or false)
Examples:
SetLookFromClass('hlvr_flashlight_attachment')
SetLookFromName('flashlight')
SetLookFromAttachment('light_attach')
SetCheckAllTargets(true)
For in-game debugging purposes you can toggle debug visuals for the current session by calling the function ToggleDebug on any trigger_look with the script attached.
CallPrivateScriptFunction -> ToggleDebug
Extension scripts add functionality to existing classes/tables.
Extensions for the
Entitiesclass.
-- Get all entities in the map as a list
local allEnts = Entities:All()
-- If you are planning to loop through all entities it might be better to use Entities:First and Entities:Next for memory reasons
-- Get a random entity in the map.
local randomEnt = Entities:Random()
-- Find an entity within the same prefab as another where name fix-up occurs
local relay = Entities:FindInPrefab(thisEntity, "associated_relay")
relay:EntFire("Trigger")
-- Also exists as an entity method
local relay = thisEntity:FindInPrefab("associated_relay")
-- Find all entities in a cone
local coneEnts = Entities:FindAllInCone(
Player:EyePosition(), -- start cone at player
Player:EyeAngles():Forward(), -- aim cone from player
2048, -- max distance
90, -- cone angle [0-180]
true -- check entity bounds instead of just origin
)
-- Find all entities within a bounding box
local boundEnts = Entities:FindAllInBounds(thisEntity:GetBoundingMins(), thisEntity:GetBoundingMaxs())
-- Find all entities within a box of width, length, height
local boxEnts = Entities:FindAllInBounds(thisEntity:GetOrigin(), 32, 32, 32)
-- Find all entities within a cube which has the same size on all axis
local cubeEnts = Entities:FindAllInCube(thisEntity:GetOrigin(), 32)
-- Find the nearest entity to a world position of any type
local nearestEnt = Entities:FindNearest(thisEntity:GetOrigin(), 512)
-- Find entities using a list of classnames
local mixedEnts = Entities:FindAllByClassnameList({ "npc_combine_s", "prop_physics", "prop_physics_overload" })
-- Find entities within a radius using a list of classnames
local mixedEnts = Entities:FindAllByClassnameListWithin({ "npc_combine_s", "prop_physics", "prop_physics_overload" }, Player:GetOrigin(), 512)
-- Find nearest entity to a position using a list of classnames
local nearestEnt = Entities:FindByClassnameListNearest({ "npc_combine_s", "prop_physics", "prop_physics_overload" }, Player:GetOrigin(), 512)
-- Find all entities which are marked is NPCs
local npcs = Entities:FindAllNPCs()
-- Find all entities within a radius which have a specific model
local modelEnts = Entities:FindAllByModelWithin("models/props/beer_bottle_1.vmdl", Player:GetOrigin(), 512)
-- Find the nearest entity to a world position which has a specific model
local nearestEnt = Entities:FindByModelNearest("models/props/beer_bottle_1.vmdl", Player:GetOrigin(), 512)
-- Find the first entity whose model name contains `namePattern`
-- This adds wildcard searching for models
local modelEnt = Entities:FindByModelPattern("bottle_1")
-- Find all entities whose model name contains `namePattern`
local modelEnts = Entities:FindAllByModelPattern("bottle_1")Provides base entity extension methods.
-- Get the entities parented to an entity. Including children of children.
--
-- This is a memory safe version of GetChildren() which causes a memory leak when called.
-- If you need to get children often you should use this function instead.
local children = thisEntity:GetChildrenMemSafe()
-- Get the top level entities parented to an entity. Not children of children.
--
-- This function is memory safe.
local topChildren = thisEntity:GetTopChildren()
-- Send an input to an entity without the need to provide all the parameters
thisEntity:EntFire("Kill")
logicCase:EntFire("InValue", 1, 2.5)
relay:EntFire("Trigger", nil, nil, Player, Player)
-- Find the first child with a given classname
local glove = Player.RightHand:GetFirstChildWithClassname("hlvr_prop_renderable_glove")
-- Find the first child with a given targetname
local handle = grenade:GetFirstChildWithName("grenade_handle")
-- Set the entity angles from a QAngle instead of each axis
local angles = thisEntity:GetAngles()
thisEntity:SetAngles(thisEntity:GetAngles())
-- VS
thisEntity:SetAngles(angles.x, angles.y, angles.z)
-- Set the local (parented) angles from a QAngle instead of each axis
-- Same as above
thisEntity:SetLocalQAngle(thisEntity:GetLocalAngles())
-- Set one or more angle axis values
thisEntity:SetAngle(90) -- pitch only
thisEntity:SetAngle(nil, nil, 45) -- roll only
-- Reset the local origin and angles
thisEntity:ResetLocal()
-- VS
thisEntity:SetLocalOrigin(Vector())
thisEntity:SetLocalAngles(0, 0, 0)
-- Get the bounding size of an entity
local sizeVector = thisEntity:GetSize()
-- Get the biggest bounding axis from x, y, or z
-- Can be used to figure out the maximum space an entity will take up in all directions as it rotates
local biggestBound = thisEntity:GetBiggestBounding()
-- Get the radius of the entity bounding box. This is half the size of the sphere
local radius = thisEntity:GetRadius()
-- Get the volume of the entity bounds in inches cubed
local volume = thisEntity:GetVolume()
-- Get a list of world positions of each corner (8 corners) of the entity's bounding box
local corners = thisEntity:GetBoundingCorners()
-- Can also rotate the corners with the entity's current angle
local rotatedCorners = thisEntity:GetBoundingCorners(true)
-- Check if entity is within the given worldspace bounds
if thisEntity:IsWithinBounds(Player:GetBoundingMins(), Player:GetBoundingMaxs()) then end
-- Fire the 'DisablePickup' input
prop:DisablePickup()
-- Fire the 'EnablePickup' input
prop:EnablePickup()
-- Delay some code for the entity
thisEntity:Delay(function()
if Player.RightHand.ItemHeld == thisEntity then
thisEntity:Kill()
end,
2 -- two seconds
)
-- Get all parents in the hierarchy upwards
-- The entity in the last index will be the same as thisEntity:GetRootMoveParent
local parents = thisEntity:GetParents()
-- Set if the physics prop can be dropped by the player
sword:DoNotDrop(true)
-- Get criteria for an entity, instead of creating a table first
Debug.PrintTable(thisEntity:GetCriteria())
-- VS
local criteria = {}
thisEntity:GatherCriteria(criteria)
Debug.PrintTable(criteria)
-- Get all entities which are owned by this entity
local iOwn = thisEntity:GetOwnedEntities()
-- Set the render alpha of an entity and all children
thisEntity:SetRenderAlphaAll(0)
-- Move an entity to a new position but at the center instead of origin
thisEntity:SetCenter(Player:EyePosition())
-- Track an entity property to call a function when it changes
thisEntity:TrackProperty(thisEntity.GetRenderAlpha, function(oldVal, newVal)
if newVal <= 0 then
thisEntity:Kill()
end
end)
-- Stop tracking a property
thisEntity:UntrackProperty(thisEntity.GetRenderAlpha)
-- Quickly set up a think function
-- This is different to Delay in that you can't specify a delay and you can specify a think interval
thisEntity:QuickThink(function()
if thisEntity:GetRenderAlpha() <= 0 then
thisEntity:Kill()
return
end
return 0.1
end)
-- Set whether an entity is rendered
thisEntity:SetRenderingEnabled(false)
-- Set whether an entity casts shadows
thisEntity:SetCastShadow(false)Extension for NPC entities. Some functions are specific to only one entity class such as
npc_combine_s, check the individual function descriptions.
-- Create an aiscripted_schedule for the entity
local newSched = npc:StartSchedule(Schedule.State.Alert, Schedule.Type.RunToGoalEntity, Schedule.Interruptability.DamageOrDeath, false, Player:GetOrigin())
-- Stops a previously created schedule
npc:StopSchedule(newSched)
-- Set an npc state
npc:SetState(Schedule.State.Combat)
-- Check if npc has an enemy target
if npc:HasEnemyTarget() then end
-- Set the relationship of npc with another entity
npc:SetRelationship(Player, "D_HT")
-- Get if an NPC is a creature, e.g. combine, headcrab, player
if npc:IsCreature() then end
-- Get if an NPC is a combine creature
if npc:IsCombine() then end
-- Get if an NPC is a Xen/alien creature
if npc:IsXen() then endProvides QAngle class extension methods.
-- Multiply two QAngles
local new = qangle1 * qangle2
-- Divide two QAngles
local new = qangle1 / qangle2
-- Subtract two QAngles
local new = qangle1 - qangle2Provides string class extension methods.
local str = "one, two, three"
str:startswith("one") -- true
str:endswith("four") -- false
-- Split a string by a separator
str:split(",") -- returns { "one", " two", " three" }
-- Split a string by whitespace (default)
string.split("foo bar") -- returns { "foo", "bar" }
-- Split a string using a custom pattern
string.splitraw("Hell1o 2wor3ld", "%d") -- returns { "Hell", "o", "wor", "ld" }
-- Truncate a string to a maximum length
string.truncate("This is too long", 7) -- returns "This..."
-- Truncate a string to a maximum length with a custom suffix
string.truncate("This is too long", 7, "") -- returns "This is"
-- Trim a string from the left up to the last occurrence of a character
str:trimleft(",") -- returns " three"
-- Trim a string from the right up to the first occurrence of a character
str:trimright("three") -- returns "one, two, "
-- Capitalize a string
str:capitalize() -- returns "ONE, TWO, THREE"
-- Capitalize first letter
str:capitalize(true) -- returns "One, two, three"Provides point_template extension methods.
ForceSpawnAtCaller
Adds a function that exists on all point_template entities to spawn the template at another entity's location, similar to ForceSpawnAtEntityOrigin for env_entity_maker.
Send the input CallScriptFunction to your template with the parameter override ForceSpawnAtCaller. The entity that called the input will be the spawn location (the !caller).

You may also call it from code
template:ForceSpawnAtCaller({
caller = Player -- caller is the entity spawn location
})Provides Vector class extension methods.
-- Get a perpendicular vector
local perpendicular = Player:EyeAngles():Forward():Perpendicular()
-- Check if vectors are perpendicular
Player:GetForwardVector():IsPerpendicularTo(Player:GetRightVector()) -- true
Player:GetForwardVector():IsPerpendicularTo(Player:GetForwardVector()) -- false
-- Check if vectors are parallel
Player:GetForwardVector():IsParallelTo(Player:GetRightVector()) -- false
Player:GetForwardVector():IsParallelTo(Player:GetForwardVector()) -- true
-- Slerp (spherical linear interpolate) one vector to another
-- This makes the entity look in the same direction as the player smoothly
local fromDir = thisEntity:GetForwardVector()
local toDir = Player:EyeAngles():Forward()
local newDir = fromDir:Slerp(toDir, 0.1)
thisEntity:SetForwardVector(newDir)
-- Translate a vector within any local space (instead of world space)
local pos = thisEntity:TransformPointEntityToWorld(Vector(32, 0, 0))
-- Is the same as
local pos = thisEntity:GetOrigin():TranslateLocal(
Vector(32, 0, 0),
thisEntity:GetForwardVector(),
thisEntity:GetRightVector(),
thisEntity:GetUpVector()
)
-- Get the smallest angle difference in degrees between two vectors
Vector(1, 0, 0):AngleDiff(Vector(0, 1, 0)) -- returns 90
-- Get the smallest signed angle difference in degrees between two vectors
Vector(1, 0, 0):AngleDiff(Vector(0, 1, 0)) -- returns 90
Vector(1, 0, 0):AngleDiff(Vector(0, -1, 0)) -- returns -90
-- Unpack a vector into 3 returns
local x, y, z = Vector(1, 2, 3):Unpack()
-- Get the squared length of a vector
Vector(10, 10, 10):LengthSquared() -- returns 300
-- Check if two vectors are essentially the same
Player:GetOrigin():IsSimilarTo(Player:GetOrigin() + Vector(0.000001)) -- true
-- Check vector similarity with relaxed tolerance
Player:GetOrigin():IsSimilarTo(Player:GetOrigin() + Vector(2.0), 2.0) -- true
Player:GetOrigin():IsSimilarTo(Player:GetOrigin() + Vector(2.1), 2.0) -- false
-- Clone a vector
local original = Vector(1, 2, 3)
local clone = original:Clone()
clone.x = 0 -- original.x is still 1Scripts to streamline common tasks.
Provides functionality for animating getter/setter entity methods using curves.
Animating a simple entity property
-- Animate the origin of an entity to the player hand over 1 second
-- The target value does not update after creating the animation
thisEntity:Animate(thisEntity.GetOrigin, thisEntity.SetOrigin, Player.RightHand:GetOrigin(), Animation.Curves.easeOut, 1)Using anonymous getter/setter to animate arbitrary variable
-- This is the value we want to animate
local animatedValue = 0
-- This starts the animation immediately
Animation:Animate(thisEntity,
-- Anonymous getter
function (_)
return animatedValue
end,
-- Anonymous setter
function (_, val)
animatedValue = val
-- do something with value each time it's updated...
thisEntity:SetRenderAlpha(animatedValue)
end,
-- Animate to a value of 255
255,
-- Elastic animation
Animation.Curves.elasticIn,
-- 10 seconds total animation time
10,
-- Callback when animation finishes
function()
print("Animation finished")
end
)Allows for quick creation of convars which support persistence saving, checking GlobalSys for default values, and callbacks on value change.
EasyConvars are useful when you need a convar that performs actions when the value is changed or needs to be persistent with game saves.
Use EasyConvars instead of Convars if you need one of the following:
- Update function on convar value change
- Initializer function to set a default value when the player loads
- Allow user value to be defined in the Steam launch parameters
- Persistent value through game loads
- Toggle commands, e.g.
god,noclip
EasyConvars uses the same naming convention as Convars to make it easy to transition your code over. Extra parameters are added at the end of the function signature.
EasyConvars:RegisterConvar("example_enemy_dmg", "Sets the enemy damage", 15.5,
-- Update function
function (newVal, oldVal)
-- Enforce a specific type
newVal = tonumber(newVal)
if newVal == nil then
-- Tell the user something went wrong
EasyConvars:Warn("Must be valid number!")
-- Keep the old value
return oldVal
end
-- We don't need to return a value because we know the string is a valid number
end)
-- We can get/set the value in the same way as regular convars
EasyConvars:GetStr("example_enemy_dmg") -- Returns "15.5"
EasyConvars:GetBool("example_enemy_dmg") -- Returns true
EasyConvars:GetInt("example_enemy_dmg") -- Returns 15
EasyConvars:GetFloat("example_enemy_dmg") -- Returns 15.5
EasyConvars:SetInt("example_enemy_dmg", 10)It's important to remember to always use the EasyConvars class when getting/setting or interacting with the convar in any way. You cannot use Convars to interact with the value because internally it is a command. Using the EasyConvars:Set* functions will also trigger the update callback if you defined one.
Setting convar persistence is done right after defining the convar.
EasyConvars:RegisterConvar("example_enemy_dmg", ...)
EasyConvars:SetPersistent("example_enemy_dmg", true)Loading is done immediately after the player entity has fully activated. Any update callback will be triggered when loading.
Convars may have an initializer function instead of a default value when you need the value decided and runtime. Initializers get called immediately after the player entity has fully activated if:
- The convar does not have a saved persistent value (only occurs on game load)
- The player has not defined a default value in launch parameters or a CFG file
EasyConvars:RegisterConvar("example_enemy_dmg",
-- Function as default value assigns initializer
function ()
-- Initialize based on skill level
return DefaultTable({
[0] = 5,
[1] = 10,
[2] = 15,
[3] = 20,
}, 5) [Convars:GetInt("skill")]
end,
-- Help string and flags
"Sets the enemy damage", 0,
-- Update function same has previous example
...)By default the convar type will display its current value and help string when the player triggers it without any arguments
] example_enemy_dmg
example_enemy_dmg = 15.5
Sets the enemy damage
You can modify this output or remove it completely by defining a displayFunc callback.
EasyConvars:RegisterConvar("example_enemy_dmg", 15.5, "Enemy damage", 0,
-- Update function
function (newVal, oldVal)
-- Enforce a specific type
newVal = tonumber(newVal)
if newVal == nil then
-- Tell the user something went wrong
EasyConvars:Warn("Must be valid number!")
-- Keep the old value
return oldVal
end
-- We don't need to return a value because we know the string is a valid number
end,
-- Custom display callback
-- reg is the internal data table for the convar
function (reg)
EasyConvars:Msg(reg.name .. " is now " .. tostring(reg.value))
EasyConvars:Msg("Help: " .. reg.desc)
end)Toggle convars work almost exactly the same as regular convars, but instead of displaying a value and help message when called without arguments they will toggle the boolean representation of their value.
You can define an initializer and display override in the same way as regular convars.
EasyConvars:RegisterToggle("example_debug_resin", "0", "Find the nearest resin", 0,
-- Update callback
function (newVal, oldVal)
newVal = truthy(newVal)
local player = Entities:GetLocalPlayer()
if not player then
-- Tell the user something went wrong
EasyConvars:Msg("Player doesn't exist!")
-- Keep the old value
return oldVal
end
if newVal then
-- Start drawing a line
player:SetThink(function ()
local resin = Entities:FindByClassnameNearest("item_hlvr_crafting_currency_small", player:GetOrigin(), 1000)
if resin then
DebugDrawLine(player:GetOrigin(), resin:GetOrigin(), 255,255,255, true, 0.1)
end
return 0.1
end, "example_debug_resin", 0)
else
-- Stop the line drawing
player:StopThink("example_debug_resin")
end
-- We don't need to return a value unless we want to force something, it's already handled
end)EasyConvars:RegisterCommand works the same way as Convars:RegisterCommand and does not have a value or display.
EasyConvars:RegisterCommand("example_set_npc_health", function (health)
health = tonumber(health)
if health == nil then
-- Tell the user something went wrong
EasyConvars:Warn("Must be valid number!")
return
end
for _, npc in ipairs(Entities:FindAllNPCs()) do
EasyConvars:Msg("Setting "..npc:GetName().." health to "..health)
npc:SetHealth(health)
end
end, "Set NPC health", 0)Extends the math library with useful functions.
-- Get the sign of a number
math.sign(-5) -- returns -1
-- Truncate to the specified number of decimal places
math.trunc(5.534) -- returns 5
math.trunc(5.534, 2) -- returns 5.53
-- Round to the nearest integer
math.round(5.534) -- returns 6
math.round(5.3) -- returns 5
-- Check if two numbers are close to each other using a relative/absolute tolerance
math.isclose(5, 5.0001) -- true, within default 0.01% difference
math.isclose(5, 5.0001, 1e-5) -- false, outside 0.001% difference
math.isclose(5, 6, 0.2) -- true, within 20% difference
math.isclose(5, 4, nil, 1) -- true, within 1 fixed difference
math.isclose(5, 4, nil, 0.9) -- false, outside 0.9 fixed difference
-- Check if a number has a fractional part
math.has_frac(5.5) -- true
math.has_frac(5) -- false
-- Get the fractional part of a number
math.get_frac(5.5) -- returns 0.5
math.get_frac(5) -- returns 0Weighted random allows you to assign chances to tables keys.
-- Create a weighted table
local wr = WeightedRandom({
-- Weights can be any number, with larger ones increasing an item's chance relative to others
{ weight = 1, name = "Common" },
{ weight = 0.75, name = "Semi-common" },
{ weight = 0.5, name = "Uncommon" },
{ weight = 0.25, name = "Rare" },
{ weight = 0.1, name = "Extremely rare" },
})
-- Print a random item 20 times to see the weights in action
for i = 1, 20 do
print(wr:Random().name)
endExample of output from above:
Common
Rare
Common
Semi-common
Semi-common
Common
Common
Uncommon
Semi-common
Common
Rare
Uncommon
Rare
Uncommon
Common
Common
Semi-common
Uncommon
Common
Uncommon
See the Panorama wiki page for full details
Extends the player entity class in Half-Life: Alyx, providing a simplified way to access and manipulate player-related entities, making it easier to write player-related code.